diff --git a/registry/coder/modules/coder-utils/README.md b/registry/coder/modules/coder-utils/README.md index 25ae28544..073d00c13 100644 --- a/registry/coder/modules/coder-utils/README.md +++ b/registry/coder/modules/coder-utils/README.md @@ -20,7 +20,7 @@ The Coder Utils module is a building block for modules that need to run multiple ```tf module "coder_utils" { source = "registry.coder.com/coder/coder-utils/coder" - version = "1.0.1" + version = "1.1.0" agent_id = coder_agent.main.id agent_name = "myagent" @@ -56,10 +56,9 @@ module "coder_utils" { The module orchestrates scripts in the following order: -1. **Log File Creation** - Creates module directory and log files -2. **Pre-Install Script** (optional) - Runs before installation -3. **Install Script** - Main installation -4. **Post-Install Script** (optional) - Runs after installation -5. **Start Script** - Starts the application +1. **Pre-Install Script** (optional) - Runs before installation +2. **Install Script** (required) - Main installation +3. **Post-Install Script** (optional) - Runs after installation +4. **Start Script** (optional) - Starts the application Each script waits for its prerequisites to complete before running using `coder exp sync` dependency management. diff --git a/registry/coder/modules/coder-utils/main.test.ts b/registry/coder/modules/coder-utils/main.test.ts index 5987c3288..a7e8e2e9f 100644 --- a/registry/coder/modules/coder-utils/main.test.ts +++ b/registry/coder/modules/coder-utils/main.test.ts @@ -7,7 +7,7 @@ describe("coder-utils", async () => { testRequiredVariables(import.meta.dir, { agent_id: "test-agent-id", agent_name: "test-agent", - module_dir_name: ".test-module", - start_script: "echo 'start'", + module_directory: ".test-module", + install_script: "echo 'install'", }); }); diff --git a/registry/coder/modules/coder-utils/main.tf b/registry/coder/modules/coder-utils/main.tf index cfb8b778a..20b383d14 100644 --- a/registry/coder/modules/coder-utils/main.tf +++ b/registry/coder/modules/coder-utils/main.tf @@ -29,7 +29,6 @@ variable "pre_install_script" { variable "install_script" { type = string description = "Script to install the agent used by AgentAPI." - default = null } variable "post_install_script" { @@ -41,6 +40,7 @@ variable "post_install_script" { variable "start_script" { type = string description = "Script that starts AgentAPI." + default = null } variable "agent_name" { @@ -49,33 +49,39 @@ variable "agent_name" { } -variable "module_dir_name" { +variable "module_directory" { type = string - description = "The name of the module directory." + description = "The module's working directory for scripts and logs." } locals { encoded_pre_install_script = var.pre_install_script != null ? base64encode(var.pre_install_script) : "" - encoded_install_script = var.install_script != null ? base64encode(var.install_script) : "" + encoded_install_script = base64encode(var.install_script) encoded_post_install_script = var.post_install_script != null ? base64encode(var.post_install_script) : "" - encoded_start_script = base64encode(var.start_script) + encoded_start_script = var.start_script != null ? base64encode(var.start_script) : "" pre_install_script_name = "${var.agent_name}-pre_install_script" install_script_name = "${var.agent_name}-install_script" post_install_script_name = "${var.agent_name}-post_install_script" start_script_name = "${var.agent_name}-start_script" - module_dir_path = "$HOME/${var.module_dir_name}" + pre_install_path = "${var.module_directory}/pre_install.sh" + install_path = "${var.module_directory}/install.sh" + post_install_path = "${var.module_directory}/post_install.sh" + start_path = "${var.module_directory}/start.sh" + + pre_install_log_path = "${var.module_directory}/pre_install.log" + install_log_path = "${var.module_directory}/install.log" + post_install_log_path = "${var.module_directory}/post_install.log" + start_log_path = "${var.module_directory}/start.log" - pre_install_path = "${local.module_dir_path}/pre_install.sh" - install_path = "${local.module_dir_path}/install.sh" - post_install_path = "${local.module_dir_path}/post_install.sh" - start_path = "${local.module_dir_path}/start.sh" + install_sync_deps = var.pre_install_script != null ? local.pre_install_script_name : null - pre_install_log_path = "${local.module_dir_path}/pre_install.log" - install_log_path = "${local.module_dir_path}/install.log" - post_install_log_path = "${local.module_dir_path}/post_install.log" - start_log_path = "${local.module_dir_path}/start.log" + start_sync_deps = ( + var.post_install_script != null + ? "${local.install_script_name} ${local.post_install_script_name}" + : local.install_script_name + ) } resource "coder_script" "pre_install_script" { @@ -88,7 +94,7 @@ resource "coder_script" "pre_install_script" { set -o errexit set -o pipefail - mkdir -p ${local.module_dir_path} + mkdir -p ${var.module_directory} trap 'coder exp sync complete ${local.pre_install_script_name}' EXIT coder exp sync start ${local.pre_install_script_name} @@ -109,11 +115,11 @@ resource "coder_script" "install_script" { set -o errexit set -o pipefail - mkdir -p ${local.module_dir_path} + mkdir -p ${var.module_directory} 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} + %{if local.install_sync_deps != null~} + coder exp sync want ${local.install_script_name} ${local.install_sync_deps} %{endif~} coder exp sync start ${local.install_script_name} echo -n '${local.encoded_install_script}' | base64 -d > ${local.install_path} @@ -145,6 +151,7 @@ resource "coder_script" "post_install_script" { } resource "coder_script" "start_script" { + count = var.start_script != null ? 1 : 0 agent_id = var.agent_id display_name = "Start Script" run_on_start = true @@ -155,11 +162,7 @@ resource "coder_script" "start_script" { trap 'coder exp sync complete ${local.start_script_name}' EXIT - %{if var.post_install_script != null~} - coder exp sync want ${local.start_script_name} ${local.install_script_name} ${local.post_install_script_name} - %{else~} - coder exp sync want ${local.start_script_name} ${local.install_script_name} - %{endif~} + coder exp sync want ${local.start_script_name} ${local.start_sync_deps} coder exp sync start ${local.start_script_name} echo -n '${local.encoded_start_script}' | base64 -d > ${local.start_path} @@ -169,22 +172,12 @@ resource "coder_script" "start_script" { EOT } -output "pre_install_script_name" { - description = "The name of the pre-install script for sync." - value = local.pre_install_script_name -} - -output "install_script_name" { - description = "The name of the install script for sync." - value = local.install_script_name -} - -output "post_install_script_name" { - description = "The name of the post-install script for sync." - value = local.post_install_script_name -} - -output "start_script_name" { - description = "The name of the start script for sync." - value = local.start_script_name +output "script_names" { + description = "The names of the scripts for sync." + value = { + pre_install = var.pre_install_script != null ? local.pre_install_script_name : "" + install = local.install_script_name + post_install = var.post_install_script != null ? local.post_install_script_name : "" + start = var.start_script != null ? local.start_script_name : "" + } } \ No newline at end of file diff --git a/registry/coder/modules/coder-utils/main.tftest.hcl b/registry/coder/modules/coder-utils/main.tftest.hcl index d1228a6d4..0e3aa9ebb 100644 --- a/registry/coder/modules/coder-utils/main.tftest.hcl +++ b/registry/coder/modules/coder-utils/main.tftest.hcl @@ -7,7 +7,7 @@ run "test_with_all_scripts" { variables { agent_id = "test-agent-id" agent_name = "test-agent" - module_dir_name = ".test-module" + module_directory = ".test-module" pre_install_script = "echo 'pre-install'" install_script = "echo 'install'" post_install_script = "echo 'post-install'" @@ -35,7 +35,7 @@ run "test_with_all_scripts" { error_message = "Pre-install script should run on start" } - # Verify install_script is created + # Verify install_script is always created assert { condition = coder_script.install_script.agent_id == "test-agent-id" error_message = "Install script agent ID should match input" @@ -51,6 +51,12 @@ run "test_with_all_scripts" { error_message = "Install script should run on start" } + # install should sync-want pre_install + assert { + condition = can(regex("sync want test-agent-install_script test-agent-pre_install_script", coder_script.install_script.script)) + error_message = "Install script should sync-want pre_install_script when pre_install is provided" + } + # Verify post_install_script is created when provided assert { condition = length(coder_script.post_install_script) == 1 @@ -72,97 +78,164 @@ run "test_with_all_scripts" { error_message = "Post-install script should run on start" } - # Verify start_script is created + # Verify start_script is created when provided assert { - condition = coder_script.start_script.agent_id == "test-agent-id" + condition = length(coder_script.start_script) == 1 + error_message = "Start script should be created when start_script is provided" + } + + assert { + condition = coder_script.start_script[0].agent_id == "test-agent-id" error_message = "Start script agent ID should match input" } assert { - condition = coder_script.start_script.display_name == "Start Script" + condition = coder_script.start_script[0].display_name == "Start Script" error_message = "Start script should have correct display name" } assert { - condition = coder_script.start_script.run_on_start == true + condition = coder_script.start_script[0].run_on_start == true error_message = "Start script should run on start" } # Verify outputs for script names assert { - condition = output.pre_install_script_name == "test-agent-pre_install_script" + condition = output.script_names.pre_install == "test-agent-pre_install_script" error_message = "Pre-install script name output should be correctly formatted" } assert { - condition = output.install_script_name == "test-agent-install_script" + condition = output.script_names.install == "test-agent-install_script" error_message = "Install script name output should be correctly formatted" } assert { - condition = output.post_install_script_name == "test-agent-post_install_script" + condition = output.script_names.post_install == "test-agent-post_install_script" error_message = "Post-install script name output should be correctly formatted" } assert { - condition = output.start_script_name == "test-agent-start_script" + condition = output.script_names.start == "test-agent-start_script" error_message = "Start script name output should be correctly formatted" } } -# Test with only required scripts (no pre/post install) -run "test_without_optional_scripts" { +# Test with only install_script (minimum required input) +run "test_install_only" { command = plan variables { - agent_id = "test-agent-id" - agent_name = "test-agent" - module_dir_name = ".test-module" - install_script = "echo 'install'" - start_script = "echo 'start'" + agent_id = "test-agent-id" + agent_name = "test-agent" + module_directory = ".test-module" + install_script = "echo 'install'" } - # Verify pre_install_script is NOT created when not provided + # Verify optional scripts are NOT created assert { condition = length(coder_script.pre_install_script) == 0 - error_message = "Pre-install script should not be created when pre_install_script is null" + error_message = "Pre-install script should not be created when not provided" } - # Verify post_install_script is NOT created when not provided assert { condition = length(coder_script.post_install_script) == 0 - error_message = "Post-install script should not be created when post_install_script is null" + error_message = "Post-install script should not be created when not provided" } - # Verify required scripts are still created + assert { + condition = length(coder_script.start_script) == 0 + error_message = "Start script should not be created when not provided" + } + + # Verify install_script is created assert { condition = coder_script.install_script.agent_id == "test-agent-id" error_message = "Install script should be created" } + # Verify outputs + assert { + condition = output.script_names.pre_install == "" + error_message = "Pre-install script name output should be empty" + } + + assert { + condition = output.script_names.install == "test-agent-install_script" + error_message = "Install script name output should be correctly formatted" + } + + assert { + condition = output.script_names.post_install == "" + error_message = "Post-install script name output should be empty" + } + assert { - condition = coder_script.start_script.agent_id == "test-agent-id" + condition = output.script_names.start == "" + error_message = "Start script name output should be empty" + } +} + +# Test with install and start scripts (no pre/post install) +run "test_install_and_start" { + command = plan + + variables { + agent_id = "test-agent-id" + agent_name = "test-agent" + module_directory = ".test-module" + install_script = "echo 'install'" + start_script = "echo 'start'" + } + + assert { + condition = length(coder_script.pre_install_script) == 0 + error_message = "Pre-install script should not be created when not provided" + } + + assert { + condition = length(coder_script.post_install_script) == 0 + error_message = "Post-install script should not be created when not provided" + } + + assert { + condition = coder_script.install_script.agent_id == "test-agent-id" + error_message = "Install script should be created" + } + + assert { + condition = length(coder_script.start_script) == 1 error_message = "Start script should be created" } - # Verify outputs assert { - condition = output.pre_install_script_name == "test-agent-pre_install_script" - error_message = "Pre-install script name output should be generated even when script is not created" + condition = coder_script.start_script[0].agent_id == "test-agent-id" + error_message = "Start script agent ID should match input" + } + + # start should sync-want install (no post_install) + assert { + condition = can(regex("sync want test-agent-start_script test-agent-install_script", coder_script.start_script[0].script)) + error_message = "Start script should sync-want install_script" } assert { - condition = output.install_script_name == "test-agent-install_script" + condition = output.script_names.pre_install == "" + error_message = "Pre-install script name output should be empty" + } + + assert { + condition = output.script_names.install == "test-agent-install_script" error_message = "Install script name output should be correctly formatted" } assert { - condition = output.post_install_script_name == "test-agent-post_install_script" - error_message = "Post-install script name output should be generated even when script is not created" + condition = output.script_names.post_install == "" + error_message = "Post-install script name output should be empty" } assert { - condition = output.start_script_name == "test-agent-start_script" + condition = output.script_names.start == "test-agent-start_script" error_message = "Start script name output should be correctly formatted" } } @@ -172,14 +245,13 @@ run "test_with_mock_data" { command = plan variables { - agent_id = "mock-agent" - agent_name = "mock-agent" - module_dir_name = ".mock-module" - install_script = "echo 'install'" - start_script = "echo 'start'" + agent_id = "mock-agent" + agent_name = "mock-agent" + module_directory = ".mock-module" + install_script = "echo 'install'" + start_script = "echo 'start'" } - # Mock the data sources for testing override_data { target = data.coder_workspace.me values = { @@ -212,14 +284,13 @@ run "test_with_mock_data" { } } - # Verify scripts are created with mocked data assert { condition = coder_script.install_script.agent_id == "mock-agent" error_message = "Install script should use the mocked agent ID" } assert { - condition = coder_script.start_script.agent_id == "mock-agent" + condition = coder_script.start_script[0].agent_id == "mock-agent" error_message = "Start script should use the mocked agent ID" } } @@ -229,43 +300,94 @@ run "test_script_naming" { command = plan variables { - agent_id = "test-agent" - agent_name = "custom-name" - module_dir_name = ".test-module" - install_script = "echo 'install'" - start_script = "echo 'start'" + agent_id = "test-agent" + agent_name = "custom-name" + module_directory = ".test-module" + install_script = "echo 'install'" + start_script = "echo 'start'" } - # Verify script names are constructed correctly - # The script should contain references to custom-name-* in the sync commands assert { condition = can(regex("custom-name-install_script", coder_script.install_script.script)) error_message = "Install script should use custom agent_name in sync commands" } assert { - condition = can(regex("custom-name-start_script", coder_script.start_script.script)) + condition = can(regex("custom-name-start_script", coder_script.start_script[0].script)) error_message = "Start script should use custom agent_name in sync commands" } - # Verify outputs use custom agent_name assert { - condition = output.pre_install_script_name == "custom-name-pre_install_script" - error_message = "Pre-install script name output should use custom agent_name" + condition = output.script_names.pre_install == "" + error_message = "Pre-install script name output should be empty when not provided" } assert { - condition = output.install_script_name == "custom-name-install_script" + condition = output.script_names.install == "custom-name-install_script" error_message = "Install script name output should use custom agent_name" } assert { - condition = output.post_install_script_name == "custom-name-post_install_script" - error_message = "Post-install script name output should use custom agent_name" + condition = output.script_names.post_install == "" + error_message = "Post-install script name output should be empty when not provided" } assert { - condition = output.start_script_name == "custom-name-start_script" + condition = output.script_names.start == "custom-name-start_script" error_message = "Start script name output should use custom agent_name" } } + +# Test install syncs with pre_install when provided +run "test_install_syncs_with_pre_install" { + command = plan + + variables { + agent_id = "test-agent-id" + agent_name = "test-agent" + module_directory = ".test-module" + pre_install_script = "echo 'pre-install'" + install_script = "echo 'install'" + } + + assert { + condition = length(coder_script.pre_install_script) == 1 + error_message = "Pre-install script should be created" + } + + assert { + condition = can(regex("sync want test-agent-install_script test-agent-pre_install_script", coder_script.install_script.script)) + error_message = "Install script should sync-want pre_install_script" + } + + assert { + condition = output.script_names.pre_install == "test-agent-pre_install_script" + error_message = "Pre-install script name output should be set" + } +} + +# Test start script sync deps with post_install present +run "test_start_syncs_with_post_install" { + command = plan + + variables { + agent_id = "test-agent-id" + agent_name = "test-agent" + module_directory = ".test-module" + install_script = "echo 'install'" + post_install_script = "echo 'post-install'" + start_script = "echo 'start'" + } + + # start should sync-want both install and post_install + assert { + condition = can(regex("sync want test-agent-start_script test-agent-install_script test-agent-post_install_script", coder_script.start_script[0].script)) + error_message = "Start script should sync-want both install_script and post_install_script" + } + + # post_install should sync-want install + assert { + condition = can(regex("sync want test-agent-post_install_script test-agent-install_script", coder_script.post_install_script[0].script)) + error_message = "Post-install script should sync-want install_script" + } +}