diff --git a/coderd/audit/diff_test.go b/coderd/audit/diff_test.go index 93e0d1bc33824..fc9c41b7cb16d 100644 --- a/coderd/audit/diff_test.go +++ b/coderd/audit/diff_test.go @@ -114,12 +114,14 @@ func TestDiff(t *testing.T) { UpdatedAt: time.Now(), OrganizationID: uuid.UUID{3}, Name: "rust", + CreatedBy: uuid.NullUUID{UUID: uuid.UUID{4}, Valid: true}, }, exp: audit.Map{ "id": uuid.UUID{1}.String(), "template_id": uuid.UUID{2}.String(), "organization_id": uuid.UUID{3}.String(), "name": "rust", + "created_by": uuid.UUID{4}.String(), }, }, { @@ -132,11 +134,13 @@ func TestDiff(t *testing.T) { UpdatedAt: time.Now(), OrganizationID: uuid.UUID{3}, Name: "rust", + CreatedBy: uuid.NullUUID{UUID: uuid.UUID{4}, Valid: true}, }, exp: audit.Map{ "id": uuid.UUID{1}.String(), "organization_id": uuid.UUID{3}.String(), "name": "rust", + "created_by": uuid.UUID{4}.String(), }, }, }) diff --git a/coderd/audit/table.go b/coderd/audit/table.go index 7562472f3c583..c842956e6cf24 100644 --- a/coderd/audit/table.go +++ b/coderd/audit/table.go @@ -83,6 +83,7 @@ var AuditableResources = auditMap(map[any]map[string]Action{ "name": ActionTrack, "readme": ActionTrack, "job_id": ActionIgnore, // Not helpful in a diff because jobs aren't tracked in audit logs. + "created_by": ActionTrack, }, &database.User{}: { "id": ActionTrack, diff --git a/coderd/database/databasefake/databasefake.go b/coderd/database/databasefake/databasefake.go index a0f5c08f9b4b3..5122029d9d31c 100644 --- a/coderd/database/databasefake/databasefake.go +++ b/coderd/database/databasefake/databasefake.go @@ -1511,6 +1511,7 @@ func (q *fakeQuerier) InsertTemplateVersion(_ context.Context, arg database.Inse Name: arg.Name, Readme: arg.Readme, JobID: arg.JobID, + CreatedBy: arg.CreatedBy, } q.templateVersions = append(q.templateVersions, version) return version, nil diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 9c757a29b4362..aa481b8400a9f 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -247,7 +247,8 @@ CREATE TABLE template_versions ( updated_at timestamp with time zone NOT NULL, name character varying(64) NOT NULL, readme character varying(1048576) NOT NULL, - job_id uuid NOT NULL + job_id uuid NOT NULL, + created_by uuid ); CREATE TABLE templates ( @@ -486,6 +487,9 @@ ALTER TABLE ONLY provisioner_job_logs ALTER TABLE ONLY provisioner_jobs ADD CONSTRAINT provisioner_jobs_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE; +ALTER TABLE ONLY template_versions + ADD CONSTRAINT template_versions_created_by_fkey FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE RESTRICT; + ALTER TABLE ONLY template_versions ADD CONSTRAINT template_versions_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE; diff --git a/coderd/database/migrations/000030_template_version_created_by.down.sql b/coderd/database/migrations/000030_template_version_created_by.down.sql new file mode 100644 index 0000000000000..66e4f2d6f0fa7 --- /dev/null +++ b/coderd/database/migrations/000030_template_version_created_by.down.sql @@ -0,0 +1 @@ +ALTER TABLE ONLY template_versions DROP COLUMN IF EXISTS created_by; diff --git a/coderd/database/migrations/000030_template_version_created_by.up.sql b/coderd/database/migrations/000030_template_version_created_by.up.sql new file mode 100644 index 0000000000000..36cdff6f88188 --- /dev/null +++ b/coderd/database/migrations/000030_template_version_created_by.up.sql @@ -0,0 +1,16 @@ +BEGIN; + +ALTER TABLE ONLY template_versions ADD COLUMN IF NOT EXISTS created_by uuid REFERENCES users (id) ON DELETE RESTRICT; + +UPDATE + template_versions +SET + created_by = ( + SELECT created_by FROM templates + WHERE template_versions.template_id = templates.id + LIMIT 1 + ) +WHERE + created_by IS NULL; + +COMMIT; diff --git a/coderd/database/models.go b/coderd/database/models.go index 931f66349d288..a7b793f33e7cf 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -478,6 +478,7 @@ type TemplateVersion struct { Name string `db:"name" json:"name"` Readme string `db:"readme" json:"readme"` JobID uuid.UUID `db:"job_id" json:"job_id"` + CreatedBy uuid.NullUUID `db:"created_by" json:"created_by"` } type User struct { diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 8addc7a2bdaab..fde6fca6a6d37 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -2145,7 +2145,7 @@ func (q *sqlQuerier) UpdateTemplateMetaByID(ctx context.Context, arg UpdateTempl const getTemplateVersionByID = `-- name: GetTemplateVersionByID :one SELECT - id, template_id, organization_id, created_at, updated_at, name, readme, job_id + id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by FROM template_versions WHERE @@ -2164,13 +2164,14 @@ func (q *sqlQuerier) GetTemplateVersionByID(ctx context.Context, id uuid.UUID) ( &i.Name, &i.Readme, &i.JobID, + &i.CreatedBy, ) return i, err } const getTemplateVersionByJobID = `-- name: GetTemplateVersionByJobID :one SELECT - id, template_id, organization_id, created_at, updated_at, name, readme, job_id + id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by FROM template_versions WHERE @@ -2189,13 +2190,14 @@ func (q *sqlQuerier) GetTemplateVersionByJobID(ctx context.Context, jobID uuid.U &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 + id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by FROM template_versions WHERE @@ -2220,13 +2222,14 @@ func (q *sqlQuerier) GetTemplateVersionByTemplateIDAndName(ctx context.Context, &i.Name, &i.Readme, &i.JobID, + &i.CreatedBy, ) return i, err } const getTemplateVersionsByTemplateID = `-- name: GetTemplateVersionsByTemplateID :many SELECT - id, template_id, organization_id, created_at, updated_at, name, readme, job_id + id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by FROM template_versions WHERE @@ -2289,6 +2292,7 @@ func (q *sqlQuerier) GetTemplateVersionsByTemplateID(ctx context.Context, arg Ge &i.Name, &i.Readme, &i.JobID, + &i.CreatedBy, ); err != nil { return nil, err } @@ -2304,7 +2308,7 @@ func (q *sqlQuerier) GetTemplateVersionsByTemplateID(ctx context.Context, arg Ge } const getTemplateVersionsCreatedAfter = `-- name: GetTemplateVersionsCreatedAfter :many -SELECT id, template_id, organization_id, created_at, updated_at, name, readme, job_id FROM template_versions WHERE created_at > $1 +SELECT id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by FROM template_versions WHERE created_at > $1 ` func (q *sqlQuerier) GetTemplateVersionsCreatedAfter(ctx context.Context, createdAt time.Time) ([]TemplateVersion, error) { @@ -2325,6 +2329,7 @@ func (q *sqlQuerier) GetTemplateVersionsCreatedAfter(ctx context.Context, create &i.Name, &i.Readme, &i.JobID, + &i.CreatedBy, ); err != nil { return nil, err } @@ -2349,10 +2354,11 @@ INSERT INTO updated_at, "name", readme, - job_id + job_id, + created_by ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id, template_id, organization_id, created_at, updated_at, name, readme, job_id + ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by ` type InsertTemplateVersionParams struct { @@ -2364,6 +2370,7 @@ type InsertTemplateVersionParams struct { Name string `db:"name" json:"name"` Readme string `db:"readme" json:"readme"` JobID uuid.UUID `db:"job_id" json:"job_id"` + CreatedBy uuid.NullUUID `db:"created_by" json:"created_by"` } func (q *sqlQuerier) InsertTemplateVersion(ctx context.Context, arg InsertTemplateVersionParams) (TemplateVersion, error) { @@ -2376,6 +2383,7 @@ func (q *sqlQuerier) InsertTemplateVersion(ctx context.Context, arg InsertTempla arg.Name, arg.Readme, arg.JobID, + arg.CreatedBy, ) var i TemplateVersion err := row.Scan( @@ -2387,6 +2395,7 @@ func (q *sqlQuerier) InsertTemplateVersion(ctx context.Context, arg InsertTempla &i.Name, &i.Readme, &i.JobID, + &i.CreatedBy, ) return i, err } diff --git a/coderd/database/queries/templateversions.sql b/coderd/database/queries/templateversions.sql index 12b9041df7c93..b92598e5051fc 100644 --- a/coderd/database/queries/templateversions.sql +++ b/coderd/database/queries/templateversions.sql @@ -70,10 +70,11 @@ INSERT INTO updated_at, "name", readme, - job_id + job_id, + created_by ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *; + ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING *; -- name: UpdateTemplateVersionByID :exec UPDATE diff --git a/coderd/templates.go b/coderd/templates.go index 5eb2f5c8e218b..884b26fabe385 100644 --- a/coderd/templates.go +++ b/coderd/templates.go @@ -209,6 +209,7 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque UUID: dbTemplate.ID, Valid: true, }, + UpdatedAt: database.Now(), }) if err != nil { return xerrors.Errorf("insert template version: %s", err) diff --git a/coderd/templateversions.go b/coderd/templateversions.go index 5b385fca946e5..cc10046791b57 100644 --- a/coderd/templateversions.go +++ b/coderd/templateversions.go @@ -1,6 +1,7 @@ package coderd import ( + "context" "database/sql" "encoding/json" "errors" @@ -36,7 +37,16 @@ func (api *API) templateVersion(rw http.ResponseWriter, r *http.Request) { return } - httpapi.Write(rw, http.StatusOK, convertTemplateVersion(templateVersion, convertProvisionerJob(job))) + createdByName, err := getUsernameByUserID(r.Context(), api.Database, templateVersion.CreatedBy) + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error fetching creator name.", + Detail: err.Error(), + }) + return + } + + httpapi.Write(rw, http.StatusOK, convertTemplateVersion(templateVersion, convertProvisionerJob(job), createdByName)) } func (api *API) patchCancelTemplateVersion(rw http.ResponseWriter, r *http.Request) { @@ -476,7 +486,15 @@ func (api *API) templateVersionsByTemplate(rw http.ResponseWriter, r *http.Reque }) return err } - apiVersions = append(apiVersions, convertTemplateVersion(version, convertProvisionerJob(job))) + createdByName, err := getUsernameByUserID(r.Context(), store, version.CreatedBy) + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error fetching creator name.", + Detail: err.Error(), + }) + return err + } + apiVersions = append(apiVersions, convertTemplateVersion(version, convertProvisionerJob(job), createdByName)) } return nil @@ -525,7 +543,16 @@ func (api *API) templateVersionByName(rw http.ResponseWriter, r *http.Request) { return } - httpapi.Write(rw, http.StatusOK, convertTemplateVersion(templateVersion, convertProvisionerJob(job))) + createdByName, err := getUsernameByUserID(r.Context(), api.Database, templateVersion.CreatedBy) + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error fetching creator name.", + Detail: err.Error(), + }) + return + } + + httpapi.Write(rw, http.StatusOK, convertTemplateVersion(templateVersion, convertProvisionerJob(job), createdByName)) } func (api *API) patchActiveTemplateVersion(rw http.ResponseWriter, r *http.Request) { @@ -735,6 +762,10 @@ func (api *API) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *ht Name: namesgenerator.GetRandomName(1), Readme: "", JobID: provisionerJob.ID, + CreatedBy: uuid.NullUUID{ + UUID: apiKey.UserID, + Valid: true, + }, }) if err != nil { return xerrors.Errorf("insert template version: %w", err) @@ -748,7 +779,16 @@ func (api *API) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *ht return } - httpapi.Write(rw, http.StatusCreated, convertTemplateVersion(templateVersion, convertProvisionerJob(provisionerJob))) + createdByName, err := getUsernameByUserID(r.Context(), api.Database, templateVersion.CreatedBy) + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error fetching creator name.", + Detail: err.Error(), + }) + return + } + + httpapi.Write(rw, http.StatusCreated, convertTemplateVersion(templateVersion, convertProvisionerJob(provisionerJob), createdByName)) } // templateVersionResources returns the workspace agent resources associated @@ -796,7 +836,18 @@ func (api *API) templateVersionLogs(rw http.ResponseWriter, r *http.Request) { api.provisionerJobLogs(rw, r, job) } -func convertTemplateVersion(version database.TemplateVersion, job codersdk.ProvisionerJob) codersdk.TemplateVersion { +func getUsernameByUserID(ctx context.Context, db database.Store, userID uuid.NullUUID) (string, error) { + if !userID.Valid { + return "", nil + } + user, err := db.GetUserByID(ctx, userID.UUID) + if err != nil { + return "", err + } + return user.Username, nil +} + +func convertTemplateVersion(version database.TemplateVersion, job codersdk.ProvisionerJob, createdByName string) codersdk.TemplateVersion { return codersdk.TemplateVersion{ ID: version.ID, TemplateID: &version.TemplateID.UUID, @@ -806,5 +857,7 @@ func convertTemplateVersion(version database.TemplateVersion, job codersdk.Provi Name: version.Name, Job: job, Readme: version.Readme, + CreatedByID: version.CreatedBy.UUID, + CreatedByName: createdByName, } } diff --git a/codersdk/templateversions.go b/codersdk/templateversions.go index c41b93a17781f..3562906e0c1ee 100644 --- a/codersdk/templateversions.go +++ b/codersdk/templateversions.go @@ -20,6 +20,8 @@ type TemplateVersion struct { Name string `json:"name"` Job ProvisionerJob `json:"job"` Readme string `json:"readme"` + CreatedByID uuid.UUID `json:"created_by_id"` + CreatedByName string `json:"created_by_name"` } // TemplateVersion returns a template version by ID. diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index be3acf0a7ca98..7714ad1965d43 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -87,7 +87,7 @@ export interface CreateTemplateRequest { readonly min_autostart_interval_ms?: number } -// From codersdk/templateversions.go:106:6 +// From codersdk/templateversions.go:108:6 export interface CreateTemplateVersionDryRunRequest { readonly WorkspaceName: string readonly ParameterValues: CreateParameterRequest[] @@ -290,6 +290,8 @@ export interface TemplateVersion { readonly name: string readonly job: ProvisionerJob readonly readme: string + readonly created_by_id: string + readonly created_by_name: string } // From codersdk/templates.go:100:6 diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 6544002edf9bf..769a5d547c0c4 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -112,6 +112,8 @@ name:Template test You can add instructions here [Some link info](https://coder.com)`, + created_by_id: "test-creator-id", + created_by_name: "test_creator", } export const MockTemplate: TypesGen.Template = {