From 327d39661ed4cfc81ccc2a5cfe7a4689b1e88ea6 Mon Sep 17 00:00:00 2001 From: Dean Sheather Date: Thu, 6 Oct 2022 16:50:27 +0000 Subject: [PATCH 1/3] feat: add app sharing_level property --- docs/resources/app.md | 12 +- examples/resources/coder_app/resource.tf | 11 +- .../resources/coder_parameter/resource.tf | 64 +++---- provider/app.go | 34 ++++ provider/app_test.go | 171 +++++++++++++++--- 5 files changed, 221 insertions(+), 71 deletions(-) diff --git a/docs/resources/app.md b/docs/resources/app.md index 3b564862..775182fb 100644 --- a/docs/resources/app.md +++ b/docs/resources/app.md @@ -26,11 +26,12 @@ EOF } resource "coder_app" "code-server" { - agent_id = coder_agent.dev.id - name = "VS Code" - icon = data.coder_workspace.me.access_url + "/icons/vscode.svg" - url = "http://localhost:13337" - subdomain = false + agent_id = coder_agent.dev.id + name = "VS Code" + icon = data.coder_workspace.me.access_url + "/icons/vscode.svg" + url = "http://localhost:13337" + sharing_level = "owner" + subdomain = false healthcheck { url = "http://localhost:13337/healthz" interval = 5 @@ -67,6 +68,7 @@ resource "coder_app" "intellij" { - `icon` (String) A URL to an icon that will display in the dashboard. View built-in icons here: https://github.com/coder/coder/tree/main/site/static/icons. Use a built-in icon with `data.coder_workspace.me.access_url + "/icons/"`. - `name` (String) A display name to identify the app. - `relative_path` (Boolean, Deprecated) Specifies whether the URL will be accessed via a relative path or wildcard. Use if wildcard routing is unavailable. Defaults to true. +- `sharing_level` (String) Determines the sharing level of the app. Application sharing is an enterprise feature and any values will be ignored (and sharing disabled) if your deployment is not entitled to use application sharing. Valid values are "owner", "template", "authenticated" and "public". Level "owner" disables sharing on the app, so only the workspace owner can access it. Level "template" shares the app with all users that can read the workspace's template. Level "authenticated" shares the app with all authenticated users. Level "public" shares it with any user, including unauthenticated users. Permitted application sharing levels can be controlled via a flag on "coder server". Defaults to "owner" (sharing disabled). - `subdomain` (Boolean) Determines whether the app will be accessed via it's own subdomain or whether it will be accessed via a path on Coder. If wildcards have not been setup by the administrator then apps with "subdomain" set to true will not be accessible. Defaults to false. - `url` (String) A URL to be proxied to from inside the workspace. Either "command" or "url" may be specified, but not both. diff --git a/examples/resources/coder_app/resource.tf b/examples/resources/coder_app/resource.tf index 715f5814..33efecf1 100644 --- a/examples/resources/coder_app/resource.tf +++ b/examples/resources/coder_app/resource.tf @@ -11,11 +11,12 @@ EOF } resource "coder_app" "code-server" { - agent_id = coder_agent.dev.id - name = "VS Code" - icon = data.coder_workspace.me.access_url + "/icons/vscode.svg" - url = "http://localhost:13337" - subdomain = false + agent_id = coder_agent.dev.id + name = "VS Code" + icon = data.coder_workspace.me.access_url + "/icons/vscode.svg" + url = "http://localhost:13337" + sharing_level = "owner" + subdomain = false healthcheck { url = "http://localhost:13337/healthz" interval = 5 diff --git a/examples/resources/coder_parameter/resource.tf b/examples/resources/coder_parameter/resource.tf index 21fb5e4b..9057b3a1 100644 --- a/examples/resources/coder_parameter/resource.tf +++ b/examples/resources/coder_parameter/resource.tf @@ -1,46 +1,46 @@ data "coder_parameter" "example" { - display_name = "Region" - description = "Specify a region to place your workspace." - immutable = true - type = "string" - option { - value = "us-central1-a" - label = "US Central" - icon = "/icon/usa.svg" - } - option { - value = "asia-central1-a" - label = "Asia" - icon = "/icon/asia.svg" - } + display_name = "Region" + description = "Specify a region to place your workspace." + immutable = true + type = "string" + option { + value = "us-central1-a" + label = "US Central" + icon = "/icon/usa.svg" + } + option { + value = "asia-central1-a" + label = "Asia" + icon = "/icon/asia.svg" + } } data "coder_parameter" "ami" { - display_name = "Machine Image" - option { - value = "ami-xxxxxxxx" - label = "Ubuntu" - icon = "/icon/ubuntu.svg" - } + display_name = "Machine Image" + option { + value = "ami-xxxxxxxx" + label = "Ubuntu" + icon = "/icon/ubuntu.svg" + } } data "coder_parameter" "image" { - display_name = "Docker Image" - icon = "/icon/docker.svg" - type = "bool" + display_name = "Docker Image" + icon = "/icon/docker.svg" + type = "bool" } data "coder_parameter" "cores" { - display_name = "CPU Cores" - icon = "/icon/" + display_name = "CPU Cores" + icon = "/icon/" } data "coder_parameter" "disk_size" { - display_name = "Disk Size" - type = "number" - validation { - # This can apply to number and string types. - min = 0 - max = 10 - } + display_name = "Disk Size" + type = "number" + validation { + # This can apply to number and string types. + min = 0 + max = 10 + } } diff --git a/provider/app.go b/provider/app.go index 2e0485bb..3357e100 100644 --- a/provider/app.go +++ b/provider/app.go @@ -5,6 +5,7 @@ import ( "net/url" "github.com/google/uuid" + "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -76,6 +77,39 @@ func appResource() *schema.Resource { ForceNew: true, Optional: true, }, + "sharing_level": { + Type: schema.TypeString, + Description: "Determines the sharing level of the app. " + + "Application sharing is an enterprise feature and any " + + "values will be ignored (and sharing disabled) if your " + + "deployment is not entitled to use application sharing. " + + `Valid values are "owner", "template", "authenticated" ` + + `and "public". Level "owner" disables sharing on the ` + + "app, so only the workspace owner can access it. Level " + + `"template" shares the app with all users that can read ` + + `the workspace's template. Level "authenticated" shares ` + + `the app with all authenticated users. Level "public" ` + + "shares it with any user, including unauthenticated " + + "users. Permitted application sharing levels can be " + + `controlled via a flag on "coder server". Defaults to ` + + `"owner" (sharing disabled).`, + ForceNew: true, + Optional: true, + Default: "owner", + ValidateDiagFunc: func(val interface{}, c cty.Path) diag.Diagnostics { + valStr, ok := val.(string) + if !ok { + return diag.Errorf("expected string, got %T", val) + } + + switch valStr { + case "owner", "template", "authenticated", "public": + return nil + } + + return diag.Errorf(`invalid app sharing_level %q, must be one of "owner", "template", "authenticated", "public"`, valStr) + }, + }, "url": { Type: schema.TypeString, Description: "A URL to be proxied to from inside the workspace. " + diff --git a/provider/app_test.go b/provider/app_test.go index f28ccaf6..4c153c94 100644 --- a/provider/app_test.go +++ b/provider/app_test.go @@ -1,6 +1,8 @@ package provider_test import ( + "fmt" + "regexp" "testing" "github.com/coder/terraform-provider-coder/provider" @@ -12,13 +14,17 @@ import ( func TestApp(t *testing.T) { t.Parallel() - resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, - Steps: []resource.TestStep{{ - Config: ` + + t.Run("OK", func(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + Providers: map[string]*schema.Provider{ + "coder": provider.New(), + }, + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: ` provider "coder" { } resource "coder_agent" "dev" { @@ -38,28 +44,135 @@ func TestApp(t *testing.T) { } } `, - Check: func(state *terraform.State) error { - require.Len(t, state.Modules, 1) - require.Len(t, state.Modules[0].Resources, 2) - resource := state.Modules[0].Resources["coder_app.code-server"] - require.NotNil(t, resource) - for _, key := range []string{ - "agent_id", - "name", - "icon", - "subdomain", - "url", - "healthcheck.0.url", - "healthcheck.0.interval", - "healthcheck.0.threshold", - } { - value := resource.Primary.Attributes[key] - t.Logf("%q = %q", key, value) - require.NotNil(t, value) - require.Greater(t, len(value), 0) - } - return nil + Check: func(state *terraform.State) error { + require.Len(t, state.Modules, 1) + require.Len(t, state.Modules[0].Resources, 2) + resource := state.Modules[0].Resources["coder_app.code-server"] + require.NotNil(t, resource) + for _, key := range []string{ + "agent_id", + "name", + "icon", + "subdomain", + // Should be set by default even though it isn't + // specified. + "sharing_level", + "url", + "healthcheck.0.url", + "healthcheck.0.interval", + "healthcheck.0.threshold", + } { + value := resource.Primary.Attributes[key] + t.Logf("%q = %q", key, value) + require.NotNil(t, value) + require.Greater(t, len(value), 0) + } + return nil + }, + }}, + }) + }) + + t.Run("SharingLevel", func(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + value string + expectValue string + expectError *regexp.Regexp + }{ + { + name: "Default", + value: "", // default + expectValue: "owner", + }, + { + name: "InvalidValue", + value: "blah", + expectError: regexp.MustCompile(`invalid app sharing_level "blah"`), + }, + { + name: "ExplicitOwner", + value: "owner", + expectValue: "owner", }, - }}, + { + name: "ExplicitTemplate", + value: "template", + expectValue: "template", + }, + { + name: "ExplicitAuthenticated", + value: "authenticated", + expectValue: "authenticated", + }, + { + name: "ExplicitPublic", + value: "public", + expectValue: "public", + }, + } + + for _, c := range cases { + c := c + + t.Run(c.name, func(t *testing.T) { + t.Parallel() + + sharingLine := "" + if c.value != "" { + sharingLine = fmt.Sprintf("sharing_level = %q", c.value) + } + config := fmt.Sprintf(` + provider "coder" { + } + resource "coder_agent" "dev" { + os = "linux" + arch = "amd64" + } + resource "coder_app" "code-server" { + agent_id = coder_agent.dev.id + name = "code-server" + icon = "builtin:vim" + url = "http://localhost:13337" + %s + healthcheck { + url = "http://localhost:13337/healthz" + interval = 5 + threshold = 6 + } + } + `, sharingLine) + + checkFn := func(state *terraform.State) error { + require.Len(t, state.Modules, 1) + require.Len(t, state.Modules[0].Resources, 2) + resource := state.Modules[0].Resources["coder_app.code-server"] + require.NotNil(t, resource) + + // Read sharing_level and ensure it matches the expected + // value. + value := resource.Primary.Attributes["sharing_level"] + require.Equal(t, c.expectValue, value) + return nil + } + if c.expectError != nil { + checkFn = nil + } + + resource.Test(t, resource.TestCase{ + Providers: map[string]*schema.Provider{ + "coder": provider.New(), + }, + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: config, + Check: checkFn, + ExpectError: c.expectError, + }}, + }) + }) + } }) } From 02d2e0ca8731bf81f7b2d1f9f0667117ce387ec2 Mon Sep 17 00:00:00 2001 From: Dean Sheather Date: Thu, 6 Oct 2022 18:33:31 +0000 Subject: [PATCH 2/3] chore: sharing_level -> share --- docs/resources/app.md | 14 +++++++------- examples/resources/coder_app/resource.tf | 12 ++++++------ provider/app.go | 4 ++-- provider/app_test.go | 10 +++++----- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/docs/resources/app.md b/docs/resources/app.md index 775182fb..bf40611c 100644 --- a/docs/resources/app.md +++ b/docs/resources/app.md @@ -26,12 +26,12 @@ EOF } resource "coder_app" "code-server" { - agent_id = coder_agent.dev.id - name = "VS Code" - icon = data.coder_workspace.me.access_url + "/icons/vscode.svg" - url = "http://localhost:13337" - sharing_level = "owner" - subdomain = false + agent_id = coder_agent.dev.id + name = "VS Code" + icon = data.coder_workspace.me.access_url + "/icons/vscode.svg" + url = "http://localhost:13337" + share = "owner" + subdomain = false healthcheck { url = "http://localhost:13337/healthz" interval = 5 @@ -68,7 +68,7 @@ resource "coder_app" "intellij" { - `icon` (String) A URL to an icon that will display in the dashboard. View built-in icons here: https://github.com/coder/coder/tree/main/site/static/icons. Use a built-in icon with `data.coder_workspace.me.access_url + "/icons/"`. - `name` (String) A display name to identify the app. - `relative_path` (Boolean, Deprecated) Specifies whether the URL will be accessed via a relative path or wildcard. Use if wildcard routing is unavailable. Defaults to true. -- `sharing_level` (String) Determines the sharing level of the app. Application sharing is an enterprise feature and any values will be ignored (and sharing disabled) if your deployment is not entitled to use application sharing. Valid values are "owner", "template", "authenticated" and "public". Level "owner" disables sharing on the app, so only the workspace owner can access it. Level "template" shares the app with all users that can read the workspace's template. Level "authenticated" shares the app with all authenticated users. Level "public" shares it with any user, including unauthenticated users. Permitted application sharing levels can be controlled via a flag on "coder server". Defaults to "owner" (sharing disabled). +- `share` (String) Determines the sharing level of the app. Application sharing is an enterprise feature and any values will be ignored (and sharing disabled) if your deployment is not entitled to use application sharing. Valid values are "owner", "template", "authenticated" and "public". Level "owner" disables sharing on the app, so only the workspace owner can access it. Level "template" shares the app with all users that can read the workspace's template. Level "authenticated" shares the app with all authenticated users. Level "public" shares it with any user, including unauthenticated users. Permitted application sharing levels can be controlled via a flag on "coder server". Defaults to "owner" (sharing disabled). - `subdomain` (Boolean) Determines whether the app will be accessed via it's own subdomain or whether it will be accessed via a path on Coder. If wildcards have not been setup by the administrator then apps with "subdomain" set to true will not be accessible. Defaults to false. - `url` (String) A URL to be proxied to from inside the workspace. Either "command" or "url" may be specified, but not both. diff --git a/examples/resources/coder_app/resource.tf b/examples/resources/coder_app/resource.tf index 33efecf1..48190440 100644 --- a/examples/resources/coder_app/resource.tf +++ b/examples/resources/coder_app/resource.tf @@ -11,12 +11,12 @@ EOF } resource "coder_app" "code-server" { - agent_id = coder_agent.dev.id - name = "VS Code" - icon = data.coder_workspace.me.access_url + "/icons/vscode.svg" - url = "http://localhost:13337" - sharing_level = "owner" - subdomain = false + agent_id = coder_agent.dev.id + name = "VS Code" + icon = data.coder_workspace.me.access_url + "/icons/vscode.svg" + url = "http://localhost:13337" + share = "owner" + subdomain = false healthcheck { url = "http://localhost:13337/healthz" interval = 5 diff --git a/provider/app.go b/provider/app.go index 3357e100..bb0c18ad 100644 --- a/provider/app.go +++ b/provider/app.go @@ -77,7 +77,7 @@ func appResource() *schema.Resource { ForceNew: true, Optional: true, }, - "sharing_level": { + "share": { Type: schema.TypeString, Description: "Determines the sharing level of the app. " + "Application sharing is an enterprise feature and any " + @@ -107,7 +107,7 @@ func appResource() *schema.Resource { return nil } - return diag.Errorf(`invalid app sharing_level %q, must be one of "owner", "template", "authenticated", "public"`, valStr) + return diag.Errorf(`invalid app share %q, must be one of "owner", "template", "authenticated", "public"`, valStr) }, }, "url": { diff --git a/provider/app_test.go b/provider/app_test.go index 4c153c94..977c3ea1 100644 --- a/provider/app_test.go +++ b/provider/app_test.go @@ -56,7 +56,7 @@ func TestApp(t *testing.T) { "subdomain", // Should be set by default even though it isn't // specified. - "sharing_level", + "share", "url", "healthcheck.0.url", "healthcheck.0.interval", @@ -90,7 +90,7 @@ func TestApp(t *testing.T) { { name: "InvalidValue", value: "blah", - expectError: regexp.MustCompile(`invalid app sharing_level "blah"`), + expectError: regexp.MustCompile(`invalid app share "blah"`), }, { name: "ExplicitOwner", @@ -122,7 +122,7 @@ func TestApp(t *testing.T) { sharingLine := "" if c.value != "" { - sharingLine = fmt.Sprintf("sharing_level = %q", c.value) + sharingLine = fmt.Sprintf("share = %q", c.value) } config := fmt.Sprintf(` provider "coder" { @@ -151,9 +151,9 @@ func TestApp(t *testing.T) { resource := state.Modules[0].Resources["coder_app.code-server"] require.NotNil(t, resource) - // Read sharing_level and ensure it matches the expected + // Read share and ensure it matches the expected // value. - value := resource.Primary.Attributes["sharing_level"] + value := resource.Primary.Attributes["share"] require.Equal(t, c.expectValue, value) return nil } From 56ba8fe94848b79961e970bf9d42f51d89097dce Mon Sep 17 00:00:00 2001 From: Dean Sheather Date: Fri, 7 Oct 2022 14:06:21 +0000 Subject: [PATCH 3/3] fixup! chore: sharing_level -> share --- docs/resources/app.md | 2 +- provider/app.go | 27 +++++++++++++-------------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/docs/resources/app.md b/docs/resources/app.md index bf40611c..d91c20a3 100644 --- a/docs/resources/app.md +++ b/docs/resources/app.md @@ -68,7 +68,7 @@ resource "coder_app" "intellij" { - `icon` (String) A URL to an icon that will display in the dashboard. View built-in icons here: https://github.com/coder/coder/tree/main/site/static/icons. Use a built-in icon with `data.coder_workspace.me.access_url + "/icons/"`. - `name` (String) A display name to identify the app. - `relative_path` (Boolean, Deprecated) Specifies whether the URL will be accessed via a relative path or wildcard. Use if wildcard routing is unavailable. Defaults to true. -- `share` (String) Determines the sharing level of the app. Application sharing is an enterprise feature and any values will be ignored (and sharing disabled) if your deployment is not entitled to use application sharing. Valid values are "owner", "template", "authenticated" and "public". Level "owner" disables sharing on the app, so only the workspace owner can access it. Level "template" shares the app with all users that can read the workspace's template. Level "authenticated" shares the app with all authenticated users. Level "public" shares it with any user, including unauthenticated users. Permitted application sharing levels can be controlled via a flag on "coder server". Defaults to "owner" (sharing disabled). +- `share` (String) Application sharing is an enterprise feature and any values will be ignored (and sharing disabled) if your deployment is not entitled to use application sharing. Valid values are "owner", "template", "authenticated" and "public". Level "owner" disables sharing on the app, so only the workspace owner can access it. Level "template" shares the app with all users that can read the workspace's template. Level "authenticated" shares the app with all authenticated users. Level "public" shares it with any user, including unauthenticated users. Permitted application sharing levels can be configured via a flag on "coder server". Defaults to "owner" (sharing disabled). - `subdomain` (Boolean) Determines whether the app will be accessed via it's own subdomain or whether it will be accessed via a path on Coder. If wildcards have not been setup by the administrator then apps with "subdomain" set to true will not be accessible. Defaults to false. - `url` (String) A URL to be proxied to from inside the workspace. Either "command" or "url" may be specified, but not both. diff --git a/provider/app.go b/provider/app.go index bb0c18ad..961f4ed3 100644 --- a/provider/app.go +++ b/provider/app.go @@ -79,20 +79,19 @@ func appResource() *schema.Resource { }, "share": { Type: schema.TypeString, - Description: "Determines the sharing level of the app. " + - "Application sharing is an enterprise feature and any " + - "values will be ignored (and sharing disabled) if your " + - "deployment is not entitled to use application sharing. " + - `Valid values are "owner", "template", "authenticated" ` + - `and "public". Level "owner" disables sharing on the ` + - "app, so only the workspace owner can access it. Level " + - `"template" shares the app with all users that can read ` + - `the workspace's template. Level "authenticated" shares ` + - `the app with all authenticated users. Level "public" ` + - "shares it with any user, including unauthenticated " + - "users. Permitted application sharing levels can be " + - `controlled via a flag on "coder server". Defaults to ` + - `"owner" (sharing disabled).`, + Description: "Application sharing is an enterprise feature " + + "and any values will be ignored (and sharing disabled) " + + "if your deployment is not entitled to use application " + + `sharing. Valid values are "owner", "template", ` + + `"authenticated" and "public". Level "owner" disables ` + + "sharing on the app, so only the workspace owner can " + + `access it. Level "template" shares the app with all users ` + + `that can read the workspace's template. Level ` + + `"authenticated" shares the app with all authenticated ` + + `users. Level "public" shares it with any user, ` + + "including unauthenticated users. Permitted application " + + `sharing levels can be configured via a flag on "coder ` + + `server". Defaults to "owner" (sharing disabled).`, ForceNew: true, Optional: true, Default: "owner",