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

Commitbb58844

Browse files
author
Rafael Rodriguez
authored
feat(cli): prompt for missing required template variables on template creation (#19973)
## SummaryIn this pull request we're adding support in the CLI for prompting theuser for any missing required template variables in the `coder templatespush` command and automatically retrying the template build once a userhas provided any missing variable values.Closes:#19782### DemoIn the following recording I created a simple template terraform filethat used different variable types (string, number, boolean, andsensitive) and prompted the user to enter a value for each variable.<details><summary>See example template terraform file</summary>```tf...# Required variables for testing interactive promptingvariable "docker_image" { description = "Docker image to use for the workspace" type = string}variable "workspace_name" { description = "Name of the workspace" type = string}variable "cpu_limit" { description = "CPU limit for the container (number of cores)" type = number}variable "enable_gpu" { description = "Enable GPU access for the container" type = bool}variable "api_key" { description = "API key for external services (sensitive)" type = string sensitive = true}# Optional variable with defaultvariable "docker_socket" { default = "/var/run/docker.sock" description = "Docker socket path" type = string}...```</details>Once the user entered a valid value for each variable, the templatebuild would be retried.https://github.com/user-attachments/assets/770cf954-3cbc-4464-925e-2be4e32a97de<details><summary>See output from recording</summary>```shell$ ./scripts/coder-dev.sh templates push test-required-params -d examples/templates/test-required-params/INFO : Overriding codersdk.SessionTokenCookie as we are developing inside a Coder workspace./home/coder/coder/build/coder-slim_2.26.0-devel+a68122ca3_linux_amd64Provisioner tags: <none>WARN: No .terraform.lock.hcl file found | When provisioning, Coder will be unable to cache providers without a lockfile and must download them from the internet each time. | Create one by running terraform init in your template directory.> Upload "examples/templates/test-required-params"? (yes/no) yes=== ✔ Queued [0ms]==> ⧗ Running==> ⧗ Running=== ✔ Running [4ms]==> ⧗ Setting up=== ✔ Setting up [0ms]==> ⧗ Parsing template parameters=== ✔ Parsing template parameters [8ms]==> ⧗ Cleaning Up=== ✘ Cleaning Up [4ms]=== ✘ Cleaning Up [8ms]Found 5 missing required variables: - docker_image (string): Docker image to use for the workspace - workspace_name (string): Name of the workspace - cpu_limit (number): CPU limit for the container (number of cores) - enable_gpu (bool): Enable GPU access for the container - api_key (string): API key for external services (sensitive)The template requires values for the following variables:var.docker_image (required) Description: Docker image to use for the workspace Type: string Current value: <empty>> Enter value: image-namevar.workspace_name (required) Description: Name of the workspace Type: string Current value: <empty>> Enter value: workspace-namevar.cpu_limit (required) Description: CPU limit for the container (number of cores) Type: number Current value: <empty>> Enter value: 1var.enable_gpu (required) Description: Enable GPU access for the container Type: bool Current value: <empty>? Select value: falsevar.api_key (required), sensitive Description: API key for external services (sensitive) Type: string Current value: <empty>> Enter value: (*redacted*) ******Retrying template build with provided variables...=== ✔ Queued [0ms]==> ⧗ Running==> ⧗ Running=== ✔ Running [2ms]==> ⧗ Setting up=== ✔ Setting up [0ms]==> ⧗ Parsing template parameters=== ✔ Parsing template parameters [7ms]==> ⧗ Detecting persistent resources2025-09-25 22:34:14.731Z Terraform 1.13.02025-09-25 22:34:15.140Z data.coder_provisioner.me: Refreshing...2025-09-25 22:34:15.140Z data.coder_workspace.me: Refreshing...2025-09-25 22:34:15.140Z data.coder_workspace_owner.me: Refreshing...2025-09-25 22:34:15.141Z data.coder_provisioner.me: Refresh complete after 0s [id=2bd73098-d127-4362-b3a5-628e5bce6998]2025-09-25 22:34:15.141Z data.coder_workspace_owner.me: Refresh complete after 0s [id=c2006933-4f3e-4c45-9e04-79612c3a5eca]2025-09-25 22:34:15.141Z data.coder_workspace.me: Refresh complete after 0s [id=36f2dc6f-0bf2-43bd-bc4d-b29768334e02]2025-09-25 22:34:15.186Z coder_agent.main: Plan to create2025-09-25 22:34:15.186Z module.code-server[0].coder_app.code-server: Plan to create2025-09-25 22:34:15.186Z docker_volume.home_volume: Plan to create2025-09-25 22:34:15.186Z module.code-server[0].coder_script.code-server: Plan to create2025-09-25 22:34:15.187Z docker_container.workspace[0]: Plan to create2025-09-25 22:34:15.187Z Plan: 5 to add, 0 to change, 0 to destroy.=== ✔ Detecting persistent resources [3104ms]==> ⧗ Detecting ephemeral resources2025-09-25 22:34:16.033Z Terraform 1.13.02025-09-25 22:34:16.428Z data.coder_workspace.me: Refreshing...2025-09-25 22:34:16.428Z data.coder_provisioner.me: Refreshing...2025-09-25 22:34:16.429Z data.coder_workspace_owner.me: Refreshing...2025-09-25 22:34:16.429Z data.coder_provisioner.me: Refresh complete after 0s [id=2d2f7083-88e6-425c-9df3-856a3bf4cc73]2025-09-25 22:34:16.429Z data.coder_workspace.me: Refresh complete after 0s [id=c723575e-c7d3-43d7-bf54-0e34d0959dc3]2025-09-25 22:34:16.431Z data.coder_workspace_owner.me: Refresh complete after 0s [id=d43470c2-236e-4ae9-a977-6b53688c2cb1]2025-09-25 22:34:16.453Z coder_agent.main: Plan to create2025-09-25 22:34:16.453Z docker_volume.home_volume: Plan to create2025-09-25 22:34:16.454Z Plan: 2 to add, 0 to change, 0 to destroy.=== ✔ Detecting ephemeral resources [1278ms]==> ⧗ Cleaning Up=== ✔ Cleaning Up [6ms]┌──────────────────────────────────┐│ Template Preview │├──────────────────────────────────┤│ RESOURCE │├──────────────────────────────────┤│ docker_container.workspace ││ └─ main (linux, amd64) │├──────────────────────────────────┤│ docker_volume.home_volume │└──────────────────────────────────┘The test-required-params template has been created at Sep 2522:34:16! Developers can provision a workspace with this template using:Updated version at Sep 25 22:34:16!```</details>### Changes- Added a new function to check if the provisioner failed due to atemplate missing required variables- Added a handler function that is called when a provisioner fails dueto the "missing required variables" error. The handler function will:- Check for provided template variables and identify any missingvariables- Prompt the user for any missing variables (prompt is adapted based onthe variable type) - Validate user input for missing variables- Retry the template build when all variables have been provided by theuser### TestingAdded tests for the following scenarios:- Ensure validation based on variable type- Ensure users are not prompted for variables with a default value- Ensure variables provided via a variables files (`--variables-file`)or a variable flag (`--variable`) take precedence over a template
1 parenta360785 commitbb58844

File tree

4 files changed

+400
-53
lines changed

4 files changed

+400
-53
lines changed

‎cli/templatepush.go‎

Lines changed: 158 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"os"
1010
"path/filepath"
1111
"slices"
12+
"strconv"
1213
"strings"
1314
"time"
1415

@@ -461,10 +462,14 @@ func createValidTemplateVersion(inv *serpent.Invocation, args createValidTemplat
461462
})
462463
iferr!=nil {
463464
varjobErr*cliui.ProvisionerJobError
464-
iferrors.As(err,&jobErr)&&!codersdk.JobIsMissingParameterErrorCode(jobErr.Code) {
465-
returnnil,err
465+
iferrors.As(err,&jobErr) {
466+
ifcodersdk.JobIsMissingRequiredTemplateVariableErrorCode(jobErr.Code) {
467+
returnhandleMissingTemplateVariables(inv,args,version.ID)
468+
}
469+
if!codersdk.JobIsMissingParameterErrorCode(jobErr.Code) {
470+
returnnil,err
471+
}
466472
}
467-
468473
returnnil,err
469474
}
470475
version,err=client.TemplateVersion(inv.Context(),version.ID)
@@ -528,3 +533,153 @@ func prettyDirectoryPath(dir string) string {
528533
}
529534
returnprettyDir
530535
}
536+
537+
funchandleMissingTemplateVariables(inv*serpent.Invocation,argscreateValidTemplateVersionArgs,failedVersionID uuid.UUID) (*codersdk.TemplateVersion,error) {
538+
client:=args.Client
539+
540+
templateVariables,err:=client.TemplateVersionVariables(inv.Context(),failedVersionID)
541+
iferr!=nil {
542+
returnnil,xerrors.Errorf("fetch template variables: %w",err)
543+
}
544+
545+
existingValues:=make(map[string]string)
546+
for_,v:=rangeargs.UserVariableValues {
547+
existingValues[v.Name]=v.Value
548+
}
549+
550+
varmissingVariables []codersdk.TemplateVersionVariable
551+
for_,variable:=rangetemplateVariables {
552+
if!variable.Required {
553+
continue
554+
}
555+
556+
ifexistingValue,exists:=existingValues[variable.Name];exists&&existingValue!="" {
557+
continue
558+
}
559+
560+
// Only prompt for variables that don't have a default value or have a redacted default
561+
// Sensitive variables have a default value of "*redacted*"
562+
// See: https://github.com/coder/coder/blob/a78790c632974e04babfef6de0e2ddf044787a7a/coderd/provisionerdserver/provisionerdserver.go#L3206
563+
ifvariable.DefaultValue==""|| (variable.Sensitive&&variable.DefaultValue=="*redacted*") {
564+
missingVariables=append(missingVariables,variable)
565+
}
566+
}
567+
568+
iflen(missingVariables)==0 {
569+
returnnil,xerrors.New("no missing required variables found")
570+
}
571+
572+
_,_=fmt.Fprintf(inv.Stderr,"Found %d missing required variables:\n",len(missingVariables))
573+
for_,v:=rangemissingVariables {
574+
_,_=fmt.Fprintf(inv.Stderr," - %s (%s): %s\n",v.Name,v.Type,v.Description)
575+
}
576+
577+
_,_=fmt.Fprintln(inv.Stderr,"\nThe template requires values for the following variables:")
578+
579+
varpromptedValues []codersdk.VariableValue
580+
for_,variable:=rangemissingVariables {
581+
value,err:=promptForTemplateVariable(inv,variable)
582+
iferr!=nil {
583+
returnnil,xerrors.Errorf("prompt for variable %q: %w",variable.Name,err)
584+
}
585+
promptedValues=append(promptedValues, codersdk.VariableValue{
586+
Name:variable.Name,
587+
Value:value,
588+
})
589+
}
590+
591+
combinedValues:=codersdk.CombineVariableValues(args.UserVariableValues,promptedValues)
592+
593+
_,_=fmt.Fprintln(inv.Stderr,"\nRetrying template build with provided variables...")
594+
595+
retryArgs:=args
596+
retryArgs.UserVariableValues=combinedValues
597+
598+
returncreateValidTemplateVersion(inv,retryArgs)
599+
}
600+
601+
funcpromptForTemplateVariable(inv*serpent.Invocation,variable codersdk.TemplateVersionVariable) (string,error) {
602+
displayVariableInfo(inv,variable)
603+
604+
switchvariable.Type {
605+
case"bool":
606+
returnpromptForBoolVariable(inv,variable)
607+
case"number":
608+
returnpromptForNumberVariable(inv,variable)
609+
default:
610+
returnpromptForStringVariable(inv,variable)
611+
}
612+
}
613+
614+
funcdisplayVariableInfo(inv*serpent.Invocation,variable codersdk.TemplateVersionVariable) {
615+
_,_=fmt.Fprintf(inv.Stderr,"var.%s",cliui.Bold(variable.Name))
616+
ifvariable.Required {
617+
_,_=fmt.Fprint(inv.Stderr,pretty.Sprint(cliui.DefaultStyles.Error," (required)"))
618+
}
619+
ifvariable.Sensitive {
620+
_,_=fmt.Fprint(inv.Stderr,pretty.Sprint(cliui.DefaultStyles.Warn,", sensitive"))
621+
}
622+
_,_=fmt.Fprintln(inv.Stderr,"")
623+
624+
ifvariable.Description!="" {
625+
_,_=fmt.Fprintf(inv.Stderr," Description: %s\n",variable.Description)
626+
}
627+
_,_=fmt.Fprintf(inv.Stderr," Type: %s\n",variable.Type)
628+
_,_=fmt.Fprintf(inv.Stderr," Current value: %s\n",pretty.Sprint(cliui.DefaultStyles.Placeholder,"<empty>"))
629+
}
630+
631+
funcpromptForBoolVariable(inv*serpent.Invocation,variable codersdk.TemplateVersionVariable) (string,error) {
632+
defaultValue:=variable.DefaultValue
633+
ifdefaultValue=="" {
634+
defaultValue="false"
635+
}
636+
637+
returncliui.Select(inv, cliui.SelectOptions{
638+
Options: []string{"true","false"},
639+
Default:defaultValue,
640+
Message:"Select value:",
641+
})
642+
}
643+
644+
funcpromptForNumberVariable(inv*serpent.Invocation,variable codersdk.TemplateVersionVariable) (string,error) {
645+
prompt:="Enter value:"
646+
if!variable.Required&&variable.DefaultValue!="" {
647+
prompt=fmt.Sprintf("Enter value (default: %q):",variable.DefaultValue)
648+
}
649+
650+
returncliui.Prompt(inv, cliui.PromptOptions{
651+
Text:prompt,
652+
Default:variable.DefaultValue,
653+
Validate:createVariableValidator(variable),
654+
})
655+
}
656+
657+
funcpromptForStringVariable(inv*serpent.Invocation,variable codersdk.TemplateVersionVariable) (string,error) {
658+
prompt:="Enter value:"
659+
if!variable.Sensitive {
660+
if!variable.Required&&variable.DefaultValue!="" {
661+
prompt=fmt.Sprintf("Enter value (default: %q):",variable.DefaultValue)
662+
}
663+
}
664+
665+
returncliui.Prompt(inv, cliui.PromptOptions{
666+
Text:prompt,
667+
Default:variable.DefaultValue,
668+
Secret:variable.Sensitive,
669+
Validate:createVariableValidator(variable),
670+
})
671+
}
672+
673+
funccreateVariableValidator(variable codersdk.TemplateVersionVariable)func(string)error {
674+
returnfunc(sstring)error {
675+
ifvariable.Required&&s==""&&variable.DefaultValue=="" {
676+
returnxerrors.New("value is required")
677+
}
678+
ifvariable.Type=="number"&&s!="" {
679+
if_,err:=strconv.ParseFloat(s,64);err!=nil {
680+
returnxerrors.Errorf("must be a valid number, got: %q",s)
681+
}
682+
}
683+
returnnil
684+
}
685+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp