mirror of
https://github.com/coder/coder.git
synced 2025-07-12 00:14:10 +00:00
fix(cli): allow specifying empty provisioner tag set with --provisioner-tag="-" (#18205)
Relates to https://github.com/coder/coder/issues/17818 Note: due to limitations in `cobra/serpent` I ended up having to use `-` to signify absence of provisioner tags. This value is not a valid key-value pair and thus not a valid tag.
This commit is contained in:
@ -8,6 +8,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -80,6 +81,46 @@ func (r *RootCmd) templatePush() *serpent.Command {
|
|||||||
createTemplate = true
|
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 = "<none>"
|
||||||
|
}
|
||||||
|
cliui.Info(inv.Stderr, "Provisioner tags: "+cliui.Code(tagStr))
|
||||||
|
}
|
||||||
|
|
||||||
err = uploadFlags.checkForLockfile(inv)
|
err = uploadFlags.checkForLockfile(inv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("check for lockfile: %w", err)
|
return xerrors.Errorf("check for lockfile: %w", err)
|
||||||
@ -104,21 +145,6 @@ func (r *RootCmd) templatePush() *serpent.Command {
|
|||||||
return err
|
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(
|
userVariableValues, err := codersdk.ParseUserVariableValues(
|
||||||
varsFiles,
|
varsFiles,
|
||||||
variablesFile,
|
variablesFile,
|
||||||
@ -214,7 +240,7 @@ func (r *RootCmd) templatePush() *serpent.Command {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Flag: "provisioner-tag",
|
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),
|
Value: serpent.StringArrayOf(&provisionerTags),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -602,7 +602,7 @@ func TestTemplatePush(t *testing.T) {
|
|||||||
templateVersion = coderdtest.AwaitTemplateVersionJobCompleted(t, client, templateVersion.ID)
|
templateVersion = coderdtest.AwaitTemplateVersionJobCompleted(t, client, templateVersion.ID)
|
||||||
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, 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{
|
source := clitest.CreateTemplateVersionSource(t, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: echo.ApplyComplete,
|
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)
|
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.Run("DoNotChangeTags", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
@ -33,7 +33,10 @@ OPTIONS:
|
|||||||
generated if not provided.
|
generated if not provided.
|
||||||
|
|
||||||
--provisioner-tag string-array
|
--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
|
--var string-array
|
||||||
Alias of --variable.
|
Alias of --variable.
|
||||||
|
2
docs/reference/cli/templates_push.md
generated
2
docs/reference/cli/templates_push.md
generated
@ -41,7 +41,7 @@ Alias of --variable.
|
|||||||
|------|---------------------------|
|
|------|---------------------------|
|
||||||
| Type | <code>string-array</code> |
|
| Type | <code>string-array</code> |
|
||||||
|
|
||||||
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
|
### --name
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user