From 84cbadd77f615d0add71a538f0fbc283ef65cdfe Mon Sep 17 00:00:00 2001 From: Lily Hoffman <0xlilyhoffman@gmail.com> Date: Wed, 9 Jun 2021 20:19:37 +0000 Subject: [PATCH 01/13] Support setting policy template via local file or remote repo --- coder-sdk/interface.go | 3 ++ coder-sdk/workspace.go | 56 +++++++++++++++++++++ internal/cmd/workspaces.go | 100 +++++++++++++++++++++++++++++++++++++ 3 files changed, 159 insertions(+) diff --git a/coder-sdk/interface.go b/coder-sdk/interface.go index 47ed28c3..e439ca32 100644 --- a/coder-sdk/interface.go +++ b/coder-sdk/interface.go @@ -238,4 +238,7 @@ type Client interface { // RenameWorkspaceProvider changes an existing providers name field. RenameWorkspaceProvider(ctx context.Context, id string, name string) error + + // SetPolicyTemplate sets the workspace policy template + SetPolicyTemplate(ctx context.Context, templateID string, templateScope TemplateScope, dryRun bool) (*SetPolicyTemplateResponse, error) } diff --git a/coder-sdk/workspace.go b/coder-sdk/workspace.go index e48d4bc7..f4b4a10e 100644 --- a/coder-sdk/workspace.go +++ b/coder-sdk/workspace.go @@ -364,3 +364,59 @@ func (c *DefaultClient) WorkspacesByWorkspaceProvider(ctx context.Context, wpID } return workspaces, nil } + +const ( + SkipTemplateOrg = "SKIP_ORG" +) + +type TemplateScope string + +const ( + TemplateScopeSite = "site" +) + +type SetPolicyTemplateRequest struct { + TemplateID string `json:"template_id"` + Type string `json:"type"` // site, org +} + +type SetPolicyTemplateResponse struct { + MergeConflicts []*WorkspaceTemplateMergeConflict `json:"merge_conflicts"` +} + +type WorkspaceTemplateMergeConflict struct { + WorkspaceID string `json:"workspace_id"` + CurrentTemplateWarnings []string `json:"current_template_warnings"` + CurrentTemplateError *TplError `json:"current_template_errors"` + LatestTemplateWarnings []string `json:"latest_template_warnings"` + LatestTemplateError *TplError `json:"latest_template_errors"` + CurrentTemplateIsLatest bool `json:"current_template_is_latest"` + Message string `json:"message"` +} +type TplError struct { + // Msgs are the human facing strings to present to the user. Since there can be multiple + // problems with a template, there might be multiple strings + Msgs []string `json:"messages"` +} + +func (c *DefaultClient) SetPolicyTemplate(ctx context.Context, templateID string, templateScope TemplateScope, dryRun bool) (*SetPolicyTemplateResponse, error) { + var ( + resp SetPolicyTemplateResponse + query = url.Values{} + ) + + req := SetPolicyTemplateRequest{ + TemplateID: templateID, + Type: string(templateScope), + } + + if dryRun { + query.Set("dry-run", "true") + } + + if err := c.requestBody(ctx, http.MethodPost, "/api/private/workspaces/template/policy", req, &resp, withQueryParams(query)); err != nil { + return nil, err + } + + return &resp, nil +} diff --git a/internal/cmd/workspaces.go b/internal/cmd/workspaces.go index b8017baa..69a1c3fe 100644 --- a/internal/cmd/workspaces.go +++ b/internal/cmd/workspaces.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "io/ioutil" + "strings" "cdr.dev/coder-cli/coder-sdk" "cdr.dev/coder-cli/internal/coderutil" @@ -47,6 +48,7 @@ func workspacesCmd() *cobra.Command { workspaceFromConfigCmd(true), workspaceFromConfigCmd(false), editWorkspaceCmd(), + setPolicyTemplate(), ) return cmd } @@ -752,3 +754,101 @@ func buildUpdateReq(ctx context.Context, client coder.Client, conf updateConf) ( } return &updateReq, nil } + +func setPolicyTemplate() *cobra.Command { + var ( + ref string + repo string + filepath string + dryRun bool + ) + + cmd := &cobra.Command{ + Use: "policy-template", + Short: "Set workspace policy template", + Long: "Set workspace policy template", + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + client, err := newClient(ctx, true) + if err != nil { + return err + } + + if filepath == "" && ref == "" && repo == "" { + return clog.Error("Must specify a configuration source", + "A template source is either sourced from a local file (-f) or from a git repository (--repo-url and --ref)", + ) + } + + var rd io.Reader + if filepath != "" { + b, err := ioutil.ReadFile(filepath) + if err != nil { + return xerrors.Errorf("read local file: %w", err) + } + rd = bytes.NewReader(b) + } + + req := coder.ParseTemplateRequest{ + RepoURL: repo, + Ref: ref, + Local: rd, + OrgID: coder.SkipTemplateOrg, + Filepath: ".coder/coder.yaml", + } + + version, err := client.ParseTemplate(ctx, req) + if err != nil { + return handleAPIError(err) + } + + resp, err := client.SetPolicyTemplate(ctx, version.TemplateID, coder.TemplateScopeSite, dryRun) + if err != nil { + return handleAPIError(err) + } + + for _, mc := range resp.MergeConflicts { + workspace, err := client.WorkspaceByID(ctx, mc.WorkspaceID) + if err == nil { + fmt.Printf("Workspace %q in organization %q:\n", workspace.Name, workspace.OrganizationID) + } + + if mc.Message != "" { + fmt.Println(mc.Message) + } + + currentConflicts := len(mc.CurrentTemplateWarnings) != 0 || mc.CurrentTemplateError != nil + updateConflicts := len(mc.LatestTemplateWarnings) != 0 || mc.LatestTemplateError != nil + + if !currentConflicts && !updateConflicts { + fmt.Println("No conflicts") + return nil + } + + if len(mc.CurrentTemplateWarnings) != 0 { + fmt.Printf("Warnings: \n%s\n", strings.Join(mc.CurrentTemplateWarnings, "\n")) + } + if mc.CurrentTemplateError != nil { + fmt.Printf("Errors: \n%s\n", strings.Join(mc.CurrentTemplateError.Msgs, "\n")) + } + + if !mc.CurrentTemplateIsLatest && updateConflicts { + fmt.Println("If workspace is updated to the latest template:") + if len(mc.LatestTemplateWarnings) != 0 { + fmt.Printf("Latest Template Warnings: \n%s\n", strings.Join(mc.LatestTemplateWarnings, "\n")) + } + if mc.LatestTemplateError != nil { + fmt.Printf("Latest Template Errors: \n%s\n", strings.Join(mc.LatestTemplateError.Msgs, "\n")) + } + } + } + return nil + }, + } + cmd.Flags().BoolVarP(&dryRun, "dry-run", "", false, "skip setting policy template, but view errors/warnings about how this policy template would impact existing workspaces") + cmd.Flags().StringVarP(&filepath, "filepath", "f", "", "full path to local policy template file.") + cmd.Flags().StringVarP(&ref, "ref", "", "master", "git reference to pull template from. May be a branch, tag, or commit hash.") + cmd.Flags().StringVarP(&repo, "repo-url", "r", "", "URL of the git repository to pull the config from. Config file must live in '.coder/coder.yaml'.") + + return cmd +} From 8bf005c1f4cbd169f8ce57e95ad3b701e6a61621 Mon Sep 17 00:00:00 2001 From: Lily Hoffman <0xlilyhoffman@gmail.com> Date: Wed, 9 Jun 2021 21:00:40 +0000 Subject: [PATCH 02/13] Print workspace id on failure to fetch name/org --- internal/cmd/workspaces.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/cmd/workspaces.go b/internal/cmd/workspaces.go index 69a1c3fe..3122abce 100644 --- a/internal/cmd/workspaces.go +++ b/internal/cmd/workspaces.go @@ -809,7 +809,9 @@ func setPolicyTemplate() *cobra.Command { for _, mc := range resp.MergeConflicts { workspace, err := client.WorkspaceByID(ctx, mc.WorkspaceID) - if err == nil { + if err != nil { + fmt.Printf("Workspace %q:\n", mc.WorkspaceID) + } else { fmt.Printf("Workspace %q in organization %q:\n", workspace.Name, workspace.OrganizationID) } From b0b8e17691cb4881b9dd7408955a2dc8a1f63dca Mon Sep 17 00:00:00 2001 From: Lily Hoffman <0xlilyhoffman@gmail.com> Date: Wed, 9 Jun 2021 21:16:36 +0000 Subject: [PATCH 03/13] Lint --- coder-sdk/workspace.go | 42 ++++++++++++++++++++++++++++++++++++++ internal/cmd/workspaces.go | 30 +-------------------------- 2 files changed, 43 insertions(+), 29 deletions(-) diff --git a/coder-sdk/workspace.go b/coder-sdk/workspace.go index f4b4a10e..5bd694c9 100644 --- a/coder-sdk/workspace.go +++ b/coder-sdk/workspace.go @@ -2,9 +2,11 @@ package coder import ( "context" + "fmt" "io" "net/http" "net/url" + "strings" "time" "cdr.dev/wsep" @@ -366,12 +368,14 @@ func (c *DefaultClient) WorkspacesByWorkspaceProvider(ctx context.Context, wpID } const ( + // SkipTemplateOrg allows skipping checks on organizations. SkipTemplateOrg = "SKIP_ORG" ) type TemplateScope string const ( + // TemplateScopeSite is the scope for a site wide policy template. TemplateScopeSite = "site" ) @@ -393,6 +397,44 @@ type WorkspaceTemplateMergeConflict struct { CurrentTemplateIsLatest bool `json:"current_template_is_latest"` Message string `json:"message"` } + +func (mc WorkspaceTemplateMergeConflict) String() string { + var sb strings.Builder + + if mc.Message != "" { + sb.WriteString(mc.Message) + } + + currentConflicts := len(mc.CurrentTemplateWarnings) != 0 || mc.CurrentTemplateError != nil + updateConflicts := len(mc.LatestTemplateWarnings) != 0 || mc.LatestTemplateError != nil + + if !currentConflicts && !updateConflicts { + sb.WriteString("No conflicts\n") + return sb.String() + } + + if currentConflicts { + if len(mc.CurrentTemplateWarnings) != 0 { + sb.WriteString(fmt.Sprintf("Warnings: \n%s\n", strings.Join(mc.CurrentTemplateWarnings, "\n"))) + } + if mc.CurrentTemplateError != nil { + sb.WriteString(fmt.Sprintf("Errors: \n%s\n", strings.Join(mc.CurrentTemplateError.Msgs, "\n"))) + } + } + + if !mc.CurrentTemplateIsLatest && updateConflicts { + sb.WriteString("If workspace is updated to the latest template:\n") + if len(mc.LatestTemplateWarnings) != 0 { + sb.WriteString(fmt.Sprintf("Warnings: \n%s\n", strings.Join(mc.LatestTemplateWarnings, "\n"))) + } + if mc.LatestTemplateError != nil { + sb.WriteString(fmt.Sprintf("Errors: \n%s\n", strings.Join(mc.LatestTemplateError.Msgs, "\n"))) + } + } + + return sb.String() +} + type TplError struct { // Msgs are the human facing strings to present to the user. Since there can be multiple // problems with a template, there might be multiple strings diff --git a/internal/cmd/workspaces.go b/internal/cmd/workspaces.go index 3122abce..6cba8b1b 100644 --- a/internal/cmd/workspaces.go +++ b/internal/cmd/workspaces.go @@ -7,7 +7,6 @@ import ( "fmt" "io" "io/ioutil" - "strings" "cdr.dev/coder-cli/coder-sdk" "cdr.dev/coder-cli/internal/coderutil" @@ -815,34 +814,7 @@ func setPolicyTemplate() *cobra.Command { fmt.Printf("Workspace %q in organization %q:\n", workspace.Name, workspace.OrganizationID) } - if mc.Message != "" { - fmt.Println(mc.Message) - } - - currentConflicts := len(mc.CurrentTemplateWarnings) != 0 || mc.CurrentTemplateError != nil - updateConflicts := len(mc.LatestTemplateWarnings) != 0 || mc.LatestTemplateError != nil - - if !currentConflicts && !updateConflicts { - fmt.Println("No conflicts") - return nil - } - - if len(mc.CurrentTemplateWarnings) != 0 { - fmt.Printf("Warnings: \n%s\n", strings.Join(mc.CurrentTemplateWarnings, "\n")) - } - if mc.CurrentTemplateError != nil { - fmt.Printf("Errors: \n%s\n", strings.Join(mc.CurrentTemplateError.Msgs, "\n")) - } - - if !mc.CurrentTemplateIsLatest && updateConflicts { - fmt.Println("If workspace is updated to the latest template:") - if len(mc.LatestTemplateWarnings) != 0 { - fmt.Printf("Latest Template Warnings: \n%s\n", strings.Join(mc.LatestTemplateWarnings, "\n")) - } - if mc.LatestTemplateError != nil { - fmt.Printf("Latest Template Errors: \n%s\n", strings.Join(mc.LatestTemplateError.Msgs, "\n")) - } - } + fmt.Println(mc.String()) } return nil }, From 72637a5614534b783585f2b4173d17066cb4633f Mon Sep 17 00:00:00 2001 From: Lily Hoffman <0xlilyhoffman@gmail.com> Date: Wed, 9 Jun 2021 21:17:14 +0000 Subject: [PATCH 04/13] Gendocs --- docs/coder_workspaces.md | 1 + docs/coder_workspaces_policy-template.md | 32 ++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 docs/coder_workspaces_policy-template.md diff --git a/docs/coder_workspaces.md b/docs/coder_workspaces.md index 4402d92c..a7ec4615 100644 --- a/docs/coder_workspaces.md +++ b/docs/coder_workspaces.md @@ -26,6 +26,7 @@ Perform operations on the Coder workspaces owned by the active user. * [coder workspaces edit](coder_workspaces_edit.md) - edit an existing workspace and initiate a rebuild. * [coder workspaces edit-from-config](coder_workspaces_edit-from-config.md) - change the template a workspace is tracking * [coder workspaces ls](coder_workspaces_ls.md) - list all workspaces owned by the active user +* [coder workspaces policy-template](coder_workspaces_policy-template.md) - Set workspace policy template * [coder workspaces rebuild](coder_workspaces_rebuild.md) - rebuild a Coder workspace * [coder workspaces rm](coder_workspaces_rm.md) - remove Coder workspaces by name * [coder workspaces stop](coder_workspaces_stop.md) - stop Coder workspaces by name diff --git a/docs/coder_workspaces_policy-template.md b/docs/coder_workspaces_policy-template.md new file mode 100644 index 00000000..1f2613e4 --- /dev/null +++ b/docs/coder_workspaces_policy-template.md @@ -0,0 +1,32 @@ +## coder workspaces policy-template + +Set workspace policy template + +### Synopsis + +Set workspace policy template + +``` +coder workspaces policy-template [flags] +``` + +### Options + +``` + --dry-run skip setting policy template, but view errors/warnings about how this policy template would impact existing workspaces + -f, --filepath string full path to local policy template file. + -h, --help help for policy-template + --ref string git reference to pull template from. May be a branch, tag, or commit hash. (default "master") + -r, --repo-url string URL of the git repository to pull the config from. Config file must live in '.coder/coder.yaml'. +``` + +### Options inherited from parent commands + +``` + -v, --verbose show verbose output +``` + +### SEE ALSO + +* [coder workspaces](coder_workspaces.md) - Interact with Coder workspaces + From 637920272edb88017999d710c6852a26ce16d48a Mon Sep 17 00:00:00 2001 From: Lily Hoffman <0xlilyhoffman@gmail.com> Date: Fri, 18 Jun 2021 17:38:38 +0000 Subject: [PATCH 05/13] Remove support for remote tracked policy templates --- internal/cmd/workspaces.go | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/internal/cmd/workspaces.go b/internal/cmd/workspaces.go index 6cba8b1b..a1910f37 100644 --- a/internal/cmd/workspaces.go +++ b/internal/cmd/workspaces.go @@ -773,20 +773,16 @@ func setPolicyTemplate() *cobra.Command { return err } - if filepath == "" && ref == "" && repo == "" { - return clog.Error("Must specify a configuration source", - "A template source is either sourced from a local file (-f) or from a git repository (--repo-url and --ref)", - ) + if filepath == "" { + return clog.Error("Missing required parameter --filepath or -f", "") } var rd io.Reader - if filepath != "" { - b, err := ioutil.ReadFile(filepath) - if err != nil { - return xerrors.Errorf("read local file: %w", err) - } - rd = bytes.NewReader(b) + b, err := ioutil.ReadFile(filepath) + if err != nil { + return xerrors.Errorf("read local file: %w", err) } + rd = bytes.NewReader(b) req := coder.ParseTemplateRequest{ RepoURL: repo, @@ -821,8 +817,6 @@ func setPolicyTemplate() *cobra.Command { } cmd.Flags().BoolVarP(&dryRun, "dry-run", "", false, "skip setting policy template, but view errors/warnings about how this policy template would impact existing workspaces") cmd.Flags().StringVarP(&filepath, "filepath", "f", "", "full path to local policy template file.") - cmd.Flags().StringVarP(&ref, "ref", "", "master", "git reference to pull template from. May be a branch, tag, or commit hash.") - cmd.Flags().StringVarP(&repo, "repo-url", "r", "", "URL of the git repository to pull the config from. Config file must live in '.coder/coder.yaml'.") return cmd } From 84ed3bbcb564f9d435a62c45d39af4f106fdd604 Mon Sep 17 00:00:00 2001 From: Lily Hoffman <0xlilyhoffman@gmail.com> Date: Fri, 18 Jun 2021 17:42:16 +0000 Subject: [PATCH 06/13] Gendocs --- docs/coder_workspaces_policy-template.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/coder_workspaces_policy-template.md b/docs/coder_workspaces_policy-template.md index 1f2613e4..da6e67d4 100644 --- a/docs/coder_workspaces_policy-template.md +++ b/docs/coder_workspaces_policy-template.md @@ -16,8 +16,6 @@ coder workspaces policy-template [flags] --dry-run skip setting policy template, but view errors/warnings about how this policy template would impact existing workspaces -f, --filepath string full path to local policy template file. -h, --help help for policy-template - --ref string git reference to pull template from. May be a branch, tag, or commit hash. (default "master") - -r, --repo-url string URL of the git repository to pull the config from. Config file must live in '.coder/coder.yaml'. ``` ### Options inherited from parent commands From d831363d0fa981df6bf8debd65f088687dd9f2b9 Mon Sep 17 00:00:00 2001 From: Lily Hoffman <0xlilyhoffman@gmail.com> Date: Fri, 18 Jun 2021 18:54:15 +0000 Subject: [PATCH 07/13] Add flag for 'scope' with default value 'site' --- docs/coder_workspaces_policy-template.md | 1 + internal/cmd/workspaces.go | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/coder_workspaces_policy-template.md b/docs/coder_workspaces_policy-template.md index da6e67d4..9cb4a45f 100644 --- a/docs/coder_workspaces_policy-template.md +++ b/docs/coder_workspaces_policy-template.md @@ -16,6 +16,7 @@ coder workspaces policy-template [flags] --dry-run skip setting policy template, but view errors/warnings about how this policy template would impact existing workspaces -f, --filepath string full path to local policy template file. -h, --help help for policy-template + --scope string scope of impact for the policy template. Supported values: site (default "site") ``` ### Options inherited from parent commands diff --git a/internal/cmd/workspaces.go b/internal/cmd/workspaces.go index a1910f37..6e3b22c6 100644 --- a/internal/cmd/workspaces.go +++ b/internal/cmd/workspaces.go @@ -760,6 +760,7 @@ func setPolicyTemplate() *cobra.Command { repo string filepath string dryRun bool + scope string ) cmd := &cobra.Command{ @@ -773,6 +774,10 @@ func setPolicyTemplate() *cobra.Command { return err } + if scope != coder.TemplateScopeSite { + return clog.Error("Invalid 'scope' value", "Valid scope values: site") + } + if filepath == "" { return clog.Error("Missing required parameter --filepath or -f", "") } @@ -797,7 +802,7 @@ func setPolicyTemplate() *cobra.Command { return handleAPIError(err) } - resp, err := client.SetPolicyTemplate(ctx, version.TemplateID, coder.TemplateScopeSite, dryRun) + resp, err := client.SetPolicyTemplate(ctx, version.TemplateID, coder.TemplateScope(scope), dryRun) if err != nil { return handleAPIError(err) } @@ -817,6 +822,6 @@ func setPolicyTemplate() *cobra.Command { } cmd.Flags().BoolVarP(&dryRun, "dry-run", "", false, "skip setting policy template, but view errors/warnings about how this policy template would impact existing workspaces") cmd.Flags().StringVarP(&filepath, "filepath", "f", "", "full path to local policy template file.") - + cmd.Flags().StringVar(&scope, "scope", "site", "scope of impact for the policy template. Supported values: site") return cmd } From 56a3c7558508e50eecf77dce20b8cdb2fabf9a2e Mon Sep 17 00:00:00 2001 From: Lily Hoffman <0xlilyhoffman@gmail.com> Date: Fri, 18 Jun 2021 18:57:57 +0000 Subject: [PATCH 08/13] Support restoring policy template to default --- docs/coder_workspaces_policy-template.md | 1 + internal/cmd/workspaces.go | 56 +++++++++++++----------- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/docs/coder_workspaces_policy-template.md b/docs/coder_workspaces_policy-template.md index 9cb4a45f..6cb618db 100644 --- a/docs/coder_workspaces_policy-template.md +++ b/docs/coder_workspaces_policy-template.md @@ -13,6 +13,7 @@ coder workspaces policy-template [flags] ### Options ``` + --default Restore policy template to default value --dry-run skip setting policy template, but view errors/warnings about how this policy template would impact existing workspaces -f, --filepath string full path to local policy template file. -h, --help help for policy-template diff --git a/internal/cmd/workspaces.go b/internal/cmd/workspaces.go index 6e3b22c6..e1001ce0 100644 --- a/internal/cmd/workspaces.go +++ b/internal/cmd/workspaces.go @@ -756,11 +756,12 @@ func buildUpdateReq(ctx context.Context, client coder.Client, conf updateConf) ( func setPolicyTemplate() *cobra.Command { var ( - ref string - repo string - filepath string - dryRun bool - scope string + ref string + repo string + filepath string + dryRun bool + defaultTemplate bool + scope string ) cmd := &cobra.Command{ @@ -778,31 +779,35 @@ func setPolicyTemplate() *cobra.Command { return clog.Error("Invalid 'scope' value", "Valid scope values: site") } - if filepath == "" { - return clog.Error("Missing required parameter --filepath or -f", "") + if filepath == "" && !defaultTemplate { + return clog.Error("Missing required parameter --filepath or --default", "Must specify a template to set") } - var rd io.Reader - b, err := ioutil.ReadFile(filepath) - if err != nil { - return xerrors.Errorf("read local file: %w", err) - } - rd = bytes.NewReader(b) - - req := coder.ParseTemplateRequest{ - RepoURL: repo, - Ref: ref, - Local: rd, - OrgID: coder.SkipTemplateOrg, - Filepath: ".coder/coder.yaml", - } + templateID := "" + if filepath != "" { + var rd io.Reader + b, err := ioutil.ReadFile(filepath) + if err != nil { + return xerrors.Errorf("read local file: %w", err) + } + rd = bytes.NewReader(b) + + req := coder.ParseTemplateRequest{ + RepoURL: repo, + Ref: ref, + Local: rd, + OrgID: coder.SkipTemplateOrg, + Filepath: ".coder/coder.yaml", + } - version, err := client.ParseTemplate(ctx, req) - if err != nil { - return handleAPIError(err) + version, err := client.ParseTemplate(ctx, req) + if err != nil { + return handleAPIError(err) + } + templateID = version.TemplateID } - resp, err := client.SetPolicyTemplate(ctx, version.TemplateID, coder.TemplateScope(scope), dryRun) + resp, err := client.SetPolicyTemplate(ctx, templateID, coder.TemplateScope(scope), dryRun) if err != nil { return handleAPIError(err) } @@ -823,5 +828,6 @@ func setPolicyTemplate() *cobra.Command { cmd.Flags().BoolVarP(&dryRun, "dry-run", "", false, "skip setting policy template, but view errors/warnings about how this policy template would impact existing workspaces") cmd.Flags().StringVarP(&filepath, "filepath", "f", "", "full path to local policy template file.") cmd.Flags().StringVar(&scope, "scope", "site", "scope of impact for the policy template. Supported values: site") + cmd.Flags().BoolVar(&defaultTemplate, "default", false, "Restore policy template to default configuration") return cmd } From abb7b7ae19778a91e2cbd4d942516901ad7112b5 Mon Sep 17 00:00:00 2001 From: Lily Hoffman <0xlilyhoffman@gmail.com> Date: Fri, 18 Jun 2021 19:06:43 +0000 Subject: [PATCH 09/13] Gendocs --- docs/coder_workspaces_policy-template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/coder_workspaces_policy-template.md b/docs/coder_workspaces_policy-template.md index 6cb618db..ff448d3e 100644 --- a/docs/coder_workspaces_policy-template.md +++ b/docs/coder_workspaces_policy-template.md @@ -13,7 +13,7 @@ coder workspaces policy-template [flags] ### Options ``` - --default Restore policy template to default value + --default Restore policy template to default configuration --dry-run skip setting policy template, but view errors/warnings about how this policy template would impact existing workspaces -f, --filepath string full path to local policy template file. -h, --help help for policy-template From 6d3d0616efe4b02f17f4a74c2f4f67d678bdb0fc Mon Sep 17 00:00:00 2001 From: Lily Hoffman <0xlilyhoffman@gmail.com> Date: Fri, 18 Jun 2021 19:12:27 +0000 Subject: [PATCH 10/13] Update command description --- docs/coder_workspaces_policy-template.md | 2 +- internal/cmd/workspaces.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/coder_workspaces_policy-template.md b/docs/coder_workspaces_policy-template.md index ff448d3e..36bf34fc 100644 --- a/docs/coder_workspaces_policy-template.md +++ b/docs/coder_workspaces_policy-template.md @@ -4,7 +4,7 @@ Set workspace policy template ### Synopsis -Set workspace policy template +Set workspace policy template or restore to default configuration. This feature is for site admins only. ``` coder workspaces policy-template [flags] diff --git a/internal/cmd/workspaces.go b/internal/cmd/workspaces.go index e1001ce0..dbea9aca 100644 --- a/internal/cmd/workspaces.go +++ b/internal/cmd/workspaces.go @@ -767,7 +767,7 @@ func setPolicyTemplate() *cobra.Command { cmd := &cobra.Command{ Use: "policy-template", Short: "Set workspace policy template", - Long: "Set workspace policy template", + Long: "Set workspace policy template or restore to default configuration. This feature is for site admins only.", RunE: func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() client, err := newClient(ctx, true) From 7e08daa40c025eb71149938e56a077dc4a361a90 Mon Sep 17 00:00:00 2001 From: Lily Hoffman <0xlilyhoffman@gmail.com> Date: Fri, 18 Jun 2021 20:06:04 +0000 Subject: [PATCH 11/13] Add summary of broken/impacted workspaces when setting policy template --- coder-sdk/workspace.go | 49 +++++++++++++++++++++++++++++++++++++- internal/cmd/workspaces.go | 4 ++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/coder-sdk/workspace.go b/coder-sdk/workspace.go index 5bd694c9..4f78869d 100644 --- a/coder-sdk/workspace.go +++ b/coder-sdk/workspace.go @@ -409,7 +409,7 @@ func (mc WorkspaceTemplateMergeConflict) String() string { updateConflicts := len(mc.LatestTemplateWarnings) != 0 || mc.LatestTemplateError != nil if !currentConflicts && !updateConflicts { - sb.WriteString("No conflicts\n") + sb.WriteString("No workspace conflicts\n") return sb.String() } @@ -435,6 +435,53 @@ func (mc WorkspaceTemplateMergeConflict) String() string { return sb.String() } +type WorkspaceTemplateMergeConflicts []*WorkspaceTemplateMergeConflict + +func (mcs WorkspaceTemplateMergeConflicts) Summary() string { + var ( + sb strings.Builder + currentWarnings int + updateWarnings int + currentErrors int + updateErrors int + ) + + for _, mc := range mcs { + if len(mc.CurrentTemplateWarnings) != 0 { + currentWarnings++ + } + if len(mc.LatestTemplateWarnings) != 0 { + updateWarnings++ + } + if mc.CurrentTemplateError != nil { + currentErrors++ + } + if mc.LatestTemplateError != nil { + updateErrors++ + } + } + + if currentErrors == 0 && updateErrors == 0 && currentWarnings == 0 && updateWarnings == 0 { + sb.WriteString("No workspace conflicts\n") + return sb.String() + } + + if currentErrors != 0 { + sb.WriteString(fmt.Sprintf("%d workspaces will not be able to be rebuilt\n", currentErrors)) + } + if updateErrors != 0 { + sb.WriteString(fmt.Sprintf("%d workspaces will not be able to be rebuilt if updated to the latest version\n", updateErrors)) + } + if currentWarnings != 0 { + sb.WriteString(fmt.Sprintf("%d workspaces will be impacted\n", currentWarnings)) + } + if updateWarnings != 0 { + sb.WriteString(fmt.Sprintf("%d workspaces will be impacted if updated to the latest version\n", updateWarnings)) + } + + return sb.String() +} + type TplError struct { // Msgs are the human facing strings to present to the user. Since there can be multiple // problems with a template, there might be multiple strings diff --git a/internal/cmd/workspaces.go b/internal/cmd/workspaces.go index dbea9aca..d1135cf4 100644 --- a/internal/cmd/workspaces.go +++ b/internal/cmd/workspaces.go @@ -822,6 +822,10 @@ func setPolicyTemplate() *cobra.Command { fmt.Println(mc.String()) } + + fmt.Println("Summary:") + fmt.Println(coder.WorkspaceTemplateMergeConflicts(resp.MergeConflicts).Summary()) + return nil }, } From fcfcc4edf303a65652e1f0696b86c4445f5892d3 Mon Sep 17 00:00:00 2001 From: Lily Hoffman <0xlilyhoffman@gmail.com> Date: Mon, 21 Jun 2021 22:37:18 +0000 Subject: [PATCH 12/13] Refine string building --- coder-sdk/workspace.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/coder-sdk/workspace.go b/coder-sdk/workspace.go index 4f78869d..9b8dd5ed 100644 --- a/coder-sdk/workspace.go +++ b/coder-sdk/workspace.go @@ -415,20 +415,20 @@ func (mc WorkspaceTemplateMergeConflict) String() string { if currentConflicts { if len(mc.CurrentTemplateWarnings) != 0 { - sb.WriteString(fmt.Sprintf("Warnings: \n%s\n", strings.Join(mc.CurrentTemplateWarnings, "\n"))) + fmt.Fprintf(&sb, "Warnings: \n%s\n", strings.Join(mc.CurrentTemplateWarnings, "\n")) } if mc.CurrentTemplateError != nil { - sb.WriteString(fmt.Sprintf("Errors: \n%s\n", strings.Join(mc.CurrentTemplateError.Msgs, "\n"))) + fmt.Fprintf(&sb, "Errors: \n%s\n", strings.Join(mc.CurrentTemplateError.Msgs, "\n")) } } if !mc.CurrentTemplateIsLatest && updateConflicts { sb.WriteString("If workspace is updated to the latest template:\n") if len(mc.LatestTemplateWarnings) != 0 { - sb.WriteString(fmt.Sprintf("Warnings: \n%s\n", strings.Join(mc.LatestTemplateWarnings, "\n"))) + fmt.Fprintf(&sb, "Warnings: \n%s\n", strings.Join(mc.LatestTemplateWarnings, "\n")) } if mc.LatestTemplateError != nil { - sb.WriteString(fmt.Sprintf("Errors: \n%s\n", strings.Join(mc.LatestTemplateError.Msgs, "\n"))) + fmt.Fprintf(&sb, "Errors: \n%s\n", strings.Join(mc.LatestTemplateError.Msgs, "\n")) } } From cd8242fc97c397c58881e85e0bbfd81dfed83377 Mon Sep 17 00:00:00 2001 From: Lily Hoffman <0xlilyhoffman@gmail.com> Date: Mon, 21 Jun 2021 23:05:03 +0000 Subject: [PATCH 13/13] Refine string building pt2 --- coder-sdk/workspace.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/coder-sdk/workspace.go b/coder-sdk/workspace.go index 9b8dd5ed..b6071b5a 100644 --- a/coder-sdk/workspace.go +++ b/coder-sdk/workspace.go @@ -467,16 +467,16 @@ func (mcs WorkspaceTemplateMergeConflicts) Summary() string { } if currentErrors != 0 { - sb.WriteString(fmt.Sprintf("%d workspaces will not be able to be rebuilt\n", currentErrors)) + fmt.Fprintf(&sb, "%d workspaces will not be able to be rebuilt\n", currentErrors) } if updateErrors != 0 { - sb.WriteString(fmt.Sprintf("%d workspaces will not be able to be rebuilt if updated to the latest version\n", updateErrors)) + fmt.Fprintf(&sb, "%d workspaces will not be able to be rebuilt if updated to the latest version\n", updateErrors) } if currentWarnings != 0 { - sb.WriteString(fmt.Sprintf("%d workspaces will be impacted\n", currentWarnings)) + fmt.Fprintf(&sb, "%d workspaces will be impacted\n", currentWarnings) } if updateWarnings != 0 { - sb.WriteString(fmt.Sprintf("%d workspaces will be impacted if updated to the latest version\n", updateWarnings)) + fmt.Fprintf(&sb, "%d workspaces will be impacted if updated to the latest version\n", updateWarnings) } return sb.String()