Compare commits

...

3 Commits

Author SHA1 Message Date
Rowan Smith 3ae8c7dcff feat: support optional installation of vault enterprise binary (#582)
## Description

When using the SAML auth method with Vault and authenticating via CLI it
is required to use the enterprise version of the binary, as SAML support
is not built into the non enterprise version of the CLI. This PR adds an
optional `enterprise` variable to support this.

@matifali can you let me know the appropriate tag command to run to
release this once approved, please?

## Type of Change

- [ ] New module
- [ ] New template
- [ ] Bug fix
- [x] Feature/enhancement
- [ ] Documentation
- [ ] Other

## Module Information

**Path:** `registry/coder/modules/vault-cli`  
**New version:** `v1.1.0`  
**Breaking change:** [ ] Yes [x] No


## Testing & Validation

- [ ] Tests pass (`bun test`)
- [ ] Code formatted (`bun fmt`)
- [x] Changes tested locally

## Related Issues

None
2025-12-08 07:56:03 -06:00
Atif Ali 2cfbe5f69c feat: add vault-cli module with optional token configuration (#575) 2025-12-04 11:11:35 +05:00
35C4n0r 186e0c4de6 feat(module/coder-labs/amp): add mode flag and disable animation (#550)
## Description
1. "mode" flag: Set the agent mode (free, rush, smart) — controls the
model, system prompt, and tool selection.
2. `"amp.terminal.animation": false`: This disables the animation.
3. Update the readme

<!-- Briefly describe what this PR does and why -->

## Type of Change

- [ ] New module
- [ ] New template
- [x] Bug fix
- [x] Feature/enhancement
- [x] Documentation
- [ ] Other

## Module Information

<!-- Delete this section if not applicable -->

**Path:** `registry/coder-labs/modules/amp`  
**New version:** `v2.1.0`  
**Breaking change:** [ ] Yes [X] No

## Testing & Validation

- [X] Tests pass (`bun test`)
- [X] Code formatted (`bun fmt`)
- [x] Changes tested locally

## Related Issues

<!-- Link related issues or write "None" if not applicable -->

---------

Co-authored-by: DevCats <christofer@coder.com>
2025-12-03 20:27:14 +00:00
8 changed files with 628 additions and 15 deletions
@@ -12,12 +12,12 @@ Run [Amp CLI](https://ampcode.com/) in your workspace to access Sourcegraph's AI
```tf
module "amp-cli" {
source = "registry.coder.com/coder-labs/sourcegraph-amp/coder"
version = "2.0.2"
agent_id = coder_agent.example.id
sourcegraph_amp_api_key = var.sourcegraph_amp_api_key
install_sourcegraph_amp = true
agentapi_version = "2.0.2"
source = "registry.coder.com/coder-labs/sourcegraph-amp/coder"
version = "2.1.0"
agent_id = coder_agent.example.id
amp_api_key = var.amp_api_key
install_amp = true
agentapi_version = "latest"
}
```
@@ -48,7 +48,7 @@ variable "amp_api_key" {
module "amp-cli" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/sourcegraph-amp/coder"
amp_version = "2.0.2"
amp_version = "2.1.0"
agent_id = coder_agent.example.id
amp_api_key = var.amp_api_key # recommended for tasks usage
workdir = "/home/coder/project"
@@ -55,7 +55,7 @@ variable "install_agentapi" {
variable "agentapi_version" {
type = string
description = "The version of AgentAPI to install."
default = "v0.10.0"
default = "v0.11.1"
}
variable "cli_app" {
@@ -160,6 +160,16 @@ variable "mcp" {
default = null
}
variable "mode" {
type = string
description = "Set the agent mode (free, rush, smart) — controls the model, system prompt, and tool selection. Default: smart"
default = "smart"
validation {
condition = contains(["", "free", "rush", "smart"], var.mode)
error_message = "Invalid mode. Select one from (free, rush, smart)"
}
}
data "external" "env" {
program = ["sh", "-c", "echo '{\"CODER_AGENT_TOKEN\":\"'$CODER_AGENT_TOKEN'\",\"CODER_AGENT_URL\":\"'$CODER_AGENT_URL'\"}'"]
}
@@ -170,6 +180,7 @@ locals {
default_base_config = jsonencode({
"amp.anthropic.thinking.enabled" = true
"amp.todos.enabled" = true
"amp.terminal.animation" = false
})
user_config = jsondecode(var.base_amp_config != "" ? var.base_amp_config : local.default_base_config)
@@ -237,6 +248,7 @@ module "agentapi" {
ARG_AMP_START_DIRECTORY='${var.workdir}' \
ARG_AMP_TASK_PROMPT='${base64encode(var.ai_prompt)}' \
ARG_REPORT_TASKS='${var.report_tasks}' \
ARG_MODE='${var.mode}' \
/tmp/start.sh
EOT
@@ -1,9 +1,4 @@
#!/bin/bash
if [ -f "$HOME/.bashrc" ]; then
source "$HOME"/.bashrc
fi
set -euo pipefail
# ANSI colors
@@ -29,6 +29,7 @@ echo "--------------------------------"
printf "Workspace: %s\n" "$ARG_AMP_START_DIRECTORY"
printf "Task Prompt: %s\n" "$ARG_AMP_TASK_PROMPT"
printf "ARG_REPORT_TASKS: %s\n" "$ARG_REPORT_TASKS"
printf "ARG_MODE: %s\n" "$ARG_MODE"
echo "--------------------------------"
ensure_command amp
@@ -50,6 +51,13 @@ else
printf "amp_api_key not provided\n"
fi
ARGS=()
if [ -n "$ARG_MODE" ]; then
printf "Running agent in: %s mode" "$ARG_MODE"
ARGS+=(--mode "$ARG_MODE")
fi
if [ -n "$ARG_AMP_TASK_PROMPT" ]; then
if [ "$ARG_REPORT_TASKS" == "true" ]; then
printf "amp task prompt provided : %s" "$ARG_AMP_TASK_PROMPT\n"
@@ -58,8 +66,8 @@ if [ -n "$ARG_AMP_TASK_PROMPT" ]; then
PROMPT="$ARG_AMP_TASK_PROMPT"
fi
# Pipe the prompt into amp, which will be run inside agentapi
agentapi server --type amp --term-width=67 --term-height=1190 -- bash -c "echo \"$PROMPT\" | amp"
agentapi server --type amp --term-width=67 --term-height=1190 -- bash -c "echo \"$PROMPT\" | amp" "${ARGS[@]}"
else
printf "No task prompt given.\n"
agentapi server --type amp --term-width=67 --term-height=1190 -- amp
agentapi server --type amp --term-width=67 --term-height=1190 -- amp "${ARGS[@]}"
fi
+121
View File
@@ -0,0 +1,121 @@
---
display_name: Vault CLI
description: Installs the Hashicorp Vault CLI and optionally configures token authentication
icon: ../../../../.icons/vault.svg
verified: true
tags: [helper, integration, vault, cli]
---
# Vault CLI
Installs the [Vault](https://www.vaultproject.io/) CLI and optionally configures token authentication. This module focuses on CLI installation and can be used standalone or as a base for other authentication methods.
```tf
module "vault_cli" {
source = "registry.coder.com/coder/vault-cli/coder"
version = "1.1.0"
agent_id = coder_agent.example.id
vault_addr = "https://vault.example.com"
}
```
## Prerequisites
The following tools are required in the workspace image:
- **HTTP client**: `curl`, `wget`, or `busybox` (at least one)
- **Archive utility**: `unzip` or `busybox` (at least one)
- **jq**: Optional but recommended for reliable JSON parsing (falls back to sed if not available)
## With Token Authentication
If you have a Vault token, you can provide it to automatically configure authentication:
```tf
module "vault_cli" {
source = "registry.coder.com/coder/vault-cli/coder"
version = "1.1.0"
agent_id = coder_agent.example.id
vault_addr = "https://vault.example.com"
vault_token = var.vault_token # Optional
}
```
## Examples
### Basic Installation (CLI Only)
Install the Vault CLI without any authentication:
```tf
module "vault_cli" {
source = "registry.coder.com/coder/vault-cli/coder"
version = "1.1.0"
agent_id = coder_agent.example.id
vault_addr = "https://vault.example.com"
}
```
### With Specific Version
```tf
module "vault_cli" {
source = "registry.coder.com/coder/vault-cli/coder"
version = "1.1.0"
agent_id = coder_agent.example.id
vault_addr = "https://vault.example.com"
vault_cli_version = "1.15.0"
}
```
### Custom Installation Directory
```tf
module "vault_cli" {
source = "registry.coder.com/coder/vault-cli/coder"
version = "1.1.0"
agent_id = coder_agent.example.id
vault_addr = "https://vault.example.com"
install_dir = "/home/coder/bin"
}
```
### With Vault Enterprise Namespace
For Vault Enterprise users who need to specify a namespace:
```tf
module "vault_cli" {
source = "registry.coder.com/coder/vault-cli/coder"
version = "1.1.0"
agent_id = coder_agent.example.id
vault_addr = "https://vault.example.com"
vault_token = var.vault_token
vault_namespace = "admin/my-namespace"
}
```
### Vault Enterprise Binary
Install the Vault Enterprise binary. This is required if using SAML authentication to Vault:
```tf
module "vault_cli" {
source = "registry.coder.com/coder/vault-cli/coder"
version = "1.1.0"
agent_id = coder_agent.example.id
vault_addr = "https://vault.example.com"
enterprise = true
}
```
## Related Modules
For more advanced authentication methods, see:
- [vault-github](https://registry.coder.com/modules/coder/vault-github) - Authenticate with Vault using GitHub tokens
- [vault-jwt](https://registry.coder.com/modules/coder/vault-jwt) - Authenticate with Vault using OIDC/JWT
For simple token-based authentication, see:
- [vault-token](https://registry.coder.com/modules/coder/vault-token) - Authenticate with Vault using a token
+97
View File
@@ -0,0 +1,97 @@
terraform {
required_version = ">= 1.0"
required_providers {
coder = {
source = "coder/coder"
version = ">= 0.17"
}
}
}
variable "agent_id" {
type = string
description = "The ID of a Coder agent."
}
variable "vault_addr" {
type = string
description = "The address of the Vault server."
}
variable "vault_token" {
type = string
description = "The Vault token to use for authentication. If not provided, only the CLI will be installed."
default = ""
sensitive = true
}
variable "install_dir" {
type = string
description = "The directory to install the Vault CLI to."
default = "/usr/local/bin"
}
variable "vault_cli_version" {
type = string
description = "The version of the Vault CLI to install."
default = "latest"
validation {
condition = var.vault_cli_version == "latest" || can(regex("^[0-9]+\\.[0-9]+\\.[0-9]+$", var.vault_cli_version))
error_message = "vault_cli_version must be either 'latest' or a semantic version (e.g., '1.15.0')."
}
}
variable "vault_namespace" {
type = string
description = "The Vault Enterprise namespace to use. If not provided, no namespace will be configured."
default = null
}
variable "enterprise" {
type = bool
description = "Whether to install the enterprise version of the Vault CLI. Required if using SAML authentication to Vault."
default = false
}
data "coder_workspace" "me" {}
resource "coder_script" "vault_cli" {
agent_id = var.agent_id
display_name = "Vault CLI"
icon = "/icon/vault.svg"
script = templatefile("${path.module}/run.sh", {
VAULT_ADDR = var.vault_addr
VAULT_TOKEN = var.vault_token
INSTALL_DIR = var.install_dir
VAULT_CLI_VERSION = var.vault_cli_version
ENTERPRISE = var.enterprise
})
run_on_start = true
start_blocks_login = true
}
resource "coder_env" "vault_addr" {
agent_id = var.agent_id
name = "VAULT_ADDR"
value = var.vault_addr
}
resource "coder_env" "vault_token" {
count = var.vault_token != "" ? 1 : 0
agent_id = var.agent_id
name = "VAULT_TOKEN"
value = var.vault_token
}
resource "coder_env" "vault_namespace" {
count = var.vault_namespace != null ? 1 : 0
agent_id = var.agent_id
name = "VAULT_NAMESPACE"
value = var.vault_namespace
}
output "vault_cli_version" {
description = "The version of the Vault CLI that was installed."
value = var.vault_cli_version
}
@@ -0,0 +1,176 @@
mock_provider "coder" {}
variables {
agent_id = "test-agent-id"
vault_addr = "https://vault.example.com"
}
run "test_vault_cli_without_token" {
assert {
condition = resource.coder_script.vault_cli.display_name == "Vault CLI"
error_message = "Display name should be 'Vault CLI'"
}
assert {
condition = resource.coder_env.vault_addr.name == "VAULT_ADDR"
error_message = "VAULT_ADDR environment variable should be set"
}
assert {
condition = resource.coder_env.vault_addr.value == "https://vault.example.com"
error_message = "VAULT_ADDR should match the provided vault_addr"
}
assert {
condition = length(resource.coder_env.vault_token) == 0
error_message = "VAULT_TOKEN should not be set when vault_token is not provided"
}
assert {
condition = length(resource.coder_env.vault_namespace) == 0
error_message = "VAULT_NAMESPACE should not be set when vault_namespace is not provided"
}
}
run "test_vault_cli_with_token" {
variables {
vault_token = "test-vault-token"
}
assert {
condition = resource.coder_script.vault_cli.display_name == "Vault CLI"
error_message = "Display name should be 'Vault CLI'"
}
assert {
condition = resource.coder_env.vault_addr.name == "VAULT_ADDR"
error_message = "VAULT_ADDR environment variable should be set"
}
assert {
condition = length(resource.coder_env.vault_token) == 1
error_message = "VAULT_TOKEN should be set when vault_token is provided"
}
assert {
condition = resource.coder_env.vault_token[0].name == "VAULT_TOKEN"
error_message = "VAULT_TOKEN environment variable name should be correct"
}
assert {
condition = resource.coder_env.vault_token[0].value == "test-vault-token"
error_message = "VAULT_TOKEN should match the provided vault_token"
}
}
run "test_vault_cli_custom_version" {
variables {
vault_cli_version = "1.15.0"
}
assert {
condition = output.vault_cli_version == "1.15.0"
error_message = "Vault CLI version output should match the provided version"
}
}
run "test_vault_cli_custom_install_dir" {
variables {
install_dir = "/custom/install/dir"
}
assert {
condition = resource.coder_script.vault_cli.display_name == "Vault CLI"
error_message = "Display name should be 'Vault CLI'"
}
}
run "test_vault_cli_invalid_version" {
command = plan
variables {
vault_cli_version = "invalid-version"
}
expect_failures = [var.vault_cli_version]
}
run "test_vault_cli_valid_semver" {
variables {
vault_cli_version = "1.18.3"
}
assert {
condition = output.vault_cli_version == "1.18.3"
error_message = "Vault CLI version output should match the provided version"
}
}
run "test_vault_cli_rejects_v_prefix" {
command = plan
variables {
vault_cli_version = "v1.18.3"
}
expect_failures = [var.vault_cli_version]
}
run "test_vault_cli_with_namespace" {
variables {
vault_namespace = "admin/my-namespace"
}
assert {
condition = length(resource.coder_env.vault_namespace) == 1
error_message = "VAULT_NAMESPACE should be set when vault_namespace is provided"
}
assert {
condition = resource.coder_env.vault_namespace[0].name == "VAULT_NAMESPACE"
error_message = "VAULT_NAMESPACE environment variable name should be correct"
}
assert {
condition = resource.coder_env.vault_namespace[0].value == "admin/my-namespace"
error_message = "VAULT_NAMESPACE should match the provided vault_namespace"
}
}
run "test_vault_cli_with_token_and_namespace" {
variables {
vault_token = "test-vault-token"
vault_namespace = "admin/my-namespace"
}
assert {
condition = length(resource.coder_env.vault_token) == 1
error_message = "VAULT_TOKEN should be set when vault_token is provided"
}
assert {
condition = length(resource.coder_env.vault_namespace) == 1
error_message = "VAULT_NAMESPACE should be set when vault_namespace is provided"
}
assert {
condition = resource.coder_env.vault_token[0].value == "test-vault-token"
error_message = "VAULT_TOKEN should match the provided vault_token"
}
assert {
condition = resource.coder_env.vault_namespace[0].value == "admin/my-namespace"
error_message = "VAULT_NAMESPACE should match the provided vault_namespace"
}
}
run "test_vault_cli_enterprise" {
variables {
enterprise = true
}
assert {
condition = resource.coder_script.vault_cli.display_name == "Vault CLI"
error_message = "Display name should be 'Vault CLI'"
}
}
+204
View File
@@ -0,0 +1,204 @@
#!/usr/bin/env bash
# Convert all templated variables to shell variables
VAULT_ADDR=${VAULT_ADDR}
VAULT_TOKEN=${VAULT_TOKEN}
INSTALL_DIR=${INSTALL_DIR}
VAULT_CLI_VERSION=${VAULT_CLI_VERSION}
ENTERPRISE=${ENTERPRISE}
# Fetch URL content. If dest is provided, write to file; otherwise output to stdout.
# Usage: fetch <url> [dest]
fetch() {
url="$1"
dest="$${2:-}"
# Detect HTTP client on first run
if [ -z "$${HTTP_CLIENT:-}" ]; then
if command -v curl > /dev/null 2>&1; then
HTTP_CLIENT="curl"
elif command -v wget > /dev/null 2>&1; then
HTTP_CLIENT="wget"
elif command -v busybox > /dev/null 2>&1; then
HTTP_CLIENT="busybox"
else
printf "curl, wget, or busybox is not installed. Please install curl or wget in your image.\n"
return 1
fi
fi
if [ -n "$${dest}" ]; then
# shellcheck disable=SC2195
case "$${HTTP_CLIENT}" in
curl) curl -sSL --fail "$${url}" -o "$${dest}" ;;
wget) wget -O "$${dest}" "$${url}" ;;
busybox) busybox wget -O "$${dest}" "$${url}" ;;
esac
else
# shellcheck disable=SC2195
case "$${HTTP_CLIENT}" in
curl) curl -sSL --fail "$${url}" ;;
wget) wget -qO- "$${url}" ;;
busybox) busybox wget -qO- "$${url}" ;;
esac
fi
}
unzip_safe() {
if command -v unzip > /dev/null 2>&1; then
command unzip "$@"
elif command -v busybox > /dev/null 2>&1; then
busybox unzip "$@"
else
printf "unzip or busybox is not installed. Please install unzip in your image.\n"
return 1
fi
}
install() {
# Get the architecture of the system
ARCH=$(uname -m)
if [ "$${ARCH}" = "x86_64" ]; then
ARCH="amd64"
elif [ "$${ARCH}" = "aarch64" ]; then
ARCH="arm64"
else
printf "Unsupported architecture: %s\n" "$${ARCH}"
return 1
fi
# Determine OS and validate
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
if [ "$${OS}" != "linux" ] && [ "$${OS}" != "darwin" ]; then
printf "Unsupported OS: %s. Only linux and darwin are supported.\n" "$${OS}"
return 1
fi
# Fetch release information from HashiCorp API
if [ "$${VAULT_CLI_VERSION}" = "latest" ]; then
if [ "$${ENTERPRISE}" = "true" ]; then
API_URL="https://api.releases.hashicorp.com/v1/releases/vault/latest?license_class=enterprise"
else
API_URL="https://api.releases.hashicorp.com/v1/releases/vault/latest"
fi
else
# For specific version, append +ent suffix for enterprise
if [ "$${ENTERPRISE}" = "true" ]; then
API_URL="https://api.releases.hashicorp.com/v1/releases/vault/$${VAULT_CLI_VERSION}+ent"
else
API_URL="https://api.releases.hashicorp.com/v1/releases/vault/$${VAULT_CLI_VERSION}"
fi
fi
API_RESPONSE=$(fetch "$${API_URL}")
if [ -z "$${API_RESPONSE}" ]; then
printf "Failed to fetch release information from HashiCorp API.\n"
return 1
fi
# Parse version and download URL from API response
if command -v jq > /dev/null 2>&1; then
VAULT_CLI_VERSION=$(printf '%s' "$${API_RESPONSE}" | jq -r '.version')
DOWNLOAD_URL=$(printf '%s' "$${API_RESPONSE}" | jq -r --arg os "$${OS}" --arg arch "$${ARCH}" '.builds[] | select(.os == $os and .arch == $arch) | .url')
else
VAULT_CLI_VERSION=$(printf '%s' "$${API_RESPONSE}" | sed -n 's/.*"version":"\([^"]*\)".*/\1/p')
# Fallback: construct URL manually if jq not available
DOWNLOAD_URL="https://releases.hashicorp.com/vault/$${VAULT_CLI_VERSION}/vault_$${VAULT_CLI_VERSION}_$${OS}_$${ARCH}.zip"
fi
if [ -z "$${VAULT_CLI_VERSION}" ]; then
printf "Failed to determine Vault version.\n"
return 1
fi
if [ -z "$${DOWNLOAD_URL}" ]; then
printf "Failed to determine download URL for Vault %s (%s/%s).\n" "$${VAULT_CLI_VERSION}" "$${OS}" "$${ARCH}"
return 1
fi
printf "Vault version: %s\n" "$${VAULT_CLI_VERSION}"
# Check if the vault CLI is installed and has the correct version
installation_needed=1
if command -v vault > /dev/null 2>&1; then
CURRENT_VERSION=$(vault version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
if [ "$${CURRENT_VERSION}" = "$${VAULT_CLI_VERSION}" ]; then
printf "Vault version %s is already installed and up-to-date.\n\n" "$${CURRENT_VERSION}"
installation_needed=0
fi
fi
if [ "$${installation_needed}" = "1" ]; then
# Download and install Vault
if [ -z "$${CURRENT_VERSION}" ]; then
printf "Installing Vault CLI ...\n\n"
else
printf "Upgrading Vault CLI from version %s to %s ...\n\n" "$${CURRENT_VERSION}" "$${VAULT_CLI_VERSION}"
fi
# Create temporary directory for download
TEMP_DIR=$(mktemp -d)
cd "$${TEMP_DIR}" || return 1
printf "Downloading from %s\n" "$${DOWNLOAD_URL}"
if ! fetch "$${DOWNLOAD_URL}" vault.zip; then
printf "Failed to download Vault.\n"
rm -rf "$${TEMP_DIR}"
return 1
fi
if ! unzip_safe vault.zip; then
printf "Failed to unzip Vault.\n"
rm -rf "$${TEMP_DIR}"
return 1
fi
# Install to the specified directory
if [ -n "$${INSTALL_DIR}" ] && [ -w "$${INSTALL_DIR}" ]; then
mv vault "$${INSTALL_DIR}/vault"
printf "Vault installed to %s successfully!\n\n" "$${INSTALL_DIR}"
elif [ -n "$${INSTALL_DIR}" ] && [ ! -w "$${INSTALL_DIR}" ]; then
# Try with sudo if install dir specified but not writable
if sudo mv vault "$${INSTALL_DIR}/vault" 2> /dev/null; then
printf "Vault installed to %s successfully!\n\n" "$${INSTALL_DIR}"
else
printf "Warning: Cannot write to %s. " "$${INSTALL_DIR}"
mkdir -p ~/.local/bin
if mv vault ~/.local/bin/vault; then
printf "Installed to ~/.local/bin instead.\n"
printf "Please add ~/.local/bin to your PATH to use vault CLI.\n"
else
printf "Failed to install Vault.\n"
rm -rf "$${TEMP_DIR}"
return 1
fi
fi
elif sudo mv vault /usr/local/bin/vault 2> /dev/null; then
printf "Vault installed successfully!\n\n"
else
mkdir -p ~/.local/bin
if ! mv vault ~/.local/bin/vault; then
printf "Failed to move Vault to local bin.\n"
rm -rf "$${TEMP_DIR}"
return 1
fi
printf "Please add ~/.local/bin to your PATH to use vault CLI.\n"
fi
# Clean up temp directory
rm -rf "$${TEMP_DIR}"
fi
return 0
}
# Run installation
if ! install; then
printf "Failed to install Vault CLI.\n"
exit 1
fi
# Indicate token configuration status
if [ -n "$${VAULT_TOKEN}" ]; then
printf "Vault token has been configured via VAULT_TOKEN environment variable.\n"
else
printf "No Vault token provided. Use 'vault login' or set VAULT_TOKEN to authenticate.\n"
fi