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

Merged
ssncferreira merged 15 commits intomainfromssncferreira/cli-create-preset
Jul 28, 2025
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
15 commits
Select commitHold shift + click to select a range
982031a
feat(cli): add CLI support for creating a workspace with preset
ssncferreiraJul 17, 2025
ae16155
chore: fix presetParameterAsWorkspaceBuildParameters function
ssncferreiraJul 23, 2025
3d7b40e
chore: minor fixes
ssncferreiraJul 23, 2025
7159de2
Merge remote-tracking branch 'origin/main' into ssncferreira/cli-crea…
ssncferreiraJul 23, 2025
b8a1c14
chore: fix merge orgin/main
ssncferreiraJul 23, 2025
77a72b1
chore: prompt user to select a preset when no --preset flag is provided
ssncferreiraJul 23, 2025
cffe049
chore: update preset flag description
ssncferreiraJul 23, 2025
ac12e0a
Merge remote-tracking branch 'origin/main' into ssncferreira/cli-crea…
ssncferreiraJul 23, 2025
dbe0685
chore: default to preset if available and none specified
ssncferreiraJul 28, 2025
a1c8b5e
Merge remote-tracking branch 'origin/main' into ssncferreira/cli-crea…
ssncferreiraJul 28, 2025
645ec89
fix: make gen
ssncferreiraJul 28, 2025
197d387
Merge remote-tracking branch 'origin/main' into ssncferreira/cli-crea…
ssncferreiraJul 28, 2025
0b7cede
chore: minor fixes
ssncferreiraJul 28, 2025
6210f22
chore: run make gen
ssncferreiraJul 28, 2025
8256700
Merge remote-tracking branch 'origin/main' into ssncferreira/cli-crea…
ssncferreiraJul 28, 2025
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
125 changes: 123 additions & 2 deletionscli/create.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,7 @@ package cli

import (
"context"
"errors"
"fmt"
"io"
"slices"
Expand All@@ -21,10 +22,18 @@ import (
"github.com/coder/serpent"
)

// PresetNone represents the special preset value "none".
// It is used when a user runs `create --preset none`,
// indicating that the CLI should not apply any preset.
const PresetNone = "none"

var ErrNoPresetFound = xerrors.New("no preset found")

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 +272,45 @@ func (r *RootCmd) create() *serpent.Command {
}
}

// Get presets for the template version
tvPresets, err := client.TemplateVersionPresets(inv.Context(), templateVersionID)
if err != nil {
return xerrors.Errorf("failed to get presets: %w", err)
}

var preset *codersdk.Preset
var presetParameters []codersdk.WorkspaceBuildParameter

// If the template has no presets, or the user explicitly used --preset none,
// skip applying a preset
if len(tvPresets) > 0 && strings.ToLower(presetName) != PresetNone {
// Attempt to resolve which preset to use
preset, err = resolvePreset(tvPresets, presetName)
if err != nil {
if !errors.Is(err, ErrNoPresetFound) {
return xerrors.Errorf("unable to resolve preset: %w", err)
}
// If no preset found, prompt the user to choose a preset
if preset, err = promptPresetSelection(inv, tvPresets); err != nil {
return xerrors.Errorf("unable to prompt user for preset: %w", err)
}
}

// Convert preset parameters into workspace build parameters
presetParameters = presetParameterAsWorkspaceBuildParameters(preset.Parameters)
// Inform the user which preset was applied and its parameters
displayAppliedPreset(inv, preset, presetParameters)
} else {
// Inform the user that no preset was applied
_, _ = fmt.Fprintf(inv.Stdout, "%s", cliui.Bold("No preset applied."))
}

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 +334,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 +383,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 the name of a template version preset. Use 'none' to explicitly indicate that no preset should be used.",
Value: serpent.StringOf(&presetName),
},
serpent.Option{
Flag: "start-at",
Env: "CODER_WORKSPACE_START_AT",
Expand DownExpand Up@@ -377,12 +433,76 @@ type prepWorkspaceBuildArgs struct {
PromptEphemeralParameters bool
EphemeralParameters []codersdk.WorkspaceBuildParameter

PresetParameters []codersdk.WorkspaceBuildParameter
PromptRichParameters bool
RichParameters []codersdk.WorkspaceBuildParameter
RichParameterFile string
RichParameterDefaults []codersdk.WorkspaceBuildParameter
}

// resolvePreset returns the preset matching the given presetName (if specified),
// or the default preset (if any).
// Returns ErrNoPresetFound if no matching or default preset is found.
func resolvePreset(presets []codersdk.Preset, presetName string) (*codersdk.Preset, error) {
// If preset name is specified, find it
if presetName != "" {
for _, p := range presets {
if p.Name == presetName {
return &p, nil
}
}
return nil, xerrors.Errorf("preset %q not found", presetName)
}

// No preset name specified, search for the default preset
for _, p := range presets {
if p.Default {
return &p, nil
}
}

// No preset found
return nil, ErrNoPresetFound
}

// promptPresetSelection shows a CLI selection menu of the presets defined in the template version.
// Returns the selected preset
func promptPresetSelection(inv *serpent.Invocation, presets []codersdk.Preset) (*codersdk.Preset, error) {
presetMap := make(map[string]*codersdk.Preset)
var presetOptions []string

for _, preset := range presets {
option := preset.Name
presetOptions = append(presetOptions, option)
presetMap[option] = &preset
}

// Show selection UI
_, _ = fmt.Fprintln(inv.Stdout, pretty.Sprint(cliui.DefaultStyles.Wrap, "Select a preset below:"))
selected, err := cliui.Select(inv, cliui.SelectOptions{
Options: presetOptions,
HideSearch: true,
})
if err != nil {
return nil, xerrors.Errorf("failed to select preset: %w", err)
}

return presetMap[selected], nil
}

// displayAppliedPreset shows the user which preset was applied and its parameters
func displayAppliedPreset(inv *serpent.Invocation, preset *codersdk.Preset, parameters []codersdk.WorkspaceBuildParameter) {
label := fmt.Sprintf("Preset '%s'", preset.Name)
if preset.Default {
label += " (default)"
}

_, _ = fmt.Fprintf(inv.Stdout, "%s applied:\n", cliui.Bold(label))
for _, param := range parameters {
_, _ = fmt.Fprintf(inv.Stdout, " %s: '%s'\n", cliui.Bold(param.Name), param.Value)
}
}

// prepWorkspaceBuild will ensure a workspace build will succeed on the latest template version.
// Any missing params will be prompted to the user. It supports rich parameters.
func prepWorkspaceBuild(inv *serpent.Invocation, client *codersdk.Client, args prepWorkspaceBuildArgs) ([]codersdk.WorkspaceBuildParameter, error) {
Expand DownExpand Up@@ -411,6 +531,7 @@ func prepWorkspaceBuild(inv *serpent.Invocation, client *codersdk.Client, args p
WithSourceWorkspaceParameters(args.SourceWorkspaceParameters).
WithPromptEphemeralParameters(args.PromptEphemeralParameters).
WithEphemeralParameters(args.EphemeralParameters).
WithPresetParameters(args.PresetParameters).
WithPromptRichParameters(args.PromptRichParameters).
WithRichParameters(args.RichParameters).
WithRichParametersFile(parameterFile).
Expand Down
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp