mirror of
https://github.com/coder/coder.git
synced 2025-07-08 11:39:50 +00:00
feat: Expose workspace build parameters via API (#5743)
This commit is contained in:
54
coderd/apidoc/docs.go
generated
54
coderd/apidoc/docs.go
generated
@ -4309,6 +4309,43 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/workspacebuilds/{workspacebuild}/parameters": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Builds"
|
||||
],
|
||||
"summary": "Get build parameters for workspace build",
|
||||
"operationId": "get-build-parameters-for-workspace-build",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Workspace build ID",
|
||||
"name": "workspacebuild",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.WorkspaceBuildParameter"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/workspacebuilds/{workspacebuild}/resources": {
|
||||
"get": {
|
||||
"security": [
|
||||
@ -5568,6 +5605,12 @@ const docTemplate = `{
|
||||
"$ref": "#/definitions/codersdk.CreateParameterRequest"
|
||||
}
|
||||
},
|
||||
"rich_parameter_values": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.WorkspaceBuildParameter"
|
||||
}
|
||||
},
|
||||
"state": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -7896,6 +7939,17 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.WorkspaceBuildParameter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.WorkspaceQuota": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
50
coderd/apidoc/swagger.json
generated
50
coderd/apidoc/swagger.json
generated
@ -3793,6 +3793,39 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/workspacebuilds/{workspacebuild}/parameters": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"produces": ["application/json"],
|
||||
"tags": ["Builds"],
|
||||
"summary": "Get build parameters for workspace build",
|
||||
"operationId": "get-build-parameters-for-workspace-build",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Workspace build ID",
|
||||
"name": "workspacebuild",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.WorkspaceBuildParameter"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/workspacebuilds/{workspacebuild}/resources": {
|
||||
"get": {
|
||||
"security": [
|
||||
@ -4930,6 +4963,12 @@
|
||||
"$ref": "#/definitions/codersdk.CreateParameterRequest"
|
||||
}
|
||||
},
|
||||
"rich_parameter_values": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.WorkspaceBuildParameter"
|
||||
}
|
||||
},
|
||||
"state": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -7108,6 +7147,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.WorkspaceBuildParameter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.WorkspaceQuota": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -582,6 +582,7 @@ func New(options *Options) *API {
|
||||
r.Get("/", api.workspaceBuild)
|
||||
r.Patch("/cancel", api.patchCancelWorkspaceBuild)
|
||||
r.Get("/logs", api.workspaceBuildLogs)
|
||||
r.Get("/parameters", api.workspaceBuildParameters)
|
||||
r.Get("/resources", api.workspaceBuildResources)
|
||||
r.Get("/state", api.workspaceBuildState)
|
||||
})
|
||||
|
@ -1372,7 +1372,7 @@ func (q *fakeQuerier) GetWorkspaceBuildParameters(_ context.Context, workspaceBu
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
params := make([]database.WorkspaceBuildParameter, 0)
|
||||
for _, param := range params {
|
||||
for _, param := range q.workspaceBuildParameters {
|
||||
if param.WorkspaceBuildID != workspaceBuildID {
|
||||
continue
|
||||
}
|
||||
|
@ -377,6 +377,11 @@ 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{
|
||||
@ -449,6 +454,24 @@ 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
|
||||
}
|
||||
parameters = make([]codersdk.WorkspaceBuildParameter, 0, len(buildParameters))
|
||||
for _, param := range buildParameters {
|
||||
parameters = append(parameters, codersdk.WorkspaceBuildParameter{
|
||||
Name: param.Name,
|
||||
Value: param.Value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var workspaceBuild database.WorkspaceBuild
|
||||
var provisionerJob database.ProvisionerJob
|
||||
// This must happen in a transaction to ensure history can be inserted, and
|
||||
@ -532,6 +555,21 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) {
|
||||
return xerrors.Errorf("insert workspace build: %w", err)
|
||||
}
|
||||
|
||||
names := make([]string, 0, len(parameters))
|
||||
values := make([]string, 0, len(parameters))
|
||||
for _, param := range parameters {
|
||||
names = append(names, param.Name)
|
||||
values = append(values, param.Value)
|
||||
}
|
||||
err = db.InsertWorkspaceBuildParameters(ctx, database.InsertWorkspaceBuildParametersParams{
|
||||
WorkspaceBuildID: workspaceBuildID,
|
||||
Name: names,
|
||||
Value: values,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("insert workspace build parameter: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}, nil)
|
||||
if err != nil {
|
||||
@ -716,6 +754,42 @@ func (api *API) workspaceBuildResources(rw http.ResponseWriter, r *http.Request)
|
||||
api.provisionerJobResources(rw, r, job)
|
||||
}
|
||||
|
||||
// @Summary Get build parameters for workspace build
|
||||
// @ID get-build-parameters-for-workspace-build
|
||||
// @Security CoderSessionToken
|
||||
// @Produce json
|
||||
// @Tags Builds
|
||||
// @Param workspacebuild path string true "Workspace build ID"
|
||||
// @Success 200 {array} codersdk.WorkspaceBuildParameter
|
||||
// @Router /workspacebuilds/{workspacebuild}/parameters [get]
|
||||
func (api *API) workspaceBuildParameters(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
workspaceBuild := httpmw.WorkspaceBuildParam(r)
|
||||
workspace, err := api.Database.GetWorkspaceByID(ctx, workspaceBuild.WorkspaceID)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "No workspace exists for this job.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if !api.Authorize(r, rbac.ActionRead, workspace) {
|
||||
httpapi.ResourceNotFound(rw)
|
||||
return
|
||||
}
|
||||
|
||||
parameters, err := api.Database.GetWorkspaceBuildParameters(ctx, workspaceBuild.ID)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching workspace build parameters.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
apiParameters := convertWorkspaceBuildParameters(parameters)
|
||||
httpapi.Write(ctx, rw, http.StatusOK, apiParameters)
|
||||
}
|
||||
|
||||
// @Summary Get workspace build logs
|
||||
// @ID get-workspace-build-logs
|
||||
// @Security CoderSessionToken
|
||||
@ -1084,3 +1158,16 @@ func convertWorkspaceStatus(jobStatus codersdk.ProvisionerJobStatus, transition
|
||||
// return error status since we should never get here
|
||||
return codersdk.WorkspaceStatusFailed
|
||||
}
|
||||
|
||||
func convertWorkspaceBuildParameters(parameters []database.WorkspaceBuildParameter) []codersdk.WorkspaceBuildParameter {
|
||||
var apiParameters = make([]codersdk.WorkspaceBuildParameter, 0, len(parameters))
|
||||
|
||||
for _, p := range parameters {
|
||||
apiParameter := codersdk.WorkspaceBuildParameter{
|
||||
Name: p.Name,
|
||||
Value: p.Value,
|
||||
}
|
||||
apiParameters = append(apiParameters, apiParameter)
|
||||
}
|
||||
return apiParameters
|
||||
}
|
||||
|
@ -636,52 +636,106 @@ func TestWorkspaceBuildStatus(t *testing.T) {
|
||||
func TestWorkspaceBuildWithRichParameters(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, &echo.Responses{
|
||||
const (
|
||||
firstParameterName = "first_parameter"
|
||||
firstParameterDescription = "This is first parameter"
|
||||
firstParameterValue = "1"
|
||||
|
||||
secondParameterName = "second_parameter"
|
||||
secondParameterDescription = "This is second parameter"
|
||||
secondParameterValue = "2"
|
||||
)
|
||||
|
||||
initialBuildParameters := []codersdk.WorkspaceBuildParameter{
|
||||
{Name: firstParameterName, Value: firstParameterValue},
|
||||
{Name: secondParameterName, Value: secondParameterValue},
|
||||
}
|
||||
|
||||
echoResponses := &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Provision_Response{
|
||||
{
|
||||
Type: &proto.Provision_Response_Complete{
|
||||
Complete: &proto.Provision_Complete{
|
||||
Parameters: []*proto.RichParameter{
|
||||
{
|
||||
Name: "first_parameter",
|
||||
Description: "This is first parameter",
|
||||
},
|
||||
{
|
||||
Name: "second_parameter",
|
||||
Description: "This is second parameter",
|
||||
},
|
||||
{Name: firstParameterName, Description: firstParameterDescription},
|
||||
{Name: secondParameterName, Description: secondParameterDescription},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
})
|
||||
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
richParameters, err := client.TemplateVersionRichParameters(ctx, version.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, richParameters, 2)
|
||||
require.Equal(t, richParameters[0].Name, "first_parameter")
|
||||
require.Equal(t, richParameters[1].Name, "second_parameter")
|
||||
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
|
||||
cwr.RichParameterValues = []codersdk.WorkspaceBuildParameter{
|
||||
{
|
||||
Name: "first_parameter",
|
||||
Value: "1",
|
||||
},
|
||||
{
|
||||
Name: "second_parameter",
|
||||
Value: "2",
|
||||
ProvisionApply: []*proto.Provision_Response{{
|
||||
Type: &proto.Provision_Response_Complete{
|
||||
Complete: &proto.Provision_Complete{},
|
||||
},
|
||||
}},
|
||||
}
|
||||
|
||||
t.Run("UpdateParameterValues", 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()
|
||||
|
||||
const updatedParameterValue = "3"
|
||||
nextBuildParameters := []codersdk.WorkspaceBuildParameter{
|
||||
{Name: firstParameterName, Value: firstParameterValue},
|
||||
{Name: secondParameterName, Value: updatedParameterValue},
|
||||
}
|
||||
nextWorkspaceBuild, err := client.CreateWorkspaceBuild(ctx, workspace.ID, codersdk.CreateWorkspaceBuildRequest{
|
||||
Transition: codersdk.WorkspaceTransitionStart,
|
||||
RichParameterValues: nextBuildParameters,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
coderdtest.AwaitWorkspaceBuildJob(t, client, nextWorkspaceBuild.ID)
|
||||
|
||||
workspaceBuildParameters, err := client.WorkspaceBuildParameters(ctx, nextWorkspaceBuild.ID)
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t, nextBuildParameters, workspaceBuildParameters)
|
||||
})
|
||||
t.Run("UsePreviousParameterValues", 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
|
||||
})
|
||||
|
||||
firstWorkspaceBuild := coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
||||
require.Equal(t, codersdk.WorkspaceStatusRunning, firstWorkspaceBuild.Status)
|
||||
|
||||
// Start new workspace build
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
nextWorkspaceBuild, err := client.CreateWorkspaceBuild(ctx, workspace.ID, codersdk.CreateWorkspaceBuildRequest{
|
||||
Transition: codersdk.WorkspaceTransitionStart,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, firstWorkspaceBuild, nextWorkspaceBuild)
|
||||
coderdtest.AwaitWorkspaceBuildJob(t, client, nextWorkspaceBuild.ID)
|
||||
|
||||
workspaceBuildParameters, err := client.WorkspaceBuildParameters(ctx, nextWorkspaceBuild.ID)
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t, initialBuildParameters, workspaceBuildParameters)
|
||||
})
|
||||
_, err = client.WorkspaceBuild(ctx, workspace.LatestBuild.ID)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
@ -502,6 +502,21 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
|
||||
if err != nil {
|
||||
return xerrors.Errorf("insert workspace build: %w", err)
|
||||
}
|
||||
|
||||
names := make([]string, 0, len(createWorkspace.RichParameterValues))
|
||||
values := make([]string, 0, len(createWorkspace.RichParameterValues))
|
||||
for _, param := range createWorkspace.RichParameterValues {
|
||||
names = append(names, param.Name)
|
||||
values = append(values, param.Value)
|
||||
}
|
||||
err = db.InsertWorkspaceBuildParameters(ctx, database.InsertWorkspaceBuildParametersParams{
|
||||
WorkspaceBuildID: workspaceBuildID,
|
||||
Name: names,
|
||||
Value: values,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("insert workspace build parameters: %w", err)
|
||||
}
|
||||
return nil
|
||||
}, nil)
|
||||
if err != nil {
|
||||
|
@ -1781,3 +1781,66 @@ func TestWorkspaceResource(t *testing.T) {
|
||||
}}, metadata)
|
||||
})
|
||||
}
|
||||
|
||||
func TestWorkspaceWithRichParameters(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
firstParameterName = "first_parameter"
|
||||
firstParameterDescription = "This is first parameter"
|
||||
firstParameterValue = "1"
|
||||
|
||||
secondParameterName = "second_parameter"
|
||||
secondParameterDescription = "This is second parameter"
|
||||
secondParameterValue = "2"
|
||||
)
|
||||
|
||||
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
version := coderdtest.CreateTemplateVersion(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},
|
||||
{Name: secondParameterName, Description: secondParameterDescription},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
ProvisionApply: []*proto.Provision_Response{{
|
||||
Type: &proto.Provision_Response_Complete{
|
||||
Complete: &proto.Provision_Complete{},
|
||||
},
|
||||
}},
|
||||
})
|
||||
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
templateRichParameters, err := client.TemplateVersionRichParameters(ctx, version.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, templateRichParameters, 2)
|
||||
require.Equal(t, templateRichParameters[0].Name, firstParameterName)
|
||||
require.Equal(t, templateRichParameters[1].Name, secondParameterName)
|
||||
|
||||
expectedBuildParameters := []codersdk.WorkspaceBuildParameter{
|
||||
{Name: firstParameterName, Value: firstParameterValue},
|
||||
{Name: secondParameterName, Value: secondParameterValue},
|
||||
}
|
||||
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
|
||||
cwr.RichParameterValues = expectedBuildParameters
|
||||
})
|
||||
|
||||
workspaceBuild := coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
||||
require.Equal(t, codersdk.WorkspaceStatusRunning, workspaceBuild.Status)
|
||||
|
||||
workspaceBuildParameters, err := client.WorkspaceBuildParameters(ctx, workspaceBuild.ID)
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t, expectedBuildParameters, workspaceBuildParameters)
|
||||
}
|
||||
|
Reference in New Issue
Block a user