Skip to content

Commit

Permalink
feat(ui): add ascode environment edition in UI (#5269)
Browse files Browse the repository at this point in the history
  • Loading branch information
sguiheux authored Jun 29, 2020
1 parent 0c4a64e commit bf1afd3
Show file tree
Hide file tree
Showing 38 changed files with 1,323 additions and 706 deletions.
1 change: 1 addition & 0 deletions engine/api/api_routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ func (api *API) InitRouter() {
r.Handle("/project/{permProjectKey}/environment/import", Scope(sdk.AuthConsumerScopeProject), r.POST(api.importNewEnvironmentHandler, DEPRECATED))
r.Handle("/project/{permProjectKey}/environment/import/{environmentName}", Scope(sdk.AuthConsumerScopeProject), r.POST(api.importIntoEnvironmentHandler, DEPRECATED))
r.Handle("/project/{permProjectKey}/environment/{environmentName}", Scope(sdk.AuthConsumerScopeProject), r.GET(api.getEnvironmentHandler), r.PUT(api.updateEnvironmentHandler), r.DELETE(api.deleteEnvironmentHandler))
r.Handle("/project/{permProjectKey}/environment/{environmentName}/ascode", Scope(sdk.AuthConsumerScopeProject), r.PUT(api.updateAsCodeEnvironmentHandler))
r.Handle("/project/{permProjectKey}/environment/{environmentName}/usage", Scope(sdk.AuthConsumerScopeProject), r.GET(api.getEnvironmentUsageHandler))
r.Handle("/project/{permProjectKey}/environment/{environmentName}/keys", Scope(sdk.AuthConsumerScopeProject), r.GET(api.getKeysInEnvironmentHandler), r.POST(api.addKeyInEnvironmentHandler))
r.Handle("/project/{permProjectKey}/environment/{environmentName}/keys/{name}", Scope(sdk.AuthConsumerScopeProject), r.DELETE(api.deleteKeyInEnvironmentHandler))
Expand Down
14 changes: 12 additions & 2 deletions engine/api/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,17 +151,27 @@ func (api *API) getApplicationHandler() service.Handler {
}

if app.FromRepository != "" {
proj, err := project.Load(api.mustDB(), projectKey)
proj, err := project.Load(api.mustDB(), projectKey,
project.LoadOptions.WithApplicationWithDeploymentStrategies,
project.LoadOptions.WithPipelines,
project.LoadOptions.WithEnvironments,
project.LoadOptions.WithIntegrations)
if err != nil {
return err
}

wkAscodeHolder, err := workflow.LoadByRepo(ctx, api.mustDB(), *proj, app.FromRepository, workflow.LoadOptions{
WithTemplate: true,
})
if err != nil && !sdk.ErrorIs(err, sdk.ErrNotFound) {
return sdk.NewErrorFrom(err, "cannot found workflow holder of the pipeline")
return sdk.NewErrorFrom(err, "cannot found workflow holder of the application")
}
app.WorkflowAscodeHolder = wkAscodeHolder

// FIXME from_repository should never be set if the workflow holder was deleted
if app.WorkflowAscodeHolder == nil {
app.FromRepository = ""
}
}

return service.WriteJSON(w, app, http.StatusOK)
Expand Down
15 changes: 15 additions & 0 deletions engine/api/ascode/pull_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const (
PipelineEvent EventType = "pipeline"
WorkflowEvent EventType = "workflow"
ApplicationEvent EventType = "application"
EnvironmentEvent EventType = "environment"
)

type EntityData struct {
Expand Down Expand Up @@ -200,6 +201,20 @@ func createPullRequest(ctx context.Context, db *gorp.DbMap, store cache.Store, p
if !found {
asCodeEvent.Data.Applications[ed.ID] = ed.Name
}
case EnvironmentEvent:
if asCodeEvent.Data.Environments == nil {
asCodeEvent.Data.Environments = make(map[int64]string)
}
found := false
for k := range asCodeEvent.Data.Environments {
if k == ed.ID {
found = true
break
}
}
if !found {
asCodeEvent.Data.Environments[ed.ID] = ed.Name
}
}

if err := UpsertEvent(db, asCodeEvent); err != nil {
Expand Down
135 changes: 135 additions & 0 deletions engine/api/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,23 @@ package api

import (
"context"
"fmt"
"net/http"

"github.com/go-gorp/gorp"
"github.com/gorilla/mux"

"github.com/ovh/cds/engine/api/application"
"github.com/ovh/cds/engine/api/ascode"
"github.com/ovh/cds/engine/api/environment"
"github.com/ovh/cds/engine/api/event"
"github.com/ovh/cds/engine/api/keys"
"github.com/ovh/cds/engine/api/operation"
"github.com/ovh/cds/engine/api/project"
"github.com/ovh/cds/engine/api/workflow"
"github.com/ovh/cds/engine/service"
"github.com/ovh/cds/sdk"
"github.com/ovh/cds/sdk/exportentities"
"github.com/ovh/cds/sdk/log"
)

Expand Down Expand Up @@ -73,6 +79,30 @@ func (api *API) getEnvironmentHandler() service.Handler {
env.Usage.Workflows = wf
}

if env.FromRepository != "" {
proj, err := project.Load(api.mustDB(), projectKey,
project.LoadOptions.WithApplicationWithDeploymentStrategies,
project.LoadOptions.WithPipelines,
project.LoadOptions.WithEnvironments,
project.LoadOptions.WithIntegrations)
if err != nil {
return err
}

wkAscodeHolder, err := workflow.LoadByRepo(ctx, api.mustDB(), *proj, env.FromRepository, workflow.LoadOptions{
WithTemplate: true,
})
if err != nil && !sdk.ErrorIs(err, sdk.ErrNotFound) {
return sdk.NewErrorFrom(err, "cannot found workflow holder of the environment")
}
env.WorkflowAscodeHolder = wkAscodeHolder

// FIXME from_repository should never be set if the workflow holder was deleted
if env.WorkflowAscodeHolder == nil {
env.FromRepository = ""
}
}

return service.WriteJSON(w, env, http.StatusOK)
}
}
Expand Down Expand Up @@ -194,6 +224,111 @@ func (api *API) deleteEnvironmentHandler() service.Handler {
}
}

func (api *API) updateAsCodeEnvironmentHandler() service.Handler {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
// Get pipeline and action name in URL
vars := mux.Vars(r)
key := vars[permProjectKey]
environmentName := vars["environmentName"]

branch := FormString(r, "branch")
message := FormString(r, "message")

if branch == "" || message == "" {
return sdk.NewErrorFrom(sdk.ErrWrongRequest, "missing branch or message data")
}

var env sdk.Environment
if err := service.UnmarshalBody(r, &env); err != nil {
return err
}

// check application name pattern
regexp := sdk.NamePatternRegex
if !regexp.MatchString(env.Name) {
return sdk.WrapError(sdk.ErrInvalidApplicationPattern, "Environment name %s do not respect pattern", env.Name)
}

proj, err := project.Load(api.mustDB(), key, project.LoadOptions.WithClearKeys)
if err != nil {
return err
}

envDB, err := environment.LoadEnvironmentByName(api.mustDB(), key, environmentName)
if err != nil {
return sdk.WrapError(err, "cannot load environment %s", environmentName)
}

if envDB.FromRepository == "" {
return sdk.NewErrorFrom(sdk.ErrForbidden, "current environment is not ascode")
}

wkHolder, err := workflow.LoadByRepo(ctx, api.mustDB(), *proj, envDB.FromRepository, workflow.LoadOptions{
WithTemplate: true,
})
if err != nil {
return err
}
if wkHolder.TemplateInstance != nil {
return sdk.NewErrorFrom(sdk.ErrForbidden, "cannot edit an application that was generated by a template")
}

var rootApp *sdk.Application
if wkHolder.WorkflowData.Node.Context != nil && wkHolder.WorkflowData.Node.Context.ApplicationID != 0 {
rootApp, err = application.LoadByIDWithClearVCSStrategyPassword(api.mustDB(), wkHolder.WorkflowData.Node.Context.ApplicationID)
if err != nil {
return err
}
}
if rootApp == nil {
return sdk.NewErrorFrom(sdk.ErrWrongRequest, "cannot find the root application of the workflow %s that hold the pipeline", wkHolder.Name)
}

// create keys
for i := range env.Keys {
k := &env.Keys[i]
newKey, err := keys.GenerateKey(k.Name, k.Type)
if err != nil {
return err
}
k.Public = newKey.Public
k.Private = newKey.Private
k.KeyID = newKey.KeyID
}

u := getAPIConsumer(ctx)
env.ProjectID = proj.ID
envExported, err := environment.ExportEnvironment(api.mustDB(), env, project.EncryptWithBuiltinKey, fmt.Sprintf("env:%d:%s", envDB.ID, branch))
if err != nil {
return err
}
wp := exportentities.WorkflowComponents{
Environments: []exportentities.Environment{envExported},
}

ope, err := operation.PushOperationUpdate(ctx, api.mustDB(), api.Cache, *proj, wp, rootApp.VCSServer, rootApp.RepositoryFullname, branch, message, rootApp.RepositoryStrategy, u)
if err != nil {
return err
}

sdk.GoRoutine(context.Background(), fmt.Sprintf("UpdateAsCodeEnvironmentHandler-%s", ope.UUID), func(ctx context.Context) {
ed := ascode.EntityData{
FromRepo: envDB.FromRepository,
Type: ascode.EnvironmentEvent,
ID: envDB.ID,
Name: envDB.Name,
OperationUUID: ope.UUID,
}
asCodeEvent := ascode.UpdateAsCodeResult(ctx, api.mustDB(), api.Cache, *proj, wkHolder.ID, *rootApp, ed, u)
if asCodeEvent != nil {
event.PublishAsCodeEvent(ctx, proj.Key, *asCodeEvent, u)
}
}, api.PanicDump())

return service.WriteJSON(w, ope, http.StatusOK)
}
}

func (api *API) updateEnvironmentHandler() service.Handler {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
// Get pipeline and action name in URL
Expand Down
4 changes: 2 additions & 2 deletions engine/api/environment/environment_exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ func Export(ctx context.Context, db gorp.SqlExecutor, key string, envName string
}
env.Keys = keys

return ExportEnvironment(db, *env, encryptFunc)
return ExportEnvironment(db, *env, encryptFunc, fmt.Sprintf("env:%d", env.ID))
}

// ExportEnvironment encrypt and export
func ExportEnvironment(db gorp.SqlExecutor, env sdk.Environment, encryptFunc sdk.EncryptFunc) (exportentities.Environment, error) {
func ExportEnvironment(db gorp.SqlExecutor, env sdk.Environment, encryptFunc sdk.EncryptFunc, encryptPrefix string) (exportentities.Environment, error) {
var envvars []sdk.EnvironmentVariable
for _, v := range env.Variables {
switch v.Type {
Expand Down
Loading

0 comments on commit bf1afd3

Please sign in to comment.