Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .github/typos.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mavrickrishi = "mavrickrishi" # Username
mavrick = "mavrick" # Username
inh = "inh" # Option in setpriv command
exportfs = "exportfs" # nfs related binary
ba = "ba" # sed branch command in :a;N;$!ba pattern

[files]
extend-exclude = ["registry/coder/templates/aws-devcontainer/architecture.svg"] #False positive
18 changes: 9 additions & 9 deletions registry/coder/modules/code-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Automatically install [code-server](https://github.com/coder/code-server) in a w
module "code-server" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/code-server/coder"
version = "1.5.1"
version = "1.5.2"
agent_id = coder_agent.example.id
}
```
Expand All @@ -29,7 +29,7 @@ module "code-server" {
module "code-server" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/code-server/coder"
version = "1.5.1"
version = "1.5.2"
agent_id = coder_agent.example.id
install_version = "4.106.3"
}
Expand All @@ -43,7 +43,7 @@ Install the Dracula theme from [OpenVSX](https://open-vsx.org/):
module "code-server" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/code-server/coder"
version = "1.5.1"
version = "1.5.2"
agent_id = coder_agent.example.id
extensions = [
"dracula-theme.theme-dracula"
Expand All @@ -61,7 +61,7 @@ Configure VS Code's [User settings.json](https://code.visualstudio.com/docs/gets
module "code-server" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/code-server/coder"
version = "1.5.1"
version = "1.5.2"
agent_id = coder_agent.example.id
extensions = ["dracula-theme.theme-dracula"]
settings = {
Expand All @@ -81,7 +81,7 @@ Install multiple extensions from [OpenVSX](https://open-vsx.org/) by adding them
module "code-server" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/code-server/coder"
version = "1.5.1"
version = "1.5.2"
agent_id = coder_agent.example.id
extensions = ["dracula-theme.theme-dracula", "ms-azuretools.vscode-docker"]
}
Expand All @@ -95,7 +95,7 @@ Open a [`.code-workspace`](https://coder.com/docs/code-server/FAQ#how-does-code-
module "code-server" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/code-server/coder"
version = "1.5.1"
version = "1.5.2"
agent_id = coder_agent.example.id
workspace = "/home/coder/project/my.code-workspace"
}
Expand All @@ -109,7 +109,7 @@ You can pass additional command-line arguments to code-server using the `additio
module "code-server" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/code-server/coder"
version = "1.5.1"
version = "1.5.2"
agent_id = coder_agent.example.id
additional_args = "--disable-workspace-trust"
}
Expand All @@ -125,7 +125,7 @@ Run an existing copy of code-server if found, otherwise download from GitHub:
module "code-server" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/code-server/coder"
version = "1.5.1"
version = "1.5.2"
agent_id = coder_agent.example.id
use_cached = true
extensions = ["dracula-theme.theme-dracula", "ms-azuretools.vscode-docker"]
Expand All @@ -138,7 +138,7 @@ Just run code-server in the background, don't fetch it from GitHub:
module "code-server" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/code-server/coder"
version = "1.5.1"
version = "1.5.2"
agent_id = coder_agent.example.id
offline = true
}
Expand Down
124 changes: 124 additions & 0 deletions registry/coder/modules/code-server/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,39 @@ import {

setDefaultTimeout(2 * 60 * 1000);

// Mock code-server CLI that records every `--install-extension <id>` call as
// "INSTALLED:<id>" on stdout so tests can assert which extensions the script
// tried to install. `--list-extensions` returns nothing (no cached extensions).
const MOCK_CODE_SERVER = `#!/bin/bash
if [ "$1" = "--list-extensions" ]; then
exit 0
fi
prev=""
for arg in "$@"; do
if [ "$prev" = "--install-extension" ]; then
echo "INSTALLED:$arg"
fi
prev="$arg"
done
exit 0`;

// A .vscode/extensions.json exercising every JSONC feature the stripper must
// handle: a standalone line comment, an end-of-line comment, a block comment
// containing a URL (the case the previous sed pipeline corrupted), a multi-line
// block comment, and a trailing comma.
const JSONC_EXTENSIONS_JSON = `{
// Recommended extensions for this workspace
"recommendations": [
"ms-python.python", // Python language support
/* linting - see https://open-vsx.org for the registry */
"dbaeumer.vscode-eslint",
/*
* Formatting tools
*/
"esbenp.prettier-vscode", // trailing comma below is intentional
]
}`;

let cleanupContainers: string[] = [];

afterEach(async () => {
Expand Down Expand Up @@ -214,6 +247,97 @@ chmod +x /tmp/code-server/bin/code-server`,
expect(settingsResult.stdout).toContain("template_value");
});

it("auto-installs recommended extensions from a JSONC extensions.json", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
use_cached: true,
auto_install_extensions: true,
});

const containerId = await runContainer("ubuntu:22.04");
cleanupContainers.push(containerId);

await execContainer(containerId, ["apt-get", "update", "-qq"]);
await execContainer(containerId, ["apt-get", "install", "-y", "-qq", "jq"]);

// use_cached + a pre-existing binary skips the real install so the script
// reaches the auto-install path.
await execContainer(containerId, [
"bash",
"-c",
`mkdir -p /tmp/code-server/bin && cat > /tmp/code-server/bin/code-server << 'MOCKEOF'
${MOCK_CODE_SERVER}
MOCKEOF
chmod +x /tmp/code-server/bin/code-server`,
]);

await execContainer(containerId, [
"bash",
"-c",
`mkdir -p /root/.vscode && cat > /root/.vscode/extensions.json << 'JSONCEOF'
${JSONC_EXTENSIONS_JSON}
JSONCEOF`,
]);

const script = findResourceInstance(state, "coder_script");
const result = await execContainer(containerId, [
"bash",
"-c",
script.script,
]);

expect(result.exitCode).toBe(0);
expect(result.stdout).toContain("INSTALLED:ms-python.python");
expect(result.stdout).toContain("INSTALLED:dbaeumer.vscode-eslint");
expect(result.stdout).toContain("INSTALLED:esbenp.prettier-vscode");
});

it("does not error on an extensions.json without a recommendations key", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
use_cached: true,
auto_install_extensions: true,
});

const containerId = await runContainer("ubuntu:22.04");
cleanupContainers.push(containerId);

await execContainer(containerId, ["apt-get", "update", "-qq"]);
await execContainer(containerId, ["apt-get", "install", "-y", "-qq", "jq"]);

await execContainer(containerId, [
"bash",
"-c",
`mkdir -p /tmp/code-server/bin && cat > /tmp/code-server/bin/code-server << 'MOCKEOF'
${MOCK_CODE_SERVER}
MOCKEOF
chmod +x /tmp/code-server/bin/code-server`,
]);

// Valid JSON, but no `recommendations` key. The null-safe query
// `(.recommendations // [])[]` must not make jq error on the missing key.
await execContainer(containerId, [
"bash",
"-c",
`mkdir -p /root/.vscode && cat > /root/.vscode/extensions.json << 'JSONCEOF'
{
"unwantedRecommendations": ["ms-python.python"]
}
JSONCEOF`,
]);

const script = findResourceInstance(state, "coder_script");
const result = await execContainer(containerId, [
"bash",
"-c",
script.script,
]);

expect(result.exitCode).toBe(0);
expect(result.stdout).not.toContain("INSTALLED:");
expect(result.stderr).not.toContain("Cannot iterate over null");
});

it("installs and runs code-server", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
Expand Down
41 changes: 37 additions & 4 deletions registry/coder/modules/code-server/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -156,18 +156,51 @@ for extension in "$${EXTENSIONLIST[@]}"; do
fi
done

# Strip JSONC features (block/line comments, trailing commas) so jq can parse
# .vscode/extensions.json and .code-workspace files. Portable across GNU, BSD,
# and BusyBox sed (Coder workspaces run on Linux, macOS and Alpine).
#
# Three passes, because each concern needs a different scope and order:
# 1. Block comments - slurps the whole file so /* ... */ can span lines.
# Runs first so a URL such as /* see https://example */ is removed as a
# unit and its // is never seen by the line-comment pass.
# 2. Line comments - per line, so // ... stops at end of line without the
# non-portable [^\n] class (BSD sed reads \n inside a bracket as a literal
# backslash and n, silently corrupting IDs containing "n"). A // preceded
# by ':' is preserved so URLs in string values (e.g. proxy settings in a
# .code-workspace) survive.
# 3. Trailing commas - slurps the whole file so a comma and its closing
# bracket may sit on different lines.
# The ':a;$!{N;ba}' slurp is single-line safe (it falls through on the last or
# only line), unlike ':a;N;$!ba', which drops single-line input entirely.
strip_jsonc_for_extensions() {
sed -E ':a
$!{
N
ba
}
s#/[*]([^*]|[*]+[^*/])*[*]+/##g' "$1" \
| sed -E 's#^[[:space:]]*//.*##; s#([^:])//.*#\1#' \
| sed -E ':a
$!{
N
ba
}
s/,[^]}"]*([]}])/\1/g'
}

if [ "${AUTO_INSTALL_EXTENSIONS}" = true ]; then
if ! command -v jq > /dev/null; then
echo "jq is required to install extensions from a workspace file."
exit 0
fi

RECOMMENDATIONS_FILE=""
RECOMMENDATIONS_QUERY=".recommendations[]"
RECOMMENDATIONS_QUERY='(.recommendations // [])[]'
if [ -n "${WORKSPACE}" ]; then
if [ -f "${WORKSPACE}" ]; then
RECOMMENDATIONS_FILE="${WORKSPACE}"
RECOMMENDATIONS_QUERY=".extensions.recommendations[]?"
RECOMMENDATIONS_QUERY='(.extensions.recommendations // [])[]'
else
echo "⚠️ Workspace file ${WORKSPACE} not found, skipping extension recommendations."
fi
Expand All @@ -183,8 +216,8 @@ if [ "${AUTO_INSTALL_EXTENSIONS}" = true ]; then

if [ -n "$RECOMMENDATIONS_FILE" ]; then
printf "🧩 Installing extensions from %s...\n" "$RECOMMENDATIONS_FILE"
# Use sed to remove single-line comments before parsing with jq
extensions=$(sed 's|//.*||g' "$RECOMMENDATIONS_FILE" | jq -r "$RECOMMENDATIONS_QUERY")
# Strip JSONC comments and trailing commas before parsing with jq
extensions=$(strip_jsonc_for_extensions "$RECOMMENDATIONS_FILE" | jq -r "$RECOMMENDATIONS_QUERY")
Comment thread
35C4n0r marked this conversation as resolved.
for extension in $extensions; do
if extension_installed "$extension"; then
continue
Expand Down
12 changes: 6 additions & 6 deletions registry/coder/modules/vscode-web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Automatically install [Visual Studio Code Server](https://code.visualstudio.com/
module "vscode-web" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/vscode-web/coder"
version = "1.6.0"
version = "1.6.1"
agent_id = coder_agent.example.id
accept_license = true
}
Expand All @@ -30,7 +30,7 @@ module "vscode-web" {
module "vscode-web" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/vscode-web/coder"
version = "1.6.0"
version = "1.6.1"
agent_id = coder_agent.example.id
install_prefix = "/home/coder/.vscode-web"
folder = "/home/coder"
Expand All @@ -44,7 +44,7 @@ module "vscode-web" {
module "vscode-web" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/vscode-web/coder"
version = "1.6.0"
version = "1.6.1"
agent_id = coder_agent.example.id
extensions = ["github.copilot", "ms-python.python", "ms-toolsai.jupyter"]
accept_license = true
Expand All @@ -59,7 +59,7 @@ Configure VS Code's [Machine settings.json](https://code.visualstudio.com/docs/g
module "vscode-web" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/vscode-web/coder"
version = "1.6.0"
version = "1.6.1"
agent_id = coder_agent.example.id
extensions = ["dracula-theme.theme-dracula"]
settings = {
Expand All @@ -80,7 +80,7 @@ By default, this module installs the latest. To pin a specific version, retrieve
module "vscode-web" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/vscode-web/coder"
version = "1.6.0"
version = "1.6.1"
agent_id = coder_agent.example.id
commit_id = "e54c774e0add60467559eb0d1e229c6452cf8447"
accept_license = true
Expand All @@ -96,7 +96,7 @@ Note: Either `workspace` or `folder` can be used, but not both simultaneously. T
module "vscode-web" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/vscode-web/coder"
version = "1.6.0"
version = "1.6.1"
agent_id = coder_agent.example.id
workspace = "/home/coder/coder.code-workspace"
}
Expand Down
Loading