From 872e21490327ccb7daf097a977bca70bc1ae0772 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Fri, 3 Nov 2023 21:28:41 +0000 Subject: [PATCH 1/9] feat: add endpoint for resolving workspace autostart --- coderd/apidoc/docs.go | 43 +++++++++++ coderd/apidoc/swagger.json | 39 ++++++++++ coderd/coderd.go | 1 + coderd/database/db2sdk/db2sdk.go | 14 ++++ coderd/workspaces.go | 108 +++++++++++++++++++++++++++ coderd/workspaces_test.go | 93 +++++++++++++++++++++++ codersdk/workspaces.go | 17 +++++ docs/api/schemas.md | 14 ++++ docs/api/workspaces.md | 31 ++++++++ enterprise/coderd/workspaces_test.go | 66 ++++++++++++++++ site/src/api/typesGenerated.ts | 5 ++ 11 files changed, 431 insertions(+) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 89c256ca85c48..720ddd282cdd2 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -6480,6 +6480,41 @@ const docTemplate = `{ } } }, + "/workspaces/{workspace}/resolve": { + "get": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "consumes": [ + "application/json" + ], + "tags": [ + "Workspaces" + ], + "summary": "Determine whether a workspace is capable of autostarting.", + "operationId": "resolve-workspace-autostart-by-id", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "Workspace ID", + "name": "workspace", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.ResolveAutostartResponse" + } + } + } + } + }, "/workspaces/{workspace}/ttl": { "put": { "security": [ @@ -9720,6 +9755,14 @@ const docTemplate = `{ } } }, + "codersdk.ResolveAutostartResponse": { + "type": "object", + "properties": { + "parameter_mismatch": { + "type": "boolean" + } + } + }, "codersdk.ResourceType": { "type": "string", "enum": [ diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index ab66040190ad1..6f2c6d70b7dc1 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -5718,6 +5718,37 @@ } } }, + "/workspaces/{workspace}/resolve": { + "get": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "consumes": ["application/json"], + "tags": ["Workspaces"], + "summary": "Determine whether a workspace is capable of autostarting.", + "operationId": "resolve-workspace-autostart-by-id", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "Workspace ID", + "name": "workspace", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.ResolveAutostartResponse" + } + } + } + } + }, "/workspaces/{workspace}/ttl": { "put": { "security": [ @@ -8756,6 +8787,14 @@ } } }, + "codersdk.ResolveAutostartResponse": { + "type": "object", + "properties": { + "parameter_mismatch": { + "type": "boolean" + } + } + }, "codersdk.ResourceType": { "type": "string", "enum": [ diff --git a/coderd/coderd.go b/coderd/coderd.go index 81ec1e87e6b18..e9155531f0dba 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -885,6 +885,7 @@ func New(options *Options) *API { r.Put("/extend", api.putExtendWorkspace) r.Put("/dormant", api.putWorkspaceDormant) r.Put("/autoupdates", api.putWorkspaceAutoupdates) + r.Get("/resolve", api.resolveAutostart) }) }) r.Route("/workspacebuilds/{workspacebuild}", func(r chi.Router) { diff --git a/coderd/database/db2sdk/db2sdk.go b/coderd/database/db2sdk/db2sdk.go index a2b1d85ac89bb..6add6d0146796 100644 --- a/coderd/database/db2sdk/db2sdk.go +++ b/coderd/database/db2sdk/db2sdk.go @@ -7,6 +7,7 @@ import ( "github.com/google/uuid" "golang.org/x/exp/slices" + "golang.org/x/xerrors" "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/parameter" @@ -30,6 +31,19 @@ func WorkspaceBuildParameter(p database.WorkspaceBuildParameter) codersdk.Worksp } } +func TemplateVersionParameters(params []database.TemplateVersionParameter) ([]codersdk.TemplateVersionParameter, error) { + out := make([]codersdk.TemplateVersionParameter, len(params)) + var err error + for i, p := range params { + out[i], err = TemplateVersionParameter(p) + if err != nil { + return nil, xerrors.Errorf("convert template version parameter %q: %w", p.Name, err) + } + } + + return out, nil +} + func TemplateVersionParameter(param database.TemplateVersionParameter) (codersdk.TemplateVersionParameter, error) { options, err := templateVersionParameterOptions(param.Options) if err != nil { diff --git a/coderd/workspaces.go b/coderd/workspaces.go index bea87fb2f427a..7b3782fde396d 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -17,6 +17,7 @@ import ( "cdr.dev/slog" "github.com/coder/coder/v2/coderd/audit" "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/database/db2sdk" "github.com/coder/coder/v2/coderd/database/dbauthz" "github.com/coder/coder/v2/coderd/database/dbtime" "github.com/coder/coder/v2/coderd/database/provisionerjobs" @@ -1059,6 +1060,113 @@ func (api *API) putWorkspaceAutoupdates(rw http.ResponseWriter, r *http.Request) rw.WriteHeader(http.StatusNoContent) } +// @Summary Determine whether a workspace is capable of autostarting. +// @ID resolve-workspace-autostart-by-id +// @Security CoderSessionToken +// @Accept json +// @Tags Workspaces +// @Param workspace path string true "Workspace ID" format(uuid) +// @Success 200 {object} codersdk.ResolveAutostartResponse +// @Router /workspaces/{workspace}/resolve [get] +func (api *API) resolveAutostart(rw http.ResponseWriter, r *http.Request) { + var ( + ctx = r.Context() + workspace = httpmw.WorkspaceParam(r) + ) + + template, err := api.Database.GetTemplateByID(ctx, workspace.TemplateID) + if err != nil { + httpapi.InternalServerError(rw, err) + return + } + + useActiveVersion := template.RequireActiveVersion || workspace.AutomaticUpdates == database.AutomaticUpdatesAlways + if !useActiveVersion { + httpapi.Write(ctx, rw, http.StatusOK, codersdk.ResolveAutostartResponse{ + ParameterMismatch: true, + }) + return + } + + build, err := api.Database.GetLatestWorkspaceBuildByWorkspaceID(ctx, workspace.ID) + if err != nil { + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error fetching latest workspace build.", + Detail: err.Error(), + }) + return + } + + if build.TemplateVersionID == template.ActiveVersionID { + httpapi.Write(ctx, rw, http.StatusOK, codersdk.ResolveAutostartResponse{ + ParameterMismatch: false, + }) + return + } + + version, err := api.Database.GetTemplateVersionByID(ctx, template.ActiveVersionID) + if httpapi.Is404Error(err) { + httpapi.ResourceNotFound(rw) + return + } + if err != nil { + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error fetching template version.", + Detail: err.Error(), + }) + return + } + + if version.TemplateID.UUID != workspace.TemplateID { + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ + Message: "Workspace cannot be resolved against unrelated template.", + Detail: err.Error(), + }) + return + } + + dbVersionParams, err := api.Database.GetTemplateVersionParameters(ctx, version.ID) + if err != nil { + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ + Message: "Internal error fetching template version parameters.", + Detail: err.Error(), + }) + return + } + + dbBuildParams, err := api.Database.GetWorkspaceBuildParameters(ctx, build.ID) + if err != nil { + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error fetching latest workspace build parameters.", + Detail: err.Error(), + }) + return + } + + versionParams, err := db2sdk.TemplateVersionParameters(dbVersionParams) + if err != nil { + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error converting template version parameters.", + Detail: err.Error(), + }) + return + } + + resolver := codersdk.ParameterResolver{ + Rich: db2sdk.WorkspaceBuildParameters(dbBuildParams), + } + + var response codersdk.ResolveAutostartResponse + for i := 0; i < len(versionParams) && !response.ParameterMismatch; i++ { + _, err := resolver.ValidateResolve(versionParams[i], nil) + // There's a parameter mismatch if we get an error back from the + // resolver. + response.ParameterMismatch = err != nil + } + + httpapi.Write(ctx, rw, http.StatusOK, response) +} + // @Summary Watch workspace by ID // @ID watch-workspace-by-id // @Security CoderSessionToken diff --git a/coderd/workspaces_test.go b/coderd/workspaces_test.go index d3f5bfa00e276..002c8a7c00d8c 100644 --- a/coderd/workspaces_test.go +++ b/coderd/workspaces_test.go @@ -340,6 +340,99 @@ func TestWorkspace(t *testing.T) { }) } +func TestResolveAutostart(t *testing.T) { + t.Parallel() + + t.Run("OK", func(t *testing.T) { + t.Parallel() + ownerClient := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) + owner := coderdtest.CreateFirstUser(t, ownerClient) + version1 := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, nil) + coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, version1.ID) + template := coderdtest.CreateTemplate(t, ownerClient, owner.OrganizationID, version1.ID) + + params := &echo.Responses{ + Parse: echo.ParseComplete, + ProvisionPlan: []*proto.Response{ + { + Type: &proto.Response_Plan{ + Plan: &proto.PlanComplete{ + Parameters: []*proto.RichParameter{ + { + Name: "param", + Description: "param", + Required: true, + Mutable: true, + }, + }, + }, + }, + }, + }, + ProvisionApply: echo.ApplyComplete, + } + version2 := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, params, func(ctvr *codersdk.CreateTemplateVersionRequest) { + ctvr.TemplateID = template.ID + }) + coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, version2.ID) + + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) + defer cancel() + + client, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID) + workspace := coderdtest.CreateWorkspace(t, client, owner.OrganizationID, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) { + cwr.AutomaticUpdates = codersdk.AutomaticUpdatesAlways + }) + coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID) + + err := ownerClient.UpdateActiveTemplateVersion(ctx, template.ID, codersdk.UpdateActiveTemplateVersion{ + ID: version2.ID, + }) + require.NoError(t, err) + + // Autostart shouldn't be possible if parameters do not match. + resp, err := client.ResolveAutostart(ctx, workspace.ID.String()) + require.NoError(t, err) + require.True(t, resp.ParameterMismatch) + + update, err := client.CreateWorkspaceBuild(ctx, workspace.ID, codersdk.CreateWorkspaceBuildRequest{ + TemplateVersionID: version2.ID, + Transition: codersdk.WorkspaceTransitionStart, + RichParameterValues: []codersdk.WorkspaceBuildParameter{ + { + Name: "param", + Value: "Hello", + }, + }, + }) + require.NoError(t, err) + coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, update.ID) + + // We should be able to autostart since parameters are updated. + resp, err = client.ResolveAutostart(ctx, workspace.ID.String()) + require.NoError(t, err) + require.False(t, resp.ParameterMismatch) + + // Create one last version where the parameters are the same as the previous + // version. + version3 := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, params, func(ctvr *codersdk.CreateTemplateVersionRequest) { + ctvr.TemplateID = template.ID + }) + coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, version3.ID) + + err = ownerClient.UpdateActiveTemplateVersion(ctx, template.ID, codersdk.UpdateActiveTemplateVersion{ + ID: version3.ID, + }) + require.NoError(t, err) + + // Even though we're out of date we should still be able to autostart + // since parameters resolve. + resp, err = client.ResolveAutostart(ctx, workspace.ID.String()) + require.NoError(t, err) + require.False(t, resp.ParameterMismatch) + }) +} + func TestAdminViewAllWorkspaces(t *testing.T) { t.Parallel() client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) diff --git a/codersdk/workspaces.go b/codersdk/workspaces.go index 54f79aa58725d..f82882ddbabec 100644 --- a/codersdk/workspaces.go +++ b/codersdk/workspaces.go @@ -449,6 +449,23 @@ func (c *Client) WorkspaceQuota(ctx context.Context, userID string) (WorkspaceQu return quota, json.NewDecoder(res.Body).Decode("a) } +type ResolveAutostartResponse struct { + ParameterMismatch bool `json:"parameter_mismatch"` +} + +func (c *Client) ResolveAutostart(ctx context.Context, workspaceID string) (ResolveAutostartResponse, error) { + res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/workspaces/%s/resolve", workspaceID), nil) + if err != nil { + return ResolveAutostartResponse{}, err + } + defer res.Body.Close() + if res.StatusCode != http.StatusOK { + return ResolveAutostartResponse{}, ReadBodyAsError(res) + } + var response ResolveAutostartResponse + return response, json.NewDecoder(res.Body).Decode(&response) +} + // WorkspaceNotifyChannel is the PostgreSQL NOTIFY // channel to listen for updates on. The payload is empty, // because the size of a workspace payload can be very large. diff --git a/docs/api/schemas.md b/docs/api/schemas.md index 4111224c08ee3..517e76981c567 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -4100,6 +4100,20 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | `region_id` | integer | false | | Region ID is the region of the replica. | | `relay_address` | string | false | | Relay address is the accessible address to relay DERP connections. | +## codersdk.ResolveAutostartResponse + +```json +{ + "parameter_mismatch": true +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| -------------------- | ------- | -------- | ------------ | ----------- | +| `parameter_mismatch` | boolean | false | | | + ## codersdk.ResourceType ```json diff --git a/docs/api/workspaces.md b/docs/api/workspaces.md index 3d53fad1711ee..d2c075c0d2c05 100644 --- a/docs/api/workspaces.md +++ b/docs/api/workspaces.md @@ -1240,6 +1240,37 @@ curl -X PUT http://coder-server:8080/api/v2/workspaces/{workspace}/extend \ To perform this operation, you must be authenticated. [Learn more](authentication.md). +## Determine whether a workspace is capable of autostarting. + +### Code samples + +```shell +# Example request using curl +curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace}/resolve \ + -H 'Accept: */*' \ + -H 'Coder-Session-Token: API_KEY' +``` + +`GET /workspaces/{workspace}/resolve` + +### Parameters + +| Name | In | Type | Required | Description | +| ----------- | ---- | ------------ | -------- | ------------ | +| `workspace` | path | string(uuid) | true | Workspace ID | + +### Example responses + +> 200 Response + +### Responses + +| Status | Meaning | Description | Schema | +| ------ | ------------------------------------------------------- | ----------- | -------------------------------------------------------------------------------- | +| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.ResolveAutostartResponse](schemas.md#codersdkresolveautostartresponse) | + +To perform this operation, you must be authenticated. [Learn more](authentication.md). + ## Update workspace TTL by ID ### Code samples diff --git a/enterprise/coderd/workspaces_test.go b/enterprise/coderd/workspaces_test.go index a80107d379e74..d96b60ea01057 100644 --- a/enterprise/coderd/workspaces_test.go +++ b/enterprise/coderd/workspaces_test.go @@ -24,6 +24,7 @@ import ( "github.com/coder/coder/v2/enterprise/coderd/license" "github.com/coder/coder/v2/enterprise/coderd/schedule" "github.com/coder/coder/v2/provisioner/echo" + "github.com/coder/coder/v2/provisionersdk/proto" "github.com/coder/coder/v2/testutil" ) @@ -1061,6 +1062,71 @@ func TestWorkspaceLock(t *testing.T) { }) } +func TestResolveAutostart(t *testing.T) { + t.Parallel() + + ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{ + Options: &coderdtest.Options{ + IncludeProvisionerDaemon: true, + TemplateScheduleStore: &schedule.EnterpriseTemplateScheduleStore{}, + }, + LicenseOptions: &coderdenttest.LicenseOptions{ + Features: license.Features{ + codersdk.FeatureAccessControl: 1, + }, + }, + }) + + version1 := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, nil) + coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, version1.ID) + template := coderdtest.CreateTemplate(t, ownerClient, owner.OrganizationID, version1.ID, func(ctr *codersdk.CreateTemplateRequest) { + ctr.RequireActiveVersion = true + }) + + params := &echo.Responses{ + Parse: echo.ParseComplete, + ProvisionPlan: []*proto.Response{ + { + Type: &proto.Response_Plan{ + Plan: &proto.PlanComplete{ + Parameters: []*proto.RichParameter{ + { + Name: "param", + Description: "param", + Required: true, + Mutable: true, + }, + }, + }, + }, + }, + }, + ProvisionApply: echo.ApplyComplete, + } + version2 := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, params, func(ctvr *codersdk.CreateTemplateVersionRequest) { + ctvr.TemplateID = template.ID + }) + coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, version2.ID) + + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) + defer cancel() + + client, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID) + workspace := coderdtest.CreateWorkspace(t, client, owner.OrganizationID, template.ID) + coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID) + + err := ownerClient.UpdateActiveTemplateVersion(ctx, template.ID, codersdk.UpdateActiveTemplateVersion{ + ID: version2.ID, + }) + require.NoError(t, err) + + // Autostart shouldn't be possible since the template requires automatic + // updates. + resp, err := client.ResolveAutostart(ctx, workspace.ID.String()) + require.NoError(t, err) + require.True(t, resp.ParameterMismatch) +} + func must[T any](value T, err error) T { if err != nil { panic(err) diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index a4f52e1f46387..ee4fc68f01672 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -812,6 +812,11 @@ export interface Replica { readonly database_latency: number; } +// From codersdk/workspaces.go +export interface ResolveAutostartResponse { + readonly parameter_mismatch: boolean; +} + // From codersdk/client.go export interface Response { readonly message: string; From d23fbedd38fcf8eadd1b06dea919a1c657ee011e Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 8 Nov 2023 03:01:42 +0000 Subject: [PATCH 2/9] make gen --- coderd/apidoc/docs.go | 3 +++ coderd/apidoc/swagger.json | 1 + coderd/workspaces.go | 1 + docs/api/workspaces.md | 8 +++++++- 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 720ddd282cdd2..b6f4f9ddb3c34 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -6490,6 +6490,9 @@ const docTemplate = `{ "consumes": [ "application/json" ], + "produces": [ + "application/json" + ], "tags": [ "Workspaces" ], diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 6f2c6d70b7dc1..5db1a308a7c17 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -5726,6 +5726,7 @@ } ], "consumes": ["application/json"], + "produces": ["application/json"], "tags": ["Workspaces"], "summary": "Determine whether a workspace is capable of autostarting.", "operationId": "resolve-workspace-autostart-by-id", diff --git a/coderd/workspaces.go b/coderd/workspaces.go index 7b3782fde396d..a83411a35681e 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -1064,6 +1064,7 @@ func (api *API) putWorkspaceAutoupdates(rw http.ResponseWriter, r *http.Request) // @ID resolve-workspace-autostart-by-id // @Security CoderSessionToken // @Accept json +// @Produce json // @Tags Workspaces // @Param workspace path string true "Workspace ID" format(uuid) // @Success 200 {object} codersdk.ResolveAutostartResponse diff --git a/docs/api/workspaces.md b/docs/api/workspaces.md index d2c075c0d2c05..495d24f4653dc 100644 --- a/docs/api/workspaces.md +++ b/docs/api/workspaces.md @@ -1247,7 +1247,7 @@ To perform this operation, you must be authenticated. [Learn more](authenticatio ```shell # Example request using curl curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace}/resolve \ - -H 'Accept: */*' \ + -H 'Accept: application/json' \ -H 'Coder-Session-Token: API_KEY' ``` @@ -1263,6 +1263,12 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace}/resolve \ > 200 Response +```json +{ + "parameter_mismatch": true +} +``` + ### Responses | Status | Meaning | Description | Schema | From f563e6684477cf025a41cde1b765b996d5c6092b Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 8 Nov 2023 03:08:38 +0000 Subject: [PATCH 3/9] make gen --- coderd/apidoc/docs.go | 3 --- coderd/apidoc/swagger.json | 1 - coderd/workspaces.go | 1 - 3 files changed, 5 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index b6f4f9ddb3c34..b5f1711b06798 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -6487,9 +6487,6 @@ const docTemplate = `{ "CoderSessionToken": [] } ], - "consumes": [ - "application/json" - ], "produces": [ "application/json" ], diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 5db1a308a7c17..7640d81dc57dd 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -5725,7 +5725,6 @@ "CoderSessionToken": [] } ], - "consumes": ["application/json"], "produces": ["application/json"], "tags": ["Workspaces"], "summary": "Determine whether a workspace is capable of autostarting.", diff --git a/coderd/workspaces.go b/coderd/workspaces.go index a83411a35681e..e0ebad40f4c02 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -1063,7 +1063,6 @@ func (api *API) putWorkspaceAutoupdates(rw http.ResponseWriter, r *http.Request) // @Summary Determine whether a workspace is capable of autostarting. // @ID resolve-workspace-autostart-by-id // @Security CoderSessionToken -// @Accept json // @Produce json // @Tags Workspaces // @Param workspace path string true "Workspace ID" format(uuid) From 601d1278dfea02f3e4748982bd21307dab44e560 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 8 Nov 2023 03:14:19 +0000 Subject: [PATCH 4/9] rm old code --- coderd/workspaces.go | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/coderd/workspaces.go b/coderd/workspaces.go index e0ebad40f4c02..dfade92d8f172 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -1082,9 +1082,7 @@ func (api *API) resolveAutostart(rw http.ResponseWriter, r *http.Request) { useActiveVersion := template.RequireActiveVersion || workspace.AutomaticUpdates == database.AutomaticUpdatesAlways if !useActiveVersion { - httpapi.Write(ctx, rw, http.StatusOK, codersdk.ResolveAutostartResponse{ - ParameterMismatch: true, - }) + httpapi.Write(ctx, rw, http.StatusOK, codersdk.ResolveAutostartResponse{}) return } @@ -1098,17 +1096,11 @@ func (api *API) resolveAutostart(rw http.ResponseWriter, r *http.Request) { } if build.TemplateVersionID == template.ActiveVersionID { - httpapi.Write(ctx, rw, http.StatusOK, codersdk.ResolveAutostartResponse{ - ParameterMismatch: false, - }) + httpapi.Write(ctx, rw, http.StatusOK, codersdk.ResolveAutostartResponse{}) return } version, err := api.Database.GetTemplateVersionByID(ctx, template.ActiveVersionID) - if httpapi.Is404Error(err) { - httpapi.ResourceNotFound(rw) - return - } if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching template version.", @@ -1117,14 +1109,6 @@ func (api *API) resolveAutostart(rw http.ResponseWriter, r *http.Request) { return } - if version.TemplateID.UUID != workspace.TemplateID { - httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: "Workspace cannot be resolved against unrelated template.", - Detail: err.Error(), - }) - return - } - dbVersionParams, err := api.Database.GetTemplateVersionParameters(ctx, version.ID) if err != nil { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ From 6dadff44759f7c0bea0777817094b3338bec5933 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 8 Nov 2023 03:25:27 +0000 Subject: [PATCH 5/9] swagger --- coderd/apidoc/docs.go | 2 +- coderd/apidoc/swagger.json | 2 +- coderd/workspaces.go | 2 +- docs/api/workspaces.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index b5f1711b06798..1487a4078c5d2 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -6493,7 +6493,7 @@ const docTemplate = `{ "tags": [ "Workspaces" ], - "summary": "Determine whether a workspace is capable of autostarting.", + "summary": "Resolve workspace autostart by id.", "operationId": "resolve-workspace-autostart-by-id", "parameters": [ { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 7640d81dc57dd..bf0204d11bfdb 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -5727,7 +5727,7 @@ ], "produces": ["application/json"], "tags": ["Workspaces"], - "summary": "Determine whether a workspace is capable of autostarting.", + "summary": "Resolve workspace autostart by id.", "operationId": "resolve-workspace-autostart-by-id", "parameters": [ { diff --git a/coderd/workspaces.go b/coderd/workspaces.go index dfade92d8f172..6bf3ae4cbd651 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -1060,7 +1060,7 @@ func (api *API) putWorkspaceAutoupdates(rw http.ResponseWriter, r *http.Request) rw.WriteHeader(http.StatusNoContent) } -// @Summary Determine whether a workspace is capable of autostarting. +// @Summary Resolve workspace autostart by id. // @ID resolve-workspace-autostart-by-id // @Security CoderSessionToken // @Produce json diff --git a/docs/api/workspaces.md b/docs/api/workspaces.md index 495d24f4653dc..9af58bef1a9ca 100644 --- a/docs/api/workspaces.md +++ b/docs/api/workspaces.md @@ -1240,7 +1240,7 @@ curl -X PUT http://coder-server:8080/api/v2/workspaces/{workspace}/extend \ To perform this operation, you must be authenticated. [Learn more](authentication.md). -## Determine whether a workspace is capable of autostarting. +## Resolve workspace autostart by id. ### Code samples From 5ea7c06f3dd88431472657b12cf0e8661c18aa5a Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 8 Nov 2023 04:22:12 +0000 Subject: [PATCH 6/9] update endpoint name --- coderd/apidoc/docs.go | 2 +- coderd/apidoc/swagger.json | 2 +- coderd/coderd.go | 2 +- coderd/workspaces.go | 2 +- codersdk/workspaces.go | 2 +- docs/api/workspaces.md | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 1487a4078c5d2..b55e9d6d23225 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -6480,7 +6480,7 @@ const docTemplate = `{ } } }, - "/workspaces/{workspace}/resolve": { + "/workspaces/{workspace}/resolve-autostart": { "get": { "security": [ { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index bf0204d11bfdb..8b474ff8ecbee 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -5718,7 +5718,7 @@ } } }, - "/workspaces/{workspace}/resolve": { + "/workspaces/{workspace}/resolve-autostart": { "get": { "security": [ { diff --git a/coderd/coderd.go b/coderd/coderd.go index e9155531f0dba..600e66404f327 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -885,7 +885,7 @@ func New(options *Options) *API { r.Put("/extend", api.putExtendWorkspace) r.Put("/dormant", api.putWorkspaceDormant) r.Put("/autoupdates", api.putWorkspaceAutoupdates) - r.Get("/resolve", api.resolveAutostart) + r.Get("/resolve-autostart", api.resolveAutostart) }) }) r.Route("/workspacebuilds/{workspacebuild}", func(r chi.Router) { diff --git a/coderd/workspaces.go b/coderd/workspaces.go index 6bf3ae4cbd651..b923b4681e30e 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -1067,7 +1067,7 @@ func (api *API) putWorkspaceAutoupdates(rw http.ResponseWriter, r *http.Request) // @Tags Workspaces // @Param workspace path string true "Workspace ID" format(uuid) // @Success 200 {object} codersdk.ResolveAutostartResponse -// @Router /workspaces/{workspace}/resolve [get] +// @Router /workspaces/{workspace}/resolve-autostart [get] func (api *API) resolveAutostart(rw http.ResponseWriter, r *http.Request) { var ( ctx = r.Context() diff --git a/codersdk/workspaces.go b/codersdk/workspaces.go index f82882ddbabec..307bbdb0d3b93 100644 --- a/codersdk/workspaces.go +++ b/codersdk/workspaces.go @@ -454,7 +454,7 @@ type ResolveAutostartResponse struct { } func (c *Client) ResolveAutostart(ctx context.Context, workspaceID string) (ResolveAutostartResponse, error) { - res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/workspaces/%s/resolve", workspaceID), nil) + res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/workspaces/%s/resolve-autostart", workspaceID), nil) if err != nil { return ResolveAutostartResponse{}, err } diff --git a/docs/api/workspaces.md b/docs/api/workspaces.md index 9af58bef1a9ca..209d7f34d2bac 100644 --- a/docs/api/workspaces.md +++ b/docs/api/workspaces.md @@ -1246,12 +1246,12 @@ To perform this operation, you must be authenticated. [Learn more](authenticatio ```shell # Example request using curl -curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace}/resolve \ +curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace}/resolve-autostart \ -H 'Accept: application/json' \ -H 'Coder-Session-Token: API_KEY' ``` -`GET /workspaces/{workspace}/resolve` +`GET /workspaces/{workspace}/resolve-autostart` ### Parameters From 1e9fede5aa10f68ce0a6913eb4fc594b46202cd4 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 8 Nov 2023 16:59:53 +0000 Subject: [PATCH 7/9] pr changes --- coderd/workspaces.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/coderd/workspaces.go b/coderd/workspaces.go index b923b4681e30e..a83ec6f679038 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -1080,7 +1080,8 @@ func (api *API) resolveAutostart(rw http.ResponseWriter, r *http.Request) { return } - useActiveVersion := template.RequireActiveVersion || workspace.AutomaticUpdates == database.AutomaticUpdatesAlways + templateAccessControl := (*(api.AccessControlStore.Load())).GetTemplateAccessControl(template) + useActiveVersion := templateAccessControl.RequireActiveVersion || workspace.AutomaticUpdates == database.AutomaticUpdatesAlways if !useActiveVersion { httpapi.Write(ctx, rw, http.StatusOK, codersdk.ResolveAutostartResponse{}) return @@ -1111,7 +1112,7 @@ func (api *API) resolveAutostart(rw http.ResponseWriter, r *http.Request) { dbVersionParams, err := api.Database.GetTemplateVersionParameters(ctx, version.ID) if err != nil { - httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching template version parameters.", Detail: err.Error(), }) @@ -1141,13 +1142,16 @@ func (api *API) resolveAutostart(rw http.ResponseWriter, r *http.Request) { } var response codersdk.ResolveAutostartResponse - for i := 0; i < len(versionParams) && !response.ParameterMismatch; i++ { - _, err := resolver.ValidateResolve(versionParams[i], nil) + for _, param := range versionParams { + _, err := resolver.ValidateResolve(param, nil) // There's a parameter mismatch if we get an error back from the // resolver. response.ParameterMismatch = err != nil - } + if response.ParameterMismatch { + break + } + } httpapi.Write(ctx, rw, http.StatusOK, response) } From 551cf1a228933379ca5fd681ad8e390b07ff6fd4 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 8 Nov 2023 17:36:14 +0000 Subject: [PATCH 8/9] lint --- coderd/workspaces.go | 1 - 1 file changed, 1 deletion(-) diff --git a/coderd/workspaces.go b/coderd/workspaces.go index a83ec6f679038..6a86e9e735501 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -1150,7 +1150,6 @@ func (api *API) resolveAutostart(rw http.ResponseWriter, r *http.Request) { if response.ParameterMismatch { break } - } httpapi.Write(ctx, rw, http.StatusOK, response) } From ebbe551c908ccbb1e025bdc1bc2ca6750a50604f Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 8 Nov 2023 17:53:13 +0000 Subject: [PATCH 9/9] wtf --- enterprise/coderd/workspaces_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/enterprise/coderd/workspaces_test.go b/enterprise/coderd/workspaces_test.go index d96b60ea01057..45060a1686820 100644 --- a/enterprise/coderd/workspaces_test.go +++ b/enterprise/coderd/workspaces_test.go @@ -1115,6 +1115,7 @@ func TestResolveAutostart(t *testing.T) { workspace := coderdtest.CreateWorkspace(t, client, owner.OrganizationID, template.ID) coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID) + //nolint:gocritic err := ownerClient.UpdateActiveTemplateVersion(ctx, template.ID, codersdk.UpdateActiveTemplateVersion{ ID: version2.ID, })