diff --git a/docs/resources/app.md b/docs/resources/app.md index 6be99cf3..35a9951b 100644 --- a/docs/resources/app.md +++ b/docs/resources/app.md @@ -31,6 +31,7 @@ resource "coder_app" "code-server" { display_name = "VS Code" icon = "${data.coder_workspace.me.access_url}/icon/code.svg" url = "http://localhost:13337" + tooltip = "You need to [Install Coder Desktop](https://coder.com/docs/user-guides/desktop#install-coder-desktop) to use this button." share = "owner" subdomain = false open_in = "window" @@ -71,6 +72,7 @@ resource "coder_app" "vim" { - `order` (Number) The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order). - `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`. +- `tooltip` (String) Markdown text that is displayed when hovering over workspace apps. - `url` (String) An external url if `external=true` or a URL to be proxied to from inside the workspace. This should be of the form `http://localhost:PORT[/SUBPATH]`. Either `command` or `url` may be specified, but not both. ### Read-Only diff --git a/examples/resources/coder_app/resource.tf b/examples/resources/coder_app/resource.tf index 8aea7b99..8dab8807 100644 --- a/examples/resources/coder_app/resource.tf +++ b/examples/resources/coder_app/resource.tf @@ -16,6 +16,7 @@ resource "coder_app" "code-server" { display_name = "VS Code" icon = "${data.coder_workspace.me.access_url}/icon/code.svg" url = "http://localhost:13337" + tooltip = "You need to [Install Coder Desktop](https://coder.com/docs/user-guides/desktop#install-coder-desktop) to use this button." share = "owner" subdomain = false open_in = "window" diff --git a/provider/app.go b/provider/app.go index 52996a72..dd0428a1 100644 --- a/provider/app.go +++ b/provider/app.go @@ -27,6 +27,7 @@ var ( const ( appDisplayNameMaxLength = 64 // database column limit appGroupNameMaxLength = 64 + appTooltipMaxLength = 2048 ) func appResource() *schema.Resource { @@ -273,6 +274,23 @@ func appResource() *schema.Resource { return diag.Errorf(`invalid "coder_app" open_in value, must be one of "tab", "slim-window": %q`, valStr) }, }, + "tooltip": { + Type: schema.TypeString, + Description: "Markdown text that is displayed when hovering over workspace apps.", + ForceNew: true, + Optional: true, + ValidateDiagFunc: func(val any, c cty.Path) diag.Diagnostics { + valStr, ok := val.(string) + if !ok { + return diag.Errorf("expected string, got %T", val) + } + + if len(valStr) > appTooltipMaxLength { + return diag.Errorf("tooltip is too long (max %d characters)", appTooltipMaxLength) + } + return nil + }, + }, }, } } diff --git a/provider/app_test.go b/provider/app_test.go index 2b9a5580..b8d4c8e7 100644 --- a/provider/app_test.go +++ b/provider/app_test.go @@ -4,6 +4,7 @@ import ( "fmt" "regexp" "strconv" + "strings" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -44,6 +45,7 @@ func TestApp(t *testing.T) { order = 4 hidden = false open_in = "slim-window" + tooltip = "You need to [Install Coder Desktop](https://coder.com/docs/user-guides/desktop#install-coder-desktop) to use this button." } `, Check: func(state *terraform.State) error { @@ -68,6 +70,7 @@ func TestApp(t *testing.T) { "order", "hidden", "open_in", + "tooltip", } { value := resource.Primary.Attributes[key] t.Logf("%q = %q", key, value) @@ -535,4 +538,62 @@ func TestApp(t *testing.T) { }) } }) + + t.Run("Tooltip", func(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + tooltip string + expectError *regexp.Regexp + }{ + { + name: "Empty", + tooltip: "", + }, + { + name: "ValidTooltip", + tooltip: "You need to [Install Coder Desktop](https://coder.com/docs/user-guides/desktop" + + "#install-coder-desktop) to use this button.", + }, + { + name: "TooltipTooLong", + tooltip: strings.Repeat("a", 2049), + expectError: regexp.MustCompile("tooltip is too long"), + }, + } + + for _, c := range cases { + c := c + + t.Run(c.name, func(t *testing.T) { + t.Parallel() + + config := fmt.Sprintf(` + provider "coder" {} + resource "coder_agent" "dev" { + os = "linux" + arch = "amd64" + } + resource "coder_app" "code-server" { + agent_id = coder_agent.dev.id + slug = "code-server" + display_name = "Testing" + url = "http://localhost:13337" + open_in = "slim-window" + tooltip = "%s" + } + `, c.tooltip) + + resource.Test(t, resource.TestCase{ + ProviderFactories: coderFactory(), + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: config, + ExpectError: c.expectError, + }}, + }) + }) + } + }) }