fix: prompt when parameter options are incompatible (#9247)

This commit is contained in:
Marcin Tojek
2023-08-23 18:18:38 +02:00
committed by GitHub
parent 8bfa312905
commit e845deaa89
15 changed files with 219 additions and 62 deletions
+28
View File
@@ -141,6 +141,10 @@ next:
continue // immutables should not be passed to consecutive builds
}
if len(tvp.Options) > 0 && !isValidTemplateParameterOption(buildParameter, tvp.Options) {
continue // do not propagate invalid options
}
for i, r := range resolved {
if r.Name == buildParameter.Name {
resolved[i].Value = buildParameter.Value
@@ -180,10 +184,12 @@ func (pr *ParameterResolver) resolveWithInput(resolved []codersdk.WorkspaceBuild
// Parameter has not been resolved yet, so CLI needs to determine if user should input it.
firstTimeUse := pr.isFirstTimeUse(tvp.Name)
promptParameterOption := pr.isLastBuildParameterInvalidOption(tvp)
if (tvp.Ephemeral && pr.promptBuildOptions) ||
(action == WorkspaceCreate && tvp.Required) ||
(action == WorkspaceCreate && !tvp.Ephemeral) ||
(action == WorkspaceUpdate && promptParameterOption) ||
(action == WorkspaceUpdate && tvp.Mutable && tvp.Required) ||
(action == WorkspaceUpdate && !tvp.Mutable && firstTimeUse) ||
(action == WorkspaceUpdate && tvp.Mutable && !tvp.Ephemeral && pr.promptRichParameters) {
@@ -207,6 +213,19 @@ func (pr *ParameterResolver) isFirstTimeUse(parameterName string) bool {
return findWorkspaceBuildParameter(parameterName, pr.lastBuildParameters) == nil
}
func (pr *ParameterResolver) isLastBuildParameterInvalidOption(templateVersionParameter codersdk.TemplateVersionParameter) bool {
if len(templateVersionParameter.Options) == 0 {
return false
}
for _, buildParameter := range pr.lastBuildParameters {
if buildParameter.Name == templateVersionParameter.Name {
return !isValidTemplateParameterOption(buildParameter, templateVersionParameter.Options)
}
}
return false
}
func findTemplateVersionParameter(workspaceBuildParameter codersdk.WorkspaceBuildParameter, templateVersionParameters []codersdk.TemplateVersionParameter) *codersdk.TemplateVersionParameter {
for _, tvp := range templateVersionParameters {
if tvp.Name == workspaceBuildParameter.Name {
@@ -224,3 +243,12 @@ func findWorkspaceBuildParameter(parameterName string, params []codersdk.Workspa
}
return nil
}
func isValidTemplateParameterOption(buildParameter codersdk.WorkspaceBuildParameter, options []codersdk.TemplateVersionParameterOption) bool {
for _, opt := range options {
if opt.Value == buildParameter.Value {
return true
}
}
return false
}
+2
View File
@@ -981,6 +981,8 @@ func (p *prettyErrorFormatter) format(err error) {
msg = sdkError.Message
if sdkError.Helper != "" {
msg = msg + "\n" + sdkError.Helper
} else if sdkError.Detail != "" {
msg = msg + "\n" + sdkError.Detail
}
// The SDK error is usually good enough, and we don't want to overwhelm
// the user with output.
+126
View File
@@ -593,12 +593,138 @@ func TestUpdateValidateRichParameters(t *testing.T) {
assert.NoError(t, err)
}()
pty.ExpectMatch("Planning workspace...")
<-doneChan
})
t.Run("ParameterOptionChanged", func(t *testing.T) {
t.Parallel()
// Create template and workspace
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
templateParameters := []*proto.RichParameter{
{Name: stringParameterName, Type: "string", Mutable: true, Required: true, Options: []*proto.RichParameterOption{
{Name: "First option", Description: "This is first option", Value: "1st"},
{Name: "Second option", Description: "This is second option", Value: "2nd"},
{Name: "Third option", Description: "This is third option", Value: "3rd"},
}},
}
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, prepareEchoResponses(templateParameters))
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
inv, root := clitest.New(t, "create", "my-workspace", "--yes", "--template", template.Name, "--parameter", fmt.Sprintf("%s=%s", stringParameterName, "2nd"))
clitest.SetupConfig(t, client, root)
err := inv.Run()
require.NoError(t, err)
// Update template
updatedTemplateParameters := []*proto.RichParameter{
{Name: stringParameterName, Type: "string", Mutable: true, Required: true, Options: []*proto.RichParameterOption{
{Name: "first_option", Description: "This is first option", Value: "1"},
{Name: "second_option", Description: "This is second option", Value: "2"},
{Name: "third_option", Description: "This is third option", Value: "3"},
}},
}
updatedVersion := coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, prepareEchoResponses(updatedTemplateParameters), template.ID)
coderdtest.AwaitTemplateVersionJob(t, client, updatedVersion.ID)
err = client.UpdateActiveTemplateVersion(context.Background(), template.ID, codersdk.UpdateActiveTemplateVersion{
ID: updatedVersion.ID,
})
require.NoError(t, err)
// Update the workspace
inv, root = clitest.New(t, "update", "my-workspace")
clitest.SetupConfig(t, client, root)
doneChan := make(chan struct{})
pty := ptytest.New(t).Attach(inv)
go func() {
defer close(doneChan)
err := inv.Run()
assert.NoError(t, err)
}()
matches := []string{
stringParameterName, "second_option",
"Planning workspace...", "",
}
for i := 0; i < len(matches); i += 2 {
match := matches[i]
value := matches[i+1]
pty.ExpectMatch(match)
if value != "" {
pty.WriteLine(value)
}
}
<-doneChan
})
t.Run("ParameterOptionDisappeared", func(t *testing.T) {
t.Parallel()
// Create template and workspace
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
templateParameters := []*proto.RichParameter{
{Name: stringParameterName, Type: "string", Mutable: true, Required: true, Options: []*proto.RichParameterOption{
{Name: "First option", Description: "This is first option", Value: "1st"},
{Name: "Second option", Description: "This is second option", Value: "2nd"},
{Name: "Third option", Description: "This is third option", Value: "3rd"},
}},
}
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, prepareEchoResponses(templateParameters))
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
inv, root := clitest.New(t, "create", "my-workspace", "--yes", "--template", template.Name, "--parameter", fmt.Sprintf("%s=%s", stringParameterName, "2nd"))
clitest.SetupConfig(t, client, root)
err := inv.Run()
require.NoError(t, err)
// Update template - 2nd option disappeared, 4th option added
updatedTemplateParameters := []*proto.RichParameter{
{Name: stringParameterName, Type: "string", Mutable: true, Required: true, Options: []*proto.RichParameterOption{
{Name: "First option", Description: "This is first option", Value: "1st"},
{Name: "Third option", Description: "This is third option", Value: "3rd"},
{Name: "Fourth option", Description: "This is fourth option", Value: "4th"},
}},
}
updatedVersion := coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, prepareEchoResponses(updatedTemplateParameters), template.ID)
coderdtest.AwaitTemplateVersionJob(t, client, updatedVersion.ID)
err = client.UpdateActiveTemplateVersion(context.Background(), template.ID, codersdk.UpdateActiveTemplateVersion{
ID: updatedVersion.ID,
})
require.NoError(t, err)
// Update the workspace
inv, root = clitest.New(t, "update", "my-workspace")
clitest.SetupConfig(t, client, root)
doneChan := make(chan struct{})
pty := ptytest.New(t).Attach(inv)
go func() {
defer close(doneChan)
err := inv.Run()
assert.NoError(t, err)
}()
matches := []string{
stringParameterName, "Third option",
"Planning workspace...", "",
}
for i := 0; i < len(matches); i += 2 {
match := matches[i]
value := matches[i+1]
pty.ExpectMatch(match)
if value != "" {
pty.WriteLine(value)
}
}
<-doneChan
})