diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 87778c66d3dab..b6e9e2049030a 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -1741,6 +1741,22 @@ func (q *querier) GetFileByID(ctx context.Context, id uuid.UUID) (database.File, return file, nil } +func (q *querier) GetFileIDByTemplateVersionID(ctx context.Context, templateVersionID uuid.UUID) (uuid.UUID, error) { + fileID, err := q.db.GetFileIDByTemplateVersionID(ctx, templateVersionID) + if err != nil { + return uuid.Nil, err + } + // This is a kind of weird check, because users will almost never have this + // permission. Since this query is not currently used to provide data in a + // user facing way, it's expected that this query is run as some system + // subject in order to be authorized. + err = q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceFile.WithID(fileID)) + if err != nil { + return uuid.Nil, err + } + return fileID, nil +} + func (q *querier) GetFileTemplates(ctx context.Context, fileID uuid.UUID) ([]database.GetFileTemplatesRow, error) { if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceSystem); err != nil { return nil, err @@ -2446,6 +2462,18 @@ func (q *querier) GetTemplateVersionParameters(ctx context.Context, templateVers return q.db.GetTemplateVersionParameters(ctx, templateVersionID) } +func (q *querier) GetTemplateVersionTerraformValues(ctx context.Context, templateVersionID uuid.UUID) (database.TemplateVersionTerraformValue, error) { + // The template_version_terraform_values table should follow the same access + // control as the template_version table. Rather than reimplement the checks, + // we just defer to existing implementation. (plus we'd need to use this query + // to reimplement the proper checks anyway) + _, err := q.GetTemplateVersionByID(ctx, templateVersionID) + if err != nil { + return database.TemplateVersionTerraformValue{}, err + } + return q.db.GetTemplateVersionTerraformValues(ctx, templateVersionID) +} + func (q *querier) GetTemplateVersionVariables(ctx context.Context, templateVersionID uuid.UUID) ([]database.TemplateVersionVariable, error) { tv, err := q.db.GetTemplateVersionByID(ctx, templateVersionID) if err != nil { diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 70c2a33443a16..b3a58f262b8b2 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -342,6 +342,15 @@ func (s *MethodTestSuite) TestFile() { f := dbgen.File(s.T(), db, database.File{}) check.Args(f.ID).Asserts(f, policy.ActionRead).Returns(f) })) + s.Run("GetFileIDByTemplateVersionID", s.Subtest(func(db database.Store, check *expects) { + o := dbgen.Organization(s.T(), db, database.Organization{}) + u := dbgen.User(s.T(), db, database.User{}) + _ = dbgen.OrganizationMember(s.T(), db, database.OrganizationMember{OrganizationID: o.ID, UserID: u.ID}) + f := dbgen.File(s.T(), db, database.File{CreatedBy: u.ID}) + j := dbgen.ProvisionerJob(s.T(), db, nil, database.ProvisionerJob{StorageMethod: database.ProvisionerStorageMethodFile, FileID: f.ID}) + tv := dbgen.TemplateVersion(s.T(), db, database.TemplateVersion{OrganizationID: o.ID, JobID: j.ID, CreatedBy: u.ID}) + check.Args(tv.ID).Asserts(rbac.ResourceFile.WithID(f.ID), policy.ActionRead).Returns(f.ID) + })) s.Run("InsertFile", s.Subtest(func(db database.Store, check *expects) { u := dbgen.User(s.T(), db, database.User{}) check.Args(database.InsertFileParams{ @@ -1196,6 +1205,23 @@ func (s *MethodTestSuite) TestTemplate() { }) check.Args(tv.ID).Asserts(t1, policy.ActionRead).Returns([]database.TemplateVersionParameter{}) })) + s.Run("GetTemplateVersionTerraformValues", s.Subtest(func(db database.Store, check *expects) { + o := dbgen.Organization(s.T(), db, database.Organization{}) + u := dbgen.User(s.T(), db, database.User{}) + _ = dbgen.OrganizationMember(s.T(), db, database.OrganizationMember{OrganizationID: o.ID, UserID: u.ID}) + t := dbgen.Template(s.T(), db, database.Template{OrganizationID: o.ID, CreatedBy: u.ID}) + job := dbgen.ProvisionerJob(s.T(), db, nil, database.ProvisionerJob{OrganizationID: o.ID}) + tv := dbgen.TemplateVersion(s.T(), db, database.TemplateVersion{ + OrganizationID: o.ID, + CreatedBy: u.ID, + JobID: job.ID, + TemplateID: uuid.NullUUID{UUID: t.ID, Valid: true}, + }) + dbgen.TemplateVersionTerraformValues(s.T(), db, database.InsertTemplateVersionTerraformValuesByJobIDParams{ + JobID: job.ID, + }) + check.Args(tv.ID).Asserts(t, policy.ActionRead) + })) s.Run("GetTemplateVersionVariables", s.Subtest(func(db database.Store, check *expects) { dbtestutil.DisableForeignKeysAndTriggers(s.T(), db) t1 := dbgen.Template(s.T(), db, database.Template{}) diff --git a/coderd/database/dbgen/dbgen.go b/coderd/database/dbgen/dbgen.go index c43bdfba2b8ca..1ea8d33757250 100644 --- a/coderd/database/dbgen/dbgen.go +++ b/coderd/database/dbgen/dbgen.go @@ -971,6 +971,19 @@ func TemplateVersionParameter(t testing.TB, db database.Store, orig database.Tem return version } +func TemplateVersionTerraformValues(t testing.TB, db database.Store, orig database.InsertTemplateVersionTerraformValuesByJobIDParams) { + t.Helper() + + params := database.InsertTemplateVersionTerraformValuesByJobIDParams{ + JobID: takeFirst(orig.JobID, uuid.New()), + CachedPlan: takeFirstSlice(orig.CachedPlan, []byte("{}")), + UpdatedAt: takeFirst(orig.UpdatedAt, dbtime.Now()), + } + + err := db.InsertTemplateVersionTerraformValuesByJobID(genCtx, params) + require.NoError(t, err, "insert template version parameter") +} + func WorkspaceAgentStat(t testing.TB, db database.Store, orig database.WorkspaceAgentStat) database.WorkspaceAgentStat { if orig.ConnectionsByProto == nil { orig.ConnectionsByProto = json.RawMessage([]byte("{}")) diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index 46f2de5a5820e..6a1fcd32144ae 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -3326,6 +3326,30 @@ func (q *FakeQuerier) GetFileByID(_ context.Context, id uuid.UUID) (database.Fil return database.File{}, sql.ErrNoRows } +func (q *FakeQuerier) GetFileIDByTemplateVersionID(ctx context.Context, templateVersionID uuid.UUID) (uuid.UUID, error) { + q.mutex.RLock() + defer q.mutex.RUnlock() + + for _, v := range q.templateVersions { + if v.ID == templateVersionID { + jobID := v.JobID + for _, j := range q.provisionerJobs { + if j.ID == jobID { + if j.StorageMethod == database.ProvisionerStorageMethodFile { + return j.FileID, nil + } + // We found the right job id but it wasn't a proper match. + break + } + } + // We found the right template version but it wasn't a proper match. + break + } + } + + return uuid.Nil, sql.ErrNoRows +} + func (q *FakeQuerier) GetFileTemplates(_ context.Context, id uuid.UUID) ([]database.GetFileTemplatesRow, error) { q.mutex.RLock() defer q.mutex.RUnlock() @@ -5991,6 +6015,19 @@ func (q *FakeQuerier) GetTemplateVersionParameters(_ context.Context, templateVe return parameters, nil } +func (q *FakeQuerier) GetTemplateVersionTerraformValues(ctx context.Context, templateVersionID uuid.UUID) (database.TemplateVersionTerraformValue, error) { + q.mutex.RLock() + defer q.mutex.RUnlock() + + for _, tvtv := range q.templateVersionTerraformValues { + if tvtv.TemplateVersionID == templateVersionID { + return tvtv, nil + } + } + + return database.TemplateVersionTerraformValue{}, sql.ErrNoRows +} + func (q *FakeQuerier) GetTemplateVersionVariables(_ context.Context, templateVersionID uuid.UUID) ([]database.TemplateVersionVariable, error) { q.mutex.RLock() defer q.mutex.RUnlock() diff --git a/coderd/database/dbmetrics/querymetrics.go b/coderd/database/dbmetrics/querymetrics.go index 05c0418c77acd..5cca877240cd3 100644 --- a/coderd/database/dbmetrics/querymetrics.go +++ b/coderd/database/dbmetrics/querymetrics.go @@ -746,6 +746,13 @@ func (m queryMetricsStore) GetFileByID(ctx context.Context, id uuid.UUID) (datab return file, err } +func (m queryMetricsStore) GetFileIDByTemplateVersionID(ctx context.Context, templateVersionID uuid.UUID) (uuid.UUID, error) { + start := time.Now() + r0, r1 := m.s.GetFileIDByTemplateVersionID(ctx, templateVersionID) + m.queryLatencies.WithLabelValues("GetFileIDByTemplateVersionID").Observe(time.Since(start).Seconds()) + return r0, r1 +} + func (m queryMetricsStore) GetFileTemplates(ctx context.Context, fileID uuid.UUID) ([]database.GetFileTemplatesRow, error) { start := time.Now() rows, err := m.s.GetFileTemplates(ctx, fileID) @@ -1369,6 +1376,13 @@ func (m queryMetricsStore) GetTemplateVersionParameters(ctx context.Context, tem return parameters, err } +func (m queryMetricsStore) GetTemplateVersionTerraformValues(ctx context.Context, templateVersionID uuid.UUID) (database.TemplateVersionTerraformValue, error) { + start := time.Now() + r0, r1 := m.s.GetTemplateVersionTerraformValues(ctx, templateVersionID) + m.queryLatencies.WithLabelValues("GetTemplateVersionTerraformValues").Observe(time.Since(start).Seconds()) + return r0, r1 +} + func (m queryMetricsStore) GetTemplateVersionVariables(ctx context.Context, templateVersionID uuid.UUID) ([]database.TemplateVersionVariable, error) { start := time.Now() variables, err := m.s.GetTemplateVersionVariables(ctx, templateVersionID) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index c5a5db20a9e90..b37547a4cf28d 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -1489,6 +1489,21 @@ func (mr *MockStoreMockRecorder) GetFileByID(ctx, id any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFileByID", reflect.TypeOf((*MockStore)(nil).GetFileByID), ctx, id) } +// GetFileIDByTemplateVersionID mocks base method. +func (m *MockStore) GetFileIDByTemplateVersionID(ctx context.Context, templateVersionID uuid.UUID) (uuid.UUID, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFileIDByTemplateVersionID", ctx, templateVersionID) + ret0, _ := ret[0].(uuid.UUID) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetFileIDByTemplateVersionID indicates an expected call of GetFileIDByTemplateVersionID. +func (mr *MockStoreMockRecorder) GetFileIDByTemplateVersionID(ctx, templateVersionID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFileIDByTemplateVersionID", reflect.TypeOf((*MockStore)(nil).GetFileIDByTemplateVersionID), ctx, templateVersionID) +} + // GetFileTemplates mocks base method. func (m *MockStore) GetFileTemplates(ctx context.Context, fileID uuid.UUID) ([]database.GetFileTemplatesRow, error) { m.ctrl.T.Helper() @@ -2854,6 +2869,21 @@ func (mr *MockStoreMockRecorder) GetTemplateVersionParameters(ctx, templateVersi return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTemplateVersionParameters", reflect.TypeOf((*MockStore)(nil).GetTemplateVersionParameters), ctx, templateVersionID) } +// GetTemplateVersionTerraformValues mocks base method. +func (m *MockStore) GetTemplateVersionTerraformValues(ctx context.Context, templateVersionID uuid.UUID) (database.TemplateVersionTerraformValue, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTemplateVersionTerraformValues", ctx, templateVersionID) + ret0, _ := ret[0].(database.TemplateVersionTerraformValue) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetTemplateVersionTerraformValues indicates an expected call of GetTemplateVersionTerraformValues. +func (mr *MockStoreMockRecorder) GetTemplateVersionTerraformValues(ctx, templateVersionID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTemplateVersionTerraformValues", reflect.TypeOf((*MockStore)(nil).GetTemplateVersionTerraformValues), ctx, templateVersionID) +} + // GetTemplateVersionVariables mocks base method. func (m *MockStore) GetTemplateVersionVariables(ctx context.Context, templateVersionID uuid.UUID) ([]database.TemplateVersionVariable, error) { m.ctrl.T.Helper() diff --git a/coderd/database/querier.go b/coderd/database/querier.go index 892582c1201e5..d3a659236036b 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -166,6 +166,7 @@ type sqlcQuerier interface { GetFailedWorkspaceBuildsByTemplateID(ctx context.Context, arg GetFailedWorkspaceBuildsByTemplateIDParams) ([]GetFailedWorkspaceBuildsByTemplateIDRow, error) GetFileByHashAndCreator(ctx context.Context, arg GetFileByHashAndCreatorParams) (File, error) GetFileByID(ctx context.Context, id uuid.UUID) (File, error) + GetFileIDByTemplateVersionID(ctx context.Context, templateVersionID uuid.UUID) (uuid.UUID, error) // Get all templates that use a file. GetFileTemplates(ctx context.Context, fileID uuid.UUID) ([]GetFileTemplatesRow, error) // Fetches inbox notifications for a user filtered by templates and targets @@ -298,6 +299,7 @@ type sqlcQuerier interface { GetTemplateVersionByJobID(ctx context.Context, jobID uuid.UUID) (TemplateVersion, error) GetTemplateVersionByTemplateIDAndName(ctx context.Context, arg GetTemplateVersionByTemplateIDAndNameParams) (TemplateVersion, error) GetTemplateVersionParameters(ctx context.Context, templateVersionID uuid.UUID) ([]TemplateVersionParameter, error) + GetTemplateVersionTerraformValues(ctx context.Context, templateVersionID uuid.UUID) (TemplateVersionTerraformValue, error) GetTemplateVersionVariables(ctx context.Context, templateVersionID uuid.UUID) ([]TemplateVersionVariable, error) GetTemplateVersionWorkspaceTags(ctx context.Context, templateVersionID uuid.UUID) ([]TemplateVersionWorkspaceTag, 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 221c9f2c51df6..d160b97f4163d 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -1342,6 +1342,30 @@ func (q *sqlQuerier) GetFileByID(ctx context.Context, id uuid.UUID) (File, error return i, err } +const getFileIDByTemplateVersionID = `-- name: GetFileIDByTemplateVersionID :one +SELECT + files.id +FROM + files +JOIN + provisioner_jobs ON + provisioner_jobs.storage_method = 'file' + AND provisioner_jobs.file_id = files.id +JOIN + template_versions ON template_versions.job_id = provisioner_jobs.id +WHERE + template_versions.id = $1 +LIMIT + 1 +` + +func (q *sqlQuerier) GetFileIDByTemplateVersionID(ctx context.Context, templateVersionID uuid.UUID) (uuid.UUID, error) { + row := q.db.QueryRowContext(ctx, getFileIDByTemplateVersionID, templateVersionID) + var id uuid.UUID + err := row.Scan(&id) + return id, err +} + const getFileTemplates = `-- name: GetFileTemplates :many SELECT files.id AS file_id, @@ -11034,6 +11058,22 @@ func (q *sqlQuerier) UpdateTemplateVersionExternalAuthProvidersByJobID(ctx conte return err } +const getTemplateVersionTerraformValues = `-- name: GetTemplateVersionTerraformValues :one +SELECT + template_version_terraform_values.template_version_id, template_version_terraform_values.updated_at, template_version_terraform_values.cached_plan +FROM + template_version_terraform_values +WHERE + template_version_terraform_values.template_version_id = $1 +` + +func (q *sqlQuerier) GetTemplateVersionTerraformValues(ctx context.Context, templateVersionID uuid.UUID) (TemplateVersionTerraformValue, error) { + row := q.db.QueryRowContext(ctx, getTemplateVersionTerraformValues, templateVersionID) + var i TemplateVersionTerraformValue + err := row.Scan(&i.TemplateVersionID, &i.UpdatedAt, &i.CachedPlan) + return i, err +} + const insertTemplateVersionTerraformValuesByJobID = `-- name: InsertTemplateVersionTerraformValuesByJobID :exec INSERT INTO template_version_terraform_values ( diff --git a/coderd/database/queries/files.sql b/coderd/database/queries/files.sql index 97fded9a6353a..1e5892e425cec 100644 --- a/coderd/database/queries/files.sql +++ b/coderd/database/queries/files.sql @@ -8,6 +8,23 @@ WHERE LIMIT 1; +-- name: GetFileIDByTemplateVersionID :one +SELECT + files.id +FROM + files +JOIN + provisioner_jobs ON + provisioner_jobs.storage_method = 'file' + AND provisioner_jobs.file_id = files.id +JOIN + template_versions ON template_versions.job_id = provisioner_jobs.id +WHERE + template_versions.id = @template_version_id +LIMIT + 1; + + -- name: GetFileByHashAndCreator :one SELECT * diff --git a/coderd/database/queries/templateversionterraformvalues.sql b/coderd/database/queries/templateversionterraformvalues.sql index 42c059d2c556e..61d5e23cf5c5c 100644 --- a/coderd/database/queries/templateversionterraformvalues.sql +++ b/coderd/database/queries/templateversionterraformvalues.sql @@ -1,3 +1,11 @@ +-- name: GetTemplateVersionTerraformValues :one +SELECT + template_version_terraform_values.* +FROM + template_version_terraform_values +WHERE + template_version_terraform_values.template_version_id = @template_version_id; + -- name: InsertTemplateVersionTerraformValuesByJobID :exec INSERT INTO template_version_terraform_values (