Skip to content

Commit

Permalink
feat: lock application/pipeline/environment as code (#4053)
Browse files Browse the repository at this point in the history
  • Loading branch information
sguiheux authored and fsamin committed Mar 25, 2019
1 parent c901936 commit e857bac
Show file tree
Hide file tree
Showing 46 changed files with 349 additions and 114 deletions.
7 changes: 7 additions & 0 deletions engine/api/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,10 @@ func (api *API) updateApplicationHandler() service.Handler {
return sdk.WrapError(errloadbyname, "updateApplicationHandler> Cannot load application %s", applicationName)
}

if app.FromRepository != "" {
return sdk.WithStack(sdk.ErrForbidden)
}

var appPost sdk.Application
if err := service.UnmarshalBody(r, &appPost); err != nil {
return err
Expand Down Expand Up @@ -466,6 +470,9 @@ func (api *API) postApplicationMetadataHandler() service.Handler {
if err != nil {
return sdk.WrapError(err, "postApplicationMetadataHandler")
}
if app.FromRepository != "" {
return sdk.WithStack(sdk.ErrForbidden)
}
oldApp := *app

m := vars["metadata"]
Expand Down
17 changes: 14 additions & 3 deletions engine/api/application/application_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,15 @@ import (
"github.com/ovh/cds/sdk/log"
)

// ImportOptions are options to import application
type ImportOptions struct {
Force bool
FromRepository string
}

// ParseAndImport parse an exportentities.Application and insert or update the application in database
func ParseAndImport(db gorp.SqlExecutor, cache cache.Store, proj *sdk.Project, eapp *exportentities.Application, force bool, decryptFunc keys.DecryptFunc, u *sdk.User) (*sdk.Application, []sdk.Message, error) {
log.Info("ParseAndImport>> Import application %s in project %s (force=%v)", eapp.Name, proj.Key, force)
func ParseAndImport(db gorp.SqlExecutor, cache cache.Store, proj *sdk.Project, eapp *exportentities.Application, opts ImportOptions, decryptFunc keys.DecryptFunc, u *sdk.User) (*sdk.Application, []sdk.Message, error) {
log.Info("ParseAndImport>> Import application %s in project %s (force=%v)", eapp.Name, proj.Key, opts.Force)

//Check valid application name
rx := sdk.NamePatternRegex
Expand All @@ -34,15 +40,20 @@ func ParseAndImport(db gorp.SqlExecutor, cache cache.Store, proj *sdk.Project, e
}

//If the application exist and we don't want to force, raise an error
if oldApp != nil && !force {
if oldApp != nil && !opts.Force {
return nil, nil, sdk.ErrApplicationExist
}

if oldApp != nil && oldApp.FromRepository != "" && opts.FromRepository != oldApp.FromRepository {
return nil, nil, sdk.WrapError(sdk.ErrForbidden, "unable to update as code application %s/%s.", oldApp.FromRepository, opts.FromRepository)
}

//Craft the application
app := new(sdk.Application)
app.Name = eapp.Name
app.VCSServer = eapp.VCSServer
app.RepositoryFullname = eapp.RepositoryName
app.FromRepository = opts.FromRepository

//Compute variables
for p, v := range eapp.Variables {
Expand Down
3 changes: 2 additions & 1 deletion engine/api/application/dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ application.last_modified,
application.metadata,
application.vcs_server,
application.vcs_strategy,
application.description
application.description,
application.from_repository
`

// LoadOptionFunc is a type for all options in LoadOptions
Expand Down
6 changes: 6 additions & 0 deletions engine/api/application_deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ func (api *API) postApplicationDeploymentStrategyConfigHandler() service.Handler
if err != nil {
return sdk.WrapError(err, "unable to load application")
}
if app.FromRepository != "" {
return sdk.WithStack(sdk.ErrForbidden)
}

oldPfConfig, has := app.DeploymentStrategies[pfName]
if !has {
Expand Down Expand Up @@ -144,6 +147,9 @@ func (api *API) deleteApplicationDeploymentStrategyConfigHandler() service.Handl
if err != nil {
return sdk.WrapError(err, "unable to load application")
}
if app.FromRepository != "" {
return sdk.WithStack(sdk.ErrForbidden)
}

isUsed, err := workflow.IsDeploymentIntegrationUsed(tx, proj.ID, app.ID, pfName)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion engine/api/application_import.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func (api *API) postApplicationImportHandler() service.Handler {
}
defer tx.Rollback()

newApp, msgList, globalError := application.ParseAndImport(tx, api.Cache, proj, eapp, force, project.DecryptWithBuiltinKey, deprecatedGetUser(ctx))
newApp, msgList, globalError := application.ParseAndImport(tx, api.Cache, proj, eapp, application.ImportOptions{Force: force}, project.DecryptWithBuiltinKey, deprecatedGetUser(ctx))
msgListString := translate(r, msgList)
if globalError != nil {
globalError = sdk.WrapError(globalError, "Unable to import application %s", eapp.Name)
Expand Down
7 changes: 7 additions & 0 deletions engine/api/application_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ func (api *API) deleteKeyInApplicationHandler() service.Handler {
if errA != nil {
return sdk.WrapError(errA, "deleteKeyInApplicationHandler> Cannot load application")
}
if app.FromRepository != "" {
return sdk.WithStack(sdk.ErrForbidden)
}

tx, errT := api.mustDB().Begin()
if errT != nil {
Expand Down Expand Up @@ -101,6 +104,10 @@ func (api *API) addKeyInApplicationHandler() service.Handler {
}
newKey.ApplicationID = app.ID

if app.FromRepository != "" {
return sdk.WithStack(sdk.ErrForbidden)
}

if !strings.HasPrefix(newKey.Name, "app-") {
newKey.Name = "app-" + newKey.Name
}
Expand Down
9 changes: 9 additions & 0 deletions engine/api/application_variable.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ func (api *API) deleteVariableFromApplicationHandler() service.Handler {
if err != nil {
return sdk.WrapError(err, "Cannot load application: %s", appName)
}
if app.FromRepository != "" {
return sdk.WithStack(sdk.ErrForbidden)
}

tx, err := api.mustDB().Begin()
if err != nil {
Expand Down Expand Up @@ -153,6 +156,9 @@ func (api *API) updateVariableInApplicationHandler() service.Handler {
if err != nil {
return sdk.WrapError(err, "Cannot load application: %s", appName)
}
if app.FromRepository != "" {
return sdk.WithStack(sdk.ErrForbidden)
}

variableBefore, err := application.LoadVariableByID(api.mustDB(), app.ID, newVar.ID, application.WithClearPassword())
if err != nil {
Expand Down Expand Up @@ -204,6 +210,9 @@ func (api *API) addVariableInApplicationHandler() service.Handler {
if err != nil {
return sdk.WrapError(err, "Cannot load application %s ", appName)
}
if app.FromRepository != "" {
return sdk.WithStack(sdk.ErrForbidden)
}

tx, err := api.mustDB().Begin()
if err != nil {
Expand Down
4 changes: 4 additions & 0 deletions engine/api/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,10 @@ func (api *API) updateEnvironmentHandler() service.Handler {
return sdk.WrapError(errEnv, "updateEnvironmentHandler> Cannot load environment %s", environmentName)
}

if env.FromRepository != "" {
return sdk.WithStack(sdk.ErrForbidden)
}

p, errProj := project.Load(api.mustDB(), api.Cache, projectKey, deprecatedGetUser(ctx))
if errProj != nil {
return sdk.WrapError(errProj, "updateEnvironmentHandler> Cannot load project %s", projectKey)
Expand Down
27 changes: 13 additions & 14 deletions engine/api/environment/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
func LoadEnvironments(db gorp.SqlExecutor, projectKey string, loadDeps bool, u *sdk.User) ([]sdk.Environment, error) {
var envs []sdk.Environment

query := `SELECT environment.id, environment.name, environment.last_modified, 7 as "perm"
query := `SELECT environment.id, environment.name, environment.last_modified, 7 as "perm", environment.from_repository
FROM environment
JOIN project ON project.id = environment.project_id
WHERE project.projectKey = $1
Expand All @@ -29,20 +29,19 @@ func LoadEnvironments(db gorp.SqlExecutor, projectKey string, loadDeps bool, u *
if err == sql.ErrNoRows {
return envs, sdk.ErrNoEnvironment
}
return envs, err
return envs, sdk.WithStack(err)
}
defer rows.Close()

for rows.Next() {
var env sdk.Environment
var lastModified time.Time
err = rows.Scan(&env.ID, &env.Name, &lastModified, &env.Permission)
if err := rows.Scan(&env.ID, &env.Name, &lastModified, &env.Permission, &env.FromRepository); err != nil {
return envs, sdk.WithStack(err)
}
env.LastModified = lastModified.Unix()
env.ProjectKey = projectKey
env.Permission = permission.ProjectPermission(projectKey, u)
if err != nil {
return envs, err
}
envs = append(envs, env)
}
rows.Close()
Expand Down Expand Up @@ -94,10 +93,10 @@ func LoadEnvironmentByID(db gorp.SqlExecutor, ID int64) (*sdk.Environment, error
return &sdk.DefaultEnv, nil
}
var env sdk.Environment
query := `SELECT environment.id, environment.name, environment.project_id
query := `SELECT environment.id, environment.name, environment.project_id, environment.from_repository
FROM environment
WHERE id = $1`
if err := db.QueryRow(query, ID).Scan(&env.ID, &env.Name, &env.ProjectID); err != nil {
if err := db.QueryRow(query, ID).Scan(&env.ID, &env.Name, &env.ProjectID, &env.FromRepository); err != nil {
if err == sql.ErrNoRows {
return nil, sdk.ErrNoEnvironment
}
Expand All @@ -113,11 +112,11 @@ func LoadEnvironmentByName(db gorp.SqlExecutor, projectKey, envName string) (*sd
}

var env sdk.Environment
query := `SELECT environment.id, environment.name, environment.project_id
query := `SELECT environment.id, environment.name, environment.project_id, environment.from_repository
FROM environment
JOIN project ON project.id = environment.project_id
WHERE project.projectKey = $1 AND environment.name = $2`
if err := db.QueryRow(query, projectKey, envName).Scan(&env.ID, &env.Name, &env.ProjectID); err != nil {
if err := db.QueryRow(query, projectKey, envName).Scan(&env.ID, &env.Name, &env.ProjectID, &env.FromRepository); err != nil {
if err == sql.ErrNoRows {
return nil, sdk.ErrNoEnvironment
}
Expand Down Expand Up @@ -196,15 +195,15 @@ func loadDependencies(db gorp.SqlExecutor, env *sdk.Environment) error {

// InsertEnvironment Insert new environment
func InsertEnvironment(db gorp.SqlExecutor, env *sdk.Environment) error {
query := `INSERT INTO environment (name, project_id) VALUES($1, $2) RETURNING id, last_modified`
query := `INSERT INTO environment (name, project_id, from_repository) VALUES($1, $2, $3) RETURNING id, last_modified`

rx := sdk.NamePatternRegex
if !rx.MatchString(env.Name) {
return sdk.NewError(sdk.ErrInvalidName, fmt.Errorf("Invalid environment name. It should match %s", sdk.NamePattern))
}

var lastModified time.Time
err := db.QueryRow(query, env.Name, env.ProjectID).Scan(&env.ID, &lastModified)
err := db.QueryRow(query, env.Name, env.ProjectID, env.FromRepository).Scan(&env.ID, &lastModified)
if err != nil {
pqerr, ok := err.(*pq.Error)
if ok {
Expand All @@ -225,8 +224,8 @@ func UpdateEnvironment(db gorp.SqlExecutor, environment *sdk.Environment) error
return sdk.NewError(sdk.ErrInvalidName, fmt.Errorf("Invalid environment name. It should match %s", sdk.NamePattern))
}

query := `UPDATE environment SET name=$1 WHERE id=$2`
if _, err := db.Exec(query, environment.Name, environment.ID); err != nil {
query := `UPDATE environment SET name=$1, from_repository=$3 WHERE id=$2`
if _, err := db.Exec(query, environment.Name, environment.ID, environment.FromRepository); err != nil {
return err
}
return nil
Expand Down
4 changes: 4 additions & 0 deletions engine/api/environment/environment_importer.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ func ImportInto(db gorp.SqlExecutor, proj *sdk.Project, env *sdk.Environment, in
}
}

if err := UpdateEnvironment(db, env); err != nil {
return sdk.WrapError(err, "unable to update environment")
}

log.Debug("ImportInto> Done")

return nil
Expand Down
19 changes: 14 additions & 5 deletions engine/api/environment/environment_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,21 @@ import (

"github.com/go-gorp/gorp"

"github.com/ovh/cds/engine/api/cache"
"github.com/ovh/cds/engine/api/keys"
"github.com/ovh/cds/sdk"
"github.com/ovh/cds/sdk/exportentities"
"github.com/ovh/cds/sdk/log"
)

// ImportOptions are options to import environment
type ImportOptions struct {
Force bool
FromRepository string
}

// ParseAndImport parse an exportentities.Environment and insert or update the environment in database
func ParseAndImport(db gorp.SqlExecutor, cache cache.Store, proj *sdk.Project, eenv *exportentities.Environment, force bool, decryptFunc keys.DecryptFunc, u *sdk.User) (*sdk.Environment, []sdk.Message, error) {
log.Debug("ParseAndImport>> Import environment %s in project %s (force=%v)", eenv.Name, proj.Key, force)
func ParseAndImport(db gorp.SqlExecutor, proj *sdk.Project, eenv *exportentities.Environment, opts ImportOptions, decryptFunc keys.DecryptFunc, u *sdk.User) (*sdk.Environment, []sdk.Message, error) {
log.Debug("ParseAndImport>> Import environment %s in project %s (force=%v)", eenv.Name, proj.Key, opts.Force)
log.Debug("ParseAndImport>> Env: %+v", eenv)

//Check valid application name
Expand All @@ -32,16 +37,20 @@ func ParseAndImport(db gorp.SqlExecutor, cache cache.Store, proj *sdk.Project, e

//If the environment exists and we don't want to force, raise an error
var exist bool
if oldEnv != nil && !force {
if oldEnv != nil && !opts.Force {
return nil, nil, sdk.ErrEnvironmentExist
}
if oldEnv != nil {
exist = true
}

if oldEnv != nil && oldEnv.FromRepository != "" && opts.FromRepository != oldEnv.FromRepository {
return nil, nil, sdk.WithStack(sdk.ErrForbidden)
}

env := new(sdk.Environment)
env.Name = eenv.Name

env.FromRepository = opts.FromRepository
if exist {
env.ID = oldEnv.ID
}
Expand Down
2 changes: 1 addition & 1 deletion engine/api/environment_import.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func (api *API) postEnvironmentImportHandler() service.Handler {
}
defer tx.Rollback()

_, msgList, globalError := environment.ParseAndImport(tx, api.Cache, proj, eenv, force, project.DecryptWithBuiltinKey, deprecatedGetUser(ctx))
_, msgList, globalError := environment.ParseAndImport(tx, proj, eenv, environment.ImportOptions{Force: force}, project.DecryptWithBuiltinKey, deprecatedGetUser(ctx))
msgListString := translate(r, msgList)
if globalError != nil {
globalError = sdk.WrapError(globalError, "Unable to import environment %s", eenv.Name)
Expand Down
7 changes: 7 additions & 0 deletions engine/api/environment_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ func (api *API) deleteKeyInEnvironmentHandler() service.Handler {
if errE != nil {
return sdk.WrapError(errE, "deleteKeyInEnvironmentHandler> Cannot load environment")
}
if env.FromRepository != "" {
return sdk.WithStack(sdk.ErrForbidden)
}

tx, errT := api.mustDB().Begin()
if errT != nil {
Expand Down Expand Up @@ -93,6 +96,10 @@ func (api *API) addKeyInEnvironmentHandler() service.Handler {
}
newKey.EnvironmentID = env.ID

if env.FromRepository != "" {
return sdk.WithStack(sdk.ErrForbidden)
}

if !strings.HasPrefix(newKey.Name, "env-") {
newKey.Name = "env-" + newKey.Name
}
Expand Down
9 changes: 9 additions & 0 deletions engine/api/environment_variable.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ func (api *API) deleteVariableFromEnvironmentHandler() service.Handler {
if errEnv != nil {
return sdk.WrapError(errEnv, "deleteVariableFromEnvironmentHandler: Cannot load environment %s", envName)
}
if env.FromRepository != "" {
return sdk.WithStack(sdk.ErrForbidden)
}

tx, errBegin := api.mustDB().Begin()
if errBegin != nil {
Expand Down Expand Up @@ -143,6 +146,9 @@ func (api *API) updateVariableInEnvironmentHandler() service.Handler {
if errEnv != nil {
return sdk.WrapError(errEnv, "updateVariableInEnvironmentHandler: cannot load environment %s", envName)
}
if env.FromRepository != "" {
return sdk.WithStack(sdk.ErrForbidden)
}

tx, errBegin := api.mustDB().Begin()
if errBegin != nil {
Expand Down Expand Up @@ -200,6 +206,9 @@ func (api *API) addVariableInEnvironmentHandler() service.Handler {
if errEnv != nil {
return sdk.WrapError(errEnv, "addVariableInEnvironmentHandler: Cannot load environment %s", envName)
}
if env.FromRepository != "" {
return sdk.WithStack(sdk.ErrForbidden)
}

tx, errBegin := api.mustDB().Begin()
if errBegin != nil {
Expand Down
12 changes: 12 additions & 0 deletions engine/api/pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ func (api *API) updatePipelineHandler() service.Handler {
return sdk.WrapError(err, "cannot load pipeline %s", name)
}

if pipelineDB.FromRepository != "" {
return sdk.WithStack(sdk.ErrForbidden)
}

tx, errB := api.mustDB().Begin()
if errB != nil {
sdk.WrapError(errB, "updatePipelineHandler> Cannot start transaction")
Expand Down Expand Up @@ -86,6 +90,14 @@ func (api *API) postPipelineRollbackHandler() service.Handler {
db := api.mustDB()
u := deprecatedGetUser(ctx)

pipDB, err := pipeline.LoadPipeline(db, key, name, false)
if err != nil {
return sdk.WrapError(err, "cannot load pipeline")
}
if pipDB.FromRepository != "" {
return sdk.WithStack(sdk.ErrForbidden)
}

proj, errP := project.Load(db, api.Cache, key, u, project.LoadOptions.WithGroups)
if errP != nil {
return sdk.WrapError(errP, "postPipelineRollbackHandler> Cannot load project")
Expand Down
Loading

0 comments on commit e857bac

Please sign in to comment.