Merge branch 'main' into 35C4n0r/feat-agentapi-architecture-improv
This commit is contained in:
commit
f14d174afc
@ -10,8 +10,6 @@ tags: [nextflow, workflow, hpc, bioinformatics]
|
||||
|
||||
A module that adds Nextflow to your Coder template.
|
||||
|
||||

|
||||
|
||||
```tf
|
||||
module "nextflow" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
|
||||
@ -3,20 +3,22 @@ set -o errexit
|
||||
set -o pipefail
|
||||
|
||||
port=${1:-3284}
|
||||
max_attempts=150
|
||||
|
||||
# This script waits for the agentapi server to start on port 3284.
|
||||
# This script waits for the agentapi server to start on the given port.
|
||||
# Each attempt sleeps 0.1s, so 150 attempts ≈ 15 seconds.
|
||||
# It considers the server started after 3 consecutive successful responses.
|
||||
|
||||
agentapi_started=false
|
||||
|
||||
echo "Waiting for agentapi server to start on port $port..."
|
||||
for i in $(seq 1 150); do
|
||||
for i in $(seq 1 "$max_attempts"); do
|
||||
for j in $(seq 1 3); do
|
||||
sleep 0.1
|
||||
if curl -fs -o /dev/null "http://localhost:$port/status"; then
|
||||
echo "agentapi response received ($j/3)"
|
||||
else
|
||||
echo "agentapi server not responding ($i/15)"
|
||||
echo "agentapi server not responding ($i/$max_attempts)"
|
||||
continue 2
|
||||
fi
|
||||
done
|
||||
@ -25,7 +27,7 @@ for i in $(seq 1 150); do
|
||||
done
|
||||
|
||||
if [ "$agentapi_started" != "true" ]; then
|
||||
echo "Error: agentapi server did not start on port $port after 15 seconds."
|
||||
echo "Error: agentapi server did not start on port $port after $max_attempts attempts."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@ Uses the [Coder Remote VS Code Extension](https://github.com/coder/vscode-coder)
|
||||
module "antigravity" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/antigravity/coder"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
agent_id = coder_agent.example.id
|
||||
}
|
||||
```
|
||||
@ -29,7 +29,7 @@ module "antigravity" {
|
||||
module "antigravity" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/antigravity/coder"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder/project"
|
||||
}
|
||||
@ -45,7 +45,7 @@ The following example configures Antigravity to use the GitHub MCP server with a
|
||||
module "antigravity" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/antigravity/coder"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder/project"
|
||||
mcp = jsonencode({
|
||||
|
||||
@ -66,15 +66,15 @@ locals {
|
||||
|
||||
module "vscode-desktop-core" {
|
||||
source = "registry.coder.com/coder/vscode-desktop-core/coder"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
|
||||
agent_id = var.agent_id
|
||||
|
||||
web_app_icon = "/icon/antigravity.svg"
|
||||
web_app_slug = var.slug
|
||||
web_app_display_name = var.display_name
|
||||
web_app_order = var.order
|
||||
web_app_group = var.group
|
||||
coder_app_icon = "/icon/antigravity.svg"
|
||||
coder_app_slug = var.slug
|
||||
coder_app_display_name = var.display_name
|
||||
coder_app_order = var.order
|
||||
coder_app_group = var.group
|
||||
|
||||
folder = var.folder
|
||||
open_recent = var.open_recent
|
||||
|
||||
@ -16,7 +16,7 @@ Uses the [Coder Remote VS Code Extension](https://github.com/coder/vscode-coder)
|
||||
module "cursor" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/cursor/coder"
|
||||
version = "1.4.0"
|
||||
version = "1.4.1"
|
||||
agent_id = coder_agent.main.id
|
||||
}
|
||||
```
|
||||
@ -29,7 +29,7 @@ module "cursor" {
|
||||
module "cursor" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/cursor/coder"
|
||||
version = "1.4.0"
|
||||
version = "1.4.1"
|
||||
agent_id = coder_agent.main.id
|
||||
folder = "/home/coder/project"
|
||||
}
|
||||
@ -45,7 +45,7 @@ The following example configures Cursor to use the GitHub MCP server with authen
|
||||
module "cursor" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/cursor/coder"
|
||||
version = "1.4.0"
|
||||
version = "1.4.1"
|
||||
agent_id = coder_agent.main.id
|
||||
folder = "/home/coder/project"
|
||||
mcp = jsonencode({
|
||||
|
||||
@ -66,7 +66,7 @@ locals {
|
||||
|
||||
module "vscode-desktop-core" {
|
||||
source = "registry.coder.com/coder/vscode-desktop-core/coder"
|
||||
version = "1.0.0"
|
||||
version = "1.0.2"
|
||||
|
||||
agent_id = var.agent_id
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ Under the hood, this module uses the [coder dotfiles](https://coder.com/docs/v2/
|
||||
module "dotfiles" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/dotfiles/coder"
|
||||
version = "1.2.4"
|
||||
version = "1.3.0"
|
||||
agent_id = coder_agent.example.id
|
||||
}
|
||||
```
|
||||
@ -31,7 +31,7 @@ module "dotfiles" {
|
||||
module "dotfiles" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/dotfiles/coder"
|
||||
version = "1.2.4"
|
||||
version = "1.3.0"
|
||||
agent_id = coder_agent.example.id
|
||||
}
|
||||
```
|
||||
@ -42,7 +42,7 @@ module "dotfiles" {
|
||||
module "dotfiles" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/dotfiles/coder"
|
||||
version = "1.2.4"
|
||||
version = "1.3.0"
|
||||
agent_id = coder_agent.example.id
|
||||
user = "root"
|
||||
}
|
||||
@ -54,14 +54,14 @@ module "dotfiles" {
|
||||
module "dotfiles" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/dotfiles/coder"
|
||||
version = "1.2.4"
|
||||
version = "1.3.0"
|
||||
agent_id = coder_agent.example.id
|
||||
}
|
||||
|
||||
module "dotfiles-root" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/dotfiles/coder"
|
||||
version = "1.2.4"
|
||||
version = "1.3.0"
|
||||
agent_id = coder_agent.example.id
|
||||
user = "root"
|
||||
dotfiles_uri = module.dotfiles.dotfiles_uri
|
||||
@ -76,7 +76,7 @@ You can set a default dotfiles repository for all users by setting the `default_
|
||||
module "dotfiles" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/dotfiles/coder"
|
||||
version = "1.2.4"
|
||||
version = "1.3.0"
|
||||
agent_id = coder_agent.example.id
|
||||
default_dotfiles_uri = "https://github.com/coder/dotfiles"
|
||||
}
|
||||
|
||||
@ -84,6 +84,12 @@ variable "manual_update" {
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "post_clone_script" {
|
||||
description = "Custom script to run after applying dotfiles. Runs every time, even if dotfiles were already applied."
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
data "coder_parameter" "dotfiles_uri" {
|
||||
count = var.dotfiles_uri == null ? 1 : 0
|
||||
type = "string"
|
||||
@ -104,13 +110,15 @@ data "coder_parameter" "dotfiles_uri" {
|
||||
locals {
|
||||
dotfiles_uri = var.dotfiles_uri != null ? var.dotfiles_uri : data.coder_parameter.dotfiles_uri[0].value
|
||||
user = var.user != null ? var.user : ""
|
||||
encoded_post_clone_script = var.post_clone_script != null ? base64encode(var.post_clone_script) : ""
|
||||
}
|
||||
|
||||
resource "coder_script" "dotfiles" {
|
||||
agent_id = var.agent_id
|
||||
script = templatefile("${path.module}/run.sh", {
|
||||
DOTFILES_URI : local.dotfiles_uri,
|
||||
DOTFILES_USER : local.user
|
||||
DOTFILES_USER : local.user,
|
||||
POST_CLONE_SCRIPT : local.encoded_post_clone_script
|
||||
})
|
||||
display_name = "Dotfiles"
|
||||
icon = "/icon/dotfiles.svg"
|
||||
@ -127,7 +135,8 @@ resource "coder_app" "dotfiles" {
|
||||
group = var.group
|
||||
command = templatefile("${path.module}/run.sh", {
|
||||
DOTFILES_URI : local.dotfiles_uri,
|
||||
DOTFILES_USER : local.user
|
||||
DOTFILES_USER : local.user,
|
||||
POST_CLONE_SCRIPT : local.encoded_post_clone_script
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -43,3 +43,14 @@ if [ -n "$${DOTFILES_URI// }" ]; then
|
||||
sudo -u "$DOTFILES_USER" "$CODER_BIN" dotfiles "$DOTFILES_URI" -y 2>&1 | tee "$DOTFILES_USER_HOME/.dotfiles.log"
|
||||
fi
|
||||
fi
|
||||
|
||||
POST_CLONE_SCRIPT="${POST_CLONE_SCRIPT}"
|
||||
|
||||
if [ -n "$POST_CLONE_SCRIPT" ]; then
|
||||
echo "Running post-clone script..."
|
||||
POST_CLONE_TMP=$(mktemp)
|
||||
echo "$POST_CLONE_SCRIPT" | base64 -d > "$POST_CLONE_TMP"
|
||||
chmod +x "$POST_CLONE_TMP"
|
||||
$POST_CLONE_TMP
|
||||
rm "$POST_CLONE_TMP"
|
||||
fi
|
||||
|
||||
@ -42,7 +42,7 @@ module "jetbrains" {
|
||||
version = "1.3.0"
|
||||
agent_id = coder_agent.main.id
|
||||
folder = "/home/coder/project"
|
||||
default = ["PY", "IU"] # Pre-configure GoLand and IntelliJ IDEA
|
||||
default = ["PY", "IU"] # Pre-configure PyCharm and IntelliJ IDEA
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ Uses the [Coder Remote VS Code Extension](https://github.com/coder/vscode-coder)
|
||||
module "kiro" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/kiro/coder"
|
||||
version = "1.2.0"
|
||||
version = "1.2.1"
|
||||
agent_id = coder_agent.main.id
|
||||
}
|
||||
```
|
||||
@ -31,7 +31,7 @@ module "kiro" {
|
||||
module "kiro" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/kiro/coder"
|
||||
version = "1.2.0"
|
||||
version = "1.2.1"
|
||||
agent_id = coder_agent.main.id
|
||||
folder = "/home/coder/project"
|
||||
}
|
||||
@ -47,7 +47,7 @@ The following example configures Kiro to use the GitHub MCP server with authenti
|
||||
module "kiro" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/kiro/coder"
|
||||
version = "1.2.0"
|
||||
version = "1.2.1"
|
||||
agent_id = coder_agent.main.id
|
||||
folder = "/home/coder/project"
|
||||
mcp = jsonencode({
|
||||
|
||||
@ -53,7 +53,7 @@ locals {
|
||||
|
||||
module "vscode-desktop-core" {
|
||||
source = "registry.coder.com/coder/vscode-desktop-core/coder"
|
||||
version = "1.0.0"
|
||||
version = "1.0.2"
|
||||
|
||||
agent_id = var.agent_id
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@ Automatically install and run [Mux](https://github.com/coder/mux) in a Coder wor
|
||||
module "mux" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/mux/coder"
|
||||
version = "1.0.8"
|
||||
version = "1.2.0"
|
||||
agent_id = coder_agent.main.id
|
||||
}
|
||||
```
|
||||
@ -37,7 +37,7 @@ module "mux" {
|
||||
module "mux" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/mux/coder"
|
||||
version = "1.0.8"
|
||||
version = "1.2.0"
|
||||
agent_id = coder_agent.main.id
|
||||
}
|
||||
```
|
||||
@ -48,7 +48,7 @@ module "mux" {
|
||||
module "mux" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/mux/coder"
|
||||
version = "1.0.8"
|
||||
version = "1.2.0"
|
||||
agent_id = coder_agent.main.id
|
||||
# Default is "latest"; set to a specific version to pin
|
||||
install_version = "0.4.0"
|
||||
@ -63,19 +63,34 @@ Start Mux with `mux server --add-project /path/to/project`:
|
||||
module "mux" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/mux/coder"
|
||||
version = "1.0.8"
|
||||
version = "1.2.0"
|
||||
agent_id = coder_agent.main.id
|
||||
add-project = "/path/to/project"
|
||||
}
|
||||
```
|
||||
|
||||
### Pass Arbitrary `mux server` Arguments
|
||||
|
||||
Use `additional_arguments` to append additional arguments to `mux server`.
|
||||
The module parses quoted values, so grouped arguments remain intact.
|
||||
|
||||
```tf
|
||||
module "mux" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/mux/coder"
|
||||
version = "1.2.0"
|
||||
agent_id = coder_agent.main.id
|
||||
additional_arguments = "--open-mode pinned --add-project '/workspaces/my repo'"
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Port
|
||||
|
||||
```tf
|
||||
module "mux" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/mux/coder"
|
||||
version = "1.0.8"
|
||||
version = "1.2.0"
|
||||
agent_id = coder_agent.main.id
|
||||
port = 8080
|
||||
}
|
||||
@ -89,7 +104,7 @@ Run an existing copy of Mux if found, otherwise install from npm:
|
||||
module "mux" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/mux/coder"
|
||||
version = "1.0.8"
|
||||
version = "1.2.0"
|
||||
agent_id = coder_agent.main.id
|
||||
use_cached = true
|
||||
}
|
||||
@ -103,7 +118,7 @@ Run without installing from the network (requires Mux to be pre-installed):
|
||||
module "mux" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/mux/coder"
|
||||
version = "1.0.8"
|
||||
version = "1.2.0"
|
||||
agent_id = coder_agent.main.id
|
||||
install = false
|
||||
}
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import {
|
||||
executeScriptInContainer,
|
||||
execContainer,
|
||||
findResourceInstance,
|
||||
readFileContainer,
|
||||
removeContainer,
|
||||
runContainer,
|
||||
runTerraformApply,
|
||||
runTerraformInit,
|
||||
testRequiredVariables,
|
||||
@ -40,6 +45,57 @@ describe("mux", async () => {
|
||||
}
|
||||
}, 60000);
|
||||
|
||||
it("parses custom additional_arguments", async () => {
|
||||
const state = await runTerraformApply(import.meta.dir, {
|
||||
agent_id: "foo",
|
||||
install: false,
|
||||
log_path: "/tmp/mux.log",
|
||||
additional_arguments:
|
||||
"--open-mode pinned --add-project '/workspaces/my repo'",
|
||||
});
|
||||
|
||||
const instance = findResourceInstance(state, "coder_script");
|
||||
const id = await runContainer("alpine/curl");
|
||||
|
||||
try {
|
||||
const setup = await execContainer(id, [
|
||||
"sh",
|
||||
"-c",
|
||||
`apk add --no-cache bash >/dev/null
|
||||
mkdir -p /tmp/mux
|
||||
cat <<'EOF' > /tmp/mux/mux
|
||||
#!/usr/bin/env sh
|
||||
i=1
|
||||
for arg in "$@"; do
|
||||
echo "arg$i=$arg"
|
||||
i=$((i + 1))
|
||||
done
|
||||
EOF
|
||||
chmod +x /tmp/mux/mux`,
|
||||
]);
|
||||
expect(setup.exitCode).toBe(0);
|
||||
|
||||
const output = await execContainer(id, ["sh", "-c", instance.script]);
|
||||
if (output.exitCode !== 0) {
|
||||
console.log("STDOUT:\n" + output.stdout);
|
||||
console.log("STDERR:\n" + output.stderr);
|
||||
}
|
||||
expect(output.exitCode).toBe(0);
|
||||
|
||||
await execContainer(id, ["sh", "-c", "sleep 1"]);
|
||||
const log = await readFileContainer(id, "/tmp/mux.log");
|
||||
expect(log).toContain("arg1=server");
|
||||
expect(log).toContain("arg2=--port");
|
||||
expect(log).toContain("arg3=4000");
|
||||
expect(log).toContain("arg4=--open-mode");
|
||||
expect(log).toContain("arg5=pinned");
|
||||
expect(log).toContain("arg6=--add-project");
|
||||
expect(log).toContain("arg7=/workspaces/my repo");
|
||||
} finally {
|
||||
await removeContainer(id);
|
||||
}
|
||||
}, 60000);
|
||||
|
||||
it("runs with npm present", async () => {
|
||||
const state = await runTerraformApply(import.meta.dir, {
|
||||
agent_id: "foo",
|
||||
|
||||
@ -7,6 +7,10 @@ terraform {
|
||||
source = "coder/coder"
|
||||
version = ">= 2.5"
|
||||
}
|
||||
random = {
|
||||
source = "hashicorp/random"
|
||||
version = ">= 3.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,6 +55,12 @@ variable "add-project" {
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "additional_arguments" {
|
||||
type = string
|
||||
description = "Additional command-line arguments to pass to `mux server` (for example: `--add-project /path --open-mode pinned`)."
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "install_version" {
|
||||
type = string
|
||||
description = "The version or dist-tag of Mux to install."
|
||||
@ -113,6 +123,22 @@ variable "open_in" {
|
||||
}
|
||||
}
|
||||
|
||||
# Per-module auth token for cross-site request protection.
|
||||
# We pass this token into each mux process at launch time (process-scoped env)
|
||||
# and include it in the app URL query string (?token=...).
|
||||
#
|
||||
# Why process-scoped env instead of a shared coder_env value:
|
||||
# multiple mux module instances can target the same agent (different slug/port).
|
||||
# A single global MUX_SERVER_AUTH_TOKEN env key would cause collisions.
|
||||
resource "random_password" "mux_auth_token" {
|
||||
length = 64
|
||||
special = false
|
||||
}
|
||||
|
||||
locals {
|
||||
mux_auth_token = random_password.mux_auth_token.result
|
||||
}
|
||||
|
||||
resource "coder_script" "mux" {
|
||||
agent_id = var.agent_id
|
||||
display_name = var.display_name
|
||||
@ -122,9 +148,11 @@ resource "coder_script" "mux" {
|
||||
PORT : var.port,
|
||||
LOG_PATH : var.log_path,
|
||||
ADD_PROJECT : var.add-project == null ? "" : var.add-project,
|
||||
ADDITIONAL_ARGUMENTS : var.additional_arguments,
|
||||
INSTALL_PREFIX : var.install_prefix,
|
||||
OFFLINE : !var.install,
|
||||
USE_CACHED : var.use_cached,
|
||||
AUTH_TOKEN : local.mux_auth_token,
|
||||
})
|
||||
run_on_start = true
|
||||
|
||||
@ -140,7 +168,7 @@ resource "coder_app" "mux" {
|
||||
agent_id = var.agent_id
|
||||
slug = var.slug
|
||||
display_name = var.display_name
|
||||
url = "http://localhost:${var.port}"
|
||||
url = "http://localhost:${var.port}?token=${local.mux_auth_token}"
|
||||
icon = "/icon/mux.svg"
|
||||
subdomain = var.subdomain
|
||||
share = var.share
|
||||
@ -154,5 +182,3 @@ resource "coder_app" "mux" {
|
||||
threshold = 6
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -20,8 +20,10 @@ run "install_false_and_use_cached_conflict" {
|
||||
]
|
||||
}
|
||||
|
||||
# Needs command = apply because the URL contains random_password.result,
|
||||
# which is unknown during plan.
|
||||
run "custom_port" {
|
||||
command = plan
|
||||
command = apply
|
||||
|
||||
variables {
|
||||
agent_id = "foo"
|
||||
@ -29,8 +31,65 @@ run "custom_port" {
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = resource.coder_app.mux.url == "http://localhost:8080"
|
||||
error_message = "coder_app URL must use the configured port"
|
||||
condition = startswith(resource.coder_app.mux.url, "http://localhost:8080?token=")
|
||||
error_message = "coder_app URL must use the configured port and include auth token"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = trimprefix(resource.coder_app.mux.url, "http://localhost:8080?token=") == random_password.mux_auth_token.result
|
||||
error_message = "URL token must match the generated auth token"
|
||||
}
|
||||
}
|
||||
|
||||
# Needs command = apply because random_password.result is unknown during plan.
|
||||
run "auth_token_in_server_script" {
|
||||
command = apply
|
||||
|
||||
variables {
|
||||
agent_id = "foo"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = strcontains(resource.coder_script.mux.script, "MUX_SERVER_AUTH_TOKEN=")
|
||||
error_message = "mux launch script must set MUX_SERVER_AUTH_TOKEN"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = strcontains(resource.coder_script.mux.script, random_password.mux_auth_token.result)
|
||||
error_message = "mux launch script must use the generated auth token"
|
||||
}
|
||||
}
|
||||
|
||||
# Needs command = apply because random_password.result is unknown during plan.
|
||||
run "auth_token_in_url" {
|
||||
command = apply
|
||||
|
||||
variables {
|
||||
agent_id = "foo"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = startswith(resource.coder_app.mux.url, "http://localhost:4000?token=")
|
||||
error_message = "coder_app URL must include auth token query parameter"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = trimprefix(resource.coder_app.mux.url, "http://localhost:4000?token=") == random_password.mux_auth_token.result
|
||||
error_message = "URL token must match the generated auth token"
|
||||
}
|
||||
}
|
||||
|
||||
run "custom_additional_arguments" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "foo"
|
||||
additional_arguments = "--open-mode pinned --add-project '/workspaces/my repo'"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = strcontains(resource.coder_script.mux.script, "--open-mode pinned --add-project '/workspaces/my repo'")
|
||||
error_message = "mux launch script must include the configured additional arguments"
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,5 +121,3 @@ run "use_cached_only_success" {
|
||||
use_cached = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -9,7 +9,9 @@ function run_mux() {
|
||||
rm -f "$HOME/.mux/server.lock"
|
||||
|
||||
local port_value
|
||||
local auth_token_value
|
||||
port_value="${PORT}"
|
||||
auth_token_value="${AUTH_TOKEN}"
|
||||
if [ -z "$port_value" ]; then
|
||||
port_value="4000"
|
||||
fi
|
||||
@ -18,9 +20,25 @@ function run_mux() {
|
||||
if [ -n "${ADD_PROJECT}" ]; then
|
||||
set -- "$@" --add-project "${ADD_PROJECT}"
|
||||
fi
|
||||
|
||||
# Parse additional user-supplied server arguments while preserving quoted groups.
|
||||
if [ -n "${ADDITIONAL_ARGUMENTS}" ]; then
|
||||
local parsed_additional_arguments
|
||||
if ! parsed_additional_arguments="$(printf "%s\n" "${ADDITIONAL_ARGUMENTS}" | xargs -n1 printf "%s\n" 2> /dev/null)"; then
|
||||
echo "❌ Failed to parse additional_arguments. Ensure quotes are balanced."
|
||||
exit 1
|
||||
fi
|
||||
while IFS= read -r parsed_arg; do
|
||||
[ -n "$parsed_arg" ] || continue
|
||||
set -- "$@" "$parsed_arg"
|
||||
done << EOF
|
||||
$${parsed_additional_arguments}
|
||||
EOF
|
||||
fi
|
||||
|
||||
echo "🚀 Starting mux server on port $port_value..."
|
||||
echo "Check logs at ${LOG_PATH}!"
|
||||
PORT="$port_value" "$MUX_BINARY" "$@" > "${LOG_PATH}" 2>&1 &
|
||||
MUX_SERVER_AUTH_TOKEN="$auth_token_value" PORT="$port_value" "$MUX_BINARY" "$@" > "${LOG_PATH}" 2>&1 &
|
||||
}
|
||||
|
||||
# Check if mux is already installed for offline mode
|
||||
|
||||
@ -16,15 +16,15 @@ The VSCode Desktop Core module is a building block for modules that need to expo
|
||||
```tf
|
||||
module "vscode-desktop-core" {
|
||||
source = "registry.coder.com/coder/vscode-desktop-core/coder"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
|
||||
agent_id = var.agent_id
|
||||
|
||||
web_app_icon = "/icon/code.svg"
|
||||
web_app_slug = "vscode"
|
||||
web_app_display_name = "VS Code Desktop"
|
||||
web_app_order = var.order
|
||||
web_app_group = var.group
|
||||
coder_app_icon = "/icon/code.svg"
|
||||
coder_app_slug = "vscode"
|
||||
coder_app_display_name = "VS Code Desktop"
|
||||
coder_app_order = var.order
|
||||
coder_app_group = var.group
|
||||
|
||||
folder = var.folder
|
||||
open_recent = var.open_recent
|
||||
|
||||
@ -11,9 +11,9 @@ const appName = "vscode-desktop";
|
||||
const defaultVariables = {
|
||||
agent_id: "foo",
|
||||
|
||||
web_app_icon: "/icon/code.svg",
|
||||
web_app_slug: "vscode",
|
||||
web_app_display_name: "VS Code Desktop",
|
||||
coder_app_icon: "/icon/code.svg",
|
||||
coder_app_slug: "vscode",
|
||||
coder_app_display_name: "VS Code Desktop",
|
||||
|
||||
protocol: "vscode",
|
||||
};
|
||||
@ -99,16 +99,16 @@ describe("vscode-desktop-core", async () => {
|
||||
);
|
||||
|
||||
expect(coder_app?.instances[0].attributes.slug).toBe(
|
||||
defaultVariables.web_app_slug,
|
||||
defaultVariables.coder_app_slug,
|
||||
);
|
||||
expect(coder_app?.instances[0].attributes.display_name).toBe(
|
||||
defaultVariables.web_app_display_name,
|
||||
defaultVariables.coder_app_display_name,
|
||||
);
|
||||
});
|
||||
|
||||
it("sets order", async () => {
|
||||
const state = await runTerraformApply(import.meta.dir, {
|
||||
web_app_order: "5",
|
||||
coder_app_order: "5",
|
||||
|
||||
...defaultVariables,
|
||||
});
|
||||
@ -122,7 +122,7 @@ describe("vscode-desktop-core", async () => {
|
||||
|
||||
it("sets group", async () => {
|
||||
const state = await runTerraformApply(import.meta.dir, {
|
||||
web_app_group: "web-app-group",
|
||||
coder_app_group: "web-app-group",
|
||||
|
||||
...defaultVariables,
|
||||
});
|
||||
|
||||
@ -31,28 +31,28 @@ variable "protocol" {
|
||||
description = "The URI protocol the IDE."
|
||||
}
|
||||
|
||||
variable "web_app_icon" {
|
||||
variable "coder_app_icon" {
|
||||
type = string
|
||||
description = "The icon of the coder_app."
|
||||
}
|
||||
|
||||
variable "web_app_slug" {
|
||||
variable "coder_app_slug" {
|
||||
type = string
|
||||
description = "The slug of the coder_app."
|
||||
}
|
||||
|
||||
variable "web_app_display_name" {
|
||||
variable "coder_app_display_name" {
|
||||
type = string
|
||||
description = "The display name of the coder_app."
|
||||
}
|
||||
|
||||
variable "web_app_order" {
|
||||
variable "coder_app_order" {
|
||||
type = number
|
||||
description = "The order of the coder_app."
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "web_app_group" {
|
||||
variable "coder_app_group" {
|
||||
type = string
|
||||
description = "The group of the coder_app."
|
||||
default = null
|
||||
@ -65,12 +65,12 @@ resource "coder_app" "vscode-desktop" {
|
||||
agent_id = var.agent_id
|
||||
external = true
|
||||
|
||||
icon = var.web_app_icon
|
||||
slug = var.web_app_slug
|
||||
display_name = var.web_app_display_name
|
||||
icon = var.coder_app_icon
|
||||
slug = var.coder_app_slug
|
||||
display_name = var.coder_app_display_name
|
||||
|
||||
order = var.web_app_order
|
||||
group = var.web_app_group
|
||||
order = var.coder_app_order
|
||||
group = var.coder_app_group
|
||||
|
||||
url = join("", [
|
||||
var.protocol,
|
||||
|
||||
@ -16,7 +16,7 @@ Uses the [Coder Remote VS Code Extension](https://github.com/coder/vscode-coder)
|
||||
module "vscode" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/vscode-desktop/coder"
|
||||
version = "1.2.0"
|
||||
version = "1.2.1"
|
||||
agent_id = coder_agent.main.id
|
||||
}
|
||||
```
|
||||
@ -29,7 +29,7 @@ module "vscode" {
|
||||
module "vscode" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/vscode-desktop/coder"
|
||||
version = "1.2.0"
|
||||
version = "1.2.1"
|
||||
agent_id = coder_agent.main.id
|
||||
folder = "/home/coder/project"
|
||||
}
|
||||
|
||||
@ -40,7 +40,7 @@ variable "group" {
|
||||
|
||||
module "vscode-desktop-core" {
|
||||
source = "registry.coder.com/coder/vscode-desktop-core/coder"
|
||||
version = "1.0.0"
|
||||
version = "1.0.2"
|
||||
|
||||
agent_id = var.agent_id
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@ Uses the [Coder Remote VS Code Extension](https://github.com/coder/vscode-coder)
|
||||
module "windsurf" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/windsurf/coder"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
agent_id = coder_agent.main.id
|
||||
}
|
||||
```
|
||||
@ -29,7 +29,7 @@ module "windsurf" {
|
||||
module "windsurf" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/windsurf/coder"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
agent_id = coder_agent.main.id
|
||||
folder = "/home/coder/project"
|
||||
}
|
||||
@ -45,7 +45,7 @@ The following example configures Windsurf to use the GitHub MCP server with auth
|
||||
module "windsurf" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/windsurf/coder"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
agent_id = coder_agent.main.id
|
||||
folder = "/home/coder/project"
|
||||
mcp = jsonencode({
|
||||
|
||||
@ -65,7 +65,7 @@ locals {
|
||||
|
||||
module "vscode-desktop-core" {
|
||||
source = "registry.coder.com/coder/vscode-desktop-core/coder"
|
||||
version = "1.0.0"
|
||||
version = "1.0.2"
|
||||
|
||||
agent_id = var.agent_id
|
||||
|
||||
|
||||
@ -27,8 +27,21 @@ This template provisions the following resources:
|
||||
|
||||
- Azure VM (ephemeral, deleted on stop)
|
||||
- Managed disk (persistent, mounted to `/home/coder`)
|
||||
- Resource group, virtual network, subnet, and network interface (persistent, required by the managed disk and VM)
|
||||
|
||||
This means, when the workspace restarts, any tools or files outside of the home directory are not persisted. To pre-bake tools into the workspace (e.g. `python3`), modify the VM image, or use a [startup script](https://registry.terraform.io/providers/coder/coder/latest/docs/resources/script). Alternatively, individual developers can [personalize](https://coder.com/docs/dotfiles) their workspaces with dotfiles.
|
||||
### What happens on stop
|
||||
|
||||
When a workspace is **stopped**, only the VM is destroyed. The managed disk, resource group, virtual network, subnet, and network interface all persist. This is by design — the managed disk retains your `/home/coder` data across workspace restarts, and the other resources remain because the disk depends on them.
|
||||
|
||||
This means you will see these Azure resources in your subscription even when a workspace is stopped. This is expected behavior.
|
||||
|
||||
### What happens on delete
|
||||
|
||||
When a workspace is **deleted**, all resources are destroyed, including the resource group, networking resources, and managed disk.
|
||||
|
||||
### Workspace restarts
|
||||
|
||||
Since the VM is ephemeral, any tools or files outside of the home directory are not persisted across restarts. To pre-bake tools into the workspace (e.g. `python3`), modify the VM image, or use a [startup script](https://registry.terraform.io/providers/coder/coder/latest/docs/resources/script). Alternatively, individual developers can [personalize](https://coder.com/docs/dotfiles) their workspaces with dotfiles.
|
||||
|
||||
> [!NOTE]
|
||||
> This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.
|
||||
|
||||
@ -16,7 +16,7 @@ Uses the [Coder Remote VS Code Extension](https://github.com/coder/vscode-coder)
|
||||
module "positron" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/cytoshahar/positron/coder"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
agent_id = coder_agent.main.id
|
||||
}
|
||||
```
|
||||
@ -29,7 +29,7 @@ module "positron" {
|
||||
module "positron" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/cytoshahar/positron/coder"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
agent_id = coder_agent.main.id
|
||||
folder = "/home/coder/project"
|
||||
}
|
||||
|
||||
@ -41,13 +41,13 @@ variable "group" {
|
||||
variable "slug" {
|
||||
type = string
|
||||
description = "The slug of the app."
|
||||
default = "cursor"
|
||||
default = "positron"
|
||||
}
|
||||
|
||||
variable "display_name" {
|
||||
type = string
|
||||
description = "The display name of the app."
|
||||
default = "Cursor Desktop"
|
||||
default = "Positron Desktop"
|
||||
}
|
||||
|
||||
data "coder_workspace" "me" {}
|
||||
@ -55,7 +55,7 @@ data "coder_workspace_owner" "me" {}
|
||||
|
||||
module "vscode-desktop-core" {
|
||||
source = "registry.coder.com/coder/vscode-desktop-core/coder"
|
||||
version = "1.0.0"
|
||||
version = "1.0.2"
|
||||
|
||||
agent_id = var.agent_id
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user