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

feat: add module for Web RDP #262

Merged
merged 74 commits into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
c5c521f
feat: add web RDP module
bpmct Apr 6, 2024
1197e6b
fix port typo
bpmct Apr 6, 2024
12fd16f
add metadata and local instructions
bpmct Apr 6, 2024
bf06e8d
fix agent id
bpmct Apr 6, 2024
0e7644b
remove count
bpmct Apr 6, 2024
9f8eee5
rename script
bpmct Apr 6, 2024
ec922c7
remove metadata for now
bpmct Apr 6, 2024
748a180
add temp link to example template
bpmct Apr 6, 2024
ac648cc
add thumbnail
bpmct Apr 6, 2024
8913567
fix module usage
bpmct Apr 6, 2024
7de78d2
add tags
bpmct Apr 6, 2024
53083a5
add more context on auto login
bpmct Apr 6, 2024
b93471a
chore: add admin username
bpmct Apr 24, 2024
20795aa
chore: add script file for overriding Devolutions
Parkreiner Jun 24, 2024
ff96b3f
wip: commit current progress for devolutions patch
Parkreiner Jun 24, 2024
aab5e55
fix: update script frequency
Parkreiner Jun 24, 2024
29209d5
fix: update typo in powershell script
Parkreiner Jun 24, 2024
452f41a
fix: add parenthesis
Parkreiner Jun 24, 2024
c7aa825
fix: dolla dolla
Parkreiner Jun 24, 2024
047ccd6
fix: dolla dolla
Parkreiner Jun 24, 2024
d530d68
fix: more money, more problems
Parkreiner Jun 24, 2024
0b6975c
fix: escape quotes
Parkreiner Jun 24, 2024
14e3fc5
fix: whitespace
Parkreiner Jun 24, 2024
fba0f84
fix: remove regex search from Select-String
Parkreiner Jun 24, 2024
d5cfadb
fix: remove template literal dollar signs
Parkreiner Jun 25, 2024
8195cf4
wip: add current code for hiding Devolutions form
Parkreiner Jun 25, 2024
652fc6b
refactor: clean up form code
Parkreiner Jun 25, 2024
7022711
fix: update HTML query selector
Parkreiner Jun 25, 2024
5ec1b20
docs: remove now-inaccurate comment
Parkreiner Jun 25, 2024
c7a4fce
fix: update instanceof check
Parkreiner Jun 25, 2024
1a0a865
wip: update logic for hiding form to avoid whiffs
Parkreiner Jun 25, 2024
ef4c87e
fix: simplify code for hiding form
Parkreiner Jun 25, 2024
a9a75b6
fix: add more changes to opacity logic
Parkreiner Jun 25, 2024
f3c30ab
fix: make form hiding logic run on webpage opening
Parkreiner Jun 25, 2024
8aff87f
fix: add logic for hiding the dropdown of protocol options
Parkreiner Jun 25, 2024
b09c4cb
fix: speed up code for filling in form
Parkreiner Jun 25, 2024
5f418c3
docs: add comments about necessary double dollar signs
Parkreiner Jun 25, 2024
b283ac3
docs: fix misleading typo in comment
Parkreiner Jun 25, 2024
aebf095
refactor: clean up patch logic for clarity
Parkreiner Jun 26, 2024
f335cd3
fix: update type definitions for helpers
Parkreiner Jun 26, 2024
33d44fd
fix: remove unneeded any types
Parkreiner Jun 26, 2024
b280764
wip: commit progress on main test file
Parkreiner Jun 26, 2024
83ecba2
wip: commit current progress
Parkreiner Jun 26, 2024
264584e
fix: make comments for test helpers exportable
Parkreiner Jun 26, 2024
de00f63
chore: add type parameter for testRequiredVariables
Parkreiner Jun 26, 2024
7d366ff
chore: add first finished test
Parkreiner Jun 27, 2024
6409ee2
refactor: clean up current code
Parkreiner Jun 27, 2024
25c9000
docs: add comment about how regex is set up
Parkreiner Jun 27, 2024
5869eb8
chore: finish all initial tests
Parkreiner Jun 27, 2024
90e15cd
fix: update string formatting logic to make tests less likely to flak…
Parkreiner Jun 27, 2024
05a20a9
docs: rewrite comment for clarity
Parkreiner Jun 27, 2024
f82c7fd
test: set up NuGet in advance
Parkreiner Jun 28, 2024
f369697
wip: add try/catch block
Parkreiner Jun 28, 2024
4ab7257
fix: remove accidental uncaught code
Parkreiner Jun 28, 2024
8262b29
wip: try reformatting try/catch
Parkreiner Jun 28, 2024
16f96d3
wip: add code for triggering try/catch
Parkreiner Jun 28, 2024
78c9480
wip: try reverting temporarily
Parkreiner Jun 28, 2024
78f91a5
wip: revert back
Parkreiner Jun 28, 2024
ec2c8ed
fix: update null check and remove typo
Parkreiner Jun 28, 2024
7a8483d
Merge branch 'main' into web-rdp
Parkreiner Jul 1, 2024
d9d1be0
fix: update README for RDP
Parkreiner Jul 1, 2024
a381c3e
fix: update structure of README for linter
Parkreiner Jul 1, 2024
c59eb0c
chore: add new video to README
Parkreiner Jul 1, 2024
fd2f91c
fix: remove commented-out code
Parkreiner Jul 1, 2024
13a8877
Merge branch 'web-rdp' of github.com:coder/modules into web-rdp
Parkreiner Jul 1, 2024
b4153a6
refactor: split off Windows script logic into separate file
Parkreiner Jul 1, 2024
49f0605
fix: update TF import
Parkreiner Jul 1, 2024
a8580fe
fix: update object definition for top-level templatefile
Parkreiner Jul 1, 2024
b23d853
refactor: try extracting main script into separate template file
Parkreiner Jul 1, 2024
3f8f618
refactor: clean up final code
Parkreiner Jul 1, 2024
894e507
fix: add verison number to rdp script
Parkreiner Jul 2, 2024
d98bfcb
fix: add versioning to all code snippets
Parkreiner Jul 2, 2024
aebdc9b
fix: update docs link
Parkreiner Jul 2, 2024
e8ee02c
fix: update URL for RDP icon
Parkreiner Jul 2, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .icons/desktop.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 17 additions & 16 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

94 changes: 57 additions & 37 deletions test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ export const runContainer = async (
return containerID.trim();
};

// executeScriptInContainer finds the only "coder_script"
// resource in the given state and runs it in a container.
/**
* Finds the only "coder_script" resource in the given state and runs it in a
* container.
*/
Comment on lines +32 to +35
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the comment formatting so that these would be public, and would show up when someone hovers over the function definition

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL: /** are public comments and // are private 🤦

export const executeScriptInContainer = async (
state: TerraformState,
image: string,
Expand Down Expand Up @@ -76,27 +78,30 @@ export const execContainer = async (
};
};

type JsonValue =
| string
| number
| boolean
| null
| JsonValue[]
| { [key: string]: JsonValue };

type TerraformStateResource = {
type: string;
name: string;
provider: string;
instances: [{ attributes: Record<string, any> }];
};

export interface TerraformState {
outputs: {
[key: string]: {
type: string;
value: any;
};
}
resources: [
{
type: string;
name: string;
provider: string;
instances: [
{
attributes: {
[key: string]: any;
};
},
];
},
];
};

resources: [TerraformStateResource, ...TerraformStateResource[]];
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redefined this so that resources is an array with at least element, rather than an array with exactly one element. This typo was causing some of the other tests to do stuff with the any type; I'll be making a separate PR to clean up more stuff

}

export interface CoderScriptAttributes {
Expand All @@ -105,10 +110,11 @@ export interface CoderScriptAttributes {
url: string;
}

// findResourceInstance finds the first instance of the given resource
// type in the given state. If name is specified, it will only find
// the instance with the given name.
export const findResourceInstance = <T extends "coder_script" | string>(
/**
* finds the first instance of the given resource type in the given state. If
* name is specified, it will only find the instance with the given name.
*/
export const findResourceInstance = <T extends string>(
state: TerraformState,
type: T,
name?: string,
Expand All @@ -131,12 +137,13 @@ export const findResourceInstance = <T extends "coder_script" | string>(
return resource.instances[0].attributes as any;
};

// testRequiredVariables creates a test-case
// for each variable provided and ensures that
// the apply fails without it.
export const testRequiredVariables = (
/**
* Creates a test-case for each variable provided and ensures that the apply
* fails without it.
*/
export const testRequiredVariables = <TVars extends Record<string, string>>(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a type parameter so that you can define what properties you expect to be valid for the function, so that you can more easily catch accidental typos

dir: string,
vars: Record<string, string>,
vars: TVars,
) => {
// Ensures that all required variables are provided.
it("required variables", async () => {
Expand Down Expand Up @@ -165,16 +172,25 @@ export const testRequiredVariables = (
});
};

// runTerraformApply runs terraform apply in the given directory
// with the given variables. It is fine to run in parallel with
// other instances of this function, as it uses a random state file.
export const runTerraformApply = async (
/**
* Runs terraform apply in the given directory with the given variables. It is
* fine to run in parallel with other instances of this function, as it uses a
* random state file.
*/
export const runTerraformApply = async <
TVars extends Readonly<Record<string, string | boolean>>,
>(
dir: string,
vars: Record<string, string>,
env: Record<string, string> = {},
vars: TVars,
env?: Record<string, string>,
): Promise<TerraformState> => {
const stateFile = `${dir}/${crypto.randomUUID()}.tfstate`;
Object.keys(vars).forEach((key) => (env[`TF_VAR_${key}`] = vars[key]));

const combinedEnv = env === undefined ? {} : { ...env };
for (const [key, value] of Object.entries(vars)) {
combinedEnv[`TF_VAR_${key}`] = String(value);
}

const proc = spawn(
[
"terraform",
Expand All @@ -188,22 +204,26 @@ export const runTerraformApply = async (
],
{
cwd: dir,
env,
env: combinedEnv,
stderr: "pipe",
stdout: "pipe",
},
);

const text = await readableStreamToText(proc.stderr);
const exitCode = await proc.exited;
if (exitCode !== 0) {
throw new Error(text);
}

const content = await readFile(stateFile, "utf8");
await unlink(stateFile);
return JSON.parse(content);
};

// runTerraformInit runs terraform init in the given directory.
/**
* Runs terraform init in the given directory.
*/
export const runTerraformInit = async (dir: string) => {
const proc = spawn(["terraform", "init"], {
cwd: dir,
Expand All @@ -221,8 +241,8 @@ export const createJSONResponse = (obj: object, statusCode = 200): Response => {
"Content-Type": "application/json",
},
status: statusCode,
})
}
});
};

export const writeCoder = async (id: string, script: string) => {
const exec = await execContainer(id, [
Expand Down
6 changes: 3 additions & 3 deletions vscode-desktop/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ describe("vscode-desktop", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
folder: "/foo/bar",
open_recent: true,
open_recent: "true",
});
expect(state.outputs.vscode_url.value).toBe(
"vscode://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&openRecent&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
Expand All @@ -54,7 +54,7 @@ describe("vscode-desktop", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
folder: "/foo/bar",
openRecent: false,
openRecent: "false",
});
expect(state.outputs.vscode_url.value).toBe(
"vscode://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
Expand All @@ -64,7 +64,7 @@ describe("vscode-desktop", async () => {
it("adds open_recent", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
open_recent: true,
open_recent: "true",
});
expect(state.outputs.vscode_url.value).toBe(
"vscode://coder.coder-remote/open?owner=default&workspace=default&openRecent&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
Expand Down
57 changes: 57 additions & 0 deletions windows-rdp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
display_name: Windows RDP
description: RDP Server and Web Client, powered by Devolutions Gateway
icon: ../.icons/desktop.svg
maintainer_github: coder
verified: true
tags: [windows, rdp, web, desktop]
---

# Windows RDP

Enable Remote Desktop + a web based client on Windows workspaces, powered by [devolutions-gateway](https://github.com/Devolutions/devolutions-gateway).

```tf
# AWS example. See below for examples of using this module with other providers
module "windows_rdp" {
source = "registry.coder.com/coder/module/windows-rdp"
version = "1.0.16"
count = data.coder_workspace.me.start_count
agent_id = resource.coder_agent.main.id
resource_id = resource.aws_instance.dev.id
}
```

## Video

https://github.com/coder/modules/assets/28937484/fb5f4a55-7b69-4550-ab62-301e13a4be02

## Examples

### With AWS

```tf
module "windows_rdp" {
source = "registry.coder.com/coder/module/windows-rdp"
version = "1.0.16"
count = data.coder_workspace.me.start_count
agent_id = resource.coder_agent.main.id
resource_id = resource.aws_instance.dev.id
}
```

### With Google Cloud

```tf
module "windows_rdp" {
source = "registry.coder.com/coder/module/windows-rdp"
version = "1.0.16"
count = data.coder_workspace.me.start_count
agent_id = resource.coder_agent.main.id
resource_id = resource.google_compute_instance.dev[0].id
}
```

## Roadmap

- [ ] Test on Microsoft Azure.
Loading