From 9c4de6ec30029a0b113a4b44b3b46cf8bed2238c Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 8 May 2023 16:49:24 -0500 Subject: [PATCH 1/7] chore: Implement workspace proxy going away When a workspace proxy shuts down, the health status of that proxy should immediately be updated. This is purely a courtesy and technically not required --- codersdk/workspaceproxy.go | 22 ++++++- enterprise/cli/proxyserver.go | 1 + enterprise/coderd/coderd.go | 4 +- .../coderd/coderdenttest/coderdenttest.go | 2 + enterprise/coderd/workspaceproxy.go | 56 +++++++++++++++-- enterprise/coderd/workspaceproxy_test.go | 61 +++++++++++++++++++ enterprise/wsproxy/wsproxy.go | 14 +++++ enterprise/wsproxy/wsproxysdk/wsproxysdk.go | 16 +++++ 8 files changed, 168 insertions(+), 8 deletions(-) diff --git a/codersdk/workspaceproxy.go b/codersdk/workspaceproxy.go index 88b7df89c5e53..5fb599465f8f9 100644 --- a/codersdk/workspaceproxy.go +++ b/codersdk/workspaceproxy.go @@ -15,9 +15,9 @@ import ( type ProxyHealthStatus string const ( - // ProxyReachable means the proxy access url is reachable and returns a healthy + // ProxyHealthy means the proxy access url is reachable and returns a healthy // status code. - ProxyReachable ProxyHealthStatus = "reachable" + ProxyHealthy ProxyHealthStatus = "ok" // ProxyUnreachable means the proxy access url is not responding. ProxyUnreachable ProxyHealthStatus = "unreachable" // ProxyUnhealthy means the proxy access url is responding, but there is some @@ -110,6 +110,24 @@ func (c *Client) WorkspaceProxies(ctx context.Context) ([]WorkspaceProxy, error) return proxies, json.NewDecoder(res.Body).Decode(&proxies) } +func (c *Client) WorkspaceProxyByName(ctx context.Context, name string) (WorkspaceProxy, error) { + res, err := c.Request(ctx, http.MethodGet, + fmt.Sprintf("/api/v2/workspaceproxies/%s", name), + nil, + ) + if err != nil { + return WorkspaceProxy{}, xerrors.Errorf("make request: %w", err) + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + return WorkspaceProxy{}, ReadBodyAsError(res) + } + + var proxy WorkspaceProxy + return proxy, json.NewDecoder(res.Body).Decode(&proxy) +} + func (c *Client) DeleteWorkspaceProxyByName(ctx context.Context, name string) error { res, err := c.Request(ctx, http.MethodDelete, fmt.Sprintf("/api/v2/workspaceproxies/%s", name), diff --git a/enterprise/cli/proxyserver.go b/enterprise/cli/proxyserver.go index 06e3a047b9e4e..eabc1c7659e5a 100644 --- a/enterprise/cli/proxyserver.go +++ b/enterprise/cli/proxyserver.go @@ -249,6 +249,7 @@ func (*RootCmd) proxyServer() *clibase.Cmd { if err != nil { return xerrors.Errorf("create workspace proxy: %w", err) } + closers.Add(func() { _ = proxy.Close() }) shutdownConnsCtx, shutdownConns := context.WithCancel(ctx) defer shutdownConns() diff --git a/enterprise/coderd/coderd.go b/enterprise/coderd/coderd.go index 190a552a80c99..017bb688c97cb 100644 --- a/enterprise/coderd/coderd.go +++ b/enterprise/coderd/coderd.go @@ -113,6 +113,7 @@ func New(ctx context.Context, options *Options) (*API, error) { ) r.Post("/issue-signed-app-token", api.workspaceProxyIssueSignedAppToken) r.Post("/register", api.workspaceProxyRegister) + r.Post("/goingaway", api.workspaceProxyGoingAway) }) r.Route("/{workspaceproxy}", func(r chi.Router) { r.Use( @@ -120,6 +121,7 @@ func New(ctx context.Context, options *Options) (*API, error) { httpmw.ExtractWorkspaceProxyParam(api.Database), ) + r.Get("/", api.getWorkspaceProxy) r.Delete("/", api.deleteWorkspaceProxy) }) }) @@ -237,7 +239,7 @@ func New(ctx context.Context, options *Options) (*API, error) { if api.AGPL.Experiments.Enabled(codersdk.ExperimentMoons) { // Proxy health is a moon feature. api.ProxyHealth, err = proxyhealth.New(&proxyhealth.Options{ - Interval: time.Minute * 1, + Interval: options.ProxyHealthInterval, DB: api.Database, Logger: options.Logger.Named("proxyhealth"), Client: api.HTTPClient, diff --git a/enterprise/coderd/coderdenttest/coderdenttest.go b/enterprise/coderd/coderdenttest/coderdenttest.go index 4dab0efbb31d1..40f819ea67507 100644 --- a/enterprise/coderd/coderdenttest/coderdenttest.go +++ b/enterprise/coderd/coderdenttest/coderdenttest.go @@ -48,6 +48,7 @@ type Options struct { EntitlementsUpdateInterval time.Duration SCIMAPIKey []byte UserWorkspaceQuota int + ProxyHealthInterval time.Duration } // New constructs a codersdk client connected to an in-memory Enterprise API instance. @@ -74,6 +75,7 @@ func NewWithAPI(t *testing.T, options *Options) (*codersdk.Client, io.Closer, *c Options: oop, EntitlementsUpdateInterval: options.EntitlementsUpdateInterval, Keys: Keys, + ProxyHealthInterval: options.ProxyHealthInterval, }) assert.NoError(t, err) setHandler(coderAPI.AGPL.RootHandler) diff --git a/enterprise/coderd/workspaceproxy.go b/enterprise/coderd/workspaceproxy.go index bd4e910838f49..8b940025e0b41 100644 --- a/enterprise/coderd/workspaceproxy.go +++ b/enterprise/coderd/workspaceproxy.go @@ -68,11 +68,7 @@ func (api *API) regions(rw http.ResponseWriter, r *http.Request) { continue } - health, ok := proxyHealth[proxy.ID] - if !ok { - health.Status = proxyhealth.Unknown - } - + health := proxyHealth[proxy.ID] regions = append(regions, codersdk.Region{ ID: proxy.ID, Name: proxy.Name, @@ -254,6 +250,24 @@ func (api *API) workspaceProxies(rw http.ResponseWriter, r *http.Request) { httpapi.Write(ctx, rw, http.StatusOK, convertProxies(proxies, statues)) } +// @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) getWorkspaceProxy(rw http.ResponseWriter, r *http.Request) { + var ( + ctx = r.Context() + proxy = httpmw.WorkspaceProxyParam(r) + ) + + status := api.ProxyHealth.HealthStatus()[proxy.ID] + httpapi.Write(ctx, rw, http.StatusOK, convertProxy(proxy, status)) +} + // @Summary Issue signed workspace app token // @ID issue-signed-workspace-app-token // @Security CoderSessionToken @@ -371,6 +385,35 @@ func (api *API) workspaceProxyRegister(rw http.ResponseWriter, r *http.Request) go api.forceWorkspaceProxyHealthUpdate(api.ctx) } +// workspaceProxyGoingAway is used to tell coderd that the workspace proxy is +// shutting down and going away. The main purpose of this function is for the +// health status of the workspace proxy to be more quickly updated when we know +// that the proxy is going to be unhealthy. This does not delete the workspace +// or cause any other side effects. +// If the workspace proxy comes back online, even without a register, it will +// be found healthy again by the normal checks. +// @Summary Workspace proxy going away +// @ID workspace-proxy-going-away +// @Security CoderSessionToken +// @Produce json +// @Tags Enterprise +// @Success 201 {object} codersdk.Response +// @Router /workspaceproxies/me/goingaway [post] +// @x-apidocgen {"skip": true} +func (api *API) workspaceProxyGoingAway(rw http.ResponseWriter, r *http.Request) { + var ( + ctx = r.Context() + ) + + // Force a health update to happen immediately. The proxy should + // not return a successful response if it is going away. + go api.forceWorkspaceProxyHealthUpdate(api.ctx) + + httpapi.Write(ctx, rw, http.StatusOK, codersdk.Response{ + Message: "OK", + }) +} + // reconnectingPTYSignedToken issues a signed app token for use when connecting // to the reconnecting PTY websocket on an external workspace proxy. This is set // by the client as a query parameter when connecting. @@ -476,6 +519,9 @@ func convertProxies(p []database.WorkspaceProxy, statuses map[uuid.UUID]proxyhea } func convertProxy(p database.WorkspaceProxy, status proxyhealth.ProxyStatus) codersdk.WorkspaceProxy { + if status.Status == "" { + status.Status = proxyhealth.Unknown + } return codersdk.WorkspaceProxy{ ID: p.ID, Name: p.Name, diff --git a/enterprise/coderd/workspaceproxy_test.go b/enterprise/coderd/workspaceproxy_test.go index 4a48a0b7349da..cdac80b3217d9 100644 --- a/enterprise/coderd/workspaceproxy_test.go +++ b/enterprise/coderd/workspaceproxy_test.go @@ -7,6 +7,7 @@ import ( "net/http/httputil" "net/url" "testing" + "time" "github.com/google/uuid" "github.com/moby/moby/pkg/namesgenerator" @@ -172,6 +173,66 @@ func TestRegions(t *testing.T) { require.Error(t, err) require.Empty(t, regions) }) + + t.Run("GoingAway", func(t *testing.T) { + t.Parallel() + + dv := coderdtest.DeploymentValues(t) + dv.Experiments = []string{ + string(codersdk.ExperimentMoons), + "*", + } + + db, pubsub := dbtestutil.NewDB(t) + + ctx := testutil.Context(t, testutil.WaitLong) + + client, closer, api := coderdenttest.NewWithAPI(t, &coderdenttest.Options{ + Options: &coderdtest.Options{ + AppHostname: appHostname, + Database: db, + Pubsub: pubsub, + DeploymentValues: dv, + }, + // The interval is set to 1 hour so the proxy health + // check will never happen manually. All checks will be + // forced updates. + ProxyHealthInterval: time.Hour, + }) + t.Cleanup(func() { + _ = closer.Close() + }) + _ = coderdtest.CreateFirstUser(t, client) + _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ + Features: license.Features{ + codersdk.FeatureWorkspaceProxy: 1, + }, + }) + + const proxyName = "testproxy" + proxy := coderdenttest.NewWorkspaceProxy(t, api, client, &coderdenttest.ProxyOptions{ + Name: proxyName, + }) + var _ = proxy + + require.Eventuallyf(t, func() bool { + proxy, err := client.WorkspaceProxyByName(ctx, proxyName) + if err != nil { + return false + } + return proxy.Status.Status == codersdk.ProxyHealthy + }, time.Second*10, time.Millisecond*100, "proxy never became healthy") + + _ = proxy.Close() + // The proxy should tell the primary on close that is is no longer healthy. + require.Eventuallyf(t, func() bool { + proxy, err := client.WorkspaceProxyByName(ctx, proxyName) + if err != nil { + return false + } + return proxy.Status.Status == codersdk.ProxyUnhealthy + }, time.Second*10, time.Millisecond*100, "proxy never became unhealthy after close") + }) } func TestWorkspaceProxyCRUD(t *testing.T) { diff --git a/enterprise/wsproxy/wsproxy.go b/enterprise/wsproxy/wsproxy.go index f617f00c0581a..2e910eea68009 100644 --- a/enterprise/wsproxy/wsproxy.go +++ b/enterprise/wsproxy/wsproxy.go @@ -250,6 +250,11 @@ func New(ctx context.Context, opts *Options) (*Server, error) { func (s *Server) Close() error { s.cancel() + go func() { + // Do this in a go routine to not block the close. This is allowed + // to fail, it is just a courtesy to the dashboard. + _ = s.SDKClient.WorkspaceProxyGoingAway(context.Background()) + }() return s.AppServer.Close() } @@ -279,6 +284,15 @@ func (s *Server) healthReport(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() var report codersdk.ProxyHealthReport + // This is to catch edge cases where the server is shutting down, but might + // still serve a web request that returns "healthy". This is mainly just for + // unit tests, as shutting down the test webserver is tied to the lifecycle + // of the test. In practice, the webserver is tied to the lifecycle of the + // app, so the webserver AND the proxy will be shut down at the same time. + if s.ctx.Err() != nil { + httpapi.Write(r.Context(), rw, http.StatusInternalServerError, "workspace proxy in middle of shutting down") + } + // Hit the build info to do basic version checking. primaryBuild, err := s.SDKClient.SDKClient.BuildInfo(ctx) if err != nil { diff --git a/enterprise/wsproxy/wsproxysdk/wsproxysdk.go b/enterprise/wsproxy/wsproxysdk/wsproxysdk.go index cd2fdf27882dc..592610ec73afd 100644 --- a/enterprise/wsproxy/wsproxysdk/wsproxysdk.go +++ b/enterprise/wsproxy/wsproxysdk/wsproxysdk.go @@ -170,3 +170,19 @@ func (c *Client) RegisterWorkspaceProxy(ctx context.Context, req RegisterWorkspa var resp RegisterWorkspaceProxyResponse return resp, json.NewDecoder(res.Body).Decode(&resp) } + +func (c *Client) WorkspaceProxyGoingAway(ctx context.Context) error { + res, err := c.Request(ctx, http.MethodPost, + "/api/v2/workspaceproxies/me/goingaway", + nil, + ) + if err != nil { + return xerrors.Errorf("make request: %w", err) + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + return codersdk.ReadBodyAsError(res) + } + return nil +} From 98a7740255ef6a3f28ff8bfdf8dcf7db8ed76e2d Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 8 May 2023 17:17:28 -0500 Subject: [PATCH 2/7] Fix going away --- enterprise/wsproxy/wsproxy.go | 11 ++++++----- site/src/api/typesGenerated.ts | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/enterprise/wsproxy/wsproxy.go b/enterprise/wsproxy/wsproxy.go index 2e910eea68009..c8312b9b59e0c 100644 --- a/enterprise/wsproxy/wsproxy.go +++ b/enterprise/wsproxy/wsproxy.go @@ -250,11 +250,12 @@ func New(ctx context.Context, opts *Options) (*Server, error) { func (s *Server) Close() error { s.cancel() - go func() { - // Do this in a go routine to not block the close. This is allowed - // to fail, it is just a courtesy to the dashboard. - _ = s.SDKClient.WorkspaceProxyGoingAway(context.Background()) - }() + + // A timeout to prevent the SDK from blocking the server shutdown. + tmp, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + _ = s.SDKClient.WorkspaceProxyGoingAway(tmp) + return s.AppServer.Close() } diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 8bc4b1e6aedc7..8be379f26ae82 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1485,12 +1485,12 @@ export const ProvisionerTypes: ProvisionerType[] = ["echo", "terraform"] // From codersdk/workspaceproxy.go export type ProxyHealthStatus = - | "reachable" + | "ok" | "unhealthy" | "unreachable" | "unregistered" export const ProxyHealthStatuses: ProxyHealthStatus[] = [ - "reachable", + "ok", "unhealthy", "unreachable", "unregistered", From 42f81542add98575b0691430fece89106db03f3c Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 8 May 2023 17:18:52 -0500 Subject: [PATCH 3/7] Fix linting --- enterprise/coderd/coderd.go | 2 +- enterprise/coderd/workspaceproxy.go | 2 +- enterprise/coderd/workspaceproxy_test.go | 4 ++-- enterprise/wsproxy/wsproxy.go | 1 + 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/enterprise/coderd/coderd.go b/enterprise/coderd/coderd.go index 017bb688c97cb..e533230f3d346 100644 --- a/enterprise/coderd/coderd.go +++ b/enterprise/coderd/coderd.go @@ -121,7 +121,7 @@ func New(ctx context.Context, options *Options) (*API, error) { httpmw.ExtractWorkspaceProxyParam(api.Database), ) - r.Get("/", api.getWorkspaceProxy) + r.Get("/", api.fetchWorkspaceProxy) r.Delete("/", api.deleteWorkspaceProxy) }) }) diff --git a/enterprise/coderd/workspaceproxy.go b/enterprise/coderd/workspaceproxy.go index 8b940025e0b41..dfd9b0da244ca 100644 --- a/enterprise/coderd/workspaceproxy.go +++ b/enterprise/coderd/workspaceproxy.go @@ -258,7 +258,7 @@ func (api *API) workspaceProxies(rw http.ResponseWriter, r *http.Request) { // @Param workspaceproxy path string true "Proxy ID or name" format(uuid) // @Success 200 {object} codersdk.WorkspaceProxy // @Router /workspaceproxies/{workspaceproxy} [get] -func (api *API) getWorkspaceProxy(rw http.ResponseWriter, r *http.Request) { +func (api *API) fetchWorkspaceProxy(rw http.ResponseWriter, r *http.Request) { var ( ctx = r.Context() proxy = httpmw.WorkspaceProxyParam(r) diff --git a/enterprise/coderd/workspaceproxy_test.go b/enterprise/coderd/workspaceproxy_test.go index cdac80b3217d9..02ba18274eacc 100644 --- a/enterprise/coderd/workspaceproxy_test.go +++ b/enterprise/coderd/workspaceproxy_test.go @@ -221,7 +221,7 @@ func TestRegions(t *testing.T) { return false } return proxy.Status.Status == codersdk.ProxyHealthy - }, time.Second*10, time.Millisecond*100, "proxy never became healthy") + }, testutil.WaitShort, testutil.IntervalFast, "proxy never became healthy") _ = proxy.Close() // The proxy should tell the primary on close that is is no longer healthy. @@ -231,7 +231,7 @@ func TestRegions(t *testing.T) { return false } return proxy.Status.Status == codersdk.ProxyUnhealthy - }, time.Second*10, time.Millisecond*100, "proxy never became unhealthy after close") + }, testutil.WaitShort, testutil.IntervalFast, "proxy never became unhealthy after close") }) } diff --git a/enterprise/wsproxy/wsproxy.go b/enterprise/wsproxy/wsproxy.go index c8312b9b59e0c..386f26bf6605c 100644 --- a/enterprise/wsproxy/wsproxy.go +++ b/enterprise/wsproxy/wsproxy.go @@ -292,6 +292,7 @@ func (s *Server) healthReport(rw http.ResponseWriter, r *http.Request) { // app, so the webserver AND the proxy will be shut down at the same time. if s.ctx.Err() != nil { httpapi.Write(r.Context(), rw, http.StatusInternalServerError, "workspace proxy in middle of shutting down") + return } // Hit the build info to do basic version checking. From e572fef3624ceedf01fa15177da1ce5c645eeb14 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 8 May 2023 22:20:26 +0000 Subject: [PATCH 4/7] Make gen --- coderd/apidoc/docs.go | 65 +++++++++++++++++++++++- coderd/apidoc/swagger.json | 57 ++++++++++++++++++++- docs/api/enterprise.md | 58 +++++++++++++++++++-- docs/api/schemas.md | 8 +-- enterprise/coderd/workspaceproxy.go | 4 +- enterprise/coderd/workspaceproxy_test.go | 2 +- 6 files changed, 179 insertions(+), 15 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 2ad130318dcbb..10ff16b26eede 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -5092,6 +5092,34 @@ const docTemplate = `{ } } }, + "/workspaceproxies/me/goingaway": { + "post": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Enterprise" + ], + "summary": "Workspace proxy going away", + "operationId": "workspace-proxy-going-away", + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/codersdk.Response" + } + } + }, + "x-apidocgen": { + "skip": true + } + } + }, "/workspaceproxies/me/issue-signed-app-token": { "post": { "security": [ @@ -5177,6 +5205,39 @@ const docTemplate = `{ } }, "/workspaceproxies/{workspaceproxy}": { + "get": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Enterprise" + ], + "summary": "Get workspace proxy", + "operationId": "get-workspace-proxy", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "Proxy ID or name", + "name": "workspaceproxy", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.WorkspaceProxy" + } + } + } + }, "delete": { "security": [ { @@ -8313,13 +8374,13 @@ const docTemplate = `{ "codersdk.ProxyHealthStatus": { "type": "string", "enum": [ - "reachable", + "ok", "unreachable", "unhealthy", "unregistered" ], "x-enum-varnames": [ - "ProxyReachable", + "ProxyHealthy", "ProxyUnreachable", "ProxyUnhealthy", "ProxyUnregistered" diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index e2a593c1a415c..558368335994e 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -4480,6 +4480,30 @@ } } }, + "/workspaceproxies/me/goingaway": { + "post": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": ["application/json"], + "tags": ["Enterprise"], + "summary": "Workspace proxy going away", + "operationId": "workspace-proxy-going-away", + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/codersdk.Response" + } + } + }, + "x-apidocgen": { + "skip": true + } + } + }, "/workspaceproxies/me/issue-signed-app-token": { "post": { "security": [ @@ -4553,6 +4577,35 @@ } }, "/workspaceproxies/{workspaceproxy}": { + "get": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": ["application/json"], + "tags": ["Enterprise"], + "summary": "Get workspace proxy", + "operationId": "get-workspace-proxy", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "Proxy ID or name", + "name": "workspaceproxy", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.WorkspaceProxy" + } + } + } + }, "delete": { "security": [ { @@ -7437,9 +7490,9 @@ }, "codersdk.ProxyHealthStatus": { "type": "string", - "enum": ["reachable", "unreachable", "unhealthy", "unregistered"], + "enum": ["ok", "unreachable", "unhealthy", "unregistered"], "x-enum-varnames": [ - "ProxyReachable", + "ProxyHealthy", "ProxyUnreachable", "ProxyUnhealthy", "ProxyUnregistered" diff --git a/docs/api/enterprise.md b/docs/api/enterprise.md index fbee85b9970f1..0d98203151208 100644 --- a/docs/api/enterprise.md +++ b/docs/api/enterprise.md @@ -1191,7 +1191,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaceproxies \ "errors": ["string"], "warnings": ["string"] }, - "status": "reachable" + "status": "ok" }, "updated_at": "2019-08-24T14:15:22Z", "url": "string", @@ -1232,7 +1232,7 @@ Status Code **200** | Property | Value | | -------- | -------------- | -| `status` | `reachable` | +| `status` | `ok` | | `status` | `unreachable` | | `status` | `unhealthy` | | `status` | `unregistered` | @@ -1286,7 +1286,7 @@ curl -X POST http://coder-server:8080/api/v2/workspaceproxies \ "errors": ["string"], "warnings": ["string"] }, - "status": "reachable" + "status": "ok" }, "updated_at": "2019-08-24T14:15:22Z", "url": "string", @@ -1302,6 +1302,58 @@ curl -X POST http://coder-server:8080/api/v2/workspaceproxies \ To perform this operation, you must be authenticated. [Learn more](authentication.md). +## Get workspace proxy + +### Code samples + +```shell +# Example request using curl +curl -X GET http://coder-server:8080/api/v2/workspaceproxies/{workspaceproxy} \ + -H 'Accept: application/json' \ + -H 'Coder-Session-Token: API_KEY' +``` + +`GET /workspaceproxies/{workspaceproxy}` + +### Parameters + +| Name | In | Type | Required | Description | +| ---------------- | ---- | ------------ | -------- | ---------------- | +| `workspaceproxy` | path | string(uuid) | true | Proxy ID or name | + +### Example responses + +> 200 Response + +```json +{ + "created_at": "2019-08-24T14:15:22Z", + "deleted": true, + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "name": "string", + "status": { + "checked_at": "2019-08-24T14:15:22Z", + "report": { + "errors": ["string"], + "warnings": ["string"] + }, + "status": "ok" + }, + "updated_at": "2019-08-24T14:15:22Z", + "url": "string", + "wildcard_hostname": "string" +} +``` + +### Responses + +| Status | Meaning | Description | Schema | +| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------------------ | +| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.WorkspaceProxy](schemas.md#codersdkworkspaceproxy) | + +To perform this operation, you must be authenticated. [Learn more](authentication.md). + ## Delete workspace proxy ### Code samples diff --git a/docs/api/schemas.md b/docs/api/schemas.md index 6a82b9a1c98ec..df3bffc6e1cfe 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -3454,7 +3454,7 @@ Parameter represents a set value for the scope. ## codersdk.ProxyHealthStatus ```json -"reachable" +"ok" ``` ### Properties @@ -3463,7 +3463,7 @@ Parameter represents a set value for the scope. | Value | | -------------- | -| `reachable` | +| `ok` | | `unreachable` | | `unhealthy` | | `unregistered` | @@ -5338,7 +5338,7 @@ Parameter represents a set value for the scope. "errors": ["string"], "warnings": ["string"] }, - "status": "reachable" + "status": "ok" }, "updated_at": "2019-08-24T14:15:22Z", "url": "string", @@ -5369,7 +5369,7 @@ Parameter represents a set value for the scope. "errors": ["string"], "warnings": ["string"] }, - "status": "reachable" + "status": "ok" } ``` diff --git a/enterprise/coderd/workspaceproxy.go b/enterprise/coderd/workspaceproxy.go index dfd9b0da244ca..7f86554a7f0cd 100644 --- a/enterprise/coderd/workspaceproxy.go +++ b/enterprise/coderd/workspaceproxy.go @@ -401,9 +401,7 @@ func (api *API) workspaceProxyRegister(rw http.ResponseWriter, r *http.Request) // @Router /workspaceproxies/me/goingaway [post] // @x-apidocgen {"skip": true} func (api *API) workspaceProxyGoingAway(rw http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - ) + ctx := r.Context() // Force a health update to happen immediately. The proxy should // not return a successful response if it is going away. diff --git a/enterprise/coderd/workspaceproxy_test.go b/enterprise/coderd/workspaceproxy_test.go index 02ba18274eacc..1624d04caf7e1 100644 --- a/enterprise/coderd/workspaceproxy_test.go +++ b/enterprise/coderd/workspaceproxy_test.go @@ -213,7 +213,7 @@ func TestRegions(t *testing.T) { proxy := coderdenttest.NewWorkspaceProxy(t, api, client, &coderdenttest.ProxyOptions{ Name: proxyName, }) - var _ = proxy + _ = proxy require.Eventuallyf(t, func() bool { proxy, err := client.WorkspaceProxyByName(ctx, proxyName) From 5f9d2163c09b489191a0eb2448b84e48cd2e379f Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 8 May 2023 17:37:13 -0500 Subject: [PATCH 5/7] Help test --- enterprise/coderd/workspaceproxy_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/enterprise/coderd/workspaceproxy_test.go b/enterprise/coderd/workspaceproxy_test.go index 1624d04caf7e1..4011e34179562 100644 --- a/enterprise/coderd/workspaceproxy_test.go +++ b/enterprise/coderd/workspaceproxy_test.go @@ -218,6 +218,9 @@ func TestRegions(t *testing.T) { require.Eventuallyf(t, func() bool { proxy, err := client.WorkspaceProxyByName(ctx, proxyName) if err != nil { + // We are testing the going away, not the initial healthy. + // Just force an update to change this to healthy. + _ = api.ProxyHealth.ForceUpdate(ctx) return false } return proxy.Status.Status == codersdk.ProxyHealthy From 2608bbcbb75421a63c1d9e75dbd97837521703e0 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 10 May 2023 15:29:25 -0500 Subject: [PATCH 6/7] Merge issues --- codersdk/workspaceproxy.go | 2 +- enterprise/coderd/workspaceproxy.go | 18 ------------------ 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/codersdk/workspaceproxy.go b/codersdk/workspaceproxy.go index cb63ab80384c1..1c10e9981b2d2 100644 --- a/codersdk/workspaceproxy.go +++ b/codersdk/workspaceproxy.go @@ -89,7 +89,7 @@ func (c *Client) CreateWorkspaceProxy(ctx context.Context, req CreateWorkspacePr if res.StatusCode != http.StatusCreated { return UpdateWorkspaceProxyResponse{}, ReadBodyAsError(res) } - var resp CreateWorkspaceProxyResponse + var resp UpdateWorkspaceProxyResponse return resp, json.NewDecoder(res.Body).Decode(&resp) } diff --git a/enterprise/coderd/workspaceproxy.go b/enterprise/coderd/workspaceproxy.go index 23ecc56dbed13..501f2a25b7d68 100644 --- a/enterprise/coderd/workspaceproxy.go +++ b/enterprise/coderd/workspaceproxy.go @@ -338,24 +338,6 @@ func (api *API) workspaceProxies(rw http.ResponseWriter, r *http.Request) { httpapi.Write(ctx, rw, http.StatusOK, convertProxies(proxies, statues)) } -// @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) fetchWorkspaceProxy(rw http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - proxy = httpmw.WorkspaceProxyParam(r) - ) - - status := api.ProxyHealth.HealthStatus()[proxy.ID] - httpapi.Write(ctx, rw, http.StatusOK, convertProxy(proxy, status)) -} - // @Summary Issue signed workspace app token // @ID issue-signed-workspace-app-token // @Security CoderSessionToken From dbd37ac9b9a76b9668a9e19ca852f9a02eb16e90 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 10 May 2023 20:31:59 +0000 Subject: [PATCH 7/7] fmt --- docs/api/enterprise.md | 128 +++++++++++++++++++++++++++- enterprise/coderd/workspaceproxy.go | 2 +- 2 files changed, 126 insertions(+), 4 deletions(-) diff --git a/docs/api/enterprise.md b/docs/api/enterprise.md index 91664a173a326..c5ecaea1c26ff 100644 --- a/docs/api/enterprise.md +++ b/docs/api/enterprise.md @@ -1192,7 +1192,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaceproxies \ "errors": ["string"], "warnings": ["string"] }, - "status": "reachable" + "status": "ok" }, "updated_at": "2019-08-24T14:15:22Z", "url": "string", @@ -1216,6 +1216,7 @@ Status Code **200** | `[array item]` | array | false | | | | `» created_at` | string(date-time) | false | | | | `» deleted` | boolean | false | | | +| `» display_name` | string | false | | | | `» icon` | string | false | | | | `» id` | string(uuid) | false | | | | `» name` | string | false | | | @@ -1233,7 +1234,7 @@ Status Code **200** | Property | Value | | -------- | -------------- | -| `status` | `reachable` | +| `status` | `ok` | | `status` | `unreachable` | | `status` | `unhealthy` | | `status` | `unregistered` | @@ -1278,6 +1279,7 @@ curl -X POST http://coder-server:8080/api/v2/workspaceproxies \ { "created_at": "2019-08-24T14:15:22Z", "deleted": true, + "display_name": "string", "icon": "string", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "name": "string", @@ -1287,7 +1289,7 @@ curl -X POST http://coder-server:8080/api/v2/workspaceproxies \ "errors": ["string"], "warnings": ["string"] }, - "status": "reachable" + "status": "ok" }, "updated_at": "2019-08-24T14:15:22Z", "url": "string", @@ -1303,6 +1305,59 @@ curl -X POST http://coder-server:8080/api/v2/workspaceproxies \ To perform this operation, you must be authenticated. [Learn more](authentication.md). +## Get workspace proxy + +### Code samples + +```shell +# Example request using curl +curl -X GET http://coder-server:8080/api/v2/workspaceproxies/{workspaceproxy} \ + -H 'Accept: application/json' \ + -H 'Coder-Session-Token: API_KEY' +``` + +`GET /workspaceproxies/{workspaceproxy}` + +### Parameters + +| Name | In | Type | Required | Description | +| ---------------- | ---- | ------------ | -------- | ---------------- | +| `workspaceproxy` | path | string(uuid) | true | Proxy ID or name | + +### Example responses + +> 200 Response + +```json +{ + "created_at": "2019-08-24T14:15:22Z", + "deleted": true, + "display_name": "string", + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "name": "string", + "status": { + "checked_at": "2019-08-24T14:15:22Z", + "report": { + "errors": ["string"], + "warnings": ["string"] + }, + "status": "ok" + }, + "updated_at": "2019-08-24T14:15:22Z", + "url": "string", + "wildcard_hostname": "string" +} +``` + +### Responses + +| Status | Meaning | Description | Schema | +| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------------------ | +| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.WorkspaceProxy](schemas.md#codersdkworkspaceproxy) | + +To perform this operation, you must be authenticated. [Learn more](authentication.md). + ## Delete workspace proxy ### Code samples @@ -1346,3 +1401,70 @@ curl -X DELETE http://coder-server:8080/api/v2/workspaceproxies/{workspaceproxy} | 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.Response](schemas.md#codersdkresponse) | To perform this operation, you must be authenticated. [Learn more](authentication.md). + +## Update workspace proxy + +### Code samples + +```shell +# Example request using curl +curl -X PATCH http://coder-server:8080/api/v2/workspaceproxies/{workspaceproxy} \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -H 'Coder-Session-Token: API_KEY' +``` + +`PATCH /workspaceproxies/{workspaceproxy}` + +> Body parameter + +```json +{ + "display_name": "string", + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "name": "string", + "regenerate_token": true +} +``` + +### Parameters + +| Name | In | Type | Required | Description | +| ---------------- | ---- | ---------------------------------------------------------------------- | -------- | ------------------------------ | +| `workspaceproxy` | path | string(uuid) | true | Proxy ID or name | +| `body` | body | [codersdk.PatchWorkspaceProxy](schemas.md#codersdkpatchworkspaceproxy) | true | Update workspace proxy request | + +### Example responses + +> 200 Response + +```json +{ + "created_at": "2019-08-24T14:15:22Z", + "deleted": true, + "display_name": "string", + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "name": "string", + "status": { + "checked_at": "2019-08-24T14:15:22Z", + "report": { + "errors": ["string"], + "warnings": ["string"] + }, + "status": "ok" + }, + "updated_at": "2019-08-24T14:15:22Z", + "url": "string", + "wildcard_hostname": "string" +} +``` + +### Responses + +| Status | Meaning | Description | Schema | +| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------------------ | +| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.WorkspaceProxy](schemas.md#codersdkworkspaceproxy) | + +To perform this operation, you must be authenticated. [Learn more](authentication.md). diff --git a/enterprise/coderd/workspaceproxy.go b/enterprise/coderd/workspaceproxy.go index 501f2a25b7d68..f0e6da58618c0 100644 --- a/enterprise/coderd/workspaceproxy.go +++ b/enterprise/coderd/workspaceproxy.go @@ -419,7 +419,7 @@ func (api *API) workspaceProxyRegister(rw http.ResponseWriter, r *http.Request) // Log: api.Logger, // Request: r, // Action: database.AuditActionWrite, - //}) + // }) ) // aReq.Old = proxy // defer commitAudit()