Skip to content

Commit e845dea

Browse files
authored
fix: prompt when parameter options are incompatible (#9247)
1 parent 8bfa312 commit e845dea

File tree

15 files changed

+219
-62
lines changed

15 files changed

+219
-62
lines changed

cli/parameterresolver.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,10 @@ next:
141141
continue // immutables should not be passed to consecutive builds
142142
}
143143

144+
if len(tvp.Options) > 0 && !isValidTemplateParameterOption(buildParameter, tvp.Options) {
145+
continue // do not propagate invalid options
146+
}
147+
144148
for i, r := range resolved {
145149
if r.Name == buildParameter.Name {
146150
resolved[i].Value = buildParameter.Value
@@ -180,10 +184,12 @@ func (pr *ParameterResolver) resolveWithInput(resolved []codersdk.WorkspaceBuild
180184
// Parameter has not been resolved yet, so CLI needs to determine if user should input it.
181185

182186
firstTimeUse := pr.isFirstTimeUse(tvp.Name)
187+
promptParameterOption := pr.isLastBuildParameterInvalidOption(tvp)
183188

184189
if (tvp.Ephemeral && pr.promptBuildOptions) ||
185190
(action == WorkspaceCreate && tvp.Required) ||
186191
(action == WorkspaceCreate && !tvp.Ephemeral) ||
192+
(action == WorkspaceUpdate && promptParameterOption) ||
187193
(action == WorkspaceUpdate && tvp.Mutable && tvp.Required) ||
188194
(action == WorkspaceUpdate && !tvp.Mutable && firstTimeUse) ||
189195
(action == WorkspaceUpdate && tvp.Mutable && !tvp.Ephemeral && pr.promptRichParameters) {
@@ -207,6 +213,19 @@ func (pr *ParameterResolver) isFirstTimeUse(parameterName string) bool {
207213
return findWorkspaceBuildParameter(parameterName, pr.lastBuildParameters) == nil
208214
}
209215

216+
func (pr *ParameterResolver) isLastBuildParameterInvalidOption(templateVersionParameter codersdk.TemplateVersionParameter) bool {
217+
if len(templateVersionParameter.Options) == 0 {
218+
return false
219+
}
220+
221+
for _, buildParameter := range pr.lastBuildParameters {
222+
if buildParameter.Name == templateVersionParameter.Name {
223+
return !isValidTemplateParameterOption(buildParameter, templateVersionParameter.Options)
224+
}
225+
}
226+
return false
227+
}
228+
210229
func findTemplateVersionParameter(workspaceBuildParameter codersdk.WorkspaceBuildParameter, templateVersionParameters []codersdk.TemplateVersionParameter) *codersdk.TemplateVersionParameter {
211230
for _, tvp := range templateVersionParameters {
212231
if tvp.Name == workspaceBuildParameter.Name {
@@ -224,3 +243,12 @@ func findWorkspaceBuildParameter(parameterName string, params []codersdk.Workspa
224243
}
225244
return nil
226245
}
246+
247+
func isValidTemplateParameterOption(buildParameter codersdk.WorkspaceBuildParameter, options []codersdk.TemplateVersionParameterOption) bool {
248+
for _, opt := range options {
249+
if opt.Value == buildParameter.Value {
250+
return true
251+
}
252+
}
253+
return false
254+
}

cli/root.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -981,6 +981,8 @@ func (p *prettyErrorFormatter) format(err error) {
981981
msg = sdkError.Message
982982
if sdkError.Helper != "" {
983983
msg = msg + "\n" + sdkError.Helper
984+
} else if sdkError.Detail != "" {
985+
msg = msg + "\n" + sdkError.Detail
984986
}
985987
// The SDK error is usually good enough, and we don't want to overwhelm
986988
// the user with output.

cli/update_test.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,12 +593,138 @@ func TestUpdateValidateRichParameters(t *testing.T) {
593593
assert.NoError(t, err)
594594
}()
595595

596+
pty.ExpectMatch("Planning workspace...")
597+
<-doneChan
598+
})
599+
600+
t.Run("ParameterOptionChanged", func(t *testing.T) {
601+
t.Parallel()
602+
603+
// Create template and workspace
604+
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
605+
user := coderdtest.CreateFirstUser(t, client)
606+
607+
templateParameters := []*proto.RichParameter{
608+
{Name: stringParameterName, Type: "string", Mutable: true, Required: true, Options: []*proto.RichParameterOption{
609+
{Name: "First option", Description: "This is first option", Value: "1st"},
610+
{Name: "Second option", Description: "This is second option", Value: "2nd"},
611+
{Name: "Third option", Description: "This is third option", Value: "3rd"},
612+
}},
613+
}
614+
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, prepareEchoResponses(templateParameters))
615+
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
616+
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
617+
618+
inv, root := clitest.New(t, "create", "my-workspace", "--yes", "--template", template.Name, "--parameter", fmt.Sprintf("%s=%s", stringParameterName, "2nd"))
619+
clitest.SetupConfig(t, client, root)
620+
err := inv.Run()
621+
require.NoError(t, err)
622+
623+
// Update template
624+
updatedTemplateParameters := []*proto.RichParameter{
625+
{Name: stringParameterName, Type: "string", Mutable: true, Required: true, Options: []*proto.RichParameterOption{
626+
{Name: "first_option", Description: "This is first option", Value: "1"},
627+
{Name: "second_option", Description: "This is second option", Value: "2"},
628+
{Name: "third_option", Description: "This is third option", Value: "3"},
629+
}},
630+
}
631+
632+
updatedVersion := coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, prepareEchoResponses(updatedTemplateParameters), template.ID)
633+
coderdtest.AwaitTemplateVersionJob(t, client, updatedVersion.ID)
634+
err = client.UpdateActiveTemplateVersion(context.Background(), template.ID, codersdk.UpdateActiveTemplateVersion{
635+
ID: updatedVersion.ID,
636+
})
637+
require.NoError(t, err)
638+
639+
// Update the workspace
640+
inv, root = clitest.New(t, "update", "my-workspace")
641+
clitest.SetupConfig(t, client, root)
642+
doneChan := make(chan struct{})
643+
pty := ptytest.New(t).Attach(inv)
644+
go func() {
645+
defer close(doneChan)
646+
err := inv.Run()
647+
assert.NoError(t, err)
648+
}()
649+
596650
matches := []string{
651+
stringParameterName, "second_option",
597652
"Planning workspace...", "",
598653
}
599654
for i := 0; i < len(matches); i += 2 {
600655
match := matches[i]
656+
value := matches[i+1]
601657
pty.ExpectMatch(match)
658+
659+
if value != "" {
660+
pty.WriteLine(value)
661+
}
662+
}
663+
<-doneChan
664+
})
665+
666+
t.Run("ParameterOptionDisappeared", func(t *testing.T) {
667+
t.Parallel()
668+
669+
// Create template and workspace
670+
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
671+
user := coderdtest.CreateFirstUser(t, client)
672+
673+
templateParameters := []*proto.RichParameter{
674+
{Name: stringParameterName, Type: "string", Mutable: true, Required: true, Options: []*proto.RichParameterOption{
675+
{Name: "First option", Description: "This is first option", Value: "1st"},
676+
{Name: "Second option", Description: "This is second option", Value: "2nd"},
677+
{Name: "Third option", Description: "This is third option", Value: "3rd"},
678+
}},
679+
}
680+
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, prepareEchoResponses(templateParameters))
681+
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
682+
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
683+
684+
inv, root := clitest.New(t, "create", "my-workspace", "--yes", "--template", template.Name, "--parameter", fmt.Sprintf("%s=%s", stringParameterName, "2nd"))
685+
clitest.SetupConfig(t, client, root)
686+
err := inv.Run()
687+
require.NoError(t, err)
688+
689+
// Update template - 2nd option disappeared, 4th option added
690+
updatedTemplateParameters := []*proto.RichParameter{
691+
{Name: stringParameterName, Type: "string", Mutable: true, Required: true, Options: []*proto.RichParameterOption{
692+
{Name: "First option", Description: "This is first option", Value: "1st"},
693+
{Name: "Third option", Description: "This is third option", Value: "3rd"},
694+
{Name: "Fourth option", Description: "This is fourth option", Value: "4th"},
695+
}},
696+
}
697+
698+
updatedVersion := coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, prepareEchoResponses(updatedTemplateParameters), template.ID)
699+
coderdtest.AwaitTemplateVersionJob(t, client, updatedVersion.ID)
700+
err = client.UpdateActiveTemplateVersion(context.Background(), template.ID, codersdk.UpdateActiveTemplateVersion{
701+
ID: updatedVersion.ID,
702+
})
703+
require.NoError(t, err)
704+
705+
// Update the workspace
706+
inv, root = clitest.New(t, "update", "my-workspace")
707+
clitest.SetupConfig(t, client, root)
708+
doneChan := make(chan struct{})
709+
pty := ptytest.New(t).Attach(inv)
710+
go func() {
711+
defer close(doneChan)
712+
err := inv.Run()
713+
assert.NoError(t, err)
714+
}()
715+
716+
matches := []string{
717+
stringParameterName, "Third option",
718+
"Planning workspace...", "",
719+
}
720+
for i := 0; i < len(matches); i += 2 {
721+
match := matches[i]
722+
value := matches[i+1]
723+
pty.ExpectMatch(match)
724+
725+
if value != "" {
726+
pty.WriteLine(value)
727+
}
602728
}
603729
<-doneChan
604730
})

coderd/apidoc/docs.go

Lines changed: 0 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

Lines changed: 3 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/wsbuilder/wsbuilder.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,7 @@ func (b *Builder) getParameters() (names, values []string, err error) {
525525
// At this point, we've queried all the data we need from the database,
526526
// so the only errors are problems with the request (missing data, failed
527527
// validation, immutable parameters, etc.)
528-
return nil, nil, BuildError{http.StatusBadRequest, err.Error(), err}
528+
return nil, nil, BuildError{http.StatusBadRequest, fmt.Sprintf("Unable to validate parameter %q", templateVersionParameter.Name), err}
529529
}
530530
names = append(names, templateVersionParameter.Name)
531531
values = append(values, value)

codersdk/provisionerdaemons.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ const (
6969
type JobErrorCode string
7070

7171
const (
72-
MissingTemplateParameter JobErrorCode = "MISSING_TEMPLATE_PARAMETER"
7372
RequiredTemplateVariables JobErrorCode = "REQUIRED_TEMPLATE_VARIABLES"
7473
)
7574

@@ -81,7 +80,7 @@ type ProvisionerJob struct {
8180
CompletedAt *time.Time `json:"completed_at,omitempty" format:"date-time"`
8281
CanceledAt *time.Time `json:"canceled_at,omitempty" format:"date-time"`
8382
Error string `json:"error,omitempty"`
84-
ErrorCode JobErrorCode `json:"error_code,omitempty" enums:"MISSING_TEMPLATE_PARAMETER,REQUIRED_TEMPLATE_VARIABLES"`
83+
ErrorCode JobErrorCode `json:"error_code,omitempty" enums:"REQUIRED_TEMPLATE_VARIABLES"`
8584
Status ProvisionerJobStatus `json:"status" enums:"pending,running,succeeded,canceling,canceled,failed"`
8685
WorkerID *uuid.UUID `json:"worker_id,omitempty" format:"uuid"`
8786
FileID uuid.UUID `json:"file_id" format:"uuid"`

docs/api/builds.md

Lines changed: 5 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/api/schemas.md

Lines changed: 6 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy