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: 
* https://github.com/coder/coder/pull/18910 
* https://github.com/coder/coder/pull/18912
* https://github.com/coder/coder/pull/18977
This commit is contained in:
Susana Ferreira
2025-07-29 16:59:26 +01:00
committed by GitHub
parent 415273f648
commit 4e7331a9c4
6 changed files with 105 additions and 23 deletions
+6 -1
View File
@@ -472,7 +472,12 @@ func promptPresetSelection(inv *serpent.Invocation, presets []codersdk.Preset) (
var presetOptions []string
for _, preset := range presets {
option := preset.Name
var option string
if preset.Description == "" {
option = preset.Name
} else {
option = fmt.Sprintf("%s: %s", preset.Name, preset.Description)
}
presetOptions = append(presetOptions, option)
presetMap[option] = &preset
}
+2 -1
View File
@@ -882,7 +882,8 @@ func TestCreateWithPreset(t *testing.T) {
// Given: a template and a template version with two presets
preset := proto.Preset{
Name: "preset-test",
Name: "preset-test",
Description: "Preset Test.",
Parameters: []*proto.PresetParameter{
{Name: firstParameterName, Value: secondOptionalParameterValue},
{Name: thirdParameterName, Value: thirdParameterValue},
+19 -10
View File
@@ -41,12 +41,13 @@ func (r *RootCmd) templatePresets() *serpent.Command {
func (r *RootCmd) templatePresetsList() *serpent.Command {
defaultColumns := []string{
"name",
"description",
"parameters",
"default",
"desired prebuild instances",
}
formatter := cliui.NewOutputFormatter(
cliui.TableFormat([]templatePresetRow{}, defaultColumns),
cliui.TableFormat([]TemplatePresetRow{}, defaultColumns),
cliui.JSONFormat(),
)
client := new(codersdk.Client)
@@ -108,10 +109,13 @@ func (r *RootCmd) templatePresetsList() *serpent.Command {
return nil
}
cliui.Infof(
inv.Stdout,
"Showing presets for template %q and template version %q.\n", template.Name, version.Name,
)
// Only display info message for table output
if formatter.FormatID() == "table" {
cliui.Infof(
inv.Stdout,
"Showing presets for template %q and template version %q.\n", template.Name, version.Name,
)
}
rows := templatePresetsToRows(presets...)
out, err := formatter.Format(inv.Context(), rows)
if err != nil {
@@ -128,12 +132,13 @@ func (r *RootCmd) templatePresetsList() *serpent.Command {
return cmd
}
type templatePresetRow struct {
// For json format:
type TemplatePresetRow struct {
// For json format
TemplatePreset codersdk.Preset `table:"-"`
// For table format:
Name string `json:"-" table:"name,default_sort"`
Description string `json:"-" table:"description"`
Parameters string `json:"-" table:"parameters"`
Default bool `json:"-" table:"default"`
DesiredPrebuildInstances string `json:"-" table:"desired prebuild instances"`
@@ -149,15 +154,19 @@ func formatPresetParameters(params []codersdk.PresetParameter) string {
// templatePresetsToRows converts a list of presets to a list of rows
// for outputting.
func templatePresetsToRows(presets ...codersdk.Preset) []templatePresetRow {
rows := make([]templatePresetRow, len(presets))
func templatePresetsToRows(presets ...codersdk.Preset) []TemplatePresetRow {
rows := make([]TemplatePresetRow, len(presets))
for i, preset := range presets {
prebuildInstances := "-"
if preset.DesiredPrebuildInstances != nil {
prebuildInstances = strconv.Itoa(*preset.DesiredPrebuildInstances)
}
rows[i] = templatePresetRow{
rows[i] = TemplatePresetRow{
// For json format
TemplatePreset: preset,
// For table format
Name: preset.Name,
Description: preset.Description,
Parameters: formatPresetParameters(preset.Parameters),
Default: preset.Default,
DesiredPrebuildInstances: prebuildInstances,
+73 -6
View File
@@ -1,11 +1,14 @@
package cli_test
import (
"bytes"
"encoding/json"
"fmt"
"testing"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/cli"
"github.com/coder/coder/v2/cli/clitest"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/codersdk"
@@ -84,8 +87,9 @@ func TestTemplatePresets(t *testing.T) {
},
},
{
Name: "preset-prebuilds",
Parameters: []*proto.PresetParameter{},
Name: "preset-prebuilds",
Description: "Preset without parameters and 2 prebuild instances.",
Parameters: []*proto.PresetParameter{},
Prebuild: &proto.Prebuild{
Instances: 2,
},
@@ -117,7 +121,7 @@ func TestTemplatePresets(t *testing.T) {
pty.ExpectRegexMatch(`preset-default\s+k1=v2\s+true\s+0`)
// The parameter order is not guaranteed in the output, so we match both possible orders
pty.ExpectRegexMatch(`preset-multiple-params\s+(k1=v1,k2=v2)|(k2=v2,k1=v1)\s+false\s+-`)
pty.ExpectRegexMatch(`preset-prebuilds\s+\s+false\s+2`)
pty.ExpectRegexMatch(`preset-prebuilds\s+Preset without parameters and 2 prebuild instances.\s+\s+false\s+2`)
})
t.Run("ListsPresetsForSpecifiedTemplateVersion", func(t *testing.T) {
@@ -158,8 +162,9 @@ func TestTemplatePresets(t *testing.T) {
},
},
{
Name: "preset-prebuilds",
Parameters: []*proto.PresetParameter{},
Name: "preset-prebuilds",
Description: "Preset without parameters and 2 prebuild instances.",
Parameters: []*proto.PresetParameter{},
Prebuild: &proto.Prebuild{
Instances: 2,
},
@@ -208,7 +213,69 @@ func TestTemplatePresets(t *testing.T) {
pty.ExpectRegexMatch(`preset-default\s+k1=v2\s+true\s+0`)
// The parameter order is not guaranteed in the output, so we match both possible orders
pty.ExpectRegexMatch(`preset-multiple-params\s+(k1=v1,k2=v2)|(k2=v2,k1=v1)\s+false\s+-`)
pty.ExpectRegexMatch(`preset-prebuilds\s+\s+false\s+2`)
pty.ExpectRegexMatch(`preset-prebuilds\s+Preset without parameters and 2 prebuild instances.\s+\s+false\s+2`)
})
t.Run("ListsPresetsJSON", 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)
// Given: an active template version that includes presets
preset := proto.Preset{
Name: "preset-default",
Description: "Preset with parameters and 2 prebuild instances.",
Icon: "/emojis/1f60e.png",
Default: true,
Parameters: []*proto.PresetParameter{
{
Name: "k1",
Value: "v2",
},
},
Prebuild: &proto.Prebuild{
Instances: 2,
},
}
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, templateWithPresets([]*proto.Preset{&preset}))
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
require.Equal(t, version.ID, template.ActiveVersionID)
// When: listing presets for that template
inv, root := clitest.New(t, "templates", "presets", "list", template.Name, "-o", "json")
clitest.SetupConfig(t, member, root)
buf := bytes.NewBuffer(nil)
inv.Stdout = buf
doneChan := make(chan struct{})
var runErr error
go func() {
defer close(doneChan)
runErr = inv.Run()
}()
<-doneChan
require.NoError(t, runErr)
// Should: return the active version's preset
var jsonPresets []cli.TemplatePresetRow
err := json.Unmarshal(buf.Bytes(), &jsonPresets)
require.NoError(t, err, "unmarshal JSON output")
require.Len(t, jsonPresets, 1)
jsonPreset := jsonPresets[0].TemplatePreset
require.Equal(t, preset.Name, jsonPreset.Name)
require.Equal(t, preset.Description, jsonPreset.Description)
require.Equal(t, preset.Icon, jsonPreset.Icon)
require.Equal(t, preset.Default, jsonPreset.Default)
require.Equal(t, len(preset.Parameters), len(jsonPreset.Parameters))
require.Equal(t, preset.Parameters[0].Name, jsonPreset.Parameters[0].Name)
require.Equal(t, preset.Parameters[0].Value, jsonPreset.Parameters[0].Value)
require.Equal(t, int(preset.Prebuild.Instances), *jsonPreset.DesiredPrebuildInstances)
})
}
+1 -1
View File
@@ -10,7 +10,7 @@ OPTIONS:
-O, --org string, $CODER_ORGANIZATION
Select which organization (uuid or name) to use.
-c, --column [name|parameters|default|desired prebuild instances] (default: name,parameters,default,desired prebuild instances)
-c, --column [name|description|parameters|default|desired prebuild instances] (default: name,description,parameters,default,desired prebuild instances)
Columns to display in table output.
-o, --output table|json (default: table)