diff --git a/.ansible-lint b/.ansible-lint deleted file mode 100644 index 834e566..0000000 --- a/.ansible-lint +++ /dev/null @@ -1,37 +0,0 @@ ---- -# Ansible-lint configuration -# https://ansible-lint.readthedocs.io/ - -profile: production - -exclude_paths: - - .cache/ - - .github/ - - .terraform/ - - test/ - -skip_list: - - yaml[line-length] # Allow longer lines in YAML - - name[casing] # Don't enforce task naming - -warn_list: - - experimental - - ignore-errors - - no-handler - - unnamed-task - -# Enable specific rules -enable_list: - - args - - empty-string-compare - - no-log-password - - no-same-owner - -# Offline mode (no internet required) -offline: false - -# Use default rules -use_default_rules: true - -# Verbosity -verbosity: 1 diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index ee4d26a..2b25652 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,181 +1,181 @@ -######################################################### TOOLCHAIN VERSIONING ######################################### -# Leave version blank to install latest, or specify a version to pin -# NOTE: Docker, Azure CLI, and Ansible always install latest (scripts do not support version pinning) -ARG UBUNTU_VERSION=24.04 -ARG KUBECTL_VERSION= -ARG HELM_VERSION= -ARG TERRAFORM_VERSION= -ARG TERRAGRUNT_VERSION= -ARG POWERSHELL_VERSION= -ARG KUBELOGIN_VERSION= -ARG YQ_VERSION= -ARG TFLINT_VERSION= -ARG TF_SUMMARIZE_VERSION= -ARG CHECKOV_VERSION= -ARG NODE_VERSION= -ARG DOTNET_VERSION= -ARG AZCOPY_VERSION= -ARG CLAUDE_CODE_VERSION= -ARG CODEX_VERSION= -ARG CSWAP_VERSION= -ARG GITLEAKS_VERSION= -ARG UV_VERSION= -FROM mcr.microsoft.com/devcontainers/base:ubuntu-${UBUNTU_VERSION} AS final - -ARG USERNAME=vscode -ARG KUBECTL_VERSION -ARG HELM_VERSION -ARG TERRAFORM_VERSION -ARG TERRAGRUNT_VERSION -ARG POWERSHELL_VERSION -ARG KUBELOGIN_VERSION -ARG YQ_VERSION -ARG TFLINT_VERSION -ARG TF_SUMMARIZE_VERSION -ARG AZCOPY_VERSION -ARG CHECKOV_VERSION -ARG NODE_VERSION -ARG DOTNET_VERSION -ARG CLAUDE_CODE_VERSION -ARG CODEX_VERSION -ARG CSWAP_VERSION -ARG GITLEAKS_VERSION -ARG UV_VERSION - -# Install base packages -ARG PKGS="\ - gnupg \ - software-properties-common \ - ncat \ - curl \ - wget \ - git \ - unzip \ - jq \ - ca-certificates \ - iputils-ping \ - dnsutils \ - postgresql-client \ - cifs-utils \ - gettext-base \ - " - -# Trust any extra CA certificates dropped into files/certs/ (see the README there). -# The directory may hold nothing but the README, in which case only the public -# roots from the base image are trusted and the build still succeeds. -# REQUESTS_CA_BUNDLE / SSL_CERT_FILE cover Python (az, ansible, checkov, etc.) and -# curl; Node.js ignores the system store, so NODE_EXTRA_CA_CERTS points it at the -# merged bundle explicitly. -COPY files/certs/ /usr/local/share/ca-certificates/extra/ -RUN chmod -R a+r /usr/local/share/ca-certificates/extra && \ - update-ca-certificates -ENV REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt \ - SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt \ - NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt - -# Allow system-wide pip installs (PEP 668 on Ubuntu 24.04+) -# Safe in a devcontainer where system Python is the intended target -ENV PIP_BREAK_SYSTEM_PACKAGES=1 -ENV PIP_ROOT_USER_ACTION=ignore - -# NOTE: apt-get upgrade removed to improve layer cacheability. -# The base image is maintained upstream; weekly scheduled builds (--no-cache) pick up updates. -RUN apt-get update && \ - apt-get install --no-install-recommends -y ${PKGS} && \ - apt-get autoremove --purge -y && \ - rm -rf /var/lib/apt/lists/* - -# Copy all installation scripts -COPY ./files/install/*.sh /tmp/install/ -RUN chmod +x /tmp/install/*.sh - -RUN /tmp/install/install-python-tools.sh - -RUN /tmp/install/install-azure-cli.sh - -RUN /tmp/install/install-azcopy.sh ${AZCOPY_VERSION} - -RUN /tmp/install/install-terraform.sh ${TERRAFORM_VERSION} && \ - /tmp/install/install-terragrunt.sh ${TERRAGRUNT_VERSION} && \ - /tmp/install/install-tflint.sh ${TFLINT_VERSION} && \ - /tmp/install/install-tf-summarize.sh ${TF_SUMMARIZE_VERSION} && \ - /tmp/install/install-checkov.sh ${CHECKOV_VERSION} - -RUN /tmp/install/install-docker.sh - -RUN /tmp/install/install-ansible.sh - -RUN /tmp/install/install-helm.sh ${HELM_VERSION} && \ - /tmp/install/install-kubectl.sh ${KUBECTL_VERSION} && \ - /tmp/install/install-kubelogin.sh ${KUBELOGIN_VERSION} - -RUN /tmp/install/install-powershell.sh ${POWERSHELL_VERSION} - -RUN /tmp/install/install-yq.sh ${YQ_VERSION} && \ - /tmp/install/install-git-crypt.sh && \ - /tmp/install/install-pre-commit.sh && \ - /tmp/install/install-gitleaks.sh ${GITLEAKS_VERSION} - -RUN /tmp/install/install-node.sh ${NODE_VERSION} && \ - /tmp/install/install-codex.sh ${CODEX_VERSION} - -# .NET SDK: system-wide install dir, dev-friendly defaults, and ~/.dotnet/tools -# (per-user global tools) on PATH so `dotnet tool install -g` works out of the box. -ENV DOTNET_ROOT=/usr/share/dotnet \ - DOTNET_CLI_TELEMETRY_OPTOUT=1 \ - DOTNET_NOLOGO=1 \ - PATH="${PATH}:/usr/share/dotnet:/home/${USERNAME}/.dotnet/tools" -RUN /tmp/install/install-dotnet.sh ${DOTNET_VERSION} - -# uv: fast Python package/project manager. Installed for interactive use only — -# nothing in this image is installed through it yet, so it deliberately sits -# late, where a version bump costs no cached layers. Migrating the first tool to -# `uv tool install` means moving this RUN above that tool's own. -RUN /tmp/install/install-uv.sh ${UV_VERSION} - -# claude-swap (`cswap`): Claude Code multi-account switcher. Installed -# system-wide rather than into ~/.local because /home/vscode is a named volume -# only seeded from /tmp-home on first start — a home-tree install would go -# stale for anyone with a pre-existing volume. -RUN /tmp/install/install-cswap.sh ${CSWAP_VERSION} - -# Claude Code is installed natively (not via npm -g) into the vscode user's -# ~/.local tree so that auto-updates work without sudo. It must run as the -# vscode user (with HOME set explicitly — the USER instruction does not set -# HOME) and before the /tmp-home snapshot below, so the install is seeded into -# the home volume on first container start. -USER ${USERNAME} -RUN HOME=/home/${USERNAME} /tmp/install/install-claude-code.sh ${CLAUDE_CODE_VERSION} -USER root - -# Codex bootstrap: ships a config template + interactive init script. -# Users run `codex-init` manually to be prompted for endpoint URL and deployment -# name; values are saved to ~/.config/codex-bootstrap.env and ~/.codex/config.toml is rendered. -COPY ./files/codex/config.toml.tmpl /usr/local/share/codex/config.toml.tmpl -COPY ./files/codex/codex-init /usr/local/bin/codex-init -RUN chmod +x /usr/local/bin/codex-init && \ - chmod 0644 /usr/local/share/codex/config.toml.tmpl - -# Set zsh as default shell and prepare home directory template -RUN chsh -s /bin/zsh ${USERNAME} && \ - cp -r /home/vscode/. /tmp-home - -COPY ./files/home/ /tmp-home/ -COPY ./files/entrypoint.sh /usr/local/bin/entrypoint.sh - -# Source environment in bashrc, install zsh plugins, and cleanup -RUN echo '' >> /tmp-home/.bashrc && \ - echo '# Load DevContainer environment' >> /tmp-home/.bashrc && \ - echo 'if [ -f ~/.environment ]; then' >> /tmp-home/.bashrc && \ - echo ' source ~/.environment' >> /tmp-home/.bashrc && \ - echo 'fi' >> /tmp-home/.bashrc && \ - git -c advice.detachedHead=false clone --branch v0.7.1 --depth 1 https://github.com/zsh-users/zsh-autosuggestions /tmp-home/.oh-my-zsh/custom/plugins/zsh-autosuggestions && \ - git -c advice.detachedHead=false clone --branch 0.8.0 --depth 1 https://github.com/zsh-users/zsh-syntax-highlighting.git /tmp-home/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting && \ - rm -rf /tmp/install-* /tmp/install /var/lib/apt/lists/* - -HEALTHCHECK --interval=60s --timeout=10s --retries=3 \ - CMD terraform version > /dev/null 2>&1 && python3 --version > /dev/null 2>&1 || exit 1 - -USER $USERNAME - -ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] \ No newline at end of file +######################################################### TOOLCHAIN VERSIONING ######################################### +# Leave version blank to install latest, or specify a version to pin +# NOTE: Docker, Azure CLI, and Ansible always install latest (scripts do not support version pinning) +ARG UBUNTU_VERSION=24.04 +ARG KUBECTL_VERSION= +ARG HELM_VERSION= +ARG TERRAFORM_VERSION= +ARG TERRAGRUNT_VERSION= +ARG POWERSHELL_VERSION= +ARG KUBELOGIN_VERSION= +ARG YQ_VERSION= +ARG TFLINT_VERSION= +ARG TF_SUMMARIZE_VERSION= +ARG CHECKOV_VERSION= +ARG NODE_VERSION= +ARG DOTNET_VERSION= +ARG AZCOPY_VERSION= +ARG CLAUDE_CODE_VERSION= +ARG CODEX_VERSION= +ARG CSWAP_VERSION= +ARG GITLEAKS_VERSION= +ARG UV_VERSION= +FROM mcr.microsoft.com/devcontainers/base:ubuntu-${UBUNTU_VERSION} AS final + +ARG USERNAME=vscode +ARG KUBECTL_VERSION +ARG HELM_VERSION +ARG TERRAFORM_VERSION +ARG TERRAGRUNT_VERSION +ARG POWERSHELL_VERSION +ARG KUBELOGIN_VERSION +ARG YQ_VERSION +ARG TFLINT_VERSION +ARG TF_SUMMARIZE_VERSION +ARG AZCOPY_VERSION +ARG CHECKOV_VERSION +ARG NODE_VERSION +ARG DOTNET_VERSION +ARG CLAUDE_CODE_VERSION +ARG CODEX_VERSION +ARG CSWAP_VERSION +ARG GITLEAKS_VERSION +ARG UV_VERSION + +# Install base packages +ARG PKGS="\ + gnupg \ + software-properties-common \ + ncat \ + curl \ + wget \ + git \ + unzip \ + jq \ + ca-certificates \ + iputils-ping \ + dnsutils \ + postgresql-client \ + cifs-utils \ + gettext-base \ + " + +# Trust any extra CA certificates dropped into files/certs/ (see the README there). +# The directory may hold nothing but the README, in which case only the public +# roots from the base image are trusted and the build still succeeds. +# REQUESTS_CA_BUNDLE / SSL_CERT_FILE cover Python (az, ansible, checkov, etc.) and +# curl; Node.js ignores the system store, so NODE_EXTRA_CA_CERTS points it at the +# merged bundle explicitly. +COPY files/certs/ /usr/local/share/ca-certificates/extra/ +RUN chmod -R a+r /usr/local/share/ca-certificates/extra && \ + update-ca-certificates +ENV REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt \ + SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt \ + NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt + +# Allow system-wide pip installs (PEP 668 on Ubuntu 24.04+) +# Safe in a devcontainer where system Python is the intended target +ENV PIP_BREAK_SYSTEM_PACKAGES=1 +ENV PIP_ROOT_USER_ACTION=ignore + +# NOTE: apt-get upgrade removed to improve layer cacheability. +# The base image is maintained upstream; weekly scheduled builds (--no-cache) pick up updates. +RUN apt-get update && \ + apt-get install --no-install-recommends -y ${PKGS} && \ + apt-get autoremove --purge -y && \ + rm -rf /var/lib/apt/lists/* + +# Copy all installation scripts +COPY ./files/install/*.sh /tmp/install/ +RUN chmod +x /tmp/install/*.sh + +RUN /tmp/install/install-python-tools.sh + +RUN /tmp/install/install-azure-cli.sh + +RUN /tmp/install/install-azcopy.sh ${AZCOPY_VERSION} + +RUN /tmp/install/install-terraform.sh ${TERRAFORM_VERSION} && \ + /tmp/install/install-terragrunt.sh ${TERRAGRUNT_VERSION} && \ + /tmp/install/install-tflint.sh ${TFLINT_VERSION} && \ + /tmp/install/install-tf-summarize.sh ${TF_SUMMARIZE_VERSION} && \ + /tmp/install/install-checkov.sh ${CHECKOV_VERSION} + +RUN /tmp/install/install-docker.sh + +RUN /tmp/install/install-ansible.sh + +RUN /tmp/install/install-helm.sh ${HELM_VERSION} && \ + /tmp/install/install-kubectl.sh ${KUBECTL_VERSION} && \ + /tmp/install/install-kubelogin.sh ${KUBELOGIN_VERSION} + +RUN /tmp/install/install-powershell.sh ${POWERSHELL_VERSION} + +RUN /tmp/install/install-yq.sh ${YQ_VERSION} && \ + /tmp/install/install-git-crypt.sh && \ + /tmp/install/install-pre-commit.sh && \ + /tmp/install/install-gitleaks.sh ${GITLEAKS_VERSION} + +RUN /tmp/install/install-node.sh ${NODE_VERSION} && \ + /tmp/install/install-codex.sh ${CODEX_VERSION} + +# .NET SDK: system-wide install dir, dev-friendly defaults, and ~/.dotnet/tools +# (per-user global tools) on PATH so `dotnet tool install -g` works out of the box. +ENV DOTNET_ROOT=/usr/share/dotnet \ + DOTNET_CLI_TELEMETRY_OPTOUT=1 \ + DOTNET_NOLOGO=1 \ + PATH="${PATH}:/usr/share/dotnet:/home/${USERNAME}/.dotnet/tools" +RUN /tmp/install/install-dotnet.sh ${DOTNET_VERSION} + +# uv: fast Python package/project manager. Installed for interactive use only — +# nothing in this image is installed through it yet, so it deliberately sits +# late, where a version bump costs no cached layers. Migrating the first tool to +# `uv tool install` means moving this RUN above that tool's own. +RUN /tmp/install/install-uv.sh ${UV_VERSION} + +# claude-swap (`cswap`): Claude Code multi-account switcher. Installed +# system-wide rather than into ~/.local because /home/vscode is a named volume +# only seeded from /tmp-home on first start — a home-tree install would go +# stale for anyone with a pre-existing volume. +RUN /tmp/install/install-cswap.sh ${CSWAP_VERSION} + +# Claude Code is installed natively (not via npm -g) into the vscode user's +# ~/.local tree so that auto-updates work without sudo. It must run as the +# vscode user (with HOME set explicitly — the USER instruction does not set +# HOME) and before the /tmp-home snapshot below, so the install is seeded into +# the home volume on first container start. +USER ${USERNAME} +RUN HOME=/home/${USERNAME} /tmp/install/install-claude-code.sh ${CLAUDE_CODE_VERSION} +USER root + +# Codex bootstrap: ships a config template + interactive init script. +# Users run `codex-init` manually to be prompted for endpoint URL and deployment +# name; values are saved to ~/.config/codex-bootstrap.env and ~/.codex/config.toml is rendered. +COPY ./files/codex/config.toml.tmpl /usr/local/share/codex/config.toml.tmpl +COPY ./files/codex/codex-init /usr/local/bin/codex-init +RUN chmod +x /usr/local/bin/codex-init && \ + chmod 0644 /usr/local/share/codex/config.toml.tmpl + +# Set zsh as default shell and prepare home directory template +RUN chsh -s /bin/zsh ${USERNAME} && \ + cp -r /home/vscode/. /tmp-home + +COPY ./files/home/ /tmp-home/ +COPY ./files/entrypoint.sh /usr/local/bin/entrypoint.sh + +# Source environment in bashrc, install zsh plugins, and cleanup +RUN echo '' >> /tmp-home/.bashrc && \ + echo '# Load DevContainer environment' >> /tmp-home/.bashrc && \ + echo 'if [ -f ~/.environment ]; then' >> /tmp-home/.bashrc && \ + echo ' source ~/.environment' >> /tmp-home/.bashrc && \ + echo 'fi' >> /tmp-home/.bashrc && \ + git -c advice.detachedHead=false clone --branch v0.7.1 --depth 1 https://github.com/zsh-users/zsh-autosuggestions /tmp-home/.oh-my-zsh/custom/plugins/zsh-autosuggestions && \ + git -c advice.detachedHead=false clone --branch 0.8.0 --depth 1 https://github.com/zsh-users/zsh-syntax-highlighting.git /tmp-home/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting && \ + rm -rf /tmp/install-* /tmp/install /var/lib/apt/lists/* + +HEALTHCHECK --interval=60s --timeout=10s --retries=3 \ + CMD terraform version > /dev/null 2>&1 && python3 --version > /dev/null 2>&1 || exit 1 + +USER $USERNAME + +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a1bf477..b4ee41b 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,126 +1,133 @@ -{ - // Option 1: Build from the local Dockerfile (default) - "build": { - "dockerfile": "Dockerfile", - "context": "", - "target": "final", - "args": { - "UBUNTU_VERSION": "24.04" - // Leave versions empty to install latest automatically - // Or specify versions to pin, e.g.: - // "TERRAFORM_VERSION": "1.13.5", - // "KUBECTL_VERSION": "1.34.2", - // "HELM_VERSION": "4.0.0" - } - }, - - // Option 2: Use a pre-built image from your own container registry. - // Comment out the "build" block above, then uncomment and edit the line - // below. For Azure Container Registry, sign in first with: - // az acr login --name - // "image": ".azurecr.io/devcontainer:latest", - "remoteUser": "vscode", - "runArgs": [ - "--network=host", - "--privileged" - ], - "mounts": [ - "source=${localWorkspaceFolder},target=/workspace/devcontainer,type=bind,consistency=cached", - "type=volume,source=dev-home-${localEnv:USER},target=/home/vscode" - ], - "workspaceMount": "source=dev-workspace-${localEnv:USER},target=/workspace,type=volume", - "workspaceFolder": "/workspace", - "postCreateCommand": "bash -c 'echo \"127.0.0.1 $(hostname)\" | sudo tee -a /etc/hosts > /dev/null' && sudo chown -R vscode:vscode /workspace && sudo chown -R vscode:vscode /home/vscode || true", - "postStartCommand": "sudo /usr/local/bin/entrypoint.sh", - "containerEnv": { - "NODE_OPTIONS": "--max-old-space-size=4096", - "CLAUDE_CONFIG_DIR": "/home/vscode/.claude", - "REQUESTS_CA_BUNDLE": "/etc/ssl/certs/ca-certificates.crt", - "SSL_CERT_FILE": "/etc/ssl/certs/ca-certificates.crt", - "NODE_EXTRA_CA_CERTS": "/etc/ssl/certs/ca-certificates.crt" - }, - "customizations": { - "vscode": { - "extensions": [ - "ms-azuretools.vscode-docker", - "hashicorp.terraform", - "ms-vscode.azurecli", - "ms-vscode.powershell", - "ms-dotnettools.csharp", - "ms-dotnettools.csdevkit", - "redhat.ansible", - "ms-python.python", - "ms-python.vscode-pylance", - "ms-python.black-formatter", - "eamodio.gitlens", - "mhutchie.git-graph", - "donjayamanne.githistory", - "GitHub.vscode-pull-request-github", - "yzhang.markdown-all-in-one", - "DavidAnson.vscode-markdownlint", - "esbenp.prettier-vscode", - "redhat.vscode-yaml", - "tamasfe.even-better-toml", - "ms-azuretools.vscode-azureterraform", - "charliermarsh.ruff", - "timonwong.shellcheck", - "streetsidesoftware.code-spell-checker", - "anthropic.claude-code", - "GitHub.copilot", - "GitHub.copilot-chat", - "openai.chatgpt" - ], - "settings": { - "terminal.integrated.defaultProfile.linux": "zsh", - "terminal.integrated.profiles.linux": { - "zsh": { - "path": "/bin/zsh", - "icon": "terminal-linux" - }, - "bash": { - "path": "/bin/bash", - "icon": "terminal-bash" - }, - "pwsh": { - "path": "/usr/bin/pwsh", - "icon": "terminal-powershell" - } - }, - "powershell.integratedConsole.suppressStartupBanner": false, - "powershell.integratedConsole.startInBackground": false, - "powershell.promptToUpdatePowerShell": false, - "files.eol": "\n", - "editor.formatOnSave": true, - "editor.formatOnPaste": false, - "editor.tabSize": 2, - "editor.insertSpaces": true, - "terraform.languageServer.enable": true, - "terraform.codelens.enable": true, - "ansible.python.interpreterPath": "/usr/bin/python3", - "[terraform]": { - "editor.defaultFormatter": "hashicorp.terraform", - "editor.formatOnSave": true - }, - "[yaml]": { - "editor.defaultFormatter": "redhat.vscode-yaml", - "editor.formatOnSave": true - }, - "[json]": { - "editor.defaultFormatter": "vscode.json-language-features" - }, - "[markdown]": { - "editor.defaultFormatter": "vscode.markdown-language-features" - }, - "git.enableSmartCommit": true, - "git.confirmSync": false, - "git.autofetch": true, - "python.defaultInterpreterPath": "/usr/bin/python3", - "[python]": { - "editor.defaultFormatter": "ms-python.python", - "editor.formatOnSave": true - }, - "remote.autoForwardPortsSource": "output" - } - } - } -} \ No newline at end of file +{ + // Option 1: pull the pre-built multi-arch image (default). + // + // Published weekly from this repo by .github/workflows/release.yml, for + // linux/amd64 and linux/arm64. Opening the folder pulls it instead of + // spending half an hour building. Pin a dated tag (e.g. :2026.08.09) or a + // digest if you need a fixed image - :latest moves every week. + "image": "ghcr.io/grinidx/devcontainer-devops:latest", + + // Option 2: build from the local Dockerfile. + // + // This is the path to use when changing the Dockerfile or the install + // scripts. Comment out the "image" line above, then uncomment the block + // below. Build args only apply on this path - they do nothing to a + // pre-built image. + // "build": { + // "dockerfile": "Dockerfile", + // "context": "", + // "target": "final", + // "args": { + // "UBUNTU_VERSION": "24.04" + // // Leave versions empty to install latest automatically + // // Or specify versions to pin, e.g.: + // // "TERRAFORM_VERSION": "1.13.5", + // // "KUBECTL_VERSION": "1.34.2", + // // "HELM_VERSION": "4.0.0" + // } + // }, + "remoteUser": "vscode", + "runArgs": [ + "--network=host", + "--privileged" + ], + "mounts": [ + "source=${localWorkspaceFolder},target=/workspace/devcontainer,type=bind,consistency=cached", + "type=volume,source=dev-home-${localEnv:USER},target=/home/vscode" + ], + "workspaceMount": "source=dev-workspace-${localEnv:USER},target=/workspace,type=volume", + "workspaceFolder": "/workspace", + "postCreateCommand": "bash -c 'echo \"127.0.0.1 $(hostname)\" | sudo tee -a /etc/hosts > /dev/null' && sudo chown -R vscode:vscode /workspace && sudo chown -R vscode:vscode /home/vscode || true", + "postStartCommand": "sudo /usr/local/bin/entrypoint.sh", + "containerEnv": { + "NODE_OPTIONS": "--max-old-space-size=4096", + "CLAUDE_CONFIG_DIR": "/home/vscode/.claude", + "REQUESTS_CA_BUNDLE": "/etc/ssl/certs/ca-certificates.crt", + "SSL_CERT_FILE": "/etc/ssl/certs/ca-certificates.crt", + "NODE_EXTRA_CA_CERTS": "/etc/ssl/certs/ca-certificates.crt" + }, + "customizations": { + "vscode": { + "extensions": [ + "ms-azuretools.vscode-docker", + "hashicorp.terraform", + "ms-vscode.azurecli", + "ms-vscode.powershell", + "ms-dotnettools.csharp", + "ms-dotnettools.csdevkit", + "redhat.ansible", + "ms-python.python", + "ms-python.vscode-pylance", + "ms-python.black-formatter", + "eamodio.gitlens", + "mhutchie.git-graph", + "donjayamanne.githistory", + "GitHub.vscode-pull-request-github", + "yzhang.markdown-all-in-one", + "DavidAnson.vscode-markdownlint", + "esbenp.prettier-vscode", + "redhat.vscode-yaml", + "tamasfe.even-better-toml", + "ms-azuretools.vscode-azureterraform", + "charliermarsh.ruff", + "timonwong.shellcheck", + "streetsidesoftware.code-spell-checker", + "anthropic.claude-code", + "GitHub.copilot", + "GitHub.copilot-chat", + "openai.chatgpt" + ], + "settings": { + "terminal.integrated.defaultProfile.linux": "zsh", + "terminal.integrated.profiles.linux": { + "zsh": { + "path": "/bin/zsh", + "icon": "terminal-linux" + }, + "bash": { + "path": "/bin/bash", + "icon": "terminal-bash" + }, + "pwsh": { + "path": "/usr/bin/pwsh", + "icon": "terminal-powershell" + } + }, + "powershell.integratedConsole.suppressStartupBanner": false, + "powershell.integratedConsole.startInBackground": false, + "powershell.promptToUpdatePowerShell": false, + "files.eol": "\n", + "editor.formatOnSave": true, + "editor.formatOnPaste": false, + "editor.tabSize": 2, + "editor.insertSpaces": true, + "terraform.languageServer.enable": true, + "terraform.codelens.enable": true, + "ansible.python.interpreterPath": "/usr/bin/python3", + "[terraform]": { + "editor.defaultFormatter": "hashicorp.terraform", + "editor.formatOnSave": true + }, + "[yaml]": { + "editor.defaultFormatter": "redhat.vscode-yaml", + "editor.formatOnSave": true + }, + "[json]": { + "editor.defaultFormatter": "vscode.json-language-features" + }, + "[markdown]": { + "editor.defaultFormatter": "vscode.markdown-language-features" + }, + "git.enableSmartCommit": true, + "git.confirmSync": false, + "git.autofetch": true, + "python.defaultInterpreterPath": "/usr/bin/python3", + "[python]": { + "editor.defaultFormatter": "ms-python.python", + "editor.formatOnSave": true + }, + "remote.autoForwardPortsSource": "output" + } + } + } +} diff --git a/.devcontainer/files/codex/codex-init b/.devcontainer/files/codex/codex-init old mode 100644 new mode 100755 diff --git a/.devcontainer/files/entrypoint.sh b/.devcontainer/files/entrypoint.sh index 34ad305..2bcb648 100755 --- a/.devcontainer/files/entrypoint.sh +++ b/.devcontainer/files/entrypoint.sh @@ -21,4 +21,4 @@ echo "Cleaning terraform and terragrunt caches..." find /home/vscode/.terragrunt-cache -mindepth 1 -maxdepth 1 -exec rm -rf {} + 2>/dev/null || true find /home/vscode/.terraform.d/plugin-cache/registry.terraform.io -mindepth 1 -maxdepth 1 -exec rm -rf {} + 2>/dev/null || true -exec "$@" \ No newline at end of file +exec "$@" diff --git a/.devcontainer/files/home/.bash_aliases b/.devcontainer/files/home/.bash_aliases index 0b1bd7e..b53b58d 100644 --- a/.devcontainer/files/home/.bash_aliases +++ b/.devcontainer/files/home/.bash_aliases @@ -112,4 +112,4 @@ alias aliases='vim ~/.bash_aliases' # Validation alias validate='bash /workspace/devcontainer/tests/validate-tools.sh' -alias testall='bash /workspace/devcontainer/tests/run-all-tests.sh' \ No newline at end of file +alias testall='bash /workspace/devcontainer/tests/run-all-tests.sh' diff --git a/.devcontainer/files/home/.config/powershell/Microsoft.PowerShell_profile.ps1 b/.devcontainer/files/home/.config/powershell/Microsoft.PowerShell_profile.ps1 index a11d292..db0aef8 100644 --- a/.devcontainer/files/home/.config/powershell/Microsoft.PowerShell_profile.ps1 +++ b/.devcontainer/files/home/.config/powershell/Microsoft.PowerShell_profile.ps1 @@ -8,4 +8,4 @@ oh-my-posh init pwsh --config $env:POSH_THEME | Invoke-Expression # NOTE: You can override the above env var from the devcontainer.json "args" under the "build" key. # Aliases -Set-Alias -Name ac -Value Add-Content \ No newline at end of file +Set-Alias -Name ac -Value Add-Content diff --git a/.devcontainer/files/home/.config/powershell/ps_theme.json b/.devcontainer/files/home/.config/powershell/ps_theme.json index d40454f..a8abedc 100644 --- a/.devcontainer/files/home/.config/powershell/ps_theme.json +++ b/.devcontainer/files/home/.config/powershell/ps_theme.json @@ -40,4 +40,4 @@ ], "final_space": true, "version": 3 -} \ No newline at end of file +} diff --git a/.devcontainer/files/home/.environment b/.devcontainer/files/home/.environment index f60e3a3..e9b0064 100644 --- a/.devcontainer/files/home/.environment +++ b/.devcontainer/files/home/.environment @@ -45,25 +45,25 @@ if [ -n "$BASH_VERSION" ]; then # Bash completions source <(kubectl completion bash) complete -F __start_kubectl k - + source <(helm completion bash) complete -F __start_helm h - + complete -C /usr/local/bin/terraform terraform complete -C /usr/local/bin/terraform tf - + source /etc/bash_completion.d/azure-cli 2>/dev/null || true elif [ -n "$ZSH_VERSION" ]; then # Zsh completions autoload -Uz compinit compinit - + source <(kubectl completion zsh) compdef __start_kubectl k - + source <(helm completion zsh) compdef __start_helm h - + complete -o nospace -C /usr/local/bin/terraform terraform complete -o nospace -C /usr/local/bin/terraform tf fi @@ -83,5 +83,3 @@ export NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt # Path additions export PATH="$HOME/.local/bin:$PATH" - - diff --git a/.devcontainer/files/install/_arch.sh b/.devcontainer/files/install/_arch.sh new file mode 100755 index 0000000..38d80f0 --- /dev/null +++ b/.devcontainer/files/install/_arch.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# Architecture detection shared by the install scripts. +# +# Source it, do not execute it: +# +# . "$(dirname "$0")/_arch.sh" +# +# Upstream projects disagree about how to spell the same architecture, so this +# exports every spelling the install scripts actually need rather than picking +# one canonical name and making each caller translate it: +# +# ARCH_DEB amd64 / arm64 Debian and Go convention - most releases +# ARCH_X64 x64 / arm64 gitleaks +# ARCH_GNU x86_64 / aarch64 Rust target triples - uv +# +# The leading underscore keeps it out of the install-.sh namespace; the +# Dockerfile copies and chmods the whole directory in one step, so no separate +# COPY line is needed for it. + +case "$(uname -m)" in + x86_64 | amd64) + ARCH_DEB="amd64" + ARCH_X64="x64" + ARCH_GNU="x86_64" + ;; + aarch64 | arm64) + ARCH_DEB="arm64" + ARCH_X64="arm64" + ARCH_GNU="aarch64" + ;; + *) + echo "ERROR: unsupported architecture: $(uname -m)" >&2 + exit 1 + ;; +esac + +export ARCH_DEB ARCH_X64 ARCH_GNU diff --git a/.devcontainer/files/install/install-ansible.sh b/.devcontainer/files/install/install-ansible.sh old mode 100644 new mode 100755 index 4fda3b5..f718b54 --- a/.devcontainer/files/install/install-ansible.sh +++ b/.devcontainer/files/install/install-ansible.sh @@ -104,4 +104,4 @@ python3 -m pip install --no-cache-dir \ # Set proper permissions for all users to read ansible collections chmod -R a+rX ${COLLECTIONS_PATH} -echo "Ansible collections installed successfully" \ No newline at end of file +echo "Ansible collections installed successfully" diff --git a/.devcontainer/files/install/install-azcopy.sh b/.devcontainer/files/install/install-azcopy.sh old mode 100644 new mode 100755 index 6fcd68d..2a785a3 --- a/.devcontainer/files/install/install-azcopy.sh +++ b/.devcontainer/files/install/install-azcopy.sh @@ -1,18 +1,33 @@ #!/bin/bash set -euo pipefail +. "$(dirname "$0")/_arch.sh" + WORKDIR="/tmp/install-azcopy" mkdir -p "${WORKDIR}" cd "${WORKDIR}" -# Get latest version if not specified if [ -z "${1:-}" ]; then + # aka.ms serves the current release per architecture. The amd64 alias has + # no suffix, arm64 does. Both redirect to the GitHub release asset. echo "Installing latest azcopy..." - DOWNLOAD_URL="https://aka.ms/downloadazcopy-v10-linux" + if [ "${ARCH_DEB}" = "amd64" ]; then + DOWNLOAD_URL="https://aka.ms/downloadazcopy-v10-linux" + else + DOWNLOAD_URL="https://aka.ms/downloadazcopy-v10-linux-${ARCH_DEB}" + fi else + # Pinned version: go straight to the GitHub release, which is where the + # aka.ms aliases end up anyway. + # + # The previous URL here could never have worked - it pointed at + # azcopyvnext.azureedge.net, a retired CDN, via a path containing a shell + # glob ("release-${VERSION}-20*") that curl has no way to expand. Nothing + # noticed because versions.json leaves azcopy empty, so only the branch + # above is ever taken. VERSION=$1 echo "Installing azcopy version ${VERSION}..." - DOWNLOAD_URL="https://azcopyvnext.azureedge.net/releases/release-${VERSION}-20*/azcopy_linux_amd64_${VERSION}.tar.gz" + DOWNLOAD_URL="https://github.com/Azure/azure-storage-azcopy/releases/download/v${VERSION}/azcopy_linux_${ARCH_DEB}_${VERSION}.tar.gz" fi # Download and extract diff --git a/.devcontainer/files/install/install-azure-cli.sh b/.devcontainer/files/install/install-azure-cli.sh old mode 100644 new mode 100755 index f6e3e44..fc1d811 --- a/.devcontainer/files/install/install-azure-cli.sh +++ b/.devcontainer/files/install/install-azure-cli.sh @@ -5,5 +5,5 @@ WORKDIR="/tmp/install-azure-cli" mkdir -p "${WORKDIR}" cd "${WORKDIR}" -curl -sSLo install-az.sh https://aka.ms/InstallAzureCLIDeb -bash install-az.sh \ No newline at end of file +curl -sSLo install-az.sh https://aka.ms/InstallAzureCLIDeb +bash install-az.sh diff --git a/.devcontainer/files/install/install-checkov.sh b/.devcontainer/files/install/install-checkov.sh old mode 100644 new mode 100755 diff --git a/.devcontainer/files/install/install-cswap.sh b/.devcontainer/files/install/install-cswap.sh index 211b5b9..17fe93c 100755 --- a/.devcontainer/files/install/install-cswap.sh +++ b/.devcontainer/files/install/install-cswap.sh @@ -33,4 +33,4 @@ python3 -m pip install --no-cache-dir claude-swap==${VERSION} # Verify installation cswap --version -echo "claude-swap ${VERSION} installed successfully (command: cswap)" \ No newline at end of file +echo "claude-swap ${VERSION} installed successfully (command: cswap)" diff --git a/.devcontainer/files/install/install-docker.sh b/.devcontainer/files/install/install-docker.sh old mode 100644 new mode 100755 diff --git a/.devcontainer/files/install/install-dotnet.sh b/.devcontainer/files/install/install-dotnet.sh index fbc43df..58589ea 100755 --- a/.devcontainer/files/install/install-dotnet.sh +++ b/.devcontainer/files/install/install-dotnet.sh @@ -50,4 +50,4 @@ rm -rf "${WORKDIR}" export DOTNET_ROOT="${DOTNET_INSTALL_DIR}" dotnet --info -echo ".NET SDK installed successfully" \ No newline at end of file +echo ".NET SDK installed successfully" diff --git a/.devcontainer/files/install/install-git-crypt.sh b/.devcontainer/files/install/install-git-crypt.sh old mode 100644 new mode 100755 diff --git a/.devcontainer/files/install/install-gitleaks.sh b/.devcontainer/files/install/install-gitleaks.sh old mode 100644 new mode 100755 index a2e6f9b..aca961f --- a/.devcontainer/files/install/install-gitleaks.sh +++ b/.devcontainer/files/install/install-gitleaks.sh @@ -1,6 +1,8 @@ #!/bin/bash set -euo pipefail +. "$(dirname "$0")/_arch.sh" + WORKDIR="/tmp/install-gitleaks" mkdir -p "${WORKDIR}" cd "${WORKDIR}" @@ -20,7 +22,7 @@ fi echo "Installing gitleaks version ${VERSION}..." -curl -L "https://github.com/gitleaks/gitleaks/releases/download/v${VERSION}/gitleaks_${VERSION}_linux_x64.tar.gz" -o gitleaks.tar.gz +curl -L "https://github.com/gitleaks/gitleaks/releases/download/v${VERSION}/gitleaks_${VERSION}_linux_${ARCH_X64}.tar.gz" -o gitleaks.tar.gz tar -xzf gitleaks.tar.gz gitleaks chmod +x gitleaks diff --git a/.devcontainer/files/install/install-helm.sh b/.devcontainer/files/install/install-helm.sh index ee74eab..ae2805a 100755 --- a/.devcontainer/files/install/install-helm.sh +++ b/.devcontainer/files/install/install-helm.sh @@ -1,6 +1,8 @@ #!/bin/bash set -euo pipefail +. "$(dirname "$0")/_arch.sh" + WORKDIR="/tmp/install-helm" mkdir -p "${WORKDIR}" cd "${WORKDIR}" @@ -21,17 +23,17 @@ fi echo "Installing Helm version ${VERSION}..." # Download Helm -curl -LO "https://get.helm.sh/helm-v${VERSION}-linux-amd64.tar.gz" +curl -LO "https://get.helm.sh/helm-v${VERSION}-linux-${ARCH_DEB}.tar.gz" # Download checksum -curl -LO "https://get.helm.sh/helm-v${VERSION}-linux-amd64.tar.gz.sha256sum" +curl -LO "https://get.helm.sh/helm-v${VERSION}-linux-${ARCH_DEB}.tar.gz.sha256sum" # Verify checksum -# sha256sum -c helm-v${VERSION}-linux-amd64.tar.gz.sha256sum +sha256sum -c "helm-v${VERSION}-linux-${ARCH_DEB}.tar.gz.sha256sum" # Extract and install -tar -zxvf helm-v${VERSION}-linux-amd64.tar.gz -mv linux-amd64/helm /usr/local/bin/helm +tar -zxf "helm-v${VERSION}-linux-${ARCH_DEB}.tar.gz" +mv "linux-${ARCH_DEB}/helm" /usr/local/bin/helm # Verify installation helm version diff --git a/.devcontainer/files/install/install-kubectl.sh b/.devcontainer/files/install/install-kubectl.sh old mode 100644 new mode 100755 index a275101..4b7a84a --- a/.devcontainer/files/install/install-kubectl.sh +++ b/.devcontainer/files/install/install-kubectl.sh @@ -1,6 +1,8 @@ #!/bin/bash set -euo pipefail +. "$(dirname "$0")/_arch.sh" + WORKDIR="/tmp/install-kubectl" mkdir -p "${WORKDIR}" cd "${WORKDIR}" @@ -17,10 +19,10 @@ fi echo "Installing kubectl version ${VERSION}..." # Download kubectl -curl -LO "https://dl.k8s.io/release/v${VERSION}/bin/linux/amd64/kubectl" +curl -LO "https://dl.k8s.io/release/v${VERSION}/bin/linux/${ARCH_DEB}/kubectl" # Download checksum -curl -LO "https://dl.k8s.io/release/v${VERSION}/bin/linux/amd64/kubectl.sha256" +curl -LO "https://dl.k8s.io/release/v${VERSION}/bin/linux/${ARCH_DEB}/kubectl.sha256" # Verify checksum echo "$(cat kubectl.sha256) kubectl" | sha256sum --check diff --git a/.devcontainer/files/install/install-kubelogin.sh b/.devcontainer/files/install/install-kubelogin.sh old mode 100644 new mode 100755 index 087643a..bf89ce5 --- a/.devcontainer/files/install/install-kubelogin.sh +++ b/.devcontainer/files/install/install-kubelogin.sh @@ -1,6 +1,8 @@ #!/bin/bash set -euo pipefail +. "$(dirname "$0")/_arch.sh" + WORKDIR="/tmp/install-kubelogin" mkdir -p "${WORKDIR}" cd "${WORKDIR}" @@ -21,12 +23,12 @@ fi echo "Installing kubelogin version ${VERSION}..." # Download kubelogin -curl -LO "https://github.com/Azure/kubelogin/releases/download/v${VERSION}/kubelogin-linux-amd64.zip" +curl -LO "https://github.com/Azure/kubelogin/releases/download/v${VERSION}/kubelogin-linux-${ARCH_DEB}.zip" # Extract and install -unzip kubelogin-linux-amd64.zip -chmod +x bin/linux_amd64/kubelogin -mv bin/linux_amd64/kubelogin /usr/local/bin/ +unzip "kubelogin-linux-${ARCH_DEB}.zip" +chmod +x "bin/linux_${ARCH_DEB}/kubelogin" +mv "bin/linux_${ARCH_DEB}/kubelogin" /usr/local/bin/ # Verify installation kubelogin --version diff --git a/.devcontainer/files/install/install-powershell.sh b/.devcontainer/files/install/install-powershell.sh index b4c2fd8..2e56aec 100755 --- a/.devcontainer/files/install/install-powershell.sh +++ b/.devcontainer/files/install/install-powershell.sh @@ -1,27 +1,80 @@ #!/bin/bash set -euo pipefail +. "$(dirname "$0")/_arch.sh" + WORKDIR="/tmp/install-powershell" mkdir -p "${WORKDIR}" cd "${WORKDIR}" -# Install PowerShell on Ubuntu +# Install PowerShell on Ubuntu. +# +# Microsoft's Ubuntu package repo publishes the `powershell` deb for amd64 +# only - there is no arm64 build in packages.microsoft.com/ubuntu//prod - +# so arm64 installs from the upstream linux-arm64 tarball instead. Everything +# below the install (PSGallery, modules, oh-my-posh) is common to both. # Get Ubuntu version dynamically . /etc/os-release -# Download the Microsoft repository keys using detected version -wget -q "https://packages.microsoft.com/config/ubuntu/${VERSION_ID}/packages-microsoft-prod.deb" +if [ "${ARCH_DEB}" = "amd64" ]; then + # Download the Microsoft repository keys using detected version + wget -q "https://packages.microsoft.com/config/ubuntu/${VERSION_ID}/packages-microsoft-prod.deb" + + # Register the Microsoft repository keys + dpkg -i packages-microsoft-prod.deb + + # Update package list + apt-get update + + # Install PowerShell + # NOTE: the apt path tracks whatever the repo currently serves and ignores + # $1 - only the tarball path below honours a pinned POWERSHELL_VERSION. + apt-get install -y --no-install-recommends powershell + rm -rf /var/lib/apt/lists/* +else + # Resolve the version - the tarball URL has no "latest" alias. + if [ -z "${1:-}" ]; then + echo "Fetching latest PowerShell version..." + VERSION=$(curl -sI https://github.com/PowerShell/PowerShell/releases/latest | grep -i '^location:' | sed -E 's|.*/v([^[:space:]]+).*|\1|') + if [ -z "${VERSION}" ]; then + echo "ERROR: Failed to determine latest PowerShell version" + exit 1 + fi + echo "Latest version: ${VERSION}" + else + VERSION=$1 + fi + + # The deb would have pulled these in. Resolve the names rather than hardcode + # them - Ubuntu suffixes several across releases (libssl3 -> libssl3t64, + # liblttng-ust1 -> liblttng-ust1t64 on noble). + apt-get update + PS_DEPS="" + for pat in 'libicu[0-9]+' 'liblttng-ust[0-9a-z]*' 'libssl3[a-z0-9]*' 'libgssapi-krb5-2'; do + pkg="$(apt-cache search --names-only "^${pat}\$" | awk '{print $1}' | sort -V | tail -1)" + if [ -n "${pkg}" ]; then + PS_DEPS="${PS_DEPS} ${pkg}" + fi + done + echo "PowerShell runtime dependencies:${PS_DEPS}" + # shellcheck disable=SC2086 + apt-get install -y --no-install-recommends ${PS_DEPS} + rm -rf /var/lib/apt/lists/* -# Register the Microsoft repository keys -dpkg -i packages-microsoft-prod.deb + echo "Installing PowerShell ${VERSION} from the linux-${ARCH_DEB} tarball..." + TARBALL="powershell-${VERSION}-linux-${ARCH_DEB}.tar.gz" + curl -sSL "https://github.com/PowerShell/PowerShell/releases/download/v${VERSION}/${TARBALL}" -o "${TARBALL}" -# Update package list -apt-get update + # Same layout the deb uses, so profiles and module paths line up. + install -d /opt/microsoft/powershell/7 + tar -xzf "${TARBALL}" -C /opt/microsoft/powershell/7 + chmod +x /opt/microsoft/powershell/7/pwsh + ln -sf /opt/microsoft/powershell/7/pwsh /usr/bin/pwsh +fi -# Install PowerShell -apt-get install -y --no-install-recommends powershell -rm -rf /var/lib/apt/lists/* +# Fails the build loudly if a runtime dependency is missing +pwsh --version # Configure PSGallery as trusted repository echo "Configuring PSGallery..." diff --git a/.devcontainer/files/install/install-pre-commit.sh b/.devcontainer/files/install/install-pre-commit.sh old mode 100644 new mode 100755 diff --git a/.devcontainer/files/install/install-python-tools.sh b/.devcontainer/files/install/install-python-tools.sh old mode 100644 new mode 100755 diff --git a/.devcontainer/files/install/install-terraform.sh b/.devcontainer/files/install/install-terraform.sh old mode 100644 new mode 100755 diff --git a/.devcontainer/files/install/install-terragrunt.sh b/.devcontainer/files/install/install-terragrunt.sh old mode 100644 new mode 100755 index 759325f..24dbdef --- a/.devcontainer/files/install/install-terragrunt.sh +++ b/.devcontainer/files/install/install-terragrunt.sh @@ -1,12 +1,14 @@ #!/bin/bash set -euo pipefail +. "$(dirname "$0")/_arch.sh" + WORKDIR="/tmp/install-terragrunt" mkdir -p "${WORKDIR}" cd "${WORKDIR}" OS="linux" -ARCH="amd64" +ARCH="${ARCH_DEB}" # Use provided version or fetch latest from GitHub if [ -z "${1:-}" ]; then diff --git a/.devcontainer/files/install/install-tf-summarize.sh b/.devcontainer/files/install/install-tf-summarize.sh old mode 100644 new mode 100755 index f645d31..8fe7d01 --- a/.devcontainer/files/install/install-tf-summarize.sh +++ b/.devcontainer/files/install/install-tf-summarize.sh @@ -1,12 +1,14 @@ #!/bin/bash set -euo pipefail +. "$(dirname "$0")/_arch.sh" + WORKDIR="/tmp/install-tf-summarize" mkdir -p "${WORKDIR}" cd "${WORKDIR}" OS="linux" -ARCH="amd64" +ARCH="${ARCH_DEB}" # Use provided version or fetch latest from GitHub if [ -z "${1:-}" ]; then diff --git a/.devcontainer/files/install/install-tflint.sh b/.devcontainer/files/install/install-tflint.sh old mode 100644 new mode 100755 index 0e333c6..9a0376b --- a/.devcontainer/files/install/install-tflint.sh +++ b/.devcontainer/files/install/install-tflint.sh @@ -1,6 +1,8 @@ #!/bin/bash set -euo pipefail +. "$(dirname "$0")/_arch.sh" + WORKDIR="/tmp/install-tflint" mkdir -p "${WORKDIR}" cd "${WORKDIR}" @@ -22,7 +24,7 @@ fi echo "Installing tflint version ${VERSION}..." # Download tflint -curl -L "https://github.com/terraform-linters/tflint/releases/download/v${VERSION}/tflint_linux_amd64.zip" -o tflint.zip +curl -L "https://github.com/terraform-linters/tflint/releases/download/v${VERSION}/tflint_linux_${ARCH_DEB}.zip" -o tflint.zip # Extract and install unzip tflint.zip diff --git a/.devcontainer/files/install/install-uv.sh b/.devcontainer/files/install/install-uv.sh index 5c331f4..041099b 100755 --- a/.devcontainer/files/install/install-uv.sh +++ b/.devcontainer/files/install/install-uv.sh @@ -12,11 +12,13 @@ set -euo pipefail # Note for whoever migrates the first tool to `uv tool install`: this RUN sits # late in the Dockerfile and will need to move above its first consumer. +. "$(dirname "$0")/_arch.sh" + WORKDIR="/tmp/install-uv" mkdir -p "${WORKDIR}" cd "${WORKDIR}" -TARGET="x86_64-unknown-linux-gnu" +TARGET="${ARCH_GNU}-unknown-linux-gnu" # Use provided version or fetch latest from GitHub. # NOTE: uv's release tags have no "v" prefix (e.g. "0.11.33"), unlike most of @@ -55,4 +57,4 @@ rm -rf "${WORKDIR}" uv --version uvx --version -echo "uv ${VERSION} installed successfully (commands: uv, uvx)" \ No newline at end of file +echo "uv ${VERSION} installed successfully (commands: uv, uvx)" diff --git a/.devcontainer/files/install/install-yq.sh b/.devcontainer/files/install/install-yq.sh old mode 100644 new mode 100755 index 806d0b0..1fc6aec --- a/.devcontainer/files/install/install-yq.sh +++ b/.devcontainer/files/install/install-yq.sh @@ -1,6 +1,8 @@ #!/bin/bash set -euo pipefail +. "$(dirname "$0")/_arch.sh" + WORKDIR="/tmp/install-yq" mkdir -p "${WORKDIR}" cd "${WORKDIR}" @@ -21,7 +23,7 @@ fi echo "Installing yq version ${VERSION}..." # Download yq -YQ_BINARY="yq_linux_amd64" +YQ_BINARY="yq_linux_${ARCH_DEB}" DOWNLOAD_URL="https://github.com/mikefarah/yq/releases/download/v${VERSION}/${YQ_BINARY}" echo "Downloading from: $DOWNLOAD_URL" diff --git a/.dockerignore b/.dockerignore index 2dd9f2c..7b8cdb0 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,90 +1,88 @@ -# Git -.git/ -.gitignore -.gitattributes - -# CI/CD -.github/ -.azuredevops/ -azure-pipelines.yml -.gitlab-ci.yml - -# Documentation -README.md -CONTRIBUTING.md -CHANGELOG.md -LICENSE -*.md -docs/ - -# IDE -.vscode/ -.idea/ -*.swp -*.swo -*~ - -# OS -.DS_Store -Thumbs.db - -# Terraform state -.terraform/ -.terraform.lock.hcl -*.tfstate -*.tfstate.* -*.tfvars -.terragrunt-cache/ - -# Python -__pycache__/ -*.py[cod] -*$py.class -.Python -venv/ -ENV/ -env/ -.venv -*.egg-info/ -dist/ -build/ - -# Logs -*.log -logs/ - -# Temporary files -tmp/ -temp/ -*.tmp -*.bak -*.backup - -# Secrets -secrets/ -*.pem -*.key -*.crt -.env -.env.* -*.env - -# Test files -tests/ -test/ -*_test.go -*.test -coverage/ - -# Build artifacts -target/ -out/ -bin/ -obj/ - -# The CA drop-in directory must survive the *.md / *.crt / *.pem excludes above, -# otherwise the Dockerfile's COPY of files/certs/ has nothing to copy. -# (Only relevant when building with the repository root as the build context — -# both devcontainer.json and azure-pipelines.yml use .devcontainer/ as context.) -!.devcontainer/files/certs -!.devcontainer/files/certs/** +# Git +.git/ +.gitignore +.gitattributes + +# CI/CD +.github/ +.gitlab-ci.yml + +# Documentation +README.md +CONTRIBUTING.md +CHANGELOG.md +LICENSE +*.md +docs/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Terraform state +.terraform/ +.terraform.lock.hcl +*.tfstate +*.tfstate.* +*.tfvars +.terragrunt-cache/ + +# Python +__pycache__/ +*.py[cod] +*$py.class +.Python +venv/ +ENV/ +env/ +.venv +*.egg-info/ +dist/ +build/ + +# Logs +*.log +logs/ + +# Temporary files +tmp/ +temp/ +*.tmp +*.bak +*.backup + +# Secrets +secrets/ +*.pem +*.key +*.crt +.env +.env.* +*.env + +# Test files +tests/ +test/ +*_test.go +*.test +coverage/ + +# Build artifacts +target/ +out/ +bin/ +obj/ + +# The CA drop-in directory must survive the *.md / *.crt / *.pem excludes above, +# otherwise the Dockerfile's COPY of files/certs/ has nothing to copy. +# (Only relevant when building with the repository root as the build context - +# both devcontainer.json and the CI workflows use .devcontainer/ as context.) +!.devcontainer/files/certs +!.devcontainer/files/certs/** diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..c809ffe --- /dev/null +++ b/.gitattributes @@ -0,0 +1,25 @@ +# Normalise every text file to LF, in the repository and in the working tree. +# +# The repo previously held a mix: the root docs and configs were CRLF while +# every shell script under .devcontainer/files/ was LF. Nothing declared which +# was intended, so editing a CRLF file with an LF-writing tool left it mixed and +# the mixed-line-ending pre-commit hook rewrote the whole file. VS Code is +# already configured for LF here (devcontainer.json sets "files.eol": "\n"), so +# LF is what this settles on. +# +# Shell scripts in particular must be LF: a CRLF shebang line makes the kernel +# look for an interpreter with a trailing carriage return and the script fails +# with a confusing "bad interpreter" error. +* text=auto eol=lf + +# Leave binaries alone +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.gz binary +*.tgz binary +*.zip binary +*.woff binary +*.woff2 binary diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..c87a8e2 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,297 @@ +name: build + +# Reusable build. Called by ci.yml (pull requests and main) and release.yml +# (the weekly CalVer release). Everything that touches the image lives here so +# the two callers stay thin and cannot drift apart. +# +# Callers must grant the permissions declared on each job below - a called +# workflow can only narrow what the caller gives it, never widen it. +# +# Actions are pinned to commit SHAs, per GitHub's hardening guidance. The +# trailing comment is the human-readable tag; update both together. + +on: + workflow_call: + inputs: + publish: + description: Push the image to GHCR. Pull requests build and test only. + type: boolean + default: false + no-cache: + description: Ignore the layer cache, so unpinned tools pick up upstream updates. + type: boolean + default: false + tags: + description: docker/metadata-action tag configuration. + type: string + default: | + type=raw,value=main,enable={{is_default_branch}} + type=sha,format=short + outputs: + digest: + description: Digest of the published multi-arch manifest. + value: ${{ jobs.merge.outputs.digest }} + version: + description: Primary tag applied to the published image. + value: ${{ jobs.merge.outputs.version }} + +env: + REGISTRY: ghcr.io + # Full image reference, used everywhere except the login step + IMAGE: ghcr.io/${{ github.repository }} + DOCKERFILE: .devcontainer/Dockerfile + BUILD_CONTEXT: .devcontainer + +jobs: + lint: + name: lint + runs-on: ubuntu-24.04 + permissions: + contents: read + steps: + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + + - name: Shell syntax and shellcheck + run: | + set -euo pipefail + shopt -s nullglob + scripts=( + .devcontainer/files/install/*.sh + .devcontainer/files/entrypoint.sh + scripts/*.sh + tests/*.sh + ) + for s in "${scripts[@]}"; do + bash -n "$s" + done + # -x so shellcheck follows the sourced _arch.sh helper + shellcheck -x -S warning "${scripts[@]}" + + - name: Validate JSON + run: | + set -euo pipefail + # devcontainer.json is deliberately excluded: it is JSONC, and + # comments are valid there per the devcontainer spec. + while IFS= read -r f; do + echo "checking $f" + python3 -m json.tool "$f" > /dev/null + done < <(find . -name '*.json' -not -path './.git/*' -not -name 'devcontainer.json') + + # Ignores live in .hadolint.yaml, with a reason against each, so this + # matches what the pre-commit hook does locally. + - name: Lint Dockerfile + uses: hadolint/hadolint-action@2a66e89f53d0771bb131a7fa31f3136336094aa6 # v3.4.0 + with: + dockerfile: .devcontainer/Dockerfile + config: .hadolint.yaml + + build: + name: build (${{ matrix.arch }}) + needs: lint + runs-on: ${{ matrix.runner }} + permissions: + contents: read + packages: write + security-events: write + strategy: + fail-fast: false + matrix: + include: + - arch: amd64 + platform: linux/amd64 + runner: ubuntu-24.04 + - arch: arm64 + platform: linux/arm64 + runner: ubuntu-24.04-arm + steps: + # The image is several GB and the runner ships with ~14 GB free, which is + # not enough for the BuildKit cache plus the loaded image. This reclaims + # roughly 25 GB and is the difference between a green build and ENOSPC. + - name: Free disk space + uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 + with: + tool-cache: true + android: true + dotnet: true + haskell: true + large-packages: true + docker-images: true + swap-storage: false + + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + + - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 + + - name: Log in to GHCR + if: inputs.publish + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Docker metadata + id: meta + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 + with: + images: ${{ env.IMAGE }} + tags: ${{ inputs.tags }} + + # Build once for this architecture and load it locally so the repo's own + # test suite can run against the real image before anything is published. + # The push step below re-runs the same build, which is a cache hit, so + # only the registry export costs anything. + - name: Build and load + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 + with: + context: ${{ env.BUILD_CONTEXT }} + file: ${{ env.DOCKERFILE }} + platforms: ${{ matrix.platform }} + target: final + load: true + tags: devcontainer-devops:test-${{ matrix.arch }} + labels: ${{ steps.meta.outputs.labels }} + no-cache: ${{ inputs.no-cache }} + cache-from: type=registry,ref=${{ env.IMAGE }}:buildcache-${{ matrix.arch }} + + # Runs as vscode because Claude Code lives in that user's ~/.local tree. + # --entrypoint "" bypasses entrypoint.sh, which seeds the home volume and + # needs root; there is no home volume here. + - name: Test image + run: | + docker run --rm \ + --entrypoint "" \ + -u vscode \ + -v "${{ github.workspace }}/tests:/tests:ro" \ + devcontainer-devops:test-${{ matrix.arch }} \ + bash -lc 'export PATH="$HOME/.local/bin:$PATH"; bash /tests/run-all-tests.sh' + + - name: Scan image + uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 + with: + image-ref: devcontainer-devops:test-${{ matrix.arch }} + # Vulnerability findings are reported, never gating. A devcontainer + # bundling the Azure CLI, Ansible and a .NET SDK always carries some + # upstream HIGHs; blocking on them would stop the weekly rebuild and + # leave the published image staler than the CVEs it is avoiding. + exit-code: '0' + # secret scanning times out on the large PowerShell module tree + scanners: vuln + severity: HIGH,CRITICAL + timeout: 15m + format: sarif + output: trivy-${{ matrix.arch }}.sarif + + # Only on publishing runs: security-events: write is not granted to pull + # requests from forks, so the upload would fail there. + - name: Upload scan results + if: inputs.publish + uses: github/codeql-action/upload-sarif@d1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5 + with: + sarif_file: trivy-${{ matrix.arch }}.sarif + category: trivy-${{ matrix.arch }} + + - name: Push by digest + id: push + if: inputs.publish + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 + with: + context: ${{ env.BUILD_CONTEXT }} + file: ${{ env.DOCKERFILE }} + platforms: ${{ matrix.platform }} + target: final + labels: ${{ steps.meta.outputs.labels }} + sbom: true + provenance: mode=max + cache-from: type=registry,ref=${{ env.IMAGE }}:buildcache-${{ matrix.arch }} + cache-to: type=registry,ref=${{ env.IMAGE }}:buildcache-${{ matrix.arch }},mode=max + outputs: type=image,name=${{ env.IMAGE }},push-by-digest=true,name-canonical=true,push=true + + - name: Export digest + if: inputs.publish + run: | + set -euo pipefail + mkdir -p /tmp/digests + digest='${{ steps.push.outputs.digest }}' + touch "/tmp/digests/${digest#sha256:}" + + - name: Upload digest + if: inputs.publish + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: digest-${{ matrix.arch }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + merge: + name: merge and attest + if: inputs.publish + needs: build + runs-on: ubuntu-24.04 + permissions: + contents: read + packages: write + id-token: write + attestations: write + artifact-metadata: write + outputs: + digest: ${{ steps.manifest.outputs.digest }} + version: ${{ steps.meta.outputs.version }} + steps: + - name: Download digests + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + path: /tmp/digests + pattern: digest-* + merge-multiple: true + + - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 + + - name: Log in to GHCR + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Docker metadata + id: meta + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 + with: + images: ${{ env.IMAGE }} + tags: ${{ inputs.tags }} + + - name: Create manifest list + id: manifest + working-directory: /tmp/digests + run: | + set -euo pipefail + annotations="$(jq -cr \ + '.target."docker-metadata-action".annotations + | map("--annotation " + .) | join(" ")' \ + <<< "$DOCKER_METADATA_OUTPUT_JSON")" + tags="$(jq -cr '.tags | map("-t " + .) | join(" ")' \ + <<< "$DOCKER_METADATA_OUTPUT_JSON")" + + # Word splitting is deliberate: these hold whole argument lists + # shellcheck disable=SC2046,SC2086 + docker buildx imagetools create $annotations $tags \ + $(printf '${{ env.IMAGE }}@sha256:%s ' *) + + digest="$(docker buildx imagetools inspect \ + "${{ env.IMAGE }}:${{ steps.meta.outputs.version }}" \ + --format '{{ json .Manifest.Digest }}' | tr -d '"')" + echo "digest=${digest}" >> "$GITHUB_OUTPUT" + echo "Published ${{ env.IMAGE }}@${digest}" + + # Sigstore-backed SLSA build provenance, pushed to the registry as an OCI + # referrer. This is what `gh attestation verify` checks, and it replaces a + # separate cosign signing step - actions/attest signs with a short-lived + # Sigstore certificate already. + - name: Attest build provenance + uses: actions/attest@508db95dd578ae2727ebd6217d5ba78e4fbda05d # v4.2.1 + with: + subject-name: ${{ env.IMAGE }} + subject-digest: ${{ steps.manifest.outputs.digest }} + push-to-registry: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..155d2c3 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,33 @@ +name: ci + +# Pull requests build and test both architectures and publish nothing. +# Pushes to main additionally publish :main and :sha- to GHCR, so there +# is always a current image to pull without waiting for the weekly release. + +on: + pull_request: + push: + branches: [main] + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + build: + uses: ./.github/workflows/build.yml + permissions: + contents: read + packages: write + security-events: write + id-token: write + attestations: write + artifact-metadata: write + with: + publish: ${{ github.event_name == 'push' }} + tags: | + type=raw,value=main,enable={{is_default_branch}} + type=sha,format=short diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..a58b1ec --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,140 @@ +name: release + +# Cuts a calendar-versioned release every Sunday from a no-cache rebuild, so +# the published image tracks upstream tool and base-image updates. +# +# CalVer rather than SemVer on purpose: almost every version in versions.json +# is "install latest", so a tag records what the world looked like on a given +# day. It cannot promise a compatibility contract, and SemVer would imply one. +# +# NOTE: GitHub disables scheduled workflows after 60 days without repository +# activity. If releases stop appearing, check whether the schedule was +# disabled before looking for a bug. + +on: + schedule: + - cron: '0 3 * * 0' + workflow_dispatch: + inputs: + no-cache: + description: Rebuild from scratch so unpinned tools pick up upstream updates + type: boolean + default: true + +concurrency: + group: release + cancel-in-progress: false + +permissions: + contents: read + +jobs: + version: + name: resolve version + runs-on: ubuntu-24.04 + permissions: + contents: read + outputs: + version: ${{ steps.calc.outputs.version }} + month: ${{ steps.calc.outputs.month }} + steps: + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + with: + fetch-depth: 0 + + - name: Compute CalVer + id: calc + run: | + set -euo pipefail + base="$(date -u +%Y.%m.%d)" + version="${base}" + n=1 + # A second release on the same day becomes .2, .3, and so on + while git rev-parse -q --verify "refs/tags/v${version}" > /dev/null 2>&1; do + n=$((n + 1)) + version="${base}.${n}" + done + { + echo "version=${version}" + echo "month=$(date -u +%Y.%m)" + } >> "$GITHUB_OUTPUT" + echo "Releasing v${version}" + + build: + name: build + needs: version + uses: ./.github/workflows/build.yml + permissions: + contents: read + packages: write + security-events: write + id-token: write + attestations: write + artifact-metadata: write + with: + publish: true + no-cache: ${{ github.event_name == 'schedule' || inputs.no-cache }} + tags: | + type=raw,value=${{ needs.version.outputs.version }} + type=raw,value=${{ needs.version.outputs.month }} + type=raw,value=latest + + release: + name: publish release + needs: [version, build] + runs-on: ubuntu-24.04 + permissions: + contents: write + packages: read + steps: + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + + - name: Log in to GHCR + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # The SBOM is already attached to the image as a build attestation; this + # pulls it out into a file so the release has a downloadable copy. + - name: Extract SBOM + run: | + set -euo pipefail + docker buildx imagetools inspect \ + "ghcr.io/${{ github.repository }}@${{ needs.build.outputs.digest }}" \ + --format '{{ json .SBOM }}' > sbom.json + echo "SBOM: $(wc -c < sbom.json) bytes" + + - name: Create release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ needs.version.outputs.version }} + DIGEST: ${{ needs.build.outputs.digest }} + run: | + set -euo pipefail + cat > notes.md < ← Installation temp dirs││ │ -│ │ │ /tmp-home ← Default home template ││ │ -│ │ └──────────────────────────────────────────────────────┘│ │ -│ └────────────────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────────┘ - │ - ├──────────────────────┐ - │ │ - ▼ ▼ - ┌────────────────────┐ ┌────────────────────┐ - │ Docker Volume │ │ Bind Mount │ - │ (dev-volume) │ │ (Local Files) │ - │ │ │ │ - │ • Workspace data │ │ • Source code │ - │ • Configuration │ │ • Scripts │ - │ • State files │ │ • Configs │ - └────────────────────┘ └────────────────────┘ - -External Connections: -───────────────────── - -┌─────────────────────────────────────────────────────────────────┐ -│ Azure Cloud │ -│ │ -│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ -│ │ Azure ACR │ │ Azure VM │ │ Azure AKS │ │ -│ │ (Images) │ │ (Resources) │ │ (K8s) │ │ -│ └──────────────┘ └──────────────┘ └──────────────┘ │ -│ │ -│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ -│ │ Key Vault │ │ Storage │ │ DevOps │ │ -│ │ (Secrets) │ │ (State) │ │ (CI/CD) │ │ -│ └──────────────┘ └──────────────┘ └──────────────┘ │ -└─────────────────────────────────────────────────────────────────┘ - -Build & Deployment Flow: -──────────────────────── - -Developer Azure DevOps ACR DevContainer - │ │ │ │ - │─── Push Code ───▶│ │ │ - │ │ │ │ - │ │─── Build ───────▶│ │ - │ │ Image │ │ - │ │ │ │ - │ │◀─── Push ────────│ │ - │ │ Success │ │ - │ │ │ │ - │─────────────── Pull Image ─────────────────────────────▶│ - │ │ │ │ - │◀──────────────── Development ──────────────────────────┘ - -Tool Interaction Flow: -───────────────────── - - User Input (VS Code) - │ - ▼ - ┌─────────────┐ - │ Terraform │──────────▶ Azure Resources - │ Terragrunt │ - └─────────────┘ - │ - ├──────▶ tflint (Linting) - └──────▶ checkov (Security) - - ┌─────────────┐ - │ Kubectl │──────────▶ Kubernetes Cluster - │ Helm │ - └─────────────┘ - │ - └──────▶ kubelogin (Auth) - - ┌─────────────┐ - │ Docker │──────────▶ Container Registry - └─────────────┘ - - ┌─────────────┐ - │ Ansible │──────────▶ Target Servers - └─────────────┘ +# DevContainer Architecture + +```text +┌─────────────────────────────────────────────────────────────────┐ +│ VS Code DevContainer │ +│ │ +│ ┌────────────────────────────────────────────────────────────┐ │ +│ │ VS Code Client │ │ +│ │ ┌──────────────────────────────────────────────────────┐ │ │ +│ │ │ Extensions: │ │ │ +│ │ │ • Docker • Terraform • Kubernetes • Azure CLI │ │ │ +│ │ │ • Python • PowerShell • Ansible • GitLens │ │ │ +│ │ └──────────────────────────────────────────────────────┘ │ │ +│ └────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌────────────────────────────────────────────────────────────┐ │ +│ │ Container Runtime Environment │ │ +│ │ │ │ +│ │ ┌──────────────────────────────────────────────┐ │ │ +│ │ │ Ubuntu 24.04 Base │ │ │ +│ │ └──────────────────────────────────────────────┘ │ │ +│ │ │ │ +│ │ ┌──────────────┬──────────────┬──────────────┐ │ │ +│ │ │ IaC Tools │ Cloud Tools │ Container │ │ │ +│ │ │ │ │ Tools │ │ │ +│ │ │ • Terraform │ • Azure CLI │ • Docker │ │ │ +│ │ │ • Terragrunt │ • az │ • kubectl │ │ │ +│ │ │ • tflint │ │ • helm │ │ │ +│ │ │ • checkov │ │ • kubelogin │ │ │ +│ │ └──────────────┴──────────────┴──────────────┘ │ │ +│ │ │ │ +│ │ ┌──────────────┬──────────────┬──────────────┐ │ │ +│ │ │ Config Mgmt │ Languages │ Utilities │ │ │ +│ │ │ │ │ │ │ │ +│ │ │ • Ansible │ • Python 3 │ • jq │ │ │ +│ │ │ │ • PowerShell │ • yq │ │ │ +│ │ │ │ • Bash/ZSH │ • git-crypt │ │ │ +│ │ └──────────────┴──────────────┴──────────────┘ │ │ +│ │ │ │ +│ │ ┌──────────────────────────────────────────────────────┐│ │ +│ │ │ File System ││ │ +│ │ │ ││ │ +│ │ │ /workspace (Volume) ← Persistent Storage ││ │ +│ │ │ /home/vscode (Volume) ← User Home (Persistent)││ │ +│ │ │ /tmp/install- ← Installation temp dirs││ │ +│ │ │ /tmp-home ← Default home template ││ │ +│ │ └──────────────────────────────────────────────────────┘│ │ +│ └────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ + │ + ├──────────────────────┐ + │ │ + ▼ ▼ + ┌────────────────────┐ ┌────────────────────┐ + │ Docker Volume │ │ Bind Mount │ + │ (dev-volume) │ │ (Local Files) │ + │ │ │ │ + │ • Workspace data │ │ • Source code │ + │ • Configuration │ │ • Scripts │ + │ • State files │ │ • Configs │ + └────────────────────┘ └────────────────────┘ + +External Connections: +───────────────────── + +What the tooling inside the container reaches out to. This is not where the +image itself comes from - see the build flow below for that. + +┌─────────────────────────────────────────────────────────────────┐ +│ Azure Cloud │ +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ Azure ACR │ │ Azure VM │ │ Azure AKS │ │ +│ │ (Registries) │ │ (Resources) │ │ (K8s) │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ Key Vault │ │ Storage │ │ Entra ID │ │ +│ │ (Secrets) │ │ (State) │ │ (Auth) │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ + +Build & Deployment Flow: +──────────────────────── + +Developer GitHub Actions GHCR DevContainer + │ │ │ │ + │─── Push Code ───▶│ │ │ + │ │ │ │ + │ │─── Build ────────┤ │ + │ │ amd64 + arm64 │ │ + │ │ native runners│ │ + │ │ │ │ + │ │─── Test ─────────┤ │ + │ │ in the image │ │ + │ │ │ │ + │ │─── Push ────────▶│ │ + │ │ by digest │ │ + │ │ │ │ + │ │─── Merge ───────▶│ │ + │ │ manifest list │ │ + │ │ │ │ + │ │─── Attest ──────▶│ │ + │ │ SBOM + SLSA │ │ + │ │ │ │ + │─────────────── Pull Image ─────────────────────────────▶│ + │ │ │ │ + │◀──────────────── Development ──────────────────────────┘ + +Tool Interaction Flow: +───────────────────── + + User Input (VS Code) + │ + ▼ + ┌─────────────┐ + │ Terraform │──────────▶ Azure Resources + │ Terragrunt │ + └─────────────┘ + │ + ├──────▶ tflint (Linting) + └──────▶ checkov (Security) + + ┌─────────────┐ + │ Kubectl │──────────▶ Kubernetes Cluster + │ Helm │ + └─────────────┘ + │ + └──────▶ kubelogin (Auth) + + ┌─────────────┐ + │ Docker │──────────▶ Container Registry + └─────────────┘ + + ┌─────────────┐ + │ Ansible │──────────▶ Target Servers + └─────────────┘ +``` + +## CI/CD Architecture + +The build lives in three workflows under `.github/workflows/`. `ci.yml` (pull +requests, pushes to `main`) and `release.yml` (weekly, or manual) are thin entry +points; both call the reusable `build.yml`, which holds every step that touches +the image. Splitting it this way means the pull request path and the release +path cannot diverge, which is the failure mode where a change passes CI and then +breaks the release. + +### Why a hand-rolled matrix rather than `docker/github-builder` + +Docker publishes a reusable workflow that distributes a multi-platform build +across runners and merges the manifest. It is the shorter route, and it was +rejected here for one reason: it supports neither loading the built image +locally nor running per-platform tests against it. This repository's whole +value is the tools inside the image, so `tests/run-all-tests.sh` has to run +*inside* each built image before anything is published. That requires +`load: true`, which the reusable workflow does not offer. + +### Why native runners rather than QEMU + +`linux/amd64` builds on `ubuntu-24.04` and `linux/arm64` on `ubuntu-24.04-arm`, +each compiling for its own architecture. Emulating arm64 under QEMU on an x64 +runner would be an order of magnitude slower on an image this size, against a +six-hour job limit. Both runner types are free and unlimited on public +repositories, so the matrix costs nothing. + +The consequence is that install scripts must never hardcode an architecture - +hence `_arch.sh`. One tool diverges: Microsoft publishes the `powershell` deb +for amd64 only, so arm64 installs PowerShell from the upstream tarball. + +### Build, test, then push + +Each architecture builds once with `load: true`, runs the test suite against the +loaded image, and only then runs the build again with `push-by-digest`. The +second build is a cache hit, so it costs only the registry export. A final job +merges the per-architecture digests into one manifest list and attests it. + +Disk is the binding constraint. Runners ship with roughly 14 GB free, which is +not enough for the BuildKit cache plus a loaded image of this size, so every +build job reclaims space first. + +### Versioning and reproducibility + +Releases are calendar-versioned. Almost every entry in `versions.json` is +"install latest", so the image genuinely is a point-in-time snapshot, and +semantic versioning would imply a compatibility contract the build cannot +honour. A digest is the only stable identifier; releases record theirs. + +### Supply chain + +BuildKit generates an SBOM and full provenance for each pushed image, and +`actions/attest` signs the merged manifest with a short-lived Sigstore +certificate, pushing the attestation to the registry as an OCI referrer. That +single mechanism covers signing - a separate cosign step would sign the same +digest a second time with the same trust root, and was left out for that reason. + +Trivy scans report to the Security tab and never gate the publish. An image +bundling the Azure CLI, Ansible and a .NET SDK carries upstream HIGH findings at +essentially all times; gating on them would halt the weekly rebuild, and a +stalled rebuild leaves users on an older image with strictly more vulnerabilities +than the one that was blocked. diff --git a/CHANGELOG.md b/CHANGELOG.md index dd833d2..b2048c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,120 +1,164 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -### Added -- Complete devcontainer configuration for DevOps workflows -- Dockerfile with multi-tool installation -- Azure DevOps CI/CD pipeline for container registry -- Installation scripts with isolated /tmp directories for: - - Terraform (latest or pinned version) - - Terragrunt (v0.93.9) - - Azure CLI (latest) - - Docker Engine - - Kubernetes kubectl (latest or pinned) - - Helm (latest or pinned) - - Ansible with collections and Python dependencies - - PowerShell with modules (Az, Pester, PSScriptAnalyzer, powershell-yaml, ImportExcel) - - Python 3 with DevOps packages - - .NET SDK 10 (LTS channel 10.0, or a pinned SDK version) - - kubelogin (latest or pinned) - - yq (latest or pinned) - - jq - - tflint (latest or pinned) - - checkov (latest or pinned) - - git-crypt - - pre-commit - - ZSH with Oh My Zsh and plugins (zsh-autosuggestions, zsh-syntax-highlighting) -- VS Code extensions for DevOps work (including C# and C# Dev Kit for .NET development) -- ZSH as default shell with Oh My Zsh configuration -- Shell-aware environment configuration (bash/zsh completions) -- Custom .bashrc, .bash_aliases, .zshrc, and .environment files -- Entrypoint script for home directory initialization -- Persistent volume mounts for workspace and home directory -- Ansible collections: community.general, ansible.posix, azure.azcollection, community.docker, ansible.windows, community.crypto, kubernetes.core, microsoft.ad, community.windows -- Automatic installation of Python requirements for Ansible collections -- claude-swap (`cswap`), the Claude Code multi-account switcher, installed - system-wide from PyPI (latest or pinned via `CSWAP_VERSION`) -- uv (`uv`, `uvx`), the Astral Python package/project manager, installed as a - standalone binary with SHA256 verification (latest or pinned via `UV_VERSION`). - Available for interactive use only — no tool is installed through it yet -- Validation and integration test scripts -- Terminal profiles for zsh, bash, and pwsh -- .gitignore and .dockerignore files -- Documentation: - - README.md with full project documentation - - CONTRIBUTING.md with contribution guidelines - - SECURITY.md with security policies - - ARCHITECTURE.md with system architecture - - CHANGELOG.md (this file) - -### Changed -- Updated all installation scripts to use dedicated /tmp/install- directories -- Set zsh as default shell for vscode user -- Configured postStartCommand to run entrypoint script -- Environment file now detects shell type and loads appropriate completions -- Optimized Docker layers for better caching -- Fixed Ubuntu version from 22.01 to 22.04 -- Enhanced bash aliases for all major tools -- Improved terminal configuration with shell-specific completions -- PowerShell installer now configures PSGallery and installs common modules -- Ansible installer automatically finds and installs collection requirements -- Added cleanup step to remove /tmp/install-* directories -- Bumped default Node.js major from 22 to 24 LTS -- CA trust is now a bring-your-own drop-in: put a PEM `*.crt` chain in - `.devcontainer/files/certs/` and the Dockerfile merges it into the system - bundle. `NODE_EXTRA_CA_CERTS` points at `/etc/ssl/certs/ca-certificates.crt` - rather than a single named certificate, so the build works with no - certificates supplied -- `devcontainer.json` now builds from the local `Dockerfile` by default; the - pre-built registry image is the commented alternative -- `azure-pipelines.yml` uses placeholders for the agent pool, container registry - and registry service connection - -### Fixed -- Entrypoint script now properly executes via postStartCommand -- Shell syntax issues in install-powershell.sh (changed from sh to bash) -- Bash completion errors in zsh by adding shell detection -- Recursive permissions for Ansible collections - -### Security -- Added checksum validation for downloaded binaries (kubectl, helm, yq, terragrunt) -- Pinned tool versions for reproducibility where appropriate -- Added security scanning tools (checkov, tflint) -- Implemented git-crypt for secret management - -## [1.0.0] - 2025-11-21 - -### Added -- Initial release of DevOps DevContainer -- Basic tool installations -- Simple devcontainer configuration - ---- - -## Version History - -### How to Update This File - -When making changes: - -1. Add entries under `[Unreleased]` section -2. Organize by type: Added, Changed, Deprecated, Removed, Fixed, Security -3. When releasing, rename `[Unreleased]` to version number with date -4. Create new `[Unreleased]` section - -### Version Numbering - -- **MAJOR**: Incompatible changes (breaking changes) -- **MINOR**: New features (backward compatible) -- **PATCH**: Bug fixes (backward compatible) - -Example: 2.1.3 -- 2 = Major version -- 1 = Minor version -- 3 = Patch version +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). + +Releases are calendar-versioned (`vYYYY.MM.DD`) rather than semantically +versioned. Most tools install at "latest", so a tag records what the image +contained on a given date; it cannot promise a compatibility contract. + +## [Unreleased] + +### Added + +- Complete devcontainer configuration for DevOps workflows +- Dockerfile with multi-tool installation +- Installation scripts with isolated /tmp directories for: + - Terraform (latest or pinned version) + - Terragrunt (v0.93.9) + - Azure CLI (latest) + - Docker Engine + - Kubernetes kubectl (latest or pinned) + - Helm (latest or pinned) + - Ansible with collections and Python dependencies + - PowerShell with modules (Az, Pester, PSScriptAnalyzer, powershell-yaml, ImportExcel) + - Python 3 with DevOps packages + - .NET SDK 10 (LTS channel 10.0, or a pinned SDK version) + - kubelogin (latest or pinned) + - yq (latest or pinned) + - jq + - tflint (latest or pinned) + - checkov (latest or pinned) + - git-crypt + - pre-commit + - ZSH with Oh My Zsh and plugins (zsh-autosuggestions, zsh-syntax-highlighting) +- VS Code extensions for DevOps work (including C# and C# Dev Kit for .NET development) +- ZSH as default shell with Oh My Zsh configuration +- Shell-aware environment configuration (bash/zsh completions) +- Custom .bashrc, .bash_aliases, .zshrc, and .environment files +- Entrypoint script for home directory initialization +- Persistent volume mounts for workspace and home directory +- Ansible collections: community.general, ansible.posix, azure.azcollection, community.docker, ansible.windows, community.crypto, kubernetes.core, microsoft.ad, community.windows +- Automatic installation of Python requirements for Ansible collections +- claude-swap (`cswap`), the Claude Code multi-account switcher, installed + system-wide from PyPI (latest or pinned via `CSWAP_VERSION`) +- uv (`uv`, `uvx`), the Astral Python package/project manager, installed as a + standalone binary with SHA256 verification (latest or pinned via `UV_VERSION`). + Available for interactive use only — no tool is installed through it yet +- Validation and integration test scripts +- Terminal profiles for zsh, bash, and pwsh +- .gitignore and .dockerignore files +- Documentation: + - README.md with full project documentation + - CONTRIBUTING.md with contribution guidelines + - SECURITY.md with security policies + - ARCHITECTURE.md with system architecture + - CHANGELOG.md (this file) +- GitHub Actions CI publishing to the GitHub Container Registry: + - `ci.yml` lints, builds and tests both architectures on pull requests, and + publishes `:main` / `:sha-` on pushes to `main` + - `release.yml` cuts a weekly calendar-versioned release from a `--no-cache` + rebuild and moves `:latest` + - `build.yml` holds the shared build so the two entry points cannot drift + - SBOM and Sigstore-signed SLSA build provenance on every published image + - Trivy scanning reported to the Security tab, non-blocking by design +- `linux/arm64` images alongside `linux/amd64`, each built on a native runner +- `_arch.sh`, a sourced helper giving the install scripts the architecture in + the three spellings upstreams use +- `.hadolint.yaml`, so Dockerfile lint rules are shared by CI and pre-commit +- `.gitattributes`, declaring LF for every text file +- `.markdownlint.json` and `.yamllint.yaml`, so the markdown and YAML hooks + have rules that match this repository rather than failing on their defaults +- `.secrets.baseline`, without which the `detect-secrets` hook could not run + +### Changed + +- Updated all installation scripts to use dedicated `/tmp/install-` directories +- Set zsh as default shell for vscode user +- Configured postStartCommand to run entrypoint script +- Environment file now detects shell type and loads appropriate completions +- Optimized Docker layers for better caching +- Fixed Ubuntu version from 22.01 to 22.04 +- Enhanced bash aliases for all major tools +- Improved terminal configuration with shell-specific completions +- PowerShell installer now configures PSGallery and installs common modules +- Ansible installer automatically finds and installs collection requirements +- Added cleanup step to remove /tmp/install-* directories +- Bumped default Node.js major from 22 to 24 LTS +- CA trust is now a bring-your-own drop-in: put a PEM `*.crt` chain in + `.devcontainer/files/certs/` and the Dockerfile merges it into the system + bundle. `NODE_EXTRA_CA_CERTS` points at `/etc/ssl/certs/ca-certificates.crt` + rather than a single named certificate, so the build works with no + certificates supplied +- `devcontainer.json` pulls the published image by default; the local + Dockerfile build is now the commented-out contributor path +- PowerShell installs from the upstream tarball on `arm64` - Microsoft's Ubuntu + package repository publishes the `powershell` deb for `amd64` only +- Helm's checksum verification is now actually performed; it was downloaded and + then verified by a commented-out line +- The `shellcheck` and `hadolint` pre-commit hooks now read the same config + as the CI lint job, so a clean run locally means a clean run in CI + +### Removed + +- `azure-pipelines.yml` and its Azure Container Registry and Dependency-Track + integration, replaced by the GitHub Actions workflows above +- The `ansible-lint` pre-commit hook and `.ansible-lint`. This repo contains no + playbooks or roles, and the hook was pointed at the GitHub workflow files + +### Fixed + +- Entrypoint script now properly executes via postStartCommand +- Shell syntax issues in install-powershell.sh (changed from sh to bash) +- Bash completion errors in zsh by adding shell detection +- Recursive permissions for Ansible collections +- `pre-commit run --all-files` now completes. It previously failed on a missing + `.secrets.baseline`, on `check-json` parsing the JSONC `devcontainer.json`, + on 21 scripts carrying a shebang without an executable bit, and on an + `ansible-lint` hook incompatible with current `ansible-core` +- `install-azcopy.sh` can now install a pinned version. The URL pointed at a + retired CDN via a path containing a shell glob that curl cannot expand, so + that branch could never have worked; it now uses the GitHub release asset +- Line endings are consistent. The repo mixed CRLF docs with LF scripts and + declared neither, so editing a file could silently leave it mixed + +### Security + +- Added checksum validation for downloaded binaries (kubectl, helm, yq, terragrunt) +- Pinned tool versions for reproducibility where appropriate +- Added security scanning tools (checkov, tflint) +- Implemented git-crypt for secret management + +## [1.0.0] - 2025-11-21 + +### Added + +- Initial release of DevOps DevContainer +- Basic tool installations +- Simple devcontainer configuration + +--- + +## Version History + +### How to Update This File + +When making changes: + +1. Add entries under `[Unreleased]` section +2. Organize by type: Added, Changed, Deprecated, Removed, Fixed, Security +3. When releasing, rename `[Unreleased]` to version number with date +4. Create new `[Unreleased]` section + +### Version Numbering + +- **MAJOR**: Incompatible changes (breaking changes) +- **MINOR**: New features (backward compatible) +- **PATCH**: Bug fixes (backward compatible) + +Example: 2.1.3 + +- 2 = Major version +- 1 = Minor version +- 3 = Patch version diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f003f7c..97950f0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,221 +1,255 @@ -# Contributing to DevOps DevContainer - -Thank you for your interest in contributing! This document provides guidelines and instructions for contributing to this project. - -## 🌟 How to Contribute - -### Reporting Issues - -- Use the GitHub issue tracker -- Check if the issue already exists -- Provide detailed information: - - Steps to reproduce - - Expected vs actual behavior - - Tool versions - - Error messages/logs - -### Suggesting Enhancements - -- Open an issue with the "enhancement" label -- Clearly describe the feature -- Explain the use case and benefits -- Provide examples if possible - -### Pull Requests - -1. **Fork the repository** -2. **Create a feature branch** - ```bash - git checkout -b feature/your-feature-name - ``` - -3. **Make your changes** - - Follow the coding standards - - Update documentation - - Add tests if applicable - -4. **Test your changes** - ```bash - bash tests/validate-tools.sh - bash tests/integration-test.sh - ``` - -5. **Commit your changes** - ```bash - git commit -m "feat: add new feature" - ``` - - Use conventional commit messages: - - `feat:` New feature - - `fix:` Bug fix - - `docs:` Documentation changes - - `chore:` Maintenance tasks - - `refactor:` Code refactoring - - `test:` Test additions/changes - -6. **Push to your fork** - ```bash - git push origin feature/your-feature-name - ``` - -7. **Create a Pull Request** - -## 🔧 Development Guidelines - -### Adding New Tools - -1. **Create installation script** - ```bash - files/scripts/install-.sh - ``` - -2. **Follow the template:** - ```bash - #!/bin/bash - set -e - - VERSION=${1:-""} - - echo "Installing version ${VERSION}..." - - # Download with checksum validation - curl -LO "" - curl -LO "" - sha256sum -c - - # Install - # ... installation steps ... - - # Verify - --version - - echo " ${VERSION} installed successfully" - ``` - -3. **Update Dockerfile** - - Add ARG for version - - Add RUN command to install script - - Update in correct order (least to most likely to change) - -4. **Add to validation script** - ```bash - validate_tool "" " --version" || ((FAILURES++)) - ``` - -5. **Update README.md** with tool information - -### Version Updates - -- Update version ARGs in Dockerfile -- Update version in devcontainer.json build args -- Test the build thoroughly -- Update CHANGELOG.md - -### Testing Changes - -Always test in the actual devcontainer: - -1. Rebuild the container -2. Run validation: `bash tests/validate-tools.sh` -3. Run integration tests: `bash tests/integration-test.sh` -4. Test common workflows manually - -### Documentation - -- Keep README.md up to date -- Document new features in detail -- Update CHANGELOG.md -- Add inline comments for complex logic - -## 📋 Code Style - -### Shell Scripts - -- Use `#!/bin/bash` shebang -- Always use `set -e` for error handling -- Add descriptive comments -- Use meaningful variable names -- Quote variables: `"${VARIABLE}"` -- Validate inputs - -### Dockerfile - -- One logical action per RUN command when possible -- Combine related commands to reduce layers -- Clean up in the same layer as installation -- Use multi-line format for readability -- Comment each section - -### JSON/YAML - -- Use 2-space indentation -- Validate syntax before committing -- Keep alphabetically organized where logical - -## 🧪 Testing Requirements - -### For New Tools - -- Installation script must include version pinning -- Checksum validation required -- Add to validation script -- Add basic integration test - -### For Bug Fixes - -- Reproduce the bug -- Add test to prevent regression -- Verify fix in clean container - -### For Features - -- Add appropriate tests -- Update documentation -- Ensure backward compatibility - -## 📝 Pull Request Checklist - -- [ ] Code follows project style guidelines -- [ ] Tests pass locally -- [ ] Documentation updated -- [ ] CHANGELOG.md updated -- [ ] Commit messages follow conventional commits -- [ ] No merge conflicts -- [ ] Tested in actual devcontainer -- [ ] All new scripts are executable (`chmod +x`) - -## 🔍 Review Process - -1. Automated checks run on PR -2. Maintainers review code -3. Feedback addressed -4. Approved and merged - -## 🤝 Code of Conduct - -- Be respectful and inclusive -- Welcome newcomers -- Accept constructive criticism -- Focus on what's best for the project - -## 💬 Communication - -- Use GitHub issues for bugs and features -- Be clear and concise -- Provide context and examples -- Be patient and respectful - -## 📚 Resources - -- [Docker Best Practices](https://docs.docker.com/develop/dev-best-practices/) -- [VS Code Dev Containers](https://code.visualstudio.com/docs/devcontainers/containers) -- [Conventional Commits](https://www.conventionalcommits.org/) -- [Shell Style Guide](https://google.github.io/styleguide/shellguide.html) - -## 🎉 Recognition - -Contributors will be recognized in: -- GitHub contributors list -- CHANGELOG.md for significant contributions - -Thank you for contributing! 🚀 +# Contributing to DevOps DevContainer + +Thank you for your interest in contributing! This document provides guidelines and instructions for contributing to this project. + +## 🌟 How to Contribute + +### Reporting Issues + +- Use the GitHub issue tracker +- Check if the issue already exists +- Provide detailed information: + - Steps to reproduce + - Expected vs actual behavior + - Tool versions + - Error messages/logs + +### Suggesting Enhancements + +- Open an issue with the "enhancement" label +- Clearly describe the feature +- Explain the use case and benefits +- Provide examples if possible + +### Pull Requests + +1. **Fork the repository** +2. **Create a feature branch** + + ```bash + git checkout -b feature/your-feature-name + ``` + +3. **Make your changes** + - Follow the coding standards + - Update documentation + - Add tests if applicable + +4. **Test your changes** + + ```bash + bash tests/validate-tools.sh + bash tests/integration-test.sh + ``` + +5. **Commit your changes** + + ```bash + git commit -m "feat: add new feature" + ``` + + Use conventional commit messages: + - `feat:` New feature + - `fix:` Bug fix + - `docs:` Documentation changes + - `chore:` Maintenance tasks + - `refactor:` Code refactoring + - `test:` Test additions/changes + +6. **Push to your fork** + + ```bash + git push origin feature/your-feature-name + ``` + +7. **Create a Pull Request** + +## 🔧 Development Guidelines + +### Adding New Tools + +1. **Create installation script** + + ```bash + files/scripts/install-.sh + ``` + +2. **Follow the template:** + + ```bash + #!/bin/bash + set -e + + VERSION=${1:-""} + + echo "Installing version ${VERSION}..." + + # Download with checksum validation + curl -LO "" + curl -LO "" + sha256sum -c + + # Install + # ... installation steps ... + + # Verify + --version + + echo " ${VERSION} installed successfully" + ``` + +3. **Update Dockerfile** + - Add ARG for version + - Add RUN command to install script + - Update in correct order (least to most likely to change) + +4. **Add to validation script** + + ```bash + validate_tool "" " --version" || ((FAILURES++)) + ``` + +5. **Update README.md** with tool information + +### Version Updates + +- Update version ARGs in Dockerfile +- Update version in devcontainer.json build args +- Test the build thoroughly +- Update CHANGELOG.md + +### Testing Changes + +Always test in the actual devcontainer: + +1. Switch `.devcontainer/devcontainer.json` to the local build - comment out the + `"image"` line and uncomment the `"build"` block. It pulls the published + image by default, which would not contain your changes. +2. Rebuild the container +3. Run validation: `bash tests/validate-tools.sh` +4. Run integration tests: `bash tests/integration-test.sh` +5. Test common workflows manually + +Take care not to commit that switch. CI builds from the `Dockerfile` regardless, +so leaving `"image"` active is correct for everyone who is not changing the +image itself. + +### What CI checks + +Opening a pull request runs the `lint` job (`bash -n` and `shellcheck` over +every script, a JSON parse over every JSON file, `hadolint` over the +Dockerfile), then builds `linux/amd64` and `linux/arm64` and runs +`tests/run-all-tests.sh` inside each image. Nothing is published from a pull +request. + +You can run the lint checks locally before pushing: + +```bash +shellcheck -x -S warning .devcontainer/files/install/*.sh tests/*.sh scripts/*.sh +hadolint --config .hadolint.yaml .devcontainer/Dockerfile +``` + +**The image builds for two architectures, so never hardcode one.** Source +`_arch.sh` in any install script that downloads an architecture-specific +artefact - see the README's "Adding New Tools". + +### Documentation + +- Keep README.md up to date +- Document new features in detail +- Update CHANGELOG.md +- Add inline comments for complex logic + +## 📋 Code Style + +### Shell Scripts + +- Use `#!/bin/bash` shebang +- Always use `set -e` for error handling +- Add descriptive comments +- Use meaningful variable names +- Quote variables: `"${VARIABLE}"` +- Validate inputs + +### Dockerfile + +- One logical action per RUN command when possible +- Combine related commands to reduce layers +- Clean up in the same layer as installation +- Use multi-line format for readability +- Comment each section + +### JSON/YAML + +- Use 2-space indentation +- Validate syntax before committing +- Keep alphabetically organized where logical + +## 🧪 Testing Requirements + +### For New Tools + +- Installation script must include version pinning +- Checksum validation required +- Add to validation script +- Add basic integration test + +### For Bug Fixes + +- Reproduce the bug +- Add test to prevent regression +- Verify fix in clean container + +### For Features + +- Add appropriate tests +- Update documentation +- Ensure backward compatibility + +## 📝 Pull Request Checklist + +- [ ] Code follows project style guidelines +- [ ] Tests pass locally +- [ ] Documentation updated +- [ ] CHANGELOG.md updated +- [ ] Commit messages follow conventional commits +- [ ] No merge conflicts +- [ ] Tested in actual devcontainer +- [ ] All new scripts are executable (`chmod +x`) + +## 🔍 Review Process + +1. Automated checks run on PR +2. Maintainers review code +3. Feedback addressed +4. Approved and merged + +## 🤝 Code of Conduct + +- Be respectful and inclusive +- Welcome newcomers +- Accept constructive criticism +- Focus on what's best for the project + +## 💬 Communication + +- Use GitHub issues for bugs and features +- Be clear and concise +- Provide context and examples +- Be patient and respectful + +## 📚 Resources + +- [Docker Best Practices](https://docs.docker.com/develop/dev-best-practices/) +- [VS Code Dev Containers](https://code.visualstudio.com/docs/devcontainers/containers) +- [Conventional Commits](https://www.conventionalcommits.org/) +- [Shell Style Guide](https://google.github.io/styleguide/shellguide.html) + +## 🎉 Recognition + +Contributors will be recognized in: + +- GitHub contributors list +- CHANGELOG.md for significant contributions + +Thank you for contributing! 🚀 diff --git a/LICENSE b/LICENSE index c311d3e..8a47ae6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ -MIT License - -Copyright (c) 2025 [Your Name/Organization] - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +MIT License + +Copyright (c) 2025 [Your Name/Organization] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/QUICKSTART.md b/QUICKSTART.md index 20c11c3..df63bfa 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -1,120 +1,130 @@ -# Quick Start Guide - -Get up and running with the DevOps DevContainer in 5 minutes! - -## Prerequisites - -- [Docker Desktop](https://www.docker.com/products/docker-desktop/) running -- [VS Code](https://code.visualstudio.com/) installed -- [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) installed - -## Steps - -### 1. Clone the Repository - -```bash -git clone -cd devcontainer -``` - -### 2. Open in VS Code - -```bash -code . -``` - -### 3. Open in Container - -When prompted, click **"Reopen in Container"** - -Or manually: -- Press `F1` or `Ctrl+Shift+P` -- Type: `Dev Containers: Reopen in Container` -- Press Enter - -### 4. Wait for Build - -First build takes 5-10 minutes. Subsequent builds are much faster. - -### 5. Verify Installation - -Once inside the container, run: - -```bash -validate -``` - -This checks all tools are installed correctly. - -## What's Included? - -✅ Terraform & Terragrunt -✅ Azure CLI -✅ Docker & Kubernetes (kubectl, helm) -✅ Ansible -✅ PowerShell 7 -✅ Python 3 with DevOps tools -✅ Security scanners (tflint, checkov) -✅ Data tools (jq, yq) - -## Common Commands - -```bash -# Terraform -tf init -tf plan -tf apply - -# Azure -az login -az account list - -# Kubernetes -k get pods -helm list - -# Docker -docker ps -docker images - -# Run tests -testall -``` - -## Next Steps - -- Review [CONTRIBUTING.md](CONTRIBUTING.md) for development guidelines -- Check [ARCHITECTURE.md](ARCHITECTURE.md) for system design -- Set up [pre-commit hooks](.pre-commit/README.md) -- Explore [variants](.devcontainer/variants/README.md) for cloud-specific setups - -## Troubleshooting - -### Container won't start -```bash -# Rebuild without cache -F1 → Dev Containers: Rebuild Container Without Cache -``` - -### Tools not found -```bash -# Verify PATH -echo $PATH - -# Re-source environment -source ~/.bashrc -``` - -### Permission issues -```bash -# Fix workspace permissions -sudo chown -R vscode:vscode /workspace -``` - -## Need Help? - -- Check [README.md](README.md) for full documentation -- Review [SECURITY.md](SECURITY.md) for security best practices -- Open an issue on GitHub - -Happy coding! 🚀 +# Quick Start Guide + +Get up and running with the DevOps DevContainer in 5 minutes! + +## Prerequisites + +- [Docker Desktop](https://www.docker.com/products/docker-desktop/) running +- [VS Code](https://code.visualstudio.com/) installed +- [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) installed + +## Steps + +### 1. Clone the Repository + +```bash +git clone https://github.com/grinidx/devcontainer-devops.git +cd devcontainer-devops +``` + +### 2. Open in VS Code + +```bash +code . +``` + +### 3. Open in Container + +When prompted, click **"Reopen in Container"** + +Or manually: + +- Press `F1` or `Ctrl+Shift+P` +- Type: `Dev Containers: Reopen in Container` +- Press Enter + +### 4. Wait for the Pull + +The pre-built image is pulled from GHCR, so there is no build to wait for. It is +a large image, so the first pull still takes a few minutes. + +If you are changing the `Dockerfile` or an install script, switch +`.devcontainer/devcontainer.json` to the local build first - see +[CONTRIBUTING.md](CONTRIBUTING.md#testing-changes). A full local build takes +considerably longer than a pull. + +### 5. Verify Installation + +Once inside the container, run: + +```bash +validate +``` + +This checks all tools are installed correctly. + +## What's Included? + +✅ Terraform & Terragrunt +✅ Azure CLI +✅ Docker & Kubernetes (kubectl, helm) +✅ Ansible +✅ PowerShell 7 +✅ Python 3 with DevOps tools +✅ Security scanners (tflint, checkov) +✅ Data tools (jq, yq) + +## Common Commands + +```bash +# Terraform +tf init +tf plan +tf apply + +# Azure +az login +az account list + +# Kubernetes +k get pods +helm list + +# Docker +docker ps +docker images + +# Run tests +testall +``` + +## Next Steps + +- Review [CONTRIBUTING.md](CONTRIBUTING.md) for development guidelines +- Check [ARCHITECTURE.md](ARCHITECTURE.md) for system design +- Set up [pre-commit hooks](.pre-commit/README.md) +- Read [VERSION_MANAGEMENT.md](VERSION_MANAGEMENT.md) to pin tool versions + +## Troubleshooting + +### Container won't start + +```bash +# Rebuild without cache +F1 → Dev Containers: Rebuild Container Without Cache +``` + +### Tools not found + +```bash +# Verify PATH +echo $PATH + +# Re-source environment +source ~/.bashrc +``` + +### Permission issues + +```bash +# Fix workspace permissions +sudo chown -R vscode:vscode /workspace +``` + +## Need Help? + +- Check [README.md](README.md) for full documentation +- Review [SECURITY.md](SECURITY.md) for security best practices +- Open an issue on GitHub + +Happy coding! 🚀 diff --git a/README.md b/README.md index 9c46184..30a7a08 100644 --- a/README.md +++ b/README.md @@ -1,336 +1,420 @@ -# DevOps Development Container - -A comprehensive development container for DevOps and Infrastructure-as-Code workflows, built on Ubuntu 24.04 with essential tools for cloud infrastructure management, container orchestration, and automation. - -## 🚀 Features - -This devcontainer includes pre-configured tools for: - -- **Infrastructure as Code**: Terraform, Terragrunt, tflint, tf-summarize, checkov -- **Cloud Management**: Azure CLI (az), AzCopy -- **Container Operations**: Docker Engine, Helm, kubectl, kubelogin -- **Configuration Management**: Ansible with 9 popular collections -- **Scripting & Automation**: PowerShell 7 with modules, Python with DevOps tools, uv, Node.js, ZSH with Oh My Zsh -- **.NET Development**: .NET 10 SDK (LTS) with C# and C# Dev Kit extensions -- **AI Tooling**: Claude Code, Codex, cswap (Claude Code account switcher) -- **Security**: git-crypt, gitleaks, checkov, optional custom CA trust chain -- **Development Utilities**: Custom bash/zsh aliases, shell completions, pre-commit -- **Data Processing**: jq, yq - -## 📋 Included Tools - -| Tool | Purpose | -|------|---------| -| Terraform | Infrastructure provisioning | -| Terragrunt | Terraform wrapper for DRY configurations | -| tflint | Terraform linting | -| tf-summarize | Human-readable Terraform plan summaries | -| checkov | IaC security and compliance scanning | -| Azure CLI | Azure cloud management | -| AzCopy | Bulk transfer to/from Azure Storage | -| Docker | Container runtime and management | -| Helm | Kubernetes package manager | -| kubectl | Kubernetes cluster management | -| kubelogin | Azure AD authentication for kubectl | -| Ansible | Configuration management and automation | -| PowerShell | Cross-platform automation and scripting | -| Python | Scripting with DevOps-focused packages | -| uv | Fast Python package/project manager (`uv`, `uvx`) | -| Node.js | JavaScript runtime and npm tooling | -| .NET SDK | C# / .NET 10 application development | -| Claude Code | Anthropic coding agent (self-updating, user-tree install) | -| Codex | OpenAI coding agent (run `codex-init` to configure) | -| cswap | Switch between Claude Code accounts (`claude-swap`) | -| git-crypt | Transparent encryption of files in git | -| gitleaks | Secret scanning | -| pre-commit | Git hook framework | -| jq / yq | JSON and YAML processing | - -> `jq`, `zsh` and the other base utilities come from the apt package list and the -> upstream devcontainer base image rather than a dedicated `install-*.sh` script — -> which is why they have no entry in the `install/` tree above. -> -> `uv` is available for interactive use, but no tool in this image is installed -> through it — the Python-based tools (checkov, claude-swap, ansible) still use -> system-wide `pip`. - -## 🏗️ Repository Structure - -``` -devcontainer/ -├── .devcontainer/ -│ ├── Dockerfile # Multi-stage container build -│ ├── devcontainer.json # VS Code devcontainer configuration -│ └── files/ -│ ├── install/ # Installation scripts (each uses /tmp/install-) -│ │ ├── install-ansible.sh -│ │ ├── install-azcopy.sh -│ │ ├── install-azure-cli.sh -│ │ ├── install-checkov.sh -│ │ ├── install-claude-code.sh -│ │ ├── install-codex.sh -│ │ ├── install-cswap.sh -│ │ ├── install-docker.sh -│ │ ├── install-dotnet.sh -│ │ ├── install-git-crypt.sh -│ │ ├── install-gitleaks.sh -│ │ ├── install-helm.sh -│ │ ├── install-kubectl.sh -│ │ ├── install-kubelogin.sh -│ │ ├── install-node.sh -│ │ ├── install-powershell.sh -│ │ ├── install-pre-commit.sh -│ │ ├── install-python-tools.sh -│ │ ├── install-terraform.sh -│ │ ├── install-terragrunt.sh -│ │ ├── install-tf-summarize.sh -│ │ ├── install-tflint.sh -│ │ ├── install-uv.sh -│ │ └── install-yq.sh -│ ├── certs/ # Drop-in dir for extra CA certificates -│ │ └── README.md # How to add your own CA chain -│ ├── codex/ # Codex bootstrap (rendered by `codex-init`) -│ │ ├── codex-init -│ │ └── config.toml.tmpl -│ ├── home/ # Home directory files -│ │ ├── .bash_aliases # Convenience aliases -│ │ ├── .environment # Shell-aware environment config -│ │ ├── .zshrc # ZSH configuration -│ │ ├── .claude/ # Claude Code defaults -│ │ └── .config/ # PowerShell profile and theme -│ └── entrypoint.sh # Container entrypoint for home dir init -├── tests/ -│ ├── integration-test.sh # Integration tests -│ ├── run-all-tests.sh # Test runner -│ └── validate-tools.sh # Tool validation -├── scripts/ -│ └── check-latest-versions.sh -├── azure-pipelines.yml # CI/CD pipeline for ACR -├── ARCHITECTURE.md # System architecture documentation -├── CHANGELOG.md # Version history -├── CONTRIBUTING.md # Contribution guidelines -├── QUICKSTART.md # Quick start guide -├── README.md # This file -├── SECURITY.md # Security policies -└── VERSION_MANAGEMENT.md # Version management guide -``` - -## 🔧 Getting Started - -### Prerequisites - -- [Visual Studio Code](https://code.visualstudio.com/) -- [Docker Desktop](https://www.docker.com/products/docker-desktop/) -- [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) - -### Quick Start - -1. **Clone the repository:** - ```bash - git clone - cd devcontainer - ``` - -2. **Open in VS Code:** - ```bash - code . - ``` - -3. **Reopen in Container:** - - Press `F1` or `Ctrl+Shift+P` - - Select `Dev Containers: Reopen in Container` - - Wait for the container to build (first time takes longer) - -4. **Start developing!** - The container will be ready with all tools pre-installed. - -## 💾 Storage Configuration - -The devcontainer uses Docker volumes for persistent storage: - -- **Workspace Volume**: `dev-workspace-` mounted at `/workspace` -- **Home Volume**: `dev-home-` mounted at `/home/vscode` -- **Bind Mount**: The local workspace folder mounted at `/workspace/devcontainer` -- **Permissions**: Automatically configured via `postCreateCommand` -- **Home Init**: Entrypoint script copies default configs on first run - -This ensures your work and settings persist across container rebuilds. - -Both volumes are per-user (suffixed with `$USER`), so several checkouts can run -side by side without sharing state. Note that `/home/vscode` is only seeded from -the image's `/tmp-home` template on **first** start — tools installed into the -home tree do not refresh on rebuild for an existing volume, which is why most -tooling installs system-wide. - -## 🔐 Custom CA Certificates - -If your environment terminates TLS with a private CA, drop the PEM-encoded chain -into `.devcontainer/files/certs/` as a `*.crt` file before building. The -`Dockerfile` copies the directory to `/usr/local/share/ca-certificates/extra/` -and runs `update-ca-certificates`, merging it into the system bundle at -`/etc/ssl/certs/ca-certificates.crt`. - -`REQUESTS_CA_BUNDLE`, `SSL_CERT_FILE` and `NODE_EXTRA_CA_CERTS` all point at that -bundle, which covers Python (`az`, `ansible`, `checkov`, …), `curl`, and Node.js — -Node ignores the system store, so it has to be told explicitly. - -Certificates are git-ignored (`*.crt`, `*.pem`), so nothing is committed by -accident. Adding none is fine: the build succeeds and the container trusts the -public roots from the base image. See -[`.devcontainer/files/certs/README.md`](.devcontainer/files/certs/README.md). - -## 🔄 CI/CD Pipeline - -An Azure DevOps pipeline is included to automatically build and push the container image to Azure Container Registry (ACR). - -### Setup - -1. **Create Azure Container Registry:** - ```bash - az acr create --resource-group --name --sku Basic - ``` - -2. **Configure Azure DevOps:** - - Create a Docker Registry service connection to your ACR - - Replace the ``, `` and - `` placeholders in `azure-pipelines.yml` - - Either provide a `Dependency_track` variable group (supplying - `Dependency_track_URL` and `Dependency_track_API_KEY`) or remove that - variable group and the SBOM upload task - -3. **Pipeline Triggers:** - - Automatically triggers on commits to `main` - - Weekly scheduled rebuild (Sundays, 00:00) using `--no-cache` so unpinned - tools and the base image pick up upstream updates - - Publishes an SBOM to Dependency Track - -See [`azure-pipelines.yml`](azure-pipelines.yml) for the full pipeline definition. - -## 🛠️ Customization - -### Adding New Tools - -1. Create an installation script in `.devcontainer/files/install/`. It should take - the desired version as `$1`, resolve the latest when that argument is empty, - and verify the install before exiting: - - ```bash - .devcontainer/files/install/install-your-tool.sh - ``` - - The whole directory is copied and `chmod +x`'d in one step, so no `COPY` line - is needed per script. - -2. Add an `ARG YOUR_TOOL_VERSION=` to **both** blocks at the top of the - `Dockerfile` (before and after the `FROM`), then invoke the script: - - ```dockerfile - RUN /tmp/install/install-your-tool.sh ${YOUR_TOOL_VERSION} - ``` - - Place the `RUN` as late in the file as the dependencies allow — a version bump - invalidates every layer after it. - -3. Record the version in `versions.json`, add a `validate_tool` line to - `tests/validate-tools.sh`, and note it in `CHANGELOG.md`. - -4. Rebuild the container - -### Modifying Tool Versions - -`versions.json` is the documented source of truth for tool versions; an empty -string means "install latest". See [`VERSION_MANAGEMENT.md`](VERSION_MANAGEMENT.md) -for the per-tool version sources. - -The `Dockerfile` ARGs default to empty (latest). `.devcontainer/devcontainer.json` -builds from the local `Dockerfile` by default — to pin, add the versions to its -`build.args` block: - -```json -"args": { - "UBUNTU_VERSION": "24.04", - "TERRAFORM_VERSION": "1.13.5", - "POWERSHELL_VERSION": "7.5.4" -} -``` - -> The CI pipeline does **not** currently pass these build args, so scheduled -> image builds install the latest of everything left unpinned in the `Dockerfile`. - -## 📝 Usage Examples - -### Terraform -```bash -terraform init -terraform plan -terraform apply -``` - -### Azure CLI -```bash -az login -az account list -az group create --name myResourceGroup --location eastus -``` - -### Docker -```bash -docker ps -docker build -t myimage . -docker run myimage -``` - -### Helm & Kubernetes -```bash -kubectl get pods -helm install myrelease mychart/ -``` - -### AI Tooling -```bash -claude # start Claude Code -codex-init # one-off: configure Codex endpoint and deployment - -cswap list # list managed Claude Code accounts -cswap status # show the account currently in use -cswap add # register the account you are signed in as -cswap switch # rotate to the next account -``` - -## 🤝 Contributing - -1. Create a feature branch -2. Make your changes -3. Test in the devcontainer -4. Submit a pull request - -## 📄 License - -[Add your license information here] - -## 🐛 Troubleshooting - -### Container won't build -- Ensure Docker Desktop is running -- Check Docker has sufficient resources (CPU/Memory) -- Try rebuilding without cache: `Dev Containers: Rebuild Container` - -### Permission issues in /workspace -- The `postCreateCommand` should handle this automatically -- Manually run: `sudo chown -R vscode:vscode /workspace` - -### Tool not found -- Verify the installation script exists in `.devcontainer/files/install/` -- Check the `Dockerfile` has a `RUN /tmp/install/install-.sh` step -- Confirm it appears in `tests/validate-tools.sh`, then run that script -- Rebuild the container - -### A home-directory tool is missing or stale after a rebuild -`/home/vscode` is a persistent per-user volume, seeded from the image only on -first start. Anything installed into the home tree (Claude Code, for example) -will not refresh for an existing volume. Remove the `dev-home-` volume to -re-seed, or update the tool in place. - -## 📞 Support - -[Add contact information or support channels] +# DevOps Development Container + +A comprehensive development container for DevOps and Infrastructure-as-Code workflows, built on Ubuntu 24.04 with essential tools for cloud infrastructure management, container orchestration, and automation. + +## 🚀 Features + +This devcontainer includes pre-configured tools for: + +- **Infrastructure as Code**: Terraform, Terragrunt, tflint, tf-summarize, checkov +- **Cloud Management**: Azure CLI (az), AzCopy +- **Container Operations**: Docker Engine, Helm, kubectl, kubelogin +- **Configuration Management**: Ansible with 9 popular collections +- **Scripting & Automation**: PowerShell 7 with modules, Python with DevOps tools, uv, Node.js, ZSH with Oh My Zsh +- **.NET Development**: .NET 10 SDK (LTS) with C# and C# Dev Kit extensions +- **AI Tooling**: Claude Code, Codex, cswap (Claude Code account switcher) +- **Security**: git-crypt, gitleaks, checkov, optional custom CA trust chain +- **Development Utilities**: Custom bash/zsh aliases, shell completions, pre-commit +- **Data Processing**: jq, yq + +## 📋 Included Tools + +| Tool | Purpose | +|------|---------| +| Terraform | Infrastructure provisioning | +| Terragrunt | Terraform wrapper for DRY configurations | +| tflint | Terraform linting | +| tf-summarize | Human-readable Terraform plan summaries | +| checkov | IaC security and compliance scanning | +| Azure CLI | Azure cloud management | +| AzCopy | Bulk transfer to/from Azure Storage | +| Docker | Container runtime and management | +| Helm | Kubernetes package manager | +| kubectl | Kubernetes cluster management | +| kubelogin | Azure AD authentication for kubectl | +| Ansible | Configuration management and automation | +| PowerShell | Cross-platform automation and scripting | +| Python | Scripting with DevOps-focused packages | +| uv | Fast Python package/project manager (`uv`, `uvx`) | +| Node.js | JavaScript runtime and npm tooling | +| .NET SDK | C# / .NET 10 application development | +| Claude Code | Anthropic coding agent (self-updating, user-tree install) | +| Codex | OpenAI coding agent (run `codex-init` to configure) | +| cswap | Switch between Claude Code accounts (`claude-swap`) | +| git-crypt | Transparent encryption of files in git | +| gitleaks | Secret scanning | +| pre-commit | Git hook framework | +| jq / yq | JSON and YAML processing | + +> `jq`, `zsh` and the other base utilities come from the apt package list and the +> upstream devcontainer base image rather than a dedicated `install-*.sh` script — +> which is why they have no entry in the `install/` tree above. +> +> `uv` is available for interactive use, but no tool in this image is installed +> through it — the Python-based tools (checkov, claude-swap, ansible) still use +> system-wide `pip`. + +## 🏗️ Repository Structure + +```text +devcontainer-devops/ +├── .github/ +│ └── workflows/ +│ ├── build.yml # Reusable build: lint, build, test, scan, publish +│ ├── ci.yml # Pull requests and pushes to main +│ └── release.yml # Weekly CalVer release +├── .devcontainer/ +│ ├── Dockerfile # Multi-stage container build +│ ├── devcontainer.json # VS Code devcontainer configuration +│ └── files/ +│ ├── install/ # Installation scripts (each uses /tmp/install-) +│ │ ├── _arch.sh # Sourced helper: architecture detection +│ │ ├── install-ansible.sh +│ │ ├── install-azcopy.sh +│ │ ├── install-azure-cli.sh +│ │ ├── install-checkov.sh +│ │ ├── install-claude-code.sh +│ │ ├── install-codex.sh +│ │ ├── install-cswap.sh +│ │ ├── install-docker.sh +│ │ ├── install-dotnet.sh +│ │ ├── install-git-crypt.sh +│ │ ├── install-gitleaks.sh +│ │ ├── install-helm.sh +│ │ ├── install-kubectl.sh +│ │ ├── install-kubelogin.sh +│ │ ├── install-node.sh +│ │ ├── install-powershell.sh +│ │ ├── install-pre-commit.sh +│ │ ├── install-python-tools.sh +│ │ ├── install-terraform.sh +│ │ ├── install-terragrunt.sh +│ │ ├── install-tf-summarize.sh +│ │ ├── install-tflint.sh +│ │ ├── install-uv.sh +│ │ └── install-yq.sh +│ ├── certs/ # Drop-in dir for extra CA certificates +│ │ └── README.md # How to add your own CA chain +│ ├── codex/ # Codex bootstrap (rendered by `codex-init`) +│ │ ├── codex-init +│ │ └── config.toml.tmpl +│ ├── home/ # Home directory files +│ │ ├── .bash_aliases # Convenience aliases +│ │ ├── .environment # Shell-aware environment config +│ │ ├── .zshrc # ZSH configuration +│ │ ├── .claude/ # Claude Code defaults +│ │ └── .config/ # PowerShell profile and theme +│ └── entrypoint.sh # Container entrypoint for home dir init +├── tests/ +│ ├── integration-test.sh # Integration tests +│ ├── run-all-tests.sh # Test runner +│ └── validate-tools.sh # Tool validation +├── scripts/ +│ └── check-latest-versions.sh +├── .hadolint.yaml # Dockerfile lint rules, shared by CI and pre-commit +├── ARCHITECTURE.md # System architecture documentation +├── CHANGELOG.md # Version history +├── CONTRIBUTING.md # Contribution guidelines +├── QUICKSTART.md # Quick start guide +├── README.md # This file +├── SECURITY.md # Security policies +└── VERSION_MANAGEMENT.md # Version management guide +``` + +## 🔧 Getting Started + +### Prerequisites + +- [Visual Studio Code](https://code.visualstudio.com/) +- [Docker Desktop](https://www.docker.com/products/docker-desktop/) +- [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) + +### Quick Start + +1. **Clone the repository:** + + ```bash + git clone https://github.com/grinidx/devcontainer-devops.git + cd devcontainer-devops + ``` + +2. **Open in VS Code:** + + ```bash + code . + ``` + +3. **Reopen in Container:** + - Press `F1` or `Ctrl+Shift+P` + - Select `Dev Containers: Reopen in Container` + - The pre-built image is pulled from GHCR, so there is no wait for a build + +4. **Start developing!** + The container will be ready with all tools pre-installed. + +### Using the image directly + +You do not need this repository to use the container. Point any +`devcontainer.json` at the published image, or pull it yourself: + +```bash +docker pull ghcr.io/grinidx/devcontainer-devops:latest +``` + +| Tag | What it is | +|-----|------------| +| `latest` | The most recent weekly release | +| `2026.08.09` | A specific weekly release | +| `2026.08` | The most recent release in that month | +| `main` | Head of the default branch, rebuilt on every push | +| `sha-abc1234` | One specific commit | + +Images are published for `linux/amd64` and `linux/arm64`; Docker picks the +right one automatically. + +## 💾 Storage Configuration + +The devcontainer uses Docker volumes for persistent storage: + +- **Workspace Volume**: `dev-workspace-` mounted at `/workspace` +- **Home Volume**: `dev-home-` mounted at `/home/vscode` +- **Bind Mount**: The local workspace folder mounted at `/workspace/devcontainer` +- **Permissions**: Automatically configured via `postCreateCommand` +- **Home Init**: Entrypoint script copies default configs on first run + +This ensures your work and settings persist across container rebuilds. + +Both volumes are per-user (suffixed with `$USER`), so several checkouts can run +side by side without sharing state. Note that `/home/vscode` is only seeded from +the image's `/tmp-home` template on **first** start — tools installed into the +home tree do not refresh on rebuild for an existing volume, which is why most +tooling installs system-wide. + +## 🔐 Custom CA Certificates + +If your environment terminates TLS with a private CA, drop the PEM-encoded chain +into `.devcontainer/files/certs/` as a `*.crt` file before building. The +`Dockerfile` copies the directory to `/usr/local/share/ca-certificates/extra/` +and runs `update-ca-certificates`, merging it into the system bundle at +`/etc/ssl/certs/ca-certificates.crt`. + +`REQUESTS_CA_BUNDLE`, `SSL_CERT_FILE` and `NODE_EXTRA_CA_CERTS` all point at that +bundle, which covers Python (`az`, `ansible`, `checkov`, …), `curl`, and Node.js — +Node ignores the system store, so it has to be told explicitly. + +Certificates are git-ignored (`*.crt`, `*.pem`), so nothing is committed by +accident. Adding none is fine: the build succeeds and the container trusts the +public roots from the base image. See +[`.devcontainer/files/certs/README.md`](.devcontainer/files/certs/README.md). + +## 🔄 CI/CD + +GitHub Actions builds, tests and publishes the image to the GitHub Container +Registry. There is nothing to configure - it runs on the repository's own +`GITHUB_TOKEN`, with no secrets and no external registry account. + +| Workflow | Trigger | What it does | +|----------|---------|--------------| +| [`ci.yml`](.github/workflows/ci.yml) | Pull requests | Lints, builds both architectures, runs the test suite. Publishes nothing | +| [`ci.yml`](.github/workflows/ci.yml) | Push to `main` | The same, then publishes `:main` and `:sha-` | +| [`release.yml`](.github/workflows/release.yml) | Sundays 03:00 UTC, or manually | A `--no-cache` rebuild, published as a dated release and `:latest` | + +Both call [`build.yml`](.github/workflows/build.yml), which holds the actual +build so the two entry points cannot drift apart. + +### How a build works + +Each architecture builds on its own native runner - `ubuntu-24.04` and +`ubuntu-24.04-arm` - rather than under QEMU emulation, which would take hours +for an image this size. Each runner builds its platform, loads it locally, runs +[`tests/run-all-tests.sh`](tests/run-all-tests.sh) against the real image, and +only then pushes by digest. A final job merges the digests into one +multi-architecture manifest. + +### Versioning + +Releases are calendar-versioned: `v2026.08.09` is the image as it was built on +that date. Most entries in `versions.json` are "install latest", so a tag is a +point-in-time snapshot rather than a reproducible build - rebuilding the same +tag a week later would produce a different image. **If you need one exact +image, pin the digest**, which every release records. + +### Supply chain + +Every published image carries an SBOM and SLSA build provenance, generated by +BuildKit and signed with a short-lived [Sigstore](https://www.sigstore.dev/) +certificate. Verify that an image really came from this repository: + +```bash +gh attestation verify oci://ghcr.io/grinidx/devcontainer-devops:latest \ + -R grinidx/devcontainer-devops +``` + +Read the SBOM out of the image: + +```bash +docker buildx imagetools inspect ghcr.io/grinidx/devcontainer-devops:latest \ + --format '{{ json .SBOM }}' +``` + +Trivy scans each build for HIGH and CRITICAL vulnerabilities and reports them +to the repository's Security tab. Scans report, they do not block: an image +bundling the Azure CLI, Ansible and a .NET SDK always carries some upstream +findings, and blocking on those would stop the weekly rebuild and leave the +published image staler than the CVEs it was avoiding. + +## 🛠️ Customization + +### Adding New Tools + +1. Create an installation script in `.devcontainer/files/install/`. It should take + the desired version as `$1`, resolve the latest when that argument is empty, + and verify the install before exiting: + + ```bash + .devcontainer/files/install/install-your-tool.sh + ``` + + The whole directory is copied and `chmod +x`'d in one step, so no `COPY` line + is needed per script. + + **The image is built for `amd64` and `arm64`, so never hardcode an + architecture.** Source the shared helper and use the spelling your upstream + uses: + + ```bash + . "$(dirname "$0")/_arch.sh" + # ARCH_DEB amd64 / arm64 Debian and Go convention, most releases + # ARCH_X64 x64 / arm64 e.g. gitleaks + # ARCH_GNU x86_64 / aarch64 Rust target triples, e.g. uv + ``` + + If the tool has no `arm64` Linux build, say so in a comment and skip it on + that architecture rather than failing the build. + +2. Add an `ARG YOUR_TOOL_VERSION=` to **both** blocks at the top of the + `Dockerfile` (before and after the `FROM`), then invoke the script: + + ```dockerfile + RUN /tmp/install/install-your-tool.sh ${YOUR_TOOL_VERSION} + ``` + + Place the `RUN` as late in the file as the dependencies allow — a version bump + invalidates every layer after it. + +3. Record the version in `versions.json`, add a `validate_tool` line to + `tests/validate-tools.sh`, and note it in `CHANGELOG.md`. + +4. Rebuild the container + +### Modifying Tool Versions + +`versions.json` is the documented source of truth for tool versions; an empty +string means "install latest". See [`VERSION_MANAGEMENT.md`](VERSION_MANAGEMENT.md) +for the per-tool version sources. + +The `Dockerfile` ARGs default to empty (latest). `.devcontainer/devcontainer.json` +builds from the local `Dockerfile` by default — to pin, add the versions to its +`build.args` block: + +```json +"args": { + "UBUNTU_VERSION": "24.04", + "TERRAFORM_VERSION": "1.13.5", + "POWERSHELL_VERSION": "7.5.4" +} +``` + +> CI does **not** pass these build args, so published images install the latest +> of everything left unpinned in the `Dockerfile`. That is deliberate - see +> [Versioning](#versioning) - and it is why a release tag is a snapshot rather +> than a reproducible build. + +## 📝 Usage Examples + +### Terraform + +```bash +terraform init +terraform plan +terraform apply +``` + +### Azure CLI + +```bash +az login +az account list +az group create --name myResourceGroup --location eastus +``` + +### Docker + +```bash +docker ps +docker build -t myimage . +docker run myimage +``` + +### Helm & Kubernetes + +```bash +kubectl get pods +helm install myrelease mychart/ +``` + +### AI Tooling + +```bash +claude # start Claude Code +codex-init # one-off: configure Codex endpoint and deployment + +cswap list # list managed Claude Code accounts +cswap status # show the account currently in use +cswap add # register the account you are signed in as +cswap switch # rotate to the next account +``` + +## 🤝 Contributing + +1. Create a feature branch +2. Make your changes +3. Test in the devcontainer +4. Submit a pull request + +## 📄 License + +MIT - see [`LICENSE`](LICENSE). + +## 🐛 Troubleshooting + +### Container won't build + +- Ensure Docker Desktop is running +- Check Docker has sufficient resources (CPU/Memory) +- Try rebuilding without cache: `Dev Containers: Rebuild Container` + +### Permission issues in /workspace + +- The `postCreateCommand` should handle this automatically +- Manually run: `sudo chown -R vscode:vscode /workspace` + +### Tool not found + +- Verify the installation script exists in `.devcontainer/files/install/` +- Check the `Dockerfile` has a `RUN /tmp/install/install-.sh` step +- Confirm it appears in `tests/validate-tools.sh`, then run that script +- Rebuild the container + +### A home-directory tool is missing or stale after a rebuild + +`/home/vscode` is a persistent per-user volume, seeded from the image only on +first start. Anything installed into the home tree (Claude Code, for example) +will not refresh for an existing volume. Remove the `dev-home-` volume to +re-seed, or update the tool in place. + +## 📞 Support + +Open an [issue](https://github.com/grinidx/devcontainer-devops/issues). For +anything security-related, follow [`SECURITY.md`](SECURITY.md) instead of +opening a public issue. diff --git a/SECURITY.md b/SECURITY.md index 73573b2..709d5aa 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,220 +1,243 @@ -# Security Policy - -## 🔒 Supported Versions - -| Version | Supported | -| ------- | ------------------ | -| 1.x.x | :white_check_mark: | -| < 1.0 | :x: | - -## 🚨 Reporting a Vulnerability - -We take security seriously. If you discover a security vulnerability, please follow these steps: - -### 1. **Do Not** Open a Public Issue - -Security vulnerabilities should not be disclosed publicly until a fix is available. - -### 2. Report Privately - -Send details to: **[your-security-email@example.com]** - -Include: -- Description of the vulnerability -- Steps to reproduce -- Potential impact -- Suggested fix (if available) - -### 3. Response Timeline - -- **Initial Response**: Within 48 hours -- **Status Update**: Within 7 days -- **Fix Timeline**: Depends on severity - - Critical: 1-3 days - - High: 1-2 weeks - - Medium: 2-4 weeks - - Low: Next regular release - -## 🛡️ Security Best Practices - -### Using This Container - -1. **Keep Tools Updated** - - Regularly rebuild with latest versions - - Monitor security advisories for installed tools - - Update ARG versions in Dockerfile - -2. **Credentials Management** - - Never commit credentials to the repository - - Use git-crypt for encrypted secrets - - Use environment variables or Azure Key Vault - - Configure `.gitignore` properly - -3. **Container Registry** - - Use Azure Container Registry with private access - - Enable vulnerability scanning in ACR - - Implement image signing - - Use managed identities for authentication - -4. **Volume Mounts** - - Be careful with bind mounts - - Don't mount sensitive host directories - - Use named volumes for persistence - -5. **Network Security** - - Limit exposed ports - - Use network policies in Kubernetes - - Implement least-privilege access - -### Development Practices - -1. **Dependencies** - ```bash - # Verify checksums - sha256sum -c - - # Pin versions - pip install package==version - ``` - -2. **Secrets in Code** - - Never hardcode credentials - - Use environment variables - - Scan code for secrets before commit - - Use pre-commit hooks - -3. **Terraform/Terragrunt** - - Use remote state with encryption - - Enable state locking - - Don't commit .tfstate files - - Use Azure Key Vault for secrets - -4. **Docker** - - Don't run containers as root - - Scan images for vulnerabilities - - Use minimal base images - - Remove unnecessary packages - -## 🔍 Security Tools Included - -### Static Analysis - -- **tflint**: Terraform linter and security scanner - ```bash - tflint --init - tflint - ``` - -- **checkov**: IaC security scanning - ```bash - checkov -d . - checkov -f main.tf - ``` - -### Secret Management - -- **git-crypt**: Transparent file encryption - ```bash - git-crypt init - git-crypt add-gpg-user - ``` - -### Recommended Additional Tools - -Consider adding: -- **trivy**: Container vulnerability scanner -- **SOPS**: Secrets encryption -- **Vault**: HashiCorp Vault for secret management -- **Aqua Security**: Container security platform - -## 🚦 Security Scanning - -### Before Committing - -```bash -# Scan Terraform -tflint -checkov -d terraform/ - -# Check for secrets -git diff | grep -i "password\|secret\|key" - -# Validate Ansible -ansible-playbook --syntax-check playbook.yml -``` - -### CI/CD Pipeline - -Add to `azure-pipelines.yml`: - -```yaml -- task: Docker@2 - displayName: 'Scan for Vulnerabilities' - inputs: - command: 'scan' - arguments: '$(imageRepository):$(tag)' -``` - -## 📋 Known Security Considerations - -### Tool Permissions - -- All tools run as `vscode` user (non-root) -- Docker requires group membership for socket access -- Kubernetes config requires proper RBAC - -### Network Access - -- Container needs internet for tool downloads during build -- Runtime may need cloud provider access -- Configure firewalls appropriately - -### Data Persistence - -- Named volume persists between container restarts -- Bind mounts expose host filesystem -- Be cautious with sensitive data - -## 🔐 Compliance - -### Industry Standards - -This container aims to support: -- CIS Docker Benchmarks -- NIST Cybersecurity Framework -- SOC 2 compliance requirements -- GDPR data protection - -### Audit Trail - -- Git history tracks all changes -- Azure DevOps provides build logs -- Enable logging for compliance - -## 📚 Security Resources - -- [Docker Security Best Practices](https://docs.docker.com/engine/security/) -- [Kubernetes Security](https://kubernetes.io/docs/concepts/security/) -- [Azure Security](https://docs.microsoft.com/azure/security/) -- [Terraform Security](https://www.terraform.io/docs/cloud/security/) -- [OWASP Top 10](https://owasp.org/www-project-top-ten/) - -## ⚠️ Disclaimer - -This devcontainer is provided as-is for development purposes. Organizations should: - -1. Conduct their own security assessments -2. Implement additional controls as needed -3. Follow their security policies -4. Regularly update and patch -5. Monitor for vulnerabilities - -## 📞 Contact - -For security concerns: **[your-security-email@example.com]** - -For general questions: Use GitHub Issues - ---- - -**Last Updated**: 2025-11-21 +# Security Policy + +## 🔒 Supported Versions + +| Version | Supported | +| ------- | ------------------ | +| 1.x.x | :white_check_mark: | +| < 1.0 | :x: | + +## 🚨 Reporting a Vulnerability + +We take security seriously. If you discover a security vulnerability, please follow these steps: + +### 1. **Do Not** Open a Public Issue + +Security vulnerabilities should not be disclosed publicly until a fix is available. + +### 2. Report Privately + +Send details to: **[your-security-email@example.com]** + +Include: + +- Description of the vulnerability +- Steps to reproduce +- Potential impact +- Suggested fix (if available) + +### 3. Response Timeline + +- **Initial Response**: Within 48 hours +- **Status Update**: Within 7 days +- **Fix Timeline**: Depends on severity + - Critical: 1-3 days + - High: 1-2 weeks + - Medium: 2-4 weeks + - Low: Next regular release + +## 🛡️ Security Best Practices + +### Using This Container + +1. **Keep Tools Updated** + - Regularly rebuild with latest versions + - Monitor security advisories for installed tools + - Update ARG versions in Dockerfile + +2. **Credentials Management** + - Never commit credentials to the repository + - Use git-crypt for encrypted secrets + - Use environment variables or Azure Key Vault + - Configure `.gitignore` properly + +3. **Container Registry** + - Images are published to the GitHub Container Registry, public and + anonymously pullable + - Every build is scanned by Trivy, with findings reported to the Security tab + - Every published image carries an SBOM and Sigstore-signed SLSA build + provenance - verify before use (see below) + - CI authenticates with the repository's own `GITHUB_TOKEN`; there are no + registry credentials to store or rotate + +4. **Volume Mounts** + - Be careful with bind mounts + - Don't mount sensitive host directories + - Use named volumes for persistence + +5. **Network Security** + - Limit exposed ports + - Use network policies in Kubernetes + - Implement least-privilege access + +### Development Practices + +1. **Dependencies** + + ```bash + # Verify checksums + sha256sum -c + + # Pin versions + pip install package==version + ``` + +2. **Secrets in Code** + - Never hardcode credentials + - Use environment variables + - Scan code for secrets before commit + - Use pre-commit hooks + +3. **Terraform/Terragrunt** + - Use remote state with encryption + - Enable state locking + - Don't commit .tfstate files + - Use Azure Key Vault for secrets + +4. **Docker** + - Don't run containers as root + - Scan images for vulnerabilities + - Use minimal base images + - Remove unnecessary packages + +## 🔍 Security Tools Included + +### Static Analysis + +- **tflint**: Terraform linter and security scanner + + ```bash + tflint --init + tflint + ``` + +- **checkov**: IaC security scanning + + ```bash + checkov -d . + checkov -f main.tf + ``` + +### Secret Management + +- **git-crypt**: Transparent file encryption + + ```bash + git-crypt init + git-crypt add-gpg-user + ``` + +### Recommended Additional Tools + +Consider adding: + +- **trivy**: Container vulnerability scanner +- **SOPS**: Secrets encryption +- **Vault**: HashiCorp Vault for secret management +- **Aqua Security**: Container security platform + +## 🚦 Security Scanning + +### Before Committing + +```bash +# Scan Terraform +tflint +checkov -d terraform/ + +# Check for secrets +git diff | grep -i "password\|secret\|key" + +# Validate Ansible +ansible-playbook --syntax-check playbook.yml +``` + +### Verifying a published image + +Confirm an image was built by this repository's workflow, and not by someone +else: + +```bash +gh attestation verify oci://ghcr.io/grinidx/devcontainer-devops:latest \ + -R grinidx/devcontainer-devops +``` + +Inspect its SBOM: + +```bash +docker buildx imagetools inspect ghcr.io/grinidx/devcontainer-devops:latest \ + --format '{{ json .SBOM }}' +``` + +Scan it yourself: + +```bash +trivy image --scanners vuln --severity HIGH,CRITICAL \ + ghcr.io/grinidx/devcontainer-devops:latest +``` + +## 📋 Known Security Considerations + +### Tool Permissions + +- All tools run as `vscode` user (non-root) +- Docker requires group membership for socket access +- Kubernetes config requires proper RBAC + +### Network Access + +- Container needs internet for tool downloads during build +- Runtime may need cloud provider access +- Configure firewalls appropriately + +### Data Persistence + +- Named volume persists between container restarts +- Bind mounts expose host filesystem +- Be cautious with sensitive data + +## 🔐 Compliance + +### Industry Standards + +This container aims to support: + +- CIS Docker Benchmarks +- NIST Cybersecurity Framework +- SOC 2 compliance requirements +- GDPR data protection + +### Audit Trail + +- Git history tracks all changes +- GitHub Actions provides build logs, and build provenance links each published + image back to the commit and workflow that produced it +- Enable logging for compliance + +## 📚 Security Resources + +- [Docker Security Best Practices](https://docs.docker.com/engine/security/) +- [Kubernetes Security](https://kubernetes.io/docs/concepts/security/) +- [Azure Security](https://docs.microsoft.com/azure/security/) +- [Terraform Security](https://www.terraform.io/docs/cloud/security/) +- [OWASP Top 10](https://owasp.org/www-project-top-ten/) + +## ⚠️ Disclaimer + +This devcontainer is provided as-is for development purposes. Organizations should: + +1. Conduct their own security assessments +2. Implement additional controls as needed +3. Follow their security policies +4. Regularly update and patch +5. Monitor for vulnerabilities + +## 📞 Contact + +For security concerns: **[your-security-email@example.com]** + +For general questions: Use GitHub Issues + +--- + +**Last Updated**: 2025-11-21 diff --git a/VERSION_MANAGEMENT.md b/VERSION_MANAGEMENT.md index 62b4b6e..27d6e40 100644 --- a/VERSION_MANAGEMENT.md +++ b/VERSION_MANAGEMENT.md @@ -1,271 +1,307 @@ -# Version Management - -This devcontainer is designed to use the **latest versions** of all tools by default, with the ability to pin specific versions when needed. - -## Default Behavior: Latest Versions - -By default, when you build the container without specifying versions, it will automatically fetch and install the latest stable version of each tool: - -- ✅ Terraform (latest) -- ✅ Terragrunt (latest) -- ✅ kubectl (latest stable) -- ✅ Helm (latest) -- ✅ Azure CLI (latest) -- ✅ PowerShell (latest) -- ✅ Ansible (latest) -- ✅ .NET SDK (latest on the 10.0 LTS channel; set `DOTNET_VERSION` to pin an exact SDK) -- ✅ All other tools... - -## How It Works - -Each installation script checks if a version is provided: -- **No version**: Fetches latest from official source (GitHub API, PyPI, etc.) -- **Version provided**: Installs that specific version - -## Pinning Versions - -### Option 1: Via devcontainer.json (Recommended) - -Edit `.devcontainer/build/devcontainer.json` or `.devcontainer/local/devcontainer.json`: - -```json -{ - "build": { - "args": { - "UBUNTU_VERSION": "24.04", - "TERRAFORM_VERSION": "1.13.5", // Pin Terraform - "KUBECTL_VERSION": "1.30.0", // Pin kubectl - "HELM_VERSION": "3.14.0" // Pin Helm - // Leave others empty for latest - } - } -} -``` - -### Option 2: Via Dockerfile - -Edit `.devcontainer/Dockerfile` ARG section: - -```dockerfile -ARG TERRAFORM_VERSION=1.13.5 # Pin this -ARG KUBECTL_VERSION= # Use latest -ARG HELM_VERSION=3.14.0 # Pin this -``` - -### Option 3: Via Pipeline - -In `azure-pipelines.yml`: - -```yaml -arguments: | - --build-arg TERRAFORM_VERSION=1.13.5 - --build-arg KUBECTL_VERSION=1.30.0 -``` - -## Checking Current Versions - -### Inside the Container - -Run validation to see installed versions: -```bash -validate -``` - -### Before Building - -Check what latest versions are available: -```bash -bash scripts/check-latest-versions.sh -``` - -This will: -- Fetch all latest versions -- Show current versions in your config -- Provide ready-to-use configuration - -## Version Strategy Recommendations - -### Development Environment -✅ **Use latest versions** for maximum features and security patches -```json -"args": { - "UBUNTU_VERSION": "24.04" - // No version pinning - always latest -} -``` - -### CI/CD Pipelines -⚠️ **Pin versions** for reproducibility -```json -"args": { - "UBUNTU_VERSION": "24.04", - "TERRAFORM_VERSION": "1.13.5", - "KUBECTL_VERSION": "1.30.0", - "HELM_VERSION": "3.14.0" -} -``` - -### Production Support -🔒 **Pin all versions** for stability -```json -"args": { - "UBUNTU_VERSION": "24.04", - "TERRAFORM_VERSION": "1.13.5", - "TERRAGRUNT_VERSION": "0.70.4", - "KUBECTL_VERSION": "1.30.0", - "HELM_VERSION": "3.14.0", - "AZ_CLI_VERSION": "2.79.0", - "ANSIBLE_VERSION": "2.18.1", - "POWERSHELL_VERSION": "7.5.4", - "KUBELOGIN_VERSION": "0.1.9", - "YQ_VERSION": "4.44.6", - "JQ_VERSION": "1.7.1", - "TFLINT_VERSION": "0.54.0", - "CHECKOV_VERSION": "3.2.337" -} -``` - -## Update Workflow - -### Regular Updates (Monthly Recommended) - -1. **Check for updates:** - ```bash - bash scripts/check-latest-versions.sh - ``` - -2. **Update configuration** with new versions if desired - -3. **Rebuild container:** - ``` - Dev Containers: Rebuild Container - ``` - -4. **Test thoroughly:** - ```bash - validate - testall - ``` - -5. **Commit changes** to version control - -### Emergency Security Update - -If a critical security patch is released: - -1. **Pin to secure version** in devcontainer.json: - ```json - "TERRAFORM_VERSION": "1.13.6" // Security patch - ``` - -2. **Rebuild immediately:** - ``` - Dev Containers: Rebuild Container Without Cache - ``` - -3. **Verify:** - ```bash - terraform version - ``` - -## Version Sources - -| Tool | Source | API/URL | -|------|--------|---------| -| Terraform | HashiCorp Checkpoint | `https://checkpoint-api.hashicorp.com/v1/check/terraform` | -| Terragrunt | GitHub Releases | `https://api.github.com/repos/gruntwork-io/terragrunt/releases/latest` | -| kubectl | Kubernetes | `https://dl.k8s.io/release/stable.txt` | -| Helm | GitHub Releases | `https://api.github.com/repos/helm/helm/releases/latest` | -| Azure CLI | GitHub Releases | `https://api.github.com/repos/Azure/azure-cli/releases/latest` | -| PowerShell | GitHub Releases | `https://api.github.com/repos/PowerShell/PowerShell/releases/latest` | -| Ansible | PyPI | `https://pypi.org/pypi/ansible/json` | -| Checkov | PyPI | `https://pypi.org/pypi/checkov/json` | -| claude-swap (cswap) | PyPI | `https://pypi.org/pypi/claude-swap/json` | -| yq | GitHub Releases | `https://api.github.com/repos/mikefarah/yq/releases/latest` | -| jq | GitHub Releases | `https://api.github.com/repos/jqlang/jq/releases/latest` | -| tflint | GitHub Releases | `https://api.github.com/repos/terraform-linters/tflint/releases/latest` | -| kubelogin | GitHub Releases | `https://api.github.com/repos/Azure/kubelogin/releases/latest` | -| uv | GitHub Releases | `https://api.github.com/repos/astral-sh/uv/releases/latest` (tags have **no** `v` prefix) | -| .NET SDK | Microsoft dotnet-install.sh | `https://dot.net/v1/dotnet-install.sh` (channel 10.0) | - -## Compatibility Matrix - -Some tools have dependencies on others. Check compatibility: - -| Terraform Version | Compatible kubectl | Compatible Helm | -|-------------------|-------------------|-----------------| -| 1.9.x | 1.30.x - 1.31.x | 3.14.x - 3.16.x | -| 1.10.x | 1.31.x - 1.32.x | 3.15.x - 3.16.x | - -Always test after updates! - -## Troubleshooting - -### Version fetch fails during build -```bash -# Fallback: The script will use hardcoded defaults -# Or manually specify version in devcontainer.json -``` - -### Incompatible versions -```bash -# Pin to known-good versions -"TERRAFORM_VERSION": "1.13.5", -"KUBECTL_VERSION": "1.30.0" -``` - -### Slow builds -```bash -# Version fetching adds ~30s to build -# Pin versions to skip API calls -``` - -## Best Practices - -✅ **DO:** -- Use latest versions in development -- Pin versions in CI/CD -- Test after each update -- Document version requirements -- Check release notes before updating - -❌ **DON'T:** -- Auto-update in production -- Skip testing after updates -- Mix latest and pinned randomly -- Ignore deprecation warnings - -## Examples - -### Pure Latest (Development) -```json -"args": { - "UBUNTU_VERSION": "24.04" -} -``` - -### Mixed (Flexible Development) -```json -"args": { - "UBUNTU_VERSION": "24.04", - "TERRAFORM_VERSION": "1.13.5", // Pin for project compatibility - // Others use latest -} -``` - -### Fully Pinned (Production) -```json -"args": { - "UBUNTU_VERSION": "24.04", - "TERRAFORM_VERSION": "1.13.5", - "KUBECTL_VERSION": "1.30.0", - "HELM_VERSION": "3.14.0", - "AZ_CLI_VERSION": "2.79.0", - "ANSIBLE_VERSION": "2.18.1", - "POWERSHELL_VERSION": "7.5.4" -} -``` - ---- - -**Remember**: Latest versions = latest features + security patches, but also potential breaking changes. Choose your strategy wisely! 🎯 +# Version Management + +This devcontainer is designed to use the **latest versions** of all tools by default, with the ability to pin specific versions when needed. + +## Default Behavior: Latest Versions + +By default, when you build the container without specifying versions, it will automatically fetch and install the latest stable version of each tool: + +- ✅ Terraform (latest) +- ✅ Terragrunt (latest) +- ✅ kubectl (latest stable) +- ✅ Helm (latest) +- ✅ Azure CLI (latest) +- ✅ PowerShell (latest) +- ✅ Ansible (latest) +- ✅ .NET SDK (latest on the 10.0 LTS channel; set `DOTNET_VERSION` to pin an exact SDK) +- ✅ All other tools... + +## How It Works + +Each installation script checks if a version is provided: + +- **No version**: Fetches latest from official source (GitHub API, PyPI, etc.) +- **Version provided**: Installs that specific version + +## Pinning Versions + +### Option 1: Via devcontainer.json (Recommended) + +> **Pinning only applies when you build locally.** `.devcontainer/devcontainer.json` +> pulls the pre-built image by default, and build args do nothing to an image +> that is already built. Comment out the `"image"` line and uncomment the +> `"build"` block first. + +Edit `.devcontainer/devcontainer.json`: + +```json +{ + "build": { + "args": { + "UBUNTU_VERSION": "24.04", + "TERRAFORM_VERSION": "1.13.5", // Pin Terraform + "KUBECTL_VERSION": "1.30.0", // Pin kubectl + "HELM_VERSION": "3.14.0" // Pin Helm + // Leave others empty for latest + } + } +} +``` + +### Option 2: Via Dockerfile + +Edit `.devcontainer/Dockerfile` ARG section: + +```dockerfile +ARG TERRAFORM_VERSION=1.13.5 # Pin this +ARG KUBECTL_VERSION= # Use latest +ARG HELM_VERSION=3.14.0 # Pin this +``` + +### Option 3: Via the build command + +When building the image yourself: + +```bash +docker build .devcontainer \ + --file .devcontainer/Dockerfile \ + --target final \ + --build-arg TERRAFORM_VERSION=1.13.5 \ + --build-arg KUBECTL_VERSION=1.30.0 \ + --tag devcontainer-devops:pinned +``` + +CI deliberately passes no version build args - published images install the +latest of everything left unpinned, which is why a release tag is a +point-in-time snapshot rather than a reproducible build. + +## Checking Current Versions + +### Inside the Container + +Run validation to see installed versions: + +```bash +validate +``` + +### Before Building + +Check what latest versions are available: + +```bash +bash scripts/check-latest-versions.sh +``` + +This will: + +- Fetch all latest versions +- Show current versions in your config +- Provide ready-to-use configuration + +## Version Strategy Recommendations + +### Development Environment + +✅ **Use latest versions** for maximum features and security patches + +```json +"args": { + "UBUNTU_VERSION": "24.04" + // No version pinning - always latest +} +``` + +### CI/CD Pipelines + +⚠️ **Pin versions** for reproducibility + +```json +"args": { + "UBUNTU_VERSION": "24.04", + "TERRAFORM_VERSION": "1.13.5", + "KUBECTL_VERSION": "1.30.0", + "HELM_VERSION": "3.14.0" +} +``` + +### Production Support + +🔒 **Pin all versions** for stability + +```json +"args": { + "UBUNTU_VERSION": "24.04", + "TERRAFORM_VERSION": "1.13.5", + "TERRAGRUNT_VERSION": "0.70.4", + "KUBECTL_VERSION": "1.30.0", + "HELM_VERSION": "3.14.0", + "AZ_CLI_VERSION": "2.79.0", + "ANSIBLE_VERSION": "2.18.1", + "POWERSHELL_VERSION": "7.5.4", + "KUBELOGIN_VERSION": "0.1.9", + "YQ_VERSION": "4.44.6", + "JQ_VERSION": "1.7.1", + "TFLINT_VERSION": "0.54.0", + "CHECKOV_VERSION": "3.2.337" +} +``` + +## Update Workflow + +### Regular Updates (Monthly Recommended) + +1. **Check for updates:** + + ```bash + bash scripts/check-latest-versions.sh + ``` + +2. **Update configuration** with new versions if desired + +3. **Rebuild container:** + + ```text + Dev Containers: Rebuild Container + ``` + +4. **Test thoroughly:** + + ```bash + validate + testall + ``` + +5. **Commit changes** to version control + +### Emergency Security Update + +If a critical security patch is released: + +1. **Pin to secure version** in devcontainer.json: + + ```json + "TERRAFORM_VERSION": "1.13.6" // Security patch + ``` + +2. **Rebuild immediately:** + + ```text + Dev Containers: Rebuild Container Without Cache + ``` + +3. **Verify:** + + ```bash + terraform version + ``` + +## Version Sources + +| Tool | Source | API/URL | +|------|--------|---------| +| Terraform | HashiCorp Checkpoint | `https://checkpoint-api.hashicorp.com/v1/check/terraform` | +| Terragrunt | GitHub Releases | `https://api.github.com/repos/gruntwork-io/terragrunt/releases/latest` | +| kubectl | Kubernetes | `https://dl.k8s.io/release/stable.txt` | +| Helm | GitHub Releases | `https://api.github.com/repos/helm/helm/releases/latest` | +| Azure CLI | GitHub Releases | `https://api.github.com/repos/Azure/azure-cli/releases/latest` | +| PowerShell | GitHub Releases | `https://api.github.com/repos/PowerShell/PowerShell/releases/latest` | +| Ansible | PyPI | `https://pypi.org/pypi/ansible/json` | +| Checkov | PyPI | `https://pypi.org/pypi/checkov/json` | +| claude-swap (cswap) | PyPI | `https://pypi.org/pypi/claude-swap/json` | +| yq | GitHub Releases | `https://api.github.com/repos/mikefarah/yq/releases/latest` | +| jq | GitHub Releases | `https://api.github.com/repos/jqlang/jq/releases/latest` | +| tflint | GitHub Releases | `https://api.github.com/repos/terraform-linters/tflint/releases/latest` | +| kubelogin | GitHub Releases | `https://api.github.com/repos/Azure/kubelogin/releases/latest` | +| uv | GitHub Releases | `https://api.github.com/repos/astral-sh/uv/releases/latest` (tags have **no** `v` prefix) | +| .NET SDK | Microsoft dotnet-install.sh | `https://dot.net/v1/dotnet-install.sh` (channel 10.0) | + +## Compatibility Matrix + +Some tools have dependencies on others. Check compatibility: + +| Terraform Version | Compatible kubectl | Compatible Helm | +|-------------------|-------------------|-----------------| +| 1.9.x | 1.30.x - 1.31.x | 3.14.x - 3.16.x | +| 1.10.x | 1.31.x - 1.32.x | 3.15.x - 3.16.x | + +Always test after updates! + +## Troubleshooting + +### Version fetch fails during build + +```bash +# Fallback: The script will use hardcoded defaults +# Or manually specify version in devcontainer.json +``` + +### Incompatible versions + +```bash +# Pin to known-good versions +"TERRAFORM_VERSION": "1.13.5", +"KUBECTL_VERSION": "1.30.0" +``` + +### Slow builds + +```bash +# Version fetching adds ~30s to build +# Pin versions to skip API calls +``` + +## Best Practices + +✅ **DO:** + +- Use latest versions in development +- Pin versions in CI/CD +- Test after each update +- Document version requirements +- Check release notes before updating + +❌ **DON'T:** + +- Auto-update in production +- Skip testing after updates +- Mix latest and pinned randomly +- Ignore deprecation warnings + +## Examples + +### Pure Latest (Development) + +```json +"args": { + "UBUNTU_VERSION": "24.04" +} +``` + +### Mixed (Flexible Development) + +```json +"args": { + "UBUNTU_VERSION": "24.04", + "TERRAFORM_VERSION": "1.13.5", // Pin for project compatibility + // Others use latest +} +``` + +### Fully Pinned (Production) + +```json +"args": { + "UBUNTU_VERSION": "24.04", + "TERRAFORM_VERSION": "1.13.5", + "KUBECTL_VERSION": "1.30.0", + "HELM_VERSION": "3.14.0", + "AZ_CLI_VERSION": "2.79.0", + "ANSIBLE_VERSION": "2.18.1", + "POWERSHELL_VERSION": "7.5.4" +} +``` + +--- + +**Remember**: Latest versions = latest features + security patches, but also potential breaking changes. Choose your strategy wisely! 🎯 diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index da15d97..0000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,284 +0,0 @@ -trigger: - branches: - include: - - main - -schedules: - - cron: 0 0 * * Sun - displayName: Weekly Rebuild - branches: - include: ["main"] - always: true - batch: true - -# Replace with your own agent pool, or use 'vmImage: ubuntu-latest' for -# Microsoft-hosted agents (note the 120-minute job timeouts below). -pool: - name: '' - -variables: - # Variable group supplying Dependency_track_URL and Dependency_track_API_KEY. - # Remove this line and the 'Upload SBOM to Dependency-Track' task if you do - # not run a Dependency-Track instance. - - group: Dependency_track - - name: dockerfilePath - value: '.devcontainer/Dockerfile' - - name: imageRepository - value: 'devcontainer' - # Your registry login server, e.g. 'myregistry.azurecr.io' - - name: containerRegistry - value: '.azurecr.io' - # Name of the Docker Registry service connection in Azure DevOps - - name: containerRegistryConnection - value: '' - - name: tag - value: '$(Build.BuildId)' - - name: majorVersion - value: '1' - - name: minorVersion - value: '0' - - name: patchVersion - value: '$(Build.BuildId)' - - name: semanticVersion - value: '$(majorVersion).$(minorVersion).$(patchVersion)' - # Use --no-cache on scheduled builds to pick up upstream updates - - ${{ if eq(variables['Build.Reason'], 'Schedule') }}: - - name: buildArgs - value: '--no-cache --build-arg BUILDKIT_INLINE_CACHE=1' - - ${{ else }}: - - name: buildArgs - value: '--build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $(containerRegistry)/$(imageRepository):latest' - -stages: -- stage: Validate - displayName: 'Validate and Lint' - jobs: - - job: Lint - displayName: 'Lint Scripts and Configs' - steps: - - checkout: self - - - task: Bash@3 - displayName: 'Syntax check shell scripts' - inputs: - targetType: 'inline' - script: | - echo "Checking shell scripts..." - errors=0 - for script in .devcontainer/files/install/*.sh scripts/*.sh tests/*.sh; do - if [ -f "$script" ]; then - if ! bash -n "$script" 2>&1; then - echo "FAIL: $script" - errors=$((errors + 1)) - else - echo "OK: $script" - fi - fi - done - if [ "$errors" -gt 0 ]; then - echo "$errors script(s) failed syntax check" - exit 1 - fi - echo "All shell scripts are valid" - - - task: Bash@3 - displayName: 'Validate JSON files' - inputs: - targetType: 'inline' - script: | - echo "Checking JSON files..." - errors=0 - # Skip devcontainer.json as it uses JSONC (JSON with comments) which is valid per spec - for json in $(find . -name "*.json" -not -path "*/.git/*" -not -name "devcontainer.json"); do - if ! python3 -m json.tool "$json" > /dev/null 2>&1; then - echo "FAIL: $json" - errors=$((errors + 1)) - else - echo "OK: $json" - fi - done - if [ "$errors" -gt 0 ]; then - echo "$errors JSON file(s) are invalid" - exit 1 - fi - echo "All JSON files are valid" - - - task: Bash@3 - displayName: 'Lint Dockerfile with Hadolint' - inputs: - targetType: 'inline' - script: | - echo "Linting Dockerfile..." - docker run --rm -i hadolint/hadolint hadolint \ - --ignore DL3008 \ - --ignore DL3009 \ - --ignore DL3015 \ - --ignore DL3059 \ - - < $(dockerfilePath) - -- stage: Build - displayName: 'Build and Test' - dependsOn: Validate - jobs: - - job: BuildImage - displayName: 'Build Docker Image' - timeoutInMinutes: 120 - steps: - - checkout: self - persistCredentials: true - - - task: Bash@3 - displayName: 'Clean docker cache' - inputs: - targetType: 'inline' - script: | - docker system prune -a -f - - - task: Docker@2 - displayName: 'Build image' - inputs: - containerRegistry: '$(containerRegistryConnection)' - repository: '$(imageRepository)' - command: 'build' - Dockerfile: '$(dockerfilePath)' - arguments: '$(buildArgs)' - tags: | - latest - $(tag) - $(semanticVersion) - - - task: Bash@3 - displayName: 'Test image - validate tools' - inputs: - targetType: 'inline' - script: | - echo "Running validation tests against built image..." - docker run --rm --entrypoint "" $(containerRegistry)/$(imageRepository):$(tag) bash -c " - set -e - echo '=== Terraform ===' && terraform version - echo '=== Terragrunt ===' && terragrunt --version - echo '=== kubectl ===' && kubectl version --client - echo '=== Helm ===' && helm version - echo '=== Azure CLI ===' && az version - echo '=== Ansible ===' && ansible --version - echo '=== PowerShell ===' && pwsh --version - echo '=== Python ===' && python3 --version - echo '=== .NET SDK ===' && dotnet --version && dotnet --list-sdks - echo '=== tflint ===' && tflint --version - echo '=== checkov ===' && checkov --version - echo '=== yq ===' && yq --version - echo '=== jq ===' && jq --version - echo '=== git-crypt ===' && git-crypt --version - echo '=== pre-commit ===' && pre-commit --version - echo 'All tools validated successfully' - " - -- stage: SecurityScan - displayName: 'Security Scanning' - dependsOn: Build - condition: succeeded() - jobs: - - job: ScanImage - displayName: 'Vulnerability Scan' - steps: - - checkout: self - - - task: Bash@3 - displayName: 'Scan Dockerfile with Checkov' - inputs: - targetType: 'inline' - script: | - pip3 install --quiet checkov - checkov -d .devcontainer --framework dockerfile --soft-fail - - - task: Bash@3 - displayName: 'Run Trivy scan' - inputs: - targetType: 'inline' - script: | - # Install Trivy if not present - if ! command -v trivy &> /dev/null; then - curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin - fi - # Scan image for HIGH and CRITICAL vulnerabilities - # --scanners vuln: skip secret scanning (times out on large PowerShell module files) - # --timeout 15m: allow extra time for large devcontainer image - trivy image --scanners vuln --timeout 15m --severity HIGH,CRITICAL --exit-code 0 $(containerRegistry)/$(imageRepository):$(tag) - - - task: Bash@3 - displayName: 'Generate SBOM' - inputs: - targetType: 'inline' - script: | - trivy image --format cyclonedx --output $(System.DefaultWorkingDirectory)/sbom.json $(containerRegistry)/$(imageRepository):$(tag) --timeout 15m - - - task: PublishBuildArtifacts@1 - displayName: 'Publish SBOM' - inputs: - PathtoPublish: '$(System.DefaultWorkingDirectory)/sbom.json' - ArtifactName: 'sbom' - publishLocation: 'Container' - -- stage: Push - displayName: 'Push to Registry' - dependsOn: SecurityScan - condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main')) - jobs: - - job: PushImage - displayName: 'Push to ACR' - steps: - - checkout: self - - - task: Docker@2 - displayName: 'Push image to ACR' - timeoutInMinutes: 120 - inputs: - containerRegistry: '$(containerRegistryConnection)' - repository: '$(imageRepository)' - command: 'push' - tags: | - latest - $(tag) - $(semanticVersion) - - - task: DownloadBuildArtifacts@1 - displayName: 'Download SBOM' - inputs: - buildType: 'current' - downloadType: 'single' - artifactName: 'sbom' - downloadPath: '$(System.DefaultWorkingDirectory)' - - - task: upload-bom-dtrack-task@1 - displayName: 'Upload SBOM to Dependency-Track' - inputs: - bomFilePath: '$(System.DefaultWorkingDirectory)/sbom/sbom.json' - dtrackProjAutoCreate: true - dtrackAPIKey: $(Dependency_track_API_KEY) - dtrackURI: $(Dependency_track_URL) - dtrackProjName: $(Build.Repository.Name) - dtrackProjVersion: $(Build.BuildNumber) - - - task: Bash@3 - displayName: 'Build summary' - inputs: - targetType: 'inline' - script: | - echo "================================" - echo "DevContainer Build Complete" - echo "================================" - echo "Build ID: $(Build.BuildId)" - echo "Version: $(semanticVersion)" - echo "Branch: $(Build.SourceBranch)" - echo "Tags pushed:" - echo " - $(containerRegistry)/$(imageRepository):latest" - echo " - $(containerRegistry)/$(imageRepository):$(tag)" - echo " - $(containerRegistry)/$(imageRepository):$(semanticVersion)" - echo "================================" - - - task: Bash@3 - displayName: 'Clean docker cache' - inputs: - targetType: 'inline' - script: | - docker system prune -a -f diff --git a/scripts/check-latest-versions.sh b/scripts/check-latest-versions.sh old mode 100644 new mode 100755 diff --git a/tests/integration-test.sh b/tests/integration-test.sh old mode 100644 new mode 100755 index cc7f08d..10ca52d --- a/tests/integration-test.sh +++ b/tests/integration-test.sh @@ -11,7 +11,6 @@ echo "" # Color codes GREEN='\033[0;32m' RED='\033[0;31m' -YELLOW='\033[1;33m' NC='\033[0m' TEST_DIR="/tmp/devcontainer-tests" @@ -24,7 +23,7 @@ cd $TEST_DIR run_test() { local test_name=$1 local test_cmd=$2 - + echo -n "Testing: $test_name... " if eval $test_cmd &> /dev/null; then echo -e "${GREEN}✓ PASS${NC}" diff --git a/tests/run-all-tests.sh b/tests/run-all-tests.sh old mode 100644 new mode 100755 diff --git a/tests/validate-tools.sh b/tests/validate-tools.sh old mode 100644 new mode 100755 index 557652c..b9865f2 --- a/tests/validate-tools.sh +++ b/tests/validate-tools.sh @@ -16,7 +16,7 @@ NC='\033[0m' # No Color validate_tool() { local tool=$1 local version_cmd=$2 - + echo -n "Checking $tool... " if command -v $tool &> /dev/null; then version=$($version_cmd 2>&1 | head -n 1)