mirror of
https://github.com/coder/coder.git
synced 2025-07-13 21:36:50 +00:00
chore: make default workspace proxy editable (#7903)
* chore: add editing the default workspace proxy
This commit is contained in:
@ -369,6 +369,11 @@ func (q *querier) DeleteLicense(ctx context.Context, id int32) (int32, error) {
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (q *querier) GetDefaultProxyConfig(ctx context.Context) (database.GetDefaultProxyConfigRow, error) {
|
||||
// No authz checks
|
||||
return q.db.GetDefaultProxyConfig(ctx)
|
||||
}
|
||||
|
||||
func (q *querier) GetDeploymentID(ctx context.Context) (string, error) {
|
||||
// No authz checks
|
||||
return q.db.GetDeploymentID(ctx)
|
||||
|
@ -335,6 +335,12 @@ func (s *MethodTestSuite) TestLicense() {
|
||||
s.Run("GetDeploymentID", s.Subtest(func(db database.Store, check *expects) {
|
||||
check.Args().Asserts().Returns("")
|
||||
}))
|
||||
s.Run("GetDefaultProxyConfig", s.Subtest(func(db database.Store, check *expects) {
|
||||
check.Args().Asserts().Returns(database.GetDefaultProxyConfigRow{
|
||||
DisplayName: "Default",
|
||||
IconUrl: "/emojis/1f3e1.png",
|
||||
})
|
||||
}))
|
||||
s.Run("GetLogoURL", s.Subtest(func(db database.Store, check *expects) {
|
||||
err := db.UpsertLogoURL(context.Background(), "value")
|
||||
require.NoError(s.T(), err)
|
||||
|
@ -431,3 +431,10 @@ func (q *querier) GetWorkspaceProxyByHostname(ctx context.Context, params databa
|
||||
}
|
||||
return q.db.GetWorkspaceProxyByHostname(ctx, params)
|
||||
}
|
||||
|
||||
func (q *querier) UpsertDefaultProxy(ctx context.Context, arg database.UpsertDefaultProxyParams) error {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionUpdate, rbac.ResourceSystem); err != nil {
|
||||
return err
|
||||
}
|
||||
return q.db.UpsertDefaultProxy(ctx, arg)
|
||||
}
|
||||
|
@ -25,6 +25,9 @@ func (s *MethodTestSuite) TestSystemFunctions() {
|
||||
LoginType: database.LoginTypeGithub,
|
||||
}).Asserts(rbac.ResourceSystem, rbac.ActionUpdate).Returns(l)
|
||||
}))
|
||||
s.Run("UpsertDefaultProxy", s.Subtest(func(db database.Store, check *expects) {
|
||||
check.Args(database.UpsertDefaultProxyParams{}).Asserts(rbac.ResourceSystem, rbac.ActionUpdate).Returns()
|
||||
}))
|
||||
s.Run("GetUserLinkByLinkedID", s.Subtest(func(db database.Store, check *expects) {
|
||||
l := dbgen.UserLink(s.T(), db, database.UserLink{})
|
||||
check.Args(l.LinkedID).Asserts(rbac.ResourceSystem, rbac.ActionRead).Returns(l)
|
||||
|
@ -41,7 +41,7 @@ var errDuplicateKey = &pq.Error{
|
||||
|
||||
// New returns an in-memory fake of the database.
|
||||
func New() database.Store {
|
||||
return &fakeQuerier{
|
||||
q := &fakeQuerier{
|
||||
mutex: &sync.RWMutex{},
|
||||
data: &data{
|
||||
apiKeys: make([]database.APIKey, 0),
|
||||
@ -73,6 +73,9 @@ func New() database.Store {
|
||||
locks: map[int64]struct{}{},
|
||||
},
|
||||
}
|
||||
q.defaultProxyDisplayName = "Default"
|
||||
q.defaultProxyIconURL = "/emojis/1f3e1.png"
|
||||
return q
|
||||
}
|
||||
|
||||
type rwMutex interface {
|
||||
@ -144,14 +147,16 @@ type data struct {
|
||||
|
||||
// Locks is a map of lock names. Any keys within the map are currently
|
||||
// locked.
|
||||
locks map[int64]struct{}
|
||||
deploymentID string
|
||||
derpMeshKey string
|
||||
lastUpdateCheck []byte
|
||||
serviceBanner []byte
|
||||
logoURL string
|
||||
appSecurityKey string
|
||||
lastLicenseID int32
|
||||
locks map[int64]struct{}
|
||||
deploymentID string
|
||||
derpMeshKey string
|
||||
lastUpdateCheck []byte
|
||||
serviceBanner []byte
|
||||
logoURL string
|
||||
appSecurityKey string
|
||||
lastLicenseID int32
|
||||
defaultProxyDisplayName string
|
||||
defaultProxyIconURL string
|
||||
}
|
||||
|
||||
func validateDatabaseTypeWithValid(v reflect.Value) (handled bool, err error) {
|
||||
@ -5171,3 +5176,16 @@ func isNull(v interface{}) bool {
|
||||
func isNotNull(v interface{}) bool {
|
||||
return reflect.ValueOf(v).FieldByName("Valid").Bool()
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) GetDefaultProxyConfig(_ context.Context) (database.GetDefaultProxyConfigRow, error) {
|
||||
return database.GetDefaultProxyConfigRow{
|
||||
DisplayName: q.defaultProxyDisplayName,
|
||||
IconUrl: q.defaultProxyIconURL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) UpsertDefaultProxy(_ context.Context, arg database.UpsertDefaultProxyParams) error {
|
||||
q.defaultProxyDisplayName = arg.DisplayName
|
||||
q.defaultProxyIconURL = arg.IconUrl
|
||||
return nil
|
||||
}
|
||||
|
@ -1518,3 +1518,17 @@ func (m metricsStore) GetAuthorizedUserCount(ctx context.Context, arg database.G
|
||||
m.queryLatencies.WithLabelValues("GetAuthorizedUserCount").Observe(time.Since(start).Seconds())
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (m metricsStore) UpsertDefaultProxy(ctx context.Context, arg database.UpsertDefaultProxyParams) error {
|
||||
start := time.Now()
|
||||
err := m.s.UpsertDefaultProxy(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("UpsertDefaultProxy").Observe(time.Since(start).Seconds())
|
||||
return err
|
||||
}
|
||||
|
||||
func (m metricsStore) GetDefaultProxyConfig(ctx context.Context) (database.GetDefaultProxyConfigRow, error) {
|
||||
start := time.Now()
|
||||
resp, err := m.s.GetDefaultProxyConfig(ctx)
|
||||
m.queryLatencies.WithLabelValues("GetDefaultProxyConfig").Observe(time.Since(start).Seconds())
|
||||
return resp, err
|
||||
}
|
||||
|
@ -418,6 +418,21 @@ func (mr *MockStoreMockRecorder) GetDERPMeshKey(arg0 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDERPMeshKey", reflect.TypeOf((*MockStore)(nil).GetDERPMeshKey), arg0)
|
||||
}
|
||||
|
||||
// GetDefaultProxyConfig mocks base method.
|
||||
func (m *MockStore) GetDefaultProxyConfig(arg0 context.Context) (database.GetDefaultProxyConfigRow, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetDefaultProxyConfig", arg0)
|
||||
ret0, _ := ret[0].(database.GetDefaultProxyConfigRow)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetDefaultProxyConfig indicates an expected call of GetDefaultProxyConfig.
|
||||
func (mr *MockStoreMockRecorder) GetDefaultProxyConfig(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDefaultProxyConfig", reflect.TypeOf((*MockStore)(nil).GetDefaultProxyConfig), arg0)
|
||||
}
|
||||
|
||||
// GetDeploymentDAUs mocks base method.
|
||||
func (m *MockStore) GetDeploymentDAUs(arg0 context.Context, arg1 int32) ([]database.GetDeploymentDAUsRow, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@ -3088,6 +3103,20 @@ func (mr *MockStoreMockRecorder) UpsertAppSecurityKey(arg0, arg1 interface{}) *g
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertAppSecurityKey", reflect.TypeOf((*MockStore)(nil).UpsertAppSecurityKey), arg0, arg1)
|
||||
}
|
||||
|
||||
// UpsertDefaultProxy mocks base method.
|
||||
func (m *MockStore) UpsertDefaultProxy(arg0 context.Context, arg1 database.UpsertDefaultProxyParams) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpsertDefaultProxy", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// UpsertDefaultProxy indicates an expected call of UpsertDefaultProxy.
|
||||
func (mr *MockStoreMockRecorder) UpsertDefaultProxy(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertDefaultProxy", reflect.TypeOf((*MockStore)(nil).UpsertDefaultProxy), arg0, arg1)
|
||||
}
|
||||
|
||||
// UpsertLastUpdateCheck mocks base method.
|
||||
func (m *MockStore) UpsertLastUpdateCheck(arg0 context.Context, arg1 string) error {
|
||||
m.ctrl.T.Helper()
|
||||
|
@ -186,6 +186,10 @@ func (w WorkspaceProxy) RBACObject() rbac.Object {
|
||||
WithID(w.ID)
|
||||
}
|
||||
|
||||
func (w WorkspaceProxy) IsPrimary() bool {
|
||||
return w.Name == "primary"
|
||||
}
|
||||
|
||||
func (f File) RBACObject() rbac.Object {
|
||||
return rbac.ResourceFile.
|
||||
WithID(f.ID).
|
||||
|
@ -54,6 +54,7 @@ type sqlcQuerier interface {
|
||||
// are included.
|
||||
GetAuthorizationUserRoles(ctx context.Context, userID uuid.UUID) (GetAuthorizationUserRolesRow, error)
|
||||
GetDERPMeshKey(ctx context.Context) (string, error)
|
||||
GetDefaultProxyConfig(ctx context.Context) (GetDefaultProxyConfigRow, error)
|
||||
GetDeploymentDAUs(ctx context.Context, tzOffset int32) ([]GetDeploymentDAUsRow, error)
|
||||
GetDeploymentID(ctx context.Context) (string, error)
|
||||
GetDeploymentWorkspaceAgentStats(ctx context.Context, createdAt time.Time) (GetDeploymentWorkspaceAgentStatsRow, error)
|
||||
@ -254,6 +255,10 @@ type sqlcQuerier interface {
|
||||
UpdateWorkspaceTTL(ctx context.Context, arg UpdateWorkspaceTTLParams) error
|
||||
UpdateWorkspaceTTLToBeWithinTemplateMax(ctx context.Context, arg UpdateWorkspaceTTLToBeWithinTemplateMaxParams) error
|
||||
UpsertAppSecurityKey(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.
|
||||
UpsertDefaultProxy(ctx context.Context, arg UpsertDefaultProxyParams) error
|
||||
UpsertLastUpdateCheck(ctx context.Context, value string) error
|
||||
UpsertLogoURL(ctx context.Context, value string) error
|
||||
UpsertServiceBanner(ctx context.Context, value string) error
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"github.com/coder/coder/coderd/database"
|
||||
"github.com/coder/coder/coderd/database/dbgen"
|
||||
"github.com/coder/coder/coderd/database/migrations"
|
||||
"github.com/coder/coder/testutil"
|
||||
)
|
||||
|
||||
func TestGetDeploymentWorkspaceAgentStats(t *testing.T) {
|
||||
@ -257,3 +258,57 @@ func TestProxyByHostname(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultProxy(t *testing.T) {
|
||||
t.Parallel()
|
||||
if testing.Short() {
|
||||
t.SkipNow()
|
||||
}
|
||||
sqlDB := testSQLDB(t)
|
||||
err := migrations.Up(sqlDB)
|
||||
require.NoError(t, err)
|
||||
db := database.New(sqlDB)
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
depID := uuid.NewString()
|
||||
err = db.InsertDeploymentID(ctx, depID)
|
||||
require.NoError(t, err, "insert deployment id")
|
||||
|
||||
// Fetch empty proxy values
|
||||
defProxy, err := db.GetDefaultProxyConfig(ctx)
|
||||
require.NoError(t, err, "get def proxy")
|
||||
|
||||
require.Equal(t, defProxy.DisplayName, "Default")
|
||||
require.Equal(t, defProxy.IconUrl, "/emojis/1f3e1.png")
|
||||
|
||||
// Set the proxy values
|
||||
args := database.UpsertDefaultProxyParams{
|
||||
DisplayName: "displayname",
|
||||
IconUrl: "/icon.png",
|
||||
}
|
||||
err = db.UpsertDefaultProxy(ctx, args)
|
||||
require.NoError(t, err, "insert def proxy")
|
||||
|
||||
defProxy, err = db.GetDefaultProxyConfig(ctx)
|
||||
require.NoError(t, err, "get def proxy")
|
||||
require.Equal(t, defProxy.DisplayName, args.DisplayName)
|
||||
require.Equal(t, defProxy.IconUrl, args.IconUrl)
|
||||
|
||||
// Upsert values
|
||||
args = database.UpsertDefaultProxyParams{
|
||||
DisplayName: "newdisplayname",
|
||||
IconUrl: "/newicon.png",
|
||||
}
|
||||
err = db.UpsertDefaultProxy(ctx, args)
|
||||
require.NoError(t, err, "upsert def proxy")
|
||||
|
||||
defProxy, err = db.GetDefaultProxyConfig(ctx)
|
||||
require.NoError(t, err, "get def proxy")
|
||||
require.Equal(t, defProxy.DisplayName, args.DisplayName)
|
||||
require.Equal(t, defProxy.IconUrl, args.IconUrl)
|
||||
|
||||
// Ensure other site configs are the same
|
||||
found, err := db.GetDeploymentID(ctx)
|
||||
require.NoError(t, err, "get deployment id")
|
||||
require.Equal(t, depID, found)
|
||||
}
|
||||
|
@ -3036,6 +3036,24 @@ func (q *sqlQuerier) GetDERPMeshKey(ctx context.Context) (string, error) {
|
||||
return value, err
|
||||
}
|
||||
|
||||
const getDefaultProxyConfig = `-- name: GetDefaultProxyConfig :one
|
||||
SELECT
|
||||
COALESCE((SELECT value FROM site_configs WHERE key = 'default_proxy_display_name'), 'Default') :: text AS display_name,
|
||||
COALESCE((SELECT value FROM site_configs WHERE key = 'default_proxy_icon_url'), '/emojis/1f3e1.png') :: text AS icon_url
|
||||
`
|
||||
|
||||
type GetDefaultProxyConfigRow struct {
|
||||
DisplayName string `db:"display_name" json:"display_name"`
|
||||
IconUrl string `db:"icon_url" json:"icon_url"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) GetDefaultProxyConfig(ctx context.Context) (GetDefaultProxyConfigRow, error) {
|
||||
row := q.db.QueryRowContext(ctx, getDefaultProxyConfig)
|
||||
var i GetDefaultProxyConfigRow
|
||||
err := row.Scan(&i.DisplayName, &i.IconUrl)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getDeploymentID = `-- name: GetDeploymentID :one
|
||||
SELECT value FROM site_configs WHERE key = 'deployment_id'
|
||||
`
|
||||
@ -3108,6 +3126,29 @@ func (q *sqlQuerier) UpsertAppSecurityKey(ctx context.Context, value string) err
|
||||
return err
|
||||
}
|
||||
|
||||
const upsertDefaultProxy = `-- name: UpsertDefaultProxy :exec
|
||||
INSERT INTO site_configs (key, value)
|
||||
VALUES
|
||||
('default_proxy_display_name', $1 :: text),
|
||||
('default_proxy_icon_url', $2 :: text)
|
||||
ON CONFLICT
|
||||
(key)
|
||||
DO UPDATE SET value = EXCLUDED.value WHERE site_configs.key = EXCLUDED.key
|
||||
`
|
||||
|
||||
type UpsertDefaultProxyParams struct {
|
||||
DisplayName string `db:"display_name" json:"display_name"`
|
||||
IconUrl string `db:"icon_url" json:"icon_url"`
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (q *sqlQuerier) UpsertDefaultProxy(ctx context.Context, arg UpsertDefaultProxyParams) error {
|
||||
_, err := q.db.ExecContext(ctx, upsertDefaultProxy, arg.DisplayName, arg.IconUrl)
|
||||
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'
|
||||
|
@ -1,3 +1,23 @@
|
||||
-- name: UpsertDefaultProxy :exec
|
||||
-- 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.
|
||||
INSERT INTO site_configs (key, value)
|
||||
VALUES
|
||||
('default_proxy_display_name', @display_name :: text),
|
||||
('default_proxy_icon_url', @icon_url :: text)
|
||||
ON CONFLICT
|
||||
(key)
|
||||
DO UPDATE SET value = EXCLUDED.value WHERE site_configs.key = EXCLUDED.key
|
||||
;
|
||||
|
||||
-- name: GetDefaultProxyConfig :one
|
||||
SELECT
|
||||
COALESCE((SELECT value FROM site_configs WHERE key = 'default_proxy_display_name'), 'Default') :: text AS display_name,
|
||||
COALESCE((SELECT value FROM site_configs WHERE key = 'default_proxy_icon_url'), '/emojis/1f3e1.png') :: text AS icon_url
|
||||
;
|
||||
|
||||
|
||||
-- name: InsertDeploymentID :exec
|
||||
INSERT INTO site_configs (key, value) VALUES ('deployment_id', $1);
|
||||
|
||||
|
Reference in New Issue
Block a user