From d88c7fb0bc0ae261165472a6279cbe317408b6f3 Mon Sep 17 00:00:00 2001 From: Daniel Grimes Date: Mon, 3 Aug 2026 19:13:48 +0000 Subject: [PATCH 1/7] feat(install): detect architecture so the image can build for arm64 Ten install scripts hardcoded amd64/x86_64/x64, which made an arm64 build impossible. Add a sourced _arch.sh helper exporting the three spellings upstreams actually use, and switch every affected script to it. All ten tools publish arm64 Linux artefacts. PowerShell is the exception: Microsoft's Ubuntu package repository has no arm64 'powershell' deb, so arm64 installs from the upstream tarball into /opt/microsoft/powershell/7, resolving the runtime dependencies the deb would have pulled in. Also verify Helm's checksum, which was downloaded and then checked by a commented-out line, and drop an unused variable that shellcheck flags. --- .devcontainer/files/install/_arch.sh | 37 ++++++++++ .devcontainer/files/install/install-azcopy.sh | 14 +++- .../files/install/install-gitleaks.sh | 4 +- .devcontainer/files/install/install-helm.sh | 12 +-- .../files/install/install-kubectl.sh | 6 +- .../files/install/install-kubelogin.sh | 10 ++- .../files/install/install-powershell.sh | 73 ++++++++++++++++--- .../files/install/install-terragrunt.sh | 4 +- .../files/install/install-tf-summarize.sh | 4 +- .devcontainer/files/install/install-tflint.sh | 4 +- .devcontainer/files/install/install-uv.sh | 4 +- .devcontainer/files/install/install-yq.sh | 4 +- tests/integration-test.sh | 1 - 13 files changed, 147 insertions(+), 30 deletions(-) create mode 100644 .devcontainer/files/install/_arch.sh diff --git a/.devcontainer/files/install/_arch.sh b/.devcontainer/files/install/_arch.sh new file mode 100644 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-azcopy.sh b/.devcontainer/files/install/install-azcopy.sh index 6fcd68d..5f4c0e9 100644 --- a/.devcontainer/files/install/install-azcopy.sh +++ b/.devcontainer/files/install/install-azcopy.sh @@ -1,18 +1,28 @@ #!/bin/bash set -euo pipefail +. "$(dirname "$0")/_arch.sh" + WORKDIR="/tmp/install-azcopy" mkdir -p "${WORKDIR}" cd "${WORKDIR}" +# aka.ms serves the current release per architecture; the amd64 alias has no +# suffix, arm64 does. +if [ "${ARCH_DEB}" = "amd64" ]; then + LATEST_URL="https://aka.ms/downloadazcopy-v10-linux" +else + LATEST_URL="https://aka.ms/downloadazcopy-v10-linux-${ARCH_DEB}" +fi + # Get latest version if not specified if [ -z "${1:-}" ]; then echo "Installing latest azcopy..." - DOWNLOAD_URL="https://aka.ms/downloadazcopy-v10-linux" + DOWNLOAD_URL="${LATEST_URL}" else 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://azcopyvnext.azureedge.net/releases/release-${VERSION}-20*/azcopy_linux_${ARCH_DEB}_${VERSION}.tar.gz" fi # Download and extract diff --git a/.devcontainer/files/install/install-gitleaks.sh b/.devcontainer/files/install/install-gitleaks.sh index a2e6f9b..aca961f 100644 --- 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 index a275101..4b7a84a 100644 --- 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 index 087643a..bf89ce5 100644 --- 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-terragrunt.sh b/.devcontainer/files/install/install-terragrunt.sh index 759325f..24dbdef 100644 --- 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 index f645d31..8fe7d01 100644 --- 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 index 0e333c6..9a0376b 100644 --- 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..3b21659 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 diff --git a/.devcontainer/files/install/install-yq.sh b/.devcontainer/files/install/install-yq.sh index 806d0b0..1fc6aec 100644 --- 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/tests/integration-test.sh b/tests/integration-test.sh index cc7f08d..9fde2d0 100644 --- 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" From 6ad51e5357461626e558afe04a7c47b1974c01d6 Mon Sep 17 00:00:00 2001 From: Daniel Grimes Date: Mon, 3 Aug 2026 19:14:09 +0000 Subject: [PATCH 2/7] ci: build and publish to GHCR from GitHub Actions Replaces azure-pipelines.yml, which needed an Azure DevOps agent pool, an ACR service connection and a Dependency-Track instance to run at all - none of which a public repository can offer a contributor. ci.yml runs on pull requests and pushes to main; release.yml cuts a weekly calendar-versioned release from a --no-cache rebuild. Both call the reusable build.yml, so the two paths cannot drift. Each architecture builds on its own native runner, loads the image, runs tests/run-all-tests.sh inside it, and only then pushes by digest; a merge job assembles the manifest list. Docker's own github-builder workflow was the shorter route but supports neither local loading nor per-platform tests. Published images carry a BuildKit SBOM and provenance plus a Sigstore-signed SLSA attestation from actions/attest, which is why there is no separate cosign step. Trivy reports to the Security tab without gating: this image always carries some upstream HIGHs, and blocking on them would stall the weekly rebuild and leave users on a strictly more vulnerable image. Auth is the repository's own GITHUB_TOKEN, so there are no secrets to set up. Actions are pinned to commit SHAs. Dockerfile lint rules move to .hadolint.yaml with a reason against each, so CI and the pre-commit hook agree by construction. --- .dockerignore | 6 +- .github/workflows/build.yml | 290 ++++++++++++++++++++++++++++++++++ .github/workflows/ci.yml | 33 ++++ .github/workflows/release.yml | 140 ++++++++++++++++ .hadolint.yaml | 16 ++ .pre-commit-config.yaml | 2 +- azure-pipelines.yml | 284 --------------------------------- 7 files changed, 482 insertions(+), 289 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml create mode 100644 .hadolint.yaml delete mode 100644 azure-pipelines.yml diff --git a/.dockerignore b/.dockerignore index 2dd9f2c..acab55a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,8 +5,6 @@ # CI/CD .github/ -.azuredevops/ -azure-pipelines.yml .gitlab-ci.yml # Documentation @@ -84,7 +82,7 @@ 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.) +# (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/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..7c04cf3 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,290 @@ +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 + IMAGE_NAME: ${{ 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.REGISTRY }}/${{ env.IMAGE_NAME }} + 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.REGISTRY }}/${{ env.IMAGE_NAME }}: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.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache-${{ matrix.arch }} + cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache-${{ matrix.arch }},mode=max + outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},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.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: ${{ inputs.tags }} + + - name: Create manifest list + id: manifest + working-directory: /tmp/digests + run: | + set -euo pipefail + # shellcheck disable=SC2046 + docker buildx imagetools create \ + $(jq -cr '.target."docker-metadata-action".annotations | map("--annotation " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *) + + digest="$(docker buildx imagetools inspect \ + "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}" \ + --format '{{ json .Manifest.Digest }}' | tr -d '"')" + echo "digest=${digest}" >> "$GITHUB_OUTPUT" + echo "Published ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${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.REGISTRY }}/${{ env.IMAGE_NAME }} + 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 <' - -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 From 483f367c43bdd2a662009505b5b1899d30ce5a65 Mon Sep 17 00:00:00 2001 From: Daniel Grimes Date: Mon, 3 Aug 2026 19:14:18 +0000 Subject: [PATCH 3/7] docs: pull the published image by default, and document how CI works devcontainer.json now points at ghcr.io/grinidx/devcontainer-devops:latest, with the local Dockerfile build as the commented-out contributor path. New users get the tools in a pull rather than a half-hour build; CONTRIBUTING covers switching back when changing the image itself. Document CalVer honestly: most of versions.json installs latest, so a tag is a point-in-time snapshot and the digest is the only stable identifier. ARCHITECTURE gains a CI/CD section covering why the matrix is hand-rolled, why the runners are native rather than QEMU, and why scans do not gate. Its code fence was also never closed, so everything after line 3 was rendering as one block. Fixes several stale or placeholder references found on the way through: - README's licence section said '[Add your license information here]' over a real MIT LICENSE file, and Support was a placeholder - 'git clone ' in README and QUICKSTART - VERSION_MANAGEMENT pointed at .devcontainer/build/ and .devcontainer/local/, neither of which exists - QUICKSTART linked to .devcontainer/variants/README.md, which does not exist - .pre-commit/README claimed pre-commit ran in the Azure DevOps pipeline; it did not, and now there is no such pipeline - two Unreleased CHANGELOG entries contradicted the changes above --- .devcontainer/devcontainer.json | 45 ++++++----- .pre-commit/README.md | 9 ++- ARCHITECTURE.md | 84 ++++++++++++++++++-- CHANGELOG.md | 37 +++++++-- CONTRIBUTING.md | 34 ++++++++- QUICKSTART.md | 16 ++-- README.md | 131 +++++++++++++++++++++++++------- SECURITY.md | 42 ++++++---- VERSION_MANAGEMENT.md | 26 +++++-- 9 files changed, 333 insertions(+), 91 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a1bf477..6398afb 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,24 +1,31 @@ { - // 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 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: 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", + // 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", diff --git a/.pre-commit/README.md b/.pre-commit/README.md index 6d2bd54..31d3d88 100644 --- a/.pre-commit/README.md +++ b/.pre-commit/README.md @@ -133,7 +133,14 @@ exclude: | ## CI/CD Integration -Pre-commit also runs in the Azure DevOps pipeline to ensure consistency. +CI does not run `pre-commit` itself - it runs the linters that matter for this +repository directly, in the `lint` job of +[`.github/workflows/build.yml`](../.github/workflows/build.yml): `bash -n` and +`shellcheck` over every shell script, a JSON parse over every JSON file, and +`hadolint` over the Dockerfile. + +Dockerfile lint rules live in [`.hadolint.yaml`](../.hadolint.yaml) and are +shared, so the `hadolint-docker` hook here and the CI job agree by construction. ## Common Issues diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 52a1c82..837ff93 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -80,15 +80,25 @@ External Connections: Build & Deployment Flow: ──────────────────────── -Developer Azure DevOps ACR DevContainer +Developer GitHub Actions GHCR DevContainer │ │ │ │ │─── Push Code ───▶│ │ │ │ │ │ │ - │ │─── Build ───────▶│ │ - │ │ Image │ │ + │ │─── Build ────────┤ │ + │ │ amd64 + arm64 │ │ + │ │ native runners│ │ │ │ │ │ - │ │◀─── Push ────────│ │ - │ │ Success │ │ + │ │─── Test ─────────┤ │ + │ │ in the image │ │ + │ │ │ │ + │ │─── Push ────────▶│ │ + │ │ by digest │ │ + │ │ │ │ + │ │─── Merge ───────▶│ │ + │ │ manifest list │ │ + │ │ │ │ + │ │─── Attest ──────▶│ │ + │ │ SBOM + SLSA │ │ │ │ │ │ │─────────────── Pull Image ─────────────────────────────▶│ │ │ │ │ @@ -122,3 +132,67 @@ Tool Interaction Flow: ┌─────────────┐ │ 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..721be5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,15 +2,41 @@ 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). +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 -- Azure DevOps CI/CD pipeline for container registry +- 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 + +### Changed +- `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 + +### Removed +- `azure-pipelines.yml` and its Azure Container Registry and Dependency-Track + integration, replaced by the GitHub Actions workflows above - Installation scripts with isolated /tmp directories for: - Terraform (latest or pinned version) - Terragrunt (v0.93.9) @@ -71,11 +97,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 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) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f003f7c..eeeda74 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -115,10 +115,36 @@ Thank you for your interest in contributing! This document provides guidelines a 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 +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 diff --git a/QUICKSTART.md b/QUICKSTART.md index 20c11c3..97ed63d 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -13,8 +13,8 @@ Get up and running with the DevOps DevContainer in 5 minutes! ### 1. Clone the Repository ```bash -git clone -cd devcontainer +git clone https://github.com/grinidx/devcontainer-devops.git +cd devcontainer-devops ``` ### 2. Open in VS Code @@ -32,9 +32,15 @@ Or manually: - Type: `Dev Containers: Reopen in Container` - Press Enter -### 4. Wait for Build +### 4. Wait for the Pull -First build takes 5-10 minutes. Subsequent builds are much faster. +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 @@ -86,7 +92,7 @@ testall - 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 +- Read [VERSION_MANAGEMENT.md](VERSION_MANAGEMENT.md) to pin tool versions ## Troubleshooting diff --git a/README.md b/README.md index 9c46184..4b47ffc 100644 --- a/README.md +++ b/README.md @@ -57,12 +57,18 @@ This devcontainer includes pre-configured tools for: ## 🏗️ Repository Structure ``` -devcontainer/ +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 @@ -105,7 +111,7 @@ devcontainer/ │ └── validate-tools.sh # Tool validation ├── scripts/ │ └── check-latest-versions.sh -├── azure-pipelines.yml # CI/CD pipeline for ACR +├── .hadolint.yaml # Dockerfile lint rules, shared by CI and pre-commit ├── ARCHITECTURE.md # System architecture documentation ├── CHANGELOG.md # Version history ├── CONTRIBUTING.md # Contribution guidelines @@ -127,8 +133,8 @@ devcontainer/ 1. **Clone the repository:** ```bash - git clone - cd devcontainer + git clone https://github.com/grinidx/devcontainer-devops.git + cd devcontainer-devops ``` 2. **Open in VS Code:** @@ -139,11 +145,31 @@ devcontainer/ 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) + - 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: @@ -179,32 +205,61 @@ 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 +## 🔄 CI/CD -An Azure DevOps pipeline is included to automatically build and push the container image to Azure Container Registry (ACR). +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. -### Setup +| 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` | -1. **Create Azure Container Registry:** - ```bash - az acr create --resource-group --name --sku Basic - ``` +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 -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 +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. -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 +### Versioning -See [`azure-pipelines.yml`](azure-pipelines.yml) for the full pipeline definition. +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 @@ -221,6 +276,20 @@ See [`azure-pipelines.yml`](azure-pipelines.yml) for the full pipeline definitio 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: @@ -254,8 +323,10 @@ builds from the local `Dockerfile` by default — to pin, add the versions to it } ``` -> The CI pipeline does **not** currently pass these build args, so scheduled -> image builds install the latest of everything left unpinned in the `Dockerfile`. +> 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 @@ -306,7 +377,7 @@ cswap switch # rotate to the next account ## 📄 License -[Add your license information here] +MIT - see [`LICENSE`](LICENSE). ## 🐛 Troubleshooting @@ -333,4 +404,6 @@ re-seed, or update the tool in place. ## 📞 Support -[Add contact information or support channels] +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..b90583e 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -51,10 +51,13 @@ Include: - 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 + - 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 @@ -143,16 +146,28 @@ git diff | grep -i "password\|secret\|key" ansible-playbook --syntax-check playbook.yml ``` -### CI/CD Pipeline +### Verifying a published image -Add to `azure-pipelines.yml`: +Confirm an image was built by this repository's workflow, and not by someone +else: -```yaml -- task: Docker@2 - displayName: 'Scan for Vulnerabilities' - inputs: - command: 'scan' - arguments: '$(imageRepository):$(tag)' +```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 @@ -188,7 +203,8 @@ This container aims to support: ### Audit Trail - Git history tracks all changes -- Azure DevOps provides build logs +- 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 diff --git a/VERSION_MANAGEMENT.md b/VERSION_MANAGEMENT.md index 62b4b6e..7c3e7c0 100644 --- a/VERSION_MANAGEMENT.md +++ b/VERSION_MANAGEMENT.md @@ -26,7 +26,12 @@ Each installation script checks if a version is provided: ### Option 1: Via devcontainer.json (Recommended) -Edit `.devcontainer/build/devcontainer.json` or `.devcontainer/local/devcontainer.json`: +> **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 { @@ -52,16 +57,23 @@ ARG KUBECTL_VERSION= # Use latest ARG HELM_VERSION=3.14.0 # Pin this ``` -### Option 3: Via Pipeline +### Option 3: Via the build command -In `azure-pipelines.yml`: +When building the image yourself: -```yaml -arguments: | - --build-arg TERRAFORM_VERSION=1.13.5 - --build-arg KUBECTL_VERSION=1.30.0 +```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 From 167b38d54be58b59255279b154508885742de8f2 Mon Sep 17 00:00:00 2001 From: Daniel Grimes Date: Mon, 3 Aug 2026 19:37:11 +0000 Subject: [PATCH 4/7] chore: normalise line endings to LF and declare them in .gitattributes The repo held a mix with nothing declaring which was intended: the root docs and configs were CRLF, every shell script under .devcontainer/files/ was LF. Editing a CRLF file with an LF-writing tool left it mixed, and the mixed-line-ending pre-commit hook then rewrote the whole file. Settle on LF, which is what VS Code was already configured for here (devcontainer.json sets files.eol to \n) and what three quarters of the tracked files already used. Shell scripts especially need it - a CRLF shebang makes the kernel look for an interpreter with a trailing carriage return and fails with a confusing 'bad interpreter'. Apart from .gitattributes itself this commit changes no content; a whitespace-blind diff over it is empty. --- .ansible-lint | 74 +-- .devcontainer/Dockerfile | 360 +++++++------- .devcontainer/devcontainer.json | 264 +++++------ .dockerignore | 176 +++---- .gitattributes | 25 + .gitignore | 254 +++++----- .pre-commit-config.yaml | 238 +++++----- .pre-commit/README.md | 380 +++++++-------- .tflint.hcl | 134 +++--- ARCHITECTURE.md | 268 +++++------ CHANGELOG.md | 282 +++++------ CONTRIBUTING.md | 494 +++++++++---------- LICENSE | 42 +- QUICKSTART.md | 252 +++++----- README.md | 818 ++++++++++++++++---------------- SECURITY.md | 472 +++++++++--------- VERSION_MANAGEMENT.md | 566 +++++++++++----------- 17 files changed, 2562 insertions(+), 2537 deletions(-) create mode 100644 .gitattributes diff --git a/.ansible-lint b/.ansible-lint index 834e566..f7e4f1e 100644 --- a/.ansible-lint +++ b/.ansible-lint @@ -1,37 +1,37 @@ ---- -# 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 +--- +# 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..e934c7e 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 - +######################################################### 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 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 6398afb..72c636a 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,133 +1,133 @@ -{ - // 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" - } - } - } +{ + // 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" + } + } + } } \ No newline at end of file diff --git a/.dockerignore b/.dockerignore index acab55a..7b8cdb0 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,88 +1,88 @@ -# 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/** +# 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/.gitignore b/.gitignore index dfda4e7..1f9eb48 100644 --- a/.gitignore +++ b/.gitignore @@ -1,127 +1,127 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -*.egg-info/ -.installed.cfg -*.egg - -# Virtual environments -venv/ -ENV/ -env/ -.venv - -# IDE -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -!.vscode/*.code-snippets -.idea/ -*.swp -*.swo -*~ - -# OS -.DS_Store -Thumbs.db -*.log - -# Terraform -.terraform/ -.terraform.lock.hcl -*.tfstate -*.tfstate.* -*.tfvars -*.auto.tfvars -crash.log -override.tf -override.tf.json -*_override.tf -*_override.tf.json -.terraformrc -terraform.rc - -# Terragrunt -.terragrunt-cache/ - -# Ansible -*.retry -.ansible/ - -# Azure -.azure/ - -# Docker -.docker/ - -# Environment variables -.env -.env.local -.env.*.local -*.env - -# Secrets -secrets/ -*.pem -*.key -*.crt -*.p12 -*.pfx - -# Helm -charts/*.tgz - -# Kubernetes -kubeconfig -*.kubeconfig - -# Logs -logs/ -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Temporary files -tmp/ -temp/ -*.tmp -*.bak -*.backup - -# Test coverage -coverage/ -*.cover -.coverage -htmlcov/ - -# Local development -.local/ -local/ -scratch/ - -# Claude Code -.claude/settings.local.json -.claude/scheduled_tasks.lock -.claude/*.lock +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Virtual environments +venv/ +ENV/ +env/ +.venv + +# IDE +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db +*.log + +# Terraform +.terraform/ +.terraform.lock.hcl +*.tfstate +*.tfstate.* +*.tfvars +*.auto.tfvars +crash.log +override.tf +override.tf.json +*_override.tf +*_override.tf.json +.terraformrc +terraform.rc + +# Terragrunt +.terragrunt-cache/ + +# Ansible +*.retry +.ansible/ + +# Azure +.azure/ + +# Docker +.docker/ + +# Environment variables +.env +.env.local +.env.*.local +*.env + +# Secrets +secrets/ +*.pem +*.key +*.crt +*.p12 +*.pfx + +# Helm +charts/*.tgz + +# Kubernetes +kubeconfig +*.kubeconfig + +# Logs +logs/ +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Temporary files +tmp/ +temp/ +*.tmp +*.bak +*.backup + +# Test coverage +coverage/ +*.cover +.coverage +htmlcov/ + +# Local development +.local/ +local/ +scratch/ + +# Claude Code +.claude/settings.local.json +.claude/scheduled_tasks.lock +.claude/*.lock diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bbaca51..ce9180c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,119 +1,119 @@ -# Pre-commit configuration -# See https://pre-commit.com for more information - -repos: - # General file checks - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 - hooks: - - id: trailing-whitespace - - id: end-of-file-fixer - - id: check-yaml - args: ['--unsafe'] - - id: check-json - - id: check-added-large-files - args: ['--maxkb=1000'] - - id: check-merge-conflict - - id: check-case-conflict - - id: check-executables-have-shebangs - - id: check-shebang-scripts-are-executable - - id: detect-private-key - - id: mixed-line-ending - - # Shell script linting - - repo: https://github.com/shellcheck-py/shellcheck-py - rev: v0.9.0.6 - hooks: - - id: shellcheck - args: ['-x'] - - # Terraform checks - - repo: https://github.com/antonbabenko/pre-commit-terraform - rev: v1.86.0 - hooks: - - id: terraform_fmt - - id: terraform_validate - - id: terraform_docs - args: - - --hook-config=--path-to-file=README.md - - --hook-config=--add-to-existing-file=true - - id: terraform_tflint - args: - - --args=--config=__GIT_WORKING_DIR__/.tflint.hcl - - id: terraform_checkov - args: - - --args=--quiet - - --args=--framework terraform - - # Ansible linting - - repo: https://github.com/ansible/ansible-lint - rev: v6.22.1 - hooks: - - id: ansible-lint - files: \.(yaml|yml)$ - - # Python checks - - repo: https://github.com/psf/black - rev: 23.12.1 - hooks: - - id: black - language_version: python3 - - - repo: https://github.com/PyCQA/flake8 - rev: 7.0.0 - hooks: - - id: flake8 - args: ['--max-line-length=88', '--extend-ignore=E203'] - - # Dockerfile linting - - repo: https://github.com/hadolint/hadolint - rev: v2.12.0 - hooks: - # Ignores come from .hadolint.yaml so this matches CI exactly - - id: hadolint-docker - - # Markdown linting - - repo: https://github.com/igorshubovych/markdownlint-cli - rev: v0.38.0 - hooks: - - id: markdownlint - args: ['--fix'] - - # YAML linting - - repo: https://github.com/adrienverge/yamllint - rev: v1.33.0 - hooks: - - id: yamllint - args: ['-d', '{extends: default, rules: {line-length: {max: 120}}}'] - - # Secret detection - - repo: https://github.com/Yelp/detect-secrets - rev: v1.4.0 - hooks: - - id: detect-secrets - args: ['--baseline', '.secrets.baseline'] - exclude: package.lock.json - - # Git commit message validation - - repo: https://github.com/compilerla/conventional-pre-commit - rev: v3.0.0 - hooks: - - id: conventional-pre-commit - stages: [commit-msg] - args: [] - -# Global settings -default_language_version: - python: python3 - -# Exclude patterns -exclude: | - (?x)^( - .git/| - .terraform/| - .terragrunt-cache/| - __pycache__/| - node_modules/| - .venv/| - venv/ - ) +# Pre-commit configuration +# See https://pre-commit.com for more information + +repos: + # General file checks + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + args: ['--unsafe'] + - id: check-json + - id: check-added-large-files + args: ['--maxkb=1000'] + - id: check-merge-conflict + - id: check-case-conflict + - id: check-executables-have-shebangs + - id: check-shebang-scripts-are-executable + - id: detect-private-key + - id: mixed-line-ending + + # Shell script linting + - repo: https://github.com/shellcheck-py/shellcheck-py + rev: v0.9.0.6 + hooks: + - id: shellcheck + args: ['-x'] + + # Terraform checks + - repo: https://github.com/antonbabenko/pre-commit-terraform + rev: v1.86.0 + hooks: + - id: terraform_fmt + - id: terraform_validate + - id: terraform_docs + args: + - --hook-config=--path-to-file=README.md + - --hook-config=--add-to-existing-file=true + - id: terraform_tflint + args: + - --args=--config=__GIT_WORKING_DIR__/.tflint.hcl + - id: terraform_checkov + args: + - --args=--quiet + - --args=--framework terraform + + # Ansible linting + - repo: https://github.com/ansible/ansible-lint + rev: v6.22.1 + hooks: + - id: ansible-lint + files: \.(yaml|yml)$ + + # Python checks + - repo: https://github.com/psf/black + rev: 23.12.1 + hooks: + - id: black + language_version: python3 + + - repo: https://github.com/PyCQA/flake8 + rev: 7.0.0 + hooks: + - id: flake8 + args: ['--max-line-length=88', '--extend-ignore=E203'] + + # Dockerfile linting + - repo: https://github.com/hadolint/hadolint + rev: v2.12.0 + hooks: + # Ignores come from .hadolint.yaml so this matches CI exactly + - id: hadolint-docker + + # Markdown linting + - repo: https://github.com/igorshubovych/markdownlint-cli + rev: v0.38.0 + hooks: + - id: markdownlint + args: ['--fix'] + + # YAML linting + - repo: https://github.com/adrienverge/yamllint + rev: v1.33.0 + hooks: + - id: yamllint + args: ['-d', '{extends: default, rules: {line-length: {max: 120}}}'] + + # Secret detection + - repo: https://github.com/Yelp/detect-secrets + rev: v1.4.0 + hooks: + - id: detect-secrets + args: ['--baseline', '.secrets.baseline'] + exclude: package.lock.json + + # Git commit message validation + - repo: https://github.com/compilerla/conventional-pre-commit + rev: v3.0.0 + hooks: + - id: conventional-pre-commit + stages: [commit-msg] + args: [] + +# Global settings +default_language_version: + python: python3 + +# Exclude patterns +exclude: | + (?x)^( + .git/| + .terraform/| + .terragrunt-cache/| + __pycache__/| + node_modules/| + .venv/| + venv/ + ) diff --git a/.pre-commit/README.md b/.pre-commit/README.md index 31d3d88..a3a938b 100644 --- a/.pre-commit/README.md +++ b/.pre-commit/README.md @@ -1,190 +1,190 @@ -# Pre-commit Hooks - -This repository uses [pre-commit](https://pre-commit.com/) to automatically run checks before commits. - -## Installation - -Pre-commit is already installed in the devcontainer. To enable the hooks: - -```bash -pre-commit install -pre-commit install --hook-type commit-msg -``` - -## What Gets Checked - -### General -- Trailing whitespace -- End of file fixes -- Large files detection -- Merge conflict markers -- Private key detection - -### Shell Scripts -- ShellCheck linting -- Shebang validation -- Execute permissions - -### Terraform -- Format checking (`terraform fmt`) -- Validation (`terraform validate`) -- Documentation generation -- Security scanning (checkov) -- Linting (tflint) - -### Ansible -- Ansible-lint for playbooks -- YAML syntax validation - -### Python -- Code formatting (black) -- Style checking (flake8) -- Import sorting - -### Docker -- Dockerfile linting (hadolint) - -### Documentation -- Markdown linting -- YAML linting - -### Security -- Secret detection -- Private key scanning - -### Git -- Conventional commit message format - -## Usage - -### Automatic (Recommended) - -After installation, hooks run automatically on `git commit`: - -```bash -git add . -git commit -m "feat: add new feature" -# Pre-commit hooks run automatically -``` - -### Manual Run - -Run checks on all files: -```bash -pre-commit run --all-files -``` - -Run specific hook: -```bash -pre-commit run terraform-fmt --all-files -pre-commit run shellcheck --all-files -``` - -### Update Hooks - -Update to latest versions: -```bash -pre-commit autoupdate -``` - -## Bypassing Hooks - -**Not recommended**, but if needed: -```bash -git commit --no-verify -m "emergency fix" -``` - -## Configuration - -Hooks are configured in `.pre-commit-config.yaml`. To modify: - -1. Edit `.pre-commit-config.yaml` -2. Update hook versions or add new hooks -3. Run `pre-commit install` again -4. Test with `pre-commit run --all-files` - -## Troubleshooting - -### Hook fails to run - -```bash -# Reinstall hooks -pre-commit uninstall -pre-commit install -``` - -### Clean hook cache - -```bash -pre-commit clean -pre-commit run --all-files -``` - -### Skip specific files - -Add to `.pre-commit-config.yaml`: -```yaml -exclude: | - (?x)^( - path/to/exclude/| - specific-file.txt - ) -``` - -## CI/CD Integration - -CI does not run `pre-commit` itself - it runs the linters that matter for this -repository directly, in the `lint` job of -[`.github/workflows/build.yml`](../.github/workflows/build.yml): `bash -n` and -`shellcheck` over every shell script, a JSON parse over every JSON file, and -`hadolint` over the Dockerfile. - -Dockerfile lint rules live in [`.hadolint.yaml`](../.hadolint.yaml) and are -shared, so the `hadolint-docker` hook here and the CI job agree by construction. - -## Common Issues - -### Terraform validation fails - -Ensure Terraform is initialized: -```bash -cd terraform/ -terraform init -``` - -### Ansible-lint fails - -Check `.ansible-lint` configuration or update playbook syntax. - -### Hadolint fails - -Fix Dockerfile issues or add ignore rules: -```yaml -args: ['--ignore', 'DL3008', '--ignore', 'DL3009'] -``` - -## Hook List - -Full list of enabled hooks: - -| Hook | Purpose | Auto-fix | -|------|---------|----------| -| trailing-whitespace | Remove trailing spaces | ✓ | -| end-of-file-fixer | Ensure newline at EOF | ✓ | -| check-yaml | Validate YAML syntax | ✗ | -| check-json | Validate JSON syntax | ✗ | -| shellcheck | Lint shell scripts | ✗ | -| terraform-fmt | Format Terraform | ✓ | -| terraform-validate | Validate Terraform | ✗ | -| checkov | Security scanning | ✗ | -| ansible-lint | Lint Ansible | ✗ | -| black | Format Python | ✓ | -| flake8 | Lint Python | ✗ | -| hadolint | Lint Dockerfile | ✗ | -| markdownlint | Lint Markdown | ✓ | -| detect-secrets | Find secrets | ✗ | -| conventional-pre-commit | Validate commit msg | ✗ | - -## Contributing - -When adding new file types or tools, update `.pre-commit-config.yaml` with appropriate hooks. +# Pre-commit Hooks + +This repository uses [pre-commit](https://pre-commit.com/) to automatically run checks before commits. + +## Installation + +Pre-commit is already installed in the devcontainer. To enable the hooks: + +```bash +pre-commit install +pre-commit install --hook-type commit-msg +``` + +## What Gets Checked + +### General +- Trailing whitespace +- End of file fixes +- Large files detection +- Merge conflict markers +- Private key detection + +### Shell Scripts +- ShellCheck linting +- Shebang validation +- Execute permissions + +### Terraform +- Format checking (`terraform fmt`) +- Validation (`terraform validate`) +- Documentation generation +- Security scanning (checkov) +- Linting (tflint) + +### Ansible +- Ansible-lint for playbooks +- YAML syntax validation + +### Python +- Code formatting (black) +- Style checking (flake8) +- Import sorting + +### Docker +- Dockerfile linting (hadolint) + +### Documentation +- Markdown linting +- YAML linting + +### Security +- Secret detection +- Private key scanning + +### Git +- Conventional commit message format + +## Usage + +### Automatic (Recommended) + +After installation, hooks run automatically on `git commit`: + +```bash +git add . +git commit -m "feat: add new feature" +# Pre-commit hooks run automatically +``` + +### Manual Run + +Run checks on all files: +```bash +pre-commit run --all-files +``` + +Run specific hook: +```bash +pre-commit run terraform-fmt --all-files +pre-commit run shellcheck --all-files +``` + +### Update Hooks + +Update to latest versions: +```bash +pre-commit autoupdate +``` + +## Bypassing Hooks + +**Not recommended**, but if needed: +```bash +git commit --no-verify -m "emergency fix" +``` + +## Configuration + +Hooks are configured in `.pre-commit-config.yaml`. To modify: + +1. Edit `.pre-commit-config.yaml` +2. Update hook versions or add new hooks +3. Run `pre-commit install` again +4. Test with `pre-commit run --all-files` + +## Troubleshooting + +### Hook fails to run + +```bash +# Reinstall hooks +pre-commit uninstall +pre-commit install +``` + +### Clean hook cache + +```bash +pre-commit clean +pre-commit run --all-files +``` + +### Skip specific files + +Add to `.pre-commit-config.yaml`: +```yaml +exclude: | + (?x)^( + path/to/exclude/| + specific-file.txt + ) +``` + +## CI/CD Integration + +CI does not run `pre-commit` itself - it runs the linters that matter for this +repository directly, in the `lint` job of +[`.github/workflows/build.yml`](../.github/workflows/build.yml): `bash -n` and +`shellcheck` over every shell script, a JSON parse over every JSON file, and +`hadolint` over the Dockerfile. + +Dockerfile lint rules live in [`.hadolint.yaml`](../.hadolint.yaml) and are +shared, so the `hadolint-docker` hook here and the CI job agree by construction. + +## Common Issues + +### Terraform validation fails + +Ensure Terraform is initialized: +```bash +cd terraform/ +terraform init +``` + +### Ansible-lint fails + +Check `.ansible-lint` configuration or update playbook syntax. + +### Hadolint fails + +Fix Dockerfile issues or add ignore rules: +```yaml +args: ['--ignore', 'DL3008', '--ignore', 'DL3009'] +``` + +## Hook List + +Full list of enabled hooks: + +| Hook | Purpose | Auto-fix | +|------|---------|----------| +| trailing-whitespace | Remove trailing spaces | ✓ | +| end-of-file-fixer | Ensure newline at EOF | ✓ | +| check-yaml | Validate YAML syntax | ✗ | +| check-json | Validate JSON syntax | ✗ | +| shellcheck | Lint shell scripts | ✗ | +| terraform-fmt | Format Terraform | ✓ | +| terraform-validate | Validate Terraform | ✗ | +| checkov | Security scanning | ✗ | +| ansible-lint | Lint Ansible | ✗ | +| black | Format Python | ✓ | +| flake8 | Lint Python | ✗ | +| hadolint | Lint Dockerfile | ✗ | +| markdownlint | Lint Markdown | ✓ | +| detect-secrets | Find secrets | ✗ | +| conventional-pre-commit | Validate commit msg | ✗ | + +## Contributing + +When adding new file types or tools, update `.pre-commit-config.yaml` with appropriate hooks. diff --git a/.tflint.hcl b/.tflint.hcl index 1edffd3..2cd4141 100644 --- a/.tflint.hcl +++ b/.tflint.hcl @@ -1,67 +1,67 @@ -# TFLint Configuration -# https://github.com/terraform-linters/tflint - -plugin "terraform" { - enabled = true - preset = "recommended" -} - -plugin "azurerm" { - enabled = true - version = "0.25.1" - source = "github.com/terraform-linters/tflint-ruleset-azurerm" -} - -# Rules -rule "terraform_deprecated_interpolation" { - enabled = true -} - -rule "terraform_deprecated_index" { - enabled = true -} - -rule "terraform_unused_declarations" { - enabled = true -} - -rule "terraform_comment_syntax" { - enabled = true -} - -rule "terraform_documented_outputs" { - enabled = true -} - -rule "terraform_documented_variables" { - enabled = true -} - -rule "terraform_typed_variables" { - enabled = true -} - -rule "terraform_module_pinned_source" { - enabled = true -} - -rule "terraform_naming_convention" { - enabled = true - format = "snake_case" -} - -rule "terraform_required_version" { - enabled = true -} - -rule "terraform_required_providers" { - enabled = true -} - -rule "terraform_standard_module_structure" { - enabled = true -} - -rule "terraform_workspace_remote" { - enabled = true -} +# TFLint Configuration +# https://github.com/terraform-linters/tflint + +plugin "terraform" { + enabled = true + preset = "recommended" +} + +plugin "azurerm" { + enabled = true + version = "0.25.1" + source = "github.com/terraform-linters/tflint-ruleset-azurerm" +} + +# Rules +rule "terraform_deprecated_interpolation" { + enabled = true +} + +rule "terraform_deprecated_index" { + enabled = true +} + +rule "terraform_unused_declarations" { + enabled = true +} + +rule "terraform_comment_syntax" { + enabled = true +} + +rule "terraform_documented_outputs" { + enabled = true +} + +rule "terraform_documented_variables" { + enabled = true +} + +rule "terraform_typed_variables" { + enabled = true +} + +rule "terraform_module_pinned_source" { + enabled = true +} + +rule "terraform_naming_convention" { + enabled = true + format = "snake_case" +} + +rule "terraform_required_version" { + enabled = true +} + +rule "terraform_required_providers" { + enabled = true +} + +rule "terraform_standard_module_structure" { + enabled = true +} + +rule "terraform_workspace_remote" { + enabled = true +} diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 837ff93..8b976ef 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,137 +1,137 @@ -# DevContainer Architecture - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ 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: -───────────────────── - -┌─────────────────────────────────────────────────────────────────┐ -│ Azure Cloud │ -│ │ -│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ -│ │ Azure ACR │ │ Azure VM │ │ Azure AKS │ │ -│ │ (Images) │ │ (Resources) │ │ (K8s) │ │ -│ └──────────────┘ └──────────────┘ └──────────────┘ │ -│ │ -│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ -│ │ Key Vault │ │ Storage │ │ DevOps │ │ -│ │ (Secrets) │ │ (State) │ │ (CI/CD) │ │ -│ └──────────────┘ └──────────────┘ └──────────────┘ │ -└─────────────────────────────────────────────────────────────────┘ - -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 - └─────────────┘ +# DevContainer Architecture + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ 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: +───────────────────── + +┌─────────────────────────────────────────────────────────────────┐ +│ Azure Cloud │ +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ Azure ACR │ │ Azure VM │ │ Azure AKS │ │ +│ │ (Images) │ │ (Resources) │ │ (K8s) │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ Key Vault │ │ Storage │ │ DevOps │ │ +│ │ (Secrets) │ │ (State) │ │ (CI/CD) │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ + +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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 721be5a..1bb6b21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,141 +1,141 @@ -# 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 -- 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 - -### Changed -- `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 - -### Removed -- `azure-pipelines.yml` and its Azure Container Registry and Dependency-Track - integration, replaced by the GitHub Actions workflows above -- 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 -### 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 +- 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 + +### Changed +- `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 + +### Removed +- `azure-pipelines.yml` and its Azure Container Registry and Dependency-Track + integration, replaced by the GitHub Actions workflows above +- 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 +### 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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index eeeda74..9241101 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,247 +1,247 @@ -# 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! 🚀 +# 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 97ed63d..fa9a94b 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -1,126 +1,126 @@ -# 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! 🚀 +# 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 4b47ffc..4c360f8 100644 --- a/README.md +++ b/README.md @@ -1,409 +1,409 @@ -# 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-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. +# 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-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 b90583e..59ea103 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,236 +1,236 @@ -# 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 +# 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 7c3e7c0..da06bbc 100644 --- a/VERSION_MANAGEMENT.md +++ b/VERSION_MANAGEMENT.md @@ -1,283 +1,283 @@ -# 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:** - ``` - 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:** + ``` + 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! 🎯 From 07fb5c84228f3aa0b29d774cbe61cd7d9f5e5090 Mon Sep 17 00:00:00 2001 From: Daniel Grimes Date: Mon, 3 Aug 2026 19:43:12 +0000 Subject: [PATCH 5/7] fix(install): repair the azcopy pinned-version path The pinned branch could never have worked. It pointed at azcopyvnext.azureedge.net, a retired CDN, through 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 'latest' branch is ever taken. Use the GitHub release asset instead, which is where the aka.ms aliases redirect to anyway. Verified that both architectures resolve 200 at v10.32.6. --- .ansible-lint | 37 ------------------- .devcontainer/files/codex/codex-init | 0 .devcontainer/files/install/_arch.sh | 0 .../files/install/install-ansible.sh | 0 .devcontainer/files/install/install-azcopy.sh | 27 ++++++++------ .../files/install/install-azure-cli.sh | 0 .../files/install/install-checkov.sh | 0 .devcontainer/files/install/install-docker.sh | 0 .../files/install/install-git-crypt.sh | 0 .../files/install/install-gitleaks.sh | 0 .../files/install/install-kubectl.sh | 0 .../files/install/install-kubelogin.sh | 0 .../files/install/install-pre-commit.sh | 0 .../files/install/install-python-tools.sh | 0 .../files/install/install-terraform.sh | 0 .../files/install/install-terragrunt.sh | 0 .../files/install/install-tf-summarize.sh | 0 .devcontainer/files/install/install-tflint.sh | 0 .devcontainer/files/install/install-yq.sh | 0 scripts/check-latest-versions.sh | 0 tests/integration-test.sh | 0 tests/run-all-tests.sh | 0 tests/validate-tools.sh | 0 23 files changed, 16 insertions(+), 48 deletions(-) delete mode 100644 .ansible-lint mode change 100644 => 100755 .devcontainer/files/codex/codex-init mode change 100644 => 100755 .devcontainer/files/install/_arch.sh mode change 100644 => 100755 .devcontainer/files/install/install-ansible.sh mode change 100644 => 100755 .devcontainer/files/install/install-azcopy.sh mode change 100644 => 100755 .devcontainer/files/install/install-azure-cli.sh mode change 100644 => 100755 .devcontainer/files/install/install-checkov.sh mode change 100644 => 100755 .devcontainer/files/install/install-docker.sh mode change 100644 => 100755 .devcontainer/files/install/install-git-crypt.sh mode change 100644 => 100755 .devcontainer/files/install/install-gitleaks.sh mode change 100644 => 100755 .devcontainer/files/install/install-kubectl.sh mode change 100644 => 100755 .devcontainer/files/install/install-kubelogin.sh mode change 100644 => 100755 .devcontainer/files/install/install-pre-commit.sh mode change 100644 => 100755 .devcontainer/files/install/install-python-tools.sh mode change 100644 => 100755 .devcontainer/files/install/install-terraform.sh mode change 100644 => 100755 .devcontainer/files/install/install-terragrunt.sh mode change 100644 => 100755 .devcontainer/files/install/install-tf-summarize.sh mode change 100644 => 100755 .devcontainer/files/install/install-tflint.sh mode change 100644 => 100755 .devcontainer/files/install/install-yq.sh mode change 100644 => 100755 scripts/check-latest-versions.sh mode change 100644 => 100755 tests/integration-test.sh mode change 100644 => 100755 tests/run-all-tests.sh mode change 100644 => 100755 tests/validate-tools.sh diff --git a/.ansible-lint b/.ansible-lint deleted file mode 100644 index f7e4f1e..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/files/codex/codex-init b/.devcontainer/files/codex/codex-init old mode 100644 new mode 100755 diff --git a/.devcontainer/files/install/_arch.sh b/.devcontainer/files/install/_arch.sh old mode 100644 new mode 100755 diff --git a/.devcontainer/files/install/install-ansible.sh b/.devcontainer/files/install/install-ansible.sh old mode 100644 new mode 100755 diff --git a/.devcontainer/files/install/install-azcopy.sh b/.devcontainer/files/install/install-azcopy.sh old mode 100644 new mode 100755 index 5f4c0e9..2a785a3 --- a/.devcontainer/files/install/install-azcopy.sh +++ b/.devcontainer/files/install/install-azcopy.sh @@ -7,22 +7,27 @@ WORKDIR="/tmp/install-azcopy" mkdir -p "${WORKDIR}" cd "${WORKDIR}" -# aka.ms serves the current release per architecture; the amd64 alias has no -# suffix, arm64 does. -if [ "${ARCH_DEB}" = "amd64" ]; then - LATEST_URL="https://aka.ms/downloadazcopy-v10-linux" -else - LATEST_URL="https://aka.ms/downloadazcopy-v10-linux-${ARCH_DEB}" -fi - -# 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="${LATEST_URL}" + 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_${ARCH_DEB}_${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 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-docker.sh b/.devcontainer/files/install/install-docker.sh old mode 100644 new mode 100755 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 diff --git a/.devcontainer/files/install/install-kubectl.sh b/.devcontainer/files/install/install-kubectl.sh old mode 100644 new mode 100755 diff --git a/.devcontainer/files/install/install-kubelogin.sh b/.devcontainer/files/install/install-kubelogin.sh old mode 100644 new mode 100755 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 diff --git a/.devcontainer/files/install/install-tf-summarize.sh b/.devcontainer/files/install/install-tf-summarize.sh old mode 100644 new mode 100755 diff --git a/.devcontainer/files/install/install-tflint.sh b/.devcontainer/files/install/install-tflint.sh old mode 100644 new mode 100755 diff --git a/.devcontainer/files/install/install-yq.sh b/.devcontainer/files/install/install-yq.sh old mode 100644 new mode 100755 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 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 From 0e242b3470c30accc9c50d30723b3164223db7bb Mon Sep 17 00:00:00 2001 From: Daniel Grimes Date: Mon, 3 Aug 2026 19:43:34 +0000 Subject: [PATCH 6/7] fix(pre-commit): make the hook suite actually run 'pre-commit run --all-files' failed before reaching half its hooks. Four separate causes: - .secrets.baseline did not exist, so the detect-secrets hook aborted - check-json parsed .devcontainer/devcontainer.json, which is JSONC and legitimately contains comments - 21 tracked scripts carried a shebang without an executable bit. The Dockerfile chmods them at build time, which is why nobody noticed - the ansible-lint hook pinned v6.22.1, incompatible with current ansible-core, and its 'files: \.(yaml|yml)$' pattern aimed it at the GitHub workflow files. This repo has no playbooks or roles, so the hook and .ansible-lint are removed rather than repaired markdownlint and yamllint were failing on their defaults against content that had never satisfied them - MD013 at 80 columns, a document-start marker GitHub workflows do not use, and the perennial yamllint complaint that the 'on:' trigger key is a YAML 1.1 boolean. Both now have config files, matching how .hadolint.yaml already works, and the findings that were real are fixed at source: code fences given languages, a literal backticked, and two over-long lines in build.yml shortened. Those two lines shrank by collapsing the repeated '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}' pair, which appeared eight times, into a single env.IMAGE. Verified: pre-commit run --all-files now exits 0, with only hadolint-docker skipped for want of a Docker daemon in this environment. --- .devcontainer/Dockerfile | 4 +- .devcontainer/devcontainer.json | 2 +- .devcontainer/files/entrypoint.sh | 2 +- .devcontainer/files/home/.bash_aliases | 2 +- .../Microsoft.PowerShell_profile.ps1 | 2 +- .../home/.config/powershell/ps_theme.json | 2 +- .devcontainer/files/home/.environment | 14 +-- .../files/install/install-ansible.sh | 2 +- .../files/install/install-azure-cli.sh | 4 +- .devcontainer/files/install/install-cswap.sh | 2 +- .devcontainer/files/install/install-dotnet.sh | 2 +- .devcontainer/files/install/install-uv.sh | 2 +- .github/workflows/build.yml | 37 +++--- .markdownlint.json | 9 ++ .pre-commit-config.yaml | 27 ++-- .pre-commit/README.md | 16 +++ .secrets.baseline | 118 ++++++++++++++++++ .yamllint.yaml | 25 ++++ CONTRIBUTING.md | 10 +- QUICKSTART.md | 20 +-- README.md | 13 +- SECURITY.md | 9 +- VERSION_MANAGEMENT.md | 28 ++++- tests/integration-test.sh | 2 +- tests/validate-tools.sh | 2 +- 25 files changed, 295 insertions(+), 61 deletions(-) create mode 100644 .markdownlint.json create mode 100644 .secrets.baseline create mode 100644 .yamllint.yaml diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index e934c7e..2b25652 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -107,7 +107,7 @@ 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} + /tmp/install/install-kubelogin.sh ${KUBELOGIN_VERSION} RUN /tmp/install/install-powershell.sh ${POWERSHELL_VERSION} @@ -178,4 +178,4 @@ HEALTHCHECK --interval=60s --timeout=10s --retries=3 \ USER $USERNAME -ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] \ No newline at end of file +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 72c636a..b4ee41b 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -130,4 +130,4 @@ } } } -} \ No newline at end of file +} 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/install-ansible.sh b/.devcontainer/files/install/install-ansible.sh index 4fda3b5..f718b54 100755 --- 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-azure-cli.sh b/.devcontainer/files/install/install-azure-cli.sh index f6e3e44..fc1d811 100755 --- 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-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-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-uv.sh b/.devcontainer/files/install/install-uv.sh index 3b21659..041099b 100755 --- a/.devcontainer/files/install/install-uv.sh +++ b/.devcontainer/files/install/install-uv.sh @@ -57,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/.github/workflows/build.yml b/.github/workflows/build.yml index 7c04cf3..c87a8e2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -37,7 +37,8 @@ on: env: REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} + # Full image reference, used everywhere except the login step + IMAGE: ghcr.io/${{ github.repository }} DOCKERFILE: .devcontainer/Dockerfile BUILD_CONTEXT: .devcontainer @@ -133,7 +134,7 @@ jobs: id: meta uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + images: ${{ env.IMAGE }} tags: ${{ inputs.tags }} # Build once for this architecture and load it locally so the repo's own @@ -151,7 +152,7 @@ jobs: tags: devcontainer-devops:test-${{ matrix.arch }} labels: ${{ steps.meta.outputs.labels }} no-cache: ${{ inputs.no-cache }} - cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache-${{ matrix.arch }} + 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 @@ -202,9 +203,9 @@ jobs: labels: ${{ steps.meta.outputs.labels }} sbom: true provenance: mode=max - cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache-${{ matrix.arch }} - cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache-${{ matrix.arch }},mode=max - outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true + 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 @@ -258,7 +259,7 @@ jobs: id: meta uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + images: ${{ env.IMAGE }} tags: ${{ inputs.tags }} - name: Create manifest list @@ -266,17 +267,23 @@ jobs: working-directory: /tmp/digests run: | set -euo pipefail - # shellcheck disable=SC2046 - docker buildx imagetools create \ - $(jq -cr '.target."docker-metadata-action".annotations | map("--annotation " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ - $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ - $(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *) + 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.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}" \ + "${{ env.IMAGE }}:${{ steps.meta.outputs.version }}" \ --format '{{ json .Manifest.Digest }}' | tr -d '"')" echo "digest=${digest}" >> "$GITHUB_OUTPUT" - echo "Published ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${digest}" + 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 @@ -285,6 +292,6 @@ jobs: - name: Attest build provenance uses: actions/attest@508db95dd578ae2727ebd6217d5ba78e4fbda05d # v4.2.1 with: - subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + subject-name: ${{ env.IMAGE }} subject-digest: ${{ steps.manifest.outputs.digest }} push-to-registry: true diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 0000000..5c7639d --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,9 @@ +{ + "_comment": "markdownlint rules for this repo. See https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md", + + "MD013": false, + + "MD024": { "siblings_only": true }, + + "MD033": { "allowed_elements": ["br"] } +} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ce9180c..8880f31 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,6 +11,9 @@ repos: - id: check-yaml args: ['--unsafe'] - id: check-json + # devcontainer.json is JSONC - comments are valid there per the + # devcontainer spec, so a strict JSON parser rejects it + exclude: ^\.devcontainer/devcontainer\.json$ - id: check-added-large-files args: ['--maxkb=1000'] - id: check-merge-conflict @@ -25,7 +28,13 @@ repos: rev: v0.9.0.6 hooks: - id: shellcheck - args: ['-x'] + # -x follows sourced files (_arch.sh); -S warning matches the CI lint + # job exactly, so a clean run here means a clean run there + args: ['-x', '-S', 'warning'] + # The home/ files are shell fragments sourced into an interactive + # shell, not standalone scripts - no shebang, and deliberately full of + # variables consumed by the shell rather than by them + exclude: ^\.devcontainer/files/home/ # Terraform checks - repo: https://github.com/antonbabenko/pre-commit-terraform @@ -45,12 +54,11 @@ repos: - --args=--quiet - --args=--framework terraform - # Ansible linting - - repo: https://github.com/ansible/ansible-lint - rev: v6.22.1 - hooks: - - id: ansible-lint - files: \.(yaml|yml)$ + # NOTE: there is no ansible-lint hook here on purpose. This repository ships + # Ansible inside the image but contains no playbooks or roles of its own, and + # the hook's `files: \.(yaml|yml)$` pattern pointed it at the GitHub workflow + # files, linting CI config as though it were Ansible. Add it back scoped to a + # real playbook directory if one ever lands. # Python checks - repo: https://github.com/psf/black @@ -72,19 +80,18 @@ repos: # Ignores come from .hadolint.yaml so this matches CI exactly - id: hadolint-docker - # Markdown linting + # Markdown linting - rules in .markdownlint.json - repo: https://github.com/igorshubovych/markdownlint-cli rev: v0.38.0 hooks: - id: markdownlint args: ['--fix'] - # YAML linting + # YAML linting - rules in .yamllint.yaml, which yamllint auto-discovers - repo: https://github.com/adrienverge/yamllint rev: v1.33.0 hooks: - id: yamllint - args: ['-d', '{extends: default, rules: {line-length: {max: 120}}}'] # Secret detection - repo: https://github.com/Yelp/detect-secrets diff --git a/.pre-commit/README.md b/.pre-commit/README.md index a3a938b..722dba5 100644 --- a/.pre-commit/README.md +++ b/.pre-commit/README.md @@ -14,6 +14,7 @@ pre-commit install --hook-type commit-msg ## What Gets Checked ### General + - Trailing whitespace - End of file fixes - Large files detection @@ -21,11 +22,13 @@ pre-commit install --hook-type commit-msg - Private key detection ### Shell Scripts + - ShellCheck linting - Shebang validation - Execute permissions ### Terraform + - Format checking (`terraform fmt`) - Validation (`terraform validate`) - Documentation generation @@ -33,26 +36,32 @@ pre-commit install --hook-type commit-msg - Linting (tflint) ### Ansible + - Ansible-lint for playbooks - YAML syntax validation ### Python + - Code formatting (black) - Style checking (flake8) - Import sorting ### Docker + - Dockerfile linting (hadolint) ### Documentation + - Markdown linting - YAML linting ### Security + - Secret detection - Private key scanning ### Git + - Conventional commit message format ## Usage @@ -70,11 +79,13 @@ git commit -m "feat: add new feature" ### Manual Run Run checks on all files: + ```bash pre-commit run --all-files ``` Run specific hook: + ```bash pre-commit run terraform-fmt --all-files pre-commit run shellcheck --all-files @@ -83,6 +94,7 @@ pre-commit run shellcheck --all-files ### Update Hooks Update to latest versions: + ```bash pre-commit autoupdate ``` @@ -90,6 +102,7 @@ pre-commit autoupdate ## Bypassing Hooks **Not recommended**, but if needed: + ```bash git commit --no-verify -m "emergency fix" ``` @@ -123,6 +136,7 @@ pre-commit run --all-files ### Skip specific files Add to `.pre-commit-config.yaml`: + ```yaml exclude: | (?x)^( @@ -147,6 +161,7 @@ shared, so the `hadolint-docker` hook here and the CI job agree by construction. ### Terraform validation fails Ensure Terraform is initialized: + ```bash cd terraform/ terraform init @@ -159,6 +174,7 @@ Check `.ansible-lint` configuration or update playbook syntax. ### Hadolint fails Fix Dockerfile issues or add ignore rules: + ```yaml args: ['--ignore', 'DL3008', '--ignore', 'DL3009'] ``` diff --git a/.secrets.baseline b/.secrets.baseline new file mode 100644 index 0000000..1cedd91 --- /dev/null +++ b/.secrets.baseline @@ -0,0 +1,118 @@ +{ + "version": "1.4.0", + "plugins_used": [ + { + "name": "ArtifactoryDetector" + }, + { + "name": "AWSKeyDetector" + }, + { + "name": "AzureStorageKeyDetector" + }, + { + "name": "Base64HighEntropyString", + "limit": 4.5 + }, + { + "name": "BasicAuthDetector" + }, + { + "name": "CloudantDetector" + }, + { + "name": "DiscordBotTokenDetector" + }, + { + "name": "GitHubTokenDetector" + }, + { + "name": "HexHighEntropyString", + "limit": 3.0 + }, + { + "name": "IbmCloudIamDetector" + }, + { + "name": "IbmCosHmacDetector" + }, + { + "name": "JwtTokenDetector" + }, + { + "name": "KeywordDetector", + "keyword_exclude": "" + }, + { + "name": "MailchimpDetector" + }, + { + "name": "NpmDetector" + }, + { + "name": "PrivateKeyDetector" + }, + { + "name": "SendGridDetector" + }, + { + "name": "SlackDetector" + }, + { + "name": "SoftlayerDetector" + }, + { + "name": "SquareOAuthDetector" + }, + { + "name": "StripeDetector" + }, + { + "name": "TwilioKeyDetector" + } + ], + "filters_used": [ + { + "path": "detect_secrets.filters.allowlist.is_line_allowlisted" + }, + { + "path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies", + "min_level": 2 + }, + { + "path": "detect_secrets.filters.heuristic.is_indirect_reference" + }, + { + "path": "detect_secrets.filters.heuristic.is_likely_id_string" + }, + { + "path": "detect_secrets.filters.heuristic.is_lock_file" + }, + { + "path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string" + }, + { + "path": "detect_secrets.filters.heuristic.is_potential_uuid" + }, + { + "path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign" + }, + { + "path": "detect_secrets.filters.heuristic.is_sequential_string" + }, + { + "path": "detect_secrets.filters.heuristic.is_swagger_file" + }, + { + "path": "detect_secrets.filters.heuristic.is_templated_secret" + }, + { + "path": "detect_secrets.filters.regex.should_exclude_file", + "pattern": [ + "^\\.git/" + ] + } + ], + "results": {}, + "generated_at": "2026-08-03T19:37:33Z" +} diff --git a/.yamllint.yaml b/.yamllint.yaml new file mode 100644 index 0000000..b8ae2d4 --- /dev/null +++ b/.yamllint.yaml @@ -0,0 +1,25 @@ +--- +# yamllint configuration. Auto-discovered by yamllint, so the pre-commit hook +# needs no inline -d argument and local runs match the hook exactly. + +extends: default + +rules: + line-length: + max: 120 + + # GitHub Actions workflows conventionally start straight at `name:` with no + # `---` marker, and every example in GitHub's own docs does the same. + document-start: disable + + # `on:` is the GitHub Actions trigger key. YAML 1.1 reads a bare `on` as the + # boolean true, which yamllint then reports as a non-canonical truthy value. + # The key is required to be spelled `on`, so exclude keys from the check and + # keep it for values. + truthy: + check-keys: false + + # One space before an inline comment is enough, and is what the pinned-action + # comments in the workflows use. + comments: + min-spaces-from-content: 1 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9241101..97950f0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,6 +25,7 @@ Thank you for your interest in contributing! This document provides guidelines a 1. **Fork the repository** 2. **Create a feature branch** + ```bash git checkout -b feature/your-feature-name ``` @@ -35,16 +36,18 @@ Thank you for your interest in contributing! This document provides guidelines a - 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 @@ -54,6 +57,7 @@ Thank you for your interest in contributing! This document provides guidelines a - `test:` Test additions/changes 6. **Push to your fork** + ```bash git push origin feature/your-feature-name ``` @@ -65,11 +69,13 @@ Thank you for your interest in contributing! This document provides guidelines a ### Adding New Tools 1. **Create installation script** + ```bash files/scripts/install-.sh ``` 2. **Follow the template:** + ```bash #!/bin/bash set -e @@ -98,6 +104,7 @@ Thank you for your interest in contributing! This document provides guidelines a - Update in correct order (least to most likely to change) 4. **Add to validation script** + ```bash validate_tool "" " --version" || ((FAILURES++)) ``` @@ -241,6 +248,7 @@ artefact - see the README's "Adding New Tools". ## 🎉 Recognition Contributors will be recognized in: + - GitHub contributors list - CHANGELOG.md for significant contributions diff --git a/QUICKSTART.md b/QUICKSTART.md index fa9a94b..df63bfa 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -28,6 +28,7 @@ code . When prompted, click **"Reopen in Container"** Or manually: + - Press `F1` or `Ctrl+Shift+P` - Type: `Dev Containers: Reopen in Container` - Press Enter @@ -54,14 +55,14 @@ 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) +✅ 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 @@ -97,12 +98,14 @@ testall ## Troubleshooting ### Container won't start + ```bash # Rebuild without cache F1 → Dev Containers: Rebuild Container Without Cache ``` ### Tools not found + ```bash # Verify PATH echo $PATH @@ -112,6 +115,7 @@ source ~/.bashrc ``` ### Permission issues + ```bash # Fix workspace permissions sudo chown -R vscode:vscode /workspace diff --git a/README.md b/README.md index 4c360f8..30a7a08 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ This devcontainer includes pre-configured tools for: ## 🏗️ Repository Structure -``` +```text devcontainer-devops/ ├── .github/ │ └── workflows/ @@ -132,12 +132,14 @@ devcontainer-devops/ ### 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 . ``` @@ -331,6 +333,7 @@ builds from the local `Dockerfile` by default — to pin, add the versions to it ## 📝 Usage Examples ### Terraform + ```bash terraform init terraform plan @@ -338,6 +341,7 @@ terraform apply ``` ### Azure CLI + ```bash az login az account list @@ -345,6 +349,7 @@ az group create --name myResourceGroup --location eastus ``` ### Docker + ```bash docker ps docker build -t myimage . @@ -352,12 +357,14 @@ 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 @@ -382,21 +389,25 @@ 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 diff --git a/SECURITY.md b/SECURITY.md index 59ea103..709d5aa 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -20,6 +20,7 @@ Security vulnerabilities should not be disclosed publicly until a fix is availab Send details to: **[your-security-email@example.com]** Include: + - Description of the vulnerability - Steps to reproduce - Potential impact @@ -72,10 +73,11 @@ Include: ### Development Practices 1. **Dependencies** + ```bash # Verify checksums sha256sum -c - + # Pin versions pip install package==version ``` @@ -103,12 +105,14 @@ Include: ### Static Analysis - **tflint**: Terraform linter and security scanner + ```bash tflint --init tflint ``` - **checkov**: IaC security scanning + ```bash checkov -d . checkov -f main.tf @@ -117,6 +121,7 @@ Include: ### Secret Management - **git-crypt**: Transparent file encryption + ```bash git-crypt init git-crypt add-gpg-user @@ -125,6 +130,7 @@ Include: ### Recommended Additional Tools Consider adding: + - **trivy**: Container vulnerability scanner - **SOPS**: Secrets encryption - **Vault**: HashiCorp Vault for secret management @@ -195,6 +201,7 @@ trivy image --scanners vuln --severity HIGH,CRITICAL \ ### Industry Standards This container aims to support: + - CIS Docker Benchmarks - NIST Cybersecurity Framework - SOC 2 compliance requirements diff --git a/VERSION_MANAGEMENT.md b/VERSION_MANAGEMENT.md index da06bbc..27d6e40 100644 --- a/VERSION_MANAGEMENT.md +++ b/VERSION_MANAGEMENT.md @@ -19,6 +19,7 @@ By default, when you build the container without specifying versions, it will au ## 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 @@ -79,6 +80,7 @@ point-in-time snapshot rather than a reproducible build. ### Inside the Container Run validation to see installed versions: + ```bash validate ``` @@ -86,11 +88,13 @@ 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 @@ -98,7 +102,9 @@ This will: ## Version Strategy Recommendations ### Development Environment + ✅ **Use latest versions** for maximum features and security patches + ```json "args": { "UBUNTU_VERSION": "24.04" @@ -107,7 +113,9 @@ This will: ``` ### CI/CD Pipelines + ⚠️ **Pin versions** for reproducibility + ```json "args": { "UBUNTU_VERSION": "24.04", @@ -118,7 +126,9 @@ This will: ``` ### Production Support + 🔒 **Pin all versions** for stability + ```json "args": { "UBUNTU_VERSION": "24.04", @@ -142,6 +152,7 @@ This will: ### Regular Updates (Monthly Recommended) 1. **Check for updates:** + ```bash bash scripts/check-latest-versions.sh ``` @@ -149,11 +160,13 @@ This will: 2. **Update configuration** with new versions if desired 3. **Rebuild container:** - ``` + + ```text Dev Containers: Rebuild Container ``` 4. **Test thoroughly:** + ```bash validate testall @@ -166,16 +179,19 @@ This will: 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 ``` @@ -214,12 +230,14 @@ 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", @@ -227,6 +245,7 @@ Always test after updates! ``` ### Slow builds + ```bash # Version fetching adds ~30s to build # Pin versions to skip API calls @@ -235,6 +254,7 @@ Always test after updates! ## Best Practices ✅ **DO:** + - Use latest versions in development - Pin versions in CI/CD - Test after each update @@ -242,6 +262,7 @@ Always test after updates! - Check release notes before updating ❌ **DON'T:** + - Auto-update in production - Skip testing after updates - Mix latest and pinned randomly @@ -250,6 +271,7 @@ Always test after updates! ## Examples ### Pure Latest (Development) + ```json "args": { "UBUNTU_VERSION": "24.04" @@ -257,6 +279,7 @@ Always test after updates! ``` ### Mixed (Flexible Development) + ```json "args": { "UBUNTU_VERSION": "24.04", @@ -266,6 +289,7 @@ Always test after updates! ``` ### Fully Pinned (Production) + ```json "args": { "UBUNTU_VERSION": "24.04", diff --git a/tests/integration-test.sh b/tests/integration-test.sh index 9fde2d0..10ca52d 100755 --- a/tests/integration-test.sh +++ b/tests/integration-test.sh @@ -23,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/validate-tools.sh b/tests/validate-tools.sh index 557652c..b9865f2 100755 --- 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) From badade6b4d377c1832a41fbe7c42ea12fd836c51 Mon Sep 17 00:00:00 2001 From: Daniel Grimes Date: Mon, 3 Aug 2026 19:43:41 +0000 Subject: [PATCH 7/7] docs: fix the changelog structure and the misleading Azure diagram The [Unreleased] section had two '### Added' and two '### Changed' blocks, and my earlier commit inserted '### Removed' partway down the first Added list. The result read 'Removed: Installation scripts ... Terraform, Terragrunt, Azure CLI', which is the opposite of what happened. Each subsection now appears once, in Keep a Changelog order, with the original entries preserved and this branch's entries appended. ARCHITECTURE's external-connections diagram listed 'Azure ACR (Images)' and 'DevOps (CI/CD)' directly above the build flow, reading as though the image were still built in Azure DevOps and stored in ACR. The box describes what the tooling inside the container talks to, so it keeps ACR as a registry the user works with, swaps the stale DevOps entry for Entra ID, which is what az and kubelogin actually authenticate against, and says plainly that this is not where the image comes from. --- ARCHITECTURE.md | 17 ++++++----- CHANGELOG.md | 75 ++++++++++++++++++++++++++++++++----------------- 2 files changed, 59 insertions(+), 33 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 8b976ef..a7f86f6 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,6 +1,6 @@ # DevContainer Architecture -``` +```text ┌─────────────────────────────────────────────────────────────────┐ │ VS Code DevContainer │ │ │ @@ -63,17 +63,20 @@ 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 │ │ -│ │ (Images) │ │ (Resources) │ │ (K8s) │ │ +│ │ (Registries) │ │ (Resources) │ │ (K8s) │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ -│ │ Key Vault │ │ Storage │ │ DevOps │ │ -│ │ (Secrets) │ │ (State) │ │ (CI/CD) │ │ +│ │ Key Vault │ │ Storage │ │ Entra ID │ │ +│ │ (Secrets) │ │ (State) │ │ (Auth) │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────────────┘ @@ -117,18 +120,18 @@ Tool Interaction Flow: │ ├──────▶ tflint (Linting) └──────▶ checkov (Security) - + ┌─────────────┐ │ Kubectl │──────────▶ Kubernetes Cluster │ Helm │ └─────────────┘ │ └──────▶ kubelogin (Auth) - + ┌─────────────┐ │ Docker │──────────▶ Container Registry └─────────────┘ - + ┌─────────────┐ │ Ansible │──────────▶ Target Servers └─────────────┘ diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bb6b21..b2048c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,32 +11,9 @@ contained on a given date; it cannot promise a compatibility contract. ## [Unreleased] ### Added + - Complete devcontainer configuration for DevOps workflows - Dockerfile with multi-tool installation -- 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 - -### Changed -- `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 - -### Removed -- `azure-pipelines.yml` and its Azure Container Registry and Dependency-Track - integration, replaced by the GitHub Actions workflows above - Installation scripts with isolated /tmp directories for: - Terraform (latest or pinned version) - Terragrunt (v0.93.9) @@ -78,9 +55,26 @@ contained on a given date; it cannot promise a compatibility contract. - 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 + +- 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 @@ -97,13 +91,40 @@ contained on a given date; it cannot promise a compatibility contract. 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) @@ -112,6 +133,7 @@ contained on a given date; it cannot promise a compatibility contract. ## [1.0.0] - 2025-11-21 ### Added + - Initial release of DevOps DevContainer - Basic tool installations - Simple devcontainer configuration @@ -136,6 +158,7 @@ When making changes: - **PATCH**: Bug fixes (backward compatible) Example: 2.1.3 + - 2 = Major version -- 1 = Minor version +- 1 = Minor version - 3 = Patch version