Compare commits

..

No commits in common. "main" and "release/coder/agent-firewall/v0.0.1" have entirely different histories.

13 changed files with 33 additions and 225 deletions

View File

@ -37,7 +37,7 @@ jobs:
all: all:
- '**' - '**'
- name: Set up Terraform - name: Set up Terraform
uses: coder/coder/.github/actions/setup-tf@2b778f292c2ddf8ac261683d0d5d8a18da1512f6 # v2.33.3 uses: coder/coder/.github/actions/setup-tf@34584e909bbe6f501fb2cbdc994325b4d3f9e2ef # v2.32.0
- name: Set up Bun - name: Set up Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with: with:
@ -87,13 +87,13 @@ jobs:
bun-version: latest bun-version: latest
# Need Terraform for its formatter # Need Terraform for its formatter
- name: Install Terraform - name: Install Terraform
uses: coder/coder/.github/actions/setup-tf@2b778f292c2ddf8ac261683d0d5d8a18da1512f6 # v2.33.3 uses: coder/coder/.github/actions/setup-tf@34584e909bbe6f501fb2cbdc994325b4d3f9e2ef # v2.32.0
- name: Install dependencies - name: Install dependencies
run: bun install run: bun install
- name: Validate formatting - name: Validate formatting
run: bun fmt:ci run: bun fmt:ci
- name: Check for typos - name: Check for typos
uses: crate-ci/typos@aca895bf05aec0cb7dffa6f94495e923224d9f17 # v1.46.2 uses: crate-ci/typos@cf5f1c29a8ac336af8568821ec41919923b05a83 # v1.45.1
with: with:
config: .github/typos.toml config: .github/typos.toml
validate-readme-files: validate-readme-files:

View File

@ -31,7 +31,7 @@ jobs:
bun-version: latest bun-version: latest
- name: Set up Terraform - name: Set up Terraform
uses: coder/coder/.github/actions/setup-tf@2b778f292c2ddf8ac261683d0d5d8a18da1512f6 # v2.33.3 uses: coder/coder/.github/actions/setup-tf@34584e909bbe6f501fb2cbdc994325b4d3f9e2ef # v2.32.0
- name: Install dependencies - name: Install dependencies
run: bun install run: bun install

View File

@ -27,7 +27,7 @@ jobs:
persist-credentials: false persist-credentials: false
- name: Run zizmor (blocking, HIGH only) - name: Run zizmor (blocking, HIGH only)
uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6 uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3
with: with:
advanced-security: false advanced-security: false
annotations: true annotations: true
@ -49,7 +49,7 @@ jobs:
persist-credentials: false persist-credentials: false
- name: Run zizmor (SARIF) - name: Run zizmor (SARIF)
uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6 uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3
with: with:
inputs: | inputs: |
.github/workflows .github/workflows

View File

@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect width="7" height="7" x="14" y="3" rx="1"/>
<path d="M10 21V8a1 1 0 0 0-1-1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-5a1 1 0 0 0-1-1H3"/>
</svg>

Before

Width:  |  Height:  |  Size: 339 B

View File

@ -1,5 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect width="18" height="7" x="3" y="3" rx="1"/>
<rect width="9" height="7" x="3" y="14" rx="1"/>
<rect width="5" height="7" x="16" y="14" rx="1"/>
</svg>

Before

Width:  |  Height:  |  Size: 336 B

View File

@ -13,7 +13,7 @@ Install and configure the [Claude Code](https://docs.anthropic.com/en/docs/agent
```tf ```tf
module "claude-code" { module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder" source = "registry.coder.com/coder/claude-code/coder"
version = "5.2.0" version = "5.1.0"
agent_id = coder_agent.main.id agent_id = coder_agent.main.id
anthropic_api_key = "xxxx-xxxxx-xxxx" anthropic_api_key = "xxxx-xxxxx-xxxx"
} }
@ -47,7 +47,7 @@ locals {
module "claude-code" { module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder" source = "registry.coder.com/coder/claude-code/coder"
version = "5.2.0" version = "5.1.0"
agent_id = coder_agent.main.id agent_id = coder_agent.main.id
workdir = local.claude_workdir workdir = local.claude_workdir
anthropic_api_key = "xxxx-xxxxx-xxxx" anthropic_api_key = "xxxx-xxxxx-xxxx"
@ -78,7 +78,7 @@ resource "coder_app" "claude" {
```tf ```tf
module "claude-code" { module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder" source = "registry.coder.com/coder/claude-code/coder"
version = "5.2.0" version = "5.1.0"
agent_id = coder_agent.main.id agent_id = coder_agent.main.id
workdir = "/home/coder/project" workdir = "/home/coder/project"
enable_ai_gateway = true enable_ai_gateway = true
@ -95,33 +95,6 @@ Claude Code then routes API requests through Coder's AI Gateway instead of direc
> [!CAUTION] > [!CAUTION]
> `enable_ai_gateway = true` is mutually exclusive with `anthropic_api_key` and `claude_code_oauth_token`. Setting any of them together fails at plan time. > `enable_ai_gateway = true` is mutually exclusive with `anthropic_api_key` and `claude_code_oauth_token`. Setting any of them together fails at plan time.
### Enterprise policy via managed settings
The `managed_settings` input writes a policy file to `/etc/claude-code/managed-settings.d/10-coder.json` inside the workspace. Claude Code reads this directory at startup with the highest configuration precedence, so users cannot override these values in their own `~/.claude/settings.json`. This is a local file mechanism and works with any inference backend (Anthropic API, AWS Bedrock, Google Vertex AI, or AI Gateway).
```tf
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "5.2.0"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
anthropic_api_key = "xxxx-xxxxx-xxxx"
managed_settings = {
permissions = {
defaultMode = "acceptEdits"
disableBypassPermissionsMode = "disable"
deny = ["Bash(curl:*)", "Bash(wget:*)", "WebFetch"]
}
env = {
DISABLE_TELEMETRY = "0"
}
}
}
```
See the [Claude Code settings reference](https://docs.anthropic.com/en/docs/claude-code/settings) for the full schema. Common keys: `permissions` (`defaultMode`, `allow`, `deny`, `disableBypassPermissionsMode`, `additionalDirectories`), `env`, `model`, `apiKeyHelper`, `hooks`, `cleanupPeriodDays`.
### Advanced Configuration ### Advanced Configuration
This example shows version pinning, a pre-installed binary path, a custom model, and MCP servers. This example shows version pinning, a pre-installed binary path, a custom model, and MCP servers.
@ -129,7 +102,7 @@ This example shows version pinning, a pre-installed binary path, a custom model,
```tf ```tf
module "claude-code" { module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder" source = "registry.coder.com/coder/claude-code/coder"
version = "5.2.0" version = "5.1.0"
agent_id = coder_agent.main.id agent_id = coder_agent.main.id
workdir = "/home/coder/project" workdir = "/home/coder/project"
@ -193,7 +166,7 @@ Downstream `coder_script` resources can wait for this module's install pipeline
```tf ```tf
module "claude-code" { module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder" source = "registry.coder.com/coder/claude-code/coder"
version = "5.2.0" version = "5.1.0"
agent_id = coder_agent.main.id agent_id = coder_agent.main.id
workdir = "/home/coder/project" workdir = "/home/coder/project"
anthropic_api_key = "xxxx-xxxxx-xxxx" anthropic_api_key = "xxxx-xxxxx-xxxx"
@ -279,7 +252,7 @@ resource "coder_env" "bedrock_api_key" {
module "claude-code" { module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder" source = "registry.coder.com/coder/claude-code/coder"
version = "5.2.0" version = "5.1.0"
agent_id = coder_agent.main.id agent_id = coder_agent.main.id
workdir = "/home/coder/project" workdir = "/home/coder/project"
model = "global.anthropic.claude-sonnet-4-5-20250929-v1:0" model = "global.anthropic.claude-sonnet-4-5-20250929-v1:0"
@ -336,7 +309,7 @@ resource "coder_env" "google_application_credentials" {
module "claude-code" { module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder" source = "registry.coder.com/coder/claude-code/coder"
version = "5.2.0" version = "5.1.0"
agent_id = coder_agent.main.id agent_id = coder_agent.main.id
workdir = "/home/coder/project" workdir = "/home/coder/project"
model = "claude-sonnet-4@20250514" model = "claude-sonnet-4@20250514"
@ -377,7 +350,7 @@ The module automatically tags every span and metric with `coder.workspace_id`, `
```tf ```tf
module "claude-code" { module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder" source = "registry.coder.com/coder/claude-code/coder"
version = "5.2.0" version = "5.1.0"
agent_id = coder_agent.main.id agent_id = coder_agent.main.id
workdir = "/home/coder/project" workdir = "/home/coder/project"
anthropic_api_key = "xxxx-xxxxx-xxxx" anthropic_api_key = "xxxx-xxxxx-xxxx"

View File

@ -382,13 +382,10 @@ describe("claude-code", async () => {
const parsed = JSON.parse(claudeConfig); const parsed = JSON.parse(claudeConfig);
expect(parsed.autoUpdaterStatus).toBe("disabled"); expect(parsed.autoUpdaterStatus).toBe("disabled");
expect(parsed.hasCompletedOnboarding).toBe(true); expect(parsed.hasCompletedOnboarding).toBe(true);
expect(parsed.bypassPermissionsModeAccepted).toBe(true);
expect(parsed.hasAcknowledgedCostThreshold).toBe(true); expect(parsed.hasAcknowledgedCostThreshold).toBe(true);
expect(parsed.projects[workdir].hasCompletedProjectOnboarding).toBe(true); expect(parsed.projects[workdir].hasCompletedProjectOnboarding).toBe(true);
expect(parsed.projects[workdir].hasTrustDialogAccepted).toBe(true); expect(parsed.projects[workdir].hasTrustDialogAccepted).toBe(true);
// Permission posture is delivered via /etc/claude-code/managed-settings.d/,
// not user-writable ~/.claude.json acceptance flags.
expect(parsed.bypassPermissionsModeAccepted).toBeUndefined();
expect(parsed.autoModeAccepted).toBeUndefined();
}); });
test("standalone-mode-with-oauth-token", async () => { test("standalone-mode-with-oauth-token", async () => {
@ -416,7 +413,7 @@ describe("claude-code", async () => {
); );
const parsed = JSON.parse(claudeConfig); const parsed = JSON.parse(claudeConfig);
expect(parsed.hasCompletedOnboarding).toBe(true); expect(parsed.hasCompletedOnboarding).toBe(true);
expect(parsed.bypassPermissionsModeAccepted).toBeUndefined(); expect(parsed.bypassPermissionsModeAccepted).toBe(true);
}); });
test("standalone-mode-no-auth", async () => { test("standalone-mode-no-auth", async () => {
@ -439,49 +436,6 @@ describe("claude-code", async () => {
expect(resp.stdout.trim()).toBe("ABSENT"); expect(resp.stdout.trim()).toBe("ABSENT");
}); });
test("claude-managed-settings-written", async () => {
const { id, scripts } = await setup({
moduleVariables: {
managed_settings: JSON.stringify({
permissions: {
defaultMode: "acceptEdits",
disableBypassPermissionsMode: "disable",
deny: ["Bash(rm -rf*)"],
},
}),
},
});
await runScripts(id, scripts);
const policy = await execContainer(id, [
"bash",
"-c",
"cat /etc/claude-code/managed-settings.d/10-coder.json",
]);
expect(policy.exitCode).toBe(0);
expect(policy.stdout).toContain('"defaultMode":"acceptEdits"');
expect(policy.stdout).toContain('"disableBypassPermissionsMode":"disable"');
expect(policy.stdout).toContain('"deny":["Bash(rm -rf*)"]');
const installLog = await readFileContainer(
id,
"/home/coder/.coder-modules/coder/claude-code/logs/install.log",
);
expect(installLog).toContain("Wrote Claude Code managed settings");
});
test("claude-managed-settings-not-set", async () => {
const { id, scripts } = await setup();
await runScripts(id, scripts);
const resp = await execContainer(id, [
"bash",
"-c",
"test -e /etc/claude-code/managed-settings.d/10-coder.json && echo EXISTS || echo ABSENT",
]);
expect(resp.stdout.trim()).toBe("ABSENT");
});
test("telemetry-otel", async () => { test("telemetry-otel", async () => {
const { coderEnvVars } = await setup({ const { coderEnvVars } = await setup({
moduleVariables: { moduleVariables: {

View File

@ -102,12 +102,6 @@ variable "claude_binary_path" {
} }
} }
variable "managed_settings" {
type = any
description = "Policy settings written to /etc/claude-code/managed-settings.d/10-coder.json. Highest-precedence client config; works with any inference backend (Anthropic API, Bedrock, Vertex, AI Gateway). See https://docs.anthropic.com/en/docs/claude-code/settings for the schema."
default = null
}
variable "enable_ai_gateway" { variable "enable_ai_gateway" {
type = bool type = bool
description = "Use AI Gateway for Claude Code. https://coder.com/docs/ai-coder/ai-gateway" description = "Use AI Gateway for Claude Code. https://coder.com/docs/ai-coder/ai-gateway"
@ -243,7 +237,6 @@ locals {
ARG_MCP = var.mcp != "" ? base64encode(var.mcp) : "" ARG_MCP = var.mcp != "" ? base64encode(var.mcp) : ""
ARG_MCP_CONFIG_REMOTE_PATH = base64encode(jsonencode(var.mcp_config_remote_path)) ARG_MCP_CONFIG_REMOTE_PATH = base64encode(jsonencode(var.mcp_config_remote_path))
ARG_ENABLE_AI_GATEWAY = tostring(var.enable_ai_gateway) ARG_ENABLE_AI_GATEWAY = tostring(var.enable_ai_gateway)
ARG_MANAGED_SETTINGS_JSON = var.managed_settings != null ? base64encode(jsonencode(var.managed_settings)) : ""
}) })
module_dir_name = ".coder-modules/coder/claude-code" module_dir_name = ".coder-modules/coder/claude-code"
} }

View File

@ -283,47 +283,3 @@ run "test_workdir_optional" {
error_message = "workdir should default to null when omitted" error_message = "workdir should default to null when omitted"
} }
} }
run "test_managed_settings" {
command = plan
variables {
agent_id = "test-agent-managed-settings"
workdir = "/home/coder/project"
managed_settings = {
permissions = {
defaultMode = "acceptEdits"
disableBypassPermissionsMode = "disable"
deny = ["Bash(rm -rf*)"]
}
}
}
assert {
condition = var.managed_settings.permissions.defaultMode == "acceptEdits"
error_message = "managed_settings should accept the permissions object"
}
assert {
condition = strcontains(local.install_script, "/etc/claude-code/managed-settings.d")
error_message = "install script should reference the managed-settings.d drop-in directory"
}
assert {
condition = strcontains(local.install_script, base64encode(jsonencode(var.managed_settings)))
error_message = "install script should embed the base64-encoded managed_settings JSON"
}
}
run "test_managed_settings_default_null" {
command = plan
variables {
agent_id = "test-agent-managed-settings-default"
}
assert {
condition = var.managed_settings == null
error_message = "managed_settings should default to null when omitted"
}
}

View File

@ -17,7 +17,6 @@ ARG_CLAUDE_BINARY_PATH="$${ARG_CLAUDE_BINARY_PATH//\$HOME/$HOME}"
ARG_MCP=$(echo -n '${ARG_MCP}' | base64 -d) ARG_MCP=$(echo -n '${ARG_MCP}' | base64 -d)
ARG_MCP_CONFIG_REMOTE_PATH=$(echo -n '${ARG_MCP_CONFIG_REMOTE_PATH}' | base64 -d) ARG_MCP_CONFIG_REMOTE_PATH=$(echo -n '${ARG_MCP_CONFIG_REMOTE_PATH}' | base64 -d)
ARG_ENABLE_AI_GATEWAY='${ARG_ENABLE_AI_GATEWAY}' ARG_ENABLE_AI_GATEWAY='${ARG_ENABLE_AI_GATEWAY}'
ARG_MANAGED_SETTINGS_JSON=$(echo -n '${ARG_MANAGED_SETTINGS_JSON}' | base64 -d)
export PATH="$${ARG_CLAUDE_BINARY_PATH}:$PATH" export PATH="$${ARG_CLAUDE_BINARY_PATH}:$PATH"
@ -30,7 +29,6 @@ printf "ARG_CLAUDE_BINARY_PATH: %s\n" "$${ARG_CLAUDE_BINARY_PATH}"
printf "ARG_MCP: %s\n" "$${ARG_MCP}" printf "ARG_MCP: %s\n" "$${ARG_MCP}"
printf "ARG_MCP_CONFIG_REMOTE_PATH: %s\n" "$${ARG_MCP_CONFIG_REMOTE_PATH}" printf "ARG_MCP_CONFIG_REMOTE_PATH: %s\n" "$${ARG_MCP_CONFIG_REMOTE_PATH}"
printf "ARG_ENABLE_AI_GATEWAY: %s\n" "$${ARG_ENABLE_AI_GATEWAY}" printf "ARG_ENABLE_AI_GATEWAY: %s\n" "$${ARG_ENABLE_AI_GATEWAY}"
printf "ARG_MANAGED_SETTINGS_JSON: %s\n" "$${ARG_MANAGED_SETTINGS_JSON}"
echo "--------------------------------" echo "--------------------------------"
@ -146,32 +144,6 @@ function setup_claude_configurations() {
} }
function write_managed_settings() {
if [ -z "$${ARG_MANAGED_SETTINGS_JSON}" ]; then
return
fi
local dropin_dir="/etc/claude-code/managed-settings.d"
local target="$${dropin_dir}/10-coder.json"
if ! echo "$${ARG_MANAGED_SETTINGS_JSON}" | jq empty 2> /dev/null; then
echo "Warning: managed_settings is not valid JSON, skipping policy write"
return
fi
if command_exists sudo; then
sudo mkdir -p "$${dropin_dir}"
echo "$${ARG_MANAGED_SETTINGS_JSON}" | sudo tee "$${target}" > /dev/null
sudo chmod 0644 "$${target}"
else
mkdir -p "$${dropin_dir}"
echo "$${ARG_MANAGED_SETTINGS_JSON}" > "$${target}"
chmod 0644 "$${target}"
fi
echo "Wrote Claude Code managed settings to $${target}"
}
function configure_standalone_mode() { function configure_standalone_mode() {
echo "Configuring Claude Code for standalone mode..." echo "Configuring Claude Code for standalone mode..."
@ -186,6 +158,8 @@ function configure_standalone_mode() {
echo "Updating existing Claude configuration at $${claude_config}" echo "Updating existing Claude configuration at $${claude_config}"
jq '.autoUpdaterStatus = "disabled" | jq '.autoUpdaterStatus = "disabled" |
.autoModeAccepted = true |
.bypassPermissionsModeAccepted = true |
.hasAcknowledgedCostThreshold = true | .hasAcknowledgedCostThreshold = true |
.hasCompletedOnboarding = true' \ .hasCompletedOnboarding = true' \
"$${claude_config}" > "$${claude_config}.tmp" && mv "$${claude_config}.tmp" "$${claude_config}" "$${claude_config}" > "$${claude_config}.tmp" && mv "$${claude_config}.tmp" "$${claude_config}"
@ -194,6 +168,8 @@ function configure_standalone_mode() {
cat > "$${claude_config}" << EOF cat > "$${claude_config}" << EOF
{ {
"autoUpdaterStatus": "disabled", "autoUpdaterStatus": "disabled",
"autoModeAccepted": true,
"bypassPermissionsModeAccepted": true,
"hasAcknowledgedCostThreshold": true, "hasAcknowledgedCostThreshold": true,
"hasCompletedOnboarding": true "hasCompletedOnboarding": true
} }
@ -213,5 +189,4 @@ EOF
install_claude_code_cli install_claude_code_cli
setup_claude_configurations setup_claude_configurations
write_managed_settings
configure_standalone_mode configure_standalone_mode

View File

@ -14,7 +14,7 @@ This module allows you to automatically clone a repository by URL and skip if it
module "git-clone" { module "git-clone" {
count = data.coder_workspace.me.start_count count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder" source = "registry.coder.com/coder/git-clone/coder"
version = "1.3.1" version = "1.3.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
url = "https://github.com/coder/coder" url = "https://github.com/coder/coder"
} }
@ -28,7 +28,7 @@ module "git-clone" {
module "git-clone" { module "git-clone" {
count = data.coder_workspace.me.start_count count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder" source = "registry.coder.com/coder/git-clone/coder"
version = "1.3.1" version = "1.3.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
url = "https://github.com/coder/coder" url = "https://github.com/coder/coder"
base_dir = "~/projects/coder" base_dir = "~/projects/coder"
@ -43,7 +43,7 @@ To use with [Git Authentication](https://coder.com/docs/v2/latest/admin/git-prov
module "git-clone" { module "git-clone" {
count = data.coder_workspace.me.start_count count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder" source = "registry.coder.com/coder/git-clone/coder"
version = "1.3.1" version = "1.3.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
url = "https://github.com/coder/coder" url = "https://github.com/coder/coder"
} }
@ -70,7 +70,7 @@ data "coder_parameter" "git_repo" {
module "git_clone" { module "git_clone" {
count = data.coder_workspace.me.start_count count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder" source = "registry.coder.com/coder/git-clone/coder"
version = "1.3.1" version = "1.3.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
url = data.coder_parameter.git_repo.value url = data.coder_parameter.git_repo.value
} }
@ -105,7 +105,7 @@ Configuring `git-clone` for a self-hosted GitHub Enterprise Server running at `g
module "git-clone" { module "git-clone" {
count = data.coder_workspace.me.start_count count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder" source = "registry.coder.com/coder/git-clone/coder"
version = "1.3.1" version = "1.3.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
url = "https://github.example.com/coder/coder/tree/feat/example" url = "https://github.example.com/coder/coder/tree/feat/example"
git_providers = { git_providers = {
@ -125,7 +125,7 @@ To GitLab clone with a specific branch like `feat/example`
module "git-clone" { module "git-clone" {
count = data.coder_workspace.me.start_count count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder" source = "registry.coder.com/coder/git-clone/coder"
version = "1.3.1" version = "1.3.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
url = "https://gitlab.com/coder/coder/-/tree/feat/example" url = "https://gitlab.com/coder/coder/-/tree/feat/example"
} }
@ -137,7 +137,7 @@ Configuring `git-clone` for a self-hosted GitLab running at `gitlab.example.com`
module "git-clone" { module "git-clone" {
count = data.coder_workspace.me.start_count count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder" source = "registry.coder.com/coder/git-clone/coder"
version = "1.3.1" version = "1.3.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
url = "https://gitlab.example.com/coder/coder/-/tree/feat/example" url = "https://gitlab.example.com/coder/coder/-/tree/feat/example"
git_providers = { git_providers = {
@ -159,7 +159,7 @@ For example, to clone the `feat/example` branch:
module "git-clone" { module "git-clone" {
count = data.coder_workspace.me.start_count count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder" source = "registry.coder.com/coder/git-clone/coder"
version = "1.3.1" version = "1.3.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
url = "https://github.com/coder/coder" url = "https://github.com/coder/coder"
branch_name = "feat/example" branch_name = "feat/example"
@ -177,7 +177,7 @@ For example, this will clone into the `~/projects/coder/coder-dev` folder:
module "git-clone" { module "git-clone" {
count = data.coder_workspace.me.start_count count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder" source = "registry.coder.com/coder/git-clone/coder"
version = "1.3.1" version = "1.3.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
url = "https://github.com/coder/coder" url = "https://github.com/coder/coder"
folder_name = "coder-dev" folder_name = "coder-dev"
@ -196,7 +196,7 @@ If not defined, the default, `0`, performs a full clone.
module "git-clone" { module "git-clone" {
count = data.coder_workspace.me.start_count count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder" source = "registry.coder.com/coder/git-clone/coder"
version = "1.3.1" version = "1.3.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
url = "https://github.com/coder/coder" url = "https://github.com/coder/coder"
depth = 1 depth = 1
@ -212,7 +212,7 @@ This is useful for preparing the environment or validating prerequisites before
module "git-clone" { module "git-clone" {
count = data.coder_workspace.me.start_count count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder" source = "registry.coder.com/coder/git-clone/coder"
version = "1.3.1" version = "1.3.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
url = "https://github.com/coder/coder" url = "https://github.com/coder/coder"
pre_clone_script = <<-EOT pre_clone_script = <<-EOT
@ -235,7 +235,7 @@ This is useful for running initialization tasks like installing dependencies or
module "git-clone" { module "git-clone" {
count = data.coder_workspace.me.start_count count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder" source = "registry.coder.com/coder/git-clone/coder"
version = "1.3.1" version = "1.3.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
url = "https://github.com/coder/coder" url = "https://github.com/coder/coder"
post_clone_script = <<-EOT post_clone_script = <<-EOT

View File

@ -250,14 +250,13 @@ describe("git-clone", async () => {
const state = await runTerraformApply(import.meta.dir, { const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo", agent_id: "foo",
url: "fake-url", url: "fake-url",
base_dir: "/tmp",
post_clone_script: "echo 'Post-clone script executed'", post_clone_script: "echo 'Post-clone script executed'",
}); });
const output = await executeScriptInContainer( const output = await executeScriptInContainer(
state, state,
"alpine/git", "alpine/git",
"sh", "sh",
"mkdir -p /tmp/fake-url && echo 'existing' > /tmp/fake-url/file.txt", "mkdir -p ~/fake-url && echo 'existing' > ~/fake-url/file.txt",
); );
expect(output.stdout).toContain("Running post-clone script..."); expect(output.stdout).toContain("Running post-clone script...");
expect(output.stdout).toContain("Post-clone script executed"); expect(output.stdout).toContain("Post-clone script executed");
@ -274,35 +273,4 @@ describe("git-clone", async () => {
expect(output.stdout).toContain("Pre-clone script executed"); expect(output.stdout).toContain("Pre-clone script executed");
expect(output.stdout).toContain("Cloning fake-url to ~/fake-url..."); expect(output.stdout).toContain("Cloning fake-url to ~/fake-url...");
}); });
it("fails when pre-clone script fails", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
url: "fake-url",
pre_clone_script: "echo 'Pre-clone script failed'; exit 42",
});
const output = await executeScriptInContainer(state, "alpine/git");
expect(output.exitCode).toBe(42);
expect(output.stdout).toContain("Running pre-clone script...");
expect(output.stdout).toContain("Pre-clone script failed");
expect(output.stdout).not.toContain("Cloning fake-url to ~/fake-url...");
});
it("fails when post-clone script fails", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
url: "fake-url",
base_dir: "/tmp",
post_clone_script: "echo 'Post-clone script failed'; exit 43",
});
const output = await executeScriptInContainer(
state,
"alpine/git",
"sh",
"mkdir -p /tmp/fake-url && echo 'existing' > /tmp/fake-url/file.txt",
);
expect(output.exitCode).toBe(43);
expect(output.stdout).toContain("Running post-clone script...");
expect(output.stdout).toContain("Post-clone script failed");
});
}); });

View File

@ -1,7 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail
REPO_URL="${REPO_URL}" REPO_URL="${REPO_URL}"
CLONE_PATH="${CLONE_PATH}" CLONE_PATH="${CLONE_PATH}"
BRANCH_NAME="${BRANCH_NAME}" BRANCH_NAME="${BRANCH_NAME}"