feat(coderd): add support for presets to the coder API

This commit is contained in:
Sas Swart
2025-02-11 14:23:14 +00:00
parent 34b46f9205
commit dcf47ab30d
10 changed files with 544 additions and 17 deletions

101
coderd/apidoc/docs.go generated
View File

@ -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": { "/templateversions/{templateversion}/resources": {
"get": { "get": {
"security": [ "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": { "codersdk.PrometheusConfig": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@ -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": { "/templateversions/{templateversion}/resources": {
"get": { "get": {
"security": [ "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": { "codersdk.PrometheusConfig": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@ -1057,6 +1057,10 @@ func New(options *Options) *API {
r.Get("/rich-parameters", api.templateVersionRichParameters) r.Get("/rich-parameters", api.templateVersionRichParameters)
r.Get("/external-auth", api.templateVersionExternalAuth) r.Get("/external-auth", api.templateVersionExternalAuth)
r.Get("/variables", api.templateVersionVariables) 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("/resources", api.templateVersionResources)
r.Get("/logs", api.templateVersionLogs) r.Get("/logs", api.templateVersionLogs)
r.Route("/dry-run", func(r chi.Router) { r.Route("/dry-run", func(r chi.Router) {

77
coderd/presets.go Normal file
View File

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

56
coderd/presets_test.go Normal file
View File

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

View File

@ -376,10 +376,7 @@ func (b *Builder) buildTx(authFunc func(action policy.Action, object rbac.Object
Reason: b.reason, Reason: b.reason,
Deadline: time.Time{}, // set by provisioner upon completion Deadline: time.Time{}, // set by provisioner upon completion
MaxDeadline: time.Time{}, // set by provisioner upon completion MaxDeadline: time.Time{}, // set by provisioner upon completion
TemplateVersionPresetID: uuid.NullUUID{ TemplateVersionPresetID: uuid.NullUUID{}, // TODO (sasswart): add this in from the caller
UUID: uuid.Nil,
Valid: false,
},
}) })
if err != nil { if err != nil {
code := http.StatusInternalServerError code := http.StatusInternalServerError

50
codersdk/presets.go Normal file
View File

@ -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(&parameters)
}

View File

@ -4427,6 +4427,40 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith
| `address` | [serpent.HostPort](#serpenthostport) | false | | | | `address` | [serpent.HostPort](#serpenthostport) | false | | |
| `enable` | boolean | 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 ## codersdk.PrometheusConfig
```json ```json

View File

@ -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). 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) |
<h3 id="get-template-version-presets-responseschema">Response Schema</h3>
Status Code **200**
| Name | Type | Required | Restrictions | Description |
|----------------|--------|----------|--------------|-------------|
| `[array item]` | array | false | | |
| `» id` | string | false | | |
| `» name` | string | false | | |
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Get template version preset parameters
### Code samples
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/presets/parameters \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`GET /templateversions/{templateversion}/presets/parameters`
### Parameters
| Name | In | Type | Required | Description |
|-------------------|------|--------------|----------|---------------------|
| `templateversion` | path | string(uuid) | true | Template version ID |
### Example responses
> 200 Response
```json
[
{
"name": "string",
"presetID": "string",
"value": "string"
}
]
```
### Responses
| Status | Meaning | Description | Schema |
|--------|---------------------------------------------------------|-------------|-------------------------------------------------------------------------|
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | array of [codersdk.PresetParameter](schemas.md#codersdkpresetparameter) |
<h3 id="get-template-version-preset-parameters-responseschema">Response Schema</h3>
Status Code **200**
| Name | Type | Required | Restrictions | Description |
|----------------|--------|----------|--------------|-------------|
| `[array item]` | array | false | | |
| `» name` | string | false | | |
| `» presetID` | string | false | | |
| `» value` | string | false | | |
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Get resources by template version ## Get resources by template version
### Code samples ### Code samples

View File

@ -1550,6 +1550,19 @@ export interface PprofConfig {
readonly address: string; readonly address: string;
} }
// From codersdk/presets.go
export interface Preset {
readonly ID: string;
readonly Name: string;
}
// From codersdk/presets.go
export interface PresetParameter {
readonly PresetID: string;
readonly Name: string;
readonly Value: string;
}
// From codersdk/deployment.go // From codersdk/deployment.go
export interface PrometheusConfig { export interface PrometheusConfig {
readonly enable: boolean; readonly enable: boolean;