Skip to content

Commit 2490fa8

Browse files
author
Jay Kumar
committed
refactor(agentapi): use coder-utils and tftpl for install script
Replace the inline coder_script resource with coder-utils module and convert scripts/main.sh to scripts/install.sh.tftpl, matching the pattern used by coder/claude-code and coder-labs/codex. Changes: - Replace coder_script.agentapi with module "coder_utils" (v0.0.1) - Convert scripts/main.sh to scripts/install.sh.tftpl using templatefile() for variable injection instead of ARG_* env vars - Keep coder_script.agentapi_shutdown as-is (coder-utils does not support run_on_stop) - Keep coder_app resources (web and CLI) unchanged - Add output "scripts" for downstream sync dependencies - Simplify agentapi.tftest.hcl assertions (install script content is now base64-encoded inside the coder-utils wrapper)
1 parent fb747ab commit 2490fa8

3 files changed

Lines changed: 78 additions & 94 deletions

File tree

registry/coder/modules/agentapi/agentapi.tftest.hcl

Lines changed: 11 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -27,22 +27,6 @@ run "default_values" {
2727
error_message = "pid_file_path should default to empty string"
2828
}
2929

30-
# Verify start script contains state persistence ARG_ vars.
31-
assert {
32-
condition = can(regex("ARG_ENABLE_STATE_PERSISTENCE", coder_script.agentapi.script))
33-
error_message = "start script should contain ARG_ENABLE_STATE_PERSISTENCE"
34-
}
35-
36-
assert {
37-
condition = can(regex("ARG_STATE_FILE_PATH", coder_script.agentapi.script))
38-
error_message = "start script should contain ARG_STATE_FILE_PATH"
39-
}
40-
41-
assert {
42-
condition = can(regex("ARG_PID_FILE_PATH", coder_script.agentapi.script))
43-
error_message = "start script should contain ARG_PID_FILE_PATH"
44-
}
45-
4630
# Verify shutdown script contains PID-related ARG_ vars.
4731
assert {
4832
condition = can(regex("ARG_PID_FILE_PATH", coder_script.agentapi_shutdown.script))
@@ -67,11 +51,10 @@ run "state_persistence_disabled" {
6751
error_message = "enable_state_persistence should be false"
6852
}
6953

70-
# Even when disabled, the ARG_ vars should still be in the script
71-
# (the shell script handles the conditional logic).
54+
# Verify shutdown script contains the disabled flag.
7255
assert {
73-
condition = can(regex("ARG_ENABLE_STATE_PERSISTENCE='false'", coder_script.agentapi.script))
74-
error_message = "start script should contain ARG_ENABLE_STATE_PERSISTENCE='false'"
56+
condition = can(regex("ARG_ENABLE_STATE_PERSISTENCE='false'", coder_script.agentapi_shutdown.script))
57+
error_message = "shutdown script should contain ARG_ENABLE_STATE_PERSISTENCE='false'"
7558
}
7659
}
7760

@@ -83,19 +66,18 @@ run "custom_paths" {
8366
pid_file_path = "/custom/agentapi.pid"
8467
}
8568

69+
# Verify custom paths appear in shutdown script.
8670
assert {
87-
condition = can(regex("/custom/state.json", coder_script.agentapi.script))
88-
error_message = "start script should contain custom state_file_path"
71+
condition = can(regex("/custom/agentapi.pid", coder_script.agentapi_shutdown.script))
72+
error_message = "shutdown script should contain custom pid_file_path"
8973
}
74+
}
9075

91-
assert {
92-
condition = can(regex("/custom/agentapi.pid", coder_script.agentapi.script))
93-
error_message = "start script should contain custom pid_file_path"
94-
}
76+
run "scripts_output" {
77+
command = plan
9578

96-
# Verify custom paths also appear in shutdown script.
9779
assert {
98-
condition = can(regex("/custom/agentapi.pid", coder_script.agentapi_shutdown.script))
99-
error_message = "shutdown script should contain custom pid_file_path"
80+
condition = length(output.scripts) == 1 && output.scripts[0] == "coder-agentapi-install_script"
81+
error_message = "scripts output should list the install script sync name"
10082
}
10183
}

registry/coder/modules/agentapi/main.tf

Lines changed: 31 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -173,54 +173,46 @@ locals {
173173
web_app = var.web_app || local.is_task
174174

175175
# we always trim the slash for consistency
176-
workdir = trimsuffix(var.folder, "/")
177-
agentapi_wait_for_start_script_b64 = base64encode(file("${path.module}/scripts/agentapi-wait-for-start.sh"))
176+
workdir = trimsuffix(var.folder, "/")
178177
// Chat base path is only set if not using a subdomain.
179178
// NOTE:
180179
// - Initial support for --chat-base-path was added in v0.3.1 but configuration
181180
// via environment variable AGENTAPI_CHAT_BASE_PATH was added in v0.3.3.
182181
// - As CODER_WORKSPACE_AGENT_NAME is a recent addition we use agent ID
183182
// for backward compatibility.
184183
agentapi_chat_base_path = var.agentapi_subdomain ? "" : "/@${data.coder_workspace_owner.me.name}/${data.coder_workspace.me.name}.${var.agent_id}/apps/${var.web_app_slug}/chat"
185-
main_script = file("${path.module}/scripts/main.sh")
186184
shutdown_script = file("${path.module}/scripts/agentapi-shutdown.sh")
187185
lib_script = file("${path.module}/scripts/lib.sh")
188186

189-
main_script_destination = "${var.module_directory}/main.sh"
190-
lib_script_destination = "${var.module_directory}/agentapi-lib.sh"
191187
shutdown_script_destination = "${var.module_directory}/agentapi-shutdown.sh"
192-
}
188+
lib_script_destination = "${var.module_directory}/agentapi-lib.sh"
193189

194-
resource "coder_script" "agentapi" {
195-
agent_id = var.agent_id
196-
display_name = "Install and start AgentAPI"
197-
icon = var.web_app_icon
198-
script = <<-EOT
199-
#!/bin/bash
200-
set -o errexit
201-
set -o pipefail
190+
install_script = templatefile("${path.module}/scripts/install.sh.tftpl", {
191+
ARG_MODULE_DIRECTORY = var.module_directory
192+
ARG_WORKDIR = local.workdir
193+
ARG_INSTALL_AGENTAPI = tostring(var.install_agentapi)
194+
ARG_AGENTAPI_VERSION = var.agentapi_version
195+
ARG_WAIT_FOR_START_SCRIPT = base64encode(file("${path.module}/scripts/agentapi-wait-for-start.sh"))
196+
ARG_AGENTAPI_PORT = tostring(var.agentapi_port)
197+
ARG_AGENTAPI_CHAT_BASE_PATH = local.agentapi_chat_base_path
198+
ARG_TASK_ID = try(data.coder_task.me.id, "")
199+
ARG_TASK_LOG_SNAPSHOT = tostring(var.task_log_snapshot)
200+
ARG_ENABLE_STATE_PERSISTENCE = tostring(var.enable_state_persistence)
201+
ARG_STATE_FILE_PATH = var.state_file_path
202+
ARG_PID_FILE_PATH = var.pid_file_path
203+
ARG_LIB_SCRIPT = base64encode(local.lib_script)
204+
})
205+
}
202206

203-
mkdir -p "${var.module_directory}"
204-
echo -n '${base64encode(local.main_script)}' | base64 -d > "${local.main_script_destination}"
205-
chmod +x "${local.main_script_destination}"
206-
echo -n '${base64encode(local.lib_script)}' | base64 -d > "${local.lib_script_destination}"
207+
module "coder_utils" {
208+
source = "registry.coder.com/coder/coder-utils/coder"
209+
version = "0.0.1"
207210

208-
ARG_MODULE_DIRECTORY='${var.module_directory}' \
209-
ARG_WORKDIR="$(echo -n '${base64encode(local.workdir)}' | base64 -d)" \
210-
ARG_INSTALL_AGENTAPI='${var.install_agentapi}' \
211-
ARG_AGENTAPI_VERSION='${var.agentapi_version}' \
212-
ARG_WAIT_FOR_START_SCRIPT="$(echo -n '${local.agentapi_wait_for_start_script_b64}' | base64 -d)" \
213-
ARG_AGENTAPI_PORT='${var.agentapi_port}' \
214-
ARG_AGENTAPI_CHAT_BASE_PATH='${local.agentapi_chat_base_path}' \
215-
ARG_TASK_ID='${try(data.coder_task.me.id, "")}' \
216-
ARG_TASK_LOG_SNAPSHOT='${var.task_log_snapshot}' \
217-
ARG_ENABLE_STATE_PERSISTENCE='${var.enable_state_persistence}' \
218-
ARG_STATE_FILE_PATH='${var.state_file_path}' \
219-
ARG_PID_FILE_PATH='${var.pid_file_path}' \
220-
ARG_LIB_SCRIPT_PATH="${local.lib_script_destination}" \
221-
"${local.main_script_destination}"
222-
EOT
223-
run_on_start = true
211+
agent_id = var.agent_id
212+
module_directory = var.module_directory
213+
display_name_prefix = "AgentAPI"
214+
icon = var.web_app_icon
215+
install_script = local.install_script
224216
}
225217

226218
resource "coder_script" "agentapi_shutdown" {
@@ -289,3 +281,8 @@ resource "coder_app" "agentapi_cli" {
289281
output "task_app_id" {
290282
value = local.web_app ? coder_app.agentapi_web[0].id : ""
291283
}
284+
285+
output "scripts" {
286+
description = "Ordered list of coder exp sync names for the coder_script resources this module creates, in run order. Scripts that were not configured are absent from the list."
287+
value = module.coder_utils.scripts
288+
}

registry/coder/modules/agentapi/scripts/main.sh renamed to registry/coder/modules/agentapi/scripts/install.sh.tftpl

Lines changed: 36 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,34 @@ set -e
33
set -x
44

55
set -o nounset
6-
MODULE_DIRECTORY="$ARG_MODULE_DIRECTORY"
7-
WORKDIR="$ARG_WORKDIR"
8-
INSTALL_AGENTAPI="$ARG_INSTALL_AGENTAPI"
9-
AGENTAPI_VERSION="$ARG_AGENTAPI_VERSION"
10-
WAIT_FOR_START_SCRIPT="$ARG_WAIT_FOR_START_SCRIPT"
11-
AGENTAPI_PORT="$ARG_AGENTAPI_PORT"
12-
AGENTAPI_CHAT_BASE_PATH="${ARG_AGENTAPI_CHAT_BASE_PATH:-}"
13-
TASK_ID="${ARG_TASK_ID:-}"
14-
TASK_LOG_SNAPSHOT="${ARG_TASK_LOG_SNAPSHOT:-true}"
15-
ENABLE_STATE_PERSISTENCE="${ARG_ENABLE_STATE_PERSISTENCE:-false}"
16-
STATE_FILE_PATH="${ARG_STATE_FILE_PATH:-}"
17-
PID_FILE_PATH="${ARG_PID_FILE_PATH:-}"
18-
LIB_SCRIPT_PATH="$ARG_LIB_SCRIPT_PATH"
6+
7+
MODULE_DIRECTORY='${ARG_MODULE_DIRECTORY}'
8+
WORKDIR='${ARG_WORKDIR}'
9+
INSTALL_AGENTAPI='${ARG_INSTALL_AGENTAPI}'
10+
AGENTAPI_VERSION='${ARG_AGENTAPI_VERSION}'
11+
WAIT_FOR_START_SCRIPT=$(echo -n '${ARG_WAIT_FOR_START_SCRIPT}' | base64 -d)
12+
AGENTAPI_PORT='${ARG_AGENTAPI_PORT}'
13+
AGENTAPI_CHAT_BASE_PATH='${ARG_AGENTAPI_CHAT_BASE_PATH}'
14+
TASK_ID='${ARG_TASK_ID}'
15+
TASK_LOG_SNAPSHOT='${ARG_TASK_LOG_SNAPSHOT}'
16+
ENABLE_STATE_PERSISTENCE='${ARG_ENABLE_STATE_PERSISTENCE}'
17+
STATE_FILE_PATH='${ARG_STATE_FILE_PATH}'
18+
PID_FILE_PATH='${ARG_PID_FILE_PATH}'
19+
LIB_SCRIPT=$(echo -n '${ARG_LIB_SCRIPT}' | base64 -d)
20+
1921
set +o nounset
2022

23+
# Write and source lib.sh
24+
LIB_SCRIPT_PATH="$${MODULE_DIRECTORY}/agentapi-lib.sh"
25+
echo -n "$LIB_SCRIPT" > "$LIB_SCRIPT_PATH"
2126
# shellcheck source=lib.sh
22-
source "${LIB_SCRIPT_PATH}"
27+
source "$LIB_SCRIPT_PATH"
2328

2429
command_exists() {
2530
command -v "$1" > /dev/null 2>&1
2631
}
2732

28-
mkdir -p "${MODULE_DIRECTORY}/scripts"
33+
mkdir -p "$${MODULE_DIRECTORY}/scripts"
2934

3035
# Check for jq dependency if task log snapshot is enabled.
3136
if [[ $TASK_LOG_SNAPSHOT == true ]] && [[ -n $TASK_ID ]]; then
@@ -34,14 +39,14 @@ if [[ $TASK_LOG_SNAPSHOT == true ]] && [[ -n $TASK_ID ]]; then
3439
echo "Install jq to enable log snapshot functionality when the workspace stops."
3540
fi
3641
fi
37-
if [ ! -d "${WORKDIR}" ]; then
38-
echo "Warning: The specified folder '${WORKDIR}' does not exist."
42+
if [ ! -d "$${WORKDIR}" ]; then
43+
echo "Warning: The specified folder '$${WORKDIR}' does not exist."
3944
echo "Creating the folder..."
40-
mkdir -p "${WORKDIR}"
45+
mkdir -p "$${WORKDIR}"
4146
echo "Folder created successfully."
4247
fi
4348
# Install AgentAPI if enabled
44-
if [ "${INSTALL_AGENTAPI}" = "true" ]; then
49+
if [ "$${INSTALL_AGENTAPI}" = "true" ]; then
4550
echo "Installing AgentAPI..."
4651
arch=$(uname -m)
4752
if [ "$arch" = "x86_64" ]; then
@@ -52,12 +57,12 @@ if [ "${INSTALL_AGENTAPI}" = "true" ]; then
5257
echo "Error: Unsupported architecture: $arch"
5358
exit 1
5459
fi
55-
if [ "${AGENTAPI_VERSION}" = "latest" ]; then
60+
if [ "$${AGENTAPI_VERSION}" = "latest" ]; then
5661
# for the latest release the download URL pattern is different than for tagged releases
5762
# https://docs.github.com/en/repositories/releasing-projects-on-github/linking-to-releases
5863
download_url="https://github.com/coder/agentapi/releases/latest/download/$binary_name"
5964
else
60-
download_url="https://github.com/coder/agentapi/releases/download/${AGENTAPI_VERSION}/$binary_name"
65+
download_url="https://github.com/coder/agentapi/releases/download/$${AGENTAPI_VERSION}/$binary_name"
6166
fi
6267
curl \
6368
--retry 5 \
@@ -76,30 +81,30 @@ if ! command_exists agentapi; then
7681
exit 1
7782
fi
7883

79-
echo -n "${WAIT_FOR_START_SCRIPT}" > "${MODULE_DIRECTORY}/scripts/agentapi-wait-for-start.sh"
80-
chmod +x "${MODULE_DIRECTORY}/scripts/agentapi-wait-for-start.sh"
84+
echo -n "$${WAIT_FOR_START_SCRIPT}" > "$${MODULE_DIRECTORY}/scripts/agentapi-wait-for-start.sh"
85+
chmod +x "$${MODULE_DIRECTORY}/scripts/agentapi-wait-for-start.sh"
8186

8287
export LANG=en_US.UTF-8
8388
export LC_ALL=en_US.UTF-8
8489

85-
cd "${WORKDIR}"
90+
cd "$${WORKDIR}"
8691

87-
export AGENTAPI_CHAT_BASE_PATH="${AGENTAPI_CHAT_BASE_PATH:-}"
92+
export AGENTAPI_CHAT_BASE_PATH="$${AGENTAPI_CHAT_BASE_PATH:-}"
8893
# Disable host header check since AgentAPI is proxied by Coder (which does its own validation)
8994
export AGENTAPI_ALLOWED_HOSTS="*"
9095

91-
export AGENTAPI_PID_FILE="${PID_FILE_PATH:-${MODULE_DIRECTORY}/agentapi.pid}"
96+
export AGENTAPI_PID_FILE="$${PID_FILE_PATH:-$${MODULE_DIRECTORY}/agentapi.pid}"
9297
# Only set state env vars when persistence is enabled and the binary supports
9398
# it. State persistence requires agentapi >= v0.12.0.
94-
if [ "${ENABLE_STATE_PERSISTENCE}" = "true" ]; then
99+
if [ "$${ENABLE_STATE_PERSISTENCE}" = "true" ]; then
95100
actual_version=$(agentapi_version)
96101
if version_at_least 0.12.0 "$actual_version"; then
97-
export AGENTAPI_STATE_FILE="${STATE_FILE_PATH:-${MODULE_DIRECTORY}/agentapi-state.json}"
102+
export AGENTAPI_STATE_FILE="$${STATE_FILE_PATH:-$${MODULE_DIRECTORY}/agentapi-state.json}"
98103
export AGENTAPI_SAVE_STATE="true"
99104
export AGENTAPI_LOAD_STATE="true"
100105
else
101-
echo "Warning: State persistence requires agentapi >= v0.12.0 (current: ${actual_version:-unknown}), skipping."
106+
echo "Warning: State persistence requires agentapi >= v0.12.0 (current: $${actual_version:-unknown}), skipping."
102107
fi
103108
fi
104-
nohup "${MODULE_DIRECTORY}/scripts/agentapi-start.sh" true "${AGENTAPI_PORT}" &> "${MODULE_DIRECTORY}/agentapi-start.log" &
105-
"${MODULE_DIRECTORY}/scripts/agentapi-wait-for-start.sh" "${AGENTAPI_PORT}"
109+
nohup "$${MODULE_DIRECTORY}/scripts/agentapi-start.sh" true "$${AGENTAPI_PORT}" &> "$${MODULE_DIRECTORY}/agentapi-start.log" &
110+
"$${MODULE_DIRECTORY}/scripts/agentapi-wait-for-start.sh" "$${AGENTAPI_PORT}"

0 commit comments

Comments
 (0)