From 99c9d27a4538092beb969181717d4d171f1317ca Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Wed, 27 Sep 2023 13:34:19 +0200 Subject: [PATCH 1/6] feat: expose application name via Appearance API --- coderd/apidoc/docs.go | 6 ++++++ coderd/apidoc/swagger.json | 6 ++++++ coderd/database/dbauthz/dbauthz.go | 12 +++++++++++ coderd/database/dbfake/dbfake.go | 20 ++++++++++++++++++ coderd/database/dbmetrics/dbmetrics.go | 14 +++++++++++++ coderd/database/dbmock/dbmock.go | 29 ++++++++++++++++++++++++++ coderd/database/querier.go | 2 ++ coderd/database/queries.sql.go | 21 +++++++++++++++++++ coderd/database/queries/siteconfig.sql | 7 +++++++ codersdk/deployment.go | 12 ++++++----- docs/api/enterprise.md | 3 +++ docs/api/schemas.md | 22 +++++++++++-------- enterprise/coderd/appearance.go | 8 +++++++ site/src/api/typesGenerated.ts | 2 ++ 14 files changed, 150 insertions(+), 14 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 6053d3bbb8813..f1a9242c8f2ab 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -7023,6 +7023,9 @@ const docTemplate = `{ "codersdk.AppearanceConfig": { "type": "object", "properties": { + "application_name": { + "type": "string" + }, "logo_url": { "type": "string" }, @@ -10201,6 +10204,9 @@ const docTemplate = `{ "codersdk.UpdateAppearanceConfig": { "type": "object", "properties": { + "application_name": { + "type": "string" + }, "logo_url": { "type": "string" }, diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index a7c589c984620..52ee8dbf03e8b 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -6233,6 +6233,9 @@ "codersdk.AppearanceConfig": { "type": "object", "properties": { + "application_name": { + "type": "string" + }, "logo_url": { "type": "string" }, @@ -9225,6 +9228,9 @@ "codersdk.UpdateAppearanceConfig": { "type": "object", "properties": { + "application_name": { + "type": "string" + }, "logo_url": { "type": "string" }, diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index a8d906fbf83c8..e7a2e04e68458 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -851,6 +851,11 @@ func (q *querier) GetAppSecurityKey(ctx context.Context) (string, error) { return q.db.GetAppSecurityKey(ctx) } +func (q *querier) GetApplicationName(ctx context.Context) (string, error) { + // No authz checks + return q.db.GetLogoURL(ctx) +} + func (q *querier) GetAuditLogsOffset(ctx context.Context, arg database.GetAuditLogsOffsetParams) ([]database.GetAuditLogsOffsetRow, error) { // To optimize audit logs, we only check the global audit log permission once. // This is because we expect a large unbounded set of audit logs, and applying a SQL @@ -2808,6 +2813,13 @@ func (q *querier) UpsertAppSecurityKey(ctx context.Context, data string) error { return q.db.UpsertAppSecurityKey(ctx, data) } +func (q *querier) UpsertApplicationName(ctx context.Context, value string) error { + if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceDeploymentValues); err != nil { + return err + } + return q.db.UpsertApplicationName(ctx, value) +} + func (q *querier) UpsertDefaultProxy(ctx context.Context, arg database.UpsertDefaultProxyParams) error { if err := q.authorizeContext(ctx, rbac.ActionUpdate, rbac.ResourceSystem); err != nil { return err diff --git a/coderd/database/dbfake/dbfake.go b/coderd/database/dbfake/dbfake.go index c7cc31ef243bb..172b9e75b771c 100644 --- a/coderd/database/dbfake/dbfake.go +++ b/coderd/database/dbfake/dbfake.go @@ -160,6 +160,7 @@ type data struct { derpMeshKey string lastUpdateCheck []byte serviceBanner []byte + applicationName string logoURL string appSecurityKey string oauthSigningKey string @@ -1128,6 +1129,17 @@ func (q *FakeQuerier) GetAppSecurityKey(_ context.Context) (string, error) { return q.appSecurityKey, nil } +func (q *FakeQuerier) GetApplicationName(ctx context.Context) (string, error) { + q.mutex.RLock() + defer q.mutex.RUnlock() + + if q.applicationName == "" { + return "", sql.ErrNoRows + } + + return q.applicationName, nil +} + func (q *FakeQuerier) GetAuditLogsOffset(_ context.Context, arg database.GetAuditLogsOffsetParams) ([]database.GetAuditLogsOffsetRow, error) { if err := validateDatabaseType(arg); err != nil { return nil, err @@ -6319,6 +6331,14 @@ func (q *FakeQuerier) UpsertAppSecurityKey(_ context.Context, data string) error return nil } +func (q *FakeQuerier) UpsertApplicationName(ctx context.Context, data string) error { + q.mutex.RLock() + defer q.mutex.RUnlock() + + q.applicationName = data + return nil +} + func (q *FakeQuerier) UpsertDefaultProxy(_ context.Context, arg database.UpsertDefaultProxyParams) error { q.defaultProxyDisplayName = arg.DisplayName q.defaultProxyIconURL = arg.IconUrl diff --git a/coderd/database/dbmetrics/dbmetrics.go b/coderd/database/dbmetrics/dbmetrics.go index a1aac01a5bff1..16f65ad84b35d 100644 --- a/coderd/database/dbmetrics/dbmetrics.go +++ b/coderd/database/dbmetrics/dbmetrics.go @@ -293,6 +293,13 @@ func (m metricsStore) GetAppSecurityKey(ctx context.Context) (string, error) { return key, err } +func (m metricsStore) GetApplicationName(ctx context.Context) (string, error) { + start := time.Now() + r0, r1 := m.s.GetApplicationName(ctx) + m.queryLatencies.WithLabelValues("GetApplicationName").Observe(time.Since(start).Seconds()) + return r0, r1 +} + func (m metricsStore) GetAuditLogsOffset(ctx context.Context, arg database.GetAuditLogsOffsetParams) ([]database.GetAuditLogsOffsetRow, error) { start := time.Now() rows, err := m.s.GetAuditLogsOffset(ctx, arg) @@ -1761,6 +1768,13 @@ func (m metricsStore) UpsertAppSecurityKey(ctx context.Context, value string) er return r0 } +func (m metricsStore) UpsertApplicationName(ctx context.Context, value string) error { + start := time.Now() + r0 := m.s.UpsertApplicationName(ctx, value) + m.queryLatencies.WithLabelValues("UpsertApplicationName").Observe(time.Since(start).Seconds()) + return r0 +} + func (m metricsStore) UpsertDefaultProxy(ctx context.Context, arg database.UpsertDefaultProxyParams) error { start := time.Now() r0 := m.s.UpsertDefaultProxy(ctx, arg) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index edd7dc567742b..3d64a27aaa351 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -488,6 +488,21 @@ func (mr *MockStoreMockRecorder) GetAppSecurityKey(arg0 interface{}) *gomock.Cal return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAppSecurityKey", reflect.TypeOf((*MockStore)(nil).GetAppSecurityKey), arg0) } +// GetApplicationName mocks base method. +func (m *MockStore) GetApplicationName(arg0 context.Context) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetApplicationName", arg0) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetApplicationName indicates an expected call of GetApplicationName. +func (mr *MockStoreMockRecorder) GetApplicationName(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetApplicationName", reflect.TypeOf((*MockStore)(nil).GetApplicationName), arg0) +} + // GetAuditLogsOffset mocks base method. func (m *MockStore) GetAuditLogsOffset(arg0 context.Context, arg1 database.GetAuditLogsOffsetParams) ([]database.GetAuditLogsOffsetRow, error) { m.ctrl.T.Helper() @@ -3698,6 +3713,20 @@ func (mr *MockStoreMockRecorder) UpsertAppSecurityKey(arg0, arg1 interface{}) *g return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertAppSecurityKey", reflect.TypeOf((*MockStore)(nil).UpsertAppSecurityKey), arg0, arg1) } +// UpsertApplicationName mocks base method. +func (m *MockStore) UpsertApplicationName(arg0 context.Context, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpsertApplicationName", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpsertApplicationName indicates an expected call of UpsertApplicationName. +func (mr *MockStoreMockRecorder) UpsertApplicationName(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertApplicationName", reflect.TypeOf((*MockStore)(nil).UpsertApplicationName), arg0, arg1) +} + // UpsertDefaultProxy mocks base method. func (m *MockStore) UpsertDefaultProxy(arg0 context.Context, arg1 database.UpsertDefaultProxyParams) error { m.ctrl.T.Helper() diff --git a/coderd/database/querier.go b/coderd/database/querier.go index a9b3489d1f86b..7cfba57762245 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -63,6 +63,7 @@ type sqlcQuerier interface { GetAllTailnetAgents(ctx context.Context) ([]TailnetAgent, error) GetAllTailnetClients(ctx context.Context) ([]GetAllTailnetClientsRow, error) GetAppSecurityKey(ctx context.Context) (string, error) + GetApplicationName(ctx context.Context) (string, error) // GetAuditLogsBefore retrieves `row_limit` number of audit logs before the provided // ID. GetAuditLogsOffset(ctx context.Context, arg GetAuditLogsOffsetParams) ([]GetAuditLogsOffsetRow, error) @@ -329,6 +330,7 @@ type sqlcQuerier interface { UpdateWorkspaceTTL(ctx context.Context, arg UpdateWorkspaceTTLParams) error UpdateWorkspacesDormantDeletingAtByTemplateID(ctx context.Context, arg UpdateWorkspacesDormantDeletingAtByTemplateIDParams) error UpsertAppSecurityKey(ctx context.Context, value string) error + UpsertApplicationName(ctx context.Context, value string) error // The default proxy is implied and not actually stored in the database. // So we need to store it's configuration here for display purposes. // The functional values are immutable and controlled implicitly. diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 70930cb120038..591fc5d36dd78 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -4066,6 +4066,17 @@ func (q *sqlQuerier) GetAppSecurityKey(ctx context.Context) (string, error) { return value, err } +const getApplicationName = `-- name: GetApplicationName :one +SELECT value FROM site_configs WHERE key = 'application_name' +` + +func (q *sqlQuerier) GetApplicationName(ctx context.Context) (string, error) { + row := q.db.QueryRowContext(ctx, getApplicationName) + var value string + err := row.Scan(&value) + return value, err +} + const getDERPMeshKey = `-- name: GetDERPMeshKey :one SELECT value FROM site_configs WHERE key = 'derp_mesh_key' ` @@ -4178,6 +4189,16 @@ func (q *sqlQuerier) UpsertAppSecurityKey(ctx context.Context, value string) err return err } +const upsertApplicationName = `-- name: UpsertApplicationName :exec +INSERT INTO site_configs (key, value) VALUES ('application_name', $1) +ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'application_name' +` + +func (q *sqlQuerier) UpsertApplicationName(ctx context.Context, value string) error { + _, err := q.db.ExecContext(ctx, upsertApplicationName, value) + return err +} + const upsertDefaultProxy = `-- name: UpsertDefaultProxy :exec INSERT INTO site_configs (key, value) VALUES diff --git a/coderd/database/queries/siteconfig.sql b/coderd/database/queries/siteconfig.sql index b165e0dd7d0f1..602b82d984180 100644 --- a/coderd/database/queries/siteconfig.sql +++ b/coderd/database/queries/siteconfig.sql @@ -50,6 +50,13 @@ ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'logo_url'; -- name: GetLogoURL :one SELECT value FROM site_configs WHERE key = 'logo_url'; +-- name: UpsertApplicationName :exec +INSERT INTO site_configs (key, value) VALUES ('application_name', $1) +ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'application_name'; + +-- name: GetApplicationName :one +SELECT value FROM site_configs WHERE key = 'application_name'; + -- name: GetAppSecurityKey :one SELECT value FROM site_configs WHERE key = 'app_signing_key'; diff --git a/codersdk/deployment.go b/codersdk/deployment.go index a131fdf295812..a13b4a1379fd6 100644 --- a/codersdk/deployment.go +++ b/codersdk/deployment.go @@ -1832,14 +1832,16 @@ func (c *Client) DeploymentStats(ctx context.Context) (DeploymentStats, error) { } type AppearanceConfig struct { - LogoURL string `json:"logo_url"` - ServiceBanner ServiceBannerConfig `json:"service_banner"` - SupportLinks []LinkConfig `json:"support_links,omitempty"` + ApplicationName string `json:"application_name"` + LogoURL string `json:"logo_url"` + ServiceBanner ServiceBannerConfig `json:"service_banner"` + SupportLinks []LinkConfig `json:"support_links,omitempty"` } type UpdateAppearanceConfig struct { - LogoURL string `json:"logo_url"` - ServiceBanner ServiceBannerConfig `json:"service_banner"` + ApplicationName string `json:"application_name"` + LogoURL string `json:"logo_url"` + ServiceBanner ServiceBannerConfig `json:"service_banner"` } type ServiceBannerConfig struct { diff --git a/docs/api/enterprise.md b/docs/api/enterprise.md index 9be7223c07968..743fbc19fd532 100644 --- a/docs/api/enterprise.md +++ b/docs/api/enterprise.md @@ -19,6 +19,7 @@ curl -X GET http://coder-server:8080/api/v2/appearance \ ```json { + "application_name": "string", "logo_url": "string", "service_banner": { "background_color": "string", @@ -61,6 +62,7 @@ curl -X PUT http://coder-server:8080/api/v2/appearance \ ```json { + "application_name": "string", "logo_url": "string", "service_banner": { "background_color": "string", @@ -82,6 +84,7 @@ curl -X PUT http://coder-server:8080/api/v2/appearance \ ```json { + "application_name": "string", "logo_url": "string", "service_banner": { "background_color": "string", diff --git a/docs/api/schemas.md b/docs/api/schemas.md index 154433f1a7063..635a4ad5e8ee1 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -952,6 +952,7 @@ _None_ ```json { + "application_name": "string", "logo_url": "string", "service_banner": { "background_color": "string", @@ -970,11 +971,12 @@ _None_ ### Properties -| Name | Type | Required | Restrictions | Description | -| ---------------- | ------------------------------------------------------------ | -------- | ------------ | ----------- | -| `logo_url` | string | false | | | -| `service_banner` | [codersdk.ServiceBannerConfig](#codersdkservicebannerconfig) | false | | | -| `support_links` | array of [codersdk.LinkConfig](#codersdklinkconfig) | false | | | +| Name | Type | Required | Restrictions | Description | +| ------------------ | ------------------------------------------------------------ | -------- | ------------ | ----------- | +| `application_name` | string | false | | | +| `logo_url` | string | false | | | +| `service_banner` | [codersdk.ServiceBannerConfig](#codersdkservicebannerconfig) | false | | | +| `support_links` | array of [codersdk.LinkConfig](#codersdklinkconfig) | false | | | ## codersdk.AssignableRoles @@ -4950,6 +4952,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in ```json { + "application_name": "string", "logo_url": "string", "service_banner": { "background_color": "string", @@ -4961,10 +4964,11 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in ### Properties -| Name | Type | Required | Restrictions | Description | -| ---------------- | ------------------------------------------------------------ | -------- | ------------ | ----------- | -| `logo_url` | string | false | | | -| `service_banner` | [codersdk.ServiceBannerConfig](#codersdkservicebannerconfig) | false | | | +| Name | Type | Required | Restrictions | Description | +| ------------------ | ------------------------------------------------------------ | -------- | ------------ | ----------- | +| `application_name` | string | false | | | +| `logo_url` | string | false | | | +| `service_banner` | [codersdk.ServiceBannerConfig](#codersdkservicebannerconfig) | false | | | ## codersdk.UpdateCheckResponse diff --git a/enterprise/coderd/appearance.go b/enterprise/coderd/appearance.go index 74d4bcb769515..725f5e7a03fd8 100644 --- a/enterprise/coderd/appearance.go +++ b/enterprise/coderd/appearance.go @@ -169,6 +169,14 @@ func (api *API) putAppearance(rw http.ResponseWriter, r *http.Request) { return } + err = api.Database.UpsertApplicationName(ctx, appearance.ApplicationName) + if err != nil { + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ + Message: fmt.Sprintf("database error: %+v", err), + }) + return + } + err = api.Database.UpsertLogoURL(ctx, appearance.LogoURL) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 6d98abee196bc..639ba1e837b4d 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -46,6 +46,7 @@ export interface AppHostResponse { // From codersdk/deployment.go export interface AppearanceConfig { + readonly application_name: string; readonly logo_url: string; readonly service_banner: ServiceBannerConfig; readonly support_links?: LinkConfig[]; @@ -1091,6 +1092,7 @@ export interface UpdateActiveTemplateVersion { // From codersdk/deployment.go export interface UpdateAppearanceConfig { + readonly application_name: string; readonly logo_url: string; readonly service_banner: ServiceBannerConfig; } From 663153e0e1e9e616138f579efd1ca16d67ccd92c Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Wed, 27 Sep 2023 13:52:04 +0200 Subject: [PATCH 2/6] fix: lint --- coderd/database/dbfake/dbfake.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/database/dbfake/dbfake.go b/coderd/database/dbfake/dbfake.go index 172b9e75b771c..55d412965a5e9 100644 --- a/coderd/database/dbfake/dbfake.go +++ b/coderd/database/dbfake/dbfake.go @@ -6331,7 +6331,7 @@ func (q *FakeQuerier) UpsertAppSecurityKey(_ context.Context, data string) error return nil } -func (q *FakeQuerier) UpsertApplicationName(ctx context.Context, data string) error { +func (q *FakeQuerier) UpsertApplicationName(_ context.Context, data string) error { q.mutex.RLock() defer q.mutex.RUnlock() From 7352b628c2ab3a4c8b4387fb4320e28a84de7e41 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Wed, 27 Sep 2023 14:01:14 +0200 Subject: [PATCH 3/6] fix: lint --- coderd/database/dbfake/dbfake.go | 2 +- site/src/api/api.ts | 1 + .../AppearanceSettingsPageView.stories.tsx | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/coderd/database/dbfake/dbfake.go b/coderd/database/dbfake/dbfake.go index 55d412965a5e9..fcc4f76e2c98b 100644 --- a/coderd/database/dbfake/dbfake.go +++ b/coderd/database/dbfake/dbfake.go @@ -1129,7 +1129,7 @@ func (q *FakeQuerier) GetAppSecurityKey(_ context.Context) (string, error) { return q.appSecurityKey, nil } -func (q *FakeQuerier) GetApplicationName(ctx context.Context) (string, error) { +func (q *FakeQuerier) GetApplicationName(_ context.Context) (string, error) { q.mutex.RLock() defer q.mutex.RUnlock() diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 580c7011ea13f..d33484c43371a 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -1088,6 +1088,7 @@ export const getAppearance = async (): Promise => { } catch (ex) { if (axios.isAxiosError(ex) && ex.response?.status === 404) { return { + application_name: "", logo_url: "", service_banner: { enabled: false, diff --git a/site/src/pages/DeploySettingsPage/AppearanceSettingsPage/AppearanceSettingsPageView.stories.tsx b/site/src/pages/DeploySettingsPage/AppearanceSettingsPage/AppearanceSettingsPageView.stories.tsx index f628aa82c9fa6..bd45ec90d1dfb 100644 --- a/site/src/pages/DeploySettingsPage/AppearanceSettingsPage/AppearanceSettingsPageView.stories.tsx +++ b/site/src/pages/DeploySettingsPage/AppearanceSettingsPage/AppearanceSettingsPageView.stories.tsx @@ -6,6 +6,7 @@ const meta: Meta = { component: AppearanceSettingsPageView, args: { appearance: { + application_name: "", logo_url: "https://github.com/coder.png", service_banner: { enabled: true, From 945828de60e5fd4a4f365881e271b6409bb296c6 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Wed, 27 Sep 2023 14:21:41 +0200 Subject: [PATCH 4/6] entitites --- site/src/testHelpers/entities.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 97a1c86ba5093..a15d43c402857 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -2152,6 +2152,7 @@ export const MockDeploymentConfig: DeploymentConfig = { }; export const MockAppearanceConfig: TypesGen.AppearanceConfig = { + application_name: "", logo_url: "", service_banner: { enabled: false, From 84d0eeb182418a45c39cd157c22e9431b5ed61d2 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Wed, 27 Sep 2023 15:13:49 +0200 Subject: [PATCH 5/6] Unit tests --- coderd/database/dbauthz/dbauthz.go | 2 +- enterprise/coderd/appearance.go | 11 +++++++++- enterprise/coderd/appearance_test.go | 31 ++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index e7a2e04e68458..c751d75eab650 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -853,7 +853,7 @@ func (q *querier) GetAppSecurityKey(ctx context.Context) (string, error) { func (q *querier) GetApplicationName(ctx context.Context) (string, error) { // No authz checks - return q.db.GetLogoURL(ctx) + return q.db.GetApplicationName(ctx) } func (q *querier) GetAuditLogsOffset(ctx context.Context, arg database.GetAuditLogsOffsetParams) ([]database.GetAuditLogsOffsetRow, error) { diff --git a/enterprise/coderd/appearance.go b/enterprise/coderd/appearance.go index 725f5e7a03fd8..3a989f73bc03b 100644 --- a/enterprise/coderd/appearance.go +++ b/enterprise/coderd/appearance.go @@ -67,8 +67,16 @@ func (api *API) fetchAppearanceConfig(ctx context.Context) (codersdk.AppearanceC } var eg errgroup.Group + var applicationName string var logoURL string var serviceBannerJSON string + eg.Go(func() (err error) { + applicationName, err = api.Database.GetApplicationName(ctx) + if err != nil && !errors.Is(err, sql.ErrNoRows) { + return xerrors.Errorf("get application name: %w", err) + } + return nil + }) eg.Go(func() (err error) { logoURL, err = api.Database.GetLogoURL(ctx) if err != nil && !errors.Is(err, sql.ErrNoRows) { @@ -89,7 +97,8 @@ func (api *API) fetchAppearanceConfig(ctx context.Context) (codersdk.AppearanceC } cfg := codersdk.AppearanceConfig{ - LogoURL: logoURL, + ApplicationName: applicationName, + LogoURL: logoURL, } if serviceBannerJSON != "" { err = json.Unmarshal([]byte(serviceBannerJSON), &cfg.ServiceBanner) diff --git a/enterprise/coderd/appearance_test.go b/enterprise/coderd/appearance_test.go index 8ee2c071377d0..5e6768374db4c 100644 --- a/enterprise/coderd/appearance_test.go +++ b/enterprise/coderd/appearance_test.go @@ -20,6 +20,37 @@ import ( "github.com/coder/coder/v2/testutil" ) +func TestCustomLogoAndCompanyName(t *testing.T) { + t.Parallel() + + // Prepare enterprise deployment + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) + defer cancel() + + adminClient, _ := coderdenttest.New(t, &coderdenttest.Options{DontAddLicense: true}) + coderdenttest.AddLicense(t, adminClient, coderdenttest.LicenseOptions{ + Features: license.Features{ + codersdk.FeatureAppearance: 1, + }, + }) + + // Update logo and application name + uac := codersdk.UpdateAppearanceConfig{ + ApplicationName: "ACME Ltd", + LogoURL: "http://logo-url/file.png", + } + + err := adminClient.UpdateAppearance(ctx, uac) + require.NoError(t, err) + + // Verify update + got, err := adminClient.Appearance(ctx) + require.NoError(t, err) + + require.Equal(t, uac.ApplicationName, got.ApplicationName) + require.Equal(t, uac.LogoURL, got.LogoURL) +} + func TestServiceBanners(t *testing.T) { t.Parallel() From b55cc59d254fb833473636c6b3d743077fcadaf5 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Wed, 27 Sep 2023 15:54:05 +0200 Subject: [PATCH 6/6] Improve appearance errors --- enterprise/coderd/appearance.go | 18 +++++++++++------- enterprise/coderd/appearance_test.go | 8 ++++++++ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/enterprise/coderd/appearance.go b/enterprise/coderd/appearance.go index 3a989f73bc03b..aceeec9ef16db 100644 --- a/enterprise/coderd/appearance.go +++ b/enterprise/coderd/appearance.go @@ -6,7 +6,6 @@ import ( "encoding/hex" "encoding/json" "errors" - "fmt" "net/http" "golang.org/x/sync/errgroup" @@ -120,7 +119,7 @@ func (api *API) fetchAppearanceConfig(ctx context.Context) (codersdk.AppearanceC func validateHexColor(color string) error { if len(color) != 7 { - return xerrors.New("expected 7 characters") + return xerrors.New("expected # prefix and 6 characters") } if color[0] != '#' { return xerrors.New("no # prefix") @@ -156,7 +155,8 @@ func (api *API) putAppearance(rw http.ResponseWriter, r *http.Request) { if appearance.ServiceBanner.Enabled { if err := validateHexColor(appearance.ServiceBanner.BackgroundColor); err != nil { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: fmt.Sprintf("parse color: %+v", err), + Message: "Invalid color format", + Detail: err.Error(), }) return } @@ -165,7 +165,8 @@ func (api *API) putAppearance(rw http.ResponseWriter, r *http.Request) { serviceBannerJSON, err := json.Marshal(appearance.ServiceBanner) if err != nil { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: fmt.Sprintf("marshal banner: %+v", err), + Message: "Unable to marshal service banner", + Detail: err.Error(), }) return } @@ -173,7 +174,8 @@ func (api *API) putAppearance(rw http.ResponseWriter, r *http.Request) { err = api.Database.UpsertServiceBanner(ctx, string(serviceBannerJSON)) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: fmt.Sprintf("database error: %+v", err), + Message: "Unable to set service banner", + Detail: err.Error(), }) return } @@ -181,7 +183,8 @@ func (api *API) putAppearance(rw http.ResponseWriter, r *http.Request) { err = api.Database.UpsertApplicationName(ctx, appearance.ApplicationName) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: fmt.Sprintf("database error: %+v", err), + Message: "Unable to set application name", + Detail: err.Error(), }) return } @@ -189,7 +192,8 @@ func (api *API) putAppearance(rw http.ResponseWriter, r *http.Request) { err = api.Database.UpsertLogoURL(ctx, appearance.LogoURL) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: fmt.Sprintf("database error: %+v", err), + Message: "Unable to set logo URL", + Detail: err.Error(), }) return } diff --git a/enterprise/coderd/appearance_test.go b/enterprise/coderd/appearance_test.go index 5e6768374db4c..e66e664ac7de2 100644 --- a/enterprise/coderd/appearance_test.go +++ b/enterprise/coderd/appearance_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/google/uuid" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/coder/coder/v2/cli/clibase" @@ -108,6 +109,13 @@ func TestServiceBanners(t *testing.T) { wantBanner.ServiceBanner.BackgroundColor = "#bad color" err = adminClient.UpdateAppearance(ctx, wantBanner) require.Error(t, err) + + var sdkErr *codersdk.Error + if assert.ErrorAs(t, err, &sdkErr) { + assert.Equal(t, http.StatusBadRequest, sdkErr.StatusCode()) + assert.Contains(t, sdkErr.Message, "Invalid color format") + assert.Contains(t, sdkErr.Detail, "expected # prefix and 6 characters") + } }) t.Run("Agent", func(t *testing.T) {