Skip to content

Commit

Permalink
feat: add app share property (#62)
Browse files Browse the repository at this point in the history
  • Loading branch information
deansheather authored Oct 7, 2022
1 parent dfd2dd0 commit 6adf9b4
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 61 deletions.
2 changes: 2 additions & 0 deletions docs/resources/app.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ resource "coder_app" "code-server" {
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"
Expand Down Expand Up @@ -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/<path>"`.
- `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) 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.

Expand Down
1 change: 1 addition & 0 deletions examples/resources/coder_app/resource.tf
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ resource "coder_app" "code-server" {
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"
Expand Down
64 changes: 32 additions & 32 deletions examples/resources/coder_parameter/resource.tf
Original file line number Diff line number Diff line change
@@ -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
}
}
33 changes: 33 additions & 0 deletions provider/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -76,6 +77,38 @@ func appResource() *schema.Resource {
ForceNew: true,
Optional: true,
},
"share": {
Type: schema.TypeString,
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",
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 share %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. " +
Expand Down
171 changes: 142 additions & 29 deletions provider/app_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package provider_test

import (
"fmt"
"regexp"
"testing"

"github.com/coder/terraform-provider-coder/provider"
Expand All @@ -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" {
Expand All @@ -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.
"share",
"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 share "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("share = %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 share and ensure it matches the expected
// value.
value := resource.Primary.Attributes["share"]
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,
}},
})
})
}
})
}

0 comments on commit 6adf9b4

Please sign in to comment.