@@ -43,7 +43,7 @@ type WizardMode int
4343const (
4444 // WizardModeDeploy includes all steps: config + hostname + description + confirm deploy.
4545 WizardModeDeploy WizardMode = iota
46- // WizardModeTemplate includes only config steps (no hostname, description, confirm).
46+ // WizardModeTemplate includes config steps + hostname pattern + template description (no deploy hostname, deploy description, or confirm).
4747 WizardModeTemplate
4848)
4949
@@ -63,11 +63,13 @@ type TemplateResult struct {
6363 StorageType string
6464 StorageSkip bool // user explicitly chose "None (skip)"
6565 StartupScriptSkip bool // user explicitly chose "None (skip)"
66+ HostnamePattern string
67+ Description string
6668}
6769
68- // RunTemplateWizard runs the VM create wizard in template mode (no hostname,
69- // description, or confirm-deploy steps). Returns the wizard results for
70- // saving as a template .
70+ // RunTemplateWizard runs the VM create wizard in template mode. Includes config
71+ // steps plus hostname-pattern and template description, but no deploy hostname,
72+ // deploy description, or confirm-deploy steps .
7173func RunTemplateWizard (ctx context.Context , f cmdutil.Factory , ioStreams cmdutil.IOStreams ) (* TemplateResult , error ) {
7274 return runTemplateWizardWithOpts (ctx , f , ioStreams , & createOptions {
7375 LocationCode : verda .LocationFIN01 ,
@@ -91,14 +93,16 @@ func optsToTemplateResult(opts *createOptions) *TemplateResult {
9193 Kind : opts .Kind ,
9294 InstanceType : opts .InstanceType ,
9395 Location : opts .LocationCode ,
94- Image : opts .Image ,
96+ Image : opts .imageName ,
9597 OSVolumeSize : opts .OSVolumeSize ,
9698 SSHKeyNames : opts .sshKeyNames ,
9799 StartupScriptName : opts .startupScriptName ,
98100 StorageSize : opts .StorageSize ,
99101 StorageType : opts .StorageType ,
100102 StorageSkip : opts .StorageSize == 0 && len (opts .VolumeSpecs ) == 0 ,
101103 StartupScriptSkip : opts .StartupScriptID == "" && opts .startupScriptName == "" ,
104+ HostnamePattern : opts .hostnamePattern ,
105+ Description : opts .templateDescription ,
102106 }
103107 if opts .IsSpot {
104108 result .BillingType = "spot"
@@ -136,6 +140,12 @@ func buildCreateFlow(ctx context.Context, getClient clientFunc, opts *createOpti
136140 stepConfirmDeploy (ctx , errOut , getClient , cache , opts ),
137141 )
138142 }
143+ if mode == WizardModeTemplate {
144+ steps = append (steps ,
145+ stepHostnamePattern (opts ),
146+ stepTemplateDescription (opts ),
147+ )
148+ }
139149
140150 layout := []wizard.ViewDef {
141151 {ID : "hints" , View : wizard .NewHintBarView (wizard .WithHintStyle (bubbletea .HintStyle ()))},
@@ -404,6 +414,7 @@ func stepLocation(getClient clientFunc, cache *apiCache, opts *createOptions) wi
404414// --- Step 6: OS Image ---
405415
406416func stepImage (getClient clientFunc , opts * createOptions ) wizard.Step {
417+ var imagesByID map [string ]string // ID → Name lookup, built by Loader
407418 return wizard.Step {
408419 Name : "image" ,
409420 Description : "Operating system image" ,
@@ -420,11 +431,13 @@ func stepImage(getClient clientFunc, opts *createOptions) wizard.Step {
420431 if err != nil {
421432 return nil , fmt .Errorf ("fetching images: %w" , err )
422433 }
434+ imagesByID = make (map [string ]string , len (images ))
423435 var choices []wizard.Choice
424436 for _ , img := range images {
425437 if img .IsCluster {
426438 continue
427439 }
440+ imagesByID [img .ID ] = img .Name
428441 desc := ""
429442 if len (img .Details ) > 0 {
430443 desc = strings .Join (img .Details , ", " )
@@ -437,9 +450,15 @@ func stepImage(getClient clientFunc, opts *createOptions) wizard.Step {
437450 }
438451 return choices , nil
439452 },
440- Default : func (_ map [string ]any ) any { return opts .Image },
441- Setter : func (v any ) { opts .Image = v .(string ) },
442- Resetter : func () { opts .Image = "" },
453+ Default : func (_ map [string ]any ) any { return opts .Image },
454+ Setter : func (v any ) {
455+ id := v .(string )
456+ opts .Image = id
457+ if imagesByID != nil {
458+ opts .imageName = imagesByID [id ]
459+ }
460+ },
461+ Resetter : func () { opts .Image = "" ; opts .imageName = "" },
443462 IsSet : func () bool { return opts .Image != "" },
444463 Value : func () any { return opts .Image },
445464 }
@@ -719,6 +738,39 @@ func stepStartupScript(getClient clientFunc, opts *createOptions) wizard.Step {
719738 }
720739}
721740
741+ // --- Template Step: Hostname Pattern ---
742+
743+ func stepHostnamePattern (opts * createOptions ) wizard.Step {
744+ return wizard.Step {
745+ Name : "hostname-pattern" ,
746+ Description : "Hostname pattern (optional)" ,
747+ Prompt : wizard .TextInputPrompt ,
748+ Required : false ,
749+ Default : func (_ map [string ]any ) any {
750+ return "{random}-{location}"
751+ },
752+ Setter : func (v any ) { opts .hostnamePattern = v .(string ) },
753+ Resetter : func () { opts .hostnamePattern = "" },
754+ IsSet : func () bool { return opts .hostnamePattern != "" },
755+ Value : func () any { return opts .hostnamePattern },
756+ }
757+ }
758+
759+ // --- Template Step: Description ---
760+
761+ func stepTemplateDescription (opts * createOptions ) wizard.Step {
762+ return wizard.Step {
763+ Name : "template-description" ,
764+ Description : "Description (optional)" ,
765+ Prompt : wizard .TextInputPrompt ,
766+ Required : false ,
767+ Setter : func (v any ) { opts .templateDescription = v .(string ) },
768+ Resetter : func () { opts .templateDescription = "" },
769+ IsSet : func () bool { return opts .templateDescription != "" },
770+ Value : func () any { return opts .templateDescription },
771+ }
772+ }
773+
722774// --- Step 11: Hostname ---
723775
724776func stepHostname (opts * createOptions ) wizard.Step {
0 commit comments