diff --git a/cli/templatepush.go b/cli/templatepush.go index 6f8edf61b5085..312c8a466ec50 100644 --- a/cli/templatepush.go +++ b/cli/templatepush.go @@ -8,6 +8,7 @@ import ( "net/http" "os" "path/filepath" + "slices" "strings" "time" @@ -80,6 +81,46 @@ func (r *RootCmd) templatePush() *serpent.Command { createTemplate = true } + var tags map[string]string + // Passing --provisioner-tag="-" allows the user to clear all provisioner tags. + if len(provisionerTags) == 1 && strings.TrimSpace(provisionerTags[0]) == "-" { + cliui.Warn(inv.Stderr, "Not reusing provisioner tags from the previous template version.") + tags = map[string]string{} + } else { + tags, err = ParseProvisionerTags(provisionerTags) + if err != nil { + return err + } + + // If user hasn't provided new provisioner tags, inherit ones from the active template version. + if len(tags) == 0 && template.ActiveVersionID != uuid.Nil { + templateVersion, err := client.TemplateVersion(inv.Context(), template.ActiveVersionID) + if err != nil { + return err + } + tags = templateVersion.Job.Tags + cliui.Info(inv.Stderr, "Re-using provisioner tags from the active template version.") + cliui.Info(inv.Stderr, "Tip: You can override these tags by passing "+cliui.Code(`--provisioner-tag="key=value"`)+".") + cliui.Info(inv.Stderr, " You can also clear all provisioner tags by passing "+cliui.Code(`--provisioner-tag="-"`)+".") + } + } + + { // For clarity, display provisioner tags to the user. + var tmp []string + for k, v := range tags { + if k == provisionersdk.TagScope || k == provisionersdk.TagOwner { + continue + } + tmp = append(tmp, fmt.Sprintf("%s=%q", k, v)) + } + slices.Sort(tmp) + tagStr := strings.Join(tmp, " ") + if len(tmp) == 0 { + tagStr = "" + } + cliui.Info(inv.Stderr, "Provisioner tags: "+cliui.Code(tagStr)) + } + err = uploadFlags.checkForLockfile(inv) if err != nil { return xerrors.Errorf("check for lockfile: %w", err) @@ -104,21 +145,6 @@ func (r *RootCmd) templatePush() *serpent.Command { return err } - tags, err := ParseProvisionerTags(provisionerTags) - if err != nil { - return err - } - - // If user hasn't provided new provisioner tags, inherit ones from the active template version. - if len(tags) == 0 && template.ActiveVersionID != uuid.Nil { - templateVersion, err := client.TemplateVersion(inv.Context(), template.ActiveVersionID) - if err != nil { - return err - } - tags = templateVersion.Job.Tags - inv.Logger.Info(inv.Context(), "reusing existing provisioner tags", "tags", tags) - } - userVariableValues, err := codersdk.ParseUserVariableValues( varsFiles, variablesFile, @@ -214,7 +240,7 @@ func (r *RootCmd) templatePush() *serpent.Command { }, { Flag: "provisioner-tag", - Description: "Specify a set of tags to target provisioner daemons.", + Description: "Specify a set of tags to target provisioner daemons. If you do not specify any tags, the tags from the active template version will be reused, if available. To remove existing tags, use --provisioner-tag=\"-\".", Value: serpent.StringArrayOf(&provisionerTags), }, { diff --git a/cli/templatepush_test.go b/cli/templatepush_test.go index b8e4147e6bab4..e1a7e612f4ed6 100644 --- a/cli/templatepush_test.go +++ b/cli/templatepush_test.go @@ -602,7 +602,7 @@ func TestTemplatePush(t *testing.T) { templateVersion = coderdtest.AwaitTemplateVersionJobCompleted(t, client, templateVersion.ID) template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, templateVersion.ID) - // Push new template version without provisioner tags. CLI should reuse tags from the previous version. + // Push new template version with different provisioner tags. source := clitest.CreateTemplateVersionSource(t, &echo.Responses{ Parse: echo.ParseComplete, ProvisionApply: echo.ApplyComplete, @@ -639,6 +639,75 @@ func TestTemplatePush(t *testing.T) { require.EqualValues(t, map[string]string{"foobar": "foobaz", "owner": "", "scope": "organization"}, templateVersion.Job.Tags) }) + t.Run("DeleteTags", func(t *testing.T) { + t.Parallel() + + ctx := testutil.Context(t, testutil.WaitLong) + + // Start the first provisioner with no tags. + client, provisionerDocker, api := coderdtest.NewWithAPI(t, &coderdtest.Options{ + IncludeProvisionerDaemon: true, + ProvisionerDaemonTags: map[string]string{}, + }) + defer provisionerDocker.Close() + + // Start the second provisioner with a tag set. + provisionerFoobar := coderdtest.NewTaggedProvisionerDaemon(t, api, "provisioner-foobar", map[string]string{ + "foobar": "foobaz", + }) + defer provisionerFoobar.Close() + + owner := coderdtest.CreateFirstUser(t, client) + templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin()) + + // Create the template with initial tagged template version. + templateVersion := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil, func(ctvr *codersdk.CreateTemplateVersionRequest) { + ctvr.ProvisionerTags = map[string]string{ + "foobar": "foobaz", + } + }) + templateVersion = coderdtest.AwaitTemplateVersionJobCompleted(t, client, templateVersion.ID) + template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, templateVersion.ID) + + // Stop the tagged provisioner daemon. + provisionerFoobar.Close() + + // Push new template version with no provisioner tags. + source := clitest.CreateTemplateVersionSource(t, &echo.Responses{ + Parse: echo.ParseComplete, + ProvisionApply: echo.ApplyComplete, + }) + inv, root := clitest.New(t, "templates", "push", template.Name, "--directory", source, "--test.provisioner", string(database.ProvisionerTypeEcho), "--name", template.Name, "--provisioner-tag=\"-\"") + clitest.SetupConfig(t, templateAdmin, root) + pty := ptytest.New(t).Attach(inv) + + execDone := make(chan error) + go func() { + execDone <- inv.WithContext(ctx).Run() + }() + + matches := []struct { + match string + write string + }{ + {match: "Upload", write: "yes"}, + } + for _, m := range matches { + pty.ExpectMatch(m.match) + pty.WriteLine(m.write) + } + + require.NoError(t, <-execDone) + + // Verify template version tags + template, err := client.Template(ctx, template.ID) + require.NoError(t, err) + + templateVersion, err = client.TemplateVersion(ctx, template.ActiveVersionID) + require.NoError(t, err) + require.EqualValues(t, map[string]string{"owner": "", "scope": "organization"}, templateVersion.Job.Tags) + }) + t.Run("DoNotChangeTags", func(t *testing.T) { t.Parallel() diff --git a/cli/testdata/coder_templates_push_--help.golden b/cli/testdata/coder_templates_push_--help.golden index eee0ad34ca925..edab61a3c55f1 100644 --- a/cli/testdata/coder_templates_push_--help.golden +++ b/cli/testdata/coder_templates_push_--help.golden @@ -33,7 +33,10 @@ OPTIONS: generated if not provided. --provisioner-tag string-array - Specify a set of tags to target provisioner daemons. + Specify a set of tags to target provisioner daemons. If you do not + specify any tags, the tags from the active template version will be + reused, if available. To remove existing tags, use + --provisioner-tag="-". --var string-array Alias of --variable. diff --git a/docs/reference/cli/templates_push.md b/docs/reference/cli/templates_push.md index 46687d3fc672e..8c7901e86e408 100644 --- a/docs/reference/cli/templates_push.md +++ b/docs/reference/cli/templates_push.md @@ -41,7 +41,7 @@ Alias of --variable. |------|---------------------------| | Type | string-array | -Specify a set of tags to target provisioner daemons. +Specify a set of tags to target provisioner daemons. If you do not specify any tags, the tags from the active template version will be reused, if available. To remove existing tags, use --provisioner-tag="-". ### --name