Skip to content

Commit 4531753

Browse files
authored
feat: Add app support (#17)
This adds what are presently named "devurls" in v1. It seems this is a dated term, since this allows much more than accessing applications via URL. "coder open <name>" will launch any apps defined. If in the web, it'll open either a web terminal or port forward to the desired application. If in the terminal, it'll open the browser, or launch the command over SSH.
1 parent 99f1e9a commit 4531753

File tree

4 files changed

+210
-0
lines changed

4 files changed

+210
-0
lines changed

Diff for: docs/resources/app.md

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "coder_app Resource - terraform-provider-coder"
4+
subcategory: ""
5+
description: |-
6+
Use this resource to define shortcuts to access applications in a workspace.
7+
---
8+
9+
# coder_app (Resource)
10+
11+
Use this resource to define shortcuts to access applications in a workspace.
12+
13+
## Example Usage
14+
15+
```terraform
16+
data "coder_workspace" "me" {}
17+
18+
resource "coder_agent" "dev" {
19+
os = "linux"
20+
arch = "amd64"
21+
dir = "/workspace"
22+
startup_script = <<EOF
23+
curl -fsSL https://code-server.dev/install.sh | sh
24+
code-server --auth none --port 13337
25+
EOF
26+
}
27+
28+
resource "coder_app" "code-server" {
29+
agent_id = coder_agent.dev.id
30+
name = "VS Code"
31+
icon = data.coder_workspace.me.access_url + "/icons/vscode.svg"
32+
target = "http://localhost:13337"
33+
}
34+
35+
resource "coder_app" "vim" {
36+
agent_id = coder_agent.dev.id
37+
name = "Vim"
38+
icon = data.coder_workspace.me.access_url + "/icons/vim.svg"
39+
command = "vim"
40+
}
41+
42+
resource "coder_app" "intellij" {
43+
agent_id = coder_agent.dev.id
44+
icon = data.coder_workspace.me.access_url + "/icons/intellij.svg"
45+
name = "JetBrains IntelliJ"
46+
command = "projector run"
47+
}
48+
```
49+
50+
<!-- schema generated by tfplugindocs -->
51+
## Schema
52+
53+
### Required
54+
55+
- `agent_id` (String) The "id" property of a "coder_agent" resource to associate with.
56+
57+
### Optional
58+
59+
- `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.
60+
- `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/<path>"`.
61+
- `id` (String) The ID of this resource.
62+
- `name` (String) A display name to identify the app.
63+
- `target` (String) A URL to be proxied to from inside the workspace.
64+
65+

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

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
data "coder_workspace" "me" {}
2+
3+
resource "coder_agent" "dev" {
4+
os = "linux"
5+
arch = "amd64"
6+
dir = "/workspace"
7+
startup_script = <<EOF
8+
curl -fsSL https://code-server.dev/install.sh | sh
9+
code-server --auth none --port 13337
10+
EOF
11+
}
12+
13+
resource "coder_app" "code-server" {
14+
agent_id = coder_agent.dev.id
15+
name = "VS Code"
16+
icon = data.coder_workspace.me.access_url + "/icons/vscode.svg"
17+
target = "http://localhost:13337"
18+
}
19+
20+
resource "coder_app" "vim" {
21+
agent_id = coder_agent.dev.id
22+
name = "Vim"
23+
icon = data.coder_workspace.me.access_url + "/icons/vim.svg"
24+
command = "vim"
25+
}
26+
27+
resource "coder_app" "intellij" {
28+
agent_id = coder_agent.dev.id
29+
icon = data.coder_workspace.me.access_url + "/icons/intellij.svg"
30+
name = "JetBrains IntelliJ"
31+
command = "projector run"
32+
}

Diff for: internal/provider/provider.go

+69
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,19 @@ func New() *schema.Provider {
8989
id = uuid.NewString()
9090
}
9191
rd.SetId(id)
92+
config, valid := i.(config)
93+
if !valid {
94+
return diag.Errorf("config was unexpected type %q", reflect.TypeOf(i).String())
95+
}
96+
rd.Set("access_url", config.URL.String())
9297
return nil
9398
},
9499
Schema: map[string]*schema.Schema{
100+
"access_url": {
101+
Type: schema.TypeString,
102+
Computed: true,
103+
Description: "The access URL of the Coder deployment provisioning this workspace.",
104+
},
95105
"start_count": {
96106
Type: schema.TypeInt,
97107
Computed: true,
@@ -227,6 +237,65 @@ func New() *schema.Provider {
227237
},
228238
},
229239
},
240+
"coder_app": {
241+
Description: "Use this resource to define shortcuts to access applications in a workspace.",
242+
CreateContext: func(c context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics {
243+
resourceData.SetId(uuid.NewString())
244+
return nil
245+
},
246+
ReadContext: func(c context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics {
247+
return nil
248+
},
249+
DeleteContext: func(ctx context.Context, rd *schema.ResourceData, i interface{}) diag.Diagnostics {
250+
return nil
251+
},
252+
Schema: map[string]*schema.Schema{
253+
"agent_id": {
254+
Type: schema.TypeString,
255+
Description: `The "id" property of a "coder_agent" resource to associate with.`,
256+
ForceNew: true,
257+
Required: true,
258+
},
259+
"command": {
260+
Type: schema.TypeString,
261+
Description: "A command to run in a terminal opening this app. In the web, " +
262+
"this will open in a new tab. In the CLI, this will SSH and execute the command. " +
263+
"Either \"command\" or \"target\" may be specified, but not both.",
264+
ConflictsWith: []string{"target"},
265+
Optional: true,
266+
ForceNew: true,
267+
},
268+
"icon": {
269+
Type: schema.TypeString,
270+
Description: "A URL to an icon that will display in the dashboard. View built-in " +
271+
"icons here: https://github.com/coder/coder/tree/main/site/static/icons. Use a " +
272+
"built-in icon with `data.coder_workspace.me.access_url + \"/icons/<path>\"`.",
273+
ForceNew: true,
274+
Optional: true,
275+
ValidateFunc: func(i interface{}, s string) ([]string, []error) {
276+
_, err := url.Parse(s)
277+
if err != nil {
278+
return nil, []error{err}
279+
}
280+
return nil, nil
281+
},
282+
},
283+
"name": {
284+
Type: schema.TypeString,
285+
Description: "A display name to identify the app.",
286+
ForceNew: true,
287+
Optional: true,
288+
},
289+
"target": {
290+
Type: schema.TypeString,
291+
Description: "A URL to be proxied to from inside the workspace. " +
292+
"Either \"command\" or \"target\" may be specified, but not both.",
293+
ForceNew: true,
294+
Optional: true,
295+
ConflictsWith: []string{"command"},
296+
},
297+
},
298+
},
230299
},
231300
}
232301
}

Diff for: internal/provider/provider_test.go

+44
Original file line numberDiff line numberDiff line change
@@ -134,3 +134,47 @@ func TestAgentInstance(t *testing.T) {
134134
}},
135135
})
136136
}
137+
138+
func TestApp(t *testing.T) {
139+
t.Parallel()
140+
resource.Test(t, resource.TestCase{
141+
Providers: map[string]*schema.Provider{
142+
"coder": provider.New(),
143+
},
144+
IsUnitTest: true,
145+
Steps: []resource.TestStep{{
146+
Config: `
147+
provider "coder" {
148+
}
149+
resource "coder_agent" "dev" {
150+
os = "linux"
151+
arch = "amd64"
152+
}
153+
resource "coder_app" "code-server" {
154+
agent_id = coder_agent.dev.id
155+
name = "code-server"
156+
icon = "builtin:vim"
157+
target = "http://localhost:13337"
158+
}
159+
`,
160+
Check: func(state *terraform.State) error {
161+
require.Len(t, state.Modules, 1)
162+
require.Len(t, state.Modules[0].Resources, 2)
163+
resource := state.Modules[0].Resources["coder_app.code-server"]
164+
require.NotNil(t, resource)
165+
for _, key := range []string{
166+
"agent_id",
167+
"name",
168+
"icon",
169+
"target",
170+
} {
171+
value := resource.Primary.Attributes[key]
172+
t.Logf("%q = %q", key, value)
173+
require.NotNil(t, value)
174+
require.Greater(t, len(value), 0)
175+
}
176+
return nil
177+
},
178+
}},
179+
})
180+
}

0 commit comments

Comments
 (0)