Skip to content

Commit c5ff4de

Browse files
feat(coder/modules/agent-helper): add agent-helper module to help run scripts (#704)
## Description The Agent Helper module is a building block for modules that need to run multiple scripts in a specific order. It uses `coder exp sync` for dependency management and is designed for orchestrating pre-install, install, post-install, and start scripts. ## Type of Change - [x] New module - [ ] New template - [ ] Bug fix - [ ] Feature/enhancement - [ ] Documentation - [ ] Other ## Module Information <!-- Delete this section if not applicable --> **Path:** `registry/coder/modules/agent-helper` **New version:** `v1.0.0` **Breaking change:** [x] Yes [ ] No ## Testing & Validation - [x] Tests pass (`bun test`) - [x] Code formatted (`bun fmt`) - [x] Changes tested locally ## Related Issues Closes: #696 Closes: #698 --------- Co-authored-by: DevCats <[email protected]>
1 parent a9a03b1 commit c5ff4de

4 files changed

Lines changed: 539 additions & 0 deletions

File tree

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
---
2+
display_name: Agent Helper
3+
description: Building block for modules that need orchestrated script execution
4+
icon: ../../../../.icons/coder.svg
5+
verified: false
6+
tags: [internal, library]
7+
---
8+
9+
# Agent Helper
10+
11+
> [!CAUTION]
12+
> We do not recommend using this module directly. It is intended primarily for internal use by Coder to create modules with orchestrated script execution.
13+
14+
The Agent Helper module is a building block for modules that need to run multiple scripts in a specific order. It uses `coder exp sync` for dependency management and is designed for orchestrating pre-install, install, post-install, and start scripts.
15+
16+
> [!NOTE]
17+
>
18+
> - The `agent_name` should be the same as that of the agentapi module's `agent_name` if used together.
19+
20+
```tf
21+
module "agent_helper" {
22+
source = "registry.coder.com/coder/agent-helper/coder"
23+
version = "1.0.0"
24+
25+
agent_id = coder_agent.main.id
26+
agent_name = "myagent"
27+
module_dir_name = ".my-module"
28+
29+
pre_install_script = <<-EOT
30+
#!/bin/bash
31+
echo "Running pre-install tasks..."
32+
# Your pre-install logic here
33+
EOT
34+
35+
install_script = <<-EOT
36+
#!/bin/bash
37+
echo "Installing dependencies..."
38+
# Your install logic here
39+
EOT
40+
41+
post_install_script = <<-EOT
42+
#!/bin/bash
43+
echo "Running post-install configuration..."
44+
# Your post-install logic here
45+
EOT
46+
47+
start_script = <<-EOT
48+
#!/bin/bash
49+
echo "Starting the application..."
50+
# Your start logic here
51+
EOT
52+
}
53+
```
54+
55+
## Execution Order
56+
57+
The module orchestrates scripts in the following order:
58+
59+
1. **Log File Creation** - Creates module directory and log files
60+
2. **Pre-Install Script** (optional) - Runs before installation
61+
3. **Install Script** - Main installation
62+
4. **Post-Install Script** (optional) - Runs after installation
63+
5. **Start Script** - Starts the application
64+
65+
Each script waits for its prerequisites to complete before running using `coder exp sync` dependency management.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { describe } from "bun:test";
2+
import { runTerraformInit, testRequiredVariables } from "~test";
3+
4+
describe("agent-helper", async () => {
5+
await runTerraformInit(import.meta.dir);
6+
7+
testRequiredVariables(import.meta.dir, {
8+
agent_id: "test-agent-id",
9+
agent_name: "test-agent",
10+
module_dir_name: ".test-module",
11+
start_script: "echo 'start'",
12+
});
13+
});
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
terraform {
2+
required_version = ">= 1.0"
3+
4+
required_providers {
5+
coder = {
6+
source = "coder/coder"
7+
version = ">= 2.13"
8+
}
9+
}
10+
}
11+
12+
variable "agent_id" {
13+
type = string
14+
description = "The ID of a Coder agent."
15+
}
16+
17+
data "coder_workspace" "me" {}
18+
19+
data "coder_workspace_owner" "me" {}
20+
21+
data "coder_task" "me" {}
22+
23+
variable "pre_install_script" {
24+
type = string
25+
description = "Custom script to run before installing the agent used by AgentAPI."
26+
default = null
27+
}
28+
29+
variable "install_script" {
30+
type = string
31+
description = "Script to install the agent used by AgentAPI."
32+
default = null
33+
}
34+
35+
variable "post_install_script" {
36+
type = string
37+
description = "Custom script to run after installing the agent used by AgentAPI."
38+
default = null
39+
}
40+
41+
variable "start_script" {
42+
type = string
43+
description = "Script that starts AgentAPI."
44+
}
45+
46+
variable "agent_name" {
47+
type = string
48+
description = "The name of the agent. This is used to construct unique script names for the experiment sync."
49+
50+
}
51+
52+
variable "module_dir_name" {
53+
type = string
54+
description = "The name of the module directory."
55+
}
56+
57+
locals {
58+
encoded_pre_install_script = var.pre_install_script != null ? base64encode(var.pre_install_script) : ""
59+
encoded_install_script = var.install_script != null ? base64encode(var.install_script) : ""
60+
encoded_post_install_script = var.post_install_script != null ? base64encode(var.post_install_script) : ""
61+
encoded_start_script = base64encode(var.start_script)
62+
63+
pre_install_script_name = "${var.agent_name}-pre_install_script"
64+
install_script_name = "${var.agent_name}-install_script"
65+
post_install_script_name = "${var.agent_name}-post_install_script"
66+
start_script_name = "${var.agent_name}-start_script"
67+
68+
module_dir_path = "$HOME/${var.module_dir_name}"
69+
70+
pre_install_path = "${local.module_dir_path}/pre_install.sh"
71+
install_path = "${local.module_dir_path}/install.sh"
72+
post_install_path = "${local.module_dir_path}/post_install.sh"
73+
start_path = "${local.module_dir_path}/start.sh"
74+
75+
pre_install_log_path = "${local.module_dir_path}/pre_install.log"
76+
install_log_path = "${local.module_dir_path}/install.log"
77+
post_install_log_path = "${local.module_dir_path}/post_install.log"
78+
start_log_path = "${local.module_dir_path}/start.log"
79+
}
80+
81+
resource "coder_script" "pre_install_script" {
82+
count = var.pre_install_script == null ? 0 : 1
83+
agent_id = var.agent_id
84+
display_name = "Pre-Install Script"
85+
run_on_start = true
86+
script = <<-EOT
87+
#!/bin/bash
88+
set -o errexit
89+
set -o pipefail
90+
91+
mkdir -p ${local.module_dir_path}
92+
93+
trap 'coder exp sync complete ${local.pre_install_script_name}' EXIT
94+
coder exp sync start ${local.pre_install_script_name}
95+
96+
echo -n '${local.encoded_pre_install_script}' | base64 -d > ${local.pre_install_path}
97+
chmod +x ${local.pre_install_path}
98+
99+
${local.pre_install_path} > ${local.pre_install_log_path} 2>&1
100+
EOT
101+
}
102+
103+
resource "coder_script" "install_script" {
104+
agent_id = var.agent_id
105+
display_name = "Install Script"
106+
run_on_start = true
107+
script = <<-EOT
108+
#!/bin/bash
109+
set -o errexit
110+
set -o pipefail
111+
112+
mkdir -p ${local.module_dir_path}
113+
114+
trap 'coder exp sync complete ${local.install_script_name}' EXIT
115+
%{if var.pre_install_script != null~}
116+
coder exp sync want ${local.install_script_name} ${local.pre_install_script_name}
117+
%{endif~}
118+
coder exp sync start ${local.install_script_name}
119+
echo -n '${local.encoded_install_script}' | base64 -d > ${local.install_path}
120+
chmod +x ${local.install_path}
121+
122+
${local.install_path} > ${local.install_log_path} 2>&1
123+
EOT
124+
}
125+
126+
resource "coder_script" "post_install_script" {
127+
count = var.post_install_script != null ? 1 : 0
128+
agent_id = var.agent_id
129+
display_name = "Post-Install Script"
130+
run_on_start = true
131+
script = <<-EOT
132+
#!/bin/bash
133+
set -o errexit
134+
set -o pipefail
135+
136+
trap 'coder exp sync complete ${local.post_install_script_name}' EXIT
137+
coder exp sync want ${local.post_install_script_name} ${local.install_script_name}
138+
coder exp sync start ${local.post_install_script_name}
139+
140+
echo -n '${local.encoded_post_install_script}' | base64 -d > ${local.post_install_path}
141+
chmod +x ${local.post_install_path}
142+
143+
${local.post_install_path} > ${local.post_install_log_path} 2>&1
144+
EOT
145+
}
146+
147+
resource "coder_script" "start_script" {
148+
agent_id = var.agent_id
149+
display_name = "Start Script"
150+
run_on_start = true
151+
script = <<-EOT
152+
#!/bin/bash
153+
set -o errexit
154+
set -o pipefail
155+
156+
trap 'coder exp sync complete ${local.start_script_name}' EXIT
157+
158+
%{if var.post_install_script != null~}
159+
coder exp sync want ${local.start_script_name} ${local.install_script_name} ${local.post_install_script_name}
160+
%{else~}
161+
coder exp sync want ${local.start_script_name} ${local.install_script_name}
162+
%{endif~}
163+
coder exp sync start ${local.start_script_name}
164+
165+
echo -n '${local.encoded_start_script}' | base64 -d > ${local.start_path}
166+
chmod +x ${local.start_path}
167+
168+
${local.start_path} > ${local.start_log_path} 2>&1
169+
EOT
170+
}
171+
172+
output "pre_install_script_name" {
173+
description = "The name of the pre-install script for sync."
174+
value = local.pre_install_script_name
175+
}
176+
177+
output "install_script_name" {
178+
description = "The name of the install script for sync."
179+
value = local.install_script_name
180+
}
181+
182+
output "post_install_script_name" {
183+
description = "The name of the post-install script for sync."
184+
value = local.post_install_script_name
185+
}
186+
187+
output "start_script_name" {
188+
description = "The name of the start script for sync."
189+
value = local.start_script_name
190+
}

0 commit comments

Comments
 (0)