diff --git a/registry/coder/modules/agentapi/agentapi.tftest.hcl b/registry/coder/modules/agentapi/agentapi.tftest.hcl index 0179d426..c4ea2fcf 100644 --- a/registry/coder/modules/agentapi/agentapi.tftest.hcl +++ b/registry/coder/modules/agentapi/agentapi.tftest.hcl @@ -27,22 +27,6 @@ run "default_values" { error_message = "pid_file_path should default to empty string" } - # Verify start script contains state persistence ARG_ vars. - assert { - condition = can(regex("ARG_ENABLE_STATE_PERSISTENCE", coder_script.agentapi.script)) - error_message = "start script should contain ARG_ENABLE_STATE_PERSISTENCE" - } - - assert { - condition = can(regex("ARG_STATE_FILE_PATH", coder_script.agentapi.script)) - error_message = "start script should contain ARG_STATE_FILE_PATH" - } - - assert { - condition = can(regex("ARG_PID_FILE_PATH", coder_script.agentapi.script)) - error_message = "start script should contain ARG_PID_FILE_PATH" - } - # Verify shutdown script contains PID-related ARG_ vars. assert { condition = can(regex("ARG_PID_FILE_PATH", coder_script.agentapi_shutdown.script)) @@ -67,11 +51,10 @@ run "state_persistence_disabled" { error_message = "enable_state_persistence should be false" } - # Even when disabled, the ARG_ vars should still be in the script - # (the shell script handles the conditional logic). + # Verify shutdown script contains the disabled flag. assert { - condition = can(regex("ARG_ENABLE_STATE_PERSISTENCE='false'", coder_script.agentapi.script)) - error_message = "start script should contain ARG_ENABLE_STATE_PERSISTENCE='false'" + condition = can(regex("ARG_ENABLE_STATE_PERSISTENCE='false'", coder_script.agentapi_shutdown.script)) + error_message = "shutdown script should contain ARG_ENABLE_STATE_PERSISTENCE='false'" } } @@ -83,19 +66,18 @@ run "custom_paths" { pid_file_path = "/custom/agentapi.pid" } - assert { - condition = can(regex("/custom/state.json", coder_script.agentapi.script)) - error_message = "start script should contain custom state_file_path" - } - - assert { - condition = can(regex("/custom/agentapi.pid", coder_script.agentapi.script)) - error_message = "start script should contain custom pid_file_path" - } - - # Verify custom paths also appear in shutdown script. + # Verify custom paths appear in shutdown script. assert { condition = can(regex("/custom/agentapi.pid", coder_script.agentapi_shutdown.script)) error_message = "shutdown script should contain custom pid_file_path" } } + +run "scripts_output" { + command = plan + + assert { + condition = length(output.scripts) == 1 && output.scripts[0] == "coder-agentapi-install_script" + error_message = "scripts output should list the install script sync name" + } +} diff --git a/registry/coder/modules/agentapi/main.tf b/registry/coder/modules/agentapi/main.tf index 5a878be0..bd671189 100644 --- a/registry/coder/modules/agentapi/main.tf +++ b/registry/coder/modules/agentapi/main.tf @@ -173,8 +173,7 @@ locals { web_app = var.web_app || local.is_task # we always trim the slash for consistency - workdir = trimsuffix(var.folder, "/") - agentapi_wait_for_start_script_b64 = base64encode(file("${path.module}/scripts/agentapi-wait-for-start.sh")) + workdir = trimsuffix(var.folder, "/") // Chat base path is only set if not using a subdomain. // NOTE: // - Initial support for --chat-base-path was added in v0.3.1 but configuration @@ -182,45 +181,38 @@ locals { // - As CODER_WORKSPACE_AGENT_NAME is a recent addition we use agent ID // for backward compatibility. 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" - main_script = file("${path.module}/scripts/main.sh") shutdown_script = file("${path.module}/scripts/agentapi-shutdown.sh") lib_script = file("${path.module}/scripts/lib.sh") - main_script_destination = "${var.module_directory}/main.sh" - lib_script_destination = "${var.module_directory}/agentapi-lib.sh" shutdown_script_destination = "${var.module_directory}/agentapi-shutdown.sh" + lib_script_destination = "${var.module_directory}/agentapi-lib.sh" + + install_script = templatefile("${path.module}/scripts/install.sh.tftpl", { + ARG_MODULE_DIRECTORY = var.module_directory + ARG_WORKDIR = local.workdir + ARG_INSTALL_AGENTAPI = tostring(var.install_agentapi) + ARG_AGENTAPI_VERSION = var.agentapi_version + ARG_WAIT_FOR_START_SCRIPT = base64encode(file("${path.module}/scripts/agentapi-wait-for-start.sh")) + ARG_AGENTAPI_PORT = tostring(var.agentapi_port) + ARG_AGENTAPI_CHAT_BASE_PATH = local.agentapi_chat_base_path + ARG_TASK_ID = try(data.coder_task.me.id, "") + ARG_TASK_LOG_SNAPSHOT = tostring(var.task_log_snapshot) + ARG_ENABLE_STATE_PERSISTENCE = tostring(var.enable_state_persistence) + ARG_STATE_FILE_PATH = var.state_file_path + ARG_PID_FILE_PATH = var.pid_file_path + ARG_LIB_SCRIPT = base64encode(local.lib_script) + }) } -resource "coder_script" "agentapi" { - agent_id = var.agent_id - display_name = "Install and start AgentAPI" - icon = var.web_app_icon - script = <<-EOT - #!/bin/bash - set -o errexit - set -o pipefail +module "coder_utils" { + source = "registry.coder.com/coder/coder-utils/coder" + version = "0.0.1" - mkdir -p "${var.module_directory}" - echo -n '${base64encode(local.main_script)}' | base64 -d > "${local.main_script_destination}" - chmod +x "${local.main_script_destination}" - echo -n '${base64encode(local.lib_script)}' | base64 -d > "${local.lib_script_destination}" - - ARG_MODULE_DIRECTORY='${var.module_directory}' \ - ARG_WORKDIR="$(echo -n '${base64encode(local.workdir)}' | base64 -d)" \ - ARG_INSTALL_AGENTAPI='${var.install_agentapi}' \ - ARG_AGENTAPI_VERSION='${var.agentapi_version}' \ - ARG_WAIT_FOR_START_SCRIPT="$(echo -n '${local.agentapi_wait_for_start_script_b64}' | base64 -d)" \ - ARG_AGENTAPI_PORT='${var.agentapi_port}' \ - ARG_AGENTAPI_CHAT_BASE_PATH='${local.agentapi_chat_base_path}' \ - ARG_TASK_ID='${try(data.coder_task.me.id, "")}' \ - ARG_TASK_LOG_SNAPSHOT='${var.task_log_snapshot}' \ - ARG_ENABLE_STATE_PERSISTENCE='${var.enable_state_persistence}' \ - ARG_STATE_FILE_PATH='${var.state_file_path}' \ - ARG_PID_FILE_PATH='${var.pid_file_path}' \ - ARG_LIB_SCRIPT_PATH="${local.lib_script_destination}" \ - "${local.main_script_destination}" - EOT - run_on_start = true + agent_id = var.agent_id + module_directory = var.module_directory + display_name_prefix = "AgentAPI" + icon = var.web_app_icon + install_script = local.install_script } resource "coder_script" "agentapi_shutdown" { @@ -289,3 +281,8 @@ resource "coder_app" "agentapi_cli" { output "task_app_id" { value = local.web_app ? coder_app.agentapi_web[0].id : "" } + +output "scripts" { + 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." + value = module.coder_utils.scripts +} diff --git a/registry/coder/modules/agentapi/scripts/main.sh b/registry/coder/modules/agentapi/scripts/install.sh.tftpl similarity index 54% rename from registry/coder/modules/agentapi/scripts/main.sh rename to registry/coder/modules/agentapi/scripts/install.sh.tftpl index 7e91732c..4fd3257d 100644 --- a/registry/coder/modules/agentapi/scripts/main.sh +++ b/registry/coder/modules/agentapi/scripts/install.sh.tftpl @@ -3,29 +3,34 @@ set -e set -x set -o nounset -MODULE_DIRECTORY="$ARG_MODULE_DIRECTORY" -WORKDIR="$ARG_WORKDIR" -INSTALL_AGENTAPI="$ARG_INSTALL_AGENTAPI" -AGENTAPI_VERSION="$ARG_AGENTAPI_VERSION" -WAIT_FOR_START_SCRIPT="$ARG_WAIT_FOR_START_SCRIPT" -AGENTAPI_PORT="$ARG_AGENTAPI_PORT" -AGENTAPI_CHAT_BASE_PATH="${ARG_AGENTAPI_CHAT_BASE_PATH:-}" -TASK_ID="${ARG_TASK_ID:-}" -TASK_LOG_SNAPSHOT="${ARG_TASK_LOG_SNAPSHOT:-true}" -ENABLE_STATE_PERSISTENCE="${ARG_ENABLE_STATE_PERSISTENCE:-false}" -STATE_FILE_PATH="${ARG_STATE_FILE_PATH:-}" -PID_FILE_PATH="${ARG_PID_FILE_PATH:-}" -LIB_SCRIPT_PATH="$ARG_LIB_SCRIPT_PATH" + +MODULE_DIRECTORY='${ARG_MODULE_DIRECTORY}' +WORKDIR='${ARG_WORKDIR}' +INSTALL_AGENTAPI='${ARG_INSTALL_AGENTAPI}' +AGENTAPI_VERSION='${ARG_AGENTAPI_VERSION}' +WAIT_FOR_START_SCRIPT=$(echo -n '${ARG_WAIT_FOR_START_SCRIPT}' | base64 -d) +AGENTAPI_PORT='${ARG_AGENTAPI_PORT}' +AGENTAPI_CHAT_BASE_PATH='${ARG_AGENTAPI_CHAT_BASE_PATH}' +TASK_ID='${ARG_TASK_ID}' +TASK_LOG_SNAPSHOT='${ARG_TASK_LOG_SNAPSHOT}' +ENABLE_STATE_PERSISTENCE='${ARG_ENABLE_STATE_PERSISTENCE}' +STATE_FILE_PATH='${ARG_STATE_FILE_PATH}' +PID_FILE_PATH='${ARG_PID_FILE_PATH}' +LIB_SCRIPT=$(echo -n '${ARG_LIB_SCRIPT}' | base64 -d) + set +o nounset +# Write and source lib.sh +LIB_SCRIPT_PATH="$${MODULE_DIRECTORY}/agentapi-lib.sh" +echo -n "$LIB_SCRIPT" > "$LIB_SCRIPT_PATH" # shellcheck source=lib.sh -source "${LIB_SCRIPT_PATH}" +source "$LIB_SCRIPT_PATH" command_exists() { command -v "$1" > /dev/null 2>&1 } -mkdir -p "${MODULE_DIRECTORY}/scripts" +mkdir -p "$${MODULE_DIRECTORY}/scripts" # Check for jq dependency if task log snapshot is enabled. if [[ $TASK_LOG_SNAPSHOT == true ]] && [[ -n $TASK_ID ]]; then @@ -34,14 +39,14 @@ if [[ $TASK_LOG_SNAPSHOT == true ]] && [[ -n $TASK_ID ]]; then echo "Install jq to enable log snapshot functionality when the workspace stops." fi fi -if [ ! -d "${WORKDIR}" ]; then - echo "Warning: The specified folder '${WORKDIR}' does not exist." +if [ ! -d "$${WORKDIR}" ]; then + echo "Warning: The specified folder '$${WORKDIR}' does not exist." echo "Creating the folder..." - mkdir -p "${WORKDIR}" + mkdir -p "$${WORKDIR}" echo "Folder created successfully." fi # Install AgentAPI if enabled -if [ "${INSTALL_AGENTAPI}" = "true" ]; then +if [ "$${INSTALL_AGENTAPI}" = "true" ]; then echo "Installing AgentAPI..." arch=$(uname -m) if [ "$arch" = "x86_64" ]; then @@ -52,12 +57,12 @@ if [ "${INSTALL_AGENTAPI}" = "true" ]; then echo "Error: Unsupported architecture: $arch" exit 1 fi - if [ "${AGENTAPI_VERSION}" = "latest" ]; then + if [ "$${AGENTAPI_VERSION}" = "latest" ]; then # for the latest release the download URL pattern is different than for tagged releases # https://docs.github.com/en/repositories/releasing-projects-on-github/linking-to-releases download_url="https://github.com/coder/agentapi/releases/latest/download/$binary_name" else - download_url="https://github.com/coder/agentapi/releases/download/${AGENTAPI_VERSION}/$binary_name" + download_url="https://github.com/coder/agentapi/releases/download/$${AGENTAPI_VERSION}/$binary_name" fi curl \ --retry 5 \ @@ -76,30 +81,30 @@ if ! command_exists agentapi; then exit 1 fi -echo -n "${WAIT_FOR_START_SCRIPT}" > "${MODULE_DIRECTORY}/scripts/agentapi-wait-for-start.sh" -chmod +x "${MODULE_DIRECTORY}/scripts/agentapi-wait-for-start.sh" +echo -n "$${WAIT_FOR_START_SCRIPT}" > "$${MODULE_DIRECTORY}/scripts/agentapi-wait-for-start.sh" +chmod +x "$${MODULE_DIRECTORY}/scripts/agentapi-wait-for-start.sh" export LANG=en_US.UTF-8 export LC_ALL=en_US.UTF-8 -cd "${WORKDIR}" +cd "$${WORKDIR}" -export AGENTAPI_CHAT_BASE_PATH="${AGENTAPI_CHAT_BASE_PATH:-}" +export AGENTAPI_CHAT_BASE_PATH="$${AGENTAPI_CHAT_BASE_PATH:-}" # Disable host header check since AgentAPI is proxied by Coder (which does its own validation) export AGENTAPI_ALLOWED_HOSTS="*" -export AGENTAPI_PID_FILE="${PID_FILE_PATH:-${MODULE_DIRECTORY}/agentapi.pid}" +export AGENTAPI_PID_FILE="$${PID_FILE_PATH:-$${MODULE_DIRECTORY}/agentapi.pid}" # Only set state env vars when persistence is enabled and the binary supports # it. State persistence requires agentapi >= v0.12.0. -if [ "${ENABLE_STATE_PERSISTENCE}" = "true" ]; then +if [ "$${ENABLE_STATE_PERSISTENCE}" = "true" ]; then actual_version=$(agentapi_version) if version_at_least 0.12.0 "$actual_version"; then - export AGENTAPI_STATE_FILE="${STATE_FILE_PATH:-${MODULE_DIRECTORY}/agentapi-state.json}" + export AGENTAPI_STATE_FILE="$${STATE_FILE_PATH:-$${MODULE_DIRECTORY}/agentapi-state.json}" export AGENTAPI_SAVE_STATE="true" export AGENTAPI_LOAD_STATE="true" else - echo "Warning: State persistence requires agentapi >= v0.12.0 (current: ${actual_version:-unknown}), skipping." + echo "Warning: State persistence requires agentapi >= v0.12.0 (current: $${actual_version:-unknown}), skipping." fi fi -nohup "${MODULE_DIRECTORY}/scripts/agentapi-start.sh" true "${AGENTAPI_PORT}" &> "${MODULE_DIRECTORY}/agentapi-start.log" & -"${MODULE_DIRECTORY}/scripts/agentapi-wait-for-start.sh" "${AGENTAPI_PORT}" +nohup "$${MODULE_DIRECTORY}/scripts/agentapi-start.sh" true "$${AGENTAPI_PORT}" &> "$${MODULE_DIRECTORY}/agentapi-start.log" & +"$${MODULE_DIRECTORY}/scripts/agentapi-wait-for-start.sh" "$${AGENTAPI_PORT}"