- Notifications
You must be signed in to change notification settings - Fork948
feat(cli): add CLI support for listing presets#18910
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
base:main
Are you sure you want to change the base?
Changes fromall commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
package cli | ||
import ( | ||
"fmt" | ||
"strconv" | ||
"strings" | ||
"golang.org/x/xerrors" | ||
"github.com/coder/coder/v2/cli/cliui" | ||
"github.com/coder/coder/v2/codersdk" | ||
"github.com/coder/serpent" | ||
) | ||
func (r*RootCmd)templateVersionPresets()*serpent.Command { | ||
cmd:=&serpent.Command{ | ||
Use:"presets", | ||
Short:"Manage presets of the specified template version", | ||
Aliases: []string{"preset"}, | ||
Long:FormatExamples( | ||
Example{ | ||
Description:"List presets of a specific template version", | ||
Command:"coder templates versions presets list my-template my-template-version", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Currently, the We could also introduce a Example usage:
Let me know what you think There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. I like I think template version flag should be optional with the active version as a default. | ||
}, | ||
), | ||
Handler:func(inv*serpent.Invocation)error { | ||
returninv.Command.HelpHandler(inv) | ||
}, | ||
Children: []*serpent.Command{ | ||
r.templateVersionPresetsList(), | ||
}, | ||
} | ||
returncmd | ||
} | ||
func (r*RootCmd)templateVersionPresetsList()*serpent.Command { | ||
defaultColumns:= []string{ | ||
"name", | ||
"parameters", | ||
"default", | ||
"prebuilds", | ||
} | ||
formatter:=cliui.NewOutputFormatter( | ||
cliui.TableFormat([]templateVersionPresetRow{},defaultColumns), | ||
cliui.JSONFormat(), | ||
) | ||
client:=new(codersdk.Client) | ||
orgContext:=NewOrganizationContext() | ||
cmd:=&serpent.Command{ | ||
Use:"list <template> <version>", | ||
Middleware:serpent.Chain( | ||
serpent.RequireNArgs(2), | ||
r.InitClient(client), | ||
), | ||
Short:"List all the presets of the specified template version", | ||
Options: serpent.OptionSet{}, | ||
Handler:func(inv*serpent.Invocation)error { | ||
organization,err:=orgContext.Selected(inv,client) | ||
iferr!=nil { | ||
returnxerrors.Errorf("get current organization: %w",err) | ||
} | ||
template,err:=client.TemplateByName(inv.Context(),organization.ID,inv.Args[0]) | ||
iferr!=nil { | ||
returnxerrors.Errorf("get template by name: %w",err) | ||
} | ||
version,err:=client.TemplateVersionByName(inv.Context(),template.ID,inv.Args[1]) | ||
iferr!=nil { | ||
returnxerrors.Errorf("get template version by name: %w",err) | ||
} | ||
presets,err:=client.TemplateVersionPresets(inv.Context(),version.ID) | ||
iferr!=nil { | ||
returnxerrors.Errorf("get template versions presets by template version: %w",err) | ||
} | ||
iflen(presets)==0 { | ||
returnxerrors.Errorf("no presets found for template %q and template-version %q",template.Name,version.Name) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Should this be an error? Or just an empty list? | ||
} | ||
rows:=templateVersionPresetsToRows(presets...) | ||
out,err:=formatter.Format(inv.Context(),rows) | ||
iferr!=nil { | ||
returnxerrors.Errorf("render table: %w",err) | ||
} | ||
_,err=fmt.Fprintln(inv.Stdout,out) | ||
returnerr | ||
}, | ||
} | ||
orgContext.AttachOptions(cmd) | ||
formatter.AttachOptions(&cmd.Options) | ||
returncmd | ||
} | ||
typetemplateVersionPresetRowstruct { | ||
// For json format: | ||
TemplateVersionPreset codersdk.Preset`table:"-"` | ||
// For table format: | ||
Namestring`json:"-" table:"name,default_sort"` | ||
Parametersstring`json:"-" table:"parameters"` | ||
Defaultbool`json:"-" table:"default"` | ||
Prebuildsstring`json:"-" table:"prebuilds"` | ||
} | ||
funcformatPresetParameters(params []codersdk.PresetParameter)string { | ||
varparamsStr []string | ||
for_,p:=rangeparams { | ||
paramsStr=append(paramsStr,fmt.Sprintf("%s=%s",p.Name,p.Value)) | ||
} | ||
returnstrings.Join(paramsStr,",") | ||
} | ||
// templateVersionPresetsToRows converts a list of presets to a list of rows | ||
// for outputting. | ||
functemplateVersionPresetsToRows(presets...codersdk.Preset) []templateVersionPresetRow { | ||
rows:=make([]templateVersionPresetRow,len(presets)) | ||
fori,preset:=rangepresets { | ||
prebuilds:="-" | ||
ifpreset.Prebuilds!=nil { | ||
prebuilds=strconv.Itoa(*preset.Prebuilds) | ||
} | ||
rows[i]=templateVersionPresetRow{ | ||
Name:preset.Name, | ||
Parameters:formatPresetParameters(preset.Parameters), | ||
Default:preset.Default, | ||
Prebuilds:prebuilds, | ||
} | ||
} | ||
returnrows | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
package cli_test | ||
import ( | ||
"fmt" | ||
"testing" | ||
"github.com/coder/coder/v2/provisioner/echo" | ||
"github.com/coder/coder/v2/provisionersdk/proto" | ||
"github.com/stretchr/testify/require" | ||
"github.com/coder/coder/v2/cli/clitest" | ||
"github.com/coder/coder/v2/coderd/coderdtest" | ||
"github.com/coder/coder/v2/pty/ptytest" | ||
) | ||
func TestTemplateVersionPresets(t *testing.T) { | ||
t.Parallel() | ||
t.Run("ListPresets", 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: a template version that includes presets | ||
presets := []*proto.Preset{ | ||
{ | ||
Name: "preset-multiple-params", | ||
Parameters: []*proto.PresetParameter{ | ||
{ | ||
Name: "k1", | ||
Value: "v1", | ||
}, { | ||
Name: "k2", | ||
Value: "v2", | ||
}, | ||
}, | ||
}, | ||
{ | ||
Name: "preset-default", | ||
Default: true, | ||
Parameters: []*proto.PresetParameter{ | ||
{ | ||
Name: "k1", | ||
Value: "v2", | ||
}, | ||
}, | ||
Prebuild: &proto.Prebuild{ | ||
Instances: 0, | ||
}, | ||
}, | ||
{ | ||
Name: "preset-prebuilds", | ||
Parameters: []*proto.PresetParameter{}, | ||
Prebuild: &proto.Prebuild{ | ||
Instances: 2, | ||
}, | ||
}, | ||
} | ||
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, templateWithPresets(presets)) | ||
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) | ||
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID) | ||
// When: listing presets for that template and template version | ||
inv, root := clitest.New(t, "templates", "versions", "presets", "list", template.Name, version.Name) | ||
clitest.SetupConfig(t, member, root) | ||
pty := ptytest.New(t).Attach(inv) | ||
doneChan := make(chan struct{}) | ||
var runErr error | ||
go func() { | ||
defer close(doneChan) | ||
runErr = inv.Run() | ||
}() | ||
<-doneChan | ||
require.NoError(t, runErr) | ||
// Should: return the presets sorted by name | ||
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`) | ||
}) | ||
t.Run("NoPresets", 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: a template version without presets | ||
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, templateWithPresets([]*proto.Preset{})) | ||
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) | ||
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID) | ||
// When: listing presets for that template and template version | ||
inv, root := clitest.New(t, "templates", "versions", "presets", "list", template.Name, version.Name) | ||
clitest.SetupConfig(t, member, root) | ||
ptytest.New(t).Attach(inv) | ||
doneChan := make(chan struct{}) | ||
var runErr error | ||
go func() { | ||
defer close(doneChan) | ||
runErr = inv.Run() | ||
}() | ||
<-doneChan | ||
// Should return an error when no presets are found for the given template and version. | ||
require.Error(t, runErr) | ||
expectedErr := fmt.Sprintf( | ||
"no presets found for template %q and template-version %q", | ||
template.Name, | ||
version.Name, | ||
) | ||
require.Contains(t, runErr.Error(), expectedErr) | ||
}) | ||
} | ||
func templateWithPresets(presets []*proto.Preset) *echo.Responses { | ||
return &echo.Responses{ | ||
Parse: echo.ParseComplete, | ||
ProvisionPlan: []*proto.Response{ | ||
{ | ||
Type: &proto.Response_Plan{ | ||
Plan: &proto.PlanComplete{ | ||
Presets: presets, | ||
}, | ||
}, | ||
}, | ||
}, | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
coder v0.0.0-devel | ||
USAGE: | ||
coder templates versions presets | ||
Manage presets of the specified template version | ||
Aliases: preset | ||
- List presets of a specific template version: | ||
$ coder templates versions presets list my-template my-template-version | ||
SUBCOMMANDS: | ||
list List all the presets of the specified template version | ||
——— | ||
Run `coder --help` for a list of global options. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
coder v0.0.0-devel | ||
USAGE: | ||
coder templates versions presets | ||
Manage presets of the specified template version | ||
Aliases: preset | ||
- List presets of a specific template version: | ||
$ coder templates versions presets list my-template my-template-version | ||
SUBCOMMANDS: | ||
list List all the presets of the specified template version | ||
——— | ||
Run `coder --help` for a list of global options. |
Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.
Uh oh!
There was an error while loading.Please reload this page.
Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.
Uh oh!
There was an error while loading.Please reload this page.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
package coderd | ||
import ( | ||
"database/sql" | ||
"net/http" | ||
"github.com/coder/coder/v2/coderd/httpapi" | ||
@@ -38,12 +39,21 @@ func (api *API) templateVersionPresets(rw http.ResponseWriter, r *http.Request) | ||
return | ||
} | ||
getPrebuildInstances:=func(desiredInstances sql.NullInt32)*int { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. this name implies fetching from somewhere, but it only does a type conversion. perhaps consider a more descriptive name? | ||
ifdesiredInstances.Valid { | ||
value:=int(desiredInstances.Int32) | ||
return&value | ||
} | ||
returnnil | ||
} | ||
varres []codersdk.Preset | ||
for_,preset:=rangepresets { | ||
sdkPreset:= codersdk.Preset{ | ||
ID:preset.ID, | ||
Name:preset.Name, | ||
Default:preset.IsDefault, | ||
Prebuilds:getPrebuildInstances(preset.DesiredInstances), | ||
} | ||
for_,presetParam:=rangepresetParams { | ||
ifpresetParam.TemplateVersionPresetID!=preset.ID { | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -15,6 +15,7 @@ type Preset struct { | ||
Namestring | ||
Parameters []PresetParameter | ||
Defaultbool | ||
Prebuilds*int | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Consider clarifying the naming to communicate whether this is the desired number of prebuilds or the eligible number of prebuilds. I can see from elsewhere that its the desired number, but we've spoken in the past about showing how many prebuilds are eligible in the frontend before. Would be good to leave room for both. | ||
} | ||
typePresetParameterstruct { | ||
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.