Skip to content

Commit 37c1960

Browse files
committed
chore: simplify presets listing under coder templates presets
1 parent 9856d7e commit 37c1960

17 files changed

+380
-240
lines changed

cli/templateversionpresets.go renamed to cli/templatepresets.go

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,50 +12,63 @@ import (
1212
"github.com/coder/serpent"
1313
)
1414

15-
func (r *RootCmd) templateVersionPresets() *serpent.Command {
15+
func (r *RootCmd) templatePresets() *serpent.Command {
1616
cmd := &serpent.Command{
1717
Use: "presets",
18-
Short: "Manage presets of the specified template version",
18+
Short: "Manage presets of the specified template",
1919
Aliases: []string{"preset"},
2020
Long: FormatExamples(
2121
Example{
22-
Description: "List presets of a specific template version",
23-
Command: "coder templates versions presets list my-template my-template-version",
22+
Description: "List presets for the active version of a template",
23+
Command: "coder templates presets list my-template",
24+
},
25+
Example{
26+
Description: "List presets for a specific version of a template",
27+
Command: "coder templates presets list my-template --template-version my-template-version",
2428
},
2529
),
2630
Handler: func(inv *serpent.Invocation) error {
2731
return inv.Command.HelpHandler(inv)
2832
},
2933
Children: []*serpent.Command{
30-
r.templateVersionPresetsList(),
34+
r.templatePresetsList(),
3135
},
3236
}
3337

3438
return cmd
3539
}
3640

37-
func (r *RootCmd) templateVersionPresetsList() *serpent.Command {
41+
func (r *RootCmd) templatePresetsList() *serpent.Command {
3842
defaultColumns := []string{
3943
"name",
4044
"parameters",
4145
"default",
4246
"desired prebuild instances",
4347
}
4448
formatter := cliui.NewOutputFormatter(
45-
cliui.TableFormat([]templateVersionPresetRow{}, defaultColumns),
49+
cliui.TableFormat([]templatePresetRow{}, defaultColumns),
4650
cliui.JSONFormat(),
4751
)
4852
client := new(codersdk.Client)
4953
orgContext := NewOrganizationContext()
5054

55+
var templateVersion string
56+
5157
cmd := &serpent.Command{
52-
Use: "list <template> <version>",
58+
Use: "list <template>",
5359
Middleware: serpent.Chain(
54-
serpent.RequireNArgs(2),
60+
serpent.RequireNArgs(1),
5561
r.InitClient(client),
5662
),
57-
Short: "List all the presets of the specified template version",
58-
Options: serpent.OptionSet{},
63+
Short: "List all presets of the specified template. Defaults to the active template version.",
64+
Options: serpent.OptionSet{
65+
{
66+
Name: "template-version",
67+
Description: "Specify a template version to list presets for. Defaults to the active version.",
68+
Flag: "template-version",
69+
Value: serpent.StringOf(&templateVersion),
70+
},
71+
},
5972
Handler: func(inv *serpent.Invocation) error {
6073
organization, err := orgContext.Selected(inv, client)
6174
if err != nil {
@@ -67,9 +80,19 @@ func (r *RootCmd) templateVersionPresetsList() *serpent.Command {
6780
return xerrors.Errorf("get template by name: %w", err)
6881
}
6982

70-
version, err := client.TemplateVersionByName(inv.Context(), template.ID, inv.Args[1])
71-
if err != nil {
72-
return xerrors.Errorf("get template version by name: %w", err)
83+
// If a template version is specified via flag, fetch that version by name
84+
var version codersdk.TemplateVersion
85+
if len(templateVersion) > 0 {
86+
version, err = client.TemplateVersionByName(inv.Context(), template.ID, templateVersion)
87+
if err != nil {
88+
return xerrors.Errorf("get template version by name: %w", err)
89+
}
90+
} else {
91+
// Otherwise, use the template's active version
92+
version, err = client.TemplateVersion(inv.Context(), template.ActiveVersionID)
93+
if err != nil {
94+
return xerrors.Errorf("get active template version: %w", err)
95+
}
7396
}
7497

7598
presets, err := client.TemplateVersionPresets(inv.Context(), version.ID)
@@ -84,7 +107,7 @@ func (r *RootCmd) templateVersionPresetsList() *serpent.Command {
84107
)
85108
}
86109

87-
rows := templateVersionPresetsToRows(presets...)
110+
rows := templatePresetsToRows(presets...)
88111
out, err := formatter.Format(inv.Context(), rows)
89112
if err != nil {
90113
return xerrors.Errorf("render table: %w", err)
@@ -100,9 +123,9 @@ func (r *RootCmd) templateVersionPresetsList() *serpent.Command {
100123
return cmd
101124
}
102125

103-
type templateVersionPresetRow struct {
126+
type templatePresetRow struct {
104127
// For json format:
105-
TemplateVersionPreset codersdk.Preset `table:"-"`
128+
TemplatePreset codersdk.Preset `table:"-"`
106129

107130
// For table format:
108131
Name string `json:"-" table:"name,default_sort"`
@@ -119,16 +142,16 @@ func formatPresetParameters(params []codersdk.PresetParameter) string {
119142
return strings.Join(paramsStr, ",")
120143
}
121144

122-
// templateVersionPresetsToRows converts a list of presets to a list of rows
145+
// templatePresetsToRows converts a list of presets to a list of rows
123146
// for outputting.
124-
func templateVersionPresetsToRows(presets ...codersdk.Preset) []templateVersionPresetRow {
125-
rows := make([]templateVersionPresetRow, len(presets))
147+
func templatePresetsToRows(presets ...codersdk.Preset) []templatePresetRow {
148+
rows := make([]templatePresetRow, len(presets))
126149
for i, preset := range presets {
127150
prebuildInstances := "-"
128151
if preset.DesiredPrebuildInstances != nil {
129152
prebuildInstances = strconv.Itoa(*preset.DesiredPrebuildInstances)
130153
}
131-
rows[i] = templateVersionPresetRow{
154+
rows[i] = templatePresetRow{
132155
Name: preset.Name,
133156
Parameters: formatPresetParameters(preset.Parameters),
134157
Default: preset.Default,

cli/templatepresets_test.go

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
package cli_test
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/coder/coder/v2/codersdk"
8+
"github.com/coder/coder/v2/testutil"
9+
10+
"github.com/coder/coder/v2/provisioner/echo"
11+
"github.com/coder/coder/v2/provisionersdk/proto"
12+
13+
"github.com/stretchr/testify/require"
14+
15+
"github.com/coder/coder/v2/cli/clitest"
16+
"github.com/coder/coder/v2/coderd/coderdtest"
17+
"github.com/coder/coder/v2/pty/ptytest"
18+
)
19+
20+
func TestTemplatePresets(t *testing.T) {
21+
t.Parallel()
22+
23+
t.Run("NoPresets", func(t *testing.T) {
24+
t.Parallel()
25+
26+
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
27+
owner := coderdtest.CreateFirstUser(t, client)
28+
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
29+
30+
// Given: a template version without presets
31+
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, templateWithPresets([]*proto.Preset{}))
32+
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
33+
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
34+
35+
// When: listing presets for that template
36+
inv, root := clitest.New(t, "templates", "presets", "list", template.Name)
37+
clitest.SetupConfig(t, member, root)
38+
39+
pty := ptytest.New(t).Attach(inv)
40+
doneChan := make(chan struct{})
41+
var runErr error
42+
go func() {
43+
defer close(doneChan)
44+
runErr = inv.Run()
45+
}()
46+
<-doneChan
47+
require.NoError(t, runErr)
48+
49+
// Should return a message when no presets are found for the given template and version.
50+
notFoundMessage := fmt.Sprintf("No presets found for template %q and template-version %q.", template.Name, version.Name)
51+
pty.ExpectRegexMatch(notFoundMessage)
52+
})
53+
54+
t.Run("ListsPresetsForDefaultTemplateVersion", func(t *testing.T) {
55+
t.Parallel()
56+
57+
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
58+
owner := coderdtest.CreateFirstUser(t, client)
59+
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
60+
61+
// Given: an active template version that includes presets
62+
presets := []*proto.Preset{
63+
{
64+
Name: "preset-multiple-params",
65+
Parameters: []*proto.PresetParameter{
66+
{
67+
Name: "k1",
68+
Value: "v1",
69+
}, {
70+
Name: "k2",
71+
Value: "v2",
72+
},
73+
},
74+
},
75+
{
76+
Name: "preset-default",
77+
Default: true,
78+
Parameters: []*proto.PresetParameter{
79+
{
80+
Name: "k1",
81+
Value: "v2",
82+
},
83+
},
84+
Prebuild: &proto.Prebuild{
85+
Instances: 0,
86+
},
87+
},
88+
{
89+
Name: "preset-prebuilds",
90+
Parameters: []*proto.PresetParameter{},
91+
Prebuild: &proto.Prebuild{
92+
Instances: 2,
93+
},
94+
},
95+
}
96+
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, templateWithPresets(presets))
97+
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
98+
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
99+
require.Equal(t, version.ID, template.ActiveVersionID)
100+
101+
// When: listing presets for that template
102+
inv, root := clitest.New(t, "templates", "presets", "list", template.Name)
103+
clitest.SetupConfig(t, member, root)
104+
105+
pty := ptytest.New(t).Attach(inv)
106+
doneChan := make(chan struct{})
107+
var runErr error
108+
go func() {
109+
defer close(doneChan)
110+
runErr = inv.Run()
111+
}()
112+
113+
<-doneChan
114+
require.NoError(t, runErr)
115+
116+
// Should: return the active version's presets sorted by name
117+
pty.ExpectRegexMatch(`preset-default\s+k1=v2\s+true\s+0`)
118+
// The parameter order is not guaranteed in the output, so we match both possible orders
119+
pty.ExpectRegexMatch(`preset-multiple-params\s+(k1=v1,k2=v2)|(k2=v2,k1=v1)\s+false\s+-`)
120+
pty.ExpectRegexMatch(`preset-prebuilds\s+\s+false\s+2`)
121+
})
122+
123+
t.Run("ListsPresetsForSpecifiedTemplateVersion", func(t *testing.T) {
124+
t.Parallel()
125+
126+
ctx := testutil.Context(t, testutil.WaitMedium)
127+
128+
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
129+
owner := coderdtest.CreateFirstUser(t, client)
130+
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
131+
132+
// Given: a template with an active version that has no presets,
133+
// and another template version that includes presets
134+
presets := []*proto.Preset{
135+
{
136+
Name: "preset-multiple-params",
137+
Parameters: []*proto.PresetParameter{
138+
{
139+
Name: "k1",
140+
Value: "v1",
141+
}, {
142+
Name: "k2",
143+
Value: "v2",
144+
},
145+
},
146+
},
147+
{
148+
Name: "preset-default",
149+
Default: true,
150+
Parameters: []*proto.PresetParameter{
151+
{
152+
Name: "k1",
153+
Value: "v2",
154+
},
155+
},
156+
Prebuild: &proto.Prebuild{
157+
Instances: 0,
158+
},
159+
},
160+
{
161+
Name: "preset-prebuilds",
162+
Parameters: []*proto.PresetParameter{},
163+
Prebuild: &proto.Prebuild{
164+
Instances: 2,
165+
},
166+
},
167+
}
168+
// Given: first template version with presets
169+
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, templateWithPresets(presets))
170+
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
171+
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
172+
// Given: second template version without presets
173+
activeVersion := coderdtest.UpdateTemplateVersion(t, client, owner.OrganizationID, templateWithPresets([]*proto.Preset{}), template.ID)
174+
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, activeVersion.ID)
175+
// Given: second template version is the active version
176+
err := client.UpdateActiveTemplateVersion(ctx, template.ID, codersdk.UpdateActiveTemplateVersion{
177+
ID: activeVersion.ID,
178+
})
179+
require.NoError(t, err)
180+
updatedTemplate, err := client.Template(ctx, template.ID)
181+
require.NoError(t, err)
182+
require.Equal(t, activeVersion.ID, updatedTemplate.ActiveVersionID)
183+
// Given: template has two versions
184+
templateVersions, err := client.TemplateVersionsByTemplate(ctx, codersdk.TemplateVersionsByTemplateRequest{
185+
TemplateID: updatedTemplate.ID,
186+
})
187+
require.NoError(t, err)
188+
require.Len(t, templateVersions, 2)
189+
190+
// When: listing presets for a specific template and its specified version
191+
inv, root := clitest.New(t, "templates", "presets", "list", updatedTemplate.Name, "--template-version", version.Name)
192+
clitest.SetupConfig(t, member, root)
193+
194+
pty := ptytest.New(t).Attach(inv)
195+
doneChan := make(chan struct{})
196+
var runErr error
197+
go func() {
198+
defer close(doneChan)
199+
runErr = inv.Run()
200+
}()
201+
202+
<-doneChan
203+
require.NoError(t, runErr)
204+
205+
// Should: return the specified version's presets sorted by name
206+
pty.ExpectRegexMatch(`preset-default\s+k1=v2\s+true\s+0`)
207+
// The parameter order is not guaranteed in the output, so we match both possible orders
208+
pty.ExpectRegexMatch(`preset-multiple-params\s+(k1=v1,k2=v2)|(k2=v2,k1=v1)\s+false\s+-`)
209+
pty.ExpectRegexMatch(`preset-prebuilds\s+\s+false\s+2`)
210+
})
211+
}
212+
213+
func templateWithPresets(presets []*proto.Preset) *echo.Responses {
214+
return &echo.Responses{
215+
Parse: echo.ParseComplete,
216+
ProvisionPlan: []*proto.Response{
217+
{
218+
Type: &proto.Response_Plan{
219+
Plan: &proto.PlanComplete{
220+
Presets: presets,
221+
},
222+
},
223+
},
224+
},
225+
}
226+
}

cli/templates.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ func (r *RootCmd) templates() *serpent.Command {
3333
r.templateList(),
3434
r.templatePush(),
3535
r.templateVersions(),
36+
r.templatePresets(),
3637
r.templateDelete(),
3738
r.templatePull(),
3839
r.archiveTemplateVersions(),

0 commit comments

Comments
 (0)