diff --git a/docs/resources/app.md b/docs/resources/app.md index 9b70c683..95f09028 100644 --- a/docs/resources/app.md +++ b/docs/resources/app.md @@ -26,12 +26,13 @@ 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" - share = "owner" - subdomain = false + agent_id = coder_agent.dev.id + slug = "code-server" + display_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 @@ -40,17 +41,19 @@ resource "coder_app" "code-server" { } resource "coder_app" "vim" { - agent_id = coder_agent.dev.id - name = "Vim" - icon = "${data.coder_workspace.me.access_url}/icon/vim.svg" - command = "vim" + agent_id = coder_agent.dev.id + slug = "vim" + display_name = "Vim" + icon = "${data.coder_workspace.me.access_url}/icon/vim.svg" + command = "vim" } resource "coder_app" "intellij" { - agent_id = coder_agent.dev.id - icon = "${data.coder_workspace.me.access_url}/icon/intellij.svg" - name = "JetBrains IntelliJ" - command = "projector run" + agent_id = coder_agent.dev.id + icon = "${data.coder_workspace.me.access_url}/icon/intellij.svg" + slug = "intellij" + display_name = "JetBrains IntelliJ" + command = "projector run" } ``` @@ -60,13 +63,15 @@ resource "coder_app" "intellij" { ### Required - `agent_id` (String) The "id" property of a "coder_agent" resource to associate with. +- `slug` (String) A hostname-friendly name for the app. This is used in URLs to access the app. May contain alphanumerics and hyphens. Cannot start/end with a hyphen or contain two consecutive hyphens. ### Optional - `command` (String) A command to run in a terminal opening this app. In the web, this will open in a new tab. In the CLI, this will SSH and execute the command. Either "command" or "url" may be specified, but not both. +- `display_name` (String) A display name to identify the app. Defaults to the slug. - `healthcheck` (Block Set, Max: 1) HTTP health checking to determine the application readiness. (see [below for nested schema](#nestedblock--healthcheck)) - `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. +- `name` (String, Deprecated) 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 "level" which the application is shared at. Valid levels are "owner" (default), "authenticated" and "public". Level "owner" disables sharing on the app, so only the workspace owner can access it. 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 site-wide via a flag on `coder server` (Enterprise only). - `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. diff --git a/examples/resources/coder_app/resource.tf b/examples/resources/coder_app/resource.tf index 48190440..762fa1a5 100644 --- a/examples/resources/coder_app/resource.tf +++ b/examples/resources/coder_app/resource.tf @@ -11,12 +11,13 @@ 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" - share = "owner" - subdomain = false + agent_id = coder_agent.dev.id + slug = "code-server" + display_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 @@ -25,15 +26,17 @@ resource "coder_app" "code-server" { } resource "coder_app" "vim" { - agent_id = coder_agent.dev.id - name = "Vim" - icon = "${data.coder_workspace.me.access_url}/icon/vim.svg" - command = "vim" + agent_id = coder_agent.dev.id + slug = "vim" + display_name = "Vim" + icon = "${data.coder_workspace.me.access_url}/icon/vim.svg" + command = "vim" } resource "coder_app" "intellij" { - agent_id = coder_agent.dev.id - icon = "${data.coder_workspace.me.access_url}/icon/intellij.svg" - name = "JetBrains IntelliJ" - command = "projector run" + agent_id = coder_agent.dev.id + icon = "${data.coder_workspace.me.access_url}/icon/intellij.svg" + slug = "intellij" + display_name = "JetBrains IntelliJ" + command = "projector run" } diff --git a/provider/app.go b/provider/app.go index 446dad72..af8b98d0 100644 --- a/provider/app.go +++ b/provider/app.go @@ -3,6 +3,7 @@ package provider import ( "context" "net/url" + "regexp" "github.com/google/uuid" "github.com/hashicorp/go-cty/cty" @@ -10,6 +11,18 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) +var ( + // appSlugRegex is the regex used to validate the slug of a coder_app + // resource. It must be a valid hostname and cannot contain two consecutive + // hyphens or start/end with a hyphen. + // + // This regex is duplicated in the Coder source code, so make sure to update + // it there as well. + // + // There are test cases for this regex in the Coder product. + appSlugRegex = regexp.MustCompile(`^[a-z0-9](-?[a-z0-9])*$`) +) + func appResource() *schema.Resource { return &schema.Resource{ Description: "Use this resource to define shortcuts to access applications in a workspace.", @@ -54,19 +67,40 @@ func appResource() *schema.Resource { return nil, nil }, }, - "name": { + "slug": { + Type: schema.TypeString, + Description: "A hostname-friendly name for the app. This is " + + "used in URLs to access the app. May contain " + + "alphanumerics and hyphens. Cannot start/end with a " + + "hyphen or contain two consecutive hyphens.", + ForceNew: true, + Required: true, + ValidateDiagFunc: func(val interface{}, c cty.Path) diag.Diagnostics { + valStr, ok := val.(string) + if !ok { + return diag.Errorf("expected string, got %T", val) + } + + if !appSlugRegex.MatchString(valStr) { + return diag.Errorf("invalid coder_app slug, must be a valid hostname (%q, cannot contain two consecutive hyphens or start/end with a hyphen): %q", appSlugRegex.String(), valStr) + } + + return nil + }, + }, + "display_name": { Type: schema.TypeString, - Description: "A display name to identify the app.", + Description: "A display name to identify the app. Defaults to the slug.", ForceNew: true, Optional: true, }, - "relative_path": { - Type: schema.TypeBool, - Deprecated: "`relative_path` on apps is deprecated, use `subdomain` instead.", - Description: "Specifies whether the URL will be accessed via a relative " + - "path or wildcard. Use if wildcard routing is unavailable. Defaults to true.", - ForceNew: true, - Optional: true, + "name": { + Type: schema.TypeString, + Description: "A display name to identify the app.", + Deprecated: "`name` on apps is deprecated, use `display_name` instead", + ForceNew: true, + Optional: true, + ConflictsWith: []string{"display_name"}, }, "subdomain": { Type: schema.TypeBool, @@ -77,6 +111,15 @@ func appResource() *schema.Resource { ForceNew: true, Optional: true, }, + "relative_path": { + Type: schema.TypeBool, + Deprecated: "`relative_path` on apps is deprecated, use `subdomain` instead.", + Description: "Specifies whether the URL will be accessed via a relative " + + "path or wildcard. Use if wildcard routing is unavailable. Defaults to true.", + ForceNew: true, + Optional: true, + ConflictsWith: []string{"subdomain"}, + }, "share": { Type: schema.TypeString, Description: `Determines the "level" which the application ` + diff --git a/provider/app_test.go b/provider/app_test.go index 4b99d8ee..25bb7c17 100644 --- a/provider/app_test.go +++ b/provider/app_test.go @@ -33,7 +33,8 @@ func TestApp(t *testing.T) { } resource "coder_app" "code-server" { agent_id = coder_agent.dev.id - name = "code-server" + slug = "code-server" + display_name = "code-server" icon = "builtin:vim" subdomain = false url = "http://localhost:13337" @@ -51,7 +52,8 @@ func TestApp(t *testing.T) { require.NotNil(t, resource) for _, key := range []string{ "agent_id", - "name", + "slug", + "display_name", "icon", "subdomain", // Should be set by default even though it isn't @@ -128,7 +130,8 @@ func TestApp(t *testing.T) { } resource "coder_app" "code-server" { agent_id = coder_agent.dev.id - name = "code-server" + slug = "code-server" + display_name = "code-server" icon = "builtin:vim" url = "http://localhost:13337" %s