## Summary
Derives `coder-utils` script names from `module_directory` instead of a
separate `agent_name` input. The `module_directory` already encodes both
the namespace and the module name, so carrying both is redundant and
error-prone. Callers like `claude-code` no longer need to pass
`agent_name`.
Scripts this module materializes lose the `${agent_name}-utils-` prefix
because `module_directory` already namespaces them per-caller.
We will address multiple instances of coder-utils per caller in a future
iteration if needed.
## Versioning Note
Previous tags (`v1.0.0` through `v1.3.0`) have been deleted because no
published module ever consumed them — the module was effectively
unreleased. This PR ships the first real public version as **`v0.0.1`**,
treating it as a fresh start rather than a breaking bump from a version
that was never in production use.
## Changes
- Remove `agent_name` variable.
- Derive `caller_name = "${namespace}-${module_name}"` from
`module_directory`.
- Validate `module_directory` matches
`$HOME/.coder-modules/<namespace>/<module-name>`.
- Rename script files on disk from `${agent_name}-utils-<phase>.sh` to
plain `<phase>.sh`.
- Add a TS test for the `module_directory` validation.
- Ship as `v0.0.1` (first published version; all prior tags removed).
## Breaking Changes
| Before | After |
|---|---|
| `agent_name = "myagent"` | removed (derived from `module_directory`) |
| `module_directory = ".my-module"` | `module_directory =
"$HOME/.coder-modules/<ns>/<name>"` (validated) |
| Script files `${agent_name}-utils-install.sh` | `install.sh` |
| Script sync names `${agent_name}-install_script` |
`${namespace}-${module_name}-install_script` |
No callers were depending on the old format (prior tags were
unpublished).
## Validation
- `terraform fmt -recursive` clean
- `terraform validate` clean
- `terraform test` → 17/17 pass
- `bun test registry/coder/modules/coder-utils` → 5/5 pass
- `prettier --check` clean
## Consumer
coder/registry#861 (`claude-code`) consumes this and is currently pinned
to the commit SHA until this merges and ships as `v0.0.1`.
> 🤖 This PR was created with the help of Coder Agents, and needs a human
review. 🧑💻
217 lines
7.5 KiB
HCL
217 lines
7.5 KiB
HCL
terraform {
|
|
required_version = ">= 1.0"
|
|
|
|
required_providers {
|
|
coder = {
|
|
source = "coder/coder"
|
|
version = ">= 2.13"
|
|
}
|
|
}
|
|
}
|
|
|
|
variable "agent_id" {
|
|
type = string
|
|
description = "The ID of a Coder agent."
|
|
}
|
|
|
|
data "coder_workspace" "me" {}
|
|
|
|
data "coder_workspace_owner" "me" {}
|
|
|
|
data "coder_task" "me" {}
|
|
|
|
variable "pre_install_script" {
|
|
type = string
|
|
description = "Custom script to run before installing the agent used by AgentAPI."
|
|
default = null
|
|
}
|
|
|
|
variable "install_script" {
|
|
type = string
|
|
description = "Script to install the agent used by AgentAPI."
|
|
}
|
|
|
|
variable "post_install_script" {
|
|
type = string
|
|
description = "Custom script to run after installing the agent used by AgentAPI."
|
|
default = null
|
|
}
|
|
|
|
variable "start_script" {
|
|
type = string
|
|
description = "Script that starts AgentAPI."
|
|
default = null
|
|
}
|
|
|
|
variable "module_directory" {
|
|
type = string
|
|
description = "The calling module's working directory. Must follow the pattern '$HOME/.coder-modules/<namespace>/<module-name>'."
|
|
|
|
validation {
|
|
condition = can(regex("^\\$HOME/\\.coder-modules/[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+$", var.module_directory))
|
|
error_message = "module_directory must match the pattern '$HOME/.coder-modules/<namespace>/<module-name>' (e.g. '$HOME/.coder-modules/coder/claude-code')."
|
|
}
|
|
}
|
|
|
|
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 {
|
|
path_parts = split("/", var.module_directory)
|
|
caller_name = "${local.path_parts[length(local.path_parts) - 2]}-${local.path_parts[length(local.path_parts) - 1]}"
|
|
|
|
encoded_pre_install_script = var.pre_install_script != null ? base64encode(var.pre_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 = var.start_script != null ? base64encode(var.start_script) : ""
|
|
|
|
pre_install_script_name = "${local.caller_name}-pre_install_script"
|
|
install_script_name = "${local.caller_name}-install_script"
|
|
post_install_script_name = "${local.caller_name}-post_install_script"
|
|
start_script_name = "${local.caller_name}-start_script"
|
|
|
|
pre_install_path = "${local.scripts_directory}/pre_install.sh"
|
|
install_path = "${local.scripts_directory}/install.sh"
|
|
post_install_path = "${local.scripts_directory}/post_install.sh"
|
|
start_path = "${local.scripts_directory}/start.sh"
|
|
|
|
pre_install_log_path = "${local.log_directory}/pre_install.log"
|
|
install_log_path = "${local.log_directory}/install.log"
|
|
post_install_log_path = "${local.log_directory}/post_install.log"
|
|
start_log_path = "${local.log_directory}/start.log"
|
|
|
|
scripts_directory = "${var.module_directory}/scripts"
|
|
log_directory = "${var.module_directory}/logs"
|
|
|
|
install_sync_deps = var.pre_install_script != null ? local.pre_install_script_name : null
|
|
|
|
start_sync_deps = (
|
|
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" {
|
|
count = var.pre_install_script == null ? 0 : 1
|
|
agent_id = var.agent_id
|
|
display_name = "${local.display_name_prefix}Pre-Install Script"
|
|
icon = var.icon
|
|
run_on_start = true
|
|
script = <<-EOT
|
|
#!/bin/bash
|
|
set -o errexit
|
|
set -o pipefail
|
|
|
|
mkdir -p ${var.module_directory}
|
|
mkdir -p ${local.scripts_directory}
|
|
mkdir -p ${local.log_directory}
|
|
|
|
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" "install_script" {
|
|
agent_id = var.agent_id
|
|
display_name = "${local.display_name_prefix}Install Script"
|
|
icon = var.icon
|
|
run_on_start = true
|
|
script = <<-EOT
|
|
#!/bin/bash
|
|
set -o errexit
|
|
set -o pipefail
|
|
|
|
mkdir -p ${var.module_directory}
|
|
mkdir -p ${local.scripts_directory}
|
|
mkdir -p ${local.log_directory}
|
|
|
|
trap 'coder exp sync complete ${local.install_script_name}' EXIT
|
|
%{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}
|
|
chmod +x ${local.install_path}
|
|
|
|
${local.install_path} 2>&1 | tee ${local.install_log_path}
|
|
EOT
|
|
}
|
|
|
|
resource "coder_script" "post_install_script" {
|
|
count = var.post_install_script != null ? 1 : 0
|
|
agent_id = var.agent_id
|
|
display_name = "${local.display_name_prefix}Post-Install Script"
|
|
icon = var.icon
|
|
run_on_start = true
|
|
script = <<-EOT
|
|
#!/bin/bash
|
|
set -o errexit
|
|
set -o pipefail
|
|
|
|
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
|
|
}
|
|
|
|
resource "coder_script" "start_script" {
|
|
count = var.start_script != null ? 1 : 0
|
|
agent_id = var.agent_id
|
|
display_name = "${local.display_name_prefix}Start Script"
|
|
icon = var.icon
|
|
run_on_start = true
|
|
script = <<-EOT
|
|
#!/bin/bash
|
|
set -o errexit
|
|
set -o pipefail
|
|
|
|
trap 'coder exp sync complete ${local.start_script_name}' EXIT
|
|
|
|
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}
|
|
chmod +x ${local.start_path}
|
|
|
|
${local.start_path} 2>&1 | tee ${local.start_log_path}
|
|
EOT
|
|
}
|
|
|
|
# Filtered, run-order list of the `coder exp sync` names for every
|
|
# coder_script this module actually creates. Absent scripts (pre/post/start
|
|
# 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
|
|
# scripts behind the install pipeline.
|
|
output "scripts" {
|
|
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] : [],
|
|
[local.install_script_name],
|
|
var.post_install_script != null ? [local.post_install_script_name] : [],
|
|
var.start_script != null ? [local.start_script_name] : [],
|
|
)
|
|
}
|