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:
@@ -72,6 +72,11 @@ func New(ctx context.Context, options *Options) (*API, error) {
|
||||
RedirectToLogin: false,
|
||||
})
|
||||
|
||||
deploymentID, err := options.Database.GetDeploymentID(ctx)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to get deployment ID: %w", err)
|
||||
}
|
||||
|
||||
api.AGPL.APIHandler.Group(func(r chi.Router) {
|
||||
r.Get("/entitlements", api.serveEntitlements)
|
||||
// /regions overrides the AGPL /regions endpoint
|
||||
@@ -118,7 +123,7 @@ func New(ctx context.Context, options *Options) (*API, error) {
|
||||
r.Route("/{workspaceproxy}", func(r chi.Router) {
|
||||
r.Use(
|
||||
apiKeyMiddleware,
|
||||
httpmw.ExtractWorkspaceProxyParam(api.Database),
|
||||
httpmw.ExtractWorkspaceProxyParam(api.Database, deploymentID, api.AGPL.PrimaryWorkspaceProxy),
|
||||
)
|
||||
|
||||
r.Get("/", api.workspaceProxy)
|
||||
@@ -225,7 +230,6 @@ func New(ctx context.Context, options *Options) (*API, error) {
|
||||
RootCAs: meshRootCA,
|
||||
ServerName: options.AccessURL.Hostname(),
|
||||
}
|
||||
var err error
|
||||
api.replicaManager, err = replicasync.New(ctx, options.Logger, options.Database, options.Pubsub, &replicasync.Options{
|
||||
ID: api.AGPL.ID,
|
||||
RelayAddress: options.DERPServerRelayAddress,
|
||||
|
||||
@@ -127,23 +127,39 @@ func (api *API) patchWorkspaceProxy(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
updatedProxy, err := api.Database.UpdateWorkspaceProxy(ctx, database.UpdateWorkspaceProxyParams{
|
||||
Name: req.Name,
|
||||
DisplayName: req.DisplayName,
|
||||
Icon: req.Icon,
|
||||
ID: proxy.ID,
|
||||
// If hashedSecret is nil or empty, this will not update the secret.
|
||||
TokenHashedSecret: hashedSecret,
|
||||
})
|
||||
if httpapi.Is404Error(err) {
|
||||
httpapi.ResourceNotFound(rw)
|
||||
return
|
||||
}
|
||||
deploymentIDStr, err := api.Database.GetDeploymentID(ctx)
|
||||
if err != nil {
|
||||
httpapi.InternalServerError(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
var updatedProxy database.WorkspaceProxy
|
||||
if proxy.ID.String() == deploymentIDStr {
|
||||
// User is editing the default primary proxy.
|
||||
var ok bool
|
||||
updatedProxy, ok = api.patchPrimaryWorkspaceProxy(req, rw, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
updatedProxy, err = api.Database.UpdateWorkspaceProxy(ctx, database.UpdateWorkspaceProxyParams{
|
||||
Name: req.Name,
|
||||
DisplayName: req.DisplayName,
|
||||
Icon: req.Icon,
|
||||
ID: proxy.ID,
|
||||
// If hashedSecret is nil or empty, this will not update the secret.
|
||||
TokenHashedSecret: hashedSecret,
|
||||
})
|
||||
if httpapi.Is404Error(err) {
|
||||
httpapi.ResourceNotFound(rw)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
httpapi.InternalServerError(rw, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
aReq.New = updatedProxy
|
||||
status, ok := api.ProxyHealth.HealthStatus()[updatedProxy.ID]
|
||||
if !ok {
|
||||
@@ -159,6 +175,68 @@ func (api *API) patchWorkspaceProxy(rw http.ResponseWriter, r *http.Request) {
|
||||
go api.forceWorkspaceProxyHealthUpdate(api.ctx)
|
||||
}
|
||||
|
||||
// patchPrimaryWorkspaceProxy handles the special case of updating the default
|
||||
func (api *API) patchPrimaryWorkspaceProxy(req codersdk.PatchWorkspaceProxy, rw http.ResponseWriter, r *http.Request) (database.WorkspaceProxy, bool) {
|
||||
var (
|
||||
ctx = r.Context()
|
||||
proxy = httpmw.WorkspaceProxyParam(r)
|
||||
)
|
||||
|
||||
// User is editing the default primary proxy.
|
||||
if req.Name != "" && req.Name != proxy.Name {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Cannot update name of default primary proxy, did you mean to update the 'display name'?",
|
||||
Validations: []codersdk.ValidationError{
|
||||
{Field: "name", Detail: "Cannot update name of default primary proxy"},
|
||||
},
|
||||
})
|
||||
return database.WorkspaceProxy{}, false
|
||||
}
|
||||
if req.DisplayName == "" && req.Icon == "" {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "No update arguments provided. Nothing to do.",
|
||||
Validations: []codersdk.ValidationError{
|
||||
{Field: "display_name", Detail: "No value provided."},
|
||||
{Field: "icon", Detail: "No value provided."},
|
||||
},
|
||||
})
|
||||
return database.WorkspaceProxy{}, false
|
||||
}
|
||||
|
||||
args := database.UpsertDefaultProxyParams{
|
||||
DisplayName: req.DisplayName,
|
||||
IconUrl: req.Icon,
|
||||
}
|
||||
if req.DisplayName == "" || req.Icon == "" {
|
||||
// If the user has not specified an update value, use the existing value.
|
||||
existing, err := api.Database.GetDefaultProxyConfig(ctx)
|
||||
if err != nil {
|
||||
httpapi.InternalServerError(rw, err)
|
||||
return database.WorkspaceProxy{}, false
|
||||
}
|
||||
if req.DisplayName == "" {
|
||||
args.DisplayName = existing.DisplayName
|
||||
}
|
||||
if req.Icon == "" {
|
||||
args.IconUrl = existing.IconUrl
|
||||
}
|
||||
}
|
||||
|
||||
err := api.Database.UpsertDefaultProxy(ctx, args)
|
||||
if err != nil {
|
||||
httpapi.InternalServerError(rw, err)
|
||||
return database.WorkspaceProxy{}, false
|
||||
}
|
||||
|
||||
// Use the primary region to fetch the default proxy values.
|
||||
updatedProxy, err := api.AGPL.PrimaryWorkspaceProxy(ctx)
|
||||
if err != nil {
|
||||
httpapi.InternalServerError(rw, err)
|
||||
return database.WorkspaceProxy{}, false
|
||||
}
|
||||
return updatedProxy, true
|
||||
}
|
||||
|
||||
// @Summary Delete workspace proxy
|
||||
// @ID delete-workspace-proxy
|
||||
// @Security CoderSessionToken
|
||||
@@ -182,6 +260,13 @@ func (api *API) deleteWorkspaceProxy(rw http.ResponseWriter, r *http.Request) {
|
||||
aReq.Old = proxy
|
||||
defer commitAudit()
|
||||
|
||||
if proxy.IsPrimary() {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Cannot delete primary proxy",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
err := api.Database.UpdateWorkspaceProxyDeleted(ctx, database.UpdateWorkspaceProxyDeletedParams{
|
||||
ID: proxy.ID,
|
||||
Deleted: true,
|
||||
@@ -334,6 +419,14 @@ func (api *API) workspaceProxies(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Add the primary as well
|
||||
primaryProxy, err := api.AGPL.PrimaryWorkspaceProxy(ctx)
|
||||
if err != nil && !xerrors.Is(err, sql.ErrNoRows) {
|
||||
httpapi.InternalServerError(rw, err)
|
||||
return
|
||||
}
|
||||
proxies = append([]database.WorkspaceProxy{primaryProxy}, proxies...)
|
||||
|
||||
statues := api.ProxyHealth.HealthStatus()
|
||||
httpapi.Write(ctx, rw, http.StatusOK, convertProxies(proxies, statues))
|
||||
}
|
||||
@@ -611,6 +704,18 @@ func convertProxies(p []database.WorkspaceProxy, statuses map[uuid.UUID]proxyhea
|
||||
}
|
||||
|
||||
func convertProxy(p database.WorkspaceProxy, status proxyhealth.ProxyStatus) codersdk.WorkspaceProxy {
|
||||
if p.IsPrimary() {
|
||||
// Primary is always healthy since the primary serves the api that this
|
||||
// is returned from.
|
||||
u, _ := url.Parse(p.Url)
|
||||
status = proxyhealth.ProxyStatus{
|
||||
Proxy: p,
|
||||
ProxyHost: u.Host,
|
||||
Status: proxyhealth.Healthy,
|
||||
Report: codersdk.ProxyHealthReport{},
|
||||
CheckedAt: time.Now(),
|
||||
}
|
||||
}
|
||||
if status.Status == "" {
|
||||
status.Status = proxyhealth.Unknown
|
||||
}
|
||||
|
||||
@@ -44,11 +44,6 @@ func TestRegions(t *testing.T) {
|
||||
}
|
||||
|
||||
db, pubsub := dbtestutil.NewDB(t)
|
||||
deploymentID := uuid.New()
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
err := db.InsertDeploymentID(ctx, deploymentID.String())
|
||||
require.NoError(t, err)
|
||||
|
||||
client := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{
|
||||
@@ -58,14 +53,18 @@ func TestRegions(t *testing.T) {
|
||||
DeploymentValues: dv,
|
||||
},
|
||||
})
|
||||
|
||||
_ = coderdtest.CreateFirstUser(t, client)
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
deploymentID, err := db.GetDeploymentID(ctx)
|
||||
require.NoError(t, err, "get deployment ID")
|
||||
|
||||
regions, err := client.Regions(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, regions, 1)
|
||||
require.NotEqual(t, uuid.Nil, regions[0].ID)
|
||||
require.Equal(t, regions[0].ID, deploymentID)
|
||||
require.Equal(t, regions[0].ID.String(), deploymentID)
|
||||
require.Equal(t, "primary", regions[0].Name)
|
||||
require.Equal(t, "Default", regions[0].DisplayName)
|
||||
require.NotEmpty(t, regions[0].IconURL)
|
||||
@@ -89,11 +88,6 @@ func TestRegions(t *testing.T) {
|
||||
}
|
||||
|
||||
db, pubsub := dbtestutil.NewDB(t)
|
||||
deploymentID := uuid.New()
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
err := db.InsertDeploymentID(ctx, deploymentID.String())
|
||||
require.NoError(t, err)
|
||||
|
||||
client, closer, api := coderdenttest.NewWithAPI(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{
|
||||
@@ -106,6 +100,9 @@ func TestRegions(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
_ = closer.Close()
|
||||
})
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
deploymentID, err := db.GetDeploymentID(ctx)
|
||||
require.NoError(t, err, "get deployment ID")
|
||||
_ = coderdtest.CreateFirstUser(t, client)
|
||||
_ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
@@ -131,7 +128,7 @@ func TestRegions(t *testing.T) {
|
||||
|
||||
// Region 0 is the primary require.Len(t, regions, 1)
|
||||
require.NotEqual(t, uuid.Nil, regions[0].ID)
|
||||
require.Equal(t, regions[0].ID, deploymentID)
|
||||
require.Equal(t, regions[0].ID.String(), deploymentID)
|
||||
require.Equal(t, "primary", regions[0].Name)
|
||||
require.Equal(t, "Default", regions[0].DisplayName)
|
||||
require.NotEmpty(t, regions[0].IconURL)
|
||||
@@ -325,7 +322,8 @@ func TestWorkspaceProxyCRUD(t *testing.T) {
|
||||
|
||||
proxies, err := client.WorkspaceProxies(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, proxies, 0)
|
||||
// Default proxy is always there
|
||||
require.Len(t, proxies, 1)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -387,11 +385,10 @@ func TestIssueSignedAppToken(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
proxyClient := wsproxysdk.New(client.URL)
|
||||
proxyClient.SetSessionToken(proxyRes.ProxyToken)
|
||||
|
||||
t.Run("BadAppRequest", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
proxyClient := wsproxysdk.New(client.URL)
|
||||
proxyClient.SetSessionToken(proxyRes.ProxyToken)
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
_, err = proxyClient.IssueSignedAppToken(ctx, workspaceapps.IssueTokenRequest{
|
||||
@@ -412,6 +409,8 @@ func TestIssueSignedAppToken(t *testing.T) {
|
||||
}
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
proxyClient := wsproxysdk.New(client.URL)
|
||||
proxyClient.SetSessionToken(proxyRes.ProxyToken)
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
_, err = proxyClient.IssueSignedAppToken(ctx, goodRequest)
|
||||
@@ -420,6 +419,8 @@ func TestIssueSignedAppToken(t *testing.T) {
|
||||
|
||||
t.Run("OKHTML", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
proxyClient := wsproxysdk.New(client.URL)
|
||||
proxyClient.SetSessionToken(proxyRes.ProxyToken)
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
|
||||
Reference in New Issue
Block a user