feat: Add CLI support for workspace build parameters (#5768)

* WIP

* WIP

* CLI: handle workspace build parameters

* fix: golintci

* Fix: dry run

* fix

* CLI: is mutable

* coderd: mutable

* fix: golanci

* fix: richParameterFile

* CLI: create unit tests

* CLI: update test

* Fix

* fix: order

* fix
This commit is contained in:
Marcin Tojek
2023-01-23 15:01:22 +01:00
committed by GitHub
parent 6a245ab1cc
commit bbb208e29c
23 changed files with 822 additions and 257 deletions

6
coderd/apidoc/docs.go generated
View File

@ -5519,6 +5519,12 @@ const docTemplate = `{
"$ref": "#/definitions/codersdk.CreateParameterRequest"
}
},
"rich_parameter_values": {
"type": "array",
"items": {
"$ref": "#/definitions/codersdk.WorkspaceBuildParameter"
}
},
"workspace_name": {
"type": "string"
}

View File

@ -4889,6 +4889,12 @@
"$ref": "#/definitions/codersdk.CreateParameterRequest"
}
},
"rich_parameter_values": {
"type": "array",
"items": {
"$ref": "#/definitions/codersdk.WorkspaceBuildParameter"
}
},
"workspace_name": {
"type": "string"
}

View File

@ -235,7 +235,8 @@ func (server *Server) AcquireJob(ctx context.Context, _ *proto.Empty) (*proto.Ac
protoJob.Type = &proto.AcquiredJob_TemplateDryRun_{
TemplateDryRun: &proto.AcquiredJob_TemplateDryRun{
ParameterValues: protoParameters,
ParameterValues: protoParameters,
RichParameterValues: convertRichParameterValues(input.RichParameterValues),
Metadata: &sdkproto.Provision_Metadata{
CoderUrl: server.AccessURL.String(),
WorkspaceName: input.WorkspaceName,
@ -1175,9 +1176,10 @@ type WorkspaceProvisionJob struct {
// TemplateVersionDryRunJob is the payload for the "template_version_dry_run" job type.
type TemplateVersionDryRunJob struct {
TemplateVersionID uuid.UUID `json:"template_version_id"`
WorkspaceName string `json:"workspace_name"`
ParameterValues []database.ParameterValue `json:"parameter_values"`
TemplateVersionID uuid.UUID `json:"template_version_id"`
WorkspaceName string `json:"workspace_name"`
ParameterValues []database.ParameterValue `json:"parameter_values"`
RichParameterValues []database.WorkspaceBuildParameter `json:"rich_parameter_values"`
}
// ProvisionerJobLogsNotifyMessage is the payload published on

View File

@ -362,12 +362,22 @@ func (api *API) postTemplateVersionDryRun(rw http.ResponseWriter, r *http.Reques
}
}
richParameterValues := make([]database.WorkspaceBuildParameter, len(req.RichParameterValues))
for i, v := range req.RichParameterValues {
richParameterValues[i] = database.WorkspaceBuildParameter{
WorkspaceBuildID: uuid.Nil,
Name: v.Name,
Value: v.Value,
}
}
// Marshal template version dry-run job with the parameters from the
// request.
input, err := json.Marshal(provisionerdserver.TemplateVersionDryRunJob{
TemplateVersionID: templateVersion.ID,
WorkspaceName: req.WorkspaceName,
ParameterValues: parameterValues,
TemplateVersionID: templateVersion.ID,
WorkspaceName: req.WorkspaceName,
ParameterValues: parameterValues,
RichParameterValues: richParameterValues,
})
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{

View File

@ -377,11 +377,6 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) {
state = createBuild.ProvisionerState
}
var parameters []codersdk.WorkspaceBuildParameter
if createBuild.RichParameterValues != nil {
parameters = createBuild.RichParameterValues
}
if createBuild.Orphan {
if createBuild.Transition != codersdk.WorkspaceTransitionDelete {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
@ -454,21 +449,42 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) {
state = priorHistory.ProvisionerState
}
if parameters == nil {
buildParameters, err := api.Database.GetWorkspaceBuildParameters(ctx, priorHistory.ID)
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching prior workspace build parameters.",
Detail: err.Error(),
})
return
templateVersionParameters, err := api.Database.GetTemplateVersionParameters(ctx, createBuild.TemplateVersionID)
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching template version parameters.",
Detail: err.Error(),
})
return
}
lastBuildParameters, err := api.Database.GetWorkspaceBuildParameters(ctx, priorHistory.ID)
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching prior workspace build parameters.",
Detail: err.Error(),
})
return
}
apiLastBuildParameters := convertWorkspaceBuildParameters(lastBuildParameters)
var parameters []codersdk.WorkspaceBuildParameter
for _, templateVersionParameter := range templateVersionParameters {
// Check if parameter value is in request
if buildParameter, found := findWorkspaceBuildParameter(createBuild.RichParameterValues, templateVersionParameter.Name); found {
if !templateVersionParameter.Mutable {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: fmt.Sprintf("Parameter %q is mutable, so it can't be updated after creating workspace.", templateVersionParameter.Name),
})
return
}
parameters = append(parameters, *buildParameter)
continue
}
parameters = make([]codersdk.WorkspaceBuildParameter, 0, len(buildParameters))
for _, param := range buildParameters {
parameters = append(parameters, codersdk.WorkspaceBuildParameter{
Name: param.Name,
Value: param.Value,
})
// Check if parameter is defined in previous build
if buildParameter, found := findWorkspaceBuildParameter(apiLastBuildParameters, templateVersionParameter.Name); found {
parameters = append(parameters, *buildParameter)
}
}
@ -567,7 +583,7 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) {
Value: values,
})
if err != nil {
return xerrors.Errorf("insert workspace build parameter: %w", err)
return xerrors.Errorf("insert workspace build parameters: %w", err)
}
return nil
@ -1171,3 +1187,12 @@ func convertWorkspaceBuildParameters(parameters []database.WorkspaceBuildParamet
}
return apiParameters
}
func findWorkspaceBuildParameter(params []codersdk.WorkspaceBuildParameter, parameterName string) (*codersdk.WorkspaceBuildParameter, bool) {
for _, p := range params {
if p.Name == parameterName {
return &p, true
}
}
return nil, false
}

View File

@ -644,11 +644,16 @@ func TestWorkspaceBuildWithRichParameters(t *testing.T) {
secondParameterName = "second_parameter"
secondParameterDescription = "This is second parameter"
secondParameterValue = "2"
immutableParameterName = "immutable_parameter"
immutableParameterDescription = "This is immutable parameter"
immutableParameterValue = "3"
)
initialBuildParameters := []codersdk.WorkspaceBuildParameter{
{Name: firstParameterName, Value: firstParameterValue},
{Name: secondParameterName, Value: secondParameterValue},
{Name: immutableParameterName, Value: immutableParameterValue},
}
echoResponses := &echo.Responses{
@ -658,8 +663,9 @@ func TestWorkspaceBuildWithRichParameters(t *testing.T) {
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
Parameters: []*proto.RichParameter{
{Name: firstParameterName, Description: firstParameterDescription},
{Name: secondParameterName, Description: secondParameterDescription},
{Name: firstParameterName, Description: firstParameterDescription, Mutable: true},
{Name: secondParameterName, Description: secondParameterDescription, Mutable: true},
{Name: immutableParameterName, Description: immutableParameterDescription, Mutable: false},
},
},
},
@ -705,7 +711,12 @@ func TestWorkspaceBuildWithRichParameters(t *testing.T) {
workspaceBuildParameters, err := client.WorkspaceBuildParameters(ctx, nextWorkspaceBuild.ID)
require.NoError(t, err)
require.ElementsMatch(t, nextBuildParameters, workspaceBuildParameters)
expected := append(nextBuildParameters, codersdk.WorkspaceBuildParameter{
Name: immutableParameterName,
Value: immutableParameterValue,
})
require.ElementsMatch(t, expected, workspaceBuildParameters)
})
t.Run("UsePreviousParameterValues", func(t *testing.T) {
t.Parallel()
@ -738,4 +749,34 @@ func TestWorkspaceBuildWithRichParameters(t *testing.T) {
require.NoError(t, err)
require.ElementsMatch(t, initialBuildParameters, workspaceBuildParameters)
})
t.Run("DoNotModifyImmutables", 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)
// Update build parameters
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
nextBuildParameters := []codersdk.WorkspaceBuildParameter{
{Name: immutableParameterName, Value: "BAD"},
}
_, err := client.CreateWorkspaceBuild(ctx, workspace.ID, codersdk.CreateWorkspaceBuildRequest{
Transition: codersdk.WorkspaceTransitionStart,
RichParameterValues: nextBuildParameters,
})
require.Error(t, err)
})
}