From aaa97249231399845a939be092167e8fc4d72fe1 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Fri, 6 Sep 2024 10:53:19 +0100 Subject: [PATCH] feat: add 'hidden' field to 'coder_app' provider (#276) * feat: add 'hidden' field to 'coder_app' provider * fix: run 'make gen' * test: add integration test for 'coder_app.hidden' * fix: warn on 'hidden' being used with conflicting fields --- docs/resources/app.md | 1 + integration/coder-app-hidden/main.tf | 62 +++++++++++++++++++++++ integration/integration_test.go | 9 ++++ provider/app.go | 38 ++++++++++++++- provider/app_test.go | 73 ++++++++++++++++++++++++++++ 5 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 integration/coder-app-hidden/main.tf diff --git a/docs/resources/app.md b/docs/resources/app.md index f61ed799..aea3439c 100644 --- a/docs/resources/app.md +++ b/docs/resources/app.md @@ -63,6 +63,7 @@ resource "coder_app" "vim" { - `display_name` (String) A display name to identify the app. Defaults to the slug. - `external` (Boolean) Specifies whether `url` is opened on the client machine instead of proxied through the workspace. - `healthcheck` (Block Set, Max: 1) HTTP health checking to determine the application readiness. (see [below for nested schema](#nestedblock--healthcheck)) +- `hidden` (Boolean) Determines if the app is visible in the UI. - `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/icon. Use a built-in icon with `"${data.coder_workspace.me.access_url}/icon/"`. - `name` (String, **Deprecated**: `name` on apps is deprecated, use `display_name` instead) A display name to identify the app. - `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). diff --git a/integration/coder-app-hidden/main.tf b/integration/coder-app-hidden/main.tf new file mode 100644 index 00000000..6376b25d --- /dev/null +++ b/integration/coder-app-hidden/main.tf @@ -0,0 +1,62 @@ +terraform { + required_providers { + coder = { + source = "coder/coder" + } + local = { + source = "hashicorp/local" + } + } +} + +data "coder_workspace" "me" {} + +resource "coder_agent" "dev" { + os = "linux" + arch = "amd64" + dir = "/workspace" +} + +resource "coder_app" "hidden" { + agent_id = coder_agent.dev.id + slug = "hidden" + share = "owner" + hidden = true +} + +resource "coder_app" "visible" { + agent_id = coder_agent.dev.id + slug = "visible" + share = "owner" + hidden = false +} + +resource "coder_app" "defaulted" { + agent_id = coder_agent.dev.id + slug = "defaulted" + share = "owner" +} + +locals { + # NOTE: these must all be strings in the output + output = { + "coder_app.hidden.hidden" = tostring(coder_app.hidden.hidden) + "coder_app.visible.hidden" = tostring(coder_app.visible.hidden) + "coder_app.defaulted.hidden" = tostring(coder_app.defaulted.hidden) + } +} + +variable "output_path" { + type = string +} + +resource "local_file" "output" { + filename = var.output_path + content = jsonencode(local.output) +} + +output "output" { + value = local.output + sensitive = true +} + diff --git a/integration/integration_test.go b/integration/integration_test.go index 322d5816..375545fb 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -126,6 +126,15 @@ func TestIntegration(t *testing.T) { "workspace_owner.ssh_public_key": `(?s)^ssh-ed25519.+$`, }, }, + { + name: "coder-app-hidden", + minVersion: "v0.0.0", + expectedOutput: map[string]string{ + "coder_app.hidden.hidden": "true", + "coder_app.visible.hidden": "false", + "coder_app.defaulted.hidden": "false", + }, + }, } { tt := tt t.Run(tt.name, func(t *testing.T) { diff --git a/provider/app.go b/provider/app.go index 91c08f0f..dcf6f22a 100644 --- a/provider/app.go +++ b/provider/app.go @@ -30,7 +30,36 @@ func appResource() *schema.Resource { Description: "Use this resource to define shortcuts to access applications in a workspace.", CreateContext: func(c context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics { resourceData.SetId(uuid.NewString()) - return nil + + diags := diag.Diagnostics{} + + hiddenData := resourceData.Get("hidden") + if hidden, ok := hiddenData.(bool); !ok { + return diag.Errorf("hidden should be a bool") + } else if hidden { + if _, ok := resourceData.GetOk("display_name"); ok { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Warning, + Summary: "`display_name` set when app is hidden", + }) + } + + if _, ok := resourceData.GetOk("icon"); ok { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Warning, + Summary: "`icon` set when app is hidden", + }) + } + + if _, ok := resourceData.GetOk("order"); ok { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Warning, + Summary: "`order` set when app is hidden", + }) + } + } + + return diags }, ReadContext: func(c context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics { return nil @@ -204,6 +233,13 @@ func appResource() *schema.Resource { ForceNew: true, Optional: true, }, + "hidden": { + Type: schema.TypeBool, + Description: "Determines if the app is visible in the UI.", + Default: false, + ForceNew: true, + Optional: true, + }, }, } } diff --git a/provider/app_test.go b/provider/app_test.go index f17513e1..80f1df14 100644 --- a/provider/app_test.go +++ b/provider/app_test.go @@ -45,6 +45,7 @@ func TestApp(t *testing.T) { threshold = 6 } order = 4 + hidden = false } `, Check: func(state *terraform.State) error { @@ -66,6 +67,7 @@ func TestApp(t *testing.T) { "healthcheck.0.interval", "healthcheck.0.threshold", "order", + "hidden", } { value := resource.Primary.Attributes[key] t.Logf("%q = %q", key, value) @@ -246,4 +248,75 @@ func TestApp(t *testing.T) { }) } }) + + t.Run("Hidden", func(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + config string + hidden bool + }{{ + name: "Is Hidden", + config: ` + provider "coder" {} + resource "coder_agent" "dev" { + os = "linux" + arch = "amd64" + } + resource "coder_app" "test" { + agent_id = coder_agent.dev.id + slug = "test" + display_name = "Testing" + url = "https://google.com" + external = true + hidden = true + } + `, + hidden: true, + }, { + name: "Is Not Hidden", + config: ` + provider "coder" {} + resource "coder_agent" "dev" { + os = "linux" + arch = "amd64" + } + resource "coder_app" "test" { + agent_id = coder_agent.dev.id + slug = "test" + display_name = "Testing" + url = "https://google.com" + external = true + hidden = false + } + `, + hidden: false, + }} + for _, tc := range cases { + tc := tc + t.Run(tc.name, 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: tc.config, + 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.test"] + require.NotNil(t, resource) + require.Equal(t, strconv.FormatBool(tc.hidden), resource.Primary.Attributes["hidden"]) + return nil + }, + ExpectError: nil, + }}, + }) + }) + } + }) + }