diff --git a/registry/thezoker/modules/nodejs/README.md b/registry/thezoker/modules/nodejs/README.md index d7293aacd..1c3e4eac6 100644 --- a/registry/thezoker/modules/nodejs/README.md +++ b/registry/thezoker/modules/nodejs/README.md @@ -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" } ``` diff --git a/registry/thezoker/modules/nodejs/main.tf b/registry/thezoker/modules/nodejs/main.tf index 9c9c5c760..08a77c91e 100644 --- a/registry/thezoker/modules/nodejs/main.tf +++ b/registry/thezoker/modules/nodejs/main.tf @@ -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 +} diff --git a/registry/thezoker/modules/nodejs/nodejs.tftest.hcl b/registry/thezoker/modules/nodejs/nodejs.tftest.hcl new file mode 100644 index 000000000..1897eed03 --- /dev/null +++ b/registry/thezoker/modules/nodejs/nodejs.tftest.hcl @@ -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" + } +}