feat: create a workspace from any template version (#9471)

This commit is contained in:
Kayla Washburn
2023-08-31 15:07:58 -06:00
committed by GitHub
parent 796a9754a9
commit eded7a4b88
8 changed files with 121 additions and 17 deletions

11
coderd/apidoc/docs.go generated
View File

@ -7715,8 +7715,7 @@ const docTemplate = `{
"codersdk.CreateWorkspaceRequest": {
"type": "object",
"required": [
"name",
"template_id"
"name"
],
"properties": {
"autostart_schedule": {
@ -7726,13 +7725,19 @@ const docTemplate = `{
"type": "string"
},
"rich_parameter_values": {
"description": "ParameterValues allows for additional parameters to be provided\nduring the initial provision.",
"description": "RichParameterValues allows for additional parameters to be provided\nduring the initial provision.",
"type": "array",
"items": {
"$ref": "#/definitions/codersdk.WorkspaceBuildParameter"
}
},
"template_id": {
"description": "TemplateID specifies which template should be used for creating the workspace.",
"type": "string",
"format": "uuid"
},
"template_version_id": {
"description": "TemplateVersionID can be used to specify a specific version of a template for creating the workspace.",
"type": "string",
"format": "uuid"
},

View File

@ -6872,7 +6872,7 @@
},
"codersdk.CreateWorkspaceRequest": {
"type": "object",
"required": ["name", "template_id"],
"required": ["name"],
"properties": {
"autostart_schedule": {
"type": "string"
@ -6881,13 +6881,19 @@
"type": "string"
},
"rich_parameter_values": {
"description": "ParameterValues allows for additional parameters to be provided\nduring the initial provision.",
"description": "RichParameterValues allows for additional parameters to be provided\nduring the initial provision.",
"type": "array",
"items": {
"$ref": "#/definitions/codersdk.WorkspaceBuildParameter"
}
},
"template_id": {
"description": "TemplateID specifies which template should be used for creating the workspace.",
"type": "string",
"format": "uuid"
},
"template_version_id": {
"description": "TemplateVersionID can be used to specify a specific version of a template for creating the workspace.",
"type": "string",
"format": "uuid"
},

View File

@ -333,10 +333,35 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
return
}
template, err := api.Database.GetTemplateByID(ctx, createWorkspace.TemplateID)
// If we were given a `TemplateVersionID`, we need to determine the `TemplateID` from it.
templateID := createWorkspace.TemplateID
if templateID == uuid.Nil {
templateVersion, err := api.Database.GetTemplateVersionByID(ctx, createWorkspace.TemplateVersionID)
if errors.Is(err, sql.ErrNoRows) {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: fmt.Sprintf("Template version %q doesn't exist.", templateID.String()),
Validations: []codersdk.ValidationError{{
Field: "template_version_id",
Detail: "template not found",
}},
})
return
}
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching template version.",
Detail: err.Error(),
})
return
}
templateID = templateVersion.TemplateID.UUID
}
template, err := api.Database.GetTemplateByID(ctx, templateID)
if errors.Is(err, sql.ErrNoRows) {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: fmt.Sprintf("Template %q doesn't exist.", createWorkspace.TemplateID.String()),
Message: fmt.Sprintf("Template %q doesn't exist.", templateID.String()),
Validations: []codersdk.ValidationError{{
Field: "template_id",
Detail: "template not found",
@ -454,6 +479,10 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
Initiator(apiKey.UserID).
ActiveVersion().
RichParameterValues(createWorkspace.RichParameterValues)
if createWorkspace.TemplateVersionID != uuid.Nil {
builder = builder.VersionID(createWorkspace.TemplateVersionID)
}
workspaceBuild, provisionerJob, err = builder.Build(
ctx, db, func(action rbac.Action, object rbac.Objecter) bool {
return api.Authorize(r, action, object)

View File

@ -491,6 +491,62 @@ func TestPostWorkspacesByOrganization(t *testing.T) {
}, testutil.WaitMedium, testutil.IntervalFast)
})
t.Run("CreateFromVersion", func(t *testing.T) {
t.Parallel()
auditor := audit.NewMock()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true, Auditor: auditor})
user := coderdtest.CreateFirstUser(t, client)
versionDefault := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, versionDefault.ID)
versionTest := coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, nil, template.ID)
coderdtest.AwaitTemplateVersionJob(t, client, versionDefault.ID)
coderdtest.AwaitTemplateVersionJob(t, client, versionTest.ID)
defaultWorkspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, uuid.Nil,
func(c *codersdk.CreateWorkspaceRequest) { c.TemplateVersionID = versionDefault.ID },
)
testWorkspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, uuid.Nil,
func(c *codersdk.CreateWorkspaceRequest) { c.TemplateVersionID = versionTest.ID },
)
defaultWorkspaceBuild := coderdtest.AwaitWorkspaceBuildJob(t, client, defaultWorkspace.LatestBuild.ID)
testWorkspaceBuild := coderdtest.AwaitWorkspaceBuildJob(t, client, testWorkspace.LatestBuild.ID)
require.Equal(t, testWorkspaceBuild.TemplateVersionID, versionTest.ID)
require.Equal(t, defaultWorkspaceBuild.TemplateVersionID, versionDefault.ID)
require.Eventually(t, func() bool {
if len(auditor.AuditLogs()) < 6 {
return false
}
return auditor.AuditLogs()[4].Action == database.AuditActionCreate
}, testutil.WaitMedium, testutil.IntervalFast)
})
t.Run("InvalidCombinationOfTemplateAndTemplateVersion", func(t *testing.T) {
t.Parallel()
auditor := audit.NewMock()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true, Auditor: auditor})
user := coderdtest.CreateFirstUser(t, client)
versionTest := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
versionDefault := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, versionDefault.ID)
coderdtest.AwaitTemplateVersionJob(t, client, versionTest.ID)
coderdtest.AwaitTemplateVersionJob(t, client, versionDefault.ID)
name, se := cryptorand.String(8)
require.NoError(t, se)
req := codersdk.CreateWorkspaceRequest{
// Deny setting both of these ID fields, even if they might correlate.
// Allowing both to be set would just create extra work for everyone involved.
TemplateID: template.ID,
TemplateVersionID: versionTest.ID,
Name: name,
AutostartSchedule: ptr.Ref("CRON_TZ=US/Central 30 9 * * 1-5"),
TTLMillis: ptr.Ref((8 * time.Hour).Milliseconds()),
}
_, err := client.CreateWorkspace(context.Background(), user.OrganizationID, codersdk.Me, req)
require.Error(t, err)
})
t.Run("CreateWithDeletedTemplate", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})