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})

View File

@ -124,12 +124,16 @@ type CreateTemplateRequest struct {
}
// CreateWorkspaceRequest provides options for creating a new workspace.
// Either TemplateID or TemplateVersionID must be specified. They cannot both be present.
type CreateWorkspaceRequest struct {
TemplateID uuid.UUID `json:"template_id" validate:"required" format:"uuid"`
// TemplateID specifies which template should be used for creating the workspace.
TemplateID uuid.UUID `json:"template_id,omitempty" validate:"required_without=TemplateVersionID,excluded_with=TemplateVersionID" format:"uuid"`
// TemplateVersionID can be used to specify a specific version of a template for creating the workspace.
TemplateVersionID uuid.UUID `json:"template_version_id,omitempty" validate:"required_without=TemplateID,excluded_with=TemplateID" format:"uuid"`
Name string `json:"name" validate:"workspace_name,required"`
AutostartSchedule *string `json:"autostart_schedule"`
TTLMillis *int64 `json:"ttl_ms,omitempty"`
// ParameterValues allows for additional parameters to be provided
// RichParameterValues allows for additional parameters to be provided
// during the initial provision.
RichParameterValues []WorkspaceBuildParameter `json:"rich_parameter_values,omitempty"`
}

16
docs/api/schemas.md generated
View File

@ -1757,19 +1757,21 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
}
],
"template_id": "c6d67e98-83ea-49f0-8812-e4abae2b68bc",
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
"ttl_ms": 0
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
| ----------------------- | ----------------------------------------------------------------------------- | -------- | ------------ | --------------------------------------------------------------------------------------------------- |
| `autostart_schedule` | string | false | | |
| `name` | string | true | | |
| `rich_parameter_values` | array of [codersdk.WorkspaceBuildParameter](#codersdkworkspacebuildparameter) | false | | Rich parameter values allows for additional parameters to be provided during the initial provision. |
| `template_id` | string | true | | |
| `ttl_ms` | integer | false | | |
| Name | Type | Required | Restrictions | Description |
| ----------------------- | ----------------------------------------------------------------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------------------- |
| `autostart_schedule` | string | false | | |
| `name` | string | true | | |
| `rich_parameter_values` | array of [codersdk.WorkspaceBuildParameter](#codersdkworkspacebuildparameter) | false | | Rich parameter values allows for additional parameters to be provided during the initial provision. |
| `template_id` | string | false | | Template ID specifies which template should be used for creating the workspace. |
| `template_version_id` | string | false | | Template version ID can be used to specify a specific version of a template for creating the workspace. |
| `ttl_ms` | integer | false | | |
## codersdk.DAUEntry

View File

@ -27,6 +27,7 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/member
}
],
"template_id": "c6d67e98-83ea-49f0-8812-e4abae2b68bc",
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
"ttl_ms": 0
}
```

View File

@ -272,7 +272,8 @@ export interface CreateWorkspaceProxyRequest {
// From codersdk/organizations.go
export interface CreateWorkspaceRequest {
readonly template_id: string
readonly template_id?: string
readonly template_version_id?: string
readonly name: string
readonly autostart_schedule?: string
readonly ttl_ms?: number