From fd92acdbd5147910e44c416e510ef8c2a717957b Mon Sep 17 00:00:00 2001 From: Ajay Kidave Date: Wed, 14 Feb 2024 18:59:47 -0800 Subject: [PATCH] Updated account link to use staged deployment --- cmd/clace/account_cmds.go | 31 +++++++--- internal/server/app_apis.go | 6 +- internal/server/app_updates.go | 101 ++++++++----------------------- internal/server/router.go | 54 +++++++++-------- internal/utils/api_types.go | 5 +- tests/commander/test_reload.yaml | 2 +- 6 files changed, 81 insertions(+), 118 deletions(-) diff --git a/cmd/clace/account_cmds.go b/cmd/clace/account_cmds.go index f64ecd3..7cf6a83 100644 --- a/cmd/clace/account_cmds.go +++ b/cmd/clace/account_cmds.go @@ -28,33 +28,34 @@ func accountLinkCommand(commonFlags []cli.Flag, clientConfig *utils.ClientConfig flags := make([]cli.Flag, 0, len(commonFlags)+2) flags = append(flags, commonFlags...) flags = append(flags, dryRunFlag()) + flags = append(flags, newBoolFlag(PROMOTE_FLAG, "p", "Promote the change from stage to prod", false)) return &cli.Command{ Name: "link", Usage: "Link an app to to use specific account for a plugin", Flags: flags, Before: altsrc.InitInputSourceWithContext(flags, altsrc.NewTomlSourceFromFlagFunc(configFileFlagName)), - ArgsUsage: " ", - UsageText: `args: + ArgsUsage: " ", + UsageText: `args: - is a required first argument. The optional domain and path are separated by a ":". This is the app for which the account link is to be created. - is the required second argument. This is the name of the plugin. - is the required third argument. This is the name of the account to link to the plugin. + is the first required argument. ` + PATH_SPEC_HELP + ` is the required second argument. This is the name of the plugin. + is the required third argument. This is the name of the account to link to for the plugin. Use "-" to unlink the existing account. Examples: Link db plugin: clace account link /myapp db.in temp Link in dryrun mode: clace account link --dry-run example.com:/ rest.in testaccount`, Action: func(cCtx *cli.Context) error { if cCtx.NArg() != 3 { - return fmt.Errorf("requires three arguments: ") + return fmt.Errorf("requires three arguments: ") } client := utils.NewHttpClient(clientConfig.ServerUri, clientConfig.AdminUser, clientConfig.AdminPassword, clientConfig.SkipCertCheck) values := url.Values{} - values.Add("appPath", cCtx.Args().First()) + values.Add("pathSpec", cCtx.Args().First()) values.Add("plugin", cCtx.Args().Get(1)) values.Add("account", cCtx.Args().Get(2)) values.Add(DRY_RUN_ARG, strconv.FormatBool(cCtx.Bool(DRY_RUN_FLAG))) + values.Add(PROMOTE_ARG, strconv.FormatBool(cCtx.Bool(PROMOTE_FLAG))) var linkResponse utils.AppLinkAccountResponse err := client.Post("/_clace/link_account", values, nil, &linkResponse) @@ -62,10 +63,22 @@ func accountLinkCommand(commonFlags []cli.Flag, clientConfig *utils.ClientConfig return err } - for _, linkedApp := range linkResponse.LinkResults { + for _, linkedApp := range linkResponse.StagedUpdateResults { fmt.Printf("Linked app %s\n", linkedApp) } - fmt.Fprintf(cCtx.App.Writer, "%d app(s) linked, 0 app(s) promoted.\n", len(linkResponse.LinkResults)) + + if len(linkResponse.PromoteResults) > 0 { + fmt.Fprintf(cCtx.App.Writer, "Promoted apps: ") + for i, promoteResult := range linkResponse.PromoteResults { + if i > 0 { + fmt.Fprintf(cCtx.App.Writer, ", ") + } + fmt.Fprintf(cCtx.App.Writer, "%s", promoteResult) + } + fmt.Fprintln(cCtx.App.Writer) + } + + fmt.Fprintf(cCtx.App.Writer, "%d app(s) linked, %d app(s) promoted.\n", len(linkResponse.StagedUpdateResults), len(linkResponse.PromoteResults)) if linkResponse.DryRun { fmt.Print(DRY_RUN_MESSAGE) diff --git a/internal/server/app_apis.go b/internal/server/app_apis.go index d8bf4fc..8767f0c 100644 --- a/internal/server/app_apis.go +++ b/internal/server/app_apis.go @@ -468,7 +468,7 @@ func (s *Server) auditApp(ctx context.Context, tx metadata.Transaction, app *app return auditResult, nil } -func (s *Server) CompleteTransaction(ctx context.Context, tx metadata.Transaction, apps []*app.App, dryRun bool) error { +func (s *Server) CompleteTransaction(ctx context.Context, tx metadata.Transaction, entries []utils.AppPathDomain, dryRun bool) error { if dryRun { return nil } @@ -478,8 +478,8 @@ func (s *Server) CompleteTransaction(ctx context.Context, tx metadata.Transactio } // Update the in memory cache - if apps != nil { - s.apps.UpdateApps(apps) + if entries != nil { + s.apps.DeleteApps(entries) } return nil } diff --git a/internal/server/app_updates.go b/internal/server/app_updates.go index aabf3c9..86b9eec 100644 --- a/internal/server/app_updates.go +++ b/internal/server/app_updates.go @@ -7,7 +7,6 @@ import ( "context" "fmt" "net/http" - "strings" "github.com/claceio/clace/internal/app" "github.com/claceio/clace/internal/metadata" @@ -111,9 +110,6 @@ func (s *Server) ReloadApps(ctx context.Context, pathSpec string, approve, dryRu } } - prodApps := make([]*app.App, 0, len(filteredApps)) - devApps := make([]*app.App, 0, len(filteredApps)) - for _, stageApp := range stageApps { if _, err := stageApp.Reload(true, true); err != nil { return nil, fmt.Errorf("error reloading stage app %s: %w", stageApp.AppEntry, err) @@ -126,10 +122,11 @@ func (s *Server) ReloadApps(ctx context.Context, pathSpec string, approve, dryRu if err != nil { return nil, fmt.Errorf("error setting up prod app %s: %w", prodAppEntry, err) } - prodApps = append(prodApps, prodApp) if _, err := prodApp.Reload(true, true); err != nil { return nil, fmt.Errorf("error reloading prod app %s: %w", prodApp.AppEntry, err) } + + reloadResults = append(reloadResults, prodAppEntry.AppPathDomain()) } } @@ -139,7 +136,6 @@ func (s *Server) ReloadApps(ctx context.Context, pathSpec string, approve, dryRu return nil, fmt.Errorf("error setting up app %s: %w", devAppEntry, err) } reloadResults = append(reloadResults, devAppEntry.AppPathDomain()) - devApps = append(devApps, devApp) devResult, err := devApp.Audit() if err != nil { @@ -164,15 +160,8 @@ func (s *Server) ReloadApps(ctx context.Context, pathSpec string, approve, dryRu } } - updatedApps := make([]*app.App, 0, len(stageApps)+len(prodApps)+len(devApps)) - updatedApps = append(updatedApps, devApps...) - updatedApps = append(updatedApps, stageApps...) - if promote { - updatedApps = append(updatedApps, prodApps...) - } - // Commit the transaction if not dry run and update the in memory app store - if err := s.CompleteTransaction(ctx, tx, updatedApps, dryRun); err != nil { + if err := s.CompleteTransaction(ctx, tx, reloadResults, dryRun); err != nil { return nil, err } @@ -211,7 +200,7 @@ func (s *Server) StagedUpdate(ctx context.Context, pathSpec string, dryRun, prom } defer tx.Rollback() - result, promoteResults, apps, err := s.StagedUpdateAppsTx(ctx, tx, pathSpec, promote, handler, args) + result, entries, promoteResults, err := s.StagedUpdateAppsTx(ctx, tx, pathSpec, promote, handler, args) if err != nil { return nil, err } @@ -222,23 +211,23 @@ func (s *Server) StagedUpdate(ctx context.Context, pathSpec string, dryRun, prom PromoteResults: promoteResults, } - if err := s.CompleteTransaction(ctx, tx, apps, dryRun); err != nil { + if err := s.CompleteTransaction(ctx, tx, entries, dryRun); err != nil { return nil, err } return ret, nil } -type stagedUpdateHandler func(ctx context.Context, tx metadata.Transaction, appEntry *utils.AppEntry, args map[string]any) (any, *app.App, error) +type stagedUpdateHandler func(ctx context.Context, tx metadata.Transaction, appEntry *utils.AppEntry, args map[string]any) (any, utils.AppPathDomain, error) -func (s *Server) StagedUpdateAppsTx(ctx context.Context, tx metadata.Transaction, pathSpec string, promote bool, handler stagedUpdateHandler, args map[string]any) ([]any, []utils.AppPathDomain, []*app.App, error) { +func (s *Server) StagedUpdateAppsTx(ctx context.Context, tx metadata.Transaction, pathSpec string, promote bool, handler stagedUpdateHandler, args map[string]any) ([]any, []utils.AppPathDomain, []utils.AppPathDomain, error) { filteredApps, err := s.FilterApps(pathSpec, false) if err != nil { return nil, nil, nil, err } results := make([]any, 0, len(filteredApps)) - apps := make([]*app.App, 0, len(filteredApps)) + entries := make([]utils.AppPathDomain, 0, len(filteredApps)) promoteResults := make([]utils.AppPathDomain, 0, len(filteredApps)) for _, appInfo := range filteredApps { appEntry, err := s.GetAppEntry(ctx, tx, appInfo.AppPathDomain) @@ -248,7 +237,7 @@ func (s *Server) StagedUpdateAppsTx(ctx context.Context, tx metadata.Transaction var prodAppEntry *utils.AppEntry if !appEntry.IsDev { - // For prod apps, approve the staging app + // For prod apps, update the staging app prodAppEntry = appEntry appEntry, err = s.getStageApp(ctx, tx, appEntry) if err != nil { @@ -283,29 +272,30 @@ func (s *Server) StagedUpdateAppsTx(ctx context.Context, tx metadata.Transaction if err != nil { return nil, nil, nil, fmt.Errorf("error setting up prod app %s: %w", prodAppEntry, err) } - apps = append(apps, prodApp) + entries = append(entries, prodApp.AppPathDomain()) promoteResults = append(promoteResults, prodAppEntry.AppPathDomain()) } } - apps = append(apps, app) + entries = append(entries, app) results = append(results, result) } - return results, promoteResults, apps, nil + return results, entries, promoteResults, nil } -func (s *Server) auditHandler(ctx context.Context, tx metadata.Transaction, appEntry *utils.AppEntry, args map[string]any) (any, *app.App, error) { +func (s *Server) auditHandler(ctx context.Context, tx metadata.Transaction, appEntry *utils.AppEntry, args map[string]any) (any, utils.AppPathDomain, error) { + appPathDomain := appEntry.AppPathDomain() app, err := s.setupApp(appEntry, tx) if err != nil { - return nil, nil, err + return nil, appPathDomain, err } result, err := s.auditApp(ctx, tx, app, true) if err != nil { - return nil, nil, err + return nil, appPathDomain, err } - return result, app, nil + return result, appPathDomain, nil } func (s *Server) PromoteApps(ctx context.Context, pathSpec string, dryRun bool) (*utils.AppPromoteResponse, error) { @@ -321,7 +311,6 @@ func (s *Server) PromoteApps(ctx context.Context, pathSpec string, dryRun bool) defer tx.Rollback() result := make([]utils.AppPathDomain, 0, len(filteredApps)) - newApps := make([]*app.App, 0, len(filteredApps)) for _, appInfo := range filteredApps { if appInfo.IsDev { // Not a prod app, skip @@ -353,11 +342,10 @@ func (s *Server) PromoteApps(ctx context.Context, pathSpec string, dryRun bool) if _, err := prodApp.Reload(true, true); err != nil { return nil, fmt.Errorf("error reloading prod app %s: %w", prodApp.AppEntry, err) } - newApps = append(newApps, prodApp) result = append(result, appInfo.AppPathDomain) } - if err = s.CompleteTransaction(ctx, tx, newApps, dryRun); err != nil { + if err = s.CompleteTransaction(ctx, tx, result, dryRun); err != nil { return nil, err } @@ -368,7 +356,6 @@ func (s *Server) PromoteApps(ctx context.Context, pathSpec string, dryRun bool) func (s *Server) promoteApp(ctx context.Context, tx metadata.Transaction, stagingApp *utils.AppEntry, prodApp *utils.AppEntry) (bool, error) { stagingFileStore := metadata.NewFileStore(stagingApp.Id, stagingApp.Metadata.VersionMetadata.Version, s.db, tx) - prevVersion := prodApp.Metadata.VersionMetadata.Version newVersion := stagingApp.Metadata.VersionMetadata.Version @@ -493,36 +480,14 @@ func (s *Server) updateAppSettings(ctx context.Context, tx metadata.Transaction, return ret, nil } -func (s *Server) LinkAccount(ctx context.Context, mainAppPath, plugin, account string, dryRun bool) (*utils.AppLinkAccountResponse, error) { - mainAppPathDomain, err := parseAppPath(mainAppPath) - if err != nil { - return nil, err - } - - tx, err := s.db.BeginTransaction(ctx) - if err != nil { - return nil, err - } - defer tx.Rollback() - - mainAppEntry, err := s.db.GetAppTx(ctx, tx, mainAppPathDomain) - if err != nil { - return nil, err - } - - appEntry := mainAppEntry - if strings.HasPrefix(string(mainAppEntry.Id), utils.ID_PREFIX_APP_PROD) { - // For prod app, update the staging app - appEntry, err = s.getStageApp(ctx, tx, mainAppEntry) - if err != nil { - return nil, err - } - } - +func (s *Server) accountLinkHandler(ctx context.Context, tx metadata.Transaction, appEntry *utils.AppEntry, args map[string]any) (any, utils.AppPathDomain, error) { if appEntry.Metadata.Accounts == nil { appEntry.Metadata.Accounts = []utils.AccountLink{} } + plugin := args["plugin"].(string) + account := args["account"].(string) + matchIndex := -1 for i, accountLink := range appEntry.Metadata.Accounts { if accountLink.Plugin == plugin { @@ -548,24 +513,6 @@ func (s *Server) LinkAccount(ctx context.Context, mainAppPath, plugin, account s } } - // Persist the updated metadata - if err := s.db.UpdateAppMetadata(ctx, tx, appEntry); err != nil { - return nil, err - } - - ret := &utils.AppLinkAccountResponse{ - DryRun: dryRun, - LinkResults: []utils.AppPathDomain{appEntry.AppPathDomain()}, - } - - if dryRun { - return ret, nil - } - - if err = tx.Commit(); err != nil { - return nil, err - } - - s.apps.DeleteApps(ret.LinkResults) // Delete from in memory cache - return ret, nil + appPathDomain := appEntry.AppPathDomain() + return appPathDomain, appPathDomain, nil } diff --git a/internal/server/router.go b/internal/server/router.go index 19717a1..de15655 100644 --- a/internal/server/router.go +++ b/internal/server/router.go @@ -280,6 +280,9 @@ func (h *Handler) deleteApps(r *http.Request) (any, error) { func (h *Handler) approveApps(r *http.Request) (any, error) { pathSpec := r.URL.Query().Get("pathSpec") dryRun, err := parseBoolArg(r.URL.Query().Get(DRY_RUN_ARG), false) + if err != nil { + return nil, err + } promote, err := parseBoolArg(r.URL.Query().Get(PROMOTE_ARG), false) if err != nil { return nil, err @@ -293,6 +296,30 @@ func (h *Handler) approveApps(r *http.Request) (any, error) { return approveResult, err } +func (h *Handler) accountLink(r *http.Request) (any, error) { + pathSpec := r.URL.Query().Get("pathSpec") + dryRun, err := parseBoolArg(r.URL.Query().Get(DRY_RUN_ARG), false) + if err != nil { + return nil, err + } + promote, err := parseBoolArg(r.URL.Query().Get(PROMOTE_ARG), false) + if err != nil { + return nil, err + } + + if pathSpec == "" { + return nil, utils.CreateRequestError("pathSpec is required", http.StatusBadRequest) + } + + args := map[string]any{ + "plugin": r.URL.Query().Get("plugin"), + "account": r.URL.Query().Get("account"), + } + + linkResult, err := h.server.StagedUpdate(r.Context(), pathSpec, dryRun, promote, h.server.accountLinkHandler, args) + return linkResult, err +} + func AddVaryHeader(next http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { value := w.Header().Get(VARY_HEADER) @@ -420,31 +447,6 @@ func (h *Handler) updateAppSettings(r *http.Request) (any, error) { return ret, nil } -func (h *Handler) linkAccount(r *http.Request) (any, error) { - appPath := r.URL.Query().Get("appPath") - dryRun, err := parseBoolArg(r.URL.Query().Get(DRY_RUN_ARG), false) - if err != nil { - return nil, err - } - - if appPath == "" { - return nil, utils.CreateRequestError("appPath is required", http.StatusBadRequest) - } - - plugin := r.URL.Query().Get("plugin") - account := r.URL.Query().Get("account") - if plugin == "" || account == "" { - return nil, utils.CreateRequestError("plugin and account are required", http.StatusBadRequest) - } - - ret, err := h.server.LinkAccount(r.Context(), appPath, plugin, account, dryRun) - if err != nil { - return nil, utils.CreateRequestError(err.Error(), http.StatusBadRequest) - } - - return ret, nil -} - func (h *Handler) serveInternal(enableBasicAuth bool) http.Handler { // These API's are mounted at /_clace r := chi.NewRouter() @@ -496,7 +498,7 @@ func (h *Handler) serveInternal(enableBasicAuth bool) http.Handler { // API to change account links r.Post("/link_account", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - h.apiHandler(w, r, enableBasicAuth, h.linkAccount) + h.apiHandler(w, r, enableBasicAuth, h.accountLink) })) return r diff --git a/internal/utils/api_types.go b/internal/utils/api_types.go index f4c8877..21552d4 100644 --- a/internal/utils/api_types.go +++ b/internal/utils/api_types.go @@ -120,8 +120,9 @@ type AppPreviewResponse struct { } type AppLinkAccountResponse struct { - DryRun bool `json:"dry_run"` - LinkResults []AppPathDomain `json:"link_results"` + DryRun bool `json:"dry_run"` + StagedUpdateResults []AppPathDomain `json:"staged_update_results"` + PromoteResults []AppPathDomain `json:"promote_results"` } type AppGetResponse struct { diff --git a/tests/commander/test_reload.yaml b/tests/commander/test_reload.yaml index 279bfb5..dc002cb 100644 --- a/tests/commander/test_reload.yaml +++ b/tests/commander/test_reload.yaml @@ -183,7 +183,7 @@ tests: stdout: "2 app(s) promoted." reload0509: # reload with promote stdout command: ../clace app reload --promote "/reload_git*" - stdout: "2 app(s) reloaded, 0 app(s) approved, 2 app(s) promoted." + stdout: "4 app(s) reloaded, 0 app(s) approved, 2 app(s) promoted." reload0510: # audit stdout command: ../clace app approve --promote "/reload_git*" stdout: "2 app(s) audited, 0 app(s) approved, 2 app(s) promoted."