mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
chore: make default workspace proxy editable (#7903)
* chore: add editing the default workspace proxy
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"database/sql"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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) {
|
||||
|
||||
@@ -212,7 +212,7 @@ func TestExtractWorkspaceProxyParam(t *testing.T) {
|
||||
routeContext.URLParams.Add("workspaceproxy", proxy.Name)
|
||||
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, routeContext))
|
||||
|
||||
httpmw.ExtractWorkspaceProxyParam(db)(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
httpmw.ExtractWorkspaceProxyParam(db, uuid.NewString(), nil)(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
// Checks that it exists on the context!
|
||||
_ = httpmw.WorkspaceProxyParam(request)
|
||||
successHandler.ServeHTTP(writer, request)
|
||||
@@ -236,7 +236,7 @@ func TestExtractWorkspaceProxyParam(t *testing.T) {
|
||||
routeContext.URLParams.Add("workspaceproxy", proxy.ID.String())
|
||||
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, routeContext))
|
||||
|
||||
httpmw.ExtractWorkspaceProxyParam(db)(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
httpmw.ExtractWorkspaceProxyParam(db, uuid.NewString(), nil)(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
// Checks that it exists on the context!
|
||||
_ = httpmw.WorkspaceProxyParam(request)
|
||||
successHandler.ServeHTTP(writer, request)
|
||||
@@ -258,9 +258,44 @@ func TestExtractWorkspaceProxyParam(t *testing.T) {
|
||||
routeContext.URLParams.Add("workspaceproxy", uuid.NewString())
|
||||
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, routeContext))
|
||||
|
||||
httpmw.ExtractWorkspaceProxyParam(db)(successHandler).ServeHTTP(rw, r)
|
||||
httpmw.ExtractWorkspaceProxyParam(db, uuid.NewString(), nil)(successHandler).ServeHTTP(rw, r)
|
||||
res := rw.Result()
|
||||
defer res.Body.Close()
|
||||
require.Equal(t, http.StatusNotFound, res.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("FetchPrimary", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
var (
|
||||
db = dbfake.New()
|
||||
r = httptest.NewRequest("GET", "/", nil)
|
||||
rw = httptest.NewRecorder()
|
||||
deploymentID = uuid.New()
|
||||
primaryProxy = database.WorkspaceProxy{
|
||||
ID: deploymentID,
|
||||
Name: "primary",
|
||||
DisplayName: "Default",
|
||||
Icon: "Icon",
|
||||
Url: "Url",
|
||||
WildcardHostname: "Wildcard",
|
||||
}
|
||||
fetchPrimary = func(ctx context.Context) (database.WorkspaceProxy, error) {
|
||||
return primaryProxy, nil
|
||||
}
|
||||
)
|
||||
|
||||
routeContext := chi.NewRouteContext()
|
||||
routeContext.URLParams.Add("workspaceproxy", deploymentID.String())
|
||||
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, routeContext))
|
||||
|
||||
httpmw.ExtractWorkspaceProxyParam(db, deploymentID.String(), fetchPrimary)(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
// Checks that it exists on the context!
|
||||
found := httpmw.WorkspaceProxyParam(request)
|
||||
require.Equal(t, primaryProxy, found)
|
||||
successHandler.ServeHTTP(writer, request)
|
||||
})).ServeHTTP(rw, r)
|
||||
res := rw.Result()
|
||||
defer res.Body.Close()
|
||||
require.Equal(t, http.StatusOK, res.StatusCode)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,11 +8,14 @@ import (
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/coderd/database"
|
||||
"github.com/coder/coder/coderd/database/dbauthz"
|
||||
"github.com/coder/coder/coderd/httpapi"
|
||||
"github.com/coder/coder/codersdk"
|
||||
)
|
||||
|
||||
// PrimaryRegion exposes the user facing values of a workspace proxy to
|
||||
// be used by a user.
|
||||
func (api *API) PrimaryRegion(ctx context.Context) (codersdk.Region, error) {
|
||||
deploymentIDStr, err := api.Database.GetDeploymentID(ctx)
|
||||
if xerrors.Is(err, sql.ErrNoRows) {
|
||||
@@ -28,19 +31,42 @@ func (api *API) PrimaryRegion(ctx context.Context) (codersdk.Region, error) {
|
||||
deploymentID = uuid.Nil
|
||||
}
|
||||
|
||||
proxy, err := api.Database.GetDefaultProxyConfig(ctx)
|
||||
if err != nil {
|
||||
return codersdk.Region{}, xerrors.Errorf("get default proxy config: %w", err)
|
||||
}
|
||||
|
||||
return codersdk.Region{
|
||||
ID: deploymentID,
|
||||
// TODO: provide some way to customize these fields for the primary
|
||||
// region
|
||||
ID: deploymentID,
|
||||
Name: "primary",
|
||||
DisplayName: "Default",
|
||||
IconURL: "/emojis/1f60e.png", // face with sunglasses
|
||||
DisplayName: proxy.DisplayName,
|
||||
IconURL: proxy.IconUrl,
|
||||
Healthy: true,
|
||||
PathAppURL: api.AccessURL.String(),
|
||||
WildcardHostname: api.AppHostname,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PrimaryWorkspaceProxy returns the primary workspace proxy for the site.
|
||||
func (api *API) PrimaryWorkspaceProxy(ctx context.Context) (database.WorkspaceProxy, error) {
|
||||
region, err := api.PrimaryRegion(ctx)
|
||||
if err != nil {
|
||||
return database.WorkspaceProxy{}, err
|
||||
}
|
||||
|
||||
// The default proxy is an edge case because these values are computed
|
||||
// rather then being stored in the database.
|
||||
return database.WorkspaceProxy{
|
||||
ID: region.ID,
|
||||
Name: region.Name,
|
||||
DisplayName: region.DisplayName,
|
||||
Icon: region.IconURL,
|
||||
Url: region.PathAppURL,
|
||||
WildcardHostname: region.WildcardHostname,
|
||||
Deleted: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// @Summary Get site-wide regions for workspace connections
|
||||
// @ID get-site-wide-regions-for-workspace-connections
|
||||
// @Security CoderSessionToken
|
||||
|
||||
Reference in New Issue
Block a user