mirror of
https://github.com/coder/coder.git
synced 2025-07-08 11:39:50 +00:00
feat: create a workspace from any template version (#9471)
This commit is contained in:
11
coderd/apidoc/docs.go
generated
11
coderd/apidoc/docs.go
generated
@ -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"
|
||||
},
|
||||
|
10
coderd/apidoc/swagger.json
generated
10
coderd/apidoc/swagger.json
generated
@ -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"
|
||||
},
|
||||
|
@ -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)
|
||||
|
@ -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})
|
||||
|
Reference in New Issue
Block a user