Compare commits

..

8 Commits

Author SHA1 Message Date
DevCats 11a64e9b08 Merge branch 'main' into feat/nodejs-pre-post-install-scripts 2026-03-20 08:11:52 -05:00
blink-so[bot] 0fd0d08d2f fix: terraform fmt alignment in coder_script.nodejs resource 2026-03-19 18:02:37 +00:00
blink-so[bot] 817238ea64 refactor(nodejs): use base64 encoding and coder exp sync for pre/post install scripts
- Base64 encode pre/post install scripts to safely handle special characters
- Use separate coder_script resources for pre_install, install, and post_install
- Add coder exp sync want/start/complete for execution ordering
- Base64 encode the main install script (run.sh) via templatefile + base64encode
- Revert run.sh to original (no pre/post install handling)
- Add sync name outputs for cross-module dependency coordination
- Update README with cross-module coordination documentation
- Add output assertions to tests
2026-03-19 17:59:22 +00:00
blink-so[bot] d0ef879e5f revert: restore unrelated comment in main.test.ts 2026-03-19 13:24:41 +00:00
blink-so[bot] 872fdbcf35 fix: bump version to 1.1.0 (minor) for new input variables 2026-03-19 13:23:26 +00:00
blink-so[bot] acdbd5777f fix: align terraform blocks to pass prettier formatting 2026-03-13 14:24:57 +00:00
blink-so[bot] 8bd24f8b44 simplify: just add pre/post install script variables inline in run.sh
Remove all the coder exp sync machinery, separate script resources,
locals, and outputs. Simply pass the scripts as template variables
into run.sh and eval them before/after the nvm install.
2026-03-13 14:23:14 +00:00
blink-so[bot] 79cef2ecfc feat(nodejs): add pre and post install scripts with coder exp sync support
Add pre_install_script and post_install_script variables to the nodejs
module following the pattern used by other registry modules (agent-helper,
claude-code, aider, etc.). Scripts use coder exp sync for reliable
execution ordering, enabling dependency coordination between modules.

Changes:
- Add pre_install_script and post_install_script optional variables
- Wrap install script with coder exp sync want/start/complete
- Add conditional pre/post install coder_script resources
- Export sync script names as outputs for cross-module coordination
- Add nodejs.tftest.hcl with 5 test cases
- Update README with pre/post install documentation and examples
- Bump version references to 1.0.14
2026-03-13 14:07:52 +00:00
17 changed files with 355 additions and 99 deletions
+3 -3
View File
@@ -37,7 +37,7 @@ jobs:
all:
- '**'
- name: Set up Terraform
uses: coder/coder/.github/actions/setup-tf@f7650296ceb9b020c79cd525ac7bd3c7f252ae1d # v2.31.6
uses: coder/coder/.github/actions/setup-tf@1a774ab7ce99063a2e01beb94de3fcbccaf84dbe # v2.31.5
- name: Set up Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
@@ -87,7 +87,7 @@ jobs:
bun-version: latest
# Need Terraform for its formatter
- name: Install Terraform
uses: coder/coder/.github/actions/setup-tf@f7650296ceb9b020c79cd525ac7bd3c7f252ae1d # v2.31.6
uses: coder/coder/.github/actions/setup-tf@1a774ab7ce99063a2e01beb94de3fcbccaf84dbe # v2.31.5
- name: Install dependencies
run: bun install
- name: Validate formatting
@@ -106,7 +106,7 @@ jobs:
- name: Check out code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version: "1.24.0"
- name: Validate contributors
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version: stable
- name: golangci-lint
+1 -1
View File
@@ -31,7 +31,7 @@ jobs:
bun-version: latest
- name: Set up Terraform
uses: coder/coder/.github/actions/setup-tf@f7650296ceb9b020c79cd525ac7bd3c7f252ae1d # v2.31.6
uses: coder/coder/.github/actions/setup-tf@1a774ab7ce99063a2e01beb94de3fcbccaf84dbe # v2.31.5
- name: Install dependencies
run: bun install
@@ -13,7 +13,7 @@ Run [OpenCode](https://opencode.ai) AI coding assistant in your workspace for in
```tf
module "opencode" {
source = "registry.coder.com/coder-labs/opencode/coder"
version = "0.1.2"
version = "0.1.1"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
}
@@ -34,7 +34,7 @@ resource "coder_ai_task" "task" {
module "opencode" {
source = "registry.coder.com/coder-labs/opencode/coder"
version = "0.1.2"
version = "0.1.1"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
@@ -89,7 +89,7 @@ Run OpenCode as a command-line tool without web interface or task reporting:
```tf
module "opencode" {
source = "registry.coder.com/coder-labs/opencode/coder"
version = "0.1.2"
version = "0.1.1"
agent_id = coder_agent.main.id
workdir = "/home/coder"
report_tasks = false
@@ -39,7 +39,7 @@ install_opencode() {
if [ "$ARG_OPENCODE_VERSION" = "latest" ]; then
curl -fsSL https://opencode.ai/install | bash
else
curl -fsSL https://opencode.ai/install | VERSION="${ARG_OPENCODE_VERSION}" bash
VERSION=$ARG_OPENCODE_VERSION curl -fsSL https://opencode.ai/install | bash
fi
export PATH=/home/coder/.opencode/bin:$PATH
printf "Opencode location: %s\n" "$(which opencode)"
+9 -9
View File
@@ -13,7 +13,7 @@ Run the [Claude Code](https://docs.anthropic.com/en/docs/agents-and-tools/claude
```tf
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.8.2"
version = "4.8.1"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
claude_api_key = "xxxx-xxxxx-xxxx"
@@ -60,7 +60,7 @@ By default, when `enable_boundary = true`, the module uses `coder boundary` subc
```tf
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.8.2"
version = "4.8.1"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
enable_boundary = true
@@ -81,7 +81,7 @@ For tasks integration with AI Bridge, add `enable_aibridge = true` to the [Usage
```tf
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.8.2"
version = "4.8.1"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
enable_aibridge = true
@@ -110,7 +110,7 @@ data "coder_task" "me" {}
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.8.2"
version = "4.8.1"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
ai_prompt = data.coder_task.me.prompt
@@ -133,7 +133,7 @@ This example shows additional configuration options for version pinning, custom
```tf
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.8.2"
version = "4.8.1"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
@@ -189,7 +189,7 @@ Run and configure Claude Code as a standalone CLI in your workspace.
```tf
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.8.2"
version = "4.8.1"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
install_claude_code = true
@@ -211,7 +211,7 @@ variable "claude_code_oauth_token" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.8.2"
version = "4.8.1"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
claude_code_oauth_token = var.claude_code_oauth_token
@@ -284,7 +284,7 @@ resource "coder_env" "bedrock_api_key" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.8.2"
version = "4.8.1"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
model = "global.anthropic.claude-sonnet-4-5-20250929-v1:0"
@@ -341,7 +341,7 @@ resource "coder_env" "google_application_credentials" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.8.2"
version = "4.8.1"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
model = "claude-sonnet-4@20250514"
+1 -1
View File
@@ -287,7 +287,7 @@ resource "coder_env" "claude_code_oauth_token" {
}
resource "coder_env" "claude_api_key" {
count = (var.enable_aibridge || (var.claude_api_key != "")) ? 1 : 0
count = local.claude_api_key != "" ? 1 : 0
agent_id = var.agent_id
name = "CLAUDE_API_KEY"
@@ -416,6 +416,7 @@ run "test_disable_state_persistence" {
}
}
run "test_no_api_key_no_env" {
command = plan
@@ -430,18 +431,3 @@ run "test_no_api_key_no_env" {
error_message = "CLAUDE_API_KEY should not be created when no API key is provided and aibridge is disabled"
}
}
run "test_api_key_count_with_aibridge_no_override" {
command = plan
variables {
agent_id = "test-agent-count"
workdir = "/home/coder/test"
enable_aibridge = true
}
assert {
condition = length(coder_env.claude_api_key) == 1
error_message = "CLAUDE_API_KEY env should be created when aibridge is enabled, regardless of session_token value"
}
}
+9 -9
View File
@@ -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.4.4"
version = "1.4.3"
agent_id = coder_agent.example.id
}
```
@@ -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.4.4"
version = "1.4.3"
agent_id = coder_agent.example.id
install_version = "4.106.3"
}
@@ -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.4.4"
version = "1.4.3"
agent_id = coder_agent.example.id
extensions = [
"dracula-theme.theme-dracula"
@@ -61,7 +61,7 @@ Configure VS Code's [settings.json](https://code.visualstudio.com/docs/getstarte
module "code-server" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/code-server/coder"
version = "1.4.4"
version = "1.4.3"
agent_id = coder_agent.example.id
extensions = ["dracula-theme.theme-dracula"]
settings = {
@@ -72,13 +72,13 @@ module "code-server" {
### Install multiple extensions
Install multiple extensions from [OpenVSX](https://open-vsx.org/) by adding them to the `extensions` list:
Just run code-server in the background, don't fetch it from GitHub:
```tf
module "code-server" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/code-server/coder"
version = "1.4.4"
version = "1.4.3"
agent_id = coder_agent.example.id
extensions = ["dracula-theme.theme-dracula", "ms-azuretools.vscode-docker"]
}
@@ -92,7 +92,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.4.4"
version = "1.4.3"
agent_id = coder_agent.example.id
additional_args = "--disable-workspace-trust"
}
@@ -108,7 +108,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.4.4"
version = "1.4.3"
agent_id = coder_agent.example.id
use_cached = true
extensions = ["dracula-theme.theme-dracula", "ms-azuretools.vscode-docker"]
@@ -121,7 +121,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.4.4"
version = "1.4.3"
agent_id = coder_agent.example.id
offline = true
}
+6 -6
View File
@@ -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.4.1"
version = "1.4.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.4.1"
version = "1.4.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.4.1"
version = "1.4.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.4.1"
version = "1.4.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.4.1"
version = "1.4.0"
agent_id = coder_agent.example.id
user = "root"
dotfiles_uri = module.dotfiles.dotfiles_uri
@@ -90,7 +90,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.4.1"
version = "1.4.0"
agent_id = coder_agent.example.id
default_dotfiles_uri = "https://github.com/coder/dotfiles"
}
@@ -56,21 +56,6 @@ describe("dotfiles", async () => {
}
});
it("command uses bash for fish shell compatibility", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
manual_update: "true",
dotfiles_uri: "https://github.com/test/dotfiles",
});
const app = state.resources.find(
(r) => r.type === "coder_app" && r.name === "dotfiles",
);
expect(app).toBeDefined();
expect(app?.instances[0]?.attributes?.command).toContain("/bin/bash -c");
});
it("set custom order for coder_parameter", async () => {
const order = 99;
const state = await runTerraformApply(import.meta.dir, {
+2 -2
View File
@@ -164,12 +164,12 @@ resource "coder_app" "dotfiles" {
icon = "/icon/dotfiles.svg"
order = var.order
group = var.group
command = "/bin/bash -c \"$(echo ${base64encode(templatefile("${path.module}/run.sh", {
command = templatefile("${path.module}/run.sh", {
DOTFILES_URI : local.dotfiles_uri,
DOTFILES_USER : local.user,
DOTFILES_BRANCH : local.dotfiles_branch,
POST_CLONE_SCRIPT : local.encoded_post_clone_script
}))} | base64 -d)\""
})
}
output "dotfiles_uri" {
+7 -7
View File
@@ -14,7 +14,7 @@ This module adds JetBrains IDE buttons to launch IDEs directly from the dashboar
module "jetbrains" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/jetbrains/coder"
version = "1.3.1"
version = "1.3.0"
agent_id = coder_agent.main.id
folder = "/home/coder/project"
}
@@ -39,7 +39,7 @@ When `default` contains IDE codes, those IDEs are created directly without user
module "jetbrains" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/jetbrains/coder"
version = "1.3.1"
version = "1.3.0"
agent_id = coder_agent.main.id
folder = "/home/coder/project"
default = ["PY", "IU"] # Pre-configure PyCharm and IntelliJ IDEA
@@ -52,7 +52,7 @@ module "jetbrains" {
module "jetbrains" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/jetbrains/coder"
version = "1.3.1"
version = "1.3.0"
agent_id = coder_agent.main.id
folder = "/home/coder/project"
# Show parameter with limited options
@@ -66,7 +66,7 @@ module "jetbrains" {
module "jetbrains" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/jetbrains/coder"
version = "1.3.1"
version = "1.3.0"
agent_id = coder_agent.main.id
folder = "/home/coder/project"
default = ["IU", "PY"]
@@ -81,7 +81,7 @@ module "jetbrains" {
module "jetbrains" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/jetbrains/coder"
version = "1.3.1"
version = "1.3.0"
agent_id = coder_agent.main.id
folder = "/workspace/project"
@@ -108,7 +108,7 @@ module "jetbrains" {
module "jetbrains_pycharm" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/jetbrains/coder"
version = "1.3.1"
version = "1.3.0"
agent_id = coder_agent.main.id
folder = "/workspace/project"
@@ -128,7 +128,7 @@ Add helpful tooltip text that appears when users hover over the IDE app buttons:
module "jetbrains" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/jetbrains/coder"
version = "1.3.1"
version = "1.3.0"
agent_id = coder_agent.main.id
folder = "/home/coder/project"
default = ["IU", "PY"]
+9 -11
View File
@@ -125,7 +125,7 @@ variable "download_base_link" {
}
data "http" "jetbrains_ide_versions" {
for_each = local.selected_ides
for_each = length(var.default) == 0 ? var.options : var.default
url = "${var.releases_base_link}/products/releases?code=${each.key}&type=${var.channel}${var.major_version == "latest" ? "&latest=true" : ""}"
}
@@ -174,14 +174,9 @@ variable "ide_config" {
}
locals {
# Determine the user's actual IDE selection.
# This is computed before the HTTP data source so that version lookups
# are only performed for IDEs the user chose — not every option.
selected_ides = length(var.default) == 0 ? toset(jsondecode(coalesce(data.coder_parameter.jetbrains_ides[0].value, "[]"))) : toset(var.default)
# Parse HTTP responses once with error handling for air-gapped environments
parsed_responses = {
for code in local.selected_ides : code => try(
for code in length(var.default) == 0 ? var.options : var.default : code => try(
jsondecode(data.http.jetbrains_ide_versions[code].response_body),
{} # Return empty object if API call fails
)
@@ -189,7 +184,7 @@ locals {
# Filter the parsed response for the requested major version if not "latest"
filtered_releases = {
for code in local.selected_ides : code => [
for code in length(var.default) == 0 ? var.options : var.default : code => [
for r in try(local.parsed_responses[code][keys(local.parsed_responses[code])[0]], []) :
r if var.major_version == "latest" || r.majorVersion == var.major_version
]
@@ -197,13 +192,13 @@ locals {
# Select the latest release for the requested major version (first item in the filtered list)
selected_releases = {
for code in local.selected_ides : code =>
for code in length(var.default) == 0 ? var.options : var.default : code =>
length(local.filtered_releases[code]) > 0 ? local.filtered_releases[code][0] : null
}
# Dynamically generate IDE configurations based on selected IDEs with fallback to ide_config
# Dynamically generate IDE configurations based on options with fallback to ide_config
options_metadata = {
for code in local.selected_ides : code => {
for code in length(var.default) == 0 ? var.options : var.default : code => {
icon = var.ide_config[code].icon
name = var.ide_config[code].name
identifier = code
@@ -216,6 +211,9 @@ locals {
json_data = local.selected_releases[code]
}
}
# Convert the parameter value to a set for for_each
selected_ides = length(var.default) == 0 ? toset(jsondecode(coalesce(data.coder_parameter.jetbrains_ides[0].value, "[]"))) : toset(var.default)
}
data "coder_parameter" "jetbrains_ides" {
+47 -7
View File
@@ -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"
}
```
+118 -8
View File
@@ -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"
}
}