mirror of
https://github.com/coder/coder.git
synced 2025-07-13 21:36:50 +00:00
feat: accept immutable parameters when used first time (#7000)
* Backend fixes * CLI: adjust update flow
This commit is contained in:
@ -6,6 +6,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
@ -213,6 +214,7 @@ type prepWorkspaceBuildArgs struct {
|
|||||||
NewWorkspaceName string
|
NewWorkspaceName string
|
||||||
|
|
||||||
UpdateWorkspace bool
|
UpdateWorkspace bool
|
||||||
|
WorkspaceID uuid.UUID
|
||||||
}
|
}
|
||||||
|
|
||||||
type buildParameters struct {
|
type buildParameters struct {
|
||||||
@ -340,9 +342,18 @@ PromptRichParamLoop:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if args.UpdateWorkspace && !templateVersionParameter.Mutable {
|
if args.UpdateWorkspace && !templateVersionParameter.Mutable {
|
||||||
|
// Check if the immutable parameter was used in the previous build. If so, then it isn't a fresh one
|
||||||
|
// and the user should be warned.
|
||||||
|
exists, err := workspaceBuildParameterExists(ctx, client, args.WorkspaceID, templateVersionParameter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if exists {
|
||||||
_, _ = fmt.Fprintln(inv.Stdout, cliui.Styles.Warn.Render(fmt.Sprintf(`Parameter %q is not mutable, so can't be customized after workspace creation.`, templateVersionParameter.Name)))
|
_, _ = fmt.Fprintln(inv.Stdout, cliui.Styles.Warn.Render(fmt.Sprintf(`Parameter %q is not mutable, so can't be customized after workspace creation.`, templateVersionParameter.Name)))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
parameterValue, err := getWorkspaceBuildParameterValueFromMapOrInput(inv, parameterMapFromFile, templateVersionParameter)
|
parameterValue, err := getWorkspaceBuildParameterValueFromMapOrInput(inv, parameterMapFromFile, templateVersionParameter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -414,3 +425,17 @@ PromptRichParamLoop:
|
|||||||
richParameters: richParameters,
|
richParameters: richParameters,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func workspaceBuildParameterExists(ctx context.Context, client *codersdk.Client, workspaceID uuid.UUID, templateVersionParameter codersdk.TemplateVersionParameter) (bool, error) {
|
||||||
|
lastBuildParameters, err := client.WorkspaceBuildParameters(ctx, workspaceID)
|
||||||
|
if err != nil {
|
||||||
|
return false, xerrors.Errorf("can't fetch last workspace build parameters: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range lastBuildParameters {
|
||||||
|
if p.Name == templateVersionParameter.Name {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
@ -59,7 +59,9 @@ func (r *RootCmd) update() *clibase.Cmd {
|
|||||||
ExistingRichParams: existingRichParams,
|
ExistingRichParams: existingRichParams,
|
||||||
RichParameterFile: richParameterFile,
|
RichParameterFile: richParameterFile,
|
||||||
NewWorkspaceName: workspace.Name,
|
NewWorkspaceName: workspace.Name,
|
||||||
|
|
||||||
UpdateWorkspace: true,
|
UpdateWorkspace: true,
|
||||||
|
WorkspaceID: workspace.LatestBuild.ID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
|
@ -530,11 +530,13 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) {
|
|||||||
// Check if parameter value is in request
|
// Check if parameter value is in request
|
||||||
if buildParameter, found := findWorkspaceBuildParameter(createBuild.RichParameterValues, templateVersionParameter.Name); found {
|
if buildParameter, found := findWorkspaceBuildParameter(createBuild.RichParameterValues, templateVersionParameter.Name); found {
|
||||||
if !templateVersionParameter.Mutable {
|
if !templateVersionParameter.Mutable {
|
||||||
|
if _, found := findWorkspaceBuildParameter(apiLastBuildParameters, templateVersionParameter.Name); found {
|
||||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||||
Message: fmt.Sprintf("Parameter %q is not mutable, so it can't be updated after creating a workspace.", templateVersionParameter.Name),
|
Message: fmt.Sprintf("Parameter %q is not mutable, so it can't be updated after creating a workspace.", templateVersionParameter.Name),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
parameters = append(parameters, *buildParameter)
|
parameters = append(parameters, *buildParameter)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -781,6 +781,190 @@ func TestWorkspaceBuildWithRichParameters(t *testing.T) {
|
|||||||
})
|
})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("NewImmutableRequiredParameterAdded", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
||||||
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, echoResponses)
|
||||||
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
||||||
|
|
||||||
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||||
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
|
||||||
|
cwr.RichParameterValues = initialBuildParameters
|
||||||
|
})
|
||||||
|
|
||||||
|
workspaceBuild := coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
||||||
|
require.Equal(t, codersdk.WorkspaceStatusRunning, workspaceBuild.Status)
|
||||||
|
|
||||||
|
// Push new template revision
|
||||||
|
const newImmutableParameterName = "new_immutable_parameter"
|
||||||
|
const newImmutableParameterDescription = "This is also an immutable parameter"
|
||||||
|
version2 := coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
|
Parse: echo.ParseComplete,
|
||||||
|
ProvisionPlan: []*proto.Provision_Response{
|
||||||
|
{
|
||||||
|
Type: &proto.Provision_Response_Complete{
|
||||||
|
Complete: &proto.Provision_Complete{
|
||||||
|
Parameters: []*proto.RichParameter{
|
||||||
|
{Name: firstParameterName, Description: firstParameterDescription, Mutable: true},
|
||||||
|
{Name: secondParameterName, Description: secondParameterDescription, Mutable: true},
|
||||||
|
{Name: immutableParameterName, Description: immutableParameterDescription, Mutable: false},
|
||||||
|
{Name: newImmutableParameterName, Description: newImmutableParameterDescription, Mutable: false, Required: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ProvisionApply: []*proto.Provision_Response{{
|
||||||
|
Type: &proto.Provision_Response_Complete{
|
||||||
|
Complete: &proto.Provision_Complete{},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}, template.ID)
|
||||||
|
coderdtest.AwaitTemplateVersionJob(t, client, version2.ID)
|
||||||
|
err := client.UpdateActiveTemplateVersion(context.Background(), template.ID, codersdk.UpdateActiveTemplateVersion{
|
||||||
|
ID: version2.ID,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Update build parameters
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
nextBuildParameters := []codersdk.WorkspaceBuildParameter{
|
||||||
|
{Name: newImmutableParameterName, Value: "good"},
|
||||||
|
}
|
||||||
|
_, err = client.CreateWorkspaceBuild(ctx, workspace.ID, codersdk.CreateWorkspaceBuildRequest{
|
||||||
|
TemplateVersionID: version2.ID,
|
||||||
|
Transition: codersdk.WorkspaceTransitionStart,
|
||||||
|
RichParameterValues: nextBuildParameters,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NewImmutableOptionalParameterAdded", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
||||||
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, echoResponses)
|
||||||
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
||||||
|
|
||||||
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||||
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
|
||||||
|
cwr.RichParameterValues = initialBuildParameters
|
||||||
|
})
|
||||||
|
|
||||||
|
workspaceBuild := coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
||||||
|
require.Equal(t, codersdk.WorkspaceStatusRunning, workspaceBuild.Status)
|
||||||
|
|
||||||
|
// Push new template revision
|
||||||
|
const newImmutableParameterName = "new_immutable_parameter"
|
||||||
|
const newImmutableParameterDescription = "This is also an immutable parameter"
|
||||||
|
version2 := coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
|
Parse: echo.ParseComplete,
|
||||||
|
ProvisionPlan: []*proto.Provision_Response{
|
||||||
|
{
|
||||||
|
Type: &proto.Provision_Response_Complete{
|
||||||
|
Complete: &proto.Provision_Complete{
|
||||||
|
Parameters: []*proto.RichParameter{
|
||||||
|
{Name: firstParameterName, Description: firstParameterDescription, Mutable: true},
|
||||||
|
{Name: secondParameterName, Description: secondParameterDescription, Mutable: true},
|
||||||
|
{Name: immutableParameterName, Description: immutableParameterDescription, Mutable: false},
|
||||||
|
{Name: newImmutableParameterName, Description: newImmutableParameterDescription, Mutable: false, DefaultValue: "12345"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ProvisionApply: []*proto.Provision_Response{{
|
||||||
|
Type: &proto.Provision_Response_Complete{
|
||||||
|
Complete: &proto.Provision_Complete{},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}, template.ID)
|
||||||
|
coderdtest.AwaitTemplateVersionJob(t, client, version2.ID)
|
||||||
|
err := client.UpdateActiveTemplateVersion(context.Background(), template.ID, codersdk.UpdateActiveTemplateVersion{
|
||||||
|
ID: version2.ID,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Update build parameters
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
nextBuildParameters := []codersdk.WorkspaceBuildParameter{
|
||||||
|
{Name: newImmutableParameterName, Value: "good"},
|
||||||
|
}
|
||||||
|
_, err = client.CreateWorkspaceBuild(ctx, workspace.ID, codersdk.CreateWorkspaceBuildRequest{
|
||||||
|
TemplateVersionID: version2.ID,
|
||||||
|
Transition: codersdk.WorkspaceTransitionStart,
|
||||||
|
RichParameterValues: nextBuildParameters,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NewImmutableOptionalParameterUsesDefault", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
||||||
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, echoResponses)
|
||||||
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
||||||
|
|
||||||
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||||
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
|
||||||
|
cwr.RichParameterValues = initialBuildParameters
|
||||||
|
})
|
||||||
|
|
||||||
|
workspaceBuild := coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
||||||
|
require.Equal(t, codersdk.WorkspaceStatusRunning, workspaceBuild.Status)
|
||||||
|
|
||||||
|
// Push new template revision
|
||||||
|
const newImmutableParameterName = "new_immutable_parameter"
|
||||||
|
const newImmutableParameterDescription = "This is also an immutable parameter"
|
||||||
|
version2 := coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
|
Parse: echo.ParseComplete,
|
||||||
|
ProvisionPlan: []*proto.Provision_Response{
|
||||||
|
{
|
||||||
|
Type: &proto.Provision_Response_Complete{
|
||||||
|
Complete: &proto.Provision_Complete{
|
||||||
|
Parameters: []*proto.RichParameter{
|
||||||
|
{Name: firstParameterName, Description: firstParameterDescription, Mutable: true},
|
||||||
|
{Name: secondParameterName, Description: secondParameterDescription, Mutable: true},
|
||||||
|
{Name: immutableParameterName, Description: immutableParameterDescription, Mutable: false},
|
||||||
|
{Name: newImmutableParameterName, Description: newImmutableParameterDescription, Mutable: false, DefaultValue: "12345"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ProvisionApply: []*proto.Provision_Response{{
|
||||||
|
Type: &proto.Provision_Response_Complete{
|
||||||
|
Complete: &proto.Provision_Complete{},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}, template.ID)
|
||||||
|
coderdtest.AwaitTemplateVersionJob(t, client, version2.ID)
|
||||||
|
err := client.UpdateActiveTemplateVersion(context.Background(), template.ID, codersdk.UpdateActiveTemplateVersion{
|
||||||
|
ID: version2.ID,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Update build parameters
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var nextBuildParameters []codersdk.WorkspaceBuildParameter
|
||||||
|
_, err = client.CreateWorkspaceBuild(ctx, workspace.ID, codersdk.CreateWorkspaceBuildRequest{
|
||||||
|
TemplateVersionID: version2.ID,
|
||||||
|
Transition: codersdk.WorkspaceTransitionStart,
|
||||||
|
RichParameterValues: nextBuildParameters,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWorkspaceBuildValidateRichParameters(t *testing.T) {
|
func TestWorkspaceBuildValidateRichParameters(t *testing.T) {
|
||||||
|
Reference in New Issue
Block a user