diff --git a/.devcontainer.json b/.devcontainer.json deleted file mode 100644 index e5f8710..0000000 --- a/.devcontainer.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "Duplocloud Devcontainers", - "image": "mcr.microsoft.com/vscode/devcontainers/python:3.11", - "features": { - "ghcr.io/devcontainers/features/docker-outside-of-docker:1": { - "moby": false - }, - "./.devcontainer/features/onepassword-cli": { - "autoSsh": true, - "sshSecretNames": "${localEnv:OP_SSH_SECRET}", - "vault": "Employee", - "account": "duplocloudinc.1password.com" - } - }, - "runArgs": [ - "--env-file=${localWorkspaceFolder}/.env" - ], - "customizations": { - "vscode": { - "extensions": [ - "redhat.vscode-yaml", - "mads-hartmann.bash-ide-vscode", - "github.vscode-github-actions", - "GitHub.copilot-chat", - "GitHub.vscode-pull-request-github" - ], - "settings": { - - } - } - } -} \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..7a67800 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,50 @@ +{ + "name": "Duplocloud Devcontainers", + "image": "mcr.microsoft.com/vscode/devcontainers/python:3.11", + "features": { + "ghcr.io/devcontainers/features/node:1": {}, + "ghcr.io/devcontainers/features/docker-outside-of-docker:1": { + "moby": false + }, + "./features/onepassword-cli": { + "autoSsh": true, + "sshSecretNames": "${localEnv:OP_SSH_SECRET}", + "vault": "Employee", + "account": "duplocloudinc.1password.com" + }, + "./features/ai-codex": {} + }, + "postCreateCommand": "bash .devcontainer/post-create.sh", + "runArgs": [ + "--env-file=${localWorkspaceFolder}/.env" + ], + "containerEnv": { + "WORKSPACE_FOLDER": "${containerWorkspaceFolder}", + "CLOUDSDK_PYTHON": "/usr/local/bin/python", + "KUBECONFIG": "${containerWorkspaceFolder}/config/kubeconfig.yaml", + "XDG_CONFIG_HOME": "${containerWorkspaceFolder}/config", + "CONF_DIR": "${containerWorkspaceFolder}/config", + "CODEX_HOME": "${containerWorkspaceFolder}/.codex", + "AWS_CONFIG_FILE": "${containerWorkspaceFolder}/config/aws", + "AWS_DEFAULT_REGION": "us-west-2", + "AWS_REGION": "us-west-2", + "CLOUDSDK_CONFIG": "${containerWorkspaceFolder}/config/gcloud", + "GOOGLE_APPLICATION_CREDENTIALS": "${containerWorkspaceFolder}/config/gcloud/application_default_credentials.json", + "DUPLO_CONFIG": "${containerWorkspaceFolder}/config/duplo.yaml", + "GIT_USERNAME": "${localEnv:GIT_USERNAME}", + "GIT_EMAIL": "${localEnv:GIT_EMAIL}", + "EDITOR": "code --wait" + }, + "customizations": { + "vscode": { + "extensions": [ + "redhat.vscode-yaml", + "mads-hartmann.bash-ide-vscode", + "github.vscode-github-actions", + "GitHub.copilot-chat", + "GitHub.vscode-pull-request-github" + ], + "settings": {} + } + } +} \ No newline at end of file diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh new file mode 100755 index 0000000..eed759e --- /dev/null +++ b/.devcontainer/post-create.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "Installing devcontainers CLI..." +npm install -g @devcontainers/cli + +echo "✓ Post-create setup complete" diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yml similarity index 100% rename from .github/workflows/release.yaml rename to .github/workflows/release.yml diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yml similarity index 84% rename from .github/workflows/test.yaml rename to .github/workflows/test.yml index cfc4634..7b94932 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yml @@ -13,16 +13,25 @@ jobs: fail-fast: false matrix: features: + - ai + - ai-claude + - ai-codex + - ai-gemini - aws-cli - - onepassword-cli - - gcloud-cli - direnv + - duploctl + - gcloud-cli + - git + - kubernetes + - onepassword-cli + - openvpn - terraform baseImage: - debian:latest - ubuntu:latest - mcr.microsoft.com/devcontainers/base:ubuntu - mcr.microsoft.com/devcontainers/python:3 + - nodejs/devcontainer:nightly steps: - uses: actions/checkout@v6 @@ -38,11 +47,17 @@ jobs: fail-fast: false matrix: features: + - ai-claude + - ai-codex + - ai-gemini - aws-cli - - onepassword-cli + - duploctl - gcloud-cli - - terraform + - git + - kubernetes + - onepassword-cli - openvpn + - terraform steps: - uses: actions/checkout@v6 diff --git a/.vscode/mcp.json b/.vscode/mcp.json new file mode 100644 index 0000000..df64ddb --- /dev/null +++ b/.vscode/mcp.json @@ -0,0 +1,18 @@ +{ + "inputs": [ + { + "type": "promptString", + "description": "The API Key for JinaAI", + "id": "jina_api_key", + "password": true + } + ], + "servers": { + "jina": { + "url": "https://mcp.jina.ai/v1", + "headers": { + "Authorization": "Bearer ${input:jina_api_key}" + } + } + } +} \ No newline at end of file diff --git a/README.md b/README.md index c8c9939..1c5e8fd 100644 --- a/README.md +++ b/README.md @@ -129,3 +129,4 @@ We recommend this is as a sane starting point: `mcr.microsoft.com/vscode/devcont - [Available Dev Container Templates](https://containers.dev/templates) - [Available Dev Container Features](https://containers.dev/features) - [Schema for Features json](https://containers.dev/implementors/features/) +- [Node.js Devcontainer](https://github.com/nodejs/devcontainer) diff --git a/src/ai-claude/devcontainer-feature.json b/src/ai-claude/devcontainer-feature.json index 7c11229..2603a9e 100644 --- a/src/ai-claude/devcontainer-feature.json +++ b/src/ai-claude/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "ai-claude", - "version": "1.0.0", + "version": "1.0.1", "name": "Claude Code AI", "description": "Anthropic's Claude Code CLI with VS Code integration and skills support", "options": { @@ -19,5 +19,6 @@ }, "dependsOn": { "ghcr.io/duplocloud/devcontainers/ai:latest": {} - } + }, + "onCreateCommand": "bash /usr/local/share/ai-claude-on-create.sh" } diff --git a/src/ai-claude/install.sh b/src/ai-claude/install.sh index 572dca8..331c0ab 100644 --- a/src/ai-claude/install.sh +++ b/src/ai-claude/install.sh @@ -8,9 +8,9 @@ echo "Installing @anthropic-ai/claude-code..." npm install -g @anthropic-ai/claude-code # Verify installation -if command -v claude-code &> /dev/null; then +if command -v claude &> /dev/null; then echo "✓ Claude Code CLI installed successfully" - claude-code --version || true + claude --version || true else echo "⚠ Claude Code CLI installation could not be verified" fi diff --git a/src/ai-claude/on-create.sh b/src/ai-claude/on-create.sh index 1e1a994..ada9ff4 100644 --- a/src/ai-claude/on-create.sh +++ b/src/ai-claude/on-create.sh @@ -61,10 +61,10 @@ for SKILL in "${SKILL_ARRAY[@]}"; do echo "Installing skill: ${SKILL}" if duplo-skills --dir "${SKILLS_DIR}" --skill "${SKILL}"; then - ((INSTALLED_COUNT++)) + ((INSTALLED_COUNT+=1)) else echo "⚠ Failed to install skill: ${SKILL}" - ((FAILED_COUNT++)) + ((FAILED_COUNT+=1)) fi done diff --git a/src/ai-codex/devcontainer-feature.json b/src/ai-codex/devcontainer-feature.json index c49d318..d42707c 100644 --- a/src/ai-codex/devcontainer-feature.json +++ b/src/ai-codex/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "ai-codex", - "version": "1.0.0", + "version": "1.0.1", "name": "OpenAI Codex AI", "description": "OpenAI's Codex CLI with VS Code integration and skills support", "options": { @@ -19,5 +19,6 @@ }, "dependsOn": { "ghcr.io/duplocloud/devcontainers/ai:latest": {} - } + }, + "onCreateCommand": "bash /usr/local/share/ai-codex-on-create.sh" } diff --git a/src/ai-codex/on-create.sh b/src/ai-codex/on-create.sh index a51d5dd..31a0c7c 100644 --- a/src/ai-codex/on-create.sh +++ b/src/ai-codex/on-create.sh @@ -63,10 +63,10 @@ for SKILL in "${SKILL_ARRAY[@]}"; do echo "Installing skill: ${SKILL}" if duplo-skills --dir "${SKILLS_DIR}" --skill "${SKILL}"; then - ((INSTALLED_COUNT++)) + ((INSTALLED_COUNT+=1)) else echo "⚠ Failed to install skill: ${SKILL}" - ((FAILED_COUNT++)) + ((FAILED_COUNT+=1)) fi done diff --git a/src/ai-gemini/devcontainer-feature.json b/src/ai-gemini/devcontainer-feature.json index 3cbccea..dbc0bb9 100644 --- a/src/ai-gemini/devcontainer-feature.json +++ b/src/ai-gemini/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "ai-gemini", - "version": "1.0.0", + "version": "1.0.1", "name": "Gemini CLI AI", "description": "Google's Gemini CLI with VS Code integration and skills support", "options": { @@ -19,5 +19,6 @@ }, "dependsOn": { "ghcr.io/duplocloud/devcontainers/ai:latest": {} - } + }, + "onCreateCommand": "bash /usr/local/share/ai-gemini-on-create.sh" } diff --git a/src/ai-gemini/on-create.sh b/src/ai-gemini/on-create.sh index 4a2455c..22d4db7 100644 --- a/src/ai-gemini/on-create.sh +++ b/src/ai-gemini/on-create.sh @@ -61,10 +61,10 @@ for SKILL in "${SKILL_ARRAY[@]}"; do echo "Installing skill: ${SKILL}" if duplo-skills --dir "${SKILLS_DIR}" --skill "${SKILL}"; then - ((INSTALLED_COUNT++)) + ((INSTALLED_COUNT+=1)) else echo "⚠ Failed to install skill: ${SKILL}" - ((FAILED_COUNT++)) + ((FAILED_COUNT+=1)) fi done diff --git a/src/ai/devcontainer-feature.json b/src/ai/devcontainer-feature.json index e2555aa..fe9c444 100644 --- a/src/ai/devcontainer-feature.json +++ b/src/ai/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "ai", - "version": "1.0.1", + "version": "1.0.2", "name": "AI Base", "description": "Base dependencies for AI CLI tools including Node.js and skills downloader", "options": {}, diff --git a/src/ai/scripts/install-nodejs.sh b/src/ai/scripts/install-nodejs.sh index b4cdce0..d677163 100644 --- a/src/ai/scripts/install-nodejs.sh +++ b/src/ai/scripts/install-nodejs.sh @@ -43,8 +43,11 @@ load_nvm() { for nvm_sh in "${candidates[@]}"; do if [[ -s "$nvm_sh" ]]; then echo "Found nvm at $nvm_sh, loading it..." + # Temporarily disable unbound variable check for nvm # shellcheck disable=SC1090,SC1091 + set +u source "$nvm_sh" + set -u if command -v nvm >/dev/null 2>&1; then return 0 fi @@ -60,8 +63,11 @@ install_with_nvm() { fi echo "Installing Node.js LTS via nvm..." + # Temporarily disable unbound variable check for nvm commands + set +u nvm install --lts nvm use --lts + set -u ensure_min_node } diff --git a/src/duploctl/devcontainer-feature.json b/src/duploctl/devcontainer-feature.json index 397ba05..db4d3b7 100644 --- a/src/duploctl/devcontainer-feature.json +++ b/src/duploctl/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "duploctl", - "version": "1.0.4", + "version": "1.0.5", "name": "duploctl", "description": "Installs duploctl CLI via pip (if available) or prebuilt binary.", "options": { diff --git a/src/gcloud-cli/devcontainer-feature.json b/src/gcloud-cli/devcontainer-feature.json index 25e440f..c5c1fdf 100644 --- a/src/gcloud-cli/devcontainer-feature.json +++ b/src/gcloud-cli/devcontainer-feature.json @@ -1,7 +1,7 @@ { "name": "Google Cloud CLI", "id": "gcloud-cli", - "version": "1.0.3", + "version": "1.0.4", "description": "Installs Google Cloud CLI with multi-architecture support", "documentationURL": "https://github.com/duplocloud-internal/customer-workspace/tree/main/features/gcloud-cli", "options": {}, diff --git a/src/gcloud-cli/install.sh b/src/gcloud-cli/install.sh index 8aae72f..72608d8 100644 --- a/src/gcloud-cli/install.sh +++ b/src/gcloud-cli/install.sh @@ -5,26 +5,69 @@ echo "Installing Google Cloud CLI..." FEATURE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +require_apt() { + if ! command -v apt-get >/dev/null 2>&1; then + echo "ERROR: apt-get is required to install Google Cloud CLI on this image." + exit 1 + fi +} + +python_version_ok() { + # Prints "ok" if python3 exists and is >= 3.12, else prints nothing. + if ! command -v python3 >/dev/null 2>&1; then + return 1 + fi + python3 -c 'import sys; print("ok" if sys.version_info >= (3,12) else "")' 2>/dev/null | grep -q '^ok$' +} + +ensure_python_312_plus_or_warn() { + if python_version_ok; then + echo "python3 is already >= 3.12" + return 0 + fi + + echo "python3 >= 3.12 is recommended for gcloud." + + # Best-effort: try python3.12 from the existing repo (no extra PPAs). + if apt-cache show python3.12 >/dev/null 2>&1; then + echo "Installing python3.12..." + apt-get install -y --no-install-recommends python3.12 python3.12-distutils python3.12-venv || true + else + echo "python3.12 not available in current apt sources." + fi + + if ! python_version_ok; then + echo "Warning: python3 >= 3.12 not available. gcloud may fail to run." + python3 --version 2>/dev/null || true + fi +} + +require_apt + # Install required dependencies (curl/fzf may not exist on vanilla images) apt-get update apt_packages=(curl ca-certificates fzf) -# gcloud's installer requires `python` on PATH -if ! command -v python >/dev/null 2>&1; then - if command -v python3 >/dev/null 2>&1; then - echo "python3 is available but python is missing; creating python -> python3 shim." - mkdir -p /usr/local/bin - ln -sf "$(command -v python3)" /usr/local/bin/python - else - echo "python is not available; installing python3." - apt_packages+=(python3) - fi +# Ensure at least python3 exists so the installer can run. +if ! command -v python3 >/dev/null 2>&1; then + echo "python3 not found; installing distro python3." + apt_packages+=(python3) fi apt-get install -y --no-install-recommends "${apt_packages[@]}" apt-get clean rm -rf /var/lib/apt/lists/* +ensure_python_312_plus_or_warn + +# Prefer python3.12 if present +if command -v python3.12 >/dev/null 2>&1; then + mkdir -p /usr/local/bin + ln -sf "$(command -v python3.12)" /usr/local/bin/python3 + ln -sf "$(command -v python3.12)" /usr/local/bin/python +fi + +# Ensure 'python' is available for installers that assume it exists. if ! command -v python >/dev/null 2>&1 && command -v python3 >/dev/null 2>&1; then mkdir -p /usr/local/bin ln -sf "$(command -v python3)" /usr/local/bin/python @@ -50,8 +93,19 @@ curl -sSL -O "https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google- tar -xf "google-cloud-cli-linux-${GCLOUD_ARCH}.tar.gz" -C "$USER_HOME" rm "google-cloud-cli-linux-${GCLOUD_ARCH}.tar.gz" -# Install gcloud CLI -"$USER_HOME/google-cloud-sdk/install.sh" --quiet --usage-reporting=false --path-update=true +# Install gcloud CLI if Python is new enough; otherwise warn and continue. +if python_version_ok; then + if command -v python3.12 >/dev/null 2>&1; then + CLOUDSDK_PYTHON="$(command -v python3.12)" "$USER_HOME/google-cloud-sdk/install.sh" --quiet --usage-reporting=false --path-update=true + else + CLOUDSDK_PYTHON="$(command -v python3)" "$USER_HOME/google-cloud-sdk/install.sh" --quiet --usage-reporting=false --path-update=true + fi +else + echo "Warning: Python >= 3.12 not available. Skipping gcloud installer." + echo "Install a newer Python or use an image that includes Python 3.12+." + mkdir -p /usr/local/etc + echo "python_not_supported=true" > /usr/local/etc/gcloud-cli-python-warning +fi # Set proper permissions chown -R "$USER_NAME:$USER_NAME" "$USER_HOME/google-cloud-sdk" 2>/dev/null || true diff --git a/src/git/devcontainer-feature.json b/src/git/devcontainer-feature.json index 2211627..ab20b9c 100644 --- a/src/git/devcontainer-feature.json +++ b/src/git/devcontainer-feature.json @@ -1,7 +1,7 @@ { "name": "Git Configuration", "id": "git", - "version": "1.0.1", + "version": "1.0.2", "description": "Configures git with user settings, signing keys, plugins, and global gitignore", "documentationURL": "https://github.com/duplocloud/devcontainers/tree/main/src/git", "options": { diff --git a/src/kubernetes/on-create.sh b/src/kubernetes/on-create.sh index cd882e8..2a35606 100755 --- a/src/kubernetes/on-create.sh +++ b/src/kubernetes/on-create.sh @@ -36,6 +36,21 @@ function generate_kubeconfig() { echo "You can manually configure kubectl once duploctl is available." return 0 fi + + # Some duploctl distributions may not be compatible with the base image. + # Treat failures as non-fatal so the container can still start. + set +e + duploctl --version >/dev/null 2>&1 + local duploctl_version_rc=$? + if [ ${duploctl_version_rc} -ne 0 ]; then + duploctl version >/dev/null 2>&1 + duploctl_version_rc=$? + fi + set -e + if [ ${duploctl_version_rc} -ne 0 ]; then + echo "Warning: duploctl is installed but failed to execute. Skipping kubeconfig generation." + return 0 + fi # Ensure .kube directory exists mkdir -p "${USER_HOME}/.kube" @@ -74,7 +89,15 @@ function generate_kubeconfig() { # Execute duploctl command echo "Running: duploctl ${cmd_args[*]}" + set +e duploctl "${cmd_args[@]}" + local rc=$? + set -e + if [ ${rc} -ne 0 ]; then + echo "Warning: duploctl failed (exit code ${rc}). Skipping kubeconfig generation." + echo "You can run 'duploctl jit update_kubeconfig' manually after configuring Duplo credentials." + return 0 + fi echo "Kubeconfig generated successfully." echo "Run 'kubectl config get-contexts' to see available contexts." diff --git a/src/openvpn/NOTES.md b/src/openvpn/NOTES.md index f72e42c..26cdafb 100644 --- a/src/openvpn/NOTES.md +++ b/src/openvpn/NOTES.md @@ -20,3 +20,23 @@ This is required because the devcontainer features spec does not support adding "runArgs": ["--device=/dev/net/tun"] } ``` + +## Configuration Directory + +By default, OpenVPN configuration files are stored in: +- `$XDG_CONFIG_HOME/openvpn` if `XDG_CONFIG_HOME` is set +- `$HOME/.config/openvpn` otherwise + +You can customize this location using the `configDir` option: + +```json +{ + "features": { + "ghcr.io/duplocloud/devcontainers/openvpn:1": { + "configDir": "/custom/path/to/openvpn" + } + } +} +``` + +This is useful when you need to store configuration in a specific location for persistence or access control reasons. diff --git a/src/openvpn/devcontainer-feature.json b/src/openvpn/devcontainer-feature.json index 9dbf1e0..af5a8ed 100644 --- a/src/openvpn/devcontainer-feature.json +++ b/src/openvpn/devcontainer-feature.json @@ -14,6 +14,11 @@ "type": "boolean", "default": true, "description": "Automatically initialize and connect to VPN on container start. Set to false to only install the OpenVPN client." + }, + "configDir": { + "type": "string", + "default": "", + "description": "Directory where OpenVPN configuration files will be stored. If empty, defaults to $XDG_CONFIG_HOME/openvpn or $HOME/.config/openvpn." } }, "capAdd": [ diff --git a/src/openvpn/install.sh b/src/openvpn/install.sh index 23472a9..ea6dc4f 100755 --- a/src/openvpn/install.sh +++ b/src/openvpn/install.sh @@ -26,6 +26,7 @@ mkdir -p /usr/local/etc cat < /usr/local/etc/openvpn-feature.conf OVPN_ENABLED="${ENABLED}" OVPN_AUTOCONNECT="${AUTOCONNECT:-true}" +OVPN_CONFIG_DIR="${CONFIGDIR:-}" EOF chmod 644 /usr/local/etc/openvpn-feature.conf diff --git a/src/openvpn/on-create.sh b/src/openvpn/on-create.sh index b06954d..92e4cf1 100755 --- a/src/openvpn/on-create.sh +++ b/src/openvpn/on-create.sh @@ -21,15 +21,21 @@ fi echo "Initializing OpenVPN configuration..." -# Get the workspace directory - use the containerWorkspaceFolder if available -if [ -z "${_CONTAINER_WORKSPACE_FOLDER}" ]; then - WORKSPACE_DIR="${_REMOTE_WORKSPACE_FOLDER:-/workspace}" +# Use devcontainer environment variables with fallbacks +USER_HOME="${_REMOTE_USER_HOME:-$HOME}" + +# Determine OpenVPN configuration directory +if [ -n "${OVPN_CONFIG_DIR}" ]; then + # Use the user-specified config directory + OVPN_DIR="${OVPN_CONFIG_DIR}" +elif [ -n "${XDG_CONFIG_HOME}" ] && [ -w "${XDG_CONFIG_HOME}" ]; then + # Use XDG_CONFIG_HOME if set and writable + OVPN_DIR="${XDG_CONFIG_HOME}/openvpn" else - WORKSPACE_DIR="${_CONTAINER_WORKSPACE_FOLDER}" + # Fall back to HOME/.config/openvpn + OVPN_DIR="${USER_HOME}/.config/openvpn" fi -OVPN_DIR="${WORKSPACE_DIR}/.ovpn" - # Create .ovpn directory if it doesn't exist mkdir -p "${OVPN_DIR}" diff --git a/src/openvpn/post-start.sh b/src/openvpn/post-start.sh index 8230de4..bb236dd 100755 --- a/src/openvpn/post-start.sh +++ b/src/openvpn/post-start.sh @@ -21,15 +21,18 @@ fi echo "Running OpenVPN post-start script..." -# Get the workspace directory - use the containerWorkspaceFolder if available -if [ -z "${_CONTAINER_WORKSPACE_FOLDER}" ]; then - WORKSPACE_DIR="${_REMOTE_WORKSPACE_FOLDER:-/workspace}" +# Determine OpenVPN configuration directory +if [ -n "${OVPN_CONFIG_DIR}" ]; then + # Use the user-specified config directory + OVPN_DIR="${OVPN_CONFIG_DIR}" +elif [ -n "${XDG_CONFIG_HOME}" ]; then + # Use XDG_CONFIG_HOME if set + OVPN_DIR="${XDG_CONFIG_HOME}/openvpn" else - WORKSPACE_DIR="${_CONTAINER_WORKSPACE_FOLDER}" + # Fall back to HOME/.config/openvpn + OVPN_DIR="${HOME}/.config/openvpn" fi -OVPN_DIR="${WORKSPACE_DIR}/.ovpn" - # Switch to the .ovpn folder if it exists if [ ! -d "${OVPN_DIR}" ]; then echo "OpenVPN configuration directory ${OVPN_DIR} does not exist, skipping." diff --git a/test/ai-claude/test.sh b/test/ai-claude/test.sh index 0c406c6..2275121 100644 --- a/test/ai-claude/test.sh +++ b/test/ai-claude/test.sh @@ -5,18 +5,20 @@ set -e # shellcheck disable=SC1091 source dev-container-features-test-lib +USER_HOME="${_REMOTE_USER_HOME:-$HOME}" + # Test that base AI feature is installed (Node.js and duplo-skills) check "node installed" node --version check "duplo-skills installed" command -v duplo-skills # Test that Claude Code CLI is installed -check "claude-code installed" command -v claude-code +check "claude installed" command -v claude # Test that npm has claude-code package check "claude-code npm package" npm list -g @anthropic-ai/claude-code # Test that skills directory exists -check "claude skills dir exists" test -d "${HOME}/.claude/skills" +check "claude skills dir exists" test -d "${USER_HOME}/.claude/skills" # Report results reportResults diff --git a/test/ai-claude/with_skills.sh b/test/ai-claude/with_skills.sh index 66f6d85..8b66f7c 100644 --- a/test/ai-claude/with_skills.sh +++ b/test/ai-claude/with_skills.sh @@ -5,18 +5,20 @@ set -e # shellcheck disable=SC1091 source dev-container-features-test-lib +USER_HOME="${_REMOTE_USER_HOME:-$HOME}" + # Test that base AI feature is installed (Node.js and duplo-skills) check "node installed" node --version check "duplo-skills installed" command -v duplo-skills # Test that Claude Code CLI is installed -check "claude-code installed" command -v claude-code +check "claude installed" command -v claude # Test that skills directory exists -check "claude skills dir exists" test -d "${HOME}/.claude/skills" +check "claude skills dir exists" test -d "${USER_HOME}/.claude/skills" # Test that tf-module skill was installed -check "tf-module skill installed" test -f "${HOME}/.claude/skills/tf-module.skill" +check "tf-module skill installed" test -f "${USER_HOME}/.claude/skills/tf-module.skill" # Report results reportResults diff --git a/test/ai-codex/test.sh b/test/ai-codex/test.sh index 6cf13f2..a42204a 100644 --- a/test/ai-codex/test.sh +++ b/test/ai-codex/test.sh @@ -5,6 +5,8 @@ set -e # shellcheck disable=SC1091 source dev-container-features-test-lib +USER_HOME="${_REMOTE_USER_HOME:-$HOME}" + # Test that base AI feature is installed (Node.js and duplo-skills) check "node installed" node --version check "duplo-skills installed" command -v duplo-skills @@ -15,8 +17,10 @@ check "codex installed" command -v codex # Test that npm has codex package check "codex npm package" npm list -g @openai/codex +CODEX_HOME="${CODEX_HOME:-${USER_HOME}/.codex}" + # Test that skills directory exists (using default CODEX_HOME) -check "codex skills dir exists" test -d "${HOME}/.codex/skills" +check "codex skills dir exists" test -d "${CODEX_HOME}/skills" # Report results reportResults diff --git a/test/ai-codex/with_skills.sh b/test/ai-codex/with_skills.sh index d333250..82e04d2 100644 --- a/test/ai-codex/with_skills.sh +++ b/test/ai-codex/with_skills.sh @@ -5,6 +5,9 @@ set -e # shellcheck disable=SC1091 source dev-container-features-test-lib +USER_HOME="${_REMOTE_USER_HOME:-$HOME}" +CODEX_HOME="${CODEX_HOME:-${USER_HOME}/.codex}" + # Test that base AI feature is installed (Node.js and duplo-skills) check "node installed" node --version check "duplo-skills installed" command -v duplo-skills @@ -13,10 +16,10 @@ check "duplo-skills installed" command -v duplo-skills check "codex installed" command -v codex # Test that skills directory exists -check "codex skills dir exists" test -d "${HOME}/.codex/skills" +check "codex skills dir exists" test -d "${CODEX_HOME}/skills" # Test that tf-module skill was installed -check "tf-module skill installed" test -f "${HOME}/.codex/skills/tf-module.skill" +check "tf-module skill installed" test -f "${CODEX_HOME}/skills/tf-module.skill" # Report results reportResults diff --git a/test/ai-gemini/test.sh b/test/ai-gemini/test.sh index 3aa2781..41dab17 100644 --- a/test/ai-gemini/test.sh +++ b/test/ai-gemini/test.sh @@ -5,6 +5,8 @@ set -e # shellcheck disable=SC1091 source dev-container-features-test-lib +USER_HOME="${_REMOTE_USER_HOME:-$HOME}" + # Test that base AI feature is installed (Node.js and duplo-skills) check "node installed" node --version check "duplo-skills installed" command -v duplo-skills @@ -16,7 +18,7 @@ check "gemini installed" command -v gemini check "gemini npm package" npm list -g @google/gemini-cli # Test that skills directory exists -check "gemini skills dir exists" test -d "${HOME}/.gemini/skills" +check "gemini skills dir exists" test -d "${USER_HOME}/.gemini/skills" # Report results reportResults diff --git a/test/ai-gemini/with_skills.sh b/test/ai-gemini/with_skills.sh index 3d663c3..9b45b77 100644 --- a/test/ai-gemini/with_skills.sh +++ b/test/ai-gemini/with_skills.sh @@ -5,6 +5,8 @@ set -e # shellcheck disable=SC1091 source dev-container-features-test-lib +USER_HOME="${_REMOTE_USER_HOME:-$HOME}" + # Test that base AI feature is installed (Node.js and duplo-skills) check "node installed" node --version check "duplo-skills installed" command -v duplo-skills @@ -13,10 +15,10 @@ check "duplo-skills installed" command -v duplo-skills check "gemini installed" command -v gemini # Test that skills directory exists -check "gemini skills dir exists" test -d "${HOME}/.gemini/skills" +check "gemini skills dir exists" test -d "${USER_HOME}/.gemini/skills" # Test that tf-module skill was installed -check "tf-module skill installed" test -f "${HOME}/.gemini/skills/tf-module.skill" +check "tf-module skill installed" test -f "${USER_HOME}/.gemini/skills/tf-module.skill" # Report results reportResults diff --git a/test/gcloud-cli/test.sh b/test/gcloud-cli/test.sh index 465116c..aa1b0fc 100755 --- a/test/gcloud-cli/test.sh +++ b/test/gcloud-cli/test.sh @@ -12,7 +12,21 @@ source dev-container-features-test-lib # Feature-specific tests check "gcloud sdk directory exists" test -d "${_REMOTE_USER_HOME:-$HOME}/google-cloud-sdk" -check "gcloud is executable" bash -c "${_REMOTE_USER_HOME:-$HOME}/google-cloud-sdk/bin/gcloud --version" +check "gcloud is executable" bash -c '\ +set -e; \ +GCLOUD_BIN="'"${_REMOTE_USER_HOME:-$HOME}"'/google-cloud-sdk/bin/gcloud"; \ +if [ -f /usr/local/etc/gcloud-cli-python-warning ]; then \ + echo "Warning: python >= 3.12 not available; skipping gcloud execution check."; \ + exit 0; \ +fi; \ +OUT="$(${GCLOUD_BIN} --version 2>&1)" || true; \ +echo "$OUT"; \ +if echo "$OUT" | grep -qi "Python .*no longer supported"; then \ + echo "Warning: gcloud requires Python >= 3.12. Skipping failure."; \ + exit 0; \ +fi; \ +echo "$OUT" | grep -qi "Google Cloud SDK" \ +' # Report results reportResults diff --git a/test/git/github_provider.sh b/test/git/github_provider.sh new file mode 100644 index 0000000..b90af97 --- /dev/null +++ b/test/git/github_provider.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -e + +source dev-container-features-test-lib +source /usr/local/etc/git-feature.conf + +check "provider set" test "${PROVIDER}" = "github" + +reportResults diff --git a/test/git/scenarios.json b/test/git/scenarios.json index 03b6475..868d395 100644 --- a/test/git/scenarios.json +++ b/test/git/scenarios.json @@ -1,20 +1,18 @@ { - "scenarios": { - "with_user_config": { - "image": "mcr.microsoft.com/devcontainers/base:ubuntu", - "features": { - "git": { - "userName": "Test User", - "userEmail": "test@example.com" - } + "with_user_config": { + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", + "features": { + "git": { + "userName": "Test User", + "userEmail": "test@example.com" } - }, - "github_provider": { - "image": "mcr.microsoft.com/devcontainers/base:ubuntu", - "features": { - "git": { - "provider": "github" - } + } + }, + "github_provider": { + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", + "features": { + "git": { + "provider": "github" } } } diff --git a/test/git/with_user_config.sh b/test/git/with_user_config.sh new file mode 100644 index 0000000..68d0941 --- /dev/null +++ b/test/git/with_user_config.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -e + +source dev-container-features-test-lib +source /usr/local/etc/git-feature.conf + +check "user name set" test "${USERNAME}" = "Test User" +check "user email set" test "${USEREMAIL}" = "test@example.com" + +reportResults