Skip to content

Commit d933a71

Browse files
authored
add open_in option to coder_app (#321)
* add open_in option to coder_app * work on tests * add missing example * rename test * lint * generate docs
1 parent 8349a69 commit d933a71

File tree

6 files changed

+207
-0
lines changed

6 files changed

+207
-0
lines changed

docs/resources/app.md

+2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ resource "coder_app" "code-server" {
3333
url = "http://localhost:13337"
3434
share = "owner"
3535
subdomain = false
36+
open_in = "window"
3637
healthcheck {
3738
url = "http://localhost:13337/healthz"
3839
interval = 5
@@ -65,6 +66,7 @@ resource "coder_app" "vim" {
6566
- `healthcheck` (Block Set, Max: 1) HTTP health checking to determine the application readiness. (see [below for nested schema](#nestedblock--healthcheck))
6667
- `hidden` (Boolean) Determines if the app is visible in the UI (minimum Coder version: v2.16).
6768
- `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/<path>"`.
69+
- `open_in` (String) Determines where the app will be opened. Valid values are `"tab"`, `"window"`, and `"slim-window" (default)`. `"tab"` opens in a new tab in the same browser window. `"window"` opens a fresh browser window with navigation options. `"slim-window"` opens a new browser window without navigation controls.
6870
- `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).
6971
- `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).
7072
- `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`.

examples/resources/coder_app/resource.tf

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ resource "coder_app" "code-server" {
1818
url = "http://localhost:13337"
1919
share = "owner"
2020
subdomain = false
21+
open_in = "window"
2122
healthcheck {
2223
url = "http://localhost:13337/healthz"
2324
interval = 5

integration/coder-app-open-in/main.tf

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
terraform {
2+
required_providers {
3+
coder = {
4+
source = "coder/coder"
5+
}
6+
local = {
7+
source = "hashicorp/local"
8+
}
9+
}
10+
}
11+
12+
data "coder_workspace" "me" {}
13+
14+
resource "coder_agent" "dev" {
15+
os = "linux"
16+
arch = "amd64"
17+
dir = "/workspace"
18+
}
19+
20+
resource "coder_app" "window" {
21+
agent_id = coder_agent.dev.id
22+
slug = "window"
23+
share = "owner"
24+
open_in = "window"
25+
}
26+
27+
resource "coder_app" "slim-window" {
28+
agent_id = coder_agent.dev.id
29+
slug = "slim-window"
30+
share = "owner"
31+
open_in = "slim-window"
32+
}
33+
34+
resource "coder_app" "defaulted" {
35+
agent_id = coder_agent.dev.id
36+
slug = "defaulted"
37+
share = "owner"
38+
}
39+
40+
locals {
41+
# NOTE: these must all be strings in the output
42+
output = {
43+
"coder_app.window.open_in" = tostring(coder_app.window.open_in)
44+
"coder_app.slim-window.open_in" = tostring(coder_app.slim-window.open_in)
45+
"coder_app.defaulted.open_in" = tostring(coder_app.defaulted.open_in)
46+
}
47+
}
48+
49+
variable "output_path" {
50+
type = string
51+
}
52+
53+
resource "local_file" "output" {
54+
filename = var.output_path
55+
content = jsonencode(local.output)
56+
}
57+
58+
output "output" {
59+
value = local.output
60+
sensitive = true
61+
}
62+

integration/integration_test.go

+9
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,15 @@ func TestIntegration(t *testing.T) {
143143
"workspace_owner.login_type": `password`,
144144
},
145145
},
146+
{
147+
name: "coder-app-open-in",
148+
minVersion: "v2.19.0",
149+
expectedOutput: map[string]string{
150+
"coder_app.window.open_in": "window",
151+
"coder_app.slim-window.open_in": "slim-window",
152+
"coder_app.defaulted.open_in": "slim-window",
153+
},
154+
},
146155
{
147156
name: "coder-app-hidden",
148157
minVersion: "v0.0.0",

provider/app.go

+22
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,28 @@ func appResource() *schema.Resource {
223223
ForceNew: true,
224224
Optional: true,
225225
},
226+
"open_in": {
227+
Type: schema.TypeString,
228+
Description: "Determines where the app will be opened. Valid values are `\"tab\"`, `\"window\"`, and `\"slim-window\" (default)`. " +
229+
"`\"tab\"` opens in a new tab in the same browser window. `\"window\"` opens a fresh browser window with navigation options. " +
230+
"`\"slim-window\"` opens a new browser window without navigation controls.",
231+
ForceNew: true,
232+
Optional: true,
233+
Default: "slim-window",
234+
ValidateDiagFunc: func(val interface{}, c cty.Path) diag.Diagnostics {
235+
valStr, ok := val.(string)
236+
if !ok {
237+
return diag.Errorf("expected string, got %T", val)
238+
}
239+
240+
switch valStr {
241+
case "tab", "window", "slim-window":
242+
return nil
243+
}
244+
245+
return diag.Errorf(`invalid "coder_app" open_in value, must be one of "tab", "window", "slim-window": %q`, valStr)
246+
},
247+
},
226248
},
227249
}
228250
}

provider/app_test.go

+111
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ func TestApp(t *testing.T) {
4242
}
4343
order = 4
4444
hidden = false
45+
open_in = "slim-window"
4546
}
4647
`,
4748
Check: func(state *terraform.State) error {
@@ -64,6 +65,7 @@ func TestApp(t *testing.T) {
6465
"healthcheck.0.threshold",
6566
"order",
6667
"hidden",
68+
"open_in",
6769
} {
6870
value := resource.Primary.Attributes[key]
6971
t.Logf("%q = %q", key, value)
@@ -98,6 +100,7 @@ func TestApp(t *testing.T) {
98100
display_name = "Testing"
99101
url = "https://google.com"
100102
external = true
103+
open_in = "slim-window"
101104
}
102105
`,
103106
external: true,
@@ -116,6 +119,7 @@ func TestApp(t *testing.T) {
116119
url = "https://google.com"
117120
external = true
118121
subdomain = true
122+
open_in = "slim-window"
119123
}
120124
`,
121125
expectError: regexp.MustCompile("conflicts with subdomain"),
@@ -209,6 +213,7 @@ func TestApp(t *testing.T) {
209213
interval = 5
210214
threshold = 6
211215
}
216+
open_in = "slim-window"
212217
}
213218
`, sharingLine)
214219

@@ -241,13 +246,114 @@ func TestApp(t *testing.T) {
241246
}
242247
})
243248

249+
t.Run("OpenIn", func(t *testing.T) {
250+
t.Parallel()
251+
252+
cases := []struct {
253+
name string
254+
value string
255+
expectValue string
256+
expectError *regexp.Regexp
257+
}{
258+
{
259+
name: "default",
260+
value: "", // default
261+
expectValue: "slim-window",
262+
},
263+
{
264+
name: "InvalidValue",
265+
value: "nonsense",
266+
expectError: regexp.MustCompile(`invalid "coder_app" open_in value, must be one of "tab", "window", "slim-window": "nonsense"`),
267+
},
268+
{
269+
name: "ExplicitWindow",
270+
value: "window",
271+
expectValue: "window",
272+
},
273+
{
274+
name: "ExplicitSlimWindow",
275+
value: "slim-window",
276+
expectValue: "slim-window",
277+
},
278+
{
279+
name: "ExplicitTab",
280+
value: "tab",
281+
expectValue: "tab",
282+
},
283+
}
284+
285+
for _, c := range cases {
286+
c := c
287+
288+
t.Run(c.name, func(t *testing.T) {
289+
t.Parallel()
290+
291+
config := `
292+
provider "coder" {
293+
}
294+
resource "coder_agent" "dev" {
295+
os = "linux"
296+
arch = "amd64"
297+
}
298+
resource "coder_app" "code-server" {
299+
agent_id = coder_agent.dev.id
300+
slug = "code-server"
301+
display_name = "code-server"
302+
icon = "builtin:vim"
303+
url = "http://localhost:13337"
304+
healthcheck {
305+
url = "http://localhost:13337/healthz"
306+
interval = 5
307+
threshold = 6
308+
}`
309+
310+
if c.value != "" {
311+
config += fmt.Sprintf(`
312+
open_in = %q
313+
`, c.value)
314+
}
315+
316+
config += `
317+
}
318+
`
319+
320+
checkFn := func(state *terraform.State) error {
321+
require.Len(t, state.Modules, 1)
322+
require.Len(t, state.Modules[0].Resources, 2)
323+
resource := state.Modules[0].Resources["coder_app.code-server"]
324+
require.NotNil(t, resource)
325+
326+
// Read share and ensure it matches the expected
327+
// value.
328+
value := resource.Primary.Attributes["open_in"]
329+
require.Equal(t, c.expectValue, value)
330+
return nil
331+
}
332+
if c.expectError != nil {
333+
checkFn = nil
334+
}
335+
336+
resource.Test(t, resource.TestCase{
337+
ProviderFactories: coderFactory(),
338+
IsUnitTest: true,
339+
Steps: []resource.TestStep{{
340+
Config: config,
341+
Check: checkFn,
342+
ExpectError: c.expectError,
343+
}},
344+
})
345+
})
346+
}
347+
})
348+
244349
t.Run("Hidden", func(t *testing.T) {
245350
t.Parallel()
246351

247352
cases := []struct {
248353
name string
249354
config string
250355
hidden bool
356+
openIn string
251357
}{{
252358
name: "Is Hidden",
253359
config: `
@@ -263,9 +369,11 @@ func TestApp(t *testing.T) {
263369
url = "https://google.com"
264370
external = true
265371
hidden = true
372+
open_in = "slim-window"
266373
}
267374
`,
268375
hidden: true,
376+
openIn: "slim-window",
269377
}, {
270378
name: "Is Not Hidden",
271379
config: `
@@ -281,9 +389,11 @@ func TestApp(t *testing.T) {
281389
url = "https://google.com"
282390
external = true
283391
hidden = false
392+
open_in = "window"
284393
}
285394
`,
286395
hidden: false,
396+
openIn: "window",
287397
}}
288398
for _, tc := range cases {
289399
tc := tc
@@ -300,6 +410,7 @@ func TestApp(t *testing.T) {
300410
resource := state.Modules[0].Resources["coder_app.test"]
301411
require.NotNil(t, resource)
302412
require.Equal(t, strconv.FormatBool(tc.hidden), resource.Primary.Attributes["hidden"])
413+
require.Equal(t, tc.openIn, resource.Primary.Attributes["open_in"])
303414
return nil
304415
},
305416
ExpectError: nil,

0 commit comments

Comments
 (0)