Merge branch 'main' into aws-cli-module

This commit is contained in:
Austen Bruhn 2025-11-18 14:34:14 -07:00 committed by GitHub
commit a7a4ae4cdf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 116 additions and 32 deletions

View File

@ -82,7 +82,7 @@ jobs:
- 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@v1.39.0 uses: crate-ci/typos@v1.39.2
with: with:
config: .github/typos.toml config: .github/typos.toml
validate-readme-files: validate-readme-files:

View File

@ -13,7 +13,7 @@ Run the [Claude Code](https://docs.anthropic.com/en/docs/agents-and-tools/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 = "4.0.1" version = "4.2.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
workdir = "/home/coder/project" workdir = "/home/coder/project"
claude_api_key = "xxxx-xxxxx-xxxx" claude_api_key = "xxxx-xxxxx-xxxx"
@ -70,7 +70,7 @@ data "coder_parameter" "ai_prompt" {
module "claude-code" { module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder" source = "registry.coder.com/coder/claude-code/coder"
version = "4.0.1" version = "4.2.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
workdir = "/home/coder/project" workdir = "/home/coder/project"
@ -106,7 +106,7 @@ Run and configure Claude Code as a standalone CLI in your workspace.
```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 = "4.0.1" version = "4.2.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
workdir = "/home/coder" workdir = "/home/coder"
install_claude_code = true install_claude_code = true
@ -129,7 +129,7 @@ variable "claude_code_oauth_token" {
module "claude-code" { module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder" source = "registry.coder.com/coder/claude-code/coder"
version = "4.0.1" version = "4.2.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
workdir = "/home/coder/project" workdir = "/home/coder/project"
claude_code_oauth_token = var.claude_code_oauth_token claude_code_oauth_token = var.claude_code_oauth_token
@ -202,7 +202,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 = "4.0.1" version = "4.2.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.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"
@ -259,7 +259,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 = "4.0.1" version = "4.2.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
workdir = "/home/coder/project" workdir = "/home/coder/project"
model = "claude-sonnet-4@20250514" model = "claude-sonnet-4@20250514"

View File

@ -114,6 +114,12 @@ variable "claude_code_version" {
default = "latest" default = "latest"
} }
variable "disable_autoupdater" {
type = bool
description = "Disable Claude Code automatic updates. When true, Claude Code will stay on the installed version."
default = false
}
variable "claude_api_key" { variable "claude_api_key" {
type = string type = string
description = "The API key to use for the Claude Code server." description = "The API key to use for the Claude Code server."
@ -240,6 +246,12 @@ variable "boundary_pprof_port" {
default = "6067" default = "6067"
} }
variable "compile_boundary_from_source" {
type = bool
description = "Whether to compile boundary from source instead of using the official install script"
default = false
}
resource "coder_env" "claude_code_md_path" { resource "coder_env" "claude_code_md_path" {
count = var.claude_md_path == "" ? 0 : 1 count = var.claude_md_path == "" ? 0 : 1
@ -268,6 +280,14 @@ resource "coder_env" "claude_api_key" {
value = var.claude_api_key value = var.claude_api_key
} }
resource "coder_env" "disable_autoupdater" {
count = var.disable_autoupdater ? 1 : 0
agent_id = var.agent_id
name = "DISABLE_AUTOUPDATER"
value = "1"
}
locals { locals {
# we have to trim the slash because otherwise coder exp mcp will # we have to trim the slash because otherwise coder exp mcp will
# set up an invalid claude config # set up an invalid claude config
@ -357,6 +377,7 @@ module "agentapi" {
ARG_BOUNDARY_PROXY_PORT='${var.boundary_proxy_port}' \ ARG_BOUNDARY_PROXY_PORT='${var.boundary_proxy_port}' \
ARG_ENABLE_BOUNDARY_PPROF='${var.enable_boundary_pprof}' \ ARG_ENABLE_BOUNDARY_PPROF='${var.enable_boundary_pprof}' \
ARG_BOUNDARY_PPROF_PORT='${var.boundary_pprof_port}' \ ARG_BOUNDARY_PPROF_PORT='${var.boundary_pprof_port}' \
ARG_COMPILE_FROM_SOURCE='${var.compile_boundary_from_source}' \
ARG_CODER_HOST='${local.coder_host}' \ ARG_CODER_HOST='${local.coder_host}' \
/tmp/start.sh /tmp/start.sh
EOT EOT

View File

@ -28,6 +28,7 @@ ARG_BOUNDARY_LOG_LEVEL=${ARG_BOUNDARY_LOG_LEVEL:-"WARN"}
ARG_BOUNDARY_PROXY_PORT=${ARG_BOUNDARY_PROXY_PORT:-"8087"} ARG_BOUNDARY_PROXY_PORT=${ARG_BOUNDARY_PROXY_PORT:-"8087"}
ARG_ENABLE_BOUNDARY_PPROF=${ARG_ENABLE_BOUNDARY_PPROF:-false} ARG_ENABLE_BOUNDARY_PPROF=${ARG_ENABLE_BOUNDARY_PPROF:-false}
ARG_BOUNDARY_PPROF_PORT=${ARG_BOUNDARY_PPROF_PORT:-"6067"} ARG_BOUNDARY_PPROF_PORT=${ARG_BOUNDARY_PPROF_PORT:-"6067"}
ARG_COMPILE_FROM_SOURCE=${ARG_COMPILE_FROM_SOURCE:-false}
ARG_CODER_HOST=${ARG_CODER_HOST:-} ARG_CODER_HOST=${ARG_CODER_HOST:-}
echo "--------------------------------" echo "--------------------------------"
@ -45,6 +46,7 @@ printf "ARG_BOUNDARY_VERSION: %s\n" "$ARG_BOUNDARY_VERSION"
printf "ARG_BOUNDARY_LOG_DIR: %s\n" "$ARG_BOUNDARY_LOG_DIR" printf "ARG_BOUNDARY_LOG_DIR: %s\n" "$ARG_BOUNDARY_LOG_DIR"
printf "ARG_BOUNDARY_LOG_LEVEL: %s\n" "$ARG_BOUNDARY_LOG_LEVEL" printf "ARG_BOUNDARY_LOG_LEVEL: %s\n" "$ARG_BOUNDARY_LOG_LEVEL"
printf "ARG_BOUNDARY_PROXY_PORT: %s\n" "$ARG_BOUNDARY_PROXY_PORT" printf "ARG_BOUNDARY_PROXY_PORT: %s\n" "$ARG_BOUNDARY_PROXY_PORT"
printf "ARG_COMPILE_FROM_SOURCE: %s\n" "$ARG_COMPILE_FROM_SOURCE"
printf "ARG_CODER_HOST: %s\n" "$ARG_CODER_HOST" printf "ARG_CODER_HOST: %s\n" "$ARG_CODER_HOST"
echo "--------------------------------" echo "--------------------------------"
@ -63,11 +65,25 @@ case $session_cleanup_exit_code in
esac esac
function install_boundary() { function install_boundary() {
# Install boundary from public github repo if [ "${ARG_COMPILE_FROM_SOURCE:-false}" = "true" ]; then
git clone https://github.com/coder/boundary # Install boundary by compiling from source
cd boundary echo "Compiling boundary from source (version: $ARG_BOUNDARY_VERSION)"
git checkout $ARG_BOUNDARY_VERSION git clone https://github.com/coder/boundary.git
go install ./cmd/... cd boundary
git checkout "$ARG_BOUNDARY_VERSION"
# Build the binary
make build
# Install binary and wrapper script (optional)
sudo cp boundary /usr/local/bin/
sudo cp scripts/boundary-wrapper.sh /usr/local/bin/boundary-run
sudo chmod +x /usr/local/bin/boundary-run
else
# Install boundary using official install script
echo "Installing boundary using official install script (version: $ARG_BOUNDARY_VERSION)"
curl -fsSL https://raw.githubusercontent.com/coder/boundary/main/install.sh | bash -s -- --version "$ARG_BOUNDARY_VERSION"
fi
} }
function validate_claude_installation() { function validate_claude_installation() {
@ -209,9 +225,8 @@ function start_agentapi() {
BOUNDARY_ARGS+=(--pprof-port ${ARG_BOUNDARY_PPROF_PORT}) BOUNDARY_ARGS+=(--pprof-port ${ARG_BOUNDARY_PPROF_PORT})
fi fi
agentapi server --allowed-hosts="*" --type claude --term-width 67 --term-height 1190 -- \ agentapi server --type claude --term-width 67 --term-height 1190 -- \
sudo -E env PATH=$PATH setpriv --reuid=$(id -u) --regid=$(id -g) --clear-groups \ boundary-run "${BOUNDARY_ARGS[@]}" -- \
--inh-caps=+net_admin --ambient-caps=+net_admin --bounding-set=+net_admin boundary "${BOUNDARY_ARGS[@]}" -- \
claude "${ARGS[@]}" claude "${ARGS[@]}"
else else
agentapi server --type claude --term-width 67 --term-height 1190 -- claude "${ARGS[@]}" agentapi server --type claude --term-width 67 --term-height 1190 -- claude "${ARGS[@]}"

View File

@ -16,7 +16,7 @@ Install the JF CLI and authenticate package managers with Artifactory using OAut
module "jfrog" { module "jfrog" {
count = data.coder_workspace.me.start_count count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/jfrog-oauth/coder" source = "registry.coder.com/coder/jfrog-oauth/coder"
version = "1.2.0" version = "1.2.2"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
jfrog_url = "https://example.jfrog.io" jfrog_url = "https://example.jfrog.io"
username_field = "username" # If you are using GitHub to login to both Coder and Artifactory, use username_field = "username" username_field = "username" # If you are using GitHub to login to both Coder and Artifactory, use username_field = "username"
@ -39,6 +39,15 @@ module "jfrog" {
This module is usable by JFrog self-hosted (on-premises) Artifactory as it requires configuring a custom integration. This integration benefits from Coder's [external-auth](https://coder.com/docs/v2/latest/admin/external-auth) feature and allows each user to authenticate with Artifactory using an OAuth flow and issues user-scoped tokens to each user. For configuration instructions, see this [guide](https://coder.com/docs/v2/latest/guides/artifactory-integration#jfrog-oauth) on the Coder documentation. This module is usable by JFrog self-hosted (on-premises) Artifactory as it requires configuring a custom integration. This integration benefits from Coder's [external-auth](https://coder.com/docs/v2/latest/admin/external-auth) feature and allows each user to authenticate with Artifactory using an OAuth flow and issues user-scoped tokens to each user. For configuration instructions, see this [guide](https://coder.com/docs/v2/latest/guides/artifactory-integration#jfrog-oauth) on the Coder documentation.
## Username Handling
The module automatically extracts your JFrog username directly from the OAuth token's JWT payload. This preserves special characters like dots (`.`), hyphens (`-`), and accented characters that Coder normalizes in usernames.
**Priority order:**
1. **JWT extraction** (default) - Extracts username from OAuth token, preserving special characters
2. **Fallback to `username_field`** - If JWT extraction fails, uses Coder username or email
## Examples ## Examples
Configure the Python pip package manager to fetch packages from Artifactory while mapping the Coder email to the Artifactory username. Configure the Python pip package manager to fetch packages from Artifactory while mapping the Coder email to the Artifactory username.
@ -47,7 +56,7 @@ Configure the Python pip package manager to fetch packages from Artifactory whil
module "jfrog" { module "jfrog" {
count = data.coder_workspace.me.start_count count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/jfrog-oauth/coder" source = "registry.coder.com/coder/jfrog-oauth/coder"
version = "1.2.0" version = "1.2.2"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
jfrog_url = "https://example.jfrog.io" jfrog_url = "https://example.jfrog.io"
username_field = "email" username_field = "email"
@ -76,7 +85,7 @@ The [JFrog extension](https://open-vsx.org/extension/JFrog/jfrog-vscode-extensio
module "jfrog" { module "jfrog" {
count = data.coder_workspace.me.start_count count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/jfrog-oauth/coder" source = "registry.coder.com/coder/jfrog-oauth/coder"
version = "1.2.0" version = "1.2.2"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
jfrog_url = "https://example.jfrog.io" jfrog_url = "https://example.jfrog.io"
username_field = "username" # If you are using GitHub to login to both Coder and Artifactory, use username_field = "username" username_field = "username" # If you are using GitHub to login to both Coder and Artifactory, use username_field = "username"

View File

@ -159,9 +159,13 @@ EOF`;
const coderScript = findResourceInstance(state, "coder_script"); const coderScript = findResourceInstance(state, "coder_script");
expect(coderScript.script).toContain( expect(coderScript.script).toContain("jf mvnc --global");
'jf mvnc --global --repo-resolve "central"', expect(coderScript.script).toContain('--server-id-resolve="0"');
); expect(coderScript.script).toContain('--repo-resolve-releases "central"');
expect(coderScript.script).toContain('--repo-resolve-snapshots "central"');
expect(coderScript.script).toContain('--server-id-deploy="0"');
expect(coderScript.script).toContain('--repo-deploy-releases "central"');
expect(coderScript.script).toContain('--repo-deploy-snapshots "central"');
expect(coderScript.script).toContain("<servers>"); expect(coderScript.script).toContain("<servers>");
expect(coderScript.script).toContain("<id>central</id>"); expect(coderScript.script).toContain("<id>central</id>");

View File

@ -76,8 +76,27 @@ variable "package_managers" {
} }
locals { locals {
# The username field to use for artifactory jwt_parts = try(split(".", data.coder_external_auth.jfrog.access_token), [])
username = var.username_field == "email" ? data.coder_workspace_owner.me.email : data.coder_workspace_owner.me.name jwt_payload = try(local.jwt_parts[1], "")
payload_padding = local.jwt_payload == "" ? "" : (
length(local.jwt_payload) % 4 == 0 ? "" :
length(local.jwt_payload) % 4 == 2 ? "==" :
length(local.jwt_payload) % 4 == 3 ? "=" :
""
)
jwt_username = try(
regex(
"/users/([^/]+)",
jsondecode(base64decode("${local.jwt_payload}${local.payload_padding}"))["sub"]
)[0],
""
)
username = coalesce(
local.jwt_username != "" ? local.jwt_username : null,
var.username_field == "email" ? data.coder_workspace_owner.me.email : data.coder_workspace_owner.me.name
)
jfrog_host = split("://", var.jfrog_url)[1] jfrog_host = split("://", var.jfrog_url)[1]
common_values = { common_values = {
JFROG_URL = var.jfrog_url JFROG_URL = var.jfrog_url

View File

@ -99,7 +99,13 @@ if [ -z "${HAS_MAVEN}" ]; then
not_configured maven not_configured maven
else else
echo "☕ Configuring maven..." echo "☕ Configuring maven..."
jf mvnc --global --repo-resolve "${REPOSITORY_MAVEN}" jf mvnc --global \
--server-id-resolve="${JFROG_SERVER_ID}" \
--repo-resolve-releases "${REPOSITORY_MAVEN}" \
--repo-resolve-snapshots "${REPOSITORY_MAVEN}" \
--server-id-deploy="${JFROG_SERVER_ID}" \
--repo-deploy-releases "${REPOSITORY_MAVEN}" \
--repo-deploy-snapshots "${REPOSITORY_MAVEN}"
# Create Maven config directory if it doesn't exist # Create Maven config directory if it doesn't exist
mkdir -p ~/.m2 mkdir -p ~/.m2
cat << EOF > ~/.m2/settings.xml cat << EOF > ~/.m2/settings.xml

View File

@ -13,7 +13,7 @@ Install the JF CLI and authenticate package managers with Artifactory using Arti
```tf ```tf
module "jfrog" { module "jfrog" {
source = "registry.coder.com/coder/jfrog-token/coder" source = "registry.coder.com/coder/jfrog-token/coder"
version = "1.2.0" version = "1.2.1"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
jfrog_url = "https://XXXX.jfrog.io" jfrog_url = "https://XXXX.jfrog.io"
artifactory_access_token = var.artifactory_access_token artifactory_access_token = var.artifactory_access_token
@ -42,7 +42,7 @@ For detailed instructions, please see this [guide](https://coder.com/docs/v2/lat
```tf ```tf
module "jfrog" { module "jfrog" {
source = "registry.coder.com/coder/jfrog-token/coder" source = "registry.coder.com/coder/jfrog-token/coder"
version = "1.2.0" version = "1.2.1"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
jfrog_url = "https://YYYY.jfrog.io" jfrog_url = "https://YYYY.jfrog.io"
artifactory_access_token = var.artifactory_access_token # An admin access token artifactory_access_token = var.artifactory_access_token # An admin access token
@ -81,7 +81,7 @@ The [JFrog extension](https://open-vsx.org/extension/JFrog/jfrog-vscode-extensio
```tf ```tf
module "jfrog" { module "jfrog" {
source = "registry.coder.com/coder/jfrog-token/coder" source = "registry.coder.com/coder/jfrog-token/coder"
version = "1.2.0" version = "1.2.1"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
jfrog_url = "https://XXXX.jfrog.io" jfrog_url = "https://XXXX.jfrog.io"
artifactory_access_token = var.artifactory_access_token artifactory_access_token = var.artifactory_access_token
@ -101,7 +101,7 @@ data "coder_workspace" "me" {}
module "jfrog" { module "jfrog" {
source = "registry.coder.com/coder/jfrog-token/coder" source = "registry.coder.com/coder/jfrog-token/coder"
version = "1.2.0" version = "1.2.1"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
jfrog_url = "https://XXXX.jfrog.io" jfrog_url = "https://XXXX.jfrog.io"
artifactory_access_token = var.artifactory_access_token artifactory_access_token = var.artifactory_access_token

View File

@ -197,9 +197,13 @@ EOF`;
const coderScript = findResourceInstance(state, "coder_script"); const coderScript = findResourceInstance(state, "coder_script");
expect(coderScript.script).toContain( expect(coderScript.script).toContain("jf mvnc --global");
'jf mvnc --global --repo-resolve "central"', expect(coderScript.script).toContain('--server-id-resolve="0"');
); expect(coderScript.script).toContain('--repo-resolve-releases "central"');
expect(coderScript.script).toContain('--repo-resolve-snapshots "central"');
expect(coderScript.script).toContain('--server-id-deploy="0"');
expect(coderScript.script).toContain('--repo-deploy-releases "central"');
expect(coderScript.script).toContain('--repo-deploy-snapshots "central"');
expect(coderScript.script).toContain("<servers>"); expect(coderScript.script).toContain("<servers>");
expect(coderScript.script).toContain("<id>central</id>"); expect(coderScript.script).toContain("<id>central</id>");

View File

@ -98,7 +98,13 @@ if [ -z "${HAS_MAVEN}" ]; then
not_configured maven not_configured maven
else else
echo "☕ Configuring maven..." echo "☕ Configuring maven..."
jf mvnc --global --repo-resolve "${REPOSITORY_MAVEN}" jf mvnc --global \
--server-id-resolve="${JFROG_SERVER_ID}" \
--repo-resolve-releases "${REPOSITORY_MAVEN}" \
--repo-resolve-snapshots "${REPOSITORY_MAVEN}" \
--server-id-deploy="${JFROG_SERVER_ID}" \
--repo-deploy-releases "${REPOSITORY_MAVEN}" \
--repo-deploy-snapshots "${REPOSITORY_MAVEN}"
# Create Maven config directory if it doesn't exist # Create Maven config directory if it doesn't exist
mkdir -p ~/.m2 mkdir -p ~/.m2
cat << EOF > ~/.m2/settings.xml cat << EOF > ~/.m2/settings.xml