Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitb975d6d

Browse files
authored
feat(cli): add CLI support for creating a workspace with preset (#18912)
## Description This PR introduces a `--preset` flag for the `create` command to allowusers to apply a predefined preset to their workspace build.## Changes- The `--preset` flag on the `create` command integrates with theparameter resolution logic and takes precedence over other sources(e.g., CLI/env vars, last build, etc.).- Added internal logic to ensure that preset parameters overrideparameters values during resolution.- Updated tests and added new ones to cover these flows.## Implementation logic* If a template has presets and includes a default, the CLI willautomatically use the default when `--preset` is not specified.* If a template has presets but no default, the CLI will prompt the userto select one when `--preset` is not specified.* If a template does not have presets, the CLI will not prompt the userfor a preset.* If the user specifies a preset using the `--preset` flag, that presetwill be used.* If the user passes `--preset None`, no preset will be applied.This logic aligns with the behavior in the UI for consistency.```> coder create --helpUSAGE: coder create [flags] [workspace] Create a workspace - Create a workspace for another user (if you have permission): $ coder create <username>/<workspace_name>OPTIONS: (...) --preset string, $CODER_PRESET_NAME Specify the name of a template version preset. Use 'none' to explicitly indicate that no preset should be used. (...) -y, --yes bool Bypass prompts.```## Breaking change**Note:** This is a breaking change to the create CLI command. If atemplate includes presets and the user does not provide a `--preset`flag, the CLI will now prompt the user to select one. This behavior maybreak non-interactive scripts or automated workflows.Relates to PR:#18910 - pleaseconsider both PRs together as they’re part of the same workflowRelates to issue:#16594
1 parent66cf90c commitb975d6d

File tree

7 files changed

+1197
-3
lines changed

7 files changed

+1197
-3
lines changed

‎cli/create.go‎

Lines changed: 123 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cli
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"io"
78
"slices"
@@ -21,10 +22,18 @@ import (
2122
"github.com/coder/serpent"
2223
)
2324

25+
// PresetNone represents the special preset value "none".
26+
// It is used when a user runs `create --preset none`,
27+
// indicating that the CLI should not apply any preset.
28+
constPresetNone="none"
29+
30+
varErrNoPresetFound=xerrors.New("no preset found")
31+
2432
func (r*RootCmd)create()*serpent.Command {
2533
var (
2634
templateNamestring
2735
templateVersionstring
36+
presetNamestring
2837
startAtstring
2938
stopAfter time.Duration
3039
workspaceNamestring
@@ -263,11 +272,45 @@ func (r *RootCmd) create() *serpent.Command {
263272
}
264273
}
265274

275+
// Get presets for the template version
276+
tvPresets,err:=client.TemplateVersionPresets(inv.Context(),templateVersionID)
277+
iferr!=nil {
278+
returnxerrors.Errorf("failed to get presets: %w",err)
279+
}
280+
281+
varpreset*codersdk.Preset
282+
varpresetParameters []codersdk.WorkspaceBuildParameter
283+
284+
// If the template has no presets, or the user explicitly used --preset none,
285+
// skip applying a preset
286+
iflen(tvPresets)>0&&strings.ToLower(presetName)!=PresetNone {
287+
// Attempt to resolve which preset to use
288+
preset,err=resolvePreset(tvPresets,presetName)
289+
iferr!=nil {
290+
if!errors.Is(err,ErrNoPresetFound) {
291+
returnxerrors.Errorf("unable to resolve preset: %w",err)
292+
}
293+
// If no preset found, prompt the user to choose a preset
294+
ifpreset,err=promptPresetSelection(inv,tvPresets);err!=nil {
295+
returnxerrors.Errorf("unable to prompt user for preset: %w",err)
296+
}
297+
}
298+
299+
// Convert preset parameters into workspace build parameters
300+
presetParameters=presetParameterAsWorkspaceBuildParameters(preset.Parameters)
301+
// Inform the user which preset was applied and its parameters
302+
displayAppliedPreset(inv,preset,presetParameters)
303+
}else {
304+
// Inform the user that no preset was applied
305+
_,_=fmt.Fprintf(inv.Stdout,"%s",cliui.Bold("No preset applied."))
306+
}
307+
266308
richParameters,err:=prepWorkspaceBuild(inv,client,prepWorkspaceBuildArgs{
267309
Action:WorkspaceCreate,
268310
TemplateVersionID:templateVersionID,
269311
NewWorkspaceName:workspaceName,
270312

313+
PresetParameters:presetParameters,
271314
RichParameterFile:parameterFlags.richParameterFile,
272315
RichParameters:cliBuildParameters,
273316
RichParameterDefaults:cliBuildParameterDefaults,
@@ -291,14 +334,21 @@ func (r *RootCmd) create() *serpent.Command {
291334
ttlMillis=ptr.Ref(stopAfter.Milliseconds())
292335
}
293336

294-
workspace,err:=client.CreateUserWorkspace(inv.Context(),workspaceOwner, codersdk.CreateWorkspaceRequest{
337+
req:= codersdk.CreateWorkspaceRequest{
295338
TemplateVersionID:templateVersionID,
296339
Name:workspaceName,
297340
AutostartSchedule:schedSpec,
298341
TTLMillis:ttlMillis,
299342
RichParameterValues:richParameters,
300343
AutomaticUpdates:codersdk.AutomaticUpdates(autoUpdates),
301-
})
344+
}
345+
346+
// If a preset exists, update the create workspace request's preset ID
347+
ifpreset!=nil {
348+
req.TemplateVersionPresetID=preset.ID
349+
}
350+
351+
workspace,err:=client.CreateUserWorkspace(inv.Context(),workspaceOwner,req)
302352
iferr!=nil {
303353
returnxerrors.Errorf("create workspace: %w",err)
304354
}
@@ -333,6 +383,12 @@ func (r *RootCmd) create() *serpent.Command {
333383
Description:"Specify a template version name.",
334384
Value:serpent.StringOf(&templateVersion),
335385
},
386+
serpent.Option{
387+
Flag:"preset",
388+
Env:"CODER_PRESET_NAME",
389+
Description:"Specify the name of a template version preset. Use 'none' to explicitly indicate that no preset should be used.",
390+
Value:serpent.StringOf(&presetName),
391+
},
336392
serpent.Option{
337393
Flag:"start-at",
338394
Env:"CODER_WORKSPACE_START_AT",
@@ -377,12 +433,76 @@ type prepWorkspaceBuildArgs struct {
377433
PromptEphemeralParametersbool
378434
EphemeralParameters []codersdk.WorkspaceBuildParameter
379435

436+
PresetParameters []codersdk.WorkspaceBuildParameter
380437
PromptRichParametersbool
381438
RichParameters []codersdk.WorkspaceBuildParameter
382439
RichParameterFilestring
383440
RichParameterDefaults []codersdk.WorkspaceBuildParameter
384441
}
385442

443+
// resolvePreset returns the preset matching the given presetName (if specified),
444+
// or the default preset (if any).
445+
// Returns ErrNoPresetFound if no matching or default preset is found.
446+
funcresolvePreset(presets []codersdk.Preset,presetNamestring) (*codersdk.Preset,error) {
447+
// If preset name is specified, find it
448+
ifpresetName!="" {
449+
for_,p:=rangepresets {
450+
ifp.Name==presetName {
451+
return&p,nil
452+
}
453+
}
454+
returnnil,xerrors.Errorf("preset %q not found",presetName)
455+
}
456+
457+
// No preset name specified, search for the default preset
458+
for_,p:=rangepresets {
459+
ifp.Default {
460+
return&p,nil
461+
}
462+
}
463+
464+
// No preset found
465+
returnnil,ErrNoPresetFound
466+
}
467+
468+
// promptPresetSelection shows a CLI selection menu of the presets defined in the template version.
469+
// Returns the selected preset
470+
funcpromptPresetSelection(inv*serpent.Invocation,presets []codersdk.Preset) (*codersdk.Preset,error) {
471+
presetMap:=make(map[string]*codersdk.Preset)
472+
varpresetOptions []string
473+
474+
for_,preset:=rangepresets {
475+
option:=preset.Name
476+
presetOptions=append(presetOptions,option)
477+
presetMap[option]=&preset
478+
}
479+
480+
// Show selection UI
481+
_,_=fmt.Fprintln(inv.Stdout,pretty.Sprint(cliui.DefaultStyles.Wrap,"Select a preset below:"))
482+
selected,err:=cliui.Select(inv, cliui.SelectOptions{
483+
Options:presetOptions,
484+
HideSearch:true,
485+
})
486+
iferr!=nil {
487+
returnnil,xerrors.Errorf("failed to select preset: %w",err)
488+
}
489+
490+
returnpresetMap[selected],nil
491+
}
492+
493+
// displayAppliedPreset shows the user which preset was applied and its parameters
494+
funcdisplayAppliedPreset(inv*serpent.Invocation,preset*codersdk.Preset,parameters []codersdk.WorkspaceBuildParameter) {
495+
label:=fmt.Sprintf("Preset '%s'",preset.Name)
496+
ifpreset.Default {
497+
label+=" (default)"
498+
}
499+
500+
_,_=fmt.Fprintf(inv.Stdout,"%s applied:\n",cliui.Bold(label))
501+
for_,param:=rangeparameters {
502+
_,_=fmt.Fprintf(inv.Stdout," %s: '%s'\n",cliui.Bold(param.Name),param.Value)
503+
}
504+
}
505+
386506
// prepWorkspaceBuild will ensure a workspace build will succeed on the latest template version.
387507
// Any missing params will be prompted to the user. It supports rich parameters.
388508
funcprepWorkspaceBuild(inv*serpent.Invocation,client*codersdk.Client,argsprepWorkspaceBuildArgs) ([]codersdk.WorkspaceBuildParameter,error) {
@@ -411,6 +531,7 @@ func prepWorkspaceBuild(inv *serpent.Invocation, client *codersdk.Client, args p
411531
WithSourceWorkspaceParameters(args.SourceWorkspaceParameters).
412532
WithPromptEphemeralParameters(args.PromptEphemeralParameters).
413533
WithEphemeralParameters(args.EphemeralParameters).
534+
WithPresetParameters(args.PresetParameters).
414535
WithPromptRichParameters(args.PromptRichParameters).
415536
WithRichParameters(args.RichParameters).
416537
WithRichParametersFile(parameterFile).

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp