Skip to content

Commit 233ea60

Browse files
authored
feat: add default_apps field to coder_agent resource (#147)
- Enables a template admin to configure which apps are displayed to the user in the dashboard.
1 parent 188ffef commit 233ea60

File tree

7 files changed

+287
-4
lines changed

7 files changed

+287
-4
lines changed

Diff for: docs/data-sources/workspace.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,6 @@ resource "kubernetes_pod" "dev" {
3434
- `owner_email` (String) Email address of the workspace owner.
3535
- `owner_id` (String) UUID of the workspace owner.
3636
- `owner_oidc_access_token` (String) A valid OpenID Connect access token of the workspace owner. This is only available if the workspace owner authenticated with OpenID Connect. If a valid token cannot be obtained, this value will be an empty string.
37-
- `owner_session_token` (String) Session token for interfacing with a Coder deployment. It is regenerated everytime a workspace is started.
37+
- `owner_session_token` (String) Session token for authenticating with a Coder deployment. It is regenerated everytime a workspace is started.
3838
- `start_count` (Number) A computed count based on "transition" state. If "start", count will equal 1.
3939
- `transition` (String) Either "start" or "stop". Use this to start/stop resources with "count".

Diff for: docs/resources/agent.md

+19
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ resource "coder_agent" "dev" {
2020
os = "linux"
2121
arch = "amd64"
2222
dir = "/workspace"
23+
display_apps {
24+
vscode = true
25+
vscode_insiders = false
26+
web_terminal = true
27+
ssh_helper = false
28+
}
2329
}
2430
2531
resource "kubernetes_pod" "dev" {
@@ -49,6 +55,7 @@ resource "kubernetes_pod" "dev" {
4955
- `auth` (String) The authentication type the agent will use. Must be one of: "token", "google-instance-identity", "aws-instance-identity", "azure-instance-identity".
5056
- `connection_timeout` (Number) Time in seconds until the agent is marked as timed out when a connection with the server cannot be established. A value of zero never marks the agent as timed out.
5157
- `dir` (String) The starting directory when a user creates a shell session. Defaults to $HOME.
58+
- `display_apps` (Block Set, Max: 1) The list of built-in apps to display in the agent bar. (see [below for nested schema](#nestedblock--display_apps))
5259
- `env` (Map of String) A mapping of environment variables to set inside the workspace.
5360
- `login_before_ready` (Boolean, Deprecated) This option defines whether or not the user can (by default) login to the workspace before it is ready. Ready means that e.g. the startup_script is done and has exited. When enabled, users may see an incomplete workspace when logging in.
5461
- `metadata` (Block List) Each "metadata" block defines a single item consisting of a key/value pair. This feature is in alpha and may break in future releases. (see [below for nested schema](#nestedblock--metadata))
@@ -66,6 +73,18 @@ resource "kubernetes_pod" "dev" {
6673
- `init_script` (String) Run this script on startup of an instance to initialize the agent.
6774
- `token` (String, Sensitive) Set the environment variable "CODER_AGENT_TOKEN" with this token to authenticate an agent.
6875

76+
<a id="nestedblock--display_apps"></a>
77+
### Nested Schema for `display_apps`
78+
79+
Optional:
80+
81+
- `port_forwarding_helper` (Boolean) Display the port-forwarding helper button in the agent bar.
82+
- `ssh_helper` (Boolean) Display the SSH helper button in the agent bar.
83+
- `vscode` (Boolean) Display the VSCode Desktop app in the agent bar.
84+
- `vscode_insiders` (Boolean) Display the VSCode Insiders app in the agent bar.
85+
- `web_terminal` (Boolean) Display the web terminal app in the agent bar.
86+
87+
6988
<a id="nestedblock--metadata"></a>
7089
### Nested Schema for `metadata`
7190

Diff for: examples/resources/coder_agent/resource.tf

+6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ resource "coder_agent" "dev" {
55
os = "linux"
66
arch = "amd64"
77
dir = "/workspace"
8+
display_apps {
9+
vscode = true
10+
vscode_insiders = false
11+
web_terminal = true
12+
ssh_helper = false
13+
}
814
}
915

1016
resource "kubernetes_pod" "dev" {

Diff for: provider/agent.go

+77
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,43 @@ func agentResource() *schema.Resource {
2323
if err != nil {
2424
return diag.FromErr(err)
2525
}
26+
27+
if _, ok := resourceData.GetOk("display_apps"); !ok {
28+
err = resourceData.Set("display_apps", []interface{}{
29+
map[string]bool{
30+
"vscode": true,
31+
"vscode_insiders": false,
32+
"web_terminal": true,
33+
"ssh_helper": true,
34+
"port_forwarding_helper": true,
35+
},
36+
})
37+
if err != nil {
38+
return diag.FromErr(err)
39+
}
40+
}
2641
return updateInitScript(resourceData, i)
2742
},
2843
ReadWithoutTimeout: func(ctx context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics {
2944
err := resourceData.Set("token", uuid.NewString())
3045
if err != nil {
3146
return diag.FromErr(err)
3247
}
48+
if _, ok := resourceData.GetOk("display_apps"); !ok {
49+
err = resourceData.Set("display_apps", []interface{}{
50+
map[string]bool{
51+
"vscode": true,
52+
"vscode_insiders": false,
53+
"web_terminal": true,
54+
"ssh_helper": true,
55+
"port_forwarding_helper": true,
56+
},
57+
})
58+
if err != nil {
59+
return diag.FromErr(err)
60+
}
61+
}
62+
3363
return updateInitScript(resourceData, i)
3464
},
3565
DeleteContext: func(ctx context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics {
@@ -198,6 +228,53 @@ func agentResource() *schema.Resource {
198228
},
199229
},
200230
},
231+
"display_apps": {
232+
Type: schema.TypeSet,
233+
Description: "The list of built-in apps to display in the agent bar.",
234+
ForceNew: true,
235+
Optional: true,
236+
MaxItems: 1,
237+
Computed: true,
238+
Elem: &schema.Resource{
239+
Schema: map[string]*schema.Schema{
240+
"vscode": {
241+
Type: schema.TypeBool,
242+
Description: "Display the VSCode Desktop app in the agent bar.",
243+
ForceNew: true,
244+
Optional: true,
245+
Default: true,
246+
},
247+
"vscode_insiders": {
248+
Type: schema.TypeBool,
249+
Description: "Display the VSCode Insiders app in the agent bar.",
250+
ForceNew: true,
251+
Optional: true,
252+
Default: false,
253+
},
254+
"web_terminal": {
255+
Type: schema.TypeBool,
256+
Description: "Display the web terminal app in the agent bar.",
257+
ForceNew: true,
258+
Optional: true,
259+
Default: true,
260+
},
261+
"port_forwarding_helper": {
262+
Type: schema.TypeBool,
263+
Description: "Display the port-forwarding helper button in the agent bar.",
264+
ForceNew: true,
265+
Optional: true,
266+
Default: true,
267+
},
268+
"ssh_helper": {
269+
Type: schema.TypeBool,
270+
Description: "Display the SSH helper button in the agent bar.",
271+
ForceNew: true,
272+
Optional: true,
273+
Default: true,
274+
},
275+
},
276+
},
277+
},
201278
},
202279
}
203280
}

Diff for: provider/agent_test.go

+181-1
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
package provider_test
22

33
import (
4+
"fmt"
45
"regexp"
56
"testing"
67

7-
"github.com/coder/terraform-provider-coder/provider"
88
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
99
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1010
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
1111
"github.com/stretchr/testify/require"
12+
13+
"github.com/coder/terraform-provider-coder/provider"
1214
)
1315

1416
func TestAgent(t *testing.T) {
@@ -247,3 +249,181 @@ func TestAgent_Metadata(t *testing.T) {
247249
}},
248250
})
249251
}
252+
253+
func TestAgent_DisplayApps(t *testing.T) {
254+
t.Parallel()
255+
t.Run("OK", func(t *testing.T) {
256+
resource.Test(t, resource.TestCase{
257+
Providers: map[string]*schema.Provider{
258+
"coder": provider.New(),
259+
},
260+
IsUnitTest: true,
261+
Steps: []resource.TestStep{{
262+
// Test the fields with non-default values.
263+
Config: `
264+
provider "coder" {
265+
url = "https://example.com"
266+
}
267+
resource "coder_agent" "dev" {
268+
os = "linux"
269+
arch = "amd64"
270+
display_apps {
271+
vscode = false
272+
vscode_insiders = true
273+
web_terminal = false
274+
port_forwarding_helper = false
275+
ssh_helper = false
276+
}
277+
}
278+
`,
279+
Check: func(state *terraform.State) error {
280+
require.Len(t, state.Modules, 1)
281+
require.Len(t, state.Modules[0].Resources, 1)
282+
283+
resource := state.Modules[0].Resources["coder_agent.dev"]
284+
require.NotNil(t, resource)
285+
286+
t.Logf("resource: %v", resource.Primary.Attributes)
287+
288+
for _, app := range []string{
289+
"web_terminal",
290+
"vscode_insiders",
291+
"vscode",
292+
"port_forwarding_helper",
293+
"ssh_helper",
294+
} {
295+
key := fmt.Sprintf("display_apps.0.%s", app)
296+
if app == "vscode_insiders" {
297+
require.Equal(t, "true", resource.Primary.Attributes[key])
298+
} else {
299+
require.Equal(t, "false", resource.Primary.Attributes[key])
300+
}
301+
}
302+
return nil
303+
},
304+
}},
305+
})
306+
})
307+
308+
t.Run("Subset", func(t *testing.T) {
309+
resource.Test(t, resource.TestCase{
310+
Providers: map[string]*schema.Provider{
311+
"coder": provider.New(),
312+
},
313+
IsUnitTest: true,
314+
Steps: []resource.TestStep{{
315+
// Test the fields with non-default values.
316+
Config: `
317+
provider "coder" {
318+
url = "https://example.com"
319+
}
320+
resource "coder_agent" "dev" {
321+
os = "linux"
322+
arch = "amd64"
323+
display_apps {
324+
vscode_insiders = true
325+
web_terminal = true
326+
}
327+
}
328+
`,
329+
Check: func(state *terraform.State) error {
330+
require.Len(t, state.Modules, 1)
331+
require.Len(t, state.Modules[0].Resources, 1)
332+
333+
resource := state.Modules[0].Resources["coder_agent.dev"]
334+
require.NotNil(t, resource)
335+
336+
t.Logf("resource: %v", resource.Primary.Attributes)
337+
338+
for _, app := range []string{
339+
"web_terminal",
340+
"vscode_insiders",
341+
"vscode",
342+
"port_forwarding_helper",
343+
"ssh_helper",
344+
} {
345+
key := fmt.Sprintf("display_apps.0.%s", app)
346+
require.Equal(t, "true", resource.Primary.Attributes[key])
347+
}
348+
return nil
349+
},
350+
}},
351+
})
352+
})
353+
354+
// Assert all the defaults are set correctly.
355+
t.Run("Omitted", func(t *testing.T) {
356+
resource.Test(t, resource.TestCase{
357+
Providers: map[string]*schema.Provider{
358+
"coder": provider.New(),
359+
},
360+
IsUnitTest: true,
361+
Steps: []resource.TestStep{{
362+
Config: `
363+
provider "coder" {
364+
url = "https://example.com"
365+
}
366+
resource "coder_agent" "dev" {
367+
os = "linux"
368+
arch = "amd64"
369+
}
370+
`,
371+
Check: func(state *terraform.State) error {
372+
require.Len(t, state.Modules, 1)
373+
require.Len(t, state.Modules[0].Resources, 1)
374+
375+
resource := state.Modules[0].Resources["coder_agent.dev"]
376+
require.NotNil(t, resource)
377+
378+
t.Logf("resource: %v", resource.Primary.Attributes)
379+
380+
for _, app := range []string{
381+
"web_terminal",
382+
"vscode_insiders",
383+
"vscode",
384+
"port_forwarding_helper",
385+
"ssh_helper",
386+
} {
387+
key := fmt.Sprintf("display_apps.0.%s", app)
388+
if app == "vscode_insiders" {
389+
require.Equal(t, "false", resource.Primary.Attributes[key])
390+
} else {
391+
require.Equal(t, "true", resource.Primary.Attributes[key])
392+
}
393+
}
394+
return nil
395+
},
396+
}},
397+
})
398+
})
399+
400+
t.Run("InvalidApp", func(t *testing.T) {
401+
resource.Test(t, resource.TestCase{
402+
Providers: map[string]*schema.Provider{
403+
"coder": provider.New(),
404+
},
405+
IsUnitTest: true,
406+
Steps: []resource.TestStep{{
407+
// Test the fields with non-default values.
408+
Config: `
409+
provider "coder" {
410+
url = "https://example.com"
411+
}
412+
resource "coder_agent" "dev" {
413+
os = "linux"
414+
arch = "amd64"
415+
display_apps {
416+
fake_app = false
417+
vscode_insiders = true
418+
web_terminal = false
419+
port_forwarding_helper = false
420+
ssh_helper = false
421+
}
422+
}
423+
`,
424+
ExpectError: regexp.MustCompile(`An argument named "fake_app" is not expected here.`),
425+
}},
426+
})
427+
})
428+
429+
}

Diff for: provider/examples_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ import (
44
"os"
55
"testing"
66

7-
"github.com/coder/terraform-provider-coder/provider"
87
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
98
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
109
"github.com/stretchr/testify/require"
10+
11+
"github.com/coder/terraform-provider-coder/provider"
1112
)
1213

1314
func TestExamples(t *testing.T) {

Diff for: provider/workspace.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ func workspaceDataSource() *schema.Resource {
137137
"owner_session_token": {
138138
Type: schema.TypeString,
139139
Computed: true,
140-
Description: "Session token for interfacing with a Coder deployment. It is regenerated everytime a workspace is started.",
140+
Description: "Session token for authenticating with a Coder deployment. It is regenerated everytime a workspace is started.",
141141
},
142142
},
143143
}

0 commit comments

Comments
 (0)