Skip to content

Commit

Permalink
feat(api,ui): edit application as code in CDS ui (#5246)
Browse files Browse the repository at this point in the history
* feat(api): edit application as code in UI
  • Loading branch information
sguiheux authored Jun 12, 2020
1 parent f549c2f commit 69fff58
Show file tree
Hide file tree
Showing 76 changed files with 965 additions and 377 deletions.
1 change: 1 addition & 0 deletions engine/api/api_routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ func (api *API) InitRouter() {

// Application
r.Handle("/project/{permProjectKey}/application/{applicationName}", Scope(sdk.AuthConsumerScopeProject), r.GET(api.getApplicationHandler), r.PUT(api.updateApplicationHandler), r.DELETE(api.deleteApplicationHandler))
r.Handle("/project/{permProjectKey}/application/{applicationName}/ascode", Scope(sdk.AuthConsumerScopeProject), r.PUT(api.updateAsCodeApplicationHandler))
r.Handle("/project/{permProjectKey}/application/{applicationName}/metrics/{metricName}", Scope(sdk.AuthConsumerScopeProject), r.GET(api.getApplicationMetricHandler))
r.Handle("/project/{permProjectKey}/application/{applicationName}/keys", Scope(sdk.AuthConsumerScopeProject), r.GET(api.getKeysInApplicationHandler), r.POST(api.addKeyInApplicationHandler))
r.Handle("/project/{permProjectKey}/application/{applicationName}/keys/{name}", Scope(sdk.AuthConsumerScopeProject), r.DELETE(api.deleteKeyInApplicationHandler))
Expand Down
125 changes: 122 additions & 3 deletions engine/api/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,25 @@ import (
"net/http"
"strings"

"github.com/ovh/cds/engine/api/permission"
"github.com/ovh/cds/engine/api/user"

"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/cache"
"github.com/ovh/cds/engine/api/environment"
"github.com/ovh/cds/engine/api/event"
"github.com/ovh/cds/engine/api/group"
"github.com/ovh/cds/engine/api/keys"
"github.com/ovh/cds/engine/api/operation"
"github.com/ovh/cds/engine/api/permission"
"github.com/ovh/cds/engine/api/project"
"github.com/ovh/cds/engine/api/repositoriesmanager"
"github.com/ovh/cds/engine/api/user"
"github.com/ovh/cds/engine/api/workflow"
"github.com/ovh/cds/engine/service"
"github.com/ovh/cds/sdk"
"github.com/ovh/cds/sdk/exportentities"
)

func (api *API) getApplicationsHandler() service.Handler {
Expand Down Expand Up @@ -147,6 +150,18 @@ func (api *API) getApplicationHandler() service.Handler {
app.Usage = &usage
}

proj, err := project.Load(api.mustDB(), projectKey)
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")
}
app.WorkflowAscodeHolder = wkAscodeHolder

return service.WriteJSON(w, app, http.StatusOK)
}
}
Expand Down Expand Up @@ -390,6 +405,110 @@ func cloneApplication(ctx context.Context, db gorp.SqlExecutor, store cache.Stor
return nil
}

func (api *API) updateAsCodeApplicationHandler() service.Handler {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
key := vars[permProjectKey]
name := vars["applicationName"]

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

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

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

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

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

appDB, err := application.LoadByName(api.mustDB(), key, name)
if err != nil {
return sdk.WrapError(err, "cannot load application %s", name)
}

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

wkHolder, err := workflow.LoadByRepo(ctx, api.mustDB(), *proj, appDB.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 a.Keys {
k := &a.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)
a.ProjectID = proj.ID
app, err := application.ExportApplication(api.mustDB(), a, project.EncryptWithBuiltinKey, fmt.Sprintf("app:%d:%s", appDB.ID, branch))
if err != nil {
return sdk.WrapError(err, "unable to export app %s", a.Name)
}
wp := exportentities.WorkflowComponents{
Applications: []exportentities.Application{app},
}

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("UpdateAsCodeApplicationHandler-%s", ope.UUID), func(ctx context.Context) {
ed := ascode.EntityData{
FromRepo: appDB.FromRepository,
Type: ascode.ApplicationEvent,
ID: appDB.ID,
Name: appDB.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) updateApplicationHandler() service.Handler {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
Expand Down
15 changes: 7 additions & 8 deletions engine/api/application/application_exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@ import (

"github.com/go-gorp/gorp"

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

// Export an application
func Export(db gorp.SqlExecutor, cache cache.Store, key string, appName string, encryptFunc sdk.EncryptFunc) (exportentities.Application, error) {
func Export(db gorp.SqlExecutor, key string, appName string, encryptFunc sdk.EncryptFunc) (exportentities.Application, error) {
app, err := LoadByNameWithClearVCSStrategyPassword(db, key, appName,
LoadOptions.WithVariablesWithClearPassword,
LoadOptions.WithClearKeys,
Expand All @@ -21,19 +20,19 @@ func Export(db gorp.SqlExecutor, cache cache.Store, key string, appName string,
return exportentities.Application{}, sdk.WrapError(err, "cannot load application %s", appName)
}

return ExportApplication(db, *app, encryptFunc)
return ExportApplication(db, *app, encryptFunc, fmt.Sprintf("appID:%d", app.ID))
}

// ExportApplication encrypt and export
func ExportApplication(db gorp.SqlExecutor, app sdk.Application, encryptFunc sdk.EncryptFunc) (exportentities.Application, error) {
func ExportApplication(db gorp.SqlExecutor, app sdk.Application, encryptFunc sdk.EncryptFunc, encryptPrefix string) (exportentities.Application, error) {
var appvars []sdk.ApplicationVariable
for _, v := range app.Variables {
switch v.Type {
case sdk.KeyVariable:
return exportentities.Application{}, sdk.NewErrorFrom(sdk.ErrUnknownError,
"variable %s: variable of type key are deprecated. Please use the standard keys from your project or your application", v.Name)
case sdk.SecretVariable:
content, err := encryptFunc(db, app.ProjectID, fmt.Sprintf("appID:%d:%s", app.ID, v.Name), v.Value)
content, err := encryptFunc(db, app.ProjectID, fmt.Sprintf("%s:%s", encryptPrefix, v.Name), v.Value)
if err != nil {
return exportentities.Application{}, sdk.WrapError(err, "unknown key type")
}
Expand All @@ -49,7 +48,7 @@ func ExportApplication(db gorp.SqlExecutor, app sdk.Application, encryptFunc sdk
var keys []exportentities.EncryptedKey
// Parse keys
for _, k := range app.Keys {
content, err := encryptFunc(db, app.ProjectID, fmt.Sprintf("appID:%d:%s", app.ID, k.Name), k.Private)
content, err := encryptFunc(db, app.ProjectID, fmt.Sprintf("%s:%s", encryptPrefix, k.Name), k.Private)
if err != nil {
return exportentities.Application{}, sdk.WrapError(err, "unable to encrypt key")
}
Expand All @@ -62,7 +61,7 @@ func ExportApplication(db gorp.SqlExecutor, app sdk.Application, encryptFunc sdk
}

if app.RepositoryStrategy.Password != "" {
content, err := encryptFunc(db, app.ProjectID, fmt.Sprintf("appID:%d:%s", app.ID, "vcs:password"), app.RepositoryStrategy.Password)
content, err := encryptFunc(db, app.ProjectID, fmt.Sprintf("%s:%s", encryptPrefix, "vcs:password"), app.RepositoryStrategy.Password)
if err != nil {
return exportentities.Application{}, sdk.WrapError(err, "unable to encrypt password")
}
Expand All @@ -72,7 +71,7 @@ func ExportApplication(db gorp.SqlExecutor, app sdk.Application, encryptFunc sdk
for pfName, pfConfig := range app.DeploymentStrategies {
for k, v := range pfConfig {
if v.Type == sdk.SecretVariable {
content, err := encryptFunc(db, app.ProjectID, fmt.Sprintf("appID:%d:%s:%s:%s", app.ID, pfName, k, "deployment:password"), v.Value)
content, err := encryptFunc(db, app.ProjectID, fmt.Sprintf("%s:%s:%s:%s", encryptPrefix, pfName, k, "deployment:password"), v.Value)
if err != nil {
return exportentities.Application{}, sdk.WrapError(err, "Unable to encrypt password")
}
Expand Down
2 changes: 1 addition & 1 deletion engine/api/application_export.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func (api *API) getApplicationExportHandler() service.Handler {
return err
}

app, err := application.Export(api.mustDB(), api.Cache, key, appName, project.EncryptWithBuiltinKey)
app, err := application.Export(api.mustDB(), key, appName, project.EncryptWithBuiltinKey)
if err != nil {
return sdk.WithStack(err)
}
Expand Down
24 changes: 6 additions & 18 deletions engine/api/application_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,25 +104,13 @@ func (api *API) addKeyInApplicationHandler() service.Handler {
newKey.Name = "app-" + newKey.Name
}

switch newKey.Type {
case sdk.KeyTypeSSH:
k, errK := keys.GenerateSSHKey(newKey.Name)
if errK != nil {
return sdk.WrapError(errK, "addKeyInApplicationHandler> Cannot generate ssh key")
}
newKey.Public = k.Public
newKey.Private = k.Private
case sdk.KeyTypePGP:
k, errGenerate := keys.GeneratePGPKeyPair(newKey.Name)
if errGenerate != nil {
return sdk.WrapError(errGenerate, "addKeyInApplicationHandler> Cannot generate pgpKey")
}
newKey.Public = k.Public
newKey.Private = k.Private
newKey.KeyID = k.KeyID
default:
return sdk.WrapError(sdk.ErrUnknownKeyType, "addKeyInApplicationHandler> unknown key of type: %s", newKey.Type)
k, err := keys.GenerateKey(newKey.Name, newKey.Type)
if err != nil {
return err
}
newKey.Public = k.Public
newKey.Private = k.Private
newKey.KeyID = k.KeyID

tx, errT := api.mustDB().Begin()
if errT != nil {
Expand Down
Loading

0 comments on commit 69fff58

Please sign in to comment.