diff --git a/cli/templateversions.go b/cli/templateversions.go index 1f6cb11d4a8dd..c90903a7c4f93 100644 --- a/cli/templateversions.go +++ b/cli/templateversions.go @@ -32,6 +32,7 @@ func (r *RootCmd) templateVersions() *serpent.Command { r.templateVersionsList(), r.archiveTemplateVersion(), r.unarchiveTemplateVersion(), + r.templateVersionsPromote(), }, } @@ -169,3 +170,66 @@ func templateVersionsToRows(activeVersionID uuid.UUID, templateVersions ...coder return rows } + +func (r *RootCmd) templateVersionsPromote() *serpent.Command { + var ( + templateName string + templateVersionName string + orgContext = NewOrganizationContext() + ) + client := new(codersdk.Client) + cmd := &serpent.Command{ + Use: "promote --template= --template-version=", + Short: "Promote a template version to active.", + Long: "Promote an existing template version to be the active version for the specified template.", + Middleware: serpent.Chain( + r.InitClient(client), + ), + Handler: func(inv *serpent.Invocation) error { + organization, err := orgContext.Selected(inv, client) + if err != nil { + return err + } + + template, err := client.TemplateByName(inv.Context(), organization.ID, templateName) + if err != nil { + return xerrors.Errorf("get template by name: %w", err) + } + + version, err := client.TemplateVersionByName(inv.Context(), template.ID, templateVersionName) + if err != nil { + return xerrors.Errorf("get template version by name: %w", err) + } + + err = client.UpdateActiveTemplateVersion(inv.Context(), template.ID, codersdk.UpdateActiveTemplateVersion{ + ID: version.ID, + }) + if err != nil { + return xerrors.Errorf("update active template version: %w", err) + } + + _, _ = fmt.Fprintf(inv.Stdout, "Successfully promoted version %q to active for template %q\n", templateVersionName, templateName) + return nil + }, + } + + cmd.Options = serpent.OptionSet{ + { + Flag: "template", + FlagShorthand: "t", + Env: "CODER_TEMPLATE_NAME", + Description: "Specify the template name.", + Required: true, + Value: serpent.StringOf(&templateName), + }, + { + Flag: "template-version", + Description: "Specify the template version name to promote.", + Env: "CODER_TEMPLATE_VERSION_NAME", + Required: true, + Value: serpent.StringOf(&templateVersionName), + }, + } + orgContext.AttachOptions(cmd) + return cmd +} diff --git a/cli/templateversions_test.go b/cli/templateversions_test.go index 8a017fb15da62..f2e2f8a38f884 100644 --- a/cli/templateversions_test.go +++ b/cli/templateversions_test.go @@ -1,12 +1,15 @@ package cli_test import ( + "context" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/coder/coder/v2/cli/clitest" "github.com/coder/coder/v2/coderd/coderdtest" + "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/pty/ptytest" ) @@ -38,3 +41,85 @@ func TestTemplateVersions(t *testing.T) { pty.ExpectMatch("Active") }) } + +func TestTemplateVersionsPromote(t *testing.T) { + t.Parallel() + + t.Run("PromoteVersion", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) + owner := coderdtest.CreateFirstUser(t, client) + + // Create a template with two versions + version1 := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, completeWithAgent()) + coderdtest.AwaitTemplateVersionJobCompleted(t, client, version1.ID) + + template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version1.ID) + + version2 := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, completeWithAgent(), func(ctvr *codersdk.CreateTemplateVersionRequest) { + ctvr.TemplateID = template.ID + ctvr.Name = "2.0.0" + }) + coderdtest.AwaitTemplateVersionJobCompleted(t, client, version2.ID) + + // Ensure version1 is active + updatedTemplate, err := client.Template(context.Background(), template.ID) + assert.NoError(t, err) + assert.Equal(t, version1.ID, updatedTemplate.ActiveVersionID) + + args := []string{ + "templates", + "versions", + "promote", + "--template", template.Name, + "--template-version", version2.Name, + } + + inv, root := clitest.New(t, args...) + //nolint:gocritic // Creating a workspace for another user requires owner permissions. + clitest.SetupConfig(t, client, root) + errC := make(chan error) + go func() { + errC <- inv.Run() + }() + + require.NoError(t, <-errC) + + // Verify that version2 is now the active version + updatedTemplate, err = client.Template(context.Background(), template.ID) + require.NoError(t, err) + assert.Equal(t, version2.ID, updatedTemplate.ActiveVersionID) + }) + + t.Run("PromoteNonExistentVersion", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) + owner := coderdtest.CreateFirstUser(t, client) + member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) + + version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil) + _ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) + template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID) + + inv, root := clitest.New(t, "templates", "versions", "promote", "--template", template.Name, "--template-version", "non-existent-version") + clitest.SetupConfig(t, member, root) + + err := inv.Run() + require.Error(t, err) + require.Contains(t, err.Error(), "get template version by name") + }) + + t.Run("PromoteVersionInvalidTemplate", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) + owner := coderdtest.CreateFirstUser(t, client) + member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) + + inv, root := clitest.New(t, "templates", "versions", "promote", "--template", "non-existent-template", "--template-version", "some-version") + clitest.SetupConfig(t, member, root) + + err := inv.Run() + require.Error(t, err) + require.Contains(t, err.Error(), "get template by name") + }) +} diff --git a/cli/testdata/coder_templates_versions_--help.golden b/cli/testdata/coder_templates_versions_--help.golden index 8d10e4a0f8d00..fa276999563d2 100644 --- a/cli/testdata/coder_templates_versions_--help.golden +++ b/cli/testdata/coder_templates_versions_--help.golden @@ -14,6 +14,7 @@ USAGE: SUBCOMMANDS: archive Archive a template version(s). list List all the versions of the specified template + promote Promote a template version to active. unarchive Unarchive a template version(s). ——— diff --git a/cli/testdata/coder_templates_versions_promote_--help.golden b/cli/testdata/coder_templates_versions_promote_--help.golden new file mode 100644 index 0000000000000..afa652aca5a3f --- /dev/null +++ b/cli/testdata/coder_templates_versions_promote_--help.golden @@ -0,0 +1,23 @@ +coder v0.0.0-devel + +USAGE: + coder templates versions promote [flags] --template= + --template-version= + + Promote a template version to active. + + Promote an existing template version to be the active version for the + specified template. + +OPTIONS: + -O, --org string, $CODER_ORGANIZATION + Select which organization (uuid or name) to use. + + -t, --template string, $CODER_TEMPLATE_NAME + Specify the template name. + + --template-version string, $CODER_TEMPLATE_VERSION_NAME + Specify the template version name to promote. + +——— +Run `coder --help` for a list of global options. diff --git a/docs/manifest.json b/docs/manifest.json index a3ebdf9b98987..9dc9e4d37dfa2 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -1314,6 +1314,11 @@ "description": "List all the versions of the specified template", "path": "reference/cli/templates_versions_list.md" }, + { + "title": "templates versions promote", + "description": "Promote a template version to active.", + "path": "reference/cli/templates_versions_promote.md" + }, { "title": "templates versions unarchive", "description": "Unarchive a template version(s).", diff --git a/docs/reference/cli/templates_versions.md b/docs/reference/cli/templates_versions.md index 21e49faa61485..5b1c3b2c2cfb8 100644 --- a/docs/reference/cli/templates_versions.md +++ b/docs/reference/cli/templates_versions.md @@ -29,3 +29,4 @@ coder templates versions | [list](./templates_versions_list.md) | List all the versions of the specified template | | [archive](./templates_versions_archive.md) | Archive a template version(s). | | [unarchive](./templates_versions_unarchive.md) | Unarchive a template version(s). | +| [promote](./templates_versions_promote.md) | Promote a template version to active. | diff --git a/docs/reference/cli/templates_versions_promote.md b/docs/reference/cli/templates_versions_promote.md new file mode 100644 index 0000000000000..30b5f1e8776c6 --- /dev/null +++ b/docs/reference/cli/templates_versions_promote.md @@ -0,0 +1,46 @@ + + +# templates versions promote + +Promote a template version to active. + +## Usage + +```console +coder templates versions promote [flags] --template= --template-version= +``` + +## Description + +```console +Promote an existing template version to be the active version for the specified template. +``` + +## Options + +### -t, --template + +| | | +| ----------- | --------------------------------- | +| Type | string | +| Environment | $CODER_TEMPLATE_NAME | + +Specify the template name. + +### --template-version + +| | | +| ----------- | ----------------------------------------- | +| Type | string | +| Environment | $CODER_TEMPLATE_VERSION_NAME | + +Specify the template version name to promote. + +### -O, --org + +| | | +| ----------- | -------------------------------- | +| Type | string | +| Environment | $CODER_ORGANIZATION | + +Select which organization (uuid or name) to use.