mirror of
https://github.com/coder/coder.git
synced 2025-07-23 21:32:07 +00:00
chore: Allow editing proxy fields via api. (#7435)
* chore: Add ability to update workspace proxy fields
This commit is contained in:
@ -120,6 +120,8 @@ func New(ctx context.Context, options *Options) (*API, error) {
|
||||
httpmw.ExtractWorkspaceProxyParam(api.Database),
|
||||
)
|
||||
|
||||
r.Get("/", api.workspaceProxy)
|
||||
r.Patch("/", api.patchWorkspaceProxy)
|
||||
r.Delete("/", api.deleteWorkspaceProxy)
|
||||
})
|
||||
})
|
||||
|
@ -90,6 +90,79 @@ func (api *API) regions(rw http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// @Summary Update workspace proxy
|
||||
// @ID update-workspace-proxy
|
||||
// @Security CoderSessionToken
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Tags Enterprise
|
||||
// @Param workspaceproxy path string true "Proxy ID or name" format(uuid)
|
||||
// @Param request body codersdk.PatchWorkspaceProxy true "Update workspace proxy request"
|
||||
// @Success 200 {object} codersdk.WorkspaceProxy
|
||||
// @Router /workspaceproxies/{workspaceproxy} [patch]
|
||||
func (api *API) patchWorkspaceProxy(rw http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
ctx = r.Context()
|
||||
proxy = httpmw.WorkspaceProxyParam(r)
|
||||
auditor = api.AGPL.Auditor.Load()
|
||||
aReq, commitAudit = audit.InitRequest[database.WorkspaceProxy](rw, &audit.RequestParams{
|
||||
Audit: *auditor,
|
||||
Log: api.Logger,
|
||||
Request: r,
|
||||
Action: database.AuditActionWrite,
|
||||
})
|
||||
)
|
||||
aReq.Old = proxy
|
||||
defer commitAudit()
|
||||
|
||||
var req codersdk.PatchWorkspaceProxy
|
||||
if !httpapi.Read(ctx, rw, r, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
var hashedSecret []byte
|
||||
var fullToken string
|
||||
if req.RegenerateToken {
|
||||
var err error
|
||||
fullToken, hashedSecret, err = generateWorkspaceProxyToken(proxy.ID)
|
||||
if err != nil {
|
||||
httpapi.InternalServerError(rw, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
// The proxy should have some status, but just in case.
|
||||
status.Status = proxyhealth.Unknown
|
||||
}
|
||||
httpapi.Write(ctx, rw, http.StatusOK, codersdk.UpdateWorkspaceProxyResponse{
|
||||
Proxy: convertProxy(updatedProxy, status),
|
||||
ProxyToken: fullToken,
|
||||
})
|
||||
|
||||
// Update the proxy cache.
|
||||
go api.forceWorkspaceProxyHealthUpdate(api.ctx)
|
||||
}
|
||||
|
||||
// @Summary Delete workspace proxy
|
||||
// @ID delete-workspace-proxy
|
||||
// @Security CoderSessionToken
|
||||
@ -107,7 +180,7 @@ func (api *API) deleteWorkspaceProxy(rw http.ResponseWriter, r *http.Request) {
|
||||
Audit: *auditor,
|
||||
Log: api.Logger,
|
||||
Request: r,
|
||||
Action: database.AuditActionCreate,
|
||||
Action: database.AuditActionDelete,
|
||||
})
|
||||
)
|
||||
aReq.Old = proxy
|
||||
@ -135,6 +208,23 @@ func (api *API) deleteWorkspaceProxy(rw http.ResponseWriter, r *http.Request) {
|
||||
go api.forceWorkspaceProxyHealthUpdate(api.ctx)
|
||||
}
|
||||
|
||||
// @Summary Get workspace proxy
|
||||
// @ID get-workspace-proxy
|
||||
// @Security CoderSessionToken
|
||||
// @Produce json
|
||||
// @Tags Enterprise
|
||||
// @Param workspaceproxy path string true "Proxy ID or name" format(uuid)
|
||||
// @Success 200 {object} codersdk.WorkspaceProxy
|
||||
// @Router /workspaceproxies/{workspaceproxy} [get]
|
||||
func (api *API) workspaceProxy(rw http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
ctx = r.Context()
|
||||
proxy = httpmw.WorkspaceProxyParam(r)
|
||||
)
|
||||
|
||||
httpapi.Write(ctx, rw, http.StatusOK, convertProxy(proxy, api.ProxyHealth.HealthStatus()[proxy.ID]))
|
||||
}
|
||||
|
||||
// @Summary Create workspace proxy
|
||||
// @ID create-workspace-proxy
|
||||
// @Security CoderSessionToken
|
||||
@ -177,13 +267,11 @@ func (api *API) postWorkspaceProxy(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
id := uuid.New()
|
||||
secret, err := cryptorand.HexString(64)
|
||||
fullToken, hashedSecret, err := generateWorkspaceProxyToken(id)
|
||||
if err != nil {
|
||||
httpapi.InternalServerError(rw, err)
|
||||
return
|
||||
}
|
||||
hashedSecret := sha256.Sum256([]byte(secret))
|
||||
fullToken := fmt.Sprintf("%s:%s", id, secret)
|
||||
|
||||
proxy, err := api.Database.InsertWorkspaceProxy(ctx, database.InsertWorkspaceProxyParams{
|
||||
ID: id,
|
||||
@ -206,7 +294,7 @@ func (api *API) postWorkspaceProxy(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
aReq.New = proxy
|
||||
httpapi.Write(ctx, rw, http.StatusCreated, codersdk.CreateWorkspaceProxyResponse{
|
||||
httpapi.Write(ctx, rw, http.StatusCreated, codersdk.UpdateWorkspaceProxyResponse{
|
||||
Proxy: convertProxy(proxy, proxyhealth.ProxyStatus{
|
||||
Proxy: proxy,
|
||||
CheckedAt: time.Now(),
|
||||
@ -325,7 +413,20 @@ func (api *API) workspaceProxyRegister(rw http.ResponseWriter, r *http.Request)
|
||||
var (
|
||||
ctx = r.Context()
|
||||
proxy = httpmw.WorkspaceProxy(r)
|
||||
// TODO: This audit log does not work because it has no user id
|
||||
// associated with it. The audit log commitAudit() function ignores
|
||||
// the audit log if there is no user id. We should find a solution
|
||||
// to make sure this event is tracked.
|
||||
// auditor = api.AGPL.Auditor.Load()
|
||||
// aReq, commitAudit = audit.InitRequest[database.WorkspaceProxy](rw, &audit.RequestParams{
|
||||
// Audit: *auditor,
|
||||
// Log: api.Logger,
|
||||
// Request: r,
|
||||
// Action: database.AuditActionWrite,
|
||||
//})
|
||||
)
|
||||
// aReq.Old = proxy
|
||||
// defer commitAudit()
|
||||
|
||||
var req wsproxysdk.RegisterWorkspaceProxyRequest
|
||||
if !httpapi.Read(ctx, rw, r, &req) {
|
||||
@ -364,6 +465,7 @@ func (api *API) workspaceProxyRegister(rw http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
// aReq.New = updatedProxy
|
||||
httpapi.Write(ctx, rw, http.StatusCreated, wsproxysdk.RegisterWorkspaceProxyResponse{
|
||||
AppSecurityKey: api.AppSecurityKey.String(),
|
||||
})
|
||||
@ -467,6 +569,16 @@ func (api *API) reconnectingPTYSignedToken(rw http.ResponseWriter, r *http.Reque
|
||||
})
|
||||
}
|
||||
|
||||
func generateWorkspaceProxyToken(id uuid.UUID) (token string, hashed []byte, err error) {
|
||||
secret, err := cryptorand.HexString(64)
|
||||
if err != nil {
|
||||
return "", nil, xerrors.Errorf("generate token: %w", err)
|
||||
}
|
||||
hashedSecret := sha256.Sum256([]byte(secret))
|
||||
fullToken := fmt.Sprintf("%s:%s", id, secret)
|
||||
return fullToken, hashedSecret[:], nil
|
||||
}
|
||||
|
||||
func convertProxies(p []database.WorkspaceProxy, statuses map[uuid.UUID]proxyhealth.ProxyStatus) []codersdk.WorkspaceProxy {
|
||||
resp := make([]codersdk.WorkspaceProxy, 0, len(p))
|
||||
for _, proxy := range p {
|
||||
@ -479,6 +591,7 @@ func convertProxy(p database.WorkspaceProxy, status proxyhealth.ProxyStatus) cod
|
||||
return codersdk.WorkspaceProxy{
|
||||
ID: p.ID,
|
||||
Name: p.Name,
|
||||
DisplayName: p.DisplayName,
|
||||
Icon: p.Icon,
|
||||
URL: p.Url,
|
||||
WildcardHostname: p.WildcardHostname,
|
||||
|
@ -177,7 +177,7 @@ func TestRegions(t *testing.T) {
|
||||
func TestWorkspaceProxyCRUD(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("create", func(t *testing.T) {
|
||||
t.Run("CreateAndUpdate", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
@ -203,14 +203,33 @@ func TestWorkspaceProxyCRUD(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
proxies, err := client.WorkspaceProxies(ctx)
|
||||
found, err := client.WorkspaceProxyByID(ctx, proxyRes.Proxy.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, proxies, 1)
|
||||
require.Equal(t, proxyRes.Proxy.ID, proxies[0].ID)
|
||||
// This will be different, so set it to the same
|
||||
found.Status = proxyRes.Proxy.Status
|
||||
require.Equal(t, proxyRes.Proxy, found, "expected proxy")
|
||||
require.NotEmpty(t, proxyRes.ProxyToken)
|
||||
|
||||
// Update the proxy
|
||||
expName := namesgenerator.GetRandomName(1)
|
||||
expDisplayName := namesgenerator.GetRandomName(1)
|
||||
expIcon := namesgenerator.GetRandomName(1)
|
||||
_, err = client.PatchWorkspaceProxy(ctx, codersdk.PatchWorkspaceProxy{
|
||||
ID: proxyRes.Proxy.ID,
|
||||
Name: expName,
|
||||
DisplayName: expDisplayName,
|
||||
Icon: expIcon,
|
||||
})
|
||||
require.NoError(t, err, "expected no error updating proxy")
|
||||
|
||||
found, err = client.WorkspaceProxyByID(ctx, proxyRes.Proxy.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expName, found.Name, "name")
|
||||
require.Equal(t, expDisplayName, found.DisplayName, "display name")
|
||||
require.Equal(t, expIcon, found.Icon, "icon")
|
||||
})
|
||||
|
||||
t.Run("delete", func(t *testing.T) {
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
|
Reference in New Issue
Block a user