Skip to content
This repository was archived by the owner on May 15, 2025. It is now read-only.

Commit 8fd54e0

Browse files
authored
Merge pull request #262 from coder/web-rdp
feat: add module for Web RDP
2 parents 45456ab + e8ee02c commit 8fd54e0

File tree

9 files changed

+839
-56
lines changed

9 files changed

+839
-56
lines changed

.icons/desktop.svg

Lines changed: 5 additions & 0 deletions
Loading

package-lock.json

Lines changed: 17 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test.ts

Lines changed: 57 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@ export const runContainer = async (
2929
return containerID.trim();
3030
};
3131

32-
// executeScriptInContainer finds the only "coder_script"
33-
// resource in the given state and runs it in a container.
32+
/**
33+
* Finds the only "coder_script" resource in the given state and runs it in a
34+
* container.
35+
*/
3436
export const executeScriptInContainer = async (
3537
state: TerraformState,
3638
image: string,
@@ -76,27 +78,30 @@ export const execContainer = async (
7678
};
7779
};
7880

81+
type JsonValue =
82+
| string
83+
| number
84+
| boolean
85+
| null
86+
| JsonValue[]
87+
| { [key: string]: JsonValue };
88+
89+
type TerraformStateResource = {
90+
type: string;
91+
name: string;
92+
provider: string;
93+
instances: [{ attributes: Record<string, any> }];
94+
};
95+
7996
export interface TerraformState {
8097
outputs: {
8198
[key: string]: {
8299
type: string;
83100
value: any;
84101
};
85-
}
86-
resources: [
87-
{
88-
type: string;
89-
name: string;
90-
provider: string;
91-
instances: [
92-
{
93-
attributes: {
94-
[key: string]: any;
95-
};
96-
},
97-
];
98-
},
99-
];
102+
};
103+
104+
resources: [TerraformStateResource, ...TerraformStateResource[]];
100105
}
101106

102107
export interface CoderScriptAttributes {
@@ -105,10 +110,11 @@ export interface CoderScriptAttributes {
105110
url: string;
106111
}
107112

108-
// findResourceInstance finds the first instance of the given resource
109-
// type in the given state. If name is specified, it will only find
110-
// the instance with the given name.
111-
export const findResourceInstance = <T extends "coder_script" | string>(
113+
/**
114+
* finds the first instance of the given resource type in the given state. If
115+
* name is specified, it will only find the instance with the given name.
116+
*/
117+
export const findResourceInstance = <T extends string>(
112118
state: TerraformState,
113119
type: T,
114120
name?: string,
@@ -131,12 +137,13 @@ export const findResourceInstance = <T extends "coder_script" | string>(
131137
return resource.instances[0].attributes as any;
132138
};
133139

134-
// testRequiredVariables creates a test-case
135-
// for each variable provided and ensures that
136-
// the apply fails without it.
137-
export const testRequiredVariables = (
140+
/**
141+
* Creates a test-case for each variable provided and ensures that the apply
142+
* fails without it.
143+
*/
144+
export const testRequiredVariables = <TVars extends Record<string, string>>(
138145
dir: string,
139-
vars: Record<string, string>,
146+
vars: TVars,
140147
) => {
141148
// Ensures that all required variables are provided.
142149
it("required variables", async () => {
@@ -165,16 +172,25 @@ export const testRequiredVariables = (
165172
});
166173
};
167174

168-
// runTerraformApply runs terraform apply in the given directory
169-
// with the given variables. It is fine to run in parallel with
170-
// other instances of this function, as it uses a random state file.
171-
export const runTerraformApply = async (
175+
/**
176+
* Runs terraform apply in the given directory with the given variables. It is
177+
* fine to run in parallel with other instances of this function, as it uses a
178+
* random state file.
179+
*/
180+
export const runTerraformApply = async <
181+
TVars extends Readonly<Record<string, string | boolean>>,
182+
>(
172183
dir: string,
173-
vars: Record<string, string>,
174-
env: Record<string, string> = {},
184+
vars: TVars,
185+
env?: Record<string, string>,
175186
): Promise<TerraformState> => {
176187
const stateFile = `${dir}/${crypto.randomUUID()}.tfstate`;
177-
Object.keys(vars).forEach((key) => (env[`TF_VAR_${key}`] = vars[key]));
188+
189+
const combinedEnv = env === undefined ? {} : { ...env };
190+
for (const [key, value] of Object.entries(vars)) {
191+
combinedEnv[`TF_VAR_${key}`] = String(value);
192+
}
193+
178194
const proc = spawn(
179195
[
180196
"terraform",
@@ -188,22 +204,26 @@ export const runTerraformApply = async (
188204
],
189205
{
190206
cwd: dir,
191-
env,
207+
env: combinedEnv,
192208
stderr: "pipe",
193209
stdout: "pipe",
194210
},
195211
);
212+
196213
const text = await readableStreamToText(proc.stderr);
197214
const exitCode = await proc.exited;
198215
if (exitCode !== 0) {
199216
throw new Error(text);
200217
}
218+
201219
const content = await readFile(stateFile, "utf8");
202220
await unlink(stateFile);
203221
return JSON.parse(content);
204222
};
205223

206-
// runTerraformInit runs terraform init in the given directory.
224+
/**
225+
* Runs terraform init in the given directory.
226+
*/
207227
export const runTerraformInit = async (dir: string) => {
208228
const proc = spawn(["terraform", "init"], {
209229
cwd: dir,
@@ -221,8 +241,8 @@ export const createJSONResponse = (obj: object, statusCode = 200): Response => {
221241
"Content-Type": "application/json",
222242
},
223243
status: statusCode,
224-
})
225-
}
244+
});
245+
};
226246

227247
export const writeCoder = async (id: string, script: string) => {
228248
const exec = await execContainer(id, [

vscode-desktop/main.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ describe("vscode-desktop", async () => {
4343
const state = await runTerraformApply(import.meta.dir, {
4444
agent_id: "foo",
4545
folder: "/foo/bar",
46-
open_recent: true,
46+
open_recent: "true",
4747
});
4848
expect(state.outputs.vscode_url.value).toBe(
4949
"vscode://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&openRecent&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
@@ -54,7 +54,7 @@ describe("vscode-desktop", async () => {
5454
const state = await runTerraformApply(import.meta.dir, {
5555
agent_id: "foo",
5656
folder: "/foo/bar",
57-
openRecent: false,
57+
openRecent: "false",
5858
});
5959
expect(state.outputs.vscode_url.value).toBe(
6060
"vscode://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
@@ -64,7 +64,7 @@ describe("vscode-desktop", async () => {
6464
it("adds open_recent", async () => {
6565
const state = await runTerraformApply(import.meta.dir, {
6666
agent_id: "foo",
67-
open_recent: true,
67+
open_recent: "true",
6868
});
6969
expect(state.outputs.vscode_url.value).toBe(
7070
"vscode://coder.coder-remote/open?owner=default&workspace=default&openRecent&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",

windows-rdp/README.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
---
2+
display_name: Windows RDP
3+
description: RDP Server and Web Client, powered by Devolutions Gateway
4+
icon: ../.icons/desktop.svg
5+
maintainer_github: coder
6+
verified: true
7+
tags: [windows, rdp, web, desktop]
8+
---
9+
10+
# Windows RDP
11+
12+
Enable Remote Desktop + a web based client on Windows workspaces, powered by [devolutions-gateway](https://github.com/Devolutions/devolutions-gateway).
13+
14+
```tf
15+
# AWS example. See below for examples of using this module with other providers
16+
module "windows_rdp" {
17+
source = "registry.coder.com/coder/module/windows-rdp"
18+
version = "1.0.16"
19+
count = data.coder_workspace.me.start_count
20+
agent_id = resource.coder_agent.main.id
21+
resource_id = resource.aws_instance.dev.id
22+
}
23+
```
24+
25+
## Video
26+
27+
https://github.com/coder/modules/assets/28937484/fb5f4a55-7b69-4550-ab62-301e13a4be02
28+
29+
## Examples
30+
31+
### With AWS
32+
33+
```tf
34+
module "windows_rdp" {
35+
source = "registry.coder.com/coder/module/windows-rdp"
36+
version = "1.0.16"
37+
count = data.coder_workspace.me.start_count
38+
agent_id = resource.coder_agent.main.id
39+
resource_id = resource.aws_instance.dev.id
40+
}
41+
```
42+
43+
### With Google Cloud
44+
45+
```tf
46+
module "windows_rdp" {
47+
source = "registry.coder.com/coder/module/windows-rdp"
48+
version = "1.0.16"
49+
count = data.coder_workspace.me.start_count
50+
agent_id = resource.coder_agent.main.id
51+
resource_id = resource.google_compute_instance.dev[0].id
52+
}
53+
```
54+
55+
## Roadmap
56+
57+
- [ ] Test on Microsoft Azure.

0 commit comments

Comments
 (0)