feat(coder): Add PATCH /templateversions/:templateversion endpoint (#6698)

This commit is contained in:
Bruno Quaresma 2023-03-23 13:26:50 -03:00 committed by GitHub
parent ed9a3b9251
commit 8857971552
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 391 additions and 19 deletions

53
coderd/apidoc/docs.go generated
View File

@ -2255,6 +2255,51 @@ const docTemplate = `{
}
}
}
},
"patch": {
"security": [
{
"CoderSessionToken": []
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Templates"
],
"summary": "Patch template version by ID",
"operationId": "patch-template-version-by-id",
"parameters": [
{
"type": "string",
"format": "uuid",
"description": "Template version ID",
"name": "templateversion",
"in": "path",
"required": true
},
{
"description": "Patch template version request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/codersdk.PatchTemplateVersionRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.TemplateVersion"
}
}
}
}
},
"/templateversions/{templateversion}/cancel": {
@ -7400,6 +7445,14 @@ const docTemplate = `{
"ParameterSourceSchemeData"
]
},
"codersdk.PatchTemplateVersionRequest": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
},
"codersdk.PprofConfig": {
"type": "object",
"properties": {

View File

@ -1971,6 +1971,45 @@
}
}
}
},
"patch": {
"security": [
{
"CoderSessionToken": []
}
],
"consumes": ["application/json"],
"produces": ["application/json"],
"tags": ["Templates"],
"summary": "Patch template version by ID",
"operationId": "patch-template-version-by-id",
"parameters": [
{
"type": "string",
"format": "uuid",
"description": "Template version ID",
"name": "templateversion",
"in": "path",
"required": true
},
{
"description": "Patch template version request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/codersdk.PatchTemplateVersionRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.TemplateVersion"
}
}
}
}
},
"/templateversions/{templateversion}/cancel": {
@ -6620,6 +6659,14 @@
"ParameterSourceSchemeData"
]
},
"codersdk.PatchTemplateVersionRequest": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
},
"codersdk.PprofConfig": {
"type": "object",
"properties": {

View File

@ -500,6 +500,7 @@ func New(options *Options) *API {
httpmw.ExtractTemplateVersionParam(options.Database),
)
r.Get("/", api.templateVersion)
r.Patch("/", api.patchTemplateVersion)
r.Patch("/cancel", api.patchCancelTemplateVersion)
r.Get("/schema", api.templateVersionSchema)
r.Get("/parameters", api.templateVersionParameters)

View File

@ -850,13 +850,13 @@ func (q *querier) UpdateTemplateScheduleByID(ctx context.Context, arg database.U
return updateWithReturn(q.log, q.auth, fetch, q.db.UpdateTemplateScheduleByID)(ctx, arg)
}
func (q *querier) UpdateTemplateVersionByID(ctx context.Context, arg database.UpdateTemplateVersionByIDParams) error {
func (q *querier) UpdateTemplateVersionByID(ctx context.Context, arg database.UpdateTemplateVersionByIDParams) (database.TemplateVersion, error) {
template, err := q.db.GetTemplateByID(ctx, arg.TemplateID.UUID)
if err != nil {
return err
return database.TemplateVersion{}, err
}
if err := q.authorizeContext(ctx, rbac.ActionUpdate, template); err != nil {
return err
return database.TemplateVersion{}, err
}
return q.db.UpdateTemplateVersionByID(ctx, arg)
}

View File

@ -721,7 +721,9 @@ func (s *MethodTestSuite) TestTemplate() {
check.Args(database.UpdateTemplateVersionByIDParams{
ID: tv.ID,
TemplateID: uuid.NullUUID{UUID: t1.ID, Valid: true},
}).Asserts(t1, rbac.ActionUpdate).Returns()
Name: tv.Name,
UpdatedAt: tv.UpdatedAt,
}).Asserts(t1, rbac.ActionUpdate).Returns(tv)
}))
s.Run("UpdateTemplateVersionDescriptionByJobID", s.Subtest(func(db database.Store, check *expects) {
jobID := uuid.New()

View File

@ -3413,9 +3413,9 @@ func (q *fakeQuerier) UpdateTemplateACLByID(_ context.Context, arg database.Upda
return database.Template{}, sql.ErrNoRows
}
func (q *fakeQuerier) UpdateTemplateVersionByID(_ context.Context, arg database.UpdateTemplateVersionByIDParams) error {
func (q *fakeQuerier) UpdateTemplateVersionByID(_ context.Context, arg database.UpdateTemplateVersionByIDParams) (database.TemplateVersion, error) {
if err := validateDatabaseType(arg); err != nil {
return err
return database.TemplateVersion{}, err
}
q.mutex.Lock()
@ -3427,10 +3427,11 @@ func (q *fakeQuerier) UpdateTemplateVersionByID(_ context.Context, arg database.
}
templateVersion.TemplateID = arg.TemplateID
templateVersion.UpdatedAt = arg.UpdatedAt
templateVersion.Name = arg.Name
q.templateVersions[index] = templateVersion
return nil
return templateVersion, nil
}
return sql.ErrNoRows
return database.TemplateVersion{}, sql.ErrNoRows
}
func (q *fakeQuerier) UpdateTemplateVersionDescriptionByJobID(_ context.Context, arg database.UpdateTemplateVersionDescriptionByJobIDParams) error {

View File

@ -210,7 +210,7 @@ type sqlcQuerier interface {
UpdateTemplateDeletedByID(ctx context.Context, arg UpdateTemplateDeletedByIDParams) error
UpdateTemplateMetaByID(ctx context.Context, arg UpdateTemplateMetaByIDParams) (Template, error)
UpdateTemplateScheduleByID(ctx context.Context, arg UpdateTemplateScheduleByIDParams) (Template, error)
UpdateTemplateVersionByID(ctx context.Context, arg UpdateTemplateVersionByIDParams) error
UpdateTemplateVersionByID(ctx context.Context, arg UpdateTemplateVersionByIDParams) (TemplateVersion, error)
UpdateTemplateVersionDescriptionByJobID(ctx context.Context, arg UpdateTemplateVersionDescriptionByJobIDParams) error
UpdateTemplateVersionGitAuthProvidersByJobID(ctx context.Context, arg UpdateTemplateVersionGitAuthProvidersByJobIDParams) error
UpdateUserDeletedByID(ctx context.Context, arg UpdateUserDeletedByIDParams) error

View File

@ -4095,25 +4095,45 @@ func (q *sqlQuerier) InsertTemplateVersion(ctx context.Context, arg InsertTempla
return i, err
}
const updateTemplateVersionByID = `-- name: UpdateTemplateVersionByID :exec
const updateTemplateVersionByID = `-- name: UpdateTemplateVersionByID :one
UPDATE
template_versions
SET
template_id = $2,
updated_at = $3
updated_at = $3,
name = $4
WHERE
id = $1
id = $1 RETURNING id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, git_auth_providers
`
type UpdateTemplateVersionByIDParams struct {
ID uuid.UUID `db:"id" json:"id"`
TemplateID uuid.NullUUID `db:"template_id" json:"template_id"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
Name string `db:"name" json:"name"`
}
func (q *sqlQuerier) UpdateTemplateVersionByID(ctx context.Context, arg UpdateTemplateVersionByIDParams) error {
_, err := q.db.ExecContext(ctx, updateTemplateVersionByID, arg.ID, arg.TemplateID, arg.UpdatedAt)
return err
func (q *sqlQuerier) UpdateTemplateVersionByID(ctx context.Context, arg UpdateTemplateVersionByIDParams) (TemplateVersion, error) {
row := q.db.QueryRowContext(ctx, updateTemplateVersionByID,
arg.ID,
arg.TemplateID,
arg.UpdatedAt,
arg.Name,
)
var i TemplateVersion
err := row.Scan(
&i.ID,
&i.TemplateID,
&i.OrganizationID,
&i.CreatedAt,
&i.UpdatedAt,
&i.Name,
&i.Readme,
&i.JobID,
&i.CreatedBy,
pq.Array(&i.GitAuthProviders),
)
return i, err
}
const updateTemplateVersionDescriptionByJobID = `-- name: UpdateTemplateVersionDescriptionByJobID :exec

View File

@ -84,14 +84,15 @@ INSERT INTO
VALUES
($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING *;
-- name: UpdateTemplateVersionByID :exec
-- name: UpdateTemplateVersionByID :one
UPDATE
template_versions
SET
template_id = $2,
updated_at = $3
updated_at = $3,
name = $4
WHERE
id = $1;
id = $1 RETURNING *;
-- name: UpdateTemplateVersionDescriptionByJobID :exec
UPDATE

View File

@ -269,13 +269,14 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
templateAudit.New = dbTemplate
err = tx.UpdateTemplateVersionByID(ctx, database.UpdateTemplateVersionByIDParams{
_, err = tx.UpdateTemplateVersionByID(ctx, database.UpdateTemplateVersionByIDParams{
ID: templateVersion.ID,
TemplateID: uuid.NullUUID{
UUID: dbTemplate.ID,
Valid: true,
},
UpdatedAt: database.Now(),
Name: templateVersion.Name,
})
if err != nil {
return xerrors.Errorf("insert template version: %s", err)

View File

@ -63,6 +63,66 @@ func (api *API) templateVersion(rw http.ResponseWriter, r *http.Request) {
httpapi.Write(ctx, rw, http.StatusOK, convertTemplateVersion(templateVersion, convertProvisionerJob(job), user))
}
// @Summary Patch template version by ID
// @ID patch-template-version-by-id
// @Security CoderSessionToken
// @Accept json
// @Produce json
// @Tags Templates
// @Param templateversion path string true "Template version ID" format(uuid)
// @Param request body codersdk.PatchTemplateVersionRequest true "Patch template version request"
// @Success 200 {object} codersdk.TemplateVersion
// @Router /templateversions/{templateversion} [patch]
func (api *API) patchTemplateVersion(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
templateVersion := httpmw.TemplateVersionParam(r)
var params codersdk.PatchTemplateVersionRequest
if !httpapi.Read(ctx, rw, r, &params) {
return
}
updateParams := database.UpdateTemplateVersionByIDParams{
ID: templateVersion.ID,
TemplateID: templateVersion.TemplateID,
UpdatedAt: database.Now(),
Name: templateVersion.Name,
}
if params.Name != "" {
updateParams.Name = params.Name
}
// It is not allowed to "patch" the template ID, and reassign it.
updatedTemplateVersion, err := api.Database.UpdateTemplateVersionByID(ctx, updateParams)
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Error on patching template version.",
Detail: err.Error(),
})
return
}
job, err := api.Database.GetProvisionerJobByID(ctx, templateVersion.JobID)
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching provisioner job.",
Detail: err.Error(),
})
return
}
user, err := api.Database.GetUserByID(ctx, templateVersion.CreatedBy)
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error on fetching user.",
Detail: err.Error(),
})
return
}
httpapi.Write(ctx, rw, http.StatusOK, convertTemplateVersion(updatedTemplateVersion, convertProvisionerJob(job), user))
}
// @Summary Cancel template version by ID
// @ID cancel-template-version-by-id
// @Security CoderSessionToken

View File

@ -1334,3 +1334,68 @@ func TestTemplateVersionVariables(t *testing.T) {
require.Equal(t, "*redacted*", actualVariables[0].Value)
})
}
func TestTemplateVersionPatch(t *testing.T) {
t.Parallel()
t.Run("Update the name", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
const newName = "new_name"
updatedVersion, err := client.UpdateTemplateVersion(ctx, version.ID, codersdk.PatchTemplateVersionRequest{
Name: newName,
})
require.NoError(t, err)
assert.Equal(t, newName, updatedVersion.Name)
assert.NotEqual(t, updatedVersion.Name, version.Name)
})
t.Run("Use the same name if a new name is not passed", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
updatedVersion, err := client.UpdateTemplateVersion(ctx, version.ID, codersdk.PatchTemplateVersionRequest{})
require.NoError(t, err)
assert.Equal(t, version.Name, updatedVersion.Name)
})
t.Run("Use the same name for two different templates", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
version1 := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
coderdtest.CreateTemplate(t, client, user.OrganizationID, version1.ID)
version2 := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
coderdtest.CreateTemplate(t, client, user.OrganizationID, version2.ID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
const commonTemplateVersionName = "common-template-version-name"
updatedVersion1, err := client.UpdateTemplateVersion(ctx, version1.ID, codersdk.PatchTemplateVersionRequest{
Name: commonTemplateVersionName,
})
require.NoError(t, err)
updatedVersion2, err := client.UpdateTemplateVersion(ctx, version2.ID, codersdk.PatchTemplateVersionRequest{
Name: commonTemplateVersionName,
})
require.NoError(t, err)
assert.NotEqual(t, updatedVersion1.ID, updatedVersion2.ID)
assert.Equal(t, updatedVersion1.Name, updatedVersion2.Name)
})
}

View File

@ -76,6 +76,10 @@ type TemplateVersionVariable struct {
Sensitive bool `json:"sensitive"`
}
type PatchTemplateVersionRequest struct {
Name string `json:"name"`
}
// TemplateVersion returns a template version by ID.
func (c *Client) TemplateVersion(ctx context.Context, id uuid.UUID) (TemplateVersion, error) {
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/templateversions/%s", id), nil)
@ -291,3 +295,16 @@ func (c *Client) PreviousTemplateVersion(ctx context.Context, organization uuid.
var version TemplateVersion
return version, json.NewDecoder(res.Body).Decode(&version)
}
func (c *Client) UpdateTemplateVersion(ctx context.Context, versionID uuid.UUID, req PatchTemplateVersionRequest) (TemplateVersion, error) {
res, err := c.Request(ctx, http.MethodPatch, fmt.Sprintf("/api/v2/templateversions/%s", versionID), req)
if err != nil {
return TemplateVersion{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return TemplateVersion{}, ReadBodyAsError(res)
}
var version TemplateVersion
return version, json.NewDecoder(res.Body).Decode(&version)
}

View File

@ -3009,6 +3009,20 @@ Parameter represents a set value for the scope.
| `none` |
| `data` |
## codersdk.PatchTemplateVersionRequest
```json
{
"name": "string"
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
| ------ | ------ | -------- | ------------ | ----------- |
| `name` | string | false | | |
## codersdk.PprofConfig
```json

View File

@ -1250,6 +1250,91 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion} \
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Patch template version by ID
### Code samples
```shell
# Example request using curl
curl -X PATCH http://coder-server:8080/api/v2/templateversions/{templateversion} \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`PATCH /templateversions/{templateversion}`
> Body parameter
```json
{
"name": "string"
}
```
### Parameters
| Name | In | Type | Required | Description |
| ----------------- | ---- | -------------------------------------------------------------------------------------- | -------- | ------------------------------ |
| `templateversion` | path | string(uuid) | true | Template version ID |
| `body` | body | [codersdk.PatchTemplateVersionRequest](schemas.md#codersdkpatchtemplateversionrequest) | true | Patch template version request |
### Example responses
> 200 Response
```json
{
"created_at": "2019-08-24T14:15:22Z",
"created_by": {
"avatar_url": "http://example.com",
"created_at": "2019-08-24T14:15:22Z",
"email": "user@example.com",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"last_seen_at": "2019-08-24T14:15:22Z",
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
"roles": [
{
"display_name": "string",
"name": "string"
}
],
"status": "active",
"username": "string"
},
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"job": {
"canceled_at": "2019-08-24T14:15:22Z",
"completed_at": "2019-08-24T14:15:22Z",
"created_at": "2019-08-24T14:15:22Z",
"error": "string",
"error_code": "MISSING_TEMPLATE_PARAMETER",
"file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"started_at": "2019-08-24T14:15:22Z",
"status": "pending",
"tags": {
"property1": "string",
"property2": "string"
},
"worker_id": "ae5fa6f7-c55b-40c1-b40a-b36ac467652b"
},
"name": "string",
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
"readme": "string",
"template_id": "c6d67e98-83ea-49f0-8812-e4abae2b68bc",
"updated_at": "2019-08-24T14:15:22Z"
}
```
### Responses
| Status | Meaning | Description | Schema |
| ------ | ------------------------------------------------------- | ----------- | -------------------------------------------------------------- |
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.TemplateVersion](schemas.md#codersdktemplateversion) |
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Cancel template version by ID
### Code samples

View File

@ -581,6 +581,11 @@ export interface PatchGroupRequest {
readonly quota_allowance?: number
}
// From codersdk/templateversions.go
export interface PatchTemplateVersionRequest {
readonly name: string
}
// From codersdk/deployment.go
export interface PprofConfig {
readonly enable: boolean