Skip to content

Commit 9ee0f13

Browse files
Merge branch 'main' into atif/agents
2 parents bd89ca2 + a9b0150 commit 9ee0f13

File tree

19 files changed

+366
-49
lines changed

19 files changed

+366
-49
lines changed

cmd/readmevalidation/codermodules.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ func validateCoderModuleReadme(rm coderResourceReadme) []error {
9494
for _, err := range validateCoderModuleReadmeBody(rm.body) {
9595
errs = append(errs, addFilePathToError(rm.filePath, err))
9696
}
97+
for _, err := range validateResourceGfmAlerts(rm.body) {
98+
errs = append(errs, addFilePathToError(rm.filePath, err))
99+
}
97100
if fmErrs := validateCoderResourceFrontmatter("modules", rm.filePath, rm.frontmatter); len(fmErrs) != 0 {
98101
errs = append(errs, fmErrs...)
99102
}

cmd/readmevalidation/coderresources.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
import (
4+
"bufio"
45
"errors"
56
"net/url"
67
"os"
@@ -16,11 +17,16 @@ import (
1617
var (
1718
supportedResourceTypes = []string{"modules", "templates"}
1819
operatingSystems = []string{"windows", "macos", "linux"}
20+
gfmAlertTypes = []string{"NOTE", "IMPORTANT", "CAUTION", "WARNING", "TIP"}
1921

2022
// TODO: This is a holdover from the validation logic used by the Coder Modules repo. It gives us some assurance, but
2123
// realistically, we probably want to parse any Terraform code snippets, and make some deeper guarantees about how it's
2224
// structured. Just validating whether it *can* be parsed as Terraform would be a big improvement.
2325
terraformVersionRe = regexp.MustCompile(`^\s*\bversion\s+=`)
26+
27+
// Matches the format "> [!INFO]". Deliberately using a broad pattern to catch formatting issues that can mess up
28+
// the renderer for the Registry website
29+
gfmAlertRegex = regexp.MustCompile(`^>(\s*)\[!(\w+)\](\s*)(.*)`)
2430
)
2531

2632
type coderResourceFrontmatter struct {
@@ -277,3 +283,73 @@ func aggregateCoderResourceReadmeFiles(resourceType string) ([]readme, error) {
277283
}
278284
return allReadmeFiles, nil
279285
}
286+
287+
func validateResourceGfmAlerts(readmeBody string) []error {
288+
trimmed := strings.TrimSpace(readmeBody)
289+
if trimmed == "" {
290+
return nil
291+
}
292+
293+
var errs []error
294+
var sourceLine string
295+
isInsideGfmQuotes := false
296+
isInsideCodeBlock := false
297+
298+
lineScanner := bufio.NewScanner(strings.NewReader(trimmed))
299+
for lineScanner.Scan() {
300+
sourceLine = lineScanner.Text()
301+
302+
if strings.HasPrefix(sourceLine, "```") {
303+
isInsideCodeBlock = !isInsideCodeBlock
304+
continue
305+
}
306+
if isInsideCodeBlock {
307+
continue
308+
}
309+
310+
isInsideGfmQuotes = isInsideGfmQuotes && strings.HasPrefix(sourceLine, "> ")
311+
312+
currentMatch := gfmAlertRegex.FindStringSubmatch(sourceLine)
313+
if currentMatch == nil {
314+
continue
315+
}
316+
317+
// Nested GFM alerts is such a weird mistake that it's probably not really safe to keep trying to process the
318+
// rest of the content, so this will prevent any other validations from happening for the given line
319+
if isInsideGfmQuotes {
320+
errs = append(errs, errors.New("registry does not support nested GFM alerts"))
321+
continue
322+
}
323+
324+
leadingWhitespace := currentMatch[1]
325+
if len(leadingWhitespace) != 1 {
326+
errs = append(errs, errors.New("GFM alerts must have one space between the '>' and the start of the GFM brackets"))
327+
}
328+
isInsideGfmQuotes = true
329+
330+
alertHeader := currentMatch[2]
331+
upperHeader := strings.ToUpper(alertHeader)
332+
if !slices.Contains(gfmAlertTypes, upperHeader) {
333+
errs = append(errs, xerrors.Errorf("GFM alert type %q is not supported", alertHeader))
334+
}
335+
if alertHeader != upperHeader {
336+
errs = append(errs, xerrors.Errorf("GFM alerts must be in all caps"))
337+
}
338+
339+
trailingWhitespace := currentMatch[3]
340+
if trailingWhitespace != "" {
341+
errs = append(errs, xerrors.Errorf("GFM alerts must not have any trailing whitespace after the closing bracket"))
342+
}
343+
344+
extraContent := currentMatch[4]
345+
if extraContent != "" {
346+
errs = append(errs, xerrors.Errorf("GFM alerts must not have any extra content on the same line"))
347+
}
348+
}
349+
350+
if gfmAlertRegex.Match([]byte(sourceLine)) {
351+
errs = append(errs, xerrors.Errorf("README has an incomplete GFM alert at the end of the file"))
352+
}
353+
354+
return errs
355+
}

cmd/readmevalidation/codertemplates.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ func validateCoderTemplateReadme(rm coderResourceReadme) []error {
7070
for _, err := range validateCoderTemplateReadmeBody(rm.body) {
7171
errs = append(errs, addFilePathToError(rm.filePath, err))
7272
}
73+
for _, err := range validateResourceGfmAlerts(rm.body) {
74+
errs = append(errs, addFilePathToError(rm.filePath, err))
75+
}
7376
if fmErrs := validateCoderResourceFrontmatter("templates", rm.filePath, rm.frontmatter); len(fmErrs) != 0 {
7477
errs = append(errs, fmErrs...)
7578
}

registry/coder/modules/claude-code/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Run the [Claude Code](https://docs.anthropic.com/en/docs/agents-and-tools/claude
1313
```tf
1414
module "claude-code" {
1515
source = "registry.coder.com/coder/claude-code/coder"
16-
version = "2.1.0"
16+
version = "2.2.0"
1717
agent_id = coder_agent.example.id
1818
folder = "/home/coder"
1919
install_claude_code = true
@@ -83,7 +83,7 @@ resource "coder_agent" "main" {
8383
module "claude-code" {
8484
count = data.coder_workspace.me.start_count
8585
source = "registry.coder.com/coder/claude-code/coder"
86-
version = "2.1.0"
86+
version = "2.2.0"
8787
agent_id = coder_agent.example.id
8888
folder = "/home/coder"
8989
install_claude_code = true
@@ -101,7 +101,7 @@ Run Claude Code as a standalone app in your workspace. This will install Claude
101101
```tf
102102
module "claude-code" {
103103
source = "registry.coder.com/coder/claude-code/coder"
104-
version = "2.1.0"
104+
version = "2.2.0"
105105
agent_id = coder_agent.example.id
106106
folder = "/home/coder"
107107
install_claude_code = true

registry/coder/modules/claude-code/main.tf

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,13 @@ variable "install_agentapi" {
100100
variable "agentapi_version" {
101101
type = string
102102
description = "The version of AgentAPI to install."
103-
default = "v0.3.0"
103+
default = "v0.3.3"
104+
}
105+
106+
variable "subdomain" {
107+
type = bool
108+
description = "Whether to use a subdomain for the Claude Code app."
109+
default = true
104110
}
105111

106112
locals {
@@ -113,6 +119,15 @@ locals {
113119
agentapi_wait_for_start_script_b64 = base64encode(file("${path.module}/scripts/agentapi-wait-for-start.sh"))
114120
remove_last_session_id_script_b64 = base64encode(file("${path.module}/scripts/remove-last-session-id.sh"))
115121
claude_code_app_slug = "ccw"
122+
// Chat base path is only set if not using a subdomain.
123+
// NOTE:
124+
// - Initial support for --chat-base-path was added in v0.3.1 but configuration
125+
// via environment variable AGENTAPI_CHAT_BASE_PATH was added in v0.3.3.
126+
// - As CODER_WORKSPACE_AGENT_NAME is a recent addition we use agent ID
127+
// for backward compatibility.
128+
agentapi_chat_base_path = var.subdomain ? "" : "/@${data.coder_workspace_owner.me.name}/${data.coder_workspace.me.name}.${var.agent_id}/apps/${local.claude_code_app_slug}/chat"
129+
server_base_path = var.subdomain ? "" : "/@${data.coder_workspace_owner.me.name}/${data.coder_workspace.me.name}.${var.agent_id}/apps/${local.claude_code_app_slug}"
130+
healthcheck_url = "http://localhost:3284${local.server_base_path}/status"
116131
}
117132

118133
# Install and Initialize Claude Code
@@ -229,6 +244,9 @@ resource "coder_script" "claude_code" {
229244
230245
# Disable host header check since AgentAPI is proxied by Coder (which does its own validation)
231246
export AGENTAPI_ALLOWED_HOSTS="*"
247+
248+
# Set chat base path for non-subdomain routing (only set if not using subdomain)
249+
export AGENTAPI_CHAT_BASE_PATH="${local.agentapi_chat_base_path}"
232250
233251
nohup "$module_path/scripts/agentapi-start.sh" use_prompt &> "$module_path/agentapi-start.log" &
234252
"$module_path/scripts/agentapi-wait-for-start.sh"
@@ -245,9 +263,9 @@ resource "coder_app" "claude_code_web" {
245263
icon = var.icon
246264
order = var.order
247265
group = var.group
248-
subdomain = true
266+
subdomain = var.subdomain
249267
healthcheck {
250-
url = "http://localhost:3284/status"
268+
url = local.healthcheck_url
251269
interval = 3
252270
threshold = 20
253271
}

registry/coder/modules/coder-login/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Automatically logs the user into Coder when creating their workspace.
1414
module "coder-login" {
1515
count = data.coder_workspace.me.start_count
1616
source = "registry.coder.com/coder/coder-login/coder"
17-
version = "1.0.31"
17+
version = "1.1.0"
1818
agent_id = coder_agent.example.id
1919
}
2020
```

registry/coder/modules/coder-login/main.tf

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,14 @@ variable "agent_id" {
1717
data "coder_workspace" "me" {}
1818
data "coder_workspace_owner" "me" {}
1919

20-
resource "coder_script" "coder-login" {
20+
resource "coder_env" "coder_session_token" {
2121
agent_id = var.agent_id
22-
script = templatefile("${path.module}/run.sh", {
23-
CODER_USER_TOKEN : data.coder_workspace_owner.me.session_token,
24-
CODER_DEPLOYMENT_URL : data.coder_workspace.me.access_url
25-
})
26-
display_name = "Coder Login"
27-
icon = "/icon/coder.svg"
28-
run_on_start = true
29-
start_blocks_login = true
22+
name = "CODER_SESSION_TOKEN"
23+
value = data.coder_workspace_owner.me.session_token
3024
}
3125

26+
resource "coder_env" "coder_url" {
27+
agent_id = var.agent_id
28+
name = "CODER_URL"
29+
value = data.coder_workspace.me.access_url
30+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Test for coder-login module
2+
3+
run "test_coder_login_module" {
4+
command = plan
5+
6+
variables {
7+
agent_id = "test-agent-id"
8+
}
9+
10+
# Test that the coder_env resources are created with correct configuration
11+
assert {
12+
condition = coder_env.coder_session_token.agent_id == "test-agent-id"
13+
error_message = "CODER_SESSION_TOKEN agent ID should match the input variable"
14+
}
15+
16+
assert {
17+
condition = coder_env.coder_session_token.name == "CODER_SESSION_TOKEN"
18+
error_message = "Environment variable name should be 'CODER_SESSION_TOKEN'"
19+
}
20+
21+
assert {
22+
condition = coder_env.coder_url.agent_id == "test-agent-id"
23+
error_message = "CODER_URL agent ID should match the input variable"
24+
}
25+
26+
assert {
27+
condition = coder_env.coder_url.name == "CODER_URL"
28+
error_message = "Environment variable name should be 'CODER_URL'"
29+
}
30+
}
31+
32+
# Test with mock data sources
33+
run "test_with_mock_data" {
34+
command = plan
35+
36+
variables {
37+
agent_id = "mock-agent"
38+
}
39+
40+
# Mock the data sources for testing
41+
override_data {
42+
target = data.coder_workspace.me
43+
values = {
44+
access_url = "https://coder.example.com"
45+
}
46+
}
47+
48+
override_data {
49+
target = data.coder_workspace_owner.me
50+
values = {
51+
session_token = "mock-session-token"
52+
}
53+
}
54+
55+
# Verify environment variables get the mocked values
56+
assert {
57+
condition = coder_env.coder_url.value == "https://coder.example.com"
58+
error_message = "CODER_URL should match workspace access_url"
59+
}
60+
61+
assert {
62+
condition = coder_env.coder_session_token.value == "mock-session-token"
63+
error_message = "CODER_SESSION_TOKEN should match workspace owner session_token"
64+
}
65+
}

registry/coder/modules/coder-login/run.sh

Lines changed: 0 additions & 15 deletions
This file was deleted.

registry/coder/modules/cursor/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Uses the [Coder Remote VS Code Extension](https://github.com/coder/vscode-coder)
1616
module "cursor" {
1717
count = data.coder_workspace.me.start_count
1818
source = "registry.coder.com/coder/cursor/coder"
19-
version = "1.3.1"
19+
version = "1.3.2"
2020
agent_id = coder_agent.example.id
2121
}
2222
```
@@ -29,7 +29,7 @@ module "cursor" {
2929
module "cursor" {
3030
count = data.coder_workspace.me.start_count
3131
source = "registry.coder.com/coder/cursor/coder"
32-
version = "1.3.1"
32+
version = "1.3.2"
3333
agent_id = coder_agent.example.id
3434
folder = "/home/coder/project"
3535
}
@@ -45,7 +45,7 @@ The following example configures Cursor to use the GitHub MCP server with authen
4545
module "cursor" {
4646
count = data.coder_workspace.me.start_count
4747
source = "registry.coder.com/coder/cursor/coder"
48-
version = "1.3.1"
48+
version = "1.3.2"
4949
agent_id = coder_agent.example.id
5050
folder = "/home/coder/project"
5151
mcp = jsonencode({

0 commit comments

Comments
 (0)