feat(registry/coder/modules/coder-utils): make install_script and start_script optional (#842)

Co-authored-by: Jay Kumar <jay.kumar@coder.com>
Co-authored-by: Atif Ali <atif@coder.com>
This commit is contained in:
35C4n0r 2026-04-22 23:23:38 +05:30 committed by GitHub
parent b108185c14
commit 39f332fcaf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 441 additions and 144 deletions

View File

@ -20,11 +20,11 @@ The Coder Utils module is a building block for modules that need to run multiple
```tf ```tf
module "coder_utils" { module "coder_utils" {
source = "registry.coder.com/coder/coder-utils/coder" source = "registry.coder.com/coder/coder-utils/coder"
version = "1.0.1" version = "1.1.0"
agent_id = coder_agent.main.id agent_id = coder_agent.main.id
agent_name = "myagent" agent_name = "myagent"
module_dir_name = ".my-module" module_directory = ".my-module"
pre_install_script = <<-EOT pre_install_script = <<-EOT
#!/bin/bash #!/bin/bash
@ -56,10 +56,30 @@ module "coder_utils" {
The module orchestrates scripts in the following order: The module orchestrates scripts in the following order:
1. **Log File Creation** - Creates module directory and log files 1. **Pre-Install Script** (optional) - Runs before installation
2. **Pre-Install Script** (optional) - Runs before installation 2. **Install Script** (required) - Main installation
3. **Install Script** - Main installation 3. **Post-Install Script** (optional) - Runs after installation
4. **Post-Install Script** (optional) - Runs after installation 4. **Start Script** (optional) - Starts the application
5. **Start Script** - Starts the application
Each script waits for its prerequisites to complete before running using `coder exp sync` dependency management. Each script waits for its prerequisites to complete before running using `coder exp sync` dependency management.
## Customizing Script Display
By default each `coder_script` renders in the Coder UI as plain "Install Script", "Pre-Install Script", etc. Downstream modules can brand them:
```tf
module "coder_utils" {
source = "registry.coder.com/coder/coder-utils/coder"
version = "1.1.0"
agent_id = coder_agent.main.id
agent_name = "myagent"
module_directory = ".my-module"
install_script = "echo installing"
display_name_prefix = "Claude Code" # yields "Claude Code: Install Script", etc.
icon = "/icon/claude.svg"
}
```
Both variables are optional. `display_name_prefix` defaults to `""` (no prefix), and `icon` defaults to `null` (use the Coder provider's default).

View File

@ -7,7 +7,7 @@ describe("coder-utils", async () => {
testRequiredVariables(import.meta.dir, { testRequiredVariables(import.meta.dir, {
agent_id: "test-agent-id", agent_id: "test-agent-id",
agent_name: "test-agent", agent_name: "test-agent",
module_dir_name: ".test-module", module_directory: ".test-module",
start_script: "echo 'start'", install_script: "echo 'install'",
}); });
}); });

View File

@ -29,7 +29,6 @@ variable "pre_install_script" {
variable "install_script" { variable "install_script" {
type = string type = string
description = "Script to install the agent used by AgentAPI." description = "Script to install the agent used by AgentAPI."
default = null
} }
variable "post_install_script" { variable "post_install_script" {
@ -41,6 +40,7 @@ variable "post_install_script" {
variable "start_script" { variable "start_script" {
type = string type = string
description = "Script that starts AgentAPI." description = "Script that starts AgentAPI."
default = null
} }
variable "agent_name" { variable "agent_name" {
@ -49,46 +49,67 @@ variable "agent_name" {
} }
variable "module_dir_name" { variable "module_directory" {
type = string type = string
description = "The name of the module directory." description = "The module's working directory for scripts and logs."
}
variable "display_name_prefix" {
type = string
description = "Prefix for each coder_script display_name. Example: setting 'Claude Code' yields 'Claude Code: Install Script', 'Claude Code: Pre-Install Script', etc. When unset, scripts show as plain 'Install Script'."
default = ""
}
variable "icon" {
type = string
description = "Icon shown in the Coder UI for every coder_script this module creates. Falls back to the Coder provider's default when unset."
default = null
} }
locals { locals {
encoded_pre_install_script = var.pre_install_script != null ? base64encode(var.pre_install_script) : "" 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_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" pre_install_script_name = "${var.agent_name}-pre_install_script"
install_script_name = "${var.agent_name}-install_script" install_script_name = "${var.agent_name}-install_script"
post_install_script_name = "${var.agent_name}-post_install_script" post_install_script_name = "${var.agent_name}-post_install_script"
start_script_name = "${var.agent_name}-start_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_path = "${local.module_dir_path}/pre_install.sh" pre_install_log_path = "${var.module_directory}/pre_install.log"
install_path = "${local.module_dir_path}/install.sh" install_log_path = "${var.module_directory}/install.log"
post_install_path = "${local.module_dir_path}/post_install.sh" post_install_log_path = "${var.module_directory}/post_install.log"
start_path = "${local.module_dir_path}/start.sh" start_log_path = "${var.module_directory}/start.log"
pre_install_log_path = "${local.module_dir_path}/pre_install.log" install_sync_deps = var.pre_install_script != null ? local.pre_install_script_name : null
install_log_path = "${local.module_dir_path}/install.log"
post_install_log_path = "${local.module_dir_path}/post_install.log" start_sync_deps = (
start_log_path = "${local.module_dir_path}/start.log" var.post_install_script != null
? "${local.install_script_name} ${local.post_install_script_name}"
: local.install_script_name
)
display_name_prefix = var.display_name_prefix != "" ? "${var.display_name_prefix}: " : ""
} }
resource "coder_script" "pre_install_script" { resource "coder_script" "pre_install_script" {
count = var.pre_install_script == null ? 0 : 1 count = var.pre_install_script == null ? 0 : 1
agent_id = var.agent_id agent_id = var.agent_id
display_name = "Pre-Install Script" display_name = "${local.display_name_prefix}Pre-Install Script"
icon = var.icon
run_on_start = true run_on_start = true
script = <<-EOT script = <<-EOT
#!/bin/bash #!/bin/bash
set -o errexit set -o errexit
set -o pipefail 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 trap 'coder exp sync complete ${local.pre_install_script_name}' EXIT
coder exp sync start ${local.pre_install_script_name} coder exp sync start ${local.pre_install_script_name}
@ -96,37 +117,39 @@ resource "coder_script" "pre_install_script" {
echo -n '${local.encoded_pre_install_script}' | base64 -d > ${local.pre_install_path} echo -n '${local.encoded_pre_install_script}' | base64 -d > ${local.pre_install_path}
chmod +x ${local.pre_install_path} chmod +x ${local.pre_install_path}
${local.pre_install_path} > ${local.pre_install_log_path} 2>&1 ${local.pre_install_path} 2>&1 | tee ${local.pre_install_log_path}
EOT EOT
} }
resource "coder_script" "install_script" { resource "coder_script" "install_script" {
agent_id = var.agent_id agent_id = var.agent_id
display_name = "Install Script" display_name = "${local.display_name_prefix}Install Script"
icon = var.icon
run_on_start = true run_on_start = true
script = <<-EOT script = <<-EOT
#!/bin/bash #!/bin/bash
set -o errexit set -o errexit
set -o pipefail set -o pipefail
mkdir -p ${local.module_dir_path} mkdir -p ${var.module_directory}
trap 'coder exp sync complete ${local.install_script_name}' EXIT trap 'coder exp sync complete ${local.install_script_name}' EXIT
%{if var.pre_install_script != null~} %{if local.install_sync_deps != null~}
coder exp sync want ${local.install_script_name} ${local.pre_install_script_name} coder exp sync want ${local.install_script_name} ${local.install_sync_deps}
%{endif~} %{endif~}
coder exp sync start ${local.install_script_name} coder exp sync start ${local.install_script_name}
echo -n '${local.encoded_install_script}' | base64 -d > ${local.install_path} echo -n '${local.encoded_install_script}' | base64 -d > ${local.install_path}
chmod +x ${local.install_path} chmod +x ${local.install_path}
${local.install_path} > ${local.install_log_path} 2>&1 ${local.install_path} 2>&1 | tee ${local.install_log_path}
EOT EOT
} }
resource "coder_script" "post_install_script" { resource "coder_script" "post_install_script" {
count = var.post_install_script != null ? 1 : 0 count = var.post_install_script != null ? 1 : 0
agent_id = var.agent_id agent_id = var.agent_id
display_name = "Post-Install Script" display_name = "${local.display_name_prefix}Post-Install Script"
icon = var.icon
run_on_start = true run_on_start = true
script = <<-EOT script = <<-EOT
#!/bin/bash #!/bin/bash
@ -140,13 +163,15 @@ resource "coder_script" "post_install_script" {
echo -n '${local.encoded_post_install_script}' | base64 -d > ${local.post_install_path} echo -n '${local.encoded_post_install_script}' | base64 -d > ${local.post_install_path}
chmod +x ${local.post_install_path} chmod +x ${local.post_install_path}
${local.post_install_path} > ${local.post_install_log_path} 2>&1 ${local.post_install_path} 2>&1 | tee ${local.post_install_log_path}
EOT EOT
} }
resource "coder_script" "start_script" { resource "coder_script" "start_script" {
count = var.start_script != null ? 1 : 0
agent_id = var.agent_id agent_id = var.agent_id
display_name = "Start Script" display_name = "${local.display_name_prefix}Start Script"
icon = var.icon
run_on_start = true run_on_start = true
script = <<-EOT script = <<-EOT
#!/bin/bash #!/bin/bash
@ -155,36 +180,28 @@ resource "coder_script" "start_script" {
trap 'coder exp sync complete ${local.start_script_name}' EXIT 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.start_sync_deps}
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 start ${local.start_script_name} coder exp sync start ${local.start_script_name}
echo -n '${local.encoded_start_script}' | base64 -d > ${local.start_path} echo -n '${local.encoded_start_script}' | base64 -d > ${local.start_path}
chmod +x ${local.start_path} chmod +x ${local.start_path}
${local.start_path} > ${local.start_log_path} 2>&1 ${local.start_path} 2>&1 | tee ${local.start_log_path}
EOT EOT
} }
output "pre_install_script_name" { # Filtered, run-order list of the `coder exp sync` names for every
description = "The name of the pre-install script for sync." # coder_script this module actually creates. Absent scripts (pre/post/start
value = local.pre_install_script_name # when their inputs are null) are omitted entirely, not padded with empty
} # strings. Downstream modules can use this with
# `coder exp sync want <self> <each of these>` to serialize their own
output "install_script_name" { # scripts behind the install pipeline.
description = "The name of the install script for sync." output "scripts" {
value = local.install_script_name description = "Ordered list of `coder exp sync` names for the coder_script resources this module creates, in the run order it enforces (pre_install, install, post_install, start). Scripts that were not configured are absent from the list."
} value = concat(
var.pre_install_script != null ? [local.pre_install_script_name] : [],
output "post_install_script_name" { [local.install_script_name],
description = "The name of the post-install script for sync." var.post_install_script != null ? [local.post_install_script_name] : [],
value = local.post_install_script_name var.start_script != null ? [local.start_script_name] : [],
} )
output "start_script_name" {
description = "The name of the start script for sync."
value = local.start_script_name
} }

View File

@ -7,7 +7,7 @@ run "test_with_all_scripts" {
variables { variables {
agent_id = "test-agent-id" agent_id = "test-agent-id"
agent_name = "test-agent" agent_name = "test-agent"
module_dir_name = ".test-module" module_directory = ".test-module"
pre_install_script = "echo 'pre-install'" pre_install_script = "echo 'pre-install'"
install_script = "echo 'install'" install_script = "echo 'install'"
post_install_script = "echo 'post-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" error_message = "Pre-install script should run on start"
} }
# Verify install_script is created # Verify install_script is always created
assert { assert {
condition = coder_script.install_script.agent_id == "test-agent-id" condition = coder_script.install_script.agent_id == "test-agent-id"
error_message = "Install script agent ID should match input" 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" 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 # Verify post_install_script is created when provided
assert { assert {
condition = length(coder_script.post_install_script) == 1 condition = length(coder_script.post_install_script) == 1
@ -72,98 +78,103 @@ run "test_with_all_scripts" {
error_message = "Post-install script should run on start" error_message = "Post-install script should run on start"
} }
# Verify start_script is created # Verify start_script is created when provided
assert { 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" error_message = "Start script agent ID should match input"
} }
assert { 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" error_message = "Start script should have correct display name"
} }
assert { 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" 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"
error_message = "Pre-install script name output should be correctly formatted"
}
assert {
condition = output.install_script_name == "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 correctly formatted"
}
assert {
condition = output.start_script_name == "test-agent-start_script"
error_message = "Start script name output should be correctly formatted"
}
} }
# Test with only required scripts (no pre/post install) # Test with only install_script (minimum required input)
run "test_without_optional_scripts" { run "test_install_only" {
command = plan command = plan
variables { variables {
agent_id = "test-agent-id" agent_id = "test-agent-id"
agent_name = "test-agent" agent_name = "test-agent"
module_dir_name = ".test-module" module_directory = ".test-module"
install_script = "echo 'install'"
}
# 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 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 = 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"
}
}
# 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'" install_script = "echo 'install'"
start_script = "echo 'start'" start_script = "echo 'start'"
} }
# Verify pre_install_script is NOT created when not provided
assert { assert {
condition = length(coder_script.pre_install_script) == 0 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 { assert {
condition = length(coder_script.post_install_script) == 0 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 { assert {
condition = coder_script.install_script.agent_id == "test-agent-id" condition = coder_script.install_script.agent_id == "test-agent-id"
error_message = "Install script should be created" error_message = "Install script should be created"
} }
assert { 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" error_message = "Start script should be created"
} }
# Verify outputs
assert { assert {
condition = output.pre_install_script_name == "test-agent-pre_install_script" condition = coder_script.start_script[0].agent_id == "test-agent-id"
error_message = "Pre-install script name output should be generated even when script is not created" error_message = "Start script agent ID should match input"
} }
# start should sync-want install (no post_install)
assert { assert {
condition = output.install_script_name == "test-agent-install_script" condition = can(regex("sync want test-agent-start_script test-agent-install_script", coder_script.start_script[0].script))
error_message = "Install script name output should be correctly formatted" error_message = "Start script should sync-want install_script"
}
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"
}
assert {
condition = output.start_script_name == "test-agent-start_script"
error_message = "Start script name output should be correctly formatted"
} }
} }
@ -174,12 +185,11 @@ run "test_with_mock_data" {
variables { variables {
agent_id = "mock-agent" agent_id = "mock-agent"
agent_name = "mock-agent" agent_name = "mock-agent"
module_dir_name = ".mock-module" module_directory = ".mock-module"
install_script = "echo 'install'" install_script = "echo 'install'"
start_script = "echo 'start'" start_script = "echo 'start'"
} }
# Mock the data sources for testing
override_data { override_data {
target = data.coder_workspace.me target = data.coder_workspace.me
values = { values = {
@ -212,14 +222,13 @@ run "test_with_mock_data" {
} }
} }
# Verify scripts are created with mocked data
assert { assert {
condition = coder_script.install_script.agent_id == "mock-agent" condition = coder_script.install_script.agent_id == "mock-agent"
error_message = "Install script should use the mocked agent ID" error_message = "Install script should use the mocked agent ID"
} }
assert { 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" error_message = "Start script should use the mocked agent ID"
} }
} }
@ -231,41 +240,292 @@ run "test_script_naming" {
variables { variables {
agent_id = "test-agent" agent_id = "test-agent"
agent_name = "custom-name" agent_name = "custom-name"
module_dir_name = ".test-module" module_directory = ".test-module"
install_script = "echo 'install'" install_script = "echo 'install'"
start_script = "echo 'start'" start_script = "echo 'start'"
} }
# Verify script names are constructed correctly
# The script should contain references to custom-name-* in the sync commands
assert { assert {
condition = can(regex("custom-name-install_script", coder_script.install_script.script)) condition = can(regex("custom-name-install_script", coder_script.install_script.script))
error_message = "Install script should use custom agent_name in sync commands" error_message = "Install script should use custom agent_name in sync commands"
} }
assert { 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" error_message = "Start script should use custom agent_name in sync commands"
} }
}
# Verify outputs use custom agent_name # Test install syncs with pre_install when provided
assert { run "test_install_syncs_with_pre_install" {
condition = output.pre_install_script_name == "custom-name-pre_install_script" command = plan
error_message = "Pre-install script name output should use custom agent_name"
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 { assert {
condition = output.install_script_name == "custom-name-install_script" condition = length(coder_script.pre_install_script) == 1
error_message = "Install script name output should use custom agent_name" error_message = "Pre-install script should be created"
} }
assert { assert {
condition = output.post_install_script_name == "custom-name-post_install_script" condition = can(regex("sync want test-agent-install_script test-agent-pre_install_script", coder_script.install_script.script))
error_message = "Post-install script name output should use custom agent_name" error_message = "Install script should sync-want pre_install_script"
} }
}
assert {
condition = output.start_script_name == "custom-name-start_script" # Test start script sync deps with post_install present
error_message = "Start script name output should use custom agent_name" 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"
}
}
# Verify display_name_prefix is prepended to every script's display_name
run "test_display_name_prefix_applied" {
command = plan
variables {
agent_id = "test-agent-id"
agent_name = "test-agent"
module_directory = ".test-module"
display_name_prefix = "Claude Code"
pre_install_script = "echo 'pre-install'"
install_script = "echo 'install'"
post_install_script = "echo 'post-install'"
start_script = "echo 'start'"
}
assert {
condition = coder_script.pre_install_script[0].display_name == "Claude Code: Pre-Install Script"
error_message = "Pre-install script display_name should be prefixed"
}
assert {
condition = coder_script.install_script.display_name == "Claude Code: Install Script"
error_message = "Install script display_name should be prefixed"
}
assert {
condition = coder_script.post_install_script[0].display_name == "Claude Code: Post-Install Script"
error_message = "Post-install script display_name should be prefixed"
}
assert {
condition = coder_script.start_script[0].display_name == "Claude Code: Start Script"
error_message = "Start script display_name should be prefixed"
}
}
# Verify icon is propagated to every coder_script
run "test_icon_applied" {
command = plan
variables {
agent_id = "test-agent-id"
agent_name = "test-agent"
module_directory = ".test-module"
icon = "/icon/claude.svg"
pre_install_script = "echo 'pre-install'"
install_script = "echo 'install'"
post_install_script = "echo 'post-install'"
start_script = "echo 'start'"
}
assert {
condition = coder_script.pre_install_script[0].icon == "/icon/claude.svg"
error_message = "Pre-install script icon should match input"
}
assert {
condition = coder_script.install_script.icon == "/icon/claude.svg"
error_message = "Install script icon should match input"
}
assert {
condition = coder_script.post_install_script[0].icon == "/icon/claude.svg"
error_message = "Post-install script icon should match input"
}
assert {
condition = coder_script.start_script[0].icon == "/icon/claude.svg"
error_message = "Start script icon should match input"
}
}
# Verify optional scripts are not created when their variables are unset
run "test_optional_scripts_absent_by_default" {
command = plan
variables {
agent_id = "test-agent-id"
agent_name = "test-agent"
module_directory = ".test-module"
install_script = "echo install"
}
assert {
condition = length(coder_script.pre_install_script) == 0
error_message = "Pre-install coder_script should not be created when pre_install_script is unset"
}
assert {
condition = length(coder_script.post_install_script) == 0
error_message = "Post-install coder_script should not be created when post_install_script is unset"
}
assert {
condition = length(coder_script.start_script) == 0
error_message = "Start coder_script should not be created when start_script is unset"
}
}
# Verify `scripts` output is a filtered, run-order list
run "test_scripts_output_with_all" {
command = plan
variables {
agent_id = "test-agent-id"
agent_name = "test-agent"
module_directory = ".test-module"
pre_install_script = "echo pre"
install_script = "echo install"
post_install_script = "echo post"
start_script = "echo start"
}
assert {
condition = length(output.scripts) == 4
error_message = "scripts should have 4 entries when every script is set"
}
assert {
condition = output.scripts[0] == "test-agent-pre_install_script"
error_message = "scripts[0] must be the pre-install name"
}
assert {
condition = output.scripts[1] == "test-agent-install_script"
error_message = "scripts[1] must be the install name"
}
assert {
condition = output.scripts[2] == "test-agent-post_install_script"
error_message = "scripts[2] must be the post-install name"
}
assert {
condition = output.scripts[3] == "test-agent-start_script"
error_message = "scripts[3] must be the start name"
}
}
run "test_scripts_output_with_install_only" {
command = plan
variables {
agent_id = "test-agent-id"
agent_name = "test-agent"
module_directory = ".test-module"
install_script = "echo install"
}
assert {
condition = length(output.scripts) == 1
error_message = "scripts should have exactly 1 entry (install) when pre/post/start are unset"
}
assert {
condition = output.scripts[0] == "test-agent-install_script"
error_message = "scripts[0] must be the install name"
}
}
run "test_scripts_output_with_install_and_post" {
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"
}
assert {
condition = length(output.scripts) == 2
error_message = "scripts should have 2 entries (install, post)"
}
assert {
condition = output.scripts[0] == "test-agent-install_script"
error_message = "scripts[0] must be the install name"
}
assert {
condition = output.scripts[1] == "test-agent-post_install_script"
error_message = "scripts[1] must be the post-install name"
}
}
# Every script must stream combined stdout+stderr to both the agent log
# (via stdout) and the on-disk log file (via tee), so workspace users
# watching `coder_script` output in the UI see progress live and can
# read the same content from the log file after the fact.
run "test_scripts_tee_stdout_and_log_file" {
command = plan
variables {
agent_id = "test-agent-id"
agent_name = "test-agent"
module_directory = ".test-module"
pre_install_script = "echo pre"
install_script = "echo install"
post_install_script = "echo post"
start_script = "echo start"
}
assert {
condition = can(regex("pre_install.sh 2>&1 \\| tee .*pre_install.log", coder_script.pre_install_script[0].script))
error_message = "pre_install wrapper must tee combined output to the log file and stdout"
}
assert {
condition = can(regex("install.sh 2>&1 \\| tee .*install.log", coder_script.install_script.script))
error_message = "install wrapper must tee combined output to the log file and stdout"
}
assert {
condition = can(regex("post_install.sh 2>&1 \\| tee .*post_install.log", coder_script.post_install_script[0].script))
error_message = "post_install wrapper must tee combined output to the log file and stdout"
}
assert {
condition = can(regex("start.sh 2>&1 \\| tee .*start.log", coder_script.start_script[0].script))
error_message = "start wrapper must tee combined output to the log file and stdout"
} }
} }