diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 5e4fcb001c..3423b696fc 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -5605,6 +5605,82 @@ const docTemplate = `{ } } }, + "/templateversions/{templateversion}/presets": { + "get": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Templates" + ], + "summary": "Get template version presets", + "operationId": "get-template-version-presets", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "Template version ID", + "name": "templateversion", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/codersdk.Preset" + } + } + } + } + } + }, + "/templateversions/{templateversion}/presets/parameters": { + "get": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Templates" + ], + "summary": "Get template version preset parameters", + "operationId": "get-template-version-preset-parameters", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "Template version ID", + "name": "templateversion", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/codersdk.PresetParameter" + } + } + } + } + } + }, "/templateversions/{templateversion}/resources": { "get": { "security": [ @@ -12967,6 +13043,31 @@ const docTemplate = `{ } } }, + "codersdk.Preset": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "codersdk.PresetParameter": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "presetID": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, "codersdk.PrometheusConfig": { "type": "object", "properties": { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 29658d0a5e..d5a836dcb3 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -4951,6 +4951,74 @@ } } }, + "/templateversions/{templateversion}/presets": { + "get": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": ["application/json"], + "tags": ["Templates"], + "summary": "Get template version presets", + "operationId": "get-template-version-presets", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "Template version ID", + "name": "templateversion", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/codersdk.Preset" + } + } + } + } + } + }, + "/templateversions/{templateversion}/presets/parameters": { + "get": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": ["application/json"], + "tags": ["Templates"], + "summary": "Get template version preset parameters", + "operationId": "get-template-version-preset-parameters", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "Template version ID", + "name": "templateversion", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/codersdk.PresetParameter" + } + } + } + } + } + }, "/templateversions/{templateversion}/resources": { "get": { "security": [ @@ -11700,6 +11768,31 @@ } } }, + "codersdk.Preset": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "codersdk.PresetParameter": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "presetID": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, "codersdk.PrometheusConfig": { "type": "object", "properties": { diff --git a/coderd/coderd.go b/coderd/coderd.go index 4603f78acc..3c5363181d 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -1057,6 +1057,10 @@ func New(options *Options) *API { r.Get("/rich-parameters", api.templateVersionRichParameters) r.Get("/external-auth", api.templateVersionExternalAuth) r.Get("/variables", api.templateVersionVariables) + r.Route("/presets", func(r chi.Router) { + r.Get("/", api.templateVersionPresets) + r.Get("/parameters", api.templateVersionPresetParameters) + }) r.Get("/resources", api.templateVersionResources) r.Get("/logs", api.templateVersionLogs) r.Route("/dry-run", func(r chi.Router) { diff --git a/coderd/presets.go b/coderd/presets.go new file mode 100644 index 0000000000..742d60f2cf --- /dev/null +++ b/coderd/presets.go @@ -0,0 +1,77 @@ +package coderd + +import ( + "net/http" + + "github.com/coder/coder/v2/coderd/httpapi" + "github.com/coder/coder/v2/coderd/httpmw" + "github.com/coder/coder/v2/codersdk" +) + +// @Summary Get template version presets +// @ID get-template-version-presets +// @Security CoderSessionToken +// @Produce json +// @Tags Templates +// @Param templateversion path string true "Template version ID" format(uuid) +// @Success 200 {array} codersdk.Preset +// @Router /templateversions/{templateversion}/presets [get] +func (api *API) templateVersionPresets(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() + templateVersion := httpmw.TemplateVersionParam(r) + + presets, err := api.Database.GetPresetsByTemplateVersionID(ctx, templateVersion.ID) + if err != nil { + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error fetching template version presets.", + Detail: err.Error(), + }) + return + } + + var res []codersdk.Preset + for _, preset := range presets { + res = append(res, codersdk.Preset{ + ID: preset.ID, + Name: preset.Name, + }) + } + + httpapi.Write(ctx, rw, http.StatusOK, res) +} + +// @Summary Get template version preset parameters +// @ID get-template-version-preset-parameters +// @Security CoderSessionToken +// @Produce json +// @Tags Templates +// @Param templateversion path string true "Template version ID" format(uuid) +// @Success 200 {array} codersdk.PresetParameter +// @Router /templateversions/{templateversion}/presets/parameters [get] +func (api *API) templateVersionPresetParameters(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() + templateVersion := httpmw.TemplateVersionParam(r) + + // TODO (sasswart): Test case: what if a user tries to read presets or preset parameters from a different org? + // TODO (sasswart): Do a prelim auth check here. + + presetParams, err := api.Database.GetPresetParametersByTemplateVersionID(ctx, templateVersion.ID) + if err != nil { + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error fetching template version presets.", + Detail: err.Error(), + }) + return + } + + var res []codersdk.PresetParameter + for _, presetParam := range presetParams { + res = append(res, codersdk.PresetParameter{ + PresetID: presetParam.TemplateVersionPresetID, + Name: presetParam.Name, + Value: presetParam.Value, + }) + } + + httpapi.Write(ctx, rw, http.StatusOK, res) +} diff --git a/coderd/presets_test.go b/coderd/presets_test.go new file mode 100644 index 0000000000..a14d8dd50d --- /dev/null +++ b/coderd/presets_test.go @@ -0,0 +1,56 @@ +package coderd_test + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/coder/coder/v2/coderd/coderdtest" + "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/database/dbauthz" + "github.com/coder/coder/v2/coderd/httpmw" + "github.com/coder/coder/v2/coderd/rbac" +) + +func TestTemplateVersionPresets(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + client, db := coderdtest.NewWithDatabase(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) + user := coderdtest.CreateFirstUser(t, client) + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) + + // nolint:gocritic // This is a test + provisionerCtx := dbauthz.AsProvisionerd(ctx) + + preset, err := db.InsertPreset(provisionerCtx, database.InsertPresetParams{ + Name: "My Preset", + TemplateVersionID: version.ID, + }) + require.NoError(t, err) + _, err = db.InsertPresetParameters(provisionerCtx, database.InsertPresetParametersParams{ + TemplateVersionPresetID: preset.ID, + Names: []string{"preset_param1", "preset_param2"}, + Values: []string{"A1B2C3", "D4E5F6"}, + }) + require.NoError(t, err) + + userSubject, _, err := httpmw.UserRBACSubject(ctx, db, user.UserID, rbac.ScopeAll) + require.NoError(t, err) + userCtx := dbauthz.As(ctx, userSubject) + + presets, err := client.TemplateVersionPresets(userCtx, version.ID) + require.NoError(t, err) + require.Equal(t, 1, len(presets)) + require.Equal(t, "My Preset", presets[0].Name) + + presetParams, err := client.TemplateVersionPresetParameters(userCtx, version.ID) + require.NoError(t, err) + require.Equal(t, 2, len(presetParams)) + require.Equal(t, "preset_param1", presetParams[0].Name) + require.Equal(t, "A1B2C3", presetParams[0].Value) + require.Equal(t, "preset_param2", presetParams[1].Name) + require.Equal(t, "D4E5F6", presetParams[1].Value) +} diff --git a/coderd/wsbuilder/wsbuilder.go b/coderd/wsbuilder/wsbuilder.go index 183fdf2d44..a31e5eff46 100644 --- a/coderd/wsbuilder/wsbuilder.go +++ b/coderd/wsbuilder/wsbuilder.go @@ -363,23 +363,20 @@ func (b *Builder) buildTx(authFunc func(action policy.Action, object rbac.Object var workspaceBuild database.WorkspaceBuild err = b.store.InTx(func(store database.Store) error { err = store.InsertWorkspaceBuild(b.ctx, database.InsertWorkspaceBuildParams{ - ID: workspaceBuildID, - CreatedAt: now, - UpdatedAt: now, - WorkspaceID: b.workspace.ID, - TemplateVersionID: templateVersionID, - BuildNumber: buildNum, - ProvisionerState: state, - InitiatorID: b.initiator, - Transition: b.trans, - JobID: provisionerJob.ID, - Reason: b.reason, - Deadline: time.Time{}, // set by provisioner upon completion - MaxDeadline: time.Time{}, // set by provisioner upon completion - TemplateVersionPresetID: uuid.NullUUID{ - UUID: uuid.Nil, - Valid: false, - }, + ID: workspaceBuildID, + CreatedAt: now, + UpdatedAt: now, + WorkspaceID: b.workspace.ID, + TemplateVersionID: templateVersionID, + BuildNumber: buildNum, + ProvisionerState: state, + InitiatorID: b.initiator, + Transition: b.trans, + JobID: provisionerJob.ID, + Reason: b.reason, + Deadline: time.Time{}, // set by provisioner upon completion + MaxDeadline: time.Time{}, // set by provisioner upon completion + TemplateVersionPresetID: uuid.NullUUID{}, // TODO (sasswart): add this in from the caller }) if err != nil { code := http.StatusInternalServerError diff --git a/codersdk/presets.go b/codersdk/presets.go new file mode 100644 index 0000000000..cd724a979a --- /dev/null +++ b/codersdk/presets.go @@ -0,0 +1,50 @@ +package codersdk + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/google/uuid" + "golang.org/x/xerrors" +) + +type Preset struct { + ID uuid.UUID + Name string +} + +type PresetParameter struct { + PresetID uuid.UUID + Name string + Value string +} + +// TemplateVersionPresets returns the presets associated with a template version. +func (c *Client) TemplateVersionPresets(ctx context.Context, templateVersionID uuid.UUID) ([]Preset, error) { + res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/templateversions/%s/presets", templateVersionID), nil) + if err != nil { + return nil, xerrors.Errorf("do request: %w", err) + } + defer res.Body.Close() + if res.StatusCode != http.StatusOK { + return nil, ReadBodyAsError(res) + } + var presets []Preset + return presets, json.NewDecoder(res.Body).Decode(&presets) +} + +// TemplateVersionPresetParameters returns the parameters associated with the given presets. +func (c *Client) TemplateVersionPresetParameters(ctx context.Context, templateVersionID uuid.UUID) ([]PresetParameter, error) { + res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/templateversions/%s/presets/parameters", templateVersionID), nil) + if err != nil { + return nil, xerrors.Errorf("do request: %w", err) + } + defer res.Body.Close() + if res.StatusCode != http.StatusOK { + return nil, ReadBodyAsError(res) + } + var parameters []PresetParameter + return parameters, json.NewDecoder(res.Body).Decode(¶meters) +} diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index ebccd362c9..d5d471ad46 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -4427,6 +4427,40 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith | `address` | [serpent.HostPort](#serpenthostport) | false | | | | `enable` | boolean | false | | | +## codersdk.Preset + +```json +{ + "id": "string", + "name": "string" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +|--------|--------|----------|--------------|-------------| +| `id` | string | false | | | +| `name` | string | false | | | + +## codersdk.PresetParameter + +```json +{ + "name": "string", + "presetID": "string", + "value": "string" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +|------------|--------|----------|--------------|-------------| +| `name` | string | false | | | +| `presetID` | string | false | | | +| `value` | string | false | | | + ## codersdk.PrometheusConfig ```json diff --git a/docs/reference/api/templates.md b/docs/reference/api/templates.md index 9a2a35f40f..57d35a6aab 100644 --- a/docs/reference/api/templates.md +++ b/docs/reference/api/templates.md @@ -2672,6 +2672,108 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/p To perform this operation, you must be authenticated. [Learn more](authentication.md). +## Get template version presets + +### Code samples + +```shell +# Example request using curl +curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/presets \ + -H 'Accept: application/json' \ + -H 'Coder-Session-Token: API_KEY' +``` + +`GET /templateversions/{templateversion}/presets` + +### Parameters + +| Name | In | Type | Required | Description | +|-------------------|------|--------------|----------|---------------------| +| `templateversion` | path | string(uuid) | true | Template version ID | + +### Example responses + +> 200 Response + +```json +[ + { + "id": "string", + "name": "string" + } +] +``` + +### Responses + +| Status | Meaning | Description | Schema | +|--------|---------------------------------------------------------|-------------|-------------------------------------------------------| +| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | array of [codersdk.Preset](schemas.md#codersdkpreset) | + +