Skip to content

Commit f262fb4

Browse files
feat: Add template version page (#5071)
1 parent 773fc73 commit f262fb4

34 files changed

+1769
-843
lines changed

coderd/coderd.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,10 @@ func New(options *Options) *API {
340340
httpmw.ExtractOrganizationParam(options.Database),
341341
)
342342
r.Get("/", api.organization)
343-
r.Post("/templateversions", api.postTemplateVersionsByOrganization)
343+
r.Route("/templateversions", func(r chi.Router) {
344+
r.Post("/", api.postTemplateVersionsByOrganization)
345+
r.Get("/{templateversionname}", api.templateVersionByOrganizationAndName)
346+
})
344347
r.Route("/templates", func(r chi.Router) {
345348
r.Post("/", api.postTemplateByOrganization)
346349
r.Get("/", api.templatesByOrganization)

coderd/coderdtest/authorize.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -238,10 +238,11 @@ func AGPLRoutes(a *AuthTester) (map[string]string, map[string]RouteCheck) {
238238
"GET:/api/v2/applications/auth-redirect": {AssertAction: rbac.ActionCreate, AssertObject: rbac.ResourceAPIKey},
239239

240240
// These endpoints need payloads to get to the auth part. Payloads will be required
241-
"PUT:/api/v2/users/{user}/roles": {StatusCode: http.StatusBadRequest, NoAuthorize: true},
242-
"PUT:/api/v2/organizations/{organization}/members/{user}/roles": {NoAuthorize: true},
243-
"POST:/api/v2/workspaces/{workspace}/builds": {StatusCode: http.StatusBadRequest, NoAuthorize: true},
244-
"POST:/api/v2/organizations/{organization}/templateversions": {StatusCode: http.StatusBadRequest, NoAuthorize: true},
241+
"PUT:/api/v2/users/{user}/roles": {StatusCode: http.StatusBadRequest, NoAuthorize: true},
242+
"PUT:/api/v2/organizations/{organization}/members/{user}/roles": {NoAuthorize: true},
243+
"POST:/api/v2/workspaces/{workspace}/builds": {StatusCode: http.StatusBadRequest, NoAuthorize: true},
244+
"POST:/api/v2/organizations/{organization}/templateversions": {StatusCode: http.StatusBadRequest, NoAuthorize: true},
245+
"GET:/api/v2/organizations/{organization}/templateversions/{templateversionname}": {StatusCode: http.StatusBadRequest, NoAuthorize: true},
245246

246247
// Endpoints that use the SQLQuery filter.
247248
"GET:/api/v2/workspaces/": {StatusCode: http.StatusOK, NoAuthorize: true},

coderd/database/databasefake/databasefake.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1471,6 +1471,22 @@ func (q *fakeQuerier) GetTemplateVersionByTemplateIDAndName(_ context.Context, a
14711471
return database.TemplateVersion{}, sql.ErrNoRows
14721472
}
14731473

1474+
func (q *fakeQuerier) GetTemplateVersionByOrganizationAndName(_ context.Context, arg database.GetTemplateVersionByOrganizationAndNameParams) (database.TemplateVersion, error) {
1475+
q.mutex.RLock()
1476+
defer q.mutex.RUnlock()
1477+
1478+
for _, templateVersion := range q.templateVersions {
1479+
if templateVersion.OrganizationID != arg.OrganizationID {
1480+
continue
1481+
}
1482+
if !strings.EqualFold(templateVersion.Name, arg.Name) {
1483+
continue
1484+
}
1485+
return templateVersion, nil
1486+
}
1487+
return database.TemplateVersion{}, sql.ErrNoRows
1488+
}
1489+
14741490
func (q *fakeQuerier) GetTemplateVersionByID(_ context.Context, templateVersionID uuid.UUID) (database.TemplateVersion, error) {
14751491
q.mutex.RLock()
14761492
defer q.mutex.RUnlock()

coderd/database/querier.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries.sql.go

Lines changed: 32 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/templateversions.sql

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,15 @@ WHERE
5252
template_id = $1
5353
AND "name" = $2;
5454

55+
-- name: GetTemplateVersionByOrganizationAndName :one
56+
SELECT
57+
*
58+
FROM
59+
template_versions
60+
WHERE
61+
organization_id = $1
62+
AND "name" = $2;
63+
5564
-- name: GetTemplateVersionByID :one
5665
SELECT
5766
*

coderd/templateversions.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,48 @@ func (api *API) templateVersionByName(rw http.ResponseWriter, r *http.Request) {
596596
httpapi.Write(ctx, rw, http.StatusOK, convertTemplateVersion(templateVersion, convertProvisionerJob(job), user))
597597
}
598598

599+
func (api *API) templateVersionByOrganizationAndName(rw http.ResponseWriter, r *http.Request) {
600+
ctx := r.Context()
601+
organization := httpmw.OrganizationParam(r)
602+
templateVersionName := chi.URLParam(r, "templateversionname")
603+
templateVersion, err := api.Database.GetTemplateVersionByOrganizationAndName(ctx, database.GetTemplateVersionByOrganizationAndNameParams{
604+
OrganizationID: organization.ID,
605+
Name: templateVersionName,
606+
})
607+
if errors.Is(err, sql.ErrNoRows) {
608+
httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{
609+
Message: fmt.Sprintf("No template version found by name %q.", templateVersionName),
610+
})
611+
return
612+
}
613+
if err != nil {
614+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
615+
Message: "Internal error fetching template version.",
616+
Detail: err.Error(),
617+
})
618+
return
619+
}
620+
job, err := api.Database.GetProvisionerJobByID(ctx, templateVersion.JobID)
621+
if err != nil {
622+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
623+
Message: "Internal error fetching provisioner job.",
624+
Detail: err.Error(),
625+
})
626+
return
627+
}
628+
629+
user, err := api.Database.GetUserByID(ctx, templateVersion.CreatedBy)
630+
if err != nil {
631+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
632+
Message: "Internal error on fetching user.",
633+
Detail: err.Error(),
634+
})
635+
return
636+
}
637+
638+
httpapi.Write(ctx, rw, http.StatusOK, convertTemplateVersion(templateVersion, convertProvisionerJob(job), user))
639+
}
640+
599641
func (api *API) patchActiveTemplateVersion(rw http.ResponseWriter, r *http.Request) {
600642
var (
601643
ctx = r.Context()

coderd/templateversions_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -928,3 +928,36 @@ func TestPaginatedTemplateVersions(t *testing.T) {
928928
})
929929
}
930930
}
931+
932+
func TestTemplateVersionByOrganizationAndName(t *testing.T) {
933+
t.Parallel()
934+
t.Run("NotFound", func(t *testing.T) {
935+
t.Parallel()
936+
client := coderdtest.New(t, nil)
937+
user := coderdtest.CreateFirstUser(t, client)
938+
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
939+
coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
940+
941+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
942+
defer cancel()
943+
944+
_, err := client.TemplateVersionByOrganizationAndName(ctx, user.OrganizationID, "nothing")
945+
var apiErr *codersdk.Error
946+
require.ErrorAs(t, err, &apiErr)
947+
require.Equal(t, http.StatusNotFound, apiErr.StatusCode())
948+
})
949+
950+
t.Run("Found", func(t *testing.T) {
951+
t.Parallel()
952+
client := coderdtest.New(t, nil)
953+
user := coderdtest.CreateFirstUser(t, client)
954+
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
955+
coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
956+
957+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
958+
defer cancel()
959+
960+
_, err := client.TemplateVersionByOrganizationAndName(ctx, user.OrganizationID, version.Name)
961+
require.NoError(t, err)
962+
})
963+
}

codersdk/organizations.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,25 @@ func (c *Client) CreateTemplateVersion(ctx context.Context, organizationID uuid.
138138
return templateVersion, json.NewDecoder(res.Body).Decode(&templateVersion)
139139
}
140140

141+
func (c *Client) TemplateVersionByOrganizationAndName(ctx context.Context, organizationID uuid.UUID, name string) (TemplateVersion, error) {
142+
res, err := c.Request(ctx, http.MethodGet,
143+
fmt.Sprintf("/api/v2/organizations/%s/templateversions/%s", organizationID.String(), name),
144+
nil,
145+
)
146+
147+
if err != nil {
148+
return TemplateVersion{}, xerrors.Errorf("execute request: %w", err)
149+
}
150+
defer res.Body.Close()
151+
152+
if res.StatusCode != http.StatusOK {
153+
return TemplateVersion{}, readBodyAsError(res)
154+
}
155+
156+
var templateVersion TemplateVersion
157+
return templateVersion, json.NewDecoder(res.Body).Decode(&templateVersion)
158+
}
159+
141160
// CreateTemplate creates a new template inside an organization.
142161
func (c *Client) CreateTemplate(ctx context.Context, organizationID uuid.UUID, request CreateTemplateRequest) (Template, error) {
143162
res, err := c.Request(ctx, http.MethodPost,

site/js-untar.d.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
declare module "js-untar" {
2+
interface File {
3+
name: string
4+
readAsString: () => string
5+
}
6+
7+
const Untar: (buffer: ArrayBuffer) => {
8+
then: (
9+
resolve?: () => Promise<void>,
10+
reject?: () => Promise<void>,
11+
progress: (file: File) => Promise<void>,
12+
) => Promise<void>
13+
}
14+
15+
export default Untar
16+
}

0 commit comments

Comments
 (0)