@@ -29,6 +29,17 @@ const PresetNone = "none"
2929
3030var ErrNoPresetFound = xerrors .New ("no preset found" )
3131
32+ // isNonInteractive checks if the command is running in non-interactive mode
33+ // (i.e., the --yes/-y flag was provided).
34+ func isNonInteractive (inv * serpent.Invocation ) bool {
35+ if inv .ParsedFlags ().Lookup ("yes" ) != nil {
36+ if skip , _ := inv .ParsedFlags ().GetBool ("yes" ); skip {
37+ return true
38+ }
39+ }
40+ return false
41+ }
42+
3243type CreateOptions struct {
3344 BeforeCreate func (ctx context.Context , client * codersdk.Client , template codersdk.Template , templateVersionID uuid.UUID ) error
3445 AfterCreate func (ctx context.Context , inv * serpent.Invocation , client * codersdk.Client , workspace codersdk.Workspace ) error
@@ -75,6 +86,9 @@ func (r *RootCmd) Create(opts CreateOptions) *serpent.Command {
7586 }
7687
7788 if workspaceName == "" {
89+ if isNonInteractive (inv ) {
90+ return xerrors .New ("workspace name is required in non-interactive mode; provide it as an argument" )
91+ }
7892 workspaceName , err = cliui .Prompt (inv , cliui.PromptOptions {
7993 Text : "Specify a name for your workspace:" ,
8094 Validate : func (workspaceName string ) error {
@@ -122,62 +136,76 @@ func (r *RootCmd) Create(opts CreateOptions) *serpent.Command {
122136 var templateVersionID uuid.UUID
123137 switch {
124138 case templateName == "" :
125- _ , _ = fmt .Fprintln (inv .Stdout , pretty .Sprint (cliui .DefaultStyles .Wrap , "Select a template below to preview the provisioned infrastructure:" ))
126-
127139 templates , err := client .Templates (inv .Context (), codersdk.TemplateFilter {})
128140 if err != nil {
129141 return err
130142 }
131143
132- slices .SortFunc (templates , func (a , b codersdk.Template ) int {
133- return slice .Descending (a .ActiveUserCount , b .ActiveUserCount )
134- })
135-
136- templateNames := make ([]string , 0 , len (templates ))
137- templateByName := make (map [string ]codersdk.Template , len (templates ))
144+ // In non-interactive mode, auto-select if only one template exists,
145+ // otherwise require explicit --template flag.
146+ if isNonInteractive (inv ) {
147+ if len (templates ) == 0 {
148+ return xerrors .New ("no templates available" )
149+ }
150+ if len (templates ) > 1 {
151+ return xerrors .New ("multiple templates available; use the --template flag to specify which one" )
152+ }
153+ // Only one template available - auto-select it
154+ template = templates [0 ]
155+ templateVersionID = template .ActiveVersionID
156+ } else {
157+ _ , _ = fmt .Fprintln (inv .Stdout , pretty .Sprint (cliui .DefaultStyles .Wrap , "Select a template below to preview the provisioned infrastructure:" ))
158+
159+ slices .SortFunc (templates , func (a , b codersdk.Template ) int {
160+ return slice .Descending (a .ActiveUserCount , b .ActiveUserCount )
161+ })
138162
139- // If more than 1 organization exists in the list of templates,
140- // then include the organization name in the select options.
141- uniqueOrganizations := make (map [uuid.UUID ]bool )
142- for _ , template := range templates {
143- uniqueOrganizations [template .OrganizationID ] = true
144- }
163+ templateNames := make ([]string , 0 , len (templates ))
164+ templateByName := make (map [string ]codersdk.Template , len (templates ))
145165
146- for _ , template := range templates {
147- templateName := template .Name
148- if len (uniqueOrganizations ) > 1 {
149- templateName += cliui .Placeholder (
150- fmt .Sprintf (
151- " (%s)" ,
152- template .OrganizationName ,
153- ),
154- )
166+ // If more than 1 organization exists in the list of templates,
167+ // then include the organization name in the select options.
168+ uniqueOrganizations := make (map [uuid.UUID ]bool )
169+ for _ , tpl := range templates {
170+ uniqueOrganizations [tpl .OrganizationID ] = true
155171 }
156172
157- if template .ActiveUserCount > 0 {
158- templateName += cliui .Placeholder (
159- fmt .Sprintf (
160- " used by %s" ,
161- formatActiveDevelopers (template .ActiveUserCount ),
162- ),
163- )
173+ for _ , tpl := range templates {
174+ tplName := tpl .Name
175+ if len (uniqueOrganizations ) > 1 {
176+ tplName += cliui .Placeholder (
177+ fmt .Sprintf (
178+ " (%s)" ,
179+ tpl .OrganizationName ,
180+ ),
181+ )
182+ }
183+
184+ if tpl .ActiveUserCount > 0 {
185+ tplName += cliui .Placeholder (
186+ fmt .Sprintf (
187+ " used by %s" ,
188+ formatActiveDevelopers (tpl .ActiveUserCount ),
189+ ),
190+ )
191+ }
192+
193+ templateNames = append (templateNames , tplName )
194+ templateByName [tplName ] = tpl
164195 }
165196
166- templateNames = append (templateNames , templateName )
167- templateByName [templateName ] = template
168- }
197+ // Move the cursor up a single line for nicer display!
198+ option , err := cliui .Select (inv , cliui.SelectOptions {
199+ Options : templateNames ,
200+ HideSearch : true ,
201+ })
202+ if err != nil {
203+ return err
204+ }
169205
170- // Move the cursor up a single line for nicer display!
171- option , err := cliui .Select (inv , cliui.SelectOptions {
172- Options : templateNames ,
173- HideSearch : true ,
174- })
175- if err != nil {
176- return err
206+ template = templateByName [option ]
207+ templateVersionID = template .ActiveVersionID
177208 }
178-
179- template = templateByName [option ]
180- templateVersionID = template .ActiveVersionID
181209 case sourceWorkspace .LatestBuild .TemplateVersionID != uuid .Nil :
182210 template , err = client .Template (inv .Context (), sourceWorkspace .TemplateID )
183211 if err != nil {
@@ -297,19 +325,28 @@ func (r *RootCmd) Create(opts CreateOptions) *serpent.Command {
297325 if ! errors .Is (err , ErrNoPresetFound ) {
298326 return xerrors .Errorf ("unable to resolve preset: %w" , err )
299327 }
300- // If no preset found, prompt the user to choose a preset
301- if preset , err = promptPresetSelection (inv , tvPresets ); err != nil {
302- return xerrors .Errorf ("unable to prompt user for preset: %w" , err )
328+ // No preset found - in non-interactive mode, skip presets instead of prompting
329+ if isNonInteractive (inv ) {
330+ // Leave preset as nil, effectively skipping presets
331+ preset = nil
332+ } else {
333+ // Interactive mode - prompt the user to choose a preset
334+ if preset , err = promptPresetSelection (inv , tvPresets ); err != nil {
335+ return xerrors .Errorf ("unable to prompt user for preset: %w" , err )
336+ }
303337 }
304338 }
305339
306- // Convert preset parameters into workspace build parameters
307- presetParameters = presetParameterAsWorkspaceBuildParameters (preset .Parameters )
308- // Inform the user which preset was applied and its parameters
309- displayAppliedPreset (inv , preset , presetParameters )
310- } else {
340+ // Convert preset parameters into workspace build parameters (if a preset was selected)
341+ if preset != nil {
342+ presetParameters = presetParameterAsWorkspaceBuildParameters (preset .Parameters )
343+ // Inform the user which preset was applied and its parameters
344+ displayAppliedPreset (inv , preset , presetParameters )
345+ }
346+ }
347+ if preset == nil {
311348 // Inform the user that no preset was applied
312- _ , _ = fmt .Fprintf (inv .Stdout , "%s" , cliui .Bold ("No preset applied." ))
349+ _ , _ = fmt .Fprintf (inv .Stdout , "%s\n " , cliui .Bold ("No preset applied." ))
313350 }
314351
315352 if opts .BeforeCreate != nil {
@@ -330,6 +367,8 @@ func (r *RootCmd) Create(opts CreateOptions) *serpent.Command {
330367 RichParameterDefaults : cliBuildParameterDefaults ,
331368
332369 SourceWorkspaceParameters : sourceWorkspaceParameters ,
370+
371+ NonInteractive : isNonInteractive (inv ),
333372 })
334373 if err != nil {
335374 return xerrors .Errorf ("prepare build: %w" , err )
@@ -460,6 +499,8 @@ type prepWorkspaceBuildArgs struct {
460499 RichParameters []codersdk.WorkspaceBuildParameter
461500 RichParameterFile string
462501 RichParameterDefaults []codersdk.WorkspaceBuildParameter
502+
503+ NonInteractive bool
463504}
464505
465506// resolvePreset returns the preset matching the given presetName (if specified),
@@ -562,7 +603,8 @@ func prepWorkspaceBuild(inv *serpent.Invocation, client *codersdk.Client, args p
562603 WithPromptRichParameters (args .PromptRichParameters ).
563604 WithRichParameters (args .RichParameters ).
564605 WithRichParametersFile (parameterFile ).
565- WithRichParametersDefaults (args .RichParameterDefaults )
606+ WithRichParametersDefaults (args .RichParameterDefaults ).
607+ WithNonInteractive (args .NonInteractive )
566608 buildParameters , err := resolver .Resolve (inv , args .Action , templateVersionParameters )
567609 if err != nil {
568610 return nil , err
@@ -572,6 +614,7 @@ func prepWorkspaceBuild(inv *serpent.Invocation, client *codersdk.Client, args p
572614 Fetch : func (ctx context.Context ) ([]codersdk.TemplateVersionExternalAuth , error ) {
573615 return client .TemplateVersionExternalAuth (ctx , templateVersion .ID )
574616 },
617+ NonInteractive : args .NonInteractive ,
575618 })
576619 if err != nil {
577620 return nil , xerrors .Errorf ("template version git auth: %w" , err )
0 commit comments