From b4ba88c9562a9e3e4c9dd2ab9c840c76bbefa00a Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Tue, 20 Aug 2024 12:14:09 +0000 Subject: [PATCH] feat: enable setting max port share level during template creation --- coderd/apidoc/docs.go | 8 ++++++ coderd/apidoc/swagger.json | 8 ++++++ coderd/templates.go | 12 ++++++++- coderd/templates_test.go | 38 +++++++++++++++++++++++++++++ codersdk/organizations.go | 4 +++ docs/reference/api/schemas.md | 2 ++ docs/reference/api/templates.md | 1 + enterprise/coderd/templates_test.go | 5 +++- site/src/api/typesGenerated.ts | 1 + 9 files changed, 77 insertions(+), 2 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 4e26d96efdbd6..d95cf43748b69 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -9220,6 +9220,14 @@ const docTemplate = `{ "description": "Icon is a relative path or external URL that specifies\nan icon to be displayed in the dashboard.", "type": "string" }, + "max_port_share_level": { + "description": "MaxPortShareLevel allows optionally specifying the maximum port share level\nfor workspaces created from the template.", + "allOf": [ + { + "$ref": "#/definitions/codersdk.WorkspaceAgentPortShareLevel" + } + ] + }, "name": { "description": "Name is the name of the template.", "type": "string" diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 5f2ed6e5aa9e7..6aa6232b6ced2 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -8209,6 +8209,14 @@ "description": "Icon is a relative path or external URL that specifies\nan icon to be displayed in the dashboard.", "type": "string" }, + "max_port_share_level": { + "description": "MaxPortShareLevel allows optionally specifying the maximum port share level\nfor workspaces created from the template.", + "allOf": [ + { + "$ref": "#/definitions/codersdk.WorkspaceAgentPortShareLevel" + } + ] + }, "name": { "description": "Name is the name of the template.", "type": "string" diff --git a/coderd/templates.go b/coderd/templates.go index ade849ba99c9c..dc32841b72aad 100644 --- a/coderd/templates.go +++ b/coderd/templates.go @@ -163,6 +163,7 @@ func (api *API) notifyTemplateDeleted(ctx context.Context, template database.Tem func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Request) { var ( ctx = r.Context() + portSharer = *api.PortSharer.Load() createTemplate codersdk.CreateTemplateRequest organization = httpmw.OrganizationParam(r) apiKey = httpmw.APIKey(r) @@ -309,6 +310,7 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque validErrs []codersdk.ValidationError autostopRequirementDaysOfWeekParsed uint8 autostartRequirementDaysOfWeekParsed uint8 + maxPortShareLevel = database.AppSharingLevelOwner // default ) if defaultTTL < 0 { validErrs = append(validErrs, codersdk.ValidationError{Field: "default_ttl_ms", Detail: "Must be a positive integer."}) @@ -329,6 +331,14 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque validErrs = append(validErrs, codersdk.ValidationError{Field: "autostart_requirement.days_of_week", Detail: err.Error()}) } } + if createTemplate.MaxPortShareLevel != nil { + err = portSharer.ValidateTemplateMaxLevel(*createTemplate.MaxPortShareLevel) + if err != nil { + validErrs = append(validErrs, codersdk.ValidationError{Field: "max_port_share_level", Detail: err.Error()}) + } else { + maxPortShareLevel = database.AppSharingLevel(*createTemplate.MaxPortShareLevel) + } + } if autostopRequirementWeeks < 0 { validErrs = append(validErrs, codersdk.ValidationError{Field: "autostop_requirement.weeks", Detail: "Must be a positive integer."}) @@ -386,7 +396,7 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque DisplayName: createTemplate.DisplayName, Icon: createTemplate.Icon, AllowUserCancelWorkspaceJobs: allowUserCancelWorkspaceJobs, - MaxPortSharingLevel: database.AppSharingLevelOwner, + MaxPortSharingLevel: maxPortShareLevel, }) if err != nil { return xerrors.Errorf("insert template: %s", err) diff --git a/coderd/templates_test.go b/coderd/templates_test.go index 5efd127681bbd..ca8d9c4cf88f2 100644 --- a/coderd/templates_test.go +++ b/coderd/templates_test.go @@ -401,6 +401,44 @@ func TestPostTemplateByOrganization(t *testing.T) { require.EqualValues(t, 1, got.AutostopRequirement.Weeks) }) }) + + t.Run("MaxPortShareLevel", func(t *testing.T) { + t.Parallel() + + t.Run("OK", func(t *testing.T) { + client := coderdtest.New(t, nil) + user := coderdtest.CreateFirstUser(t, client) + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) + + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) + defer cancel() + + got, err := client.CreateTemplate(ctx, user.OrganizationID, codersdk.CreateTemplateRequest{ + Name: "testing", + VersionID: version.ID, + }) + require.NoError(t, err) + require.Equal(t, codersdk.WorkspaceAgentPortShareLevelPublic, got.MaxPortShareLevel) + }) + + t.Run("EnterpriseLevelError", func(t *testing.T) { + client := coderdtest.New(t, nil) + user := coderdtest.CreateFirstUser(t, client) + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) + + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) + defer cancel() + + _, err := client.CreateTemplate(ctx, user.OrganizationID, codersdk.CreateTemplateRequest{ + Name: "testing", + VersionID: version.ID, + MaxPortShareLevel: ptr.Ref(codersdk.WorkspaceAgentPortShareLevelPublic), + }) + var apiErr *codersdk.Error + require.ErrorAs(t, err, &apiErr) + require.Equal(t, http.StatusBadRequest, apiErr.StatusCode()) + }) + }) } func TestTemplatesByOrganization(t *testing.T) { diff --git a/codersdk/organizations.go b/codersdk/organizations.go index a1dc562055f82..77e24a2be3e10 100644 --- a/codersdk/organizations.go +++ b/codersdk/organizations.go @@ -184,6 +184,10 @@ type CreateTemplateRequest struct { // RequireActiveVersion mandates that workspaces are built with the active // template version. RequireActiveVersion bool `json:"require_active_version"` + + // MaxPortShareLevel allows optionally specifying the maximum port share level + // for workspaces created from the template. + MaxPortShareLevel *WorkspaceAgentPortShareLevel `json:"max_port_share_level"` } // CreateWorkspaceRequest provides options for creating a new workspace. diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index 27d65ad5ecd6f..9f9188ced1761 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -1107,6 +1107,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in "dormant_ttl_ms": 0, "failure_ttl_ms": 0, "icon": "string", + "max_port_share_level": "owner", "name": "string", "require_active_version": true, "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1" @@ -1131,6 +1132,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | `dormant_ttl_ms` | integer | false | | Dormant ttl ms allows optionally specifying the max lifetime before Coder locks inactive workspaces created from this template. | | `failure_ttl_ms` | integer | false | | Failure ttl ms allows optionally specifying the max lifetime before Coder stops all resources for failed workspaces created from this template. | | `icon` | string | false | | Icon is a relative path or external URL that specifies an icon to be displayed in the dashboard. | +| `max_port_share_level` | [codersdk.WorkspaceAgentPortShareLevel](#codersdkworkspaceagentportsharelevel) | false | | Max port share level allows optionally specifying the maximum port share level for workspaces created from the template. | | `name` | string | true | | Name is the name of the template. | | `require_active_version` | boolean | false | | Require active version mandates that workspaces are built with the active template version. | | `template_version_id` | string | true | | Template version ID is an in-progress or completed job to use as an initial version of the template. | diff --git a/docs/reference/api/templates.md b/docs/reference/api/templates.md index da7b61d8a2fa9..ef90c243a5961 100644 --- a/docs/reference/api/templates.md +++ b/docs/reference/api/templates.md @@ -175,6 +175,7 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/templa "dormant_ttl_ms": 0, "failure_ttl_ms": 0, "icon": "string", + "max_port_share_level": "owner", "name": "string", "require_active_version": true, "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1" diff --git a/enterprise/coderd/templates_test.go b/enterprise/coderd/templates_test.go index 5bb41cf534e68..782132f5d3ba2 100644 --- a/enterprise/coderd/templates_test.go +++ b/enterprise/coderd/templates_test.go @@ -132,7 +132,10 @@ func TestTemplates(t *testing.T) { }, }}, }) - template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) + template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) { + ctr.MaxPortShareLevel = ptr.Ref(codersdk.WorkspaceAgentPortShareLevelPublic) + }) + require.Equal(t, template.MaxPortShareLevel, codersdk.WorkspaceAgentPortShareLevelPublic) coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) ws := coderdtest.CreateWorkspace(t, client, template.ID) coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, ws.LatestBuild.ID) diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 0f99c2fd2ef4e..1186f26ad90c8 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -261,6 +261,7 @@ export interface CreateTemplateRequest { readonly delete_ttl_ms?: number; readonly disable_everyone_group_access: boolean; readonly require_active_version: boolean; + readonly max_port_share_level?: WorkspaceAgentPortShareLevel; } // From codersdk/templateversions.go