mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
chore: Add workspace proxy enterprise cli commands (#7176)
* feat: Add workspace proxy enterprise cli commands * chore: Handle custom workspace proxy options. Remove excess * chore: Add endpoint to register workspace proxies
This commit is contained in:
@@ -100,15 +100,16 @@ func New(ctx context.Context, options *Options) (*API, error) {
|
||||
}),
|
||||
)
|
||||
r.Post("/issue-signed-app-token", api.workspaceProxyIssueSignedAppToken)
|
||||
r.Post("/register", api.workspaceProxyRegister)
|
||||
})
|
||||
r.Route("/{workspaceproxy}", func(r chi.Router) {
|
||||
r.Use(
|
||||
apiKeyMiddleware,
|
||||
httpmw.ExtractWorkspaceProxyParam(api.Database),
|
||||
)
|
||||
|
||||
r.Delete("/", api.deleteWorkspaceProxy)
|
||||
})
|
||||
// TODO: Add specific workspace proxy endpoints.
|
||||
// r.Route("/{proxyName}", func(r chi.Router) {
|
||||
// r.Use(
|
||||
// httpmw.ExtractWorkspaceProxyByNameParam(api.Database),
|
||||
// )
|
||||
//
|
||||
// r.Get("/", api.workspaceProxyByName)
|
||||
// })
|
||||
})
|
||||
r.Route("/organizations/{organization}/groups", func(r chi.Router) {
|
||||
r.Use(
|
||||
|
||||
@@ -115,14 +115,13 @@ func NewWorkspaceProxy(t *testing.T, coderdAPI *coderd.API, owner *codersdk.Clie
|
||||
})
|
||||
require.NoError(t, err, "failed to create workspace proxy")
|
||||
|
||||
wssrv, err := wsproxy.New(&wsproxy.Options{
|
||||
wssrv, err := wsproxy.New(ctx, &wsproxy.Options{
|
||||
Logger: slogtest.Make(t, nil).Leveled(slog.LevelDebug),
|
||||
DashboardURL: coderdAPI.AccessURL,
|
||||
AccessURL: accessURL,
|
||||
AppHostname: options.AppHostname,
|
||||
AppHostnameRegex: appHostnameRegex,
|
||||
RealIPConfig: coderdAPI.RealIPConfig,
|
||||
AppSecurityKey: coderdAPI.AppSecurityKey,
|
||||
Tracing: coderdAPI.TracerProvider,
|
||||
APIRateLimit: coderdAPI.APIRateLimit,
|
||||
SecureAuthCookie: coderdAPI.SecureAuthCookie,
|
||||
|
||||
@@ -13,12 +13,55 @@ import (
|
||||
"github.com/coder/coder/coderd/audit"
|
||||
"github.com/coder/coder/coderd/database"
|
||||
"github.com/coder/coder/coderd/httpapi"
|
||||
"github.com/coder/coder/coderd/httpmw"
|
||||
"github.com/coder/coder/coderd/workspaceapps"
|
||||
"github.com/coder/coder/codersdk"
|
||||
"github.com/coder/coder/cryptorand"
|
||||
"github.com/coder/coder/enterprise/wsproxy/wsproxysdk"
|
||||
)
|
||||
|
||||
// @Summary Delete workspace proxy
|
||||
// @ID delete-workspace-proxy
|
||||
// @Security CoderSessionToken
|
||||
// @Produce json
|
||||
// @Tags Enterprise
|
||||
// @Param workspaceproxy path string true "Proxy ID or name" format(uuid)
|
||||
// @Success 200 {object} codersdk.Response
|
||||
// @Router /workspaceproxies/{workspaceproxy} [delete]
|
||||
func (api *API) deleteWorkspaceProxy(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.AuditActionCreate,
|
||||
})
|
||||
)
|
||||
aReq.Old = proxy
|
||||
defer commitAudit()
|
||||
|
||||
err := api.Database.UpdateWorkspaceProxyDeleted(ctx, database.UpdateWorkspaceProxyDeletedParams{
|
||||
ID: proxy.ID,
|
||||
Deleted: true,
|
||||
})
|
||||
if httpapi.Is404Error(err) {
|
||||
httpapi.ResourceNotFound(rw)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
httpapi.InternalServerError(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
aReq.New = database.WorkspaceProxy{}
|
||||
httpapi.Write(ctx, rw, http.StatusOK, codersdk.Response{
|
||||
Message: "Proxy has been deleted!",
|
||||
})
|
||||
}
|
||||
|
||||
// @Summary Create workspace proxy
|
||||
// @ID create-workspace-proxy
|
||||
// @Security CoderSessionToken
|
||||
@@ -208,3 +251,66 @@ func (api *API) workspaceProxyIssueSignedAppToken(rw http.ResponseWriter, r *htt
|
||||
SignedTokenStr: tokenStr,
|
||||
})
|
||||
}
|
||||
|
||||
// workspaceProxyRegister is used to register a new workspace proxy. When a proxy
|
||||
// comes online, it will announce itself to this endpoint. This updates its values
|
||||
// in the database and returns a signed token that can be used to authenticate
|
||||
// tokens.
|
||||
//
|
||||
// @Summary Register workspace proxy
|
||||
// @ID register-workspace-proxy
|
||||
// @Security CoderSessionToken
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Tags Enterprise
|
||||
// @Param request body wsproxysdk.RegisterWorkspaceProxyRequest true "Issue signed app token request"
|
||||
// @Success 201 {object} wsproxysdk.RegisterWorkspaceProxyResponse
|
||||
// @Router /workspaceproxies/me/register [post]
|
||||
// @x-apidocgen {"skip": true}
|
||||
func (api *API) workspaceProxyRegister(rw http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
ctx = r.Context()
|
||||
proxy = httpmw.WorkspaceProxy(r)
|
||||
)
|
||||
|
||||
var req wsproxysdk.RegisterWorkspaceProxyRequest
|
||||
if !httpapi.Read(ctx, rw, r, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := validateProxyURL(req.AccessURL); err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "URL is invalid.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if req.WildcardHostname != "" {
|
||||
if _, err := httpapi.CompileHostnamePattern(req.WildcardHostname); err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Wildcard URL is invalid.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_, err := api.Database.RegisterWorkspaceProxy(ctx, database.RegisterWorkspaceProxyParams{
|
||||
ID: proxy.ID,
|
||||
Url: req.AccessURL,
|
||||
WildcardHostname: req.WildcardHostname,
|
||||
})
|
||||
if httpapi.Is404Error(err) {
|
||||
httpapi.ResourceNotFound(rw)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
httpapi.InternalServerError(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
httpapi.Write(ctx, rw, http.StatusCreated, wsproxysdk.RegisterWorkspaceProxyResponse{
|
||||
AppSecurityKey: api.AppSecurityKey.String(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -62,6 +62,42 @@ func TestWorkspaceProxyCRUD(t *testing.T) {
|
||||
require.Equal(t, proxyRes.Proxy, proxies[0])
|
||||
require.NotEmpty(t, proxyRes.ProxyToken)
|
||||
})
|
||||
|
||||
t.Run("delete", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.Experiments = []string{
|
||||
string(codersdk.ExperimentMoons),
|
||||
"*",
|
||||
}
|
||||
client := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{
|
||||
DeploymentValues: dv,
|
||||
},
|
||||
})
|
||||
_ = coderdtest.CreateFirstUser(t, client)
|
||||
_ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
codersdk.FeatureWorkspaceProxy: 1,
|
||||
},
|
||||
})
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
proxyRes, err := client.CreateWorkspaceProxy(ctx, codersdk.CreateWorkspaceProxyRequest{
|
||||
Name: namesgenerator.GetRandomName(1),
|
||||
Icon: "/emojis/flag.png",
|
||||
URL: "https://" + namesgenerator.GetRandomName(1) + ".com",
|
||||
WildcardHostname: "*.sub.example.com",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = client.DeleteWorkspaceProxyByID(ctx, proxyRes.Proxy.ID)
|
||||
require.NoError(t, err, "failed to delete workspace proxy")
|
||||
|
||||
proxies, err := client.WorkspaceProxies(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, proxies, 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIssueSignedAppToken(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user