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
This commit is contained in:
blink-so[bot] 2026-03-19 17:59:22 +00:00
parent d0ef879e5f
commit 817238ea64
4 changed files with 142 additions and 21 deletions

View File

@ -55,6 +55,28 @@ module "nodejs" {
} }
``` ```
## 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 ## Full example
A example with all available options: A example with all available options:

View File

@ -50,17 +50,113 @@ variable "post_install_script" {
default = null 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" { resource "coder_script" "nodejs" {
agent_id = var.agent_id agent_id = var.agent_id
display_name = "Node.js:" display_name = "Node.js: Install"
script = templatefile("${path.module}/run.sh", { script = <<-EOT
NVM_VERSION : var.nvm_version, #!/bin/bash
INSTALL_PREFIX : var.nvm_install_prefix, set -o errexit
NODE_VERSIONS : join(",", var.node_versions), set -o pipefail
DEFAULT : var.default_node_version,
PRE_INSTALL_SCRIPT : var.pre_install_script != null ? var.pre_install_script : "", mkdir -p ${local.module_dir_path}
POST_INSTALL_SCRIPT : var.post_install_script != null ? var.post_install_script : "",
}) 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 run_on_start = true
start_blocks_login = 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
}

View File

@ -29,6 +29,11 @@ run "test_nodejs_basic" {
condition = var.post_install_script == null condition = var.post_install_script == null
error_message = "post_install_script should default to 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" { run "test_with_scripts" {
@ -49,6 +54,16 @@ run "test_with_scripts" {
condition = var.post_install_script == "echo 'Post-install script'" condition = var.post_install_script == "echo 'Post-install script'"
error_message = "Post-install script should be set correctly" 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" { run "test_custom_options" {

View File

@ -4,17 +4,10 @@ NVM_VERSION='${NVM_VERSION}'
NODE_VERSIONS='${NODE_VERSIONS}' NODE_VERSIONS='${NODE_VERSIONS}'
INSTALL_PREFIX='${INSTALL_PREFIX}' INSTALL_PREFIX='${INSTALL_PREFIX}'
DEFAULT='${DEFAULT}' DEFAULT='${DEFAULT}'
PRE_INSTALL_SCRIPT='${PRE_INSTALL_SCRIPT}'
POST_INSTALL_SCRIPT='${POST_INSTALL_SCRIPT}'
BOLD='\033[0;1m' BOLD='\033[0;1m'
CODE='\033[36;40;1m' CODE='\033[36;40;1m'
RESET='\033[0m' RESET='\033[0m'
if [ -n "$${PRE_INSTALL_SCRIPT}" ]; then
printf "$${BOLD}Running pre-install script...$${RESET}\n"
eval "$${PRE_INSTALL_SCRIPT}"
fi
printf "$${BOLD}Installing nvm!$${RESET}\n" printf "$${BOLD}Installing nvm!$${RESET}\n"
export NVM_DIR="$HOME/$${INSTALL_PREFIX}/nvm" export NVM_DIR="$HOME/$${INSTALL_PREFIX}/nvm"
@ -58,8 +51,3 @@ if [ -n "$${DEFAULT}" ]; then
printf "🛠️ Setting default node version $${CODE}$DEFAULT$${RESET}...\n" printf "🛠️ Setting default node version $${CODE}$DEFAULT$${RESET}...\n"
output=$(nvm alias default $DEFAULT 2>&1) output=$(nvm alias default $DEFAULT 2>&1)
fi fi
if [ -n "$${POST_INSTALL_SCRIPT}" ]; then
printf "$${BOLD}Running post-install script...$${RESET}\n"
eval "$${POST_INSTALL_SCRIPT}"
fi