diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 9087bef4df8ef..347f22f7aac0f 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -1021,6 +1021,11 @@ func (q *querier) GetGroupsByOrganizationID(ctx context.Context, organizationID return fetchWithPostFilter(q.auth, q.db.GetGroupsByOrganizationID)(ctx, organizationID) } +func (q *querier) GetHealthSettings(ctx context.Context) (string, error) { + // No authz checks + return q.db.GetHealthSettings(ctx) +} + // TODO: We need to create a ProvisionerJob resource type func (q *querier) GetHungProvisionerJobs(ctx context.Context, hungSince time.Time) ([]database.ProvisionerJob, error) { // if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceSystem); err != nil { @@ -2958,6 +2963,13 @@ func (q *querier) UpsertDefaultProxy(ctx context.Context, arg database.UpsertDef return q.db.UpsertDefaultProxy(ctx, arg) } +func (q *querier) UpsertHealthSettings(ctx context.Context, value string) error { + if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceDeploymentValues); err != nil { + return err + } + return q.db.UpsertHealthSettings(ctx, value) +} + func (q *querier) UpsertLastUpdateCheck(ctx context.Context, value string) error { if err := q.authorizeContext(ctx, rbac.ActionUpdate, rbac.ResourceSystem); err != nil { return err diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index aaf8914faa273..d66b5b225b79a 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -159,6 +159,7 @@ type data struct { derpMeshKey string lastUpdateCheck []byte serviceBanner []byte + healthSettings []byte applicationName string logoURL string appSecurityKey string @@ -1771,6 +1772,17 @@ func (q *FakeQuerier) GetGroupsByOrganizationID(_ context.Context, id uuid.UUID) return groups, nil } +func (q *FakeQuerier) GetHealthSettings(_ context.Context) (string, error) { + q.mutex.RLock() + defer q.mutex.RUnlock() + + if q.healthSettings == nil { + return "{}", nil + } + + return string(q.healthSettings), nil +} + func (q *FakeQuerier) GetHungProvisionerJobs(_ context.Context, hungSince time.Time) ([]database.ProvisionerJob, error) { q.mutex.RLock() defer q.mutex.RUnlock() @@ -6790,6 +6802,14 @@ func (q *FakeQuerier) UpsertDefaultProxy(_ context.Context, arg database.UpsertD return nil } +func (q *FakeQuerier) UpsertHealthSettings(_ context.Context, data string) error { + q.mutex.RLock() + defer q.mutex.RUnlock() + + q.healthSettings = []byte(data) + return nil +} + func (q *FakeQuerier) UpsertLastUpdateCheck(_ context.Context, data string) error { q.mutex.Lock() defer q.mutex.Unlock() diff --git a/coderd/database/dbmetrics/dbmetrics.go b/coderd/database/dbmetrics/dbmetrics.go index 115a9bf8b5f20..5d8d0f1030088 100644 --- a/coderd/database/dbmetrics/dbmetrics.go +++ b/coderd/database/dbmetrics/dbmetrics.go @@ -461,6 +461,13 @@ func (m metricsStore) GetGroupsByOrganizationID(ctx context.Context, organizatio return groups, err } +func (m metricsStore) GetHealthSettings(ctx context.Context) (string, error) { + start := time.Now() + r0, r1 := m.s.GetHealthSettings(ctx) + m.queryLatencies.WithLabelValues("GetHealthSettings").Observe(time.Since(start).Seconds()) + return r0, r1 +} + func (m metricsStore) GetHungProvisionerJobs(ctx context.Context, hungSince time.Time) ([]database.ProvisionerJob, error) { start := time.Now() jobs, err := m.s.GetHungProvisionerJobs(ctx, hungSince) @@ -1866,6 +1873,13 @@ func (m metricsStore) UpsertDefaultProxy(ctx context.Context, arg database.Upser return r0 } +func (m metricsStore) UpsertHealthSettings(ctx context.Context, value string) error { + start := time.Now() + r0 := m.s.UpsertHealthSettings(ctx, value) + m.queryLatencies.WithLabelValues("UpsertHealthSettings").Observe(time.Since(start).Seconds()) + return r0 +} + func (m metricsStore) UpsertLastUpdateCheck(ctx context.Context, value string) error { start := time.Now() r0 := m.s.UpsertLastUpdateCheck(ctx, value) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index 3454dda39445d..2bf37350efbaa 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -892,6 +892,21 @@ func (mr *MockStoreMockRecorder) GetGroupsByOrganizationID(arg0, arg1 interface{ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGroupsByOrganizationID", reflect.TypeOf((*MockStore)(nil).GetGroupsByOrganizationID), arg0, arg1) } +// GetHealthSettings mocks base method. +func (m *MockStore) GetHealthSettings(arg0 context.Context) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetHealthSettings", arg0) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetHealthSettings indicates an expected call of GetHealthSettings. +func (mr *MockStoreMockRecorder) GetHealthSettings(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHealthSettings", reflect.TypeOf((*MockStore)(nil).GetHealthSettings), arg0) +} + // GetHungProvisionerJobs mocks base method. func (m *MockStore) GetHungProvisionerJobs(arg0 context.Context, arg1 time.Time) ([]database.ProvisionerJob, error) { m.ctrl.T.Helper() @@ -3917,6 +3932,20 @@ func (mr *MockStoreMockRecorder) UpsertDefaultProxy(arg0, arg1 interface{}) *gom return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertDefaultProxy", reflect.TypeOf((*MockStore)(nil).UpsertDefaultProxy), arg0, arg1) } +// UpsertHealthSettings mocks base method. +func (m *MockStore) UpsertHealthSettings(arg0 context.Context, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpsertHealthSettings", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpsertHealthSettings indicates an expected call of UpsertHealthSettings. +func (mr *MockStoreMockRecorder) UpsertHealthSettings(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertHealthSettings", reflect.TypeOf((*MockStore)(nil).UpsertHealthSettings), arg0, arg1) +} + // UpsertLastUpdateCheck mocks base method. func (m *MockStore) UpsertLastUpdateCheck(arg0 context.Context, arg1 string) error { m.ctrl.T.Helper() diff --git a/coderd/database/querier.go b/coderd/database/querier.go index cafc1d0249414..10c6be43a8179 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -102,6 +102,7 @@ type sqlcQuerier interface { // If it is the "Everyone" group, then we need to check the organization_members table. GetGroupMembers(ctx context.Context, groupID uuid.UUID) ([]User, error) GetGroupsByOrganizationID(ctx context.Context, organizationID uuid.UUID) ([]Group, error) + GetHealthSettings(ctx context.Context) (string, error) GetHungProvisionerJobs(ctx context.Context, updatedAt time.Time) ([]ProvisionerJob, error) GetLastUpdateCheck(ctx context.Context) (string, error) GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) (WorkspaceBuild, error) @@ -356,6 +357,7 @@ type sqlcQuerier interface { // So we need to store it's configuration here for display purposes. // The functional values are immutable and controlled implicitly. UpsertDefaultProxy(ctx context.Context, arg UpsertDefaultProxyParams) error + UpsertHealthSettings(ctx context.Context, value string) error UpsertLastUpdateCheck(ctx context.Context, value string) error UpsertLogoURL(ctx context.Context, value string) error UpsertOAuthSigningKey(ctx context.Context, value string) error diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 09f36c69b848b..00ca22b0b34db 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -4344,6 +4344,18 @@ func (q *sqlQuerier) GetDeploymentID(ctx context.Context) (string, error) { return value, err } +const getHealthSettings = `-- name: GetHealthSettings :one +SELECT + COALESCE((SELECT value FROM site_configs WHERE key = 'health_settings'), '{}') :: text AS health_settings +` + +func (q *sqlQuerier) GetHealthSettings(ctx context.Context) (string, error) { + row := q.db.QueryRowContext(ctx, getHealthSettings) + var health_settings string + err := row.Scan(&health_settings) + return health_settings, err +} + const getLastUpdateCheck = `-- name: GetLastUpdateCheck :one SELECT value FROM site_configs WHERE key = 'last_update_check' ` @@ -4449,6 +4461,16 @@ func (q *sqlQuerier) UpsertDefaultProxy(ctx context.Context, arg UpsertDefaultPr return err } +const upsertHealthSettings = `-- name: UpsertHealthSettings :exec +INSERT INTO site_configs (key, value) VALUES ('health_settings', $1) +ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'health_settings' +` + +func (q *sqlQuerier) UpsertHealthSettings(ctx context.Context, value string) error { + _, err := q.db.ExecContext(ctx, upsertHealthSettings, value) + return err +} + const upsertLastUpdateCheck = `-- name: UpsertLastUpdateCheck :exec INSERT INTO site_configs (key, value) VALUES ('last_update_check', $1) ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'last_update_check' diff --git a/coderd/database/queries/siteconfig.sql b/coderd/database/queries/siteconfig.sql index 602b82d984180..a432b71e3a91d 100644 --- a/coderd/database/queries/siteconfig.sql +++ b/coderd/database/queries/siteconfig.sql @@ -70,3 +70,12 @@ SELECT value FROM site_configs WHERE key = 'oauth_signing_key'; -- name: UpsertOAuthSigningKey :exec INSERT INTO site_configs (key, value) VALUES ('oauth_signing_key', $1) ON CONFLICT (key) DO UPDATE set value = $1 WHERE site_configs.key = 'oauth_signing_key'; + +-- name: GetHealthSettings :one +SELECT + COALESCE((SELECT value FROM site_configs WHERE key = 'health_settings'), '{}') :: text AS health_settings +; + +-- name: UpsertHealthSettings :exec +INSERT INTO site_configs (key, value) VALUES ('health_settings', $1) +ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'health_settings';