diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index fb5236fe61..6bb45c4fb3 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -1330,6 +1330,104 @@ const docTemplate = `{ } } }, + "/organizations/{organization}/templates/{templatename}/versions/{templateversionname}": { + "get": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Templates" + ], + "summary": "Get template version by organization, template, and name", + "operationId": "get-template-version-by-organization-template-and-name", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "Organization ID", + "name": "organization", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Template name", + "name": "templatename", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Template version name", + "name": "templateversionname", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.TemplateVersion" + } + } + } + } + }, + "/organizations/{organization}/templates/{templatename}/versions/{templateversionname}/previous": { + "get": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Templates" + ], + "summary": "Get previous template version by organization, template, and name", + "operationId": "get-previous-template-version-by-organization-template-and-name", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "Organization ID", + "name": "organization", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Template name", + "name": "templatename", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Template version name", + "name": "templateversionname", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.TemplateVersion" + } + } + } + } + }, "/organizations/{organization}/templateversions": { "post": { "security": [ @@ -1377,90 +1475,6 @@ const docTemplate = `{ } } }, - "/organizations/{organization}/templateversions/{templateversionname}": { - "get": { - "security": [ - { - "CoderSessionToken": [] - } - ], - "produces": [ - "application/json" - ], - "tags": [ - "Templates" - ], - "summary": "Get template version by organization and name", - "operationId": "get-template-version-by-organization-and-name", - "parameters": [ - { - "type": "string", - "format": "uuid", - "description": "Organization ID", - "name": "organization", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "Template version name", - "name": "templateversionname", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/codersdk.TemplateVersion" - } - } - } - } - }, - "/organizations/{organization}/templateversions/{templateversionname}/previous": { - "get": { - "security": [ - { - "CoderSessionToken": [] - } - ], - "produces": [ - "application/json" - ], - "tags": [ - "Templates" - ], - "summary": "Get previous template version by organization and name", - "operationId": "get-previous-template-version-by-organization-and-name", - "parameters": [ - { - "type": "string", - "format": "uuid", - "description": "Organization ID", - "name": "organization", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "Template version name", - "name": "templateversionname", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/codersdk.TemplateVersion" - } - } - } - } - }, "/parameters/{scope}/{id}": { "get": { "security": [ diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 0d6c224dbc..91c1d15ff2 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -1160,6 +1160,96 @@ } } }, + "/organizations/{organization}/templates/{templatename}/versions/{templateversionname}": { + "get": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": ["application/json"], + "tags": ["Templates"], + "summary": "Get template version by organization, template, and name", + "operationId": "get-template-version-by-organization-template-and-name", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "Organization ID", + "name": "organization", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Template name", + "name": "templatename", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Template version name", + "name": "templateversionname", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.TemplateVersion" + } + } + } + } + }, + "/organizations/{organization}/templates/{templatename}/versions/{templateversionname}/previous": { + "get": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": ["application/json"], + "tags": ["Templates"], + "summary": "Get previous template version by organization, template, and name", + "operationId": "get-previous-template-version-by-organization-template-and-name", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "Organization ID", + "name": "organization", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Template name", + "name": "templatename", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Template version name", + "name": "templateversionname", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.TemplateVersion" + } + } + } + } + }, "/organizations/{organization}/templateversions": { "post": { "security": [ @@ -1201,82 +1291,6 @@ } } }, - "/organizations/{organization}/templateversions/{templateversionname}": { - "get": { - "security": [ - { - "CoderSessionToken": [] - } - ], - "produces": ["application/json"], - "tags": ["Templates"], - "summary": "Get template version by organization and name", - "operationId": "get-template-version-by-organization-and-name", - "parameters": [ - { - "type": "string", - "format": "uuid", - "description": "Organization ID", - "name": "organization", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "Template version name", - "name": "templateversionname", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/codersdk.TemplateVersion" - } - } - } - } - }, - "/organizations/{organization}/templateversions/{templateversionname}/previous": { - "get": { - "security": [ - { - "CoderSessionToken": [] - } - ], - "produces": ["application/json"], - "tags": ["Templates"], - "summary": "Get previous template version by organization and name", - "operationId": "get-previous-template-version-by-organization-and-name", - "parameters": [ - { - "type": "string", - "format": "uuid", - "description": "Organization ID", - "name": "organization", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "Template version name", - "name": "templateversionname", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/codersdk.TemplateVersion" - } - } - } - } - }, "/parameters/{scope}/{id}": { "get": { "security": [ diff --git a/coderd/coderd.go b/coderd/coderd.go index 62ffe040ad..2b16094d2d 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -398,16 +398,18 @@ func New(options *Options) *API { httpmw.ExtractOrganizationParam(options.Database), ) r.Get("/", api.organization) - r.Route("/templateversions", func(r chi.Router) { - r.Post("/", api.postTemplateVersionsByOrganization) - r.Get("/{templateversionname}", api.templateVersionByOrganizationAndName) - r.Get("/{templateversionname}/previous", api.previousTemplateVersionByOrganizationAndName) - }) + r.Post("/templateversions", api.postTemplateVersionsByOrganization) r.Route("/templates", func(r chi.Router) { r.Post("/", api.postTemplateByOrganization) r.Get("/", api.templatesByOrganization) - r.Get("/{templatename}", api.templateByOrganizationAndName) r.Get("/examples", api.templateExamples) + r.Route("/{templatename}", func(r chi.Router) { + r.Get("/", api.templateByOrganizationAndName) + r.Route("/versions/{templateversionname}", func(r chi.Router) { + r.Get("/", api.templateVersionByOrganizationTemplateAndName) + r.Get("/previous", api.previousTemplateVersionByOrganizationTemplateAndName) + }) + }) }) r.Route("/members", func(r chi.Router) { r.Get("/roles", api.assignableOrgRoles) diff --git a/coderd/coderdtest/authorize.go b/coderd/coderdtest/authorize.go index 14019815d2..0a95e8bb52 100644 --- a/coderd/coderdtest/authorize.go +++ b/coderd/coderdtest/authorize.go @@ -239,6 +239,14 @@ func AGPLRoutes(a *AuthTester) (map[string]string, map[string]RouteCheck) { AssertAction: rbac.ActionRead, AssertObject: templateObj, }, + "GET:/api/v2/organizations/{organization}/templates/{templatename}/versions/{templateversionname}": { + AssertAction: rbac.ActionRead, + AssertObject: templateObj, + }, + "GET:/api/v2/organizations/{organization}/templates/{templatename}/versions/{templateversionname}/previous": { + AssertAction: rbac.ActionRead, + AssertObject: templateObj, + }, "POST:/api/v2/organizations/{organization}/members/{user}/workspaces": { AssertAction: rbac.ActionCreate, // No ID when creating @@ -252,12 +260,10 @@ func AGPLRoutes(a *AuthTester) (map[string]string, map[string]RouteCheck) { "GET:/api/v2/applications/auth-redirect": {AssertAction: rbac.ActionCreate, AssertObject: rbac.ResourceAPIKey}, // These endpoints need payloads to get to the auth part. Payloads will be required - "PUT:/api/v2/users/{user}/roles": {StatusCode: http.StatusBadRequest, NoAuthorize: true}, - "PUT:/api/v2/organizations/{organization}/members/{user}/roles": {NoAuthorize: true}, - "POST:/api/v2/workspaces/{workspace}/builds": {StatusCode: http.StatusBadRequest, NoAuthorize: true}, - "POST:/api/v2/organizations/{organization}/templateversions": {StatusCode: http.StatusBadRequest, NoAuthorize: true}, - "GET:/api/v2/organizations/{organization}/templateversions/{templateversionname}": {StatusCode: http.StatusBadRequest, NoAuthorize: true}, - "GET:/api/v2/organizations/{organization}/templateversions/{templateversionname}/previous": {StatusCode: http.StatusBadRequest, NoAuthorize: true}, + "PUT:/api/v2/users/{user}/roles": {StatusCode: http.StatusBadRequest, NoAuthorize: true}, + "PUT:/api/v2/organizations/{organization}/members/{user}/roles": {NoAuthorize: true}, + "POST:/api/v2/workspaces/{workspace}/builds": {StatusCode: http.StatusBadRequest, NoAuthorize: true}, + "POST:/api/v2/organizations/{organization}/templateversions": {StatusCode: http.StatusBadRequest, NoAuthorize: true}, // Endpoints that use the SQLQuery filter. "GET:/api/v2/workspaces/": { diff --git a/coderd/database/databasefake/databasefake.go b/coderd/database/databasefake/databasefake.go index 4c2fa4bbcd..f3d7b4a6f3 100644 --- a/coderd/database/databasefake/databasefake.go +++ b/coderd/database/databasefake/databasefake.go @@ -1797,26 +1797,6 @@ func (q *fakeQuerier) GetTemplateVersionParameters(_ context.Context, templateVe return parameters, nil } -func (q *fakeQuerier) GetTemplateVersionByOrganizationAndName(_ context.Context, arg database.GetTemplateVersionByOrganizationAndNameParams) (database.TemplateVersion, error) { - if err := validateDatabaseType(arg); err != nil { - return database.TemplateVersion{}, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, templateVersion := range q.templateVersions { - if templateVersion.OrganizationID != arg.OrganizationID { - continue - } - if !strings.EqualFold(templateVersion.Name, arg.Name) { - continue - } - return templateVersion, nil - } - return database.TemplateVersion{}, sql.ErrNoRows -} - func (q *fakeQuerier) GetTemplateVersionByID(_ context.Context, templateVersionID uuid.UUID) (database.TemplateVersion, error) { q.mutex.RLock() defer q.mutex.RUnlock() diff --git a/coderd/database/querier.go b/coderd/database/querier.go index 4795250699..38b5b34c6a 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -84,7 +84,6 @@ type sqlcQuerier interface { GetTemplateDAUs(ctx context.Context, templateID uuid.UUID) ([]GetTemplateDAUsRow, error) GetTemplateVersionByID(ctx context.Context, id uuid.UUID) (TemplateVersion, error) GetTemplateVersionByJobID(ctx context.Context, jobID uuid.UUID) (TemplateVersion, error) - GetTemplateVersionByOrganizationAndName(ctx context.Context, arg GetTemplateVersionByOrganizationAndNameParams) (TemplateVersion, error) GetTemplateVersionByTemplateIDAndName(ctx context.Context, arg GetTemplateVersionByTemplateIDAndNameParams) (TemplateVersion, error) GetTemplateVersionParameters(ctx context.Context, templateVersionID uuid.UUID) ([]TemplateVersionParameter, error) GetTemplateVersionsByIDs(ctx context.Context, ids []uuid.UUID) ([]TemplateVersion, error) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index d7ac44430a..7b2b77f753 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -3731,38 +3731,6 @@ func (q *sqlQuerier) GetTemplateVersionByJobID(ctx context.Context, jobID uuid.U return i, err } -const getTemplateVersionByOrganizationAndName = `-- name: GetTemplateVersionByOrganizationAndName :one -SELECT - id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by -FROM - template_versions -WHERE - organization_id = $1 - AND "name" = $2 -` - -type GetTemplateVersionByOrganizationAndNameParams struct { - OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` - Name string `db:"name" json:"name"` -} - -func (q *sqlQuerier) GetTemplateVersionByOrganizationAndName(ctx context.Context, arg GetTemplateVersionByOrganizationAndNameParams) (TemplateVersion, error) { - row := q.db.QueryRowContext(ctx, getTemplateVersionByOrganizationAndName, arg.OrganizationID, 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, - ) - return i, err -} - const getTemplateVersionByTemplateIDAndName = `-- name: GetTemplateVersionByTemplateIDAndName :one SELECT id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by diff --git a/coderd/database/queries/templateversions.sql b/coderd/database/queries/templateversions.sql index d49d86bf56..78dcff44e2 100644 --- a/coderd/database/queries/templateversions.sql +++ b/coderd/database/queries/templateversions.sql @@ -52,15 +52,6 @@ WHERE template_id = $1 AND "name" = $2; --- name: GetTemplateVersionByOrganizationAndName :one -SELECT - * -FROM - template_versions -WHERE - organization_id = $1 - AND "name" = $2; - -- name: GetTemplateVersionByID :one SELECT * diff --git a/coderd/templateversions.go b/coderd/templateversions.go index abcf87ce1e..f013912521 100644 --- a/coderd/templateversions.go +++ b/coderd/templateversions.go @@ -763,22 +763,50 @@ func (api *API) templateVersionByName(rw http.ResponseWriter, r *http.Request) { httpapi.Write(ctx, rw, http.StatusOK, convertTemplateVersion(templateVersion, convertProvisionerJob(job), user)) } -// @Summary Get template version by organization and name -// @ID get-template-version-by-organization-and-name +// @Summary Get template version by organization, template, and name +// @ID get-template-version-by-organization-template-and-name // @Security CoderSessionToken // @Produce json // @Tags Templates // @Param organization path string true "Organization ID" format(uuid) +// @Param templatename path string true "Template name" // @Param templateversionname path string true "Template version name" // @Success 200 {object} codersdk.TemplateVersion -// @Router /organizations/{organization}/templateversions/{templateversionname} [get] -func (api *API) templateVersionByOrganizationAndName(rw http.ResponseWriter, r *http.Request) { +// @Router /organizations/{organization}/templates/{templatename}/versions/{templateversionname} [get] +func (api *API) templateVersionByOrganizationTemplateAndName(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() organization := httpmw.OrganizationParam(r) - templateVersionName := chi.URLParam(r, "templateversionname") - templateVersion, err := api.Database.GetTemplateVersionByOrganizationAndName(ctx, database.GetTemplateVersionByOrganizationAndNameParams{ + templateName := chi.URLParam(r, "templatename") + + template, err := api.Database.GetTemplateByOrganizationAndName(ctx, database.GetTemplateByOrganizationAndNameParams{ OrganizationID: organization.ID, - Name: templateVersionName, + Name: templateName, + }) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + httpapi.ResourceNotFound(rw) + return + } + + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error fetching template.", + Detail: err.Error(), + }) + return + } + + if !api.Authorize(r, rbac.ActionRead, template) { + httpapi.ResourceNotFound(rw) + return + } + + templateVersionName := chi.URLParam(r, "templateversionname") + templateVersion, err := api.Database.GetTemplateVersionByTemplateIDAndName(ctx, database.GetTemplateVersionByTemplateIDAndNameParams{ + TemplateID: uuid.NullUUID{ + UUID: template.ID, + Valid: true, + }, + Name: templateVersionName, }) if errors.Is(err, sql.ErrNoRows) { httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{ @@ -814,22 +842,49 @@ func (api *API) templateVersionByOrganizationAndName(rw http.ResponseWriter, r * httpapi.Write(ctx, rw, http.StatusOK, convertTemplateVersion(templateVersion, convertProvisionerJob(job), user)) } -// @Summary Get previous template version by organization and name -// @ID get-previous-template-version-by-organization-and-name +// @Summary Get previous template version by organization, template, and name +// @ID get-previous-template-version-by-organization-template-and-name // @Security CoderSessionToken // @Produce json // @Tags Templates // @Param organization path string true "Organization ID" format(uuid) +// @Param templatename path string true "Template name" // @Param templateversionname path string true "Template version name" // @Success 200 {object} codersdk.TemplateVersion -// @Router /organizations/{organization}/templateversions/{templateversionname}/previous [get] -func (api *API) previousTemplateVersionByOrganizationAndName(rw http.ResponseWriter, r *http.Request) { +// @Router /organizations/{organization}/templates/{templatename}/versions/{templateversionname}/previous [get] +func (api *API) previousTemplateVersionByOrganizationTemplateAndName(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() organization := httpmw.OrganizationParam(r) - templateVersionName := chi.URLParam(r, "templateversionname") - templateVersion, err := api.Database.GetTemplateVersionByOrganizationAndName(ctx, database.GetTemplateVersionByOrganizationAndNameParams{ + templateName := chi.URLParam(r, "templatename") + template, err := api.Database.GetTemplateByOrganizationAndName(ctx, database.GetTemplateByOrganizationAndNameParams{ OrganizationID: organization.ID, - Name: templateVersionName, + Name: templateName, + }) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + httpapi.ResourceNotFound(rw) + return + } + + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error fetching template.", + Detail: err.Error(), + }) + return + } + + if !api.Authorize(r, rbac.ActionRead, template) { + httpapi.ResourceNotFound(rw) + return + } + + templateVersionName := chi.URLParam(r, "templateversionname") + templateVersion, err := api.Database.GetTemplateVersionByTemplateIDAndName(ctx, database.GetTemplateVersionByTemplateIDAndNameParams{ + TemplateID: uuid.NullUUID{ + UUID: template.ID, + Valid: true, + }, + Name: templateVersionName, }) if err != nil { if xerrors.Is(err, sql.ErrNoRows) { diff --git a/coderd/templateversions_test.go b/coderd/templateversions_test.go index adf35aeb1c..f178c613ce 100644 --- a/coderd/templateversions_test.go +++ b/coderd/templateversions_test.go @@ -992,19 +992,19 @@ func TestPaginatedTemplateVersions(t *testing.T) { } } -func TestTemplateVersionByOrganizationAndName(t *testing.T) { +func TestTemplateVersionByOrganizationTemplateAndName(t *testing.T) { t.Parallel() t.Run("NotFound", 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) + template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() - _, err := client.TemplateVersionByOrganizationAndName(ctx, user.OrganizationID, "nothing") + _, err := client.TemplateVersionByOrganizationAndName(ctx, user.OrganizationID, template.Name, "nothing") var apiErr *codersdk.Error require.ErrorAs(t, err, &apiErr) require.Equal(t, http.StatusNotFound, apiErr.StatusCode()) @@ -1015,12 +1015,12 @@ func TestTemplateVersionByOrganizationAndName(t *testing.T) { 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) + template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() - _, err := client.TemplateVersionByOrganizationAndName(ctx, user.OrganizationID, version.Name) + _, err := client.TemplateVersionByOrganizationAndName(ctx, user.OrganizationID, template.Name, version.Name) require.NoError(t, err) }) } @@ -1048,7 +1048,7 @@ func TestPreviousTemplateVersion(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() - _, err := client.PreviousTemplateVersion(ctx, user.OrganizationID, templateBVersion1.Name) + _, err := client.PreviousTemplateVersion(ctx, user.OrganizationID, templateB.Name, templateBVersion1.Name) var apiErr *codersdk.Error require.ErrorAs(t, err, &apiErr) require.Equal(t, http.StatusNotFound, apiErr.StatusCode()) @@ -1075,7 +1075,7 @@ func TestPreviousTemplateVersion(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() - result, err := client.PreviousTemplateVersion(ctx, user.OrganizationID, templateBVersion2.Name) + result, err := client.PreviousTemplateVersion(ctx, user.OrganizationID, templateB.Name, templateBVersion2.Name) require.NoError(t, err) require.Equal(t, templateBVersion1.ID, result.ID) }) diff --git a/codersdk/organizations.go b/codersdk/organizations.go index e4a07f6990..e7d1689240 100644 --- a/codersdk/organizations.go +++ b/codersdk/organizations.go @@ -153,9 +153,9 @@ func (c *Client) CreateTemplateVersion(ctx context.Context, organizationID uuid. return templateVersion, json.NewDecoder(res.Body).Decode(&templateVersion) } -func (c *Client) TemplateVersionByOrganizationAndName(ctx context.Context, organizationID uuid.UUID, name string) (TemplateVersion, error) { +func (c *Client) TemplateVersionByOrganizationAndName(ctx context.Context, organizationID uuid.UUID, templateName, versionName string) (TemplateVersion, error) { res, err := c.Request(ctx, http.MethodGet, - fmt.Sprintf("/api/v2/organizations/%s/templateversions/%s", organizationID.String(), name), + fmt.Sprintf("/api/v2/organizations/%s/templates/%s/versions/%s", organizationID.String(), templateName, versionName), nil, ) diff --git a/codersdk/templateversions.go b/codersdk/templateversions.go index f7b456779c..55feefca30 100644 --- a/codersdk/templateversions.go +++ b/codersdk/templateversions.go @@ -221,8 +221,8 @@ func (c *Client) CancelTemplateVersionDryRun(ctx context.Context, version, job u return nil } -func (c *Client) PreviousTemplateVersion(ctx context.Context, organization uuid.UUID, versionName string) (TemplateVersion, error) { - res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s/templateversions/%s/previous", organization, versionName), nil) +func (c *Client) PreviousTemplateVersion(ctx context.Context, organization uuid.UUID, templateName, versionName string) (TemplateVersion, error) { + res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s/templates/%s/versions/%s/previous", organization, templateName, versionName), nil) if err != nil { return TemplateVersion{}, err } diff --git a/docs/api/templates.md b/docs/api/templates.md index 2d62e30f5c..859618198a 100644 --- a/docs/api/templates.md +++ b/docs/api/templates.md @@ -375,6 +375,158 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/templat To perform this operation, you must be authenticated. [Learn more](authentication.md). +## Get template version by organization, template, and name + +### Code samples + +```shell +# Example request using curl +curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/templates/{templatename}/versions/{templateversionname} \ + -H 'Accept: application/json' \ + -H 'Coder-Session-Token: API_KEY' +``` + +`GET /organizations/{organization}/templates/{templatename}/versions/{templateversionname}` + +### Parameters + +| Name | In | Type | Required | Description | +| --------------------- | ---- | ------------ | -------- | --------------------- | +| `organization` | path | string(uuid) | true | Organization ID | +| `templatename` | path | string | true | Template name | +| `templateversionname` | path | string | true | Template version name | + +### 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", + "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). + +## Get previous template version by organization, template, and name + +### Code samples + +```shell +# Example request using curl +curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/templates/{templatename}/versions/{templateversionname}/previous \ + -H 'Accept: application/json' \ + -H 'Coder-Session-Token: API_KEY' +``` + +`GET /organizations/{organization}/templates/{templatename}/versions/{templateversionname}/previous` + +### Parameters + +| Name | In | Type | Required | Description | +| --------------------- | ---- | ------------ | -------- | --------------------- | +| `organization` | path | string(uuid) | true | Organization ID | +| `templatename` | path | string | true | Template name | +| `templateversionname` | path | string | true | Template version name | + +### 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", + "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). + ## Create template version by organization ### Code samples @@ -474,156 +626,6 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/templa To perform this operation, you must be authenticated. [Learn more](authentication.md). -## Get template version by organization and name - -### Code samples - -```shell -# Example request using curl -curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/templateversions/{templateversionname} \ - -H 'Accept: application/json' \ - -H 'Coder-Session-Token: API_KEY' -``` - -`GET /organizations/{organization}/templateversions/{templateversionname}` - -### Parameters - -| Name | In | Type | Required | Description | -| --------------------- | ---- | ------------ | -------- | --------------------- | -| `organization` | path | string(uuid) | true | Organization ID | -| `templateversionname` | path | string | true | Template version name | - -### 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", - "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). - -## Get previous template version by organization and name - -### Code samples - -```shell -# Example request using curl -curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/templateversions/{templateversionname}/previous \ - -H 'Accept: application/json' \ - -H 'Coder-Session-Token: API_KEY' -``` - -`GET /organizations/{organization}/templateversions/{templateversionname}/previous` - -### Parameters - -| Name | In | Type | Required | Description | -| --------------------- | ---- | ------------ | -------- | --------------------- | -| `organization` | path | string(uuid) | true | Organization ID | -| `templateversionname` | path | string | true | Template version name | - -### 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", - "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). - ## Get template metadata by ID ### Code samples diff --git a/site/src/api/api.ts b/site/src/api/api.ts index dd651a54ae..2007b40a36 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -231,10 +231,11 @@ export const getTemplateVersions = async ( export const getTemplateVersionByName = async ( organizationId: string, + templateName: string, versionName: string, ): Promise => { const response = await axios.get( - `/api/v2/organizations/${organizationId}/templateversions/${versionName}`, + `/api/v2/organizations/${organizationId}/templates/${templateName}/versions/${versionName}`, ) return response.data } @@ -245,11 +246,12 @@ export type GetPreviousTemplateVersionByNameResponse = export const getPreviousTemplateVersionByName = async ( organizationId: string, + templateName: string, versionName: string, ): Promise => { try { const response = await axios.get( - `/api/v2/organizations/${organizationId}/templateversions/${versionName}/previous`, + `/api/v2/organizations/${organizationId}/templates/${templateName}/versions/${versionName}/previous`, ) return response.data } catch (error) { diff --git a/site/src/pages/TemplateVersionPage/TemplateVersionPage.tsx b/site/src/pages/TemplateVersionPage/TemplateVersionPage.tsx index 0f2b40d37c..ed836d58fd 100644 --- a/site/src/pages/TemplateVersionPage/TemplateVersionPage.tsx +++ b/site/src/pages/TemplateVersionPage/TemplateVersionPage.tsx @@ -18,7 +18,7 @@ export const TemplateVersionPage: FC = () => { const { version: versionName, template: templateName } = useParams() as Params const orgId = useOrganizationId() const [state] = useMachine(templateVersionMachine, { - context: { versionName, orgId }, + context: { templateName, versionName, orgId }, }) const tab = useTab("file", "0") const { t } = useTranslation("templateVersionPage") diff --git a/site/src/testHelpers/handlers.ts b/site/src/testHelpers/handlers.ts index 9b11058004..0dd0f2d0fe 100644 --- a/site/src/testHelpers/handlers.ts +++ b/site/src/testHelpers/handlers.ts @@ -90,13 +90,13 @@ export const handlers = [ }, ), rest.get( - "api/v2/organizations/:organizationId/templateversions/:templateVersionName", + "api/v2/organizations/:organizationId/templates/:templateName/versions/:templateVersionName", async (req, res, ctx) => { return res(ctx.status(200), ctx.json(M.MockTemplateVersion)) }, ), rest.get( - "api/v2/organizations/:organizationId/templateversions/:templateVersionName/previous", + "api/v2/organizations/:organizationId/templates/:templateName/versions/:templateVersionName/previous", async (req, res, ctx) => { return res(ctx.status(200), ctx.json(M.MockTemplateVersion2)) }, diff --git a/site/src/xServices/templateVersion/templateVersionXService.ts b/site/src/xServices/templateVersion/templateVersionXService.ts index 73cd0989bf..1c59f792e2 100644 --- a/site/src/xServices/templateVersion/templateVersionXService.ts +++ b/site/src/xServices/templateVersion/templateVersionXService.ts @@ -12,6 +12,7 @@ import { assign, createMachine } from "xstate" export interface TemplateVersionMachineContext { orgId: string + templateName: string versionName: string currentVersion?: TemplateVersion currentFiles?: TemplateVersionFiles @@ -94,10 +95,10 @@ export const templateVersionMachine = createMachine( }), }, services: { - loadVersions: async ({ orgId, versionName }) => { + loadVersions: async ({ orgId, templateName, versionName }) => { const [currentVersion, previousVersion] = await Promise.all([ - getTemplateVersionByName(orgId, versionName), - getPreviousTemplateVersionByName(orgId, versionName), + getTemplateVersionByName(orgId, templateName, versionName), + getPreviousTemplateVersionByName(orgId, templateName, versionName), ]) return {