Skip to content

Commit 4e7331a

Browse files
authored
feat(cli): support description in create and presets list CLI commands (#19079)
## Description This PR improves the `coder templates presets` and `coder create` CLI commands to include preset descriptions. ## Changes * Added a `description` column to the `coder templates presets list` CLI command. * Fixed the `-o json` output for `coder templates presets list` to correctly include and format data. * Updated the `coder create` CLI command to display the preset's description in the selection menu. Follow-up from: * #18910 * #18912 * #18977
1 parent 415273f commit 4e7331a

File tree

6 files changed

+105
-23
lines changed

6 files changed

+105
-23
lines changed

cli/create.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,12 @@ func promptPresetSelection(inv *serpent.Invocation, presets []codersdk.Preset) (
472472
var presetOptions []string
473473

474474
for _, preset := range presets {
475-
option := preset.Name
475+
var option string
476+
if preset.Description == "" {
477+
option = preset.Name
478+
} else {
479+
option = fmt.Sprintf("%s: %s", preset.Name, preset.Description)
480+
}
476481
presetOptions = append(presetOptions, option)
477482
presetMap[option] = &preset
478483
}

cli/create_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -882,7 +882,8 @@ func TestCreateWithPreset(t *testing.T) {
882882

883883
// Given: a template and a template version with two presets
884884
preset := proto.Preset{
885-
Name: "preset-test",
885+
Name: "preset-test",
886+
Description: "Preset Test.",
886887
Parameters: []*proto.PresetParameter{
887888
{Name: firstParameterName, Value: secondOptionalParameterValue},
888889
{Name: thirdParameterName, Value: thirdParameterValue},

cli/templatepresets.go

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,13 @@ func (r *RootCmd) templatePresets() *serpent.Command {
4141
func (r *RootCmd) templatePresetsList() *serpent.Command {
4242
defaultColumns := []string{
4343
"name",
44+
"description",
4445
"parameters",
4546
"default",
4647
"desired prebuild instances",
4748
}
4849
formatter := cliui.NewOutputFormatter(
49-
cliui.TableFormat([]templatePresetRow{}, defaultColumns),
50+
cliui.TableFormat([]TemplatePresetRow{}, defaultColumns),
5051
cliui.JSONFormat(),
5152
)
5253
client := new(codersdk.Client)
@@ -108,10 +109,13 @@ func (r *RootCmd) templatePresetsList() *serpent.Command {
108109
return nil
109110
}
110111

111-
cliui.Infof(
112-
inv.Stdout,
113-
"Showing presets for template %q and template version %q.\n", template.Name, version.Name,
114-
)
112+
// Only display info message for table output
113+
if formatter.FormatID() == "table" {
114+
cliui.Infof(
115+
inv.Stdout,
116+
"Showing presets for template %q and template version %q.\n", template.Name, version.Name,
117+
)
118+
}
115119
rows := templatePresetsToRows(presets...)
116120
out, err := formatter.Format(inv.Context(), rows)
117121
if err != nil {
@@ -128,12 +132,13 @@ func (r *RootCmd) templatePresetsList() *serpent.Command {
128132
return cmd
129133
}
130134

131-
type templatePresetRow struct {
132-
// For json format:
135+
type TemplatePresetRow struct {
136+
// For json format
133137
TemplatePreset codersdk.Preset `table:"-"`
134138

135139
// For table format:
136140
Name string `json:"-" table:"name,default_sort"`
141+
Description string `json:"-" table:"description"`
137142
Parameters string `json:"-" table:"parameters"`
138143
Default bool `json:"-" table:"default"`
139144
DesiredPrebuildInstances string `json:"-" table:"desired prebuild instances"`
@@ -149,15 +154,19 @@ func formatPresetParameters(params []codersdk.PresetParameter) string {
149154

150155
// templatePresetsToRows converts a list of presets to a list of rows
151156
// for outputting.
152-
func templatePresetsToRows(presets ...codersdk.Preset) []templatePresetRow {
153-
rows := make([]templatePresetRow, len(presets))
157+
func templatePresetsToRows(presets ...codersdk.Preset) []TemplatePresetRow {
158+
rows := make([]TemplatePresetRow, len(presets))
154159
for i, preset := range presets {
155160
prebuildInstances := "-"
156161
if preset.DesiredPrebuildInstances != nil {
157162
prebuildInstances = strconv.Itoa(*preset.DesiredPrebuildInstances)
158163
}
159-
rows[i] = templatePresetRow{
164+
rows[i] = TemplatePresetRow{
165+
// For json format
166+
TemplatePreset: preset,
167+
// For table format
160168
Name: preset.Name,
169+
Description: preset.Description,
161170
Parameters: formatPresetParameters(preset.Parameters),
162171
Default: preset.Default,
163172
DesiredPrebuildInstances: prebuildInstances,

cli/templatepresets_test.go

Lines changed: 73 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package cli_test
22

33
import (
4+
"bytes"
5+
"encoding/json"
46
"fmt"
57
"testing"
68

79
"github.com/stretchr/testify/require"
810

11+
"github.com/coder/coder/v2/cli"
912
"github.com/coder/coder/v2/cli/clitest"
1013
"github.com/coder/coder/v2/coderd/coderdtest"
1114
"github.com/coder/coder/v2/codersdk"
@@ -84,8 +87,9 @@ func TestTemplatePresets(t *testing.T) {
8487
},
8588
},
8689
{
87-
Name: "preset-prebuilds",
88-
Parameters: []*proto.PresetParameter{},
90+
Name: "preset-prebuilds",
91+
Description: "Preset without parameters and 2 prebuild instances.",
92+
Parameters: []*proto.PresetParameter{},
8993
Prebuild: &proto.Prebuild{
9094
Instances: 2,
9195
},
@@ -117,7 +121,7 @@ func TestTemplatePresets(t *testing.T) {
117121
pty.ExpectRegexMatch(`preset-default\s+k1=v2\s+true\s+0`)
118122
// The parameter order is not guaranteed in the output, so we match both possible orders
119123
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`)
124+
pty.ExpectRegexMatch(`preset-prebuilds\s+Preset without parameters and 2 prebuild instances.\s+\s+false\s+2`)
121125
})
122126

123127
t.Run("ListsPresetsForSpecifiedTemplateVersion", func(t *testing.T) {
@@ -158,8 +162,9 @@ func TestTemplatePresets(t *testing.T) {
158162
},
159163
},
160164
{
161-
Name: "preset-prebuilds",
162-
Parameters: []*proto.PresetParameter{},
165+
Name: "preset-prebuilds",
166+
Description: "Preset without parameters and 2 prebuild instances.",
167+
Parameters: []*proto.PresetParameter{},
163168
Prebuild: &proto.Prebuild{
164169
Instances: 2,
165170
},
@@ -208,7 +213,69 @@ func TestTemplatePresets(t *testing.T) {
208213
pty.ExpectRegexMatch(`preset-default\s+k1=v2\s+true\s+0`)
209214
// The parameter order is not guaranteed in the output, so we match both possible orders
210215
pty.ExpectRegexMatch(`preset-multiple-params\s+(k1=v1,k2=v2)|(k2=v2,k1=v1)\s+false\s+-`)
211-
pty.ExpectRegexMatch(`preset-prebuilds\s+\s+false\s+2`)
216+
pty.ExpectRegexMatch(`preset-prebuilds\s+Preset without parameters and 2 prebuild instances.\s+\s+false\s+2`)
217+
})
218+
219+
t.Run("ListsPresetsJSON", func(t *testing.T) {
220+
t.Parallel()
221+
222+
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
223+
owner := coderdtest.CreateFirstUser(t, client)
224+
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
225+
226+
// Given: an active template version that includes presets
227+
preset := proto.Preset{
228+
Name: "preset-default",
229+
Description: "Preset with parameters and 2 prebuild instances.",
230+
Icon: "/emojis/1f60e.png",
231+
Default: true,
232+
Parameters: []*proto.PresetParameter{
233+
{
234+
Name: "k1",
235+
Value: "v2",
236+
},
237+
},
238+
Prebuild: &proto.Prebuild{
239+
Instances: 2,
240+
},
241+
}
242+
243+
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, templateWithPresets([]*proto.Preset{&preset}))
244+
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
245+
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
246+
require.Equal(t, version.ID, template.ActiveVersionID)
247+
248+
// When: listing presets for that template
249+
inv, root := clitest.New(t, "templates", "presets", "list", template.Name, "-o", "json")
250+
clitest.SetupConfig(t, member, root)
251+
252+
buf := bytes.NewBuffer(nil)
253+
inv.Stdout = buf
254+
doneChan := make(chan struct{})
255+
var runErr error
256+
go func() {
257+
defer close(doneChan)
258+
runErr = inv.Run()
259+
}()
260+
261+
<-doneChan
262+
require.NoError(t, runErr)
263+
264+
// Should: return the active version's preset
265+
var jsonPresets []cli.TemplatePresetRow
266+
err := json.Unmarshal(buf.Bytes(), &jsonPresets)
267+
require.NoError(t, err, "unmarshal JSON output")
268+
require.Len(t, jsonPresets, 1)
269+
270+
jsonPreset := jsonPresets[0].TemplatePreset
271+
require.Equal(t, preset.Name, jsonPreset.Name)
272+
require.Equal(t, preset.Description, jsonPreset.Description)
273+
require.Equal(t, preset.Icon, jsonPreset.Icon)
274+
require.Equal(t, preset.Default, jsonPreset.Default)
275+
require.Equal(t, len(preset.Parameters), len(jsonPreset.Parameters))
276+
require.Equal(t, preset.Parameters[0].Name, jsonPreset.Parameters[0].Name)
277+
require.Equal(t, preset.Parameters[0].Value, jsonPreset.Parameters[0].Value)
278+
require.Equal(t, int(preset.Prebuild.Instances), *jsonPreset.DesiredPrebuildInstances)
212279
})
213280
}
214281

cli/testdata/coder_templates_presets_list_--help.golden

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ OPTIONS:
1010
-O, --org string, $CODER_ORGANIZATION
1111
Select which organization (uuid or name) to use.
1212

13-
-c, --column [name|parameters|default|desired prebuild instances] (default: name,parameters,default,desired prebuild instances)
13+
-c, --column [name|description|parameters|default|desired prebuild instances] (default: name,description,parameters,default,desired prebuild instances)
1414
Columns to display in table output.
1515

1616
-o, --output table|json (default: table)

docs/reference/cli/templates_presets_list.md

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)