Skip to content

Commit 356fb39

Browse files
committed
feat(cli): add CLI support for listing presets
1 parent aae5fc2 commit 356fb39

18 files changed

+427
-11
lines changed

cli/templateversionpresets.go

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package cli
2+
3+
import (
4+
"fmt"
5+
"strconv"
6+
"strings"
7+
8+
"golang.org/x/xerrors"
9+
10+
"github.com/coder/coder/v2/cli/cliui"
11+
"github.com/coder/coder/v2/codersdk"
12+
"github.com/coder/serpent"
13+
)
14+
15+
func (r *RootCmd) templateVersionPresets() *serpent.Command {
16+
cmd := &serpent.Command{
17+
Use: "presets",
18+
Short: "Manage presets of the specified template version",
19+
Aliases: []string{"preset"},
20+
Long: FormatExamples(
21+
Example{
22+
Description: "List presets of a specific template version",
23+
Command: "coder templates versions presets list my-template my-template-version",
24+
},
25+
),
26+
Handler: func(inv *serpent.Invocation) error {
27+
return inv.Command.HelpHandler(inv)
28+
},
29+
Children: []*serpent.Command{
30+
r.templateVersionPresetsList(),
31+
},
32+
}
33+
34+
return cmd
35+
}
36+
37+
func (r *RootCmd) templateVersionPresetsList() *serpent.Command {
38+
defaultColumns := []string{
39+
"name",
40+
"parameters",
41+
"default",
42+
"prebuilds",
43+
}
44+
formatter := cliui.NewOutputFormatter(
45+
cliui.TableFormat([]templateVersionPresetRow{}, defaultColumns),
46+
cliui.JSONFormat(),
47+
)
48+
client := new(codersdk.Client)
49+
orgContext := NewOrganizationContext()
50+
51+
cmd := &serpent.Command{
52+
Use: "list <template> <version>",
53+
Middleware: serpent.Chain(
54+
serpent.RequireNArgs(2),
55+
r.InitClient(client),
56+
),
57+
Short: "List all the presets of the specified template version",
58+
Options: serpent.OptionSet{},
59+
Handler: func(inv *serpent.Invocation) error {
60+
organization, err := orgContext.Selected(inv, client)
61+
if err != nil {
62+
return xerrors.Errorf("get current organization: %w", err)
63+
}
64+
65+
template, err := client.TemplateByName(inv.Context(), organization.ID, inv.Args[0])
66+
if err != nil {
67+
return xerrors.Errorf("get template by name: %w", err)
68+
}
69+
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)
73+
}
74+
75+
presets, err := client.TemplateVersionPresets(inv.Context(), version.ID)
76+
if err != nil {
77+
return xerrors.Errorf("get template versions presets by template version: %w", err)
78+
}
79+
80+
if len(presets) == 0 {
81+
return xerrors.Errorf("no presets found for template %q and template-version %q", template.Name, version.Name)
82+
}
83+
84+
rows := templateVersionPresetsToRows(presets...)
85+
out, err := formatter.Format(inv.Context(), rows)
86+
if err != nil {
87+
return xerrors.Errorf("render table: %w", err)
88+
}
89+
90+
_, err = fmt.Fprintln(inv.Stdout, out)
91+
return err
92+
},
93+
}
94+
95+
orgContext.AttachOptions(cmd)
96+
formatter.AttachOptions(&cmd.Options)
97+
return cmd
98+
}
99+
100+
type templateVersionPresetRow struct {
101+
// For json format:
102+
TemplateVersionPreset codersdk.Preset `table:"-"`
103+
104+
// For table format:
105+
Name string `json:"-" table:"name,default_sort"`
106+
Parameters string `json:"-" table:"parameters"`
107+
Default bool `json:"-" table:"default"`
108+
Prebuilds string `json:"-" table:"prebuilds"`
109+
}
110+
111+
func formatPresetParameters(params []codersdk.PresetParameter) string {
112+
var paramsStr []string
113+
for _, p := range params {
114+
paramsStr = append(paramsStr, fmt.Sprintf("%s=%s", p.Name, p.Value))
115+
}
116+
return strings.Join(paramsStr, ",")
117+
}
118+
119+
// templateVersionPresetsToRows converts a list of presets to a list of rows
120+
// for outputting.
121+
func templateVersionPresetsToRows(presets ...codersdk.Preset) []templateVersionPresetRow {
122+
rows := make([]templateVersionPresetRow, len(presets))
123+
for i, preset := range presets {
124+
prebuilds := "-"
125+
if preset.Prebuilds != nil {
126+
prebuilds = strconv.Itoa(*preset.Prebuilds)
127+
}
128+
rows[i] = templateVersionPresetRow{
129+
Name: preset.Name,
130+
Parameters: formatPresetParameters(preset.Parameters),
131+
Default: preset.Default,
132+
Prebuilds: prebuilds,
133+
}
134+
}
135+
136+
return rows
137+
}

cli/templateversionpresets_test.go

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package cli_test
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/coder/coder/v2/provisioner/echo"
8+
"github.com/coder/coder/v2/provisionersdk/proto"
9+
10+
"github.com/stretchr/testify/require"
11+
12+
"github.com/coder/coder/v2/cli/clitest"
13+
"github.com/coder/coder/v2/coderd/coderdtest"
14+
"github.com/coder/coder/v2/pty/ptytest"
15+
)
16+
17+
func TestTemplateVersionPresets(t *testing.T) {
18+
t.Parallel()
19+
20+
t.Run("ListPresets", func(t *testing.T) {
21+
t.Parallel()
22+
23+
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
24+
owner := coderdtest.CreateFirstUser(t, client)
25+
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
26+
27+
// Given: a template version that includes presets
28+
presets := []*proto.Preset{
29+
{
30+
Name: "preset-multiple-params",
31+
Parameters: []*proto.PresetParameter{
32+
{
33+
Name: "k1",
34+
Value: "v1",
35+
}, {
36+
Name: "k2",
37+
Value: "v2",
38+
},
39+
},
40+
},
41+
{
42+
Name: "preset-default",
43+
Default: true,
44+
Parameters: []*proto.PresetParameter{
45+
{
46+
Name: "k1",
47+
Value: "v2",
48+
},
49+
},
50+
Prebuild: &proto.Prebuild{
51+
Instances: 0,
52+
},
53+
},
54+
{
55+
Name: "preset-prebuilds",
56+
Parameters: []*proto.PresetParameter{},
57+
Prebuild: &proto.Prebuild{
58+
Instances: 2,
59+
},
60+
},
61+
}
62+
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, templateWithPresets(presets))
63+
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
64+
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
65+
66+
// When: listing presets for that template and template version
67+
inv, root := clitest.New(t, "templates", "versions", "presets", "list", template.Name, version.Name)
68+
clitest.SetupConfig(t, member, root)
69+
70+
pty := ptytest.New(t).Attach(inv)
71+
doneChan := make(chan struct{})
72+
var runErr error
73+
go func() {
74+
defer close(doneChan)
75+
runErr = inv.Run()
76+
}()
77+
78+
<-doneChan
79+
require.NoError(t, runErr)
80+
81+
// Should: return the presets sorted by name
82+
pty.ExpectRegexMatch(`preset-default\s+k1=v2\s+true\s+0`)
83+
// The parameter order is not guaranteed in the output, so we match both possible orders
84+
pty.ExpectRegexMatch(`preset-multiple-params\s+(k1=v1,k2=v2)|(k2=v2,k1=v1)\s+false\s+-`)
85+
pty.ExpectRegexMatch(`preset-prebuilds\s+\s+false\s+2`)
86+
})
87+
88+
t.Run("NoPresets", func(t *testing.T) {
89+
t.Parallel()
90+
91+
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
92+
owner := coderdtest.CreateFirstUser(t, client)
93+
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
94+
95+
// Given: a template version without presets
96+
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, templateWithPresets([]*proto.Preset{}))
97+
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
98+
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
99+
100+
// When: listing presets for that template and template version
101+
inv, root := clitest.New(t, "templates", "versions", "presets", "list", template.Name, version.Name)
102+
clitest.SetupConfig(t, member, root)
103+
104+
ptytest.New(t).Attach(inv)
105+
doneChan := make(chan struct{})
106+
var runErr error
107+
go func() {
108+
defer close(doneChan)
109+
runErr = inv.Run()
110+
}()
111+
<-doneChan
112+
113+
// Should return an error when no presets are found for the given template and version.
114+
require.Error(t, runErr)
115+
expectedErr := fmt.Sprintf(
116+
"no presets found for template %q and template-version %q",
117+
template.Name,
118+
version.Name,
119+
)
120+
require.Contains(t, runErr.Error(), expectedErr)
121+
})
122+
}
123+
124+
func templateWithPresets(presets []*proto.Preset) *echo.Responses {
125+
return &echo.Responses{
126+
Parse: echo.ParseComplete,
127+
ProvisionPlan: []*proto.Response{
128+
{
129+
Type: &proto.Response_Plan{
130+
Plan: &proto.PlanComplete{
131+
Presets: presets,
132+
},
133+
},
134+
},
135+
},
136+
}
137+
}

cli/templateversions.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ func (r *RootCmd) templateVersions() *serpent.Command {
3333
r.archiveTemplateVersion(),
3434
r.unarchiveTemplateVersion(),
3535
r.templateVersionsPromote(),
36+
r.templateVersionPresets(),
3637
},
3738
}
3839

cli/testdata/coder_templates_versions_--help.golden

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ USAGE:
1414
SUBCOMMANDS:
1515
archive Archive a template version(s).
1616
list List all the versions of the specified template
17+
presets Manage presets of the specified template version
1718
promote Promote a template version to active.
1819
unarchive Unarchive a template version(s).
1920

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
coder v0.0.0-devel
2+
3+
USAGE:
4+
coder templates versions presets
5+
6+
Manage presets of the specified template version
7+
8+
Aliases: preset
9+
10+
- List presets of a specific template version:
11+
12+
$ coder templates versions presets list my-template my-template-version
13+
14+
SUBCOMMANDS:
15+
list List all the presets of the specified template version
16+
17+
———
18+
Run `coder --help` for a list of global options.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
coder v0.0.0-devel
2+
3+
USAGE:
4+
coder templates versions presets
5+
6+
Manage presets of the specified template version
7+
8+
Aliases: preset
9+
10+
- List presets of a specific template version:
11+
12+
$ coder templates versions presets list my-template my-template-version
13+
14+
SUBCOMMANDS:
15+
list List all the presets of the specified template version
16+
17+
———
18+
Run `coder --help` for a list of global options.

coderd/apidoc/docs.go

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

coderd/apidoc/swagger.json

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

coderd/presets.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package coderd
22

33
import (
4+
"database/sql"
45
"net/http"
56

67
"github.com/coder/coder/v2/coderd/httpapi"
@@ -38,12 +39,21 @@ func (api *API) templateVersionPresets(rw http.ResponseWriter, r *http.Request)
3839
return
3940
}
4041

42+
getPrebuildInstances := func(desiredInstances sql.NullInt32) *int {
43+
if desiredInstances.Valid {
44+
value := int(desiredInstances.Int32)
45+
return &value
46+
}
47+
return nil
48+
}
49+
4150
var res []codersdk.Preset
4251
for _, preset := range presets {
4352
sdkPreset := codersdk.Preset{
44-
ID: preset.ID,
45-
Name: preset.Name,
46-
Default: preset.IsDefault,
53+
ID: preset.ID,
54+
Name: preset.Name,
55+
Default: preset.IsDefault,
56+
Prebuilds: getPrebuildInstances(preset.DesiredInstances),
4757
}
4858
for _, presetParam := range presetParams {
4959
if presetParam.TemplateVersionPresetID != preset.ID {

codersdk/presets.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ type Preset struct {
1515
Name string
1616
Parameters []PresetParameter
1717
Default bool
18+
Prebuilds *int
1819
}
1920

2021
type PresetParameter struct {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy