Skip to content

chore: make default workspace proxy editable #7903

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jun 8, 2023
11 changes: 11 additions & 0 deletions coderd/coderdtest/coderdtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"database/sql"
"encoding/base64"
"encoding/json"
"encoding/pem"
Expand Down Expand Up @@ -206,6 +207,16 @@ func NewOptions(t testing.TB, options *Options) (func(http.Handler), context.Can
options.Database = dbauthz.New(options.Database, options.Authorizer, slogtest.Make(t, nil).Leveled(slog.LevelDebug))
}

// Some routes expect a deployment ID, so just make sure one exists.
// Check first incase the caller already set up this database.
// nolint:gocritic // Setting up unit test data inside test helper
depID, err := options.Database.GetDeploymentID(dbauthz.AsSystemRestricted(context.Background()))
if xerrors.Is(err, sql.ErrNoRows) || depID == "" {
// nolint:gocritic // Setting up unit test data inside test helper
err := options.Database.InsertDeploymentID(dbauthz.AsSystemRestricted(context.Background()), uuid.NewString())
require.NoError(t, err, "insert a deployment id")
}

if options.DeploymentValues == nil {
options.DeploymentValues = DeploymentValues(t)
}
Expand Down
5 changes: 5 additions & 0 deletions coderd/database/dbauthz/querier.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 6 additions & 0 deletions coderd/database/dbauthz/querier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
7 changes: 7 additions & 0 deletions coderd/database/dbauthz/system.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
3 changes: 3 additions & 0 deletions coderd/database/dbauthz/system_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
36 changes: 27 additions & 9 deletions coderd/database/dbfake/databasefake.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -5170,3 +5175,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
}
14 changes: 14 additions & 0 deletions coderd/database/dbmetrics/dbmetrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
29 changes: 29 additions & 0 deletions coderd/database/dbmock/store.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions coderd/database/modelmethods.go
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
5 changes: 5 additions & 0 deletions coderd/database/querier.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

55 changes: 55 additions & 0 deletions coderd/database/querier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
}
41 changes: 41 additions & 0 deletions coderd/database/queries.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions coderd/database/queries/siteconfig.sql
Original file line number Diff line number Diff line change
@@ -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);

Expand Down
9 changes: 7 additions & 2 deletions coderd/httpmw/workspaceproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ func WorkspaceProxyParam(r *http.Request) database.WorkspaceProxy {
// parameter.
//
//nolint:revive
func ExtractWorkspaceProxyParam(db database.Store) func(http.Handler) http.Handler {
func ExtractWorkspaceProxyParam(db database.Store, deploymentID string, fetchPrimaryProxy func(ctx context.Context) (database.WorkspaceProxy, error)) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
Expand All @@ -188,9 +188,14 @@ func ExtractWorkspaceProxyParam(db database.Store) func(http.Handler) http.Handl

var proxy database.WorkspaceProxy
var dbErr error
if proxyID, err := uuid.Parse(proxyQuery); err == nil {
if proxyQuery == "primary" || proxyQuery == deploymentID {
// Requesting primary proxy
proxy, dbErr = fetchPrimaryProxy(ctx)
} else if proxyID, err := uuid.Parse(proxyQuery); err == nil {
// Request proxy by id
proxy, dbErr = db.GetWorkspaceProxyByID(ctx, proxyID)
} else {
// Request proxy by name
proxy, dbErr = db.GetWorkspaceProxyByName(ctx, proxyQuery)
}
if httpapi.Is404Error(dbErr) {
Expand Down
Loading