Compare commits

..

2 Commits

Author SHA1 Message Date
Parkreiner
6edc903d03 Merge branch 'web-rdp' into mes/rdp-glitch-repro 2024-07-01 20:43:16 +00:00
Parkreiner
83ffef3a7b wip: try breaking coder UI on purpose 2024-07-01 14:41:28 +00:00
30 changed files with 335 additions and 475 deletions

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 1.5 MiB

View File

@@ -1,47 +1,24 @@
# Contributing # Contributing
## Getting started To create a new module, clone this repository and run:
This repo uses the [Bun runtime](https://bun.sh/) to to run all code and tests. To install Bun, you can run this command on Linux/MacOS:
```shell ```shell
curl -fsSL https://bun.sh/install | bash ./new.sh MODULE_NAME
```
Or this command on Windows:
```shell
powershell -c "irm bun.sh/install.ps1 | iex"
```
Follow the instructions to ensure that Bun is available globally. Once Bun has been installed, clone this repository. From there, run this script to create a new module:
```shell
./new.sh NAME_OF_NEW_MODULE
``` ```
## Testing a Module ## Testing a Module
> **Note:** It is the responsibility of the module author to implement tests for their module. The author must test the module locally before submitting a PR.
A suite of test-helpers exists to run `terraform apply` on modules with variables, and test script output against containers. A suite of test-helpers exists to run `terraform apply` on modules with variables, and test script output against containers.
The testing suite must be able to run docker containers with the `--network=host` flag. This typically requires running the tests on Linux as this flag does not apply to Docker Desktop for MacOS and Windows. MacOS users can work around this by using something like [colima](https://github.com/abiosoft/colima) or [Orbstack](https://orbstack.dev/) instead of Docker Desktop. The testing suite must be able to run docker containers with the `--network=host` flag, which typically requires running the tests on Linux as this flag does not apply to Docker Desktop for MacOS and Windows. MacOS users can work around this by using something like [colima](https://github.com/abiosoft/colima) or [Orbstack](https://orbstack.dev/) instead of Docker Desktop.
Reference the existing `*.test.ts` files to get an idea for how to set up tests. Reference existing `*.test.ts` files for implementation.
You can run all tests in a specific file with this command:
```shell ```shell
# Run tests for a specific module!
$ bun test -t '<module>' $ bun test -t '<module>'
``` ```
Or run all tests by running this command:
```shell
$ bun test
```
You can test a module locally by updating the source as follows You can test a module locally by updating the source as follows
```tf ```tf
@@ -50,25 +27,4 @@ module "example" {
} }
``` ```
## Releases > **Note:** This is the responsibility of the module author to implement tests for their module. and test the module locally before submitting a PR.
> [!WARNING]
> When creating a new release, make sure that your new version number is fully accurate. If a version number is incorrect or does not exist, we may end up serving incorrect/old data for our various tools and providers.
Much of our release process is automated. To cut a new release:
1. Navigate to [GitHub's Releases page](https://github.com/coder/modules/releases)
2. Click "Draft a new release"
3. Click the "Choose a tag" button and type a new release number in the format `v<major>.<minor>.<patch>` (e.g., `v1.18.0`). Then click "Create new tag".
4. Click the "Generate release notes" button, and clean up the resulting README. Be sure to remove any notes that would not be relevant to end-users (e.g., bumping dependencies).
5. Once everything looks good, click the "Publish release" button.
Once the release has been cut, a script will run to check whether there are any modules that will require that the new release number be published to Terraform. If there are any, a new pull request will automatically be generated. Be sure to approve this PR and merge it into the `main` branch.
Following that, our automated processes will handle publishing new data for [`registry.coder.com`](https://github.com/coder/registry.coder.com/):
1. Publishing new versions to Coder's [Terraform Registry](https://registry.terraform.io/providers/coder/coder/latest)
2. Publishing new data to the [Coder Registry](https://registry.coder.com)
> [!NOTE]
> Some data in `registry.coder.com` is fetched on demand from the Module repo's main branch. This data should be updated almost immediately after a new release, but other changes will take some time to propagate.

View File

@@ -3,14 +3,14 @@
Modules Modules
</h1> </h1>
[Module Registry](https://registry.coder.com) | [Coder Docs](https://coder.com/docs) | [Why Coder](https://coder.com/why) | [Coder Enterprise](https://coder.com/docs/v2/latest/enterprise) [Registry](https://registry.coder.com) | [Coder Docs](https://coder.com/docs) | [Why Coder](https://coder.com/why) | [Coder Enterprise](https://coder.com/docs/v2/latest/enterprise)
[![discord](https://img.shields.io/discord/747933592273027093?label=discord)](https://discord.gg/coder) [![discord](https://img.shields.io/discord/747933592273027093?label=discord)](https://discord.gg/coder)
[![license](https://img.shields.io/github/license/coder/modules)](./LICENSE) [![license](https://img.shields.io/github/license/coder/modules)](./LICENSE)
</div> </div>
Modules extend Coder Templates to create reusable components for your development environment. Modules extend Templates to create reusable components for your development environment.
e.g. e.g.

BIN
bun.lockb

Binary file not shown.

View File

@@ -14,7 +14,7 @@ Automatically install [code-server](https://github.com/coder/code-server) in a w
```tf ```tf
module "code-server" { module "code-server" {
source = "registry.coder.com/modules/code-server/coder" source = "registry.coder.com/modules/code-server/coder"
version = "1.0.17" version = "1.0.15"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
} }
``` ```
@@ -28,7 +28,7 @@ module "code-server" {
```tf ```tf
module "code-server" { module "code-server" {
source = "registry.coder.com/modules/code-server/coder" source = "registry.coder.com/modules/code-server/coder"
version = "1.0.17" version = "1.0.15"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
install_version = "4.8.3" install_version = "4.8.3"
} }
@@ -41,7 +41,7 @@ Install the Dracula theme from [OpenVSX](https://open-vsx.org/):
```tf ```tf
module "code-server" { module "code-server" {
source = "registry.coder.com/modules/code-server/coder" source = "registry.coder.com/modules/code-server/coder"
version = "1.0.17" version = "1.0.15"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
extensions = [ extensions = [
"dracula-theme.theme-dracula" "dracula-theme.theme-dracula"
@@ -58,7 +58,7 @@ Configure VS Code's [settings.json](https://code.visualstudio.com/docs/getstarte
```tf ```tf
module "code-server" { module "code-server" {
source = "registry.coder.com/modules/code-server/coder" source = "registry.coder.com/modules/code-server/coder"
version = "1.0.17" version = "1.0.15"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
extensions = ["dracula-theme.theme-dracula"] extensions = ["dracula-theme.theme-dracula"]
settings = { settings = {
@@ -74,7 +74,7 @@ Just run code-server in the background, don't fetch it from GitHub:
```tf ```tf
module "code-server" { module "code-server" {
source = "registry.coder.com/modules/code-server/coder" source = "registry.coder.com/modules/code-server/coder"
version = "1.0.17" version = "1.0.15"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
extensions = ["dracula-theme.theme-dracula", "ms-azuretools.vscode-docker"] extensions = ["dracula-theme.theme-dracula", "ms-azuretools.vscode-docker"]
} }
@@ -89,7 +89,7 @@ Run an existing copy of code-server if found, otherwise download from GitHub:
```tf ```tf
module "code-server" { module "code-server" {
source = "registry.coder.com/modules/code-server/coder" source = "registry.coder.com/modules/code-server/coder"
version = "1.0.17" version = "1.0.15"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
use_cached = true use_cached = true
extensions = ["dracula-theme.theme-dracula", "ms-azuretools.vscode-docker"] extensions = ["dracula-theme.theme-dracula", "ms-azuretools.vscode-docker"]
@@ -101,7 +101,7 @@ Just run code-server in the background, don't fetch it from GitHub:
```tf ```tf
module "code-server" { module "code-server" {
source = "registry.coder.com/modules/code-server/coder" source = "registry.coder.com/modules/code-server/coder"
version = "1.0.17" version = "1.0.15"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
offline = true offline = true
} }

View File

@@ -113,15 +113,6 @@ variable "auto_install_extensions" {
default = false default = false
} }
variable "subdomain" {
type = bool
description = <<-EOT
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.
EOT
default = false
}
resource "coder_script" "code-server" { resource "coder_script" "code-server" {
agent_id = var.agent_id agent_id = var.agent_id
display_name = "code-server" display_name = "code-server"
@@ -163,7 +154,7 @@ resource "coder_app" "code-server" {
display_name = var.display_name display_name = var.display_name
url = "http://localhost:${var.port}/${var.folder != "" ? "?folder=${urlencode(var.folder)}" : ""}" url = "http://localhost:${var.port}/${var.folder != "" ? "?folder=${urlencode(var.folder)}" : ""}"
icon = "/icon/code.svg" icon = "/icon/code.svg"
subdomain = var.subdomain subdomain = false
share = var.share share = var.share
order = var.order order = var.order

View File

@@ -10,7 +10,6 @@ CODE_SERVER="${INSTALL_PREFIX}/bin/code-server"
EXTENSION_ARG="" EXTENSION_ARG=""
if [ -n "${EXTENSIONS_DIR}" ]; then if [ -n "${EXTENSIONS_DIR}" ]; then
EXTENSION_ARG="--extensions-dir=${EXTENSIONS_DIR}" EXTENSION_ARG="--extensions-dir=${EXTENSIONS_DIR}"
mkdir -p "${EXTENSIONS_DIR}"
fi fi
function run_code_server() { function run_code_server() {

View File

@@ -1,35 +0,0 @@
---
display_name: Cursor IDE
description: Add a one-click button to launch Cursor IDE
icon: ../.icons/cursor.svg
maintainer_github: coder
verified: true
tags: [ide, cursor, helper]
---
# Cursor IDE
Add a button to open any workspace with a single click in Cursor IDE.
Uses the [Coder Remote VS Code Extension](https://github.com/coder/cursor-coder).
```tf
module "cursor" {
source = "registry.coder.com/modules/cursor/coder"
version = "1.0.18"
agent_id = coder_agent.example.id
}
```
## Examples
### Open in a specific directory
```tf
module "cursor" {
source = "registry.coder.com/modules/cursor/coder"
version = "1.0.18"
agent_id = coder_agent.example.id
folder = "/home/coder/project"
}
```

View File

@@ -1,89 +0,0 @@
import { describe, expect, it } from "bun:test";
import {
executeScriptInContainer,
runTerraformApply,
runTerraformInit,
testRequiredVariables,
} from "../test";
describe("cursor", async () => {
await runTerraformInit(import.meta.dir);
testRequiredVariables(import.meta.dir, {
agent_id: "foo",
});
it("default output", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
});
expect(state.outputs.cursor_url.value).toBe(
"cursor://coder.coder-remote/open?owner=default&workspace=default&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
);
const coder_app = state.resources.find(
(res) => res.type === "coder_app" && res.name === "cursor",
);
expect(coder_app).not.toBeNull();
expect(coder_app?.instances.length).toBe(1);
expect(coder_app?.instances[0].attributes.order).toBeNull();
});
it("adds folder", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
folder: "/foo/bar",
});
expect(state.outputs.cursor_url.value).toBe(
"cursor://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
);
});
it("adds folder and open_recent", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
folder: "/foo/bar",
open_recent: "true",
});
expect(state.outputs.cursor_url.value).toBe(
"cursor://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&openRecent&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
);
});
it("adds folder but not open_recent", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
folder: "/foo/bar",
openRecent: "false",
});
expect(state.outputs.cursor_url.value).toBe(
"cursor://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
);
});
it("adds open_recent", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
open_recent: "true",
});
expect(state.outputs.cursor_url.value).toBe(
"cursor://coder.coder-remote/open?owner=default&workspace=default&openRecent&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
);
});
it("expect order to be set", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
order: "22",
});
const coder_app = state.resources.find(
(res) => res.type === "coder_app" && res.name === "cursor",
);
expect(coder_app).not.toBeNull();
expect(coder_app?.instances.length).toBe(1);
expect(coder_app?.instances[0].attributes.order).toBe(22);
});
});

View File

@@ -1,62 +0,0 @@
terraform {
required_version = ">= 1.0"
required_providers {
coder = {
source = "coder/coder"
version = ">= 0.23"
}
}
}
variable "agent_id" {
type = string
description = "The ID of a Coder agent."
}
variable "folder" {
type = string
description = "The folder to open in Cursor IDE."
default = ""
}
variable "open_recent" {
type = bool
description = "Open the most recent workspace or folder. Falls back to the folder if there is no recent workspace or folder to open."
default = false
}
variable "order" {
type = number
description = "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)."
default = null
}
data "coder_workspace" "me" {}
data "coder_workspace_owner" "me" {}
resource "coder_app" "cursor" {
agent_id = var.agent_id
external = true
icon = "/icon/cursor.svg"
slug = "cursor"
display_name = "Cursor IDE"
order = var.order
url = join("", [
"cursor://coder.coder-remote/open",
"?owner=",
data.coder_workspace_owner.me.name,
"&workspace=",
data.coder_workspace.me.name,
var.folder != "" ? join("", ["&folder=", var.folder]) : "",
var.open_recent ? "&openRecent" : "",
"&url=",
data.coder_workspace.me.access_url,
"&token=$SESSION_TOKEN",
])
}
output "cursor_url" {
value = coder_app.cursor.url
description = "Cursor IDE Desktop URL."
}

View File

@@ -39,14 +39,9 @@ variable "coder_parameter_order" {
default = null default = null
} }
variable "manual_update" {
type = bool
description = "If true, this adds a button to workspace page to refresh dotfiles on demand."
default = false
}
data "coder_parameter" "dotfiles_uri" { data "coder_parameter" "dotfiles_uri" {
count = var.dotfiles_uri == null ? 1 : 0 count = var.dotfiles_uri == null ? 1 : 0
type = "string" type = "string"
name = "dotfiles_uri" name = "dotfiles_uri"
display_name = "Dotfiles URL" display_name = "Dotfiles URL"
@@ -73,18 +68,6 @@ resource "coder_script" "dotfiles" {
run_on_start = true run_on_start = true
} }
resource "coder_app" "dotfiles" {
count = var.manual_update ? 1 : 0
agent_id = var.agent_id
display_name = "Refresh Dotfiles"
slug = "dotfiles"
icon = "/icon/dotfiles.svg"
command = templatefile("${path.module}/run.sh", {
DOTFILES_URI : local.dotfiles_uri,
DOTFILES_USER : local.user
})
}
output "dotfiles_uri" { output "dotfiles_uri" {
description = "Dotfiles URI" description = "Dotfiles URI"
value = local.dotfiles_uri value = local.dotfiles_uri

View File

@@ -13,10 +13,9 @@ A file browser for your workspace.
```tf ```tf
module "filebrowser" { module "filebrowser" {
source = "registry.coder.com/modules/filebrowser/coder" source = "registry.coder.com/modules/filebrowser/coder"
version = "1.0.8" version = "1.0.8"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
agent_name = "main"
} }
``` ```
@@ -28,11 +27,10 @@ module "filebrowser" {
```tf ```tf
module "filebrowser" { module "filebrowser" {
source = "registry.coder.com/modules/filebrowser/coder" source = "registry.coder.com/modules/filebrowser/coder"
version = "1.0.8" version = "1.0.8"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
agent_name = "main" folder = "/home/coder/project"
folder = "/home/coder/project"
} }
``` ```
@@ -43,18 +41,6 @@ module "filebrowser" {
source = "registry.coder.com/modules/filebrowser/coder" source = "registry.coder.com/modules/filebrowser/coder"
version = "1.0.8" version = "1.0.8"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
agent_name = "main"
database_path = ".config/filebrowser.db" database_path = ".config/filebrowser.db"
} }
``` ```
### Serve from the same domain (no subdomain)
```tf
module "filebrowser" {
source = "registry.coder.com/modules/filebrowser/coder"
agent_id = coder_agent.example.id
agent_name = "main"
subdomain = false
}
```

View File

@@ -11,13 +11,11 @@ describe("filebrowser", async () => {
testRequiredVariables(import.meta.dir, { testRequiredVariables(import.meta.dir, {
agent_id: "foo", agent_id: "foo",
agent_name: "main",
}); });
it("fails with wrong database_path", async () => { it("fails with wrong database_path", async () => {
const state = await runTerraformApply(import.meta.dir, { const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo", agent_id: "foo",
agent_name: "main",
database_path: "nofb", database_path: "nofb",
}).catch((e) => { }).catch((e) => {
if (!e.message.startsWith("\nError: Invalid value for variable")) { if (!e.message.startsWith("\nError: Invalid value for variable")) {
@@ -29,7 +27,6 @@ describe("filebrowser", async () => {
it("runs with default", async () => { it("runs with default", async () => {
const state = await runTerraformApply(import.meta.dir, { const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo", agent_id: "foo",
agent_name: "main",
}); });
const output = await executeScriptInContainer(state, "alpine"); const output = await executeScriptInContainer(state, "alpine");
expect(output.exitCode).toBe(0); expect(output.exitCode).toBe(0);
@@ -51,7 +48,6 @@ describe("filebrowser", async () => {
it("runs with database_path var", async () => { it("runs with database_path var", async () => {
const state = await runTerraformApply(import.meta.dir, { const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo", agent_id: "foo",
agent_name: "main",
database_path: ".config/filebrowser.db", database_path: ".config/filebrowser.db",
}); });
const output = await executeScriptInContainer(state, "alpine"); const output = await executeScriptInContainer(state, "alpine");
@@ -74,7 +70,6 @@ describe("filebrowser", async () => {
it("runs with folder var", async () => { it("runs with folder var", async () => {
const state = await runTerraformApply(import.meta.dir, { const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo", agent_id: "foo",
agent_name: "main",
folder: "/home/coder/project", folder: "/home/coder/project",
}); });
const output = await executeScriptInContainer(state, "alpine"); const output = await executeScriptInContainer(state, "alpine");
@@ -93,27 +88,4 @@ describe("filebrowser", async () => {
"📝 Logs at /tmp/filebrowser.log", "📝 Logs at /tmp/filebrowser.log",
]); ]);
}); });
it("runs with subdomain=false", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
agent_name: "main",
subdomain: false,
});
const output = await executeScriptInContainer(state, "alpine");
expect(output.exitCode).toBe(0);
expect(output.stdout).toEqual([
"\u001B[0;1mInstalling filebrowser ",
"",
"🥳 Installation complete! ",
"",
"👷 Starting filebrowser in background... ",
"",
"📂 Serving /root at http://localhost:13339 ",
"",
"Running 'filebrowser --noauth --root /root --port 13339' ",
"",
"📝 Logs at /tmp/filebrowser.log",
]);
});
}); });

View File

@@ -14,15 +14,6 @@ variable "agent_id" {
description = "The ID of a Coder agent." description = "The ID of a Coder agent."
} }
data "coder_workspace" "me" {}
data "coder_workspace_owner" "me" {}
variable "agent_name" {
type = string
description = "The name of the main deployment. (Used to build the subpath for coder_app.)"
}
variable "database_path" { variable "database_path" {
type = string type = string
description = "The path to the filebrowser database." description = "The path to the filebrowser database."
@@ -67,15 +58,6 @@ variable "order" {
default = null default = null
} }
variable "subdomain" {
type = bool
description = <<-EOT
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.
EOT
default = true
}
resource "coder_script" "filebrowser" { resource "coder_script" "filebrowser" {
agent_id = var.agent_id agent_id = var.agent_id
display_name = "File Browser" display_name = "File Browser"
@@ -85,9 +67,7 @@ resource "coder_script" "filebrowser" {
PORT : var.port, PORT : var.port,
FOLDER : var.folder, FOLDER : var.folder,
LOG_PATH : var.log_path, LOG_PATH : var.log_path,
DB_PATH : var.database_path, DB_PATH : var.database_path
SUBDOMAIN : var.subdomain,
SERVER_BASE_PATH : var.subdomain ? "" : format("/@%s/%s.%s/apps/filebrowser", data.coder_workspace_owner.me.name, data.coder_workspace.me.name, var.agent_name),
}) })
run_on_start = true run_on_start = true
} }
@@ -98,7 +78,7 @@ resource "coder_app" "filebrowser" {
display_name = "File Browser" display_name = "File Browser"
url = "http://localhost:${var.port}" url = "http://localhost:${var.port}"
icon = "https://raw.githubusercontent.com/filebrowser/logo/master/icon_raw.svg" icon = "https://raw.githubusercontent.com/filebrowser/logo/master/icon_raw.svg"
subdomain = var.subdomain subdomain = true
share = var.share share = var.share
order = var.order order = var.order
} }

View File

@@ -17,9 +17,6 @@ if [ "${DB_PATH}" != "filebrowser.db" ]; then
DB_FLAG=" -d ${DB_PATH}" DB_FLAG=" -d ${DB_PATH}"
fi fi
# set baseurl to be able to run if sudomain=false; if subdomain=true the SERVER_BASE_PATH value will be ""
filebrowser config set --baseurl "${SERVER_BASE_PATH}" > ${LOG_PATH} 2>&1
printf "📂 Serving $${ROOT_DIR} at http://localhost:${PORT} \n\n" printf "📂 Serving $${ROOT_DIR} at http://localhost:${PORT} \n\n"
printf "Running 'filebrowser --noauth --root $ROOT_DIR --port ${PORT}$${DB_FLAG}' \n\n" printf "Running 'filebrowser --noauth --root $ROOT_DIR --port ${PORT}$${DB_FLAG}' \n\n"

View File

@@ -153,20 +153,3 @@ module "git-clone" {
branch_name = "feat/example" branch_name = "feat/example"
} }
``` ```
## Git clone with different destination folder
By default, the repository will be cloned into a folder matching the repository name. You can use the `folder_name` attribute to change the name of the destination folder to something else.
For example, this will clone into the `~/projects/coder/coder-dev` folder:
```tf
module "git-clone" {
source = "registry.coder.com/modules/git-clone/coder"
version = "1.0.12"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
folder_name = "coder-dev"
base_dir = "~/projects/coder"
}
```

View File

@@ -79,22 +79,6 @@ describe("git-clone", async () => {
expect(state.outputs.branch_name.value).toEqual(""); expect(state.outputs.branch_name.value).toEqual("");
}); });
it("repo_dir should match base_dir/folder_name", async () => {
const url = "git@github.com:coder/coder.git";
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
base_dir: "/tmp",
folder_name: "foo",
url,
});
expect(state.outputs.repo_dir.value).toEqual("/tmp/foo");
expect(state.outputs.folder_name.value).toEqual("foo");
expect(state.outputs.clone_url.value).toEqual(url);
const https_url = "https://github.com/coder/coder.git";
expect(state.outputs.web_url.value).toEqual(https_url);
expect(state.outputs.branch_name.value).toEqual("");
});
it("branch_name should not include query string", async () => { it("branch_name should not include query string", async () => {
const state = await runTerraformApply(import.meta.dir, { const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo", agent_id: "foo",

View File

@@ -50,12 +50,6 @@ variable "branch_name" {
default = "" default = ""
} }
variable "folder_name" {
description = "The destination folder to clone the repository into."
type = string
default = ""
}
locals { locals {
# Remove query parameters and fragments from the URL # Remove query parameters and fragments from the URL
url = replace(replace(var.url, "/\\?.*/", ""), "/#.*/", "") url = replace(replace(var.url, "/\\?.*/", ""), "/#.*/", "")
@@ -70,7 +64,7 @@ locals {
# Extract the branch name from the URL # Extract the branch name from the URL
branch_name = var.branch_name == "" && local.tree_path != "" ? replace(replace(local.url, local.clone_url, ""), "/.*${local.tree_path}/", "") : var.branch_name branch_name = var.branch_name == "" && local.tree_path != "" ? replace(replace(local.url, local.clone_url, ""), "/.*${local.tree_path}/", "") : var.branch_name
# Extract the folder name from the URL # Extract the folder name from the URL
folder_name = var.folder_name == "" ? replace(basename(local.clone_url), ".git", "") : var.folder_name folder_name = replace(basename(local.clone_url), ".git", "")
# Construct the path to clone the repository # Construct the path to clone the repository
clone_path = var.base_dir != "" ? join("/", [var.base_dir, local.folder_name]) : join("/", ["~", local.folder_name]) clone_path = var.base_dir != "" ? join("/", [var.base_dir, local.folder_name]) : join("/", ["~", local.folder_name])
# Construct the web URL # Construct the web URL

View File

@@ -2,8 +2,8 @@
display_name: Git commit signing display_name: Git commit signing
description: Configures Git to sign commits using your Coder SSH key description: Configures Git to sign commits using your Coder SSH key
icon: ../.icons/git.svg icon: ../.icons/git.svg
maintainer_github: coder maintainer_github: phorcys420
verified: true verified: false
tags: [helper, git] tags: [helper, git]
--- ---

15
lint.ts
View File

@@ -5,15 +5,14 @@ import grayMatter from "gray-matter";
const files = await readdir(".", { withFileTypes: true }); const files = await readdir(".", { withFileTypes: true });
const dirs = files.filter( const dirs = files.filter(
(f) => (f) => f.isDirectory() && !f.name.startsWith(".") && f.name !== "node_modules"
f.isDirectory() && !f.name.startsWith(".") && f.name !== "node_modules",
); );
let badExit = false; let badExit = false;
// error reports an error to the console and sets badExit to true // error reports an error to the console and sets badExit to true
// so that the process will exit with a non-zero exit code. // so that the process will exit with a non-zero exit code.
const error = (...data: unknown[]) => { const error = (...data: any[]) => {
console.error(...data); console.error(...data);
badExit = true; badExit = true;
}; };
@@ -23,7 +22,7 @@ const verifyCodeBlocks = (
res = { res = {
codeIsTF: false, codeIsTF: false,
codeIsHCL: false, codeIsHCL: false,
}, }
) => { ) => {
for (const token of tokens) { for (const token of tokens) {
// Check in-depth. // Check in-depth.
@@ -31,12 +30,7 @@ const verifyCodeBlocks = (
verifyCodeBlocks(token.items, res); verifyCodeBlocks(token.items, res);
continue; continue;
} }
if (token.type === "list_item") { if (token.type === "list_item") {
if (token.tokens === undefined) {
throw new Error("Tokens are missing for type list_item");
}
verifyCodeBlocks(token.tokens, res); verifyCodeBlocks(token.tokens, res);
continue; continue;
} }
@@ -86,9 +80,8 @@ for (const dir of dirs) {
if (!data.maintainer_github) { if (!data.maintainer_github) {
error(dir.name, "missing maintainer_github"); error(dir.name, "missing maintainer_github");
} }
try { try {
await stat(path.join(".", dir.name, data.icon ?? "")); await stat(path.join(".", dir.name, data.icon));
} catch (ex) { } catch (ex) {
error(dir.name, "icon does not exist", data.icon); error(dir.name, "icon does not exist", data.icon);
} }

264
package-lock.json generated Normal file
View File

@@ -0,0 +1,264 @@
{
"name": "modules",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "modules",
"devDependencies": {
"bun-types": "^1.0.18",
"gray-matter": "^4.0.3",
"marked": "^12.0.0",
"prettier": "^3.2.5",
"prettier-plugin-sh": "^0.13.1",
"prettier-plugin-terraform-formatter": "^1.2.1"
},
"peerDependencies": {
"typescript": "^5.3.3"
}
},
"node_modules/@types/node": {
"version": "20.12.14",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.14.tgz",
"integrity": "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/@types/ws": {
"version": "8.5.10",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz",
"integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
"dependencies": {
"sprintf-js": "~1.0.2"
}
},
"node_modules/bun-types": {
"version": "1.1.16",
"resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.1.16.tgz",
"integrity": "sha512-LpAh8dQe4NKvhSW390Rkftw0ume0moSkRm575e1JZ1PwI/dXjbXyjpntq+2F0bVW1FV7V6B8EfWx088b+dNurw==",
"dev": true,
"dependencies": {
"@types/node": "~20.12.8",
"@types/ws": "~8.5.10"
}
},
"node_modules/esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"dev": true,
"bin": {
"esparse": "bin/esparse.js",
"esvalidate": "bin/esvalidate.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
"dev": true,
"dependencies": {
"is-extendable": "^0.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/gray-matter": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz",
"integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==",
"dev": true,
"dependencies": {
"js-yaml": "^3.13.1",
"kind-of": "^6.0.2",
"section-matter": "^1.0.0",
"strip-bom-string": "^1.0.0"
},
"engines": {
"node": ">=6.0"
}
},
"node_modules/is-extendable": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
"integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"dev": true,
"dependencies": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/marked": {
"version": "12.0.2",
"resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz",
"integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==",
"dev": true,
"bin": {
"marked": "bin/marked.js"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/mvdan-sh": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/mvdan-sh/-/mvdan-sh-0.10.1.tgz",
"integrity": "sha512-kMbrH0EObaKmK3nVRKUIIya1dpASHIEusM13S4V1ViHFuxuNxCo+arxoa6j/dbV22YBGjl7UKJm9QQKJ2Crzhg==",
"dev": true
},
"node_modules/prettier": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz",
"integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==",
"dev": true,
"peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/prettier-plugin-sh": {
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/prettier-plugin-sh/-/prettier-plugin-sh-0.13.1.tgz",
"integrity": "sha512-ytMcl1qK4s4BOFGvsc9b0+k9dYECal7U29bL/ke08FEUsF/JLN0j6Peo0wUkFDG4y2UHLMhvpyd6Sd3zDXe/eg==",
"dev": true,
"dependencies": {
"mvdan-sh": "^0.10.1",
"sh-syntax": "^0.4.1"
},
"engines": {
"node": ">=16.0.0"
},
"funding": {
"url": "https://opencollective.com/unts"
},
"peerDependencies": {
"prettier": "^3.0.0"
}
},
"node_modules/prettier-plugin-terraform-formatter": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prettier-plugin-terraform-formatter/-/prettier-plugin-terraform-formatter-1.2.1.tgz",
"integrity": "sha512-rdzV61Bs/Ecnn7uAS/vL5usTX8xUWM+nQejNLZxt3I1kJH5WSeLEmq7LYu1wCoEQF+y7Uv1xGvPRfl3lIe6+tA==",
"dev": true,
"peerDependencies": {
"prettier": ">= 1.16.0"
},
"peerDependenciesMeta": {
"prettier": {
"optional": true
}
}
},
"node_modules/section-matter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
"integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==",
"dev": true,
"dependencies": {
"extend-shallow": "^2.0.1",
"kind-of": "^6.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/sh-syntax": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/sh-syntax/-/sh-syntax-0.4.2.tgz",
"integrity": "sha512-/l2UZ5fhGZLVZa16XQM9/Vq/hezGGbdHeVEA01uWjOL1+7Ek/gt6FquW0iKKws4a9AYPYvlz6RyVvjh3JxOteg==",
"dev": true,
"dependencies": {
"tslib": "^2.6.2"
},
"engines": {
"node": ">=16.0.0"
},
"funding": {
"url": "https://opencollective.com/unts"
}
},
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
"dev": true
},
"node_modules/strip-bom-string": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
"integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/tslib": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
"integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==",
"dev": true
},
"node_modules/typescript": {
"version": "5.5.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz",
"integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true
}
}
}

View File

@@ -8,15 +8,15 @@
"update-version": "./update-version.sh" "update-version": "./update-version.sh"
}, },
"devDependencies": { "devDependencies": {
"bun-types": "^1.1.23", "bun-types": "^1.0.18",
"gray-matter": "^4.0.3", "gray-matter": "^4.0.3",
"marked": "^12.0.2", "marked": "^12.0.0",
"prettier": "^3.3.3", "prettier": "^3.2.5",
"prettier-plugin-sh": "^0.13.1", "prettier-plugin-sh": "^0.13.1",
"prettier-plugin-terraform-formatter": "^1.2.1" "prettier-plugin-terraform-formatter": "^1.2.1"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "^5.5.4" "typescript": "^5.3.3"
}, },
"prettier": { "prettier": {
"plugins": [ "plugins": [

View File

@@ -126,10 +126,7 @@ const assertSlackMessage = async (opts: {
durationMS?: number; durationMS?: number;
output: string; output: string;
}) => { }) => {
// Have to use non-null assertion because TS can't tell when the fetch let url: URL;
// function will run
let url!: URL;
const fakeSlackHost = serve({ const fakeSlackHost = serve({
fetch: (req) => { fetch: (req) => {
url = new URL(req.url); url = new URL(req.url);
@@ -141,16 +138,15 @@ const assertSlackMessage = async (opts: {
}, },
port: 0, port: 0,
}); });
const { instance, id } = await setupContainer( const { instance, id } = await setupContainer(
"alpine/curl", "alpine/curl",
opts.format ? { slack_message: opts.format } : undefined, opts.format && {
slack_message: opts.format,
},
); );
await writeCoder(id, "echo 'token'"); await writeCoder(id, "echo 'token'");
let exec = await execContainer(id, ["sh", "-c", instance.script]); let exec = await execContainer(id, ["sh", "-c", instance.script]);
expect(exec.exitCode).toBe(0); expect(exec.exitCode).toBe(0);
exec = await execContainer(id, [ exec = await execContainer(id, [
"sh", "sh",
"-c", "-c",
@@ -158,7 +154,6 @@ const assertSlackMessage = async (opts: {
fakeSlackHost.hostname fakeSlackHost.hostname
}:${fakeSlackHost.port}" slackme ${opts.command}`, }:${fakeSlackHost.port}" slackme ${opts.command}`,
]); ]);
expect(exec.stderr.trim()).toBe(""); expect(exec.stderr.trim()).toBe("");
expect(url.pathname).toEqual("/api/chat.postMessage"); expect(url.pathname).toEqual("/api/chat.postMessage");
expect(url.searchParams.get("channel")).toEqual("token"); expect(url.searchParams.get("channel")).toEqual("token");

28
test.ts
View File

@@ -90,21 +90,17 @@ type TerraformStateResource = {
type: string; type: string;
name: string; name: string;
provider: string; provider: string;
instances: [{ attributes: Record<string, any> }];
instances: [
{
attributes: Record<string, JsonValue>;
},
];
};
type TerraformOutput = {
type: string;
value: JsonValue;
}; };
export interface TerraformState { export interface TerraformState {
outputs: Record<string, TerraformOutput>; outputs: {
[key: string]: {
type: string;
value: any;
};
};
resources: [TerraformStateResource, ...TerraformStateResource[]]; resources: [TerraformStateResource, ...TerraformStateResource[]];
} }
@@ -153,25 +149,19 @@ export const testRequiredVariables = <TVars extends Record<string, string>>(
it("required variables", async () => { it("required variables", async () => {
await runTerraformApply(dir, vars); await runTerraformApply(dir, vars);
}); });
const varNames = Object.keys(vars); const varNames = Object.keys(vars);
varNames.forEach((varName) => { varNames.forEach((varName) => {
// Ensures that every variable provided is required! // Ensures that every variable provided is required!
it("missing variable " + varName, async () => { it("missing variable " + varName, async () => {
const localVars: Record<string, string> = {}; const localVars = {};
varNames.forEach((otherVarName) => { varNames.forEach((otherVarName) => {
if (otherVarName !== varName) { if (otherVarName !== varName) {
localVars[otherVarName] = vars[otherVarName]; localVars[otherVarName] = vars[otherVarName];
} }
}); });
try { try {
await runTerraformApply(dir, localVars); await runTerraformApply(dir, localVars);
} catch (ex) { } catch (ex) {
if (!(ex instanceof Error)) {
throw new Error("Unknown error generated");
}
expect(ex.message).toContain( expect(ex.message).toContain(
`input variable \"${varName}\" is not set`, `input variable \"${varName}\" is not set`,
); );

View File

@@ -2,7 +2,6 @@
"compilerOptions": { "compilerOptions": {
"target": "esnext", "target": "esnext",
"module": "esnext", "module": "esnext",
"strict": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"moduleResolution": "nodenext", "moduleResolution": "nodenext",
"types": ["bun-types"] "types": ["bun-types"]

View File

@@ -24,10 +24,9 @@ describe("vscode-desktop", async () => {
const coder_app = state.resources.find( const coder_app = state.resources.find(
(res) => res.type == "coder_app" && res.name == "vscode", (res) => res.type == "coder_app" && res.name == "vscode",
); );
expect(coder_app).not.toBeNull(); expect(coder_app).not.toBeNull();
expect(coder_app?.instances.length).toBe(1); expect(coder_app.instances.length).toBe(1);
expect(coder_app?.instances[0].attributes.order).toBeNull(); expect(coder_app.instances[0].attributes.order).toBeNull();
}); });
it("adds folder", async () => { it("adds folder", async () => {
@@ -81,9 +80,8 @@ describe("vscode-desktop", async () => {
const coder_app = state.resources.find( const coder_app = state.resources.find(
(res) => res.type == "coder_app" && res.name == "vscode", (res) => res.type == "coder_app" && res.name == "vscode",
); );
expect(coder_app).not.toBeNull(); expect(coder_app).not.toBeNull();
expect(coder_app?.instances.length).toBe(1); expect(coder_app.instances.length).toBe(1);
expect(coder_app?.instances[0].attributes.order).toBe(22); expect(coder_app.instances[0].attributes.order).toBe(22);
}); });
}); });

View File

@@ -14,9 +14,8 @@ Enable Remote Desktop + a web based client on Windows workspaces, powered by [de
```tf ```tf
# AWS example. See below for examples of using this module with other providers # AWS example. See below for examples of using this module with other providers
module "windows_rdp" { module "windows_rdp" {
source = "registry.coder.com/modules/windows-rdp/coder"
version = "1.0.16"
count = data.coder_workspace.me.start_count count = data.coder_workspace.me.start_count
source = "github.com/coder/modules//windows-rdp"
agent_id = resource.coder_agent.main.id agent_id = resource.coder_agent.main.id
resource_id = resource.aws_instance.dev.id resource_id = resource.aws_instance.dev.id
} }
@@ -24,7 +23,7 @@ module "windows_rdp" {
## Video ## Video
[![Video](./video-thumbnails/video-thumbnail.png)](https://github.com/coder/modules/assets/28937484/fb5f4a55-7b69-4550-ab62-301e13a4be02) https://github.com/coder/modules/assets/28937484/fb5f4a55-7b69-4550-ab62-301e13a4be02
## Examples ## Examples
@@ -32,9 +31,8 @@ module "windows_rdp" {
```tf ```tf
module "windows_rdp" { module "windows_rdp" {
source = "registry.coder.com/modules/windows-rdp/coder"
version = "1.0.16"
count = data.coder_workspace.me.start_count count = data.coder_workspace.me.start_count
source = "github.com/coder/modules//windows-rdp"
agent_id = resource.coder_agent.main.id agent_id = resource.coder_agent.main.id
resource_id = resource.aws_instance.dev.id resource_id = resource.aws_instance.dev.id
} }
@@ -44,9 +42,8 @@ module "windows_rdp" {
```tf ```tf
module "windows_rdp" { module "windows_rdp" {
source = "registry.coder.com/modules/windows-rdp/coder"
version = "1.0.16"
count = data.coder_workspace.me.start_count count = data.coder_workspace.me.start_count
source = "github.com/coder/modules//windows-rdp"
agent_id = resource.coder_agent.main.id agent_id = resource.coder_agent.main.id
resource_id = resource.google_compute_instance.dev[0].id resource_id = resource.google_compute_instance.dev[0].id
} }

View File

@@ -9,7 +9,6 @@ import {
type TestVariables = Readonly<{ type TestVariables = Readonly<{
agent_id: string; agent_id: string;
resource_id: string; resource_id: string;
share?: string;
admin_username?: string; admin_username?: string;
admin_password?: string; admin_password?: string;
}>; }>;
@@ -24,10 +23,7 @@ function findWindowsRdpScript(state: TerraformState): string | null {
} }
for (const instance of resource.instances) { for (const instance of resource.instances) {
if ( if (instance.attributes.display_name === "windows-rdp") {
instance.attributes.display_name === "windows-rdp" &&
typeof instance.attributes.script === "string"
) {
return instance.attributes.script; return instance.attributes.script;
} }
} }
@@ -103,11 +99,11 @@ describe("Web RDP", async () => {
const defaultRdpScript = findWindowsRdpScript(defaultState); const defaultRdpScript = findWindowsRdpScript(defaultState);
expect(defaultRdpScript).toBeString(); expect(defaultRdpScript).toBeString();
const defaultResultsGroup = const { username: defaultUsername, password: defaultPassword } =
formEntryValuesRe.exec(defaultRdpScript ?? "")?.groups ?? {}; formEntryValuesRe.exec(defaultRdpScript)?.groups ?? {};
expect(defaultResultsGroup.username).toBe("Administrator"); expect(defaultUsername).toBe("Administrator");
expect(defaultResultsGroup.password).toBe("coderRDP!"); expect(defaultPassword).toBe("coderRDP!");
// Test that custom usernames/passwords are also forwarded correctly // Test that custom usernames/passwords are also forwarded correctly
const customAdminUsername = "crouton"; const customAdminUsername = "crouton";
@@ -125,10 +121,10 @@ describe("Web RDP", async () => {
const customRdpScript = findWindowsRdpScript(customizedState); const customRdpScript = findWindowsRdpScript(customizedState);
expect(customRdpScript).toBeString(); expect(customRdpScript).toBeString();
const customResultsGroup = const { username: customUsername, password: customPassword } =
formEntryValuesRe.exec(customRdpScript ?? "")?.groups ?? {}; formEntryValuesRe.exec(customRdpScript)?.groups ?? {};
expect(customResultsGroup.username).toBe(customAdminUsername); expect(customUsername).toBe(customAdminUsername);
expect(customResultsGroup.password).toBe(customAdminPassword); expect(customPassword).toBe(customAdminPassword);
}); });
}); });

View File

@@ -9,15 +9,6 @@ terraform {
} }
} }
variable "share" {
type = string
default = "owner"
validation {
condition = var.share == "owner" || var.share == "authenticated" || var.share == "public"
error_message = "Incorrect value. Please set either 'owner', 'authenticated', or 'public'."
}
}
variable "agent_id" { variable "agent_id" {
type = string type = string
description = "The ID of a Coder agent." description = "The ID of a Coder agent."
@@ -42,7 +33,7 @@ variable "admin_password" {
resource "coder_script" "windows-rdp" { resource "coder_script" "windows-rdp" {
agent_id = var.agent_id agent_id = var.agent_id
display_name = "windows-rdp" display_name = "windows-rdp"
icon = "/icon/desktop.svg" icon = "https://svgur.com/i/158F.svg" # TODO: add to Coder icons
script = templatefile("${path.module}/powershell-installation-script.tftpl", { script = templatefile("${path.module}/powershell-installation-script.tftpl", {
admin_username = var.admin_username admin_username = var.admin_username
@@ -62,11 +53,10 @@ resource "coder_script" "windows-rdp" {
resource "coder_app" "windows-rdp" { resource "coder_app" "windows-rdp" {
agent_id = var.agent_id agent_id = var.agent_id
share = var.share
slug = "web-rdp" slug = "web-rdp"
display_name = "Web RDP" display_name = "Web RDP"
url = "http://localhost:7171" url = "http://localhost:7171"
icon = "/icon/desktop.svg" icon = "https://svgur.com/i/158F.svg"
subdomain = true subdomain = true
healthcheck { healthcheck {
@@ -81,6 +71,6 @@ resource "coder_app" "rdp-docs" {
display_name = "Local RDP" display_name = "Local RDP"
slug = "rdp-docs" slug = "rdp-docs"
icon = "https://raw.githubusercontent.com/matifali/logos/main/windows.svg" icon = "https://raw.githubusercontent.com/matifali/logos/main/windows.svg"
url = "https://coder.com/docs/ides/remote-desktops#rdp-desktop" url = "https://coder.com/docs/v2/latest/ides/remote-desktops#rdp-desktop"
external = true external = true
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB