mirror of
https://github.com/coder/registry.git
synced 2026-06-03 04:58:15 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 11a64e9b08 | |||
| 0fd0d08d2f | |||
| 817238ea64 | |||
| d0ef879e5f | |||
| 872fdbcf35 | |||
| ce50e52fc5 | |||
| 6940774628 | |||
| 85c51816f9 | |||
| acdbd5777f | |||
| 8bd24f8b44 | |||
| 79cef2ecfc |
@@ -14,7 +14,7 @@ jobs:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Detect changed files
|
||||
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3
|
||||
uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
|
||||
id: filter
|
||||
with:
|
||||
list-files: shell
|
||||
@@ -37,9 +37,9 @@ jobs:
|
||||
all:
|
||||
- '**'
|
||||
- name: Set up Terraform
|
||||
uses: coder/coder/.github/actions/setup-tf@deaacff8437e3f4ee84bc51c4e5162f6dd7d190e # v2.31.3
|
||||
uses: coder/coder/.github/actions/setup-tf@1a774ab7ce99063a2e01beb94de3fcbccaf84dbe # v2.31.5
|
||||
- name: Set up Bun
|
||||
uses: oven-sh/setup-bun@ecf28ddc73e819eb6fa29df6b34ef8921c743461 # v2
|
||||
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
|
||||
with:
|
||||
# We're using the latest version of Bun for now, but it might be worth
|
||||
# reconsidering. They've pushed breaking changes in patch releases
|
||||
@@ -82,12 +82,12 @@ jobs:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Install Bun
|
||||
uses: oven-sh/setup-bun@ecf28ddc73e819eb6fa29df6b34ef8921c743461 # v2
|
||||
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
|
||||
with:
|
||||
bun-version: latest
|
||||
# Need Terraform for its formatter
|
||||
- name: Install Terraform
|
||||
uses: coder/coder/.github/actions/setup-tf@deaacff8437e3f4ee84bc51c4e5162f6dd7d190e # v2.31.3
|
||||
uses: coder/coder/.github/actions/setup-tf@1a774ab7ce99063a2e01beb94de3fcbccaf84dbe # v2.31.5
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
- name: Validate formatting
|
||||
|
||||
@@ -26,12 +26,12 @@ jobs:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up Bun
|
||||
uses: oven-sh/setup-bun@ecf28ddc73e819eb6fa29df6b34ef8921c743461 # v2
|
||||
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Set up Terraform
|
||||
uses: coder/coder/.github/actions/setup-tf@deaacff8437e3f4ee84bc51c4e5162f6dd7d190e # v2.31.3
|
||||
uses: coder/coder/.github/actions/setup-tf@1a774ab7ce99063a2e01beb94de3fcbccaf84dbe # v2.31.5
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
@@ -13,7 +13,7 @@ Run Codex CLI in your workspace to access OpenAI's models through the Codex inte
|
||||
```tf
|
||||
module "codex" {
|
||||
source = "registry.coder.com/coder-labs/codex/coder"
|
||||
version = "4.3.0"
|
||||
version = "4.3.1"
|
||||
agent_id = coder_agent.example.id
|
||||
openai_api_key = var.openai_api_key
|
||||
workdir = "/home/coder/project"
|
||||
@@ -32,7 +32,7 @@ module "codex" {
|
||||
module "codex" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder-labs/codex/coder"
|
||||
version = "4.3.0"
|
||||
version = "4.3.1"
|
||||
agent_id = coder_agent.example.id
|
||||
openai_api_key = "..."
|
||||
workdir = "/home/coder/project"
|
||||
@@ -51,7 +51,7 @@ For tasks integration with AI Bridge, add `enable_aibridge = true` to the [Usage
|
||||
```tf
|
||||
module "codex" {
|
||||
source = "registry.coder.com/coder-labs/codex/coder"
|
||||
version = "4.3.0"
|
||||
version = "4.3.1"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder/project"
|
||||
enable_aibridge = true
|
||||
@@ -60,21 +60,16 @@ module "codex" {
|
||||
|
||||
When `enable_aibridge = true`, the module:
|
||||
|
||||
- Configures Codex to use the AI Bridge profile with `base_url` pointing to `${data.coder_workspace.me.access_url}/api/v2/aibridge/openai/v1` and `env_key` pointing to the workspace owner's session token
|
||||
- Configures Codex to use the aibridge model_provider with `base_url` pointing to `${data.coder_workspace.me.access_url}/api/v2/aibridge/openai/v1` and `env_key` pointing to the workspace owner's session token
|
||||
|
||||
```toml
|
||||
profile = "aibridge" # sets the default profile to aibridge
|
||||
model_provider = "aibridge"
|
||||
|
||||
[model_providers.aibridge]
|
||||
name = "AI Bridge"
|
||||
base_url = "https://example.coder.com/api/v2/aibridge/openai/v1"
|
||||
env_key = "CODER_AIBRIDGE_SESSION_TOKEN"
|
||||
wire_api = "responses"
|
||||
|
||||
[profiles.aibridge]
|
||||
model_provider = "aibridge"
|
||||
model = "<model>" # as configured in the module input
|
||||
model_reasoning_effort = "<model_reasoning_effort>" # as configured in the module input
|
||||
```
|
||||
|
||||
This allows Codex to route API requests through Coder's AI Bridge instead of directly to OpenAI's API.
|
||||
@@ -94,7 +89,7 @@ data "coder_task" "me" {}
|
||||
|
||||
module "codex" {
|
||||
source = "registry.coder.com/coder-labs/codex/coder"
|
||||
version = "4.3.0"
|
||||
version = "4.3.1"
|
||||
agent_id = coder_agent.example.id
|
||||
openai_api_key = "..."
|
||||
ai_prompt = data.coder_task.me.prompt
|
||||
@@ -114,7 +109,7 @@ By default, when `enable_boundary = true`, the module uses `coder boundary` subc
|
||||
```tf
|
||||
module "codex" {
|
||||
source = "registry.coder.com/coder-labs/codex/coder"
|
||||
version = "4.3.0"
|
||||
version = "4.3.1"
|
||||
agent_id = coder_agent.main.id
|
||||
openai_api_key = var.openai_api_key
|
||||
workdir = "/home/coder/project"
|
||||
@@ -132,7 +127,7 @@ This example shows additional configuration options for custom models, MCP serve
|
||||
```tf
|
||||
module "codex" {
|
||||
source = "registry.coder.com/coder-labs/codex/coder"
|
||||
version = "4.3.0"
|
||||
version = "4.3.1"
|
||||
agent_id = coder_agent.example.id
|
||||
openai_api_key = "..."
|
||||
workdir = "/home/coder/project"
|
||||
|
||||
@@ -468,10 +468,7 @@ describe("codex", async () => {
|
||||
id,
|
||||
"/home/coder/.codex/config.toml",
|
||||
);
|
||||
expect(configToml).toContain(
|
||||
"[profiles.aibridge]\n" + 'model_provider = "aibridge"',
|
||||
);
|
||||
expect(configToml).toContain('profile = "aibridge"');
|
||||
expect(configToml).toContain('model_provider = "aibridge"');
|
||||
});
|
||||
|
||||
test("boundary-enabled", async () => {
|
||||
|
||||
@@ -84,10 +84,10 @@ variable "enable_aibridge" {
|
||||
|
||||
variable "model_reasoning_effort" {
|
||||
type = string
|
||||
description = "The reasoning effort for the AI Bridge model. One of: none, low, medium, high. https://platform.openai.com/docs/guides/latest-model#lower-reasoning-effort"
|
||||
default = "medium"
|
||||
description = "The reasoning effort for the model. One of: none, low, medium, high. https://platform.openai.com/docs/guides/latest-model#lower-reasoning-effort"
|
||||
default = ""
|
||||
validation {
|
||||
condition = contains(["none", "low", "medium", "high"], var.model_reasoning_effort)
|
||||
condition = contains(["", "none", "minimal", "low", "medium", "high", "xhigh"], var.model_reasoning_effort)
|
||||
error_message = "model_reasoning_effort must be one of: none, low, medium, high."
|
||||
}
|
||||
}
|
||||
@@ -137,7 +137,7 @@ variable "agentapi_version" {
|
||||
variable "codex_model" {
|
||||
type = string
|
||||
description = "The model for Codex to use. Defaults to gpt-5.3-codex."
|
||||
default = "gpt-5.3-codex"
|
||||
default = "gpt-5.4"
|
||||
}
|
||||
|
||||
variable "pre_install_script" {
|
||||
@@ -225,7 +225,7 @@ locals {
|
||||
install_script = file("${path.module}/scripts/install.sh")
|
||||
start_script = file("${path.module}/scripts/start.sh")
|
||||
module_dir_name = ".codex-module"
|
||||
latest_codex_model = "gpt-5.3-codex"
|
||||
latest_codex_model = "gpt-5.4"
|
||||
aibridge_config = <<-EOF
|
||||
[model_providers.aibridge]
|
||||
name = "AI Bridge"
|
||||
@@ -233,10 +233,6 @@ locals {
|
||||
env_key = "CODER_AIBRIDGE_SESSION_TOKEN"
|
||||
wire_api = "responses"
|
||||
|
||||
[profiles.aibridge]
|
||||
model_provider = "aibridge"
|
||||
model = "${var.codex_model}"
|
||||
model_reasoning_effort = "${var.model_reasoning_effort}"
|
||||
EOF
|
||||
}
|
||||
|
||||
@@ -302,6 +298,7 @@ module "agentapi" {
|
||||
ARG_ADDITIONAL_MCP_SERVERS='${base64encode(var.additional_mcp_servers)}' \
|
||||
ARG_CODER_MCP_APP_STATUS_SLUG='${local.app_slug}' \
|
||||
ARG_CODEX_START_DIRECTORY='${local.workdir}' \
|
||||
ARG_MODEL_REASONING_EFFORT='${var.model_reasoning_effort}' \
|
||||
ARG_CODEX_INSTRUCTION_PROMPT='${base64encode(var.codex_system_prompt)}' \
|
||||
/tmp/install.sh
|
||||
EOT
|
||||
|
||||
@@ -93,10 +93,14 @@ function install_codex() {
|
||||
write_minimal_default_config() {
|
||||
local config_path="$1"
|
||||
|
||||
ARG_DEFAULT_PROFILE=""
|
||||
ARG_OPTIONAL_TOP_LEVEL_CONFIG=""
|
||||
|
||||
if [[ "${ARG_ENABLE_AIBRIDGE}" = "true" ]]; then
|
||||
ARG_DEFAULT_PROFILE='profile = "aibridge"'
|
||||
ARG_OPTIONAL_TOP_LEVEL_CONFIG='model_provider = "aibridge"'
|
||||
fi
|
||||
|
||||
if [[ "${ARG_MODEL_REASONING_EFFORT}" != "" ]]; then
|
||||
ARG_OPTIONAL_TOP_LEVEL_CONFIG+=$'\n'"model_reasoning_effort = \"${ARG_MODEL_REASONING_EFFORT}\""
|
||||
fi
|
||||
|
||||
cat << EOF > "$config_path"
|
||||
@@ -104,13 +108,17 @@ write_minimal_default_config() {
|
||||
sandbox_mode = "workspace-write"
|
||||
approval_policy = "never"
|
||||
preferred_auth_method = "apikey"
|
||||
${ARG_DEFAULT_PROFILE}
|
||||
${ARG_OPTIONAL_TOP_LEVEL_CONFIG}
|
||||
|
||||
[sandbox_workspace_write]
|
||||
network_access = true
|
||||
|
||||
[notice.model_migrations]
|
||||
"${ARG_CODEX_MODEL}" = "${ARG_LATEST_CODEX_MODEL}"
|
||||
|
||||
[projects."${ARG_CODEX_START_DIRECTORY}"]
|
||||
trust_level = "trusted"
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
|
||||
@@ -155,7 +155,7 @@ setup_workdir() {
|
||||
build_codex_args() {
|
||||
CODEX_ARGS=()
|
||||
|
||||
if [[ -n "${ARG_CODEX_MODEL}" ]] && [[ "${ARG_ENABLE_AIBRIDGE}" != "true" ]]; then
|
||||
if [[ -n "${ARG_CODEX_MODEL}" ]]; then
|
||||
CODEX_ARGS+=("--model" "${ARG_CODEX_MODEL}")
|
||||
fi
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
display_name: Portable Desktop
|
||||
description: Install the portabledesktop binary for lightweight Linux desktop sessions.
|
||||
icon: ../../../../.icons/desktop.svg
|
||||
verified: true
|
||||
tags: [desktop, vnc, ai]
|
||||
---
|
||||
|
||||
# Portable Desktop
|
||||
|
||||
Install [portabledesktop](https://github.com/coder/portabledesktop) for lightweight Linux desktop sessions over VNC. The binary is stored in the agent's script data directory and is automatically available on PATH via `CODER_SCRIPT_BIN_DIR`.
|
||||
|
||||
```tf
|
||||
module "portabledesktop" {
|
||||
source = "registry.coder.com/coder/portabledesktop/coder"
|
||||
version = "0.1.0"
|
||||
agent_id = coder_agent.example.id
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Custom download URL with checksum verification
|
||||
|
||||
```tf
|
||||
module "portabledesktop" {
|
||||
source = "registry.coder.com/coder/portabledesktop/coder"
|
||||
version = "0.1.0"
|
||||
agent_id = coder_agent.example.id
|
||||
url = "https://example.com/portabledesktop-linux-x64"
|
||||
sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
}
|
||||
```
|
||||
|
||||
### Additionally copy to a system path
|
||||
|
||||
Use `install_dir` to copy the binary to a system-wide directory in addition to the default script data directory:
|
||||
|
||||
```tf
|
||||
module "portabledesktop" {
|
||||
source = "registry.coder.com/coder/portabledesktop/coder"
|
||||
version = "0.1.0"
|
||||
agent_id = coder_agent.example.id
|
||||
install_dir = "/usr/local/bin"
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,242 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import {
|
||||
execContainer,
|
||||
findResourceInstance,
|
||||
removeContainer,
|
||||
runContainer,
|
||||
runTerraformApply,
|
||||
runTerraformInit,
|
||||
testRequiredVariables,
|
||||
type TerraformState,
|
||||
} from "~test";
|
||||
|
||||
interface TestFixture {
|
||||
state: TerraformState;
|
||||
server: ReturnType<typeof Bun.serve>;
|
||||
[Symbol.asyncDispose](): Promise<void>;
|
||||
}
|
||||
|
||||
interface ContainerHandle {
|
||||
id: string;
|
||||
[Symbol.asyncDispose](): Promise<void>;
|
||||
}
|
||||
|
||||
async function setupContainer(image: string): Promise<ContainerHandle> {
|
||||
const id = await runContainer(image);
|
||||
return {
|
||||
id,
|
||||
[Symbol.asyncDispose]: async () => {
|
||||
await removeContainer(id);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const ENV_PREFIX =
|
||||
'export CODER_SCRIPT_DATA_DIR=/tmp/coder-script-data && export CODER_SCRIPT_BIN_DIR=/tmp/coder-script-data/bin && mkdir -p "$CODER_SCRIPT_DATA_DIR" "$CODER_SCRIPT_BIN_DIR" && ';
|
||||
|
||||
async function setupFakeBinaryServer(
|
||||
dir: string,
|
||||
extraVars?: Record<string, string>,
|
||||
): Promise<TestFixture> {
|
||||
const fakeBinary = "#!/bin/sh\necho portabledesktop";
|
||||
const server = Bun.serve({
|
||||
port: 0,
|
||||
fetch() {
|
||||
return new Response(fakeBinary);
|
||||
},
|
||||
});
|
||||
|
||||
const state = await runTerraformApply(dir, {
|
||||
agent_id: "foo",
|
||||
url: `http://localhost:${server.port}/portabledesktop`,
|
||||
...extraVars,
|
||||
});
|
||||
|
||||
return {
|
||||
state,
|
||||
server,
|
||||
[Symbol.asyncDispose]: async () => {
|
||||
server.stop(true);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe("portabledesktop", async () => {
|
||||
await runTerraformInit(import.meta.dir);
|
||||
|
||||
testRequiredVariables(import.meta.dir, {
|
||||
agent_id: "foo",
|
||||
});
|
||||
|
||||
it("installs portabledesktop successfully", async () => {
|
||||
await using fixture = await setupFakeBinaryServer(import.meta.dir);
|
||||
await using container = await setupContainer("alpine/curl");
|
||||
|
||||
const script = findResourceInstance(fixture.state, "coder_script").script;
|
||||
const resp = await execContainer(container.id, [
|
||||
"sh",
|
||||
"-c",
|
||||
ENV_PREFIX + script,
|
||||
]);
|
||||
|
||||
expect(resp.exitCode).toBe(0);
|
||||
expect(resp.stdout).toContain("portabledesktop installed successfully");
|
||||
|
||||
// Check binary exists at CODER_SCRIPT_DATA_DIR.
|
||||
const checkBinary = await execContainer(container.id, [
|
||||
"test",
|
||||
"-x",
|
||||
"/tmp/coder-script-data/portabledesktop",
|
||||
]);
|
||||
expect(checkBinary.exitCode).toBe(0);
|
||||
|
||||
// Check symlink exists at CODER_SCRIPT_BIN_DIR.
|
||||
const checkSymlink = await execContainer(container.id, [
|
||||
"test",
|
||||
"-L",
|
||||
"/tmp/coder-script-data/bin/portabledesktop",
|
||||
]);
|
||||
expect(checkSymlink.exitCode).toBe(0);
|
||||
}, 30000);
|
||||
|
||||
it("verifies checksum when sha256 is provided", async () => {
|
||||
const fakeBinary = "#!/bin/sh\necho portabledesktop";
|
||||
const hasher = new Bun.CryptoHasher("sha256");
|
||||
hasher.update(fakeBinary);
|
||||
const sha256 = hasher.digest("hex");
|
||||
|
||||
await using fixture = await setupFakeBinaryServer(import.meta.dir, {
|
||||
sha256,
|
||||
});
|
||||
await using container = await setupContainer("alpine/curl");
|
||||
|
||||
const script = findResourceInstance(fixture.state, "coder_script").script;
|
||||
const resp = await execContainer(container.id, [
|
||||
"sh",
|
||||
"-c",
|
||||
ENV_PREFIX + script,
|
||||
]);
|
||||
|
||||
expect(resp.exitCode).toBe(0);
|
||||
expect(resp.stdout).toContain("Checksum verified successfully");
|
||||
expect(resp.stdout).toContain("portabledesktop installed successfully");
|
||||
}, 30000);
|
||||
|
||||
it("fails when sha256 does not match", async () => {
|
||||
const wrongSha256 =
|
||||
"0000000000000000000000000000000000000000000000000000000000000000";
|
||||
|
||||
await using fixture = await setupFakeBinaryServer(import.meta.dir, {
|
||||
sha256: wrongSha256,
|
||||
});
|
||||
await using container = await setupContainer("alpine/curl");
|
||||
|
||||
const script = findResourceInstance(fixture.state, "coder_script").script;
|
||||
const resp = await execContainer(container.id, [
|
||||
"sh",
|
||||
"-c",
|
||||
ENV_PREFIX + script,
|
||||
]);
|
||||
|
||||
expect(resp.exitCode).toBe(1);
|
||||
expect(resp.stdout).toContain("Checksum mismatch");
|
||||
}, 30000);
|
||||
|
||||
it("skips checksum verification when sha256 is not set", async () => {
|
||||
await using fixture = await setupFakeBinaryServer(import.meta.dir);
|
||||
await using container = await setupContainer("alpine/curl");
|
||||
|
||||
const script = findResourceInstance(fixture.state, "coder_script").script;
|
||||
const resp = await execContainer(container.id, [
|
||||
"sh",
|
||||
"-c",
|
||||
ENV_PREFIX + script,
|
||||
]);
|
||||
|
||||
expect(resp.exitCode).toBe(0);
|
||||
expect(resp.stdout).not.toContain("Checksum verified");
|
||||
expect(resp.stdout).toContain("portabledesktop installed successfully");
|
||||
}, 30000);
|
||||
|
||||
it("falls back to sudo when install_dir is not writable", async () => {
|
||||
await using fixture = await setupFakeBinaryServer(import.meta.dir, {
|
||||
install_dir: "/usr/local/bin",
|
||||
});
|
||||
await using container = await setupContainer("alpine/curl");
|
||||
|
||||
await execContainer(container.id, [
|
||||
"sh",
|
||||
"-c",
|
||||
"apk add sudo && " +
|
||||
"adduser -D testuser && " +
|
||||
"echo 'testuser ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers && " +
|
||||
"mkdir -p /usr/local/bin",
|
||||
]);
|
||||
|
||||
const script = findResourceInstance(fixture.state, "coder_script").script;
|
||||
const resp = await execContainer(
|
||||
container.id,
|
||||
["sh", "-c", ENV_PREFIX + script],
|
||||
["--user", "testuser"],
|
||||
);
|
||||
|
||||
expect(resp.exitCode).toBe(0);
|
||||
expect(resp.stdout).toContain("via sudo");
|
||||
expect(resp.stdout).toContain("portabledesktop installed successfully");
|
||||
|
||||
// Verify the binary was copied to the install_dir.
|
||||
const check = await execContainer(container.id, [
|
||||
"test",
|
||||
"-x",
|
||||
"/usr/local/bin/portabledesktop",
|
||||
]);
|
||||
expect(check.exitCode).toBe(0);
|
||||
}, 30000);
|
||||
|
||||
it("creates install_dir if it does not exist", async () => {
|
||||
await using fixture = await setupFakeBinaryServer(import.meta.dir, {
|
||||
install_dir: "/opt/custom/bin",
|
||||
});
|
||||
await using container = await setupContainer("alpine/curl");
|
||||
|
||||
const script = findResourceInstance(fixture.state, "coder_script").script;
|
||||
const resp = await execContainer(container.id, [
|
||||
"sh",
|
||||
"-c",
|
||||
ENV_PREFIX + script,
|
||||
]);
|
||||
|
||||
expect(resp.exitCode).toBe(0);
|
||||
expect(resp.stdout).toContain("portabledesktop installed successfully");
|
||||
|
||||
const check = await execContainer(container.id, [
|
||||
"test",
|
||||
"-x",
|
||||
"/opt/custom/bin/portabledesktop",
|
||||
]);
|
||||
expect(check.exitCode).toBe(0);
|
||||
}, 30000);
|
||||
|
||||
it("falls back to wget when curl is not available", async () => {
|
||||
await using fixture = await setupFakeBinaryServer(import.meta.dir);
|
||||
await using container = await setupContainer("alpine");
|
||||
|
||||
// Install wget but ensure curl is not present.
|
||||
await execContainer(container.id, [
|
||||
"sh",
|
||||
"-c",
|
||||
"apk add wget && ! command -v curl",
|
||||
]);
|
||||
|
||||
const script = findResourceInstance(fixture.state, "coder_script").script;
|
||||
const resp = await execContainer(container.id, [
|
||||
"sh",
|
||||
"-c",
|
||||
ENV_PREFIX + script,
|
||||
]);
|
||||
|
||||
expect(resp.exitCode).toBe(0);
|
||||
expect(resp.stdout).toContain("via wget");
|
||||
expect(resp.stdout).toContain("portabledesktop installed successfully");
|
||||
}, 30000);
|
||||
});
|
||||
@@ -0,0 +1,65 @@
|
||||
terraform {
|
||||
required_version = ">= 1.0"
|
||||
|
||||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = ">= 2.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variable "agent_id" {
|
||||
type = string
|
||||
description = "The ID of a Coder agent."
|
||||
}
|
||||
|
||||
variable "install_dir" {
|
||||
type = string
|
||||
description = "Optional directory to copy the binary into (e.g. /usr/local/bin). The binary is always stored in the agent's script data directory and available on PATH via CODER_SCRIPT_BIN_DIR."
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "url" {
|
||||
type = string
|
||||
description = "Custom download URL. Overrides the default GitHub latest release URL when set."
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "sha256" {
|
||||
type = string
|
||||
description = "SHA256 checksum. When set, the downloaded binary is verified against it."
|
||||
default = null
|
||||
}
|
||||
|
||||
locals {
|
||||
default_amd64_url = "https://github.com/coder/portabledesktop/releases/latest/download/portabledesktop-linux-x64"
|
||||
default_arm64_url = "https://github.com/coder/portabledesktop/releases/latest/download/portabledesktop-linux-arm64"
|
||||
|
||||
using_custom_url = var.url != null
|
||||
|
||||
amd64_url = local.using_custom_url ? var.url : local.default_amd64_url
|
||||
arm64_url = local.using_custom_url ? var.url : local.default_arm64_url
|
||||
|
||||
# Empty string signals "skip verification" to the shell script.
|
||||
sha256 = var.sha256 != null ? var.sha256 : ""
|
||||
install_dir = var.install_dir != null ? var.install_dir : ""
|
||||
}
|
||||
|
||||
resource "coder_script" "portabledesktop" {
|
||||
agent_id = var.agent_id
|
||||
display_name = "Portable Desktop"
|
||||
icon = "/icon/desktop.svg"
|
||||
script = <<-EOT
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
echo -n '${base64encode(file("${path.module}/run.sh"))}' | base64 -d > /tmp/portabledesktop-install.sh
|
||||
chmod +x /tmp/portabledesktop-install.sh
|
||||
ARG_AMD64_URL="$(echo -n '${base64encode(local.amd64_url)}' | base64 -d)" \
|
||||
ARG_ARM64_URL="$(echo -n '${base64encode(local.arm64_url)}' | base64 -d)" \
|
||||
ARG_SHA256="$(echo -n '${base64encode(local.sha256)}' | base64 -d)" \
|
||||
ARG_INSTALL_DIR="$(echo -n '${base64encode(local.install_dir)}' | base64 -d)" \
|
||||
/tmp/portabledesktop-install.sh
|
||||
EOT
|
||||
run_on_start = true
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
run "plan_with_required_vars" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "example-agent-id"
|
||||
}
|
||||
}
|
||||
|
||||
run "plan_with_custom_install_dir" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "example-agent-id"
|
||||
install_dir = "/opt/bin"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = resource.coder_script.portabledesktop.display_name == "Portable Desktop"
|
||||
error_message = "Expected coder_script resource to have correct display name"
|
||||
}
|
||||
}
|
||||
|
||||
run "plan_with_custom_url" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "example-agent-id"
|
||||
url = "https://example.com/custom-portabledesktop"
|
||||
sha256 = "abc123"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = resource.coder_script.portabledesktop.run_on_start == true
|
||||
error_message = "Expected coder_script to run on start"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env sh
|
||||
# shellcheck disable=SC2292
|
||||
# SC2292: We use [ ] instead of [[ ]] for POSIX sh compatibility.
|
||||
set -eu
|
||||
|
||||
error() {
|
||||
printf "ERROR: %s\n" "$@"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Check if portabledesktop is already in PATH.
|
||||
if command -v portabledesktop > /dev/null 2>&1; then
|
||||
printf "portabledesktop is already installed and in PATH.\n"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Determine the storage path.
|
||||
STORAGE_DIR="${CODER_SCRIPT_DATA_DIR}"
|
||||
BINARY_PATH="${STORAGE_DIR}/portabledesktop"
|
||||
mkdir -p "${STORAGE_DIR}"
|
||||
|
||||
# If the binary already exists and is executable, skip download.
|
||||
if [ -x "${BINARY_PATH}" ]; then
|
||||
printf "portabledesktop is already installed at %s, skipping download.\n" "${BINARY_PATH}"
|
||||
else
|
||||
# Detect architecture and select the appropriate download URL.
|
||||
ARCH=$(uname -m)
|
||||
case "${ARCH}" in
|
||||
x86_64)
|
||||
URL="${ARG_AMD64_URL}"
|
||||
;;
|
||||
aarch64)
|
||||
URL="${ARG_ARM64_URL}"
|
||||
;;
|
||||
*)
|
||||
error "Unsupported architecture: ${ARCH}"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Select download tool.
|
||||
if command -v curl > /dev/null 2>&1; then
|
||||
DOWNLOAD_CMD="curl"
|
||||
elif command -v wget > /dev/null 2>&1; then
|
||||
DOWNLOAD_CMD="wget"
|
||||
else
|
||||
error "No download tool available (curl or wget required)."
|
||||
fi
|
||||
|
||||
# Download with retry loop (3 attempts, 1s sleep between).
|
||||
TMPFILE=$(mktemp)
|
||||
MAX_ATTEMPTS=3
|
||||
DOWNLOAD_SUCCESS=false
|
||||
ATTEMPT=1
|
||||
|
||||
while [ "${ATTEMPT}" -le "${MAX_ATTEMPTS}" ]; do
|
||||
printf "Downloading portabledesktop (attempt %s/%s) via %s...\n" "${ATTEMPT}" "${MAX_ATTEMPTS}" "${DOWNLOAD_CMD}"
|
||||
|
||||
DOWNLOAD_OK=false
|
||||
if [ "${DOWNLOAD_CMD}" = "curl" ]; then
|
||||
curl -fsSL "${URL}" -o "${TMPFILE}" && DOWNLOAD_OK=true
|
||||
else
|
||||
wget -qO "${TMPFILE}" "${URL}" && DOWNLOAD_OK=true
|
||||
fi
|
||||
|
||||
if [ "${DOWNLOAD_OK}" = "true" ]; then
|
||||
# Verify checksum when ARG_SHA256 is non-empty.
|
||||
if [ -n "${ARG_SHA256}" ]; then
|
||||
CHECKSUM_MATCH=false
|
||||
if command -v sha256sum > /dev/null 2>&1; then
|
||||
echo "${ARG_SHA256} ${TMPFILE}" | sha256sum -c - > /dev/null 2>&1 && CHECKSUM_MATCH=true
|
||||
elif command -v shasum > /dev/null 2>&1; then
|
||||
echo "${ARG_SHA256} ${TMPFILE}" | shasum -a 256 -c - > /dev/null 2>&1 && CHECKSUM_MATCH=true
|
||||
else
|
||||
rm -f "${TMPFILE}"
|
||||
error "No SHA256 tool available (sha256sum or shasum required)."
|
||||
fi
|
||||
|
||||
if [ "${CHECKSUM_MATCH}" != "true" ]; then
|
||||
printf "WARNING: Checksum mismatch (attempt %s/%s): expected %s\n" \
|
||||
"${ATTEMPT}" "${MAX_ATTEMPTS}" "${ARG_SHA256}"
|
||||
rm -f "${TMPFILE}"
|
||||
if [ "${ATTEMPT}" -lt "${MAX_ATTEMPTS}" ]; then
|
||||
sleep 1
|
||||
fi
|
||||
ATTEMPT=$((ATTEMPT + 1))
|
||||
continue
|
||||
fi
|
||||
printf "Checksum verified successfully.\n"
|
||||
fi
|
||||
|
||||
DOWNLOAD_SUCCESS=true
|
||||
break
|
||||
else
|
||||
printf "WARNING: Download failed (attempt %s/%s).\n" "${ATTEMPT}" "${MAX_ATTEMPTS}"
|
||||
if [ "${ATTEMPT}" -lt "${MAX_ATTEMPTS}" ]; then
|
||||
sleep 1
|
||||
fi
|
||||
fi
|
||||
|
||||
ATTEMPT=$((ATTEMPT + 1))
|
||||
done
|
||||
|
||||
if [ "${DOWNLOAD_SUCCESS}" != "true" ]; then
|
||||
rm -f "${TMPFILE}"
|
||||
error "Failed to download portabledesktop after ${MAX_ATTEMPTS} attempts."
|
||||
fi
|
||||
|
||||
# Make the binary executable and move to storage path.
|
||||
chmod 755 "${TMPFILE}"
|
||||
mv "${TMPFILE}" "${BINARY_PATH}"
|
||||
fi
|
||||
|
||||
# Symlink into CODER_SCRIPT_BIN_DIR for PATH access.
|
||||
if [ -n "${CODER_SCRIPT_BIN_DIR}" ] && [ ! -e "${CODER_SCRIPT_BIN_DIR}/portabledesktop" ]; then
|
||||
ln -s "${CODER_SCRIPT_DATA_DIR}/portabledesktop" "${CODER_SCRIPT_BIN_DIR}/portabledesktop"
|
||||
fi
|
||||
|
||||
# If ARG_INSTALL_DIR is set, copy the binary there with sudo fallback.
|
||||
if [ -n "${ARG_INSTALL_DIR}" ]; then
|
||||
if [ ! -d "${ARG_INSTALL_DIR}" ]; then
|
||||
mkdir -p "${ARG_INSTALL_DIR}" 2> /dev/null || sudo mkdir -p "${ARG_INSTALL_DIR}" 2> /dev/null || true
|
||||
fi
|
||||
if cp "${CODER_SCRIPT_DATA_DIR}/portabledesktop" "${ARG_INSTALL_DIR}/portabledesktop" 2> /dev/null; then
|
||||
printf "Copied portabledesktop to %s.\n" "${ARG_INSTALL_DIR}/portabledesktop"
|
||||
elif sudo cp "${CODER_SCRIPT_DATA_DIR}/portabledesktop" "${ARG_INSTALL_DIR}/portabledesktop" 2> /dev/null; then
|
||||
printf "Copied portabledesktop to %s (via sudo).\n" "${ARG_INSTALL_DIR}/portabledesktop"
|
||||
else
|
||||
error "Failed to copy portabledesktop to ${ARG_INSTALL_DIR}/portabledesktop."
|
||||
fi
|
||||
fi
|
||||
|
||||
printf "portabledesktop installed successfully.\n"
|
||||
@@ -15,7 +15,7 @@ Automatically installs [Node.js](https://github.com/nodejs/node) via [`nvm`](htt
|
||||
module "nodejs" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/thezoker/nodejs/coder"
|
||||
version = "1.0.13"
|
||||
version = "1.1.0"
|
||||
agent_id = coder_agent.example.id
|
||||
}
|
||||
```
|
||||
@@ -28,17 +28,55 @@ This installs multiple versions of Node.js:
|
||||
module "nodejs" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/thezoker/nodejs/coder"
|
||||
version = "1.0.13"
|
||||
version = "1.1.0"
|
||||
agent_id = coder_agent.example.id
|
||||
node_versions = [
|
||||
"18",
|
||||
"20",
|
||||
"node"
|
||||
]
|
||||
default_node_version = "1.0.13"
|
||||
default_node_version = "20"
|
||||
}
|
||||
```
|
||||
|
||||
## Pre and Post Install Scripts
|
||||
|
||||
Use `pre_install_script` and `post_install_script` to run custom scripts before and after Node.js installation.
|
||||
|
||||
```tf
|
||||
module "nodejs" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/thezoker/nodejs/coder"
|
||||
version = "1.1.0"
|
||||
agent_id = coder_agent.example.id
|
||||
|
||||
pre_install_script = "echo 'Setting up prerequisites...'"
|
||||
post_install_script = "npm install -g yarn pnpm"
|
||||
}
|
||||
```
|
||||
|
||||
## Cross-Module Dependency Ordering
|
||||
|
||||
This module uses `coder exp sync` to coordinate execution ordering with other modules. It exposes the following outputs for use with `coder exp sync want`:
|
||||
|
||||
- `install_script_name` — the sync name for the main Node.js installation script
|
||||
- `pre_install_script_name` — the sync name for the pre-install script
|
||||
- `post_install_script_name` — the sync name for the post-install script
|
||||
|
||||
For example, to ensure another module waits for Node.js to be fully installed:
|
||||
|
||||
```tf
|
||||
module "nodejs" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/thezoker/nodejs/coder"
|
||||
version = "1.1.0"
|
||||
agent_id = coder_agent.example.id
|
||||
}
|
||||
|
||||
# In another module's coder_script, wait for Node.js installation:
|
||||
# coder exp sync want my-script ${module.nodejs[0].install_script_name}
|
||||
```
|
||||
|
||||
## Full example
|
||||
|
||||
A example with all available options:
|
||||
@@ -47,15 +85,17 @@ A example with all available options:
|
||||
module "nodejs" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/thezoker/nodejs/coder"
|
||||
version = "1.0.13"
|
||||
version = "1.1.0"
|
||||
agent_id = coder_agent.example.id
|
||||
nvm_version = "1.0.13"
|
||||
nvm_version = "v0.39.7"
|
||||
nvm_install_prefix = "/opt/nvm"
|
||||
node_versions = [
|
||||
"16",
|
||||
"18",
|
||||
"20",
|
||||
"node"
|
||||
]
|
||||
default_node_version = "1.0.13"
|
||||
default_node_version = "20"
|
||||
pre_install_script = "echo 'Pre-install setup'"
|
||||
post_install_script = "npm install -g typescript"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -38,15 +38,125 @@ variable "default_node_version" {
|
||||
default = "node"
|
||||
}
|
||||
|
||||
resource "coder_script" "nodejs" {
|
||||
agent_id = var.agent_id
|
||||
display_name = "Node.js:"
|
||||
script = templatefile("${path.module}/run.sh", {
|
||||
NVM_VERSION : var.nvm_version,
|
||||
INSTALL_PREFIX : var.nvm_install_prefix,
|
||||
NODE_VERSIONS : join(",", var.node_versions),
|
||||
DEFAULT : var.default_node_version,
|
||||
variable "pre_install_script" {
|
||||
type = string
|
||||
description = "Custom script to run before installing Node.js."
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "post_install_script" {
|
||||
type = string
|
||||
description = "Custom script to run after installing Node.js."
|
||||
default = null
|
||||
}
|
||||
|
||||
locals {
|
||||
encoded_pre_install_script = var.pre_install_script != null ? base64encode(var.pre_install_script) : ""
|
||||
encoded_post_install_script = var.post_install_script != null ? base64encode(var.post_install_script) : ""
|
||||
|
||||
install_script = templatefile("${path.module}/run.sh", {
|
||||
NVM_VERSION = var.nvm_version,
|
||||
INSTALL_PREFIX = var.nvm_install_prefix,
|
||||
NODE_VERSIONS = join(",", var.node_versions),
|
||||
DEFAULT = var.default_node_version,
|
||||
})
|
||||
encoded_install_script = base64encode(local.install_script)
|
||||
|
||||
pre_install_script_name = "nodejs-pre_install_script"
|
||||
install_script_name = "nodejs-install_script"
|
||||
post_install_script_name = "nodejs-post_install_script"
|
||||
|
||||
module_dir_path = "$HOME/.nodejs-module"
|
||||
|
||||
pre_install_path = "${local.module_dir_path}/pre_install.sh"
|
||||
pre_install_log_path = "${local.module_dir_path}/pre_install.log"
|
||||
install_path = "${local.module_dir_path}/install.sh"
|
||||
install_log_path = "${local.module_dir_path}/install.log"
|
||||
post_install_path = "${local.module_dir_path}/post_install.sh"
|
||||
post_install_log_path = "${local.module_dir_path}/post_install.log"
|
||||
}
|
||||
|
||||
resource "coder_script" "pre_install_script" {
|
||||
count = var.pre_install_script == null ? 0 : 1
|
||||
agent_id = var.agent_id
|
||||
display_name = "Node.js: Pre-Install"
|
||||
run_on_start = true
|
||||
script = <<-EOT
|
||||
#!/bin/bash
|
||||
set -o errexit
|
||||
set -o pipefail
|
||||
|
||||
mkdir -p ${local.module_dir_path}
|
||||
|
||||
trap 'coder exp sync complete ${local.pre_install_script_name}' EXIT
|
||||
coder exp sync start ${local.pre_install_script_name}
|
||||
|
||||
echo -n '${local.encoded_pre_install_script}' | base64 -d > ${local.pre_install_path}
|
||||
chmod +x ${local.pre_install_path}
|
||||
|
||||
${local.pre_install_path} 2>&1 | tee ${local.pre_install_log_path}
|
||||
EOT
|
||||
}
|
||||
|
||||
resource "coder_script" "nodejs" {
|
||||
agent_id = var.agent_id
|
||||
display_name = "Node.js: Install"
|
||||
script = <<-EOT
|
||||
#!/bin/bash
|
||||
set -o errexit
|
||||
set -o pipefail
|
||||
|
||||
mkdir -p ${local.module_dir_path}
|
||||
|
||||
trap 'coder exp sync complete ${local.install_script_name}' EXIT
|
||||
%{if var.pre_install_script != null~}
|
||||
coder exp sync want ${local.install_script_name} ${local.pre_install_script_name}
|
||||
%{endif~}
|
||||
coder exp sync start ${local.install_script_name}
|
||||
|
||||
echo -n '${local.encoded_install_script}' | base64 -d > ${local.install_path}
|
||||
chmod +x ${local.install_path}
|
||||
|
||||
${local.install_path} 2>&1 | tee ${local.install_log_path}
|
||||
EOT
|
||||
run_on_start = true
|
||||
start_blocks_login = true
|
||||
}
|
||||
|
||||
resource "coder_script" "post_install_script" {
|
||||
count = var.post_install_script != null ? 1 : 0
|
||||
agent_id = var.agent_id
|
||||
display_name = "Node.js: Post-Install"
|
||||
run_on_start = true
|
||||
script = <<-EOT
|
||||
#!/bin/bash
|
||||
set -o errexit
|
||||
set -o pipefail
|
||||
|
||||
mkdir -p ${local.module_dir_path}
|
||||
|
||||
trap 'coder exp sync complete ${local.post_install_script_name}' EXIT
|
||||
coder exp sync want ${local.post_install_script_name} ${local.install_script_name}
|
||||
coder exp sync start ${local.post_install_script_name}
|
||||
|
||||
echo -n '${local.encoded_post_install_script}' | base64 -d > ${local.post_install_path}
|
||||
chmod +x ${local.post_install_path}
|
||||
|
||||
${local.post_install_path} 2>&1 | tee ${local.post_install_log_path}
|
||||
EOT
|
||||
}
|
||||
|
||||
output "pre_install_script_name" {
|
||||
description = "The name of the pre-install script for coder exp sync coordination."
|
||||
value = local.pre_install_script_name
|
||||
}
|
||||
|
||||
output "install_script_name" {
|
||||
description = "The name of the install script for coder exp sync coordination."
|
||||
value = local.install_script_name
|
||||
}
|
||||
|
||||
output "post_install_script_name" {
|
||||
description = "The name of the post-install script for coder exp sync coordination."
|
||||
value = local.post_install_script_name
|
||||
}
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
run "test_nodejs_basic" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent-123"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.agent_id == "test-agent-123"
|
||||
error_message = "Agent ID variable should be set correctly"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.nvm_version == "master"
|
||||
error_message = "nvm_version should default to master"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.default_node_version == "node"
|
||||
error_message = "default_node_version should default to node"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.pre_install_script == null
|
||||
error_message = "pre_install_script should default to null"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.post_install_script == null
|
||||
error_message = "post_install_script should default to null"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = output.install_script_name == "nodejs-install_script"
|
||||
error_message = "install_script_name output should be set"
|
||||
}
|
||||
}
|
||||
|
||||
run "test_with_scripts" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent-scripts"
|
||||
pre_install_script = "echo 'Pre-install script'"
|
||||
post_install_script = "echo 'Post-install script'"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.pre_install_script == "echo 'Pre-install script'"
|
||||
error_message = "Pre-install script should be set correctly"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.post_install_script == "echo 'Post-install script'"
|
||||
error_message = "Post-install script should be set correctly"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = output.pre_install_script_name == "nodejs-pre_install_script"
|
||||
error_message = "pre_install_script_name output should be set"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = output.post_install_script_name == "nodejs-post_install_script"
|
||||
error_message = "post_install_script_name output should be set"
|
||||
}
|
||||
}
|
||||
|
||||
run "test_custom_options" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent-custom"
|
||||
nvm_version = "v0.39.7"
|
||||
nvm_install_prefix = ".custom-nvm"
|
||||
node_versions = ["18", "20", "node"]
|
||||
default_node_version = "20"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.nvm_version == "v0.39.7"
|
||||
error_message = "nvm_version should be set to v0.39.7"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.nvm_install_prefix == ".custom-nvm"
|
||||
error_message = "nvm_install_prefix should be set correctly"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = length(var.node_versions) == 3
|
||||
error_message = "node_versions should have 3 entries"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.default_node_version == "20"
|
||||
error_message = "default_node_version should be set to 20"
|
||||
}
|
||||
}
|
||||
|
||||
run "test_with_pre_install_only" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent-pre"
|
||||
pre_install_script = "echo 'pre-install'"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.pre_install_script != null
|
||||
error_message = "Pre-install script should be set"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.post_install_script == null
|
||||
error_message = "Post-install script should default to null"
|
||||
}
|
||||
}
|
||||
|
||||
run "test_with_post_install_only" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent-post"
|
||||
post_install_script = "echo 'post-install'"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.pre_install_script == null
|
||||
error_message = "Pre-install script should default to null"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.post_install_script != null
|
||||
error_message = "Post-install script should be set"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user