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

feat(cli): add CLI support for creating a workspace with preset#18912

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

Open
ssncferreira wants to merge1 commit intomain
base:main
Choose a base branch
Loading
fromssncferreira/cli-create-preset
Open
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 69 additions & 2 deletionscli/create.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,10 +21,15 @@ import (
"github.com/coder/serpent"
)

// DefaultPresetName is used when a user runs `create --preset default`.
// It instructs the CLI to use the default preset defined for the template version, if one exists.
const DefaultPresetName = "default"
Copy link
ContributorAuthor

@ssncferreirassncferreiraJul 17, 2025
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

To preserve current behavior, if no--preset is provided, the CLI does not apply any preset. I added support for a specialdefault value to allow users to explicitly use the default preset (if one exists). However, this approach might not be ideal, as it introduces ambiguity, e.g., what if a preset is literally named "default"?

Another approach I considered was assigning a default value (likedefault) to the--preset flag itself. But that could lead to unexpected behavior: the user may not realize a preset is being applied at all.

A potentially better UX would be:

  • If the template has presets and none is specified via--preset, prompt the user to select one.
  • If there’s a default preset, it should appear as the first option (as in the UI).
  • Add anone option to explicitly skip using a preset (as in the UI). This means that instead of having a default value for the preset, we can have a none value that explicitly indicates no preset should be used.
  • This would bring the CLI experience closer to the web interface and make it more intuitive.

However, the downside is that this would break existing automation/scripts that rely on creating workspaces silently when presets exist. Those scripts would now hang, waiting for input.

Let me know what you think

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

You've summarised the trade-offs well. My assessment of them is that without a--preset flag specified, it should use the default as defined in the template. This use case is what we allow a default to be defined for.

I see you've already added a line to the command output to inform the user which preset was chosen, so that should help make it more visible.

Like with the frontend, we can allow for a "None" preset. In fact it might be a good idea to move that from the frontend to the API so that we have it here without any additional trouble.


func (r *RootCmd) create() *serpent.Command {
var (
templateName string
templateVersion string
presetName string
startAt string
stopAfter time.Duration
workspaceName string
Expand DownExpand Up@@ -263,11 +268,58 @@ func (r *RootCmd) create() *serpent.Command {
}
}

// If a preset name is provided, resolve the preset to use.
var preset *codersdk.Preset
var presetParameters []codersdk.WorkspaceBuildParameter
isDefaultPreset := false
if len(presetName) > 0 {
tvPresets, err := client.TemplateVersionPresets(inv.Context(), templateVersionID)
if err != nil {
return xerrors.Errorf("failed to get presets: %w", err)
}

for _, tvPreset := range tvPresets {
// If the preset name is the special "default" keyword,
// fetch the template version's default preset (if any).
if presetName == DefaultPresetName && tvPreset.Default {
preset = &tvPreset
isDefaultPreset = true
break
}
if tvPreset.Name == presetName {
preset = &tvPreset
break
}
}

if preset == nil {
return xerrors.Errorf("preset %q not found", presetName)
}

// Convert preset parameters into workspace build parameters.
presetBuildParameters, err := presetParameterAsWorkspaceBuildParameters(preset.Parameters)
if err != nil {
return xerrors.Errorf("failed to parse preset parameters: %w", err)
}
presetParameters = append(presetParameters, presetBuildParameters...)

// Inform the user which preset was applied and its parameters.
presetLabel := fmt.Sprintf("Preset '%s'", preset.Name)
if isDefaultPreset {
presetLabel += " (default)"
}
_, _ = fmt.Fprintf(inv.Stdout, "%s applied:", cliui.Bold(presetLabel))
for _, p := range presetParameters {
_, _ = fmt.Fprintf(inv.Stdout, " %s: '%s'\n", cliui.Bold(p.Name), p.Value)
}
}

richParameters, err := prepWorkspaceBuild(inv, client, prepWorkspaceBuildArgs{
Action: WorkspaceCreate,
TemplateVersionID: templateVersionID,
NewWorkspaceName: workspaceName,

PresetParameters: presetParameters,
RichParameterFile: parameterFlags.richParameterFile,
RichParameters: cliBuildParameters,
RichParameterDefaults: cliBuildParameterDefaults,
Expand All@@ -291,14 +343,21 @@ func (r *RootCmd) create() *serpent.Command {
ttlMillis = ptr.Ref(stopAfter.Milliseconds())
}

workspace, err := client.CreateUserWorkspace(inv.Context(), workspaceOwner, codersdk.CreateWorkspaceRequest{
req := codersdk.CreateWorkspaceRequest{
TemplateVersionID: templateVersionID,
Name: workspaceName,
AutostartSchedule: schedSpec,
TTLMillis: ttlMillis,
RichParameterValues: richParameters,
AutomaticUpdates: codersdk.AutomaticUpdates(autoUpdates),
})
}

// If a preset exists, update the create workspace request's preset ID
if preset != nil {
req.TemplateVersionPresetID = preset.ID
}

workspace, err := client.CreateUserWorkspace(inv.Context(), workspaceOwner, req)
if err != nil {
return xerrors.Errorf("create workspace: %w", err)
}
Expand DownExpand Up@@ -333,6 +392,12 @@ func (r *RootCmd) create() *serpent.Command {
Description: "Specify a template version name.",
Value: serpent.StringOf(&templateVersion),
},
serpent.Option{
Flag: "preset",
Env: "CODER_PRESET_NAME",
Description: "Specify a template version preset name. Use 'default' to apply the default preset defined in the template version, if available.",
Value: serpent.StringOf(&presetName),
},
serpent.Option{
Flag: "start-at",
Env: "CODER_WORKSPACE_START_AT",
Expand DownExpand Up@@ -377,6 +442,7 @@ type prepWorkspaceBuildArgs struct {
PromptEphemeralParameters bool
EphemeralParameters []codersdk.WorkspaceBuildParameter

PresetParameters []codersdk.WorkspaceBuildParameter
PromptRichParameters bool
RichParameters []codersdk.WorkspaceBuildParameter
RichParameterFile string
Expand DownExpand Up@@ -411,6 +477,7 @@ func prepWorkspaceBuild(inv *serpent.Invocation, client *codersdk.Client, args p
WithSourceWorkspaceParameters(args.SourceWorkspaceParameters).
WithPromptEphemeralParameters(args.PromptEphemeralParameters).
WithEphemeralParameters(args.EphemeralParameters).
WithPresetParameters(args.PresetParameters).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Should this not be last in the list? Otherwise we allow preset parameters to be overwritten.

WithPromptRichParameters(args.PromptRichParameters).
WithRichParameters(args.RichParameters).
WithRichParametersFile(parameterFile).
Expand Down
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp