diff --git a/.commitlintrc.yaml b/.commitlintrc.yaml new file mode 100644 index 0000000..9cb74a7 --- /dev/null +++ b/.commitlintrc.yaml @@ -0,0 +1,2 @@ +extends: + - "@commitlint/config-conventional" diff --git a/.containerignore b/.containerignore new file mode 100644 index 0000000..fc87576 --- /dev/null +++ b/.containerignore @@ -0,0 +1,13 @@ +.git +.github +build +.env +*.md +LICENSE +.dive-ci +.hadolint.yaml +.releaserc.yaml +.commitlintrc.yaml +.containerignore +.pre-commit-config.yaml +scripts/ diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..97e1871 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,40 @@ +name: CI + +on: + pull_request: + branches: [master] + +permissions: + contents: read + pull-requests: read + +jobs: + commitlint: + name: Lint commit messages + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: wagoid/commitlint-github-action@v6 + + hadolint: + name: Lint Containerfile + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: hadolint/hadolint-action@v3.1.0 + with: + dockerfile: Containerfile + + build: + name: Test build + runs-on: ubuntu-latest + needs: [hadolint] + steps: + - uses: actions/checkout@v4 + + - name: Build image + run: docker build -f Containerfile -t test-build . diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..0939c83 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,145 @@ +name: Release + +on: + push: + branches: [master] + +permissions: + contents: write + packages: write + +jobs: + release: + name: Semantic release + runs-on: ubuntu-latest + outputs: + new_release_published: ${{ steps.semantic.outputs.new_release_published }} + new_release_version: ${{ steps.semantic.outputs.new_release_version }} + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - uses: cycjimmy/semantic-release-action@v4 + id: semantic + with: + extra_plugins: | + @semantic-release/changelog + @semantic-release/git + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + build-and-push: + name: Build, scan & push + needs: release + if: needs.release.outputs.new_release_published == 'true' + runs-on: ubuntu-latest + env: + IMAGE_VERSION: ${{ needs.release.outputs.new_release_version }} + steps: + - uses: actions/checkout@v4 + + - name: Install build tools + run: ./scripts/install_tools.sh + + - name: Read manifest + id: manifest + run: | + echo "image_name=$(yq e '.name' manifest.yaml)" >> "$GITHUB_OUTPUT" + echo "registry=$(yq e '.registry' manifest.yaml)" >> "$GITHUB_OUTPUT" + echo "format=$(yq e '.build.format' manifest.yaml)" >> "$GITHUB_OUTPUT" + + - name: Validate Containerfile + run: | + docker pull -q ghcr.io/hadolint/hadolint:latest + docker run --rm -i hadolint/hadolint:latest < Containerfile + + - name: Build image + env: + IMAGE_NAME: ${{ steps.manifest.outputs.image_name }} + IMAGE_FORMAT: ${{ steps.manifest.outputs.format }} + run: | + # Build args from manifest + BUILD_ARGS="" + for arg in $(yq e '.build.args[]' manifest.yaml); do + BUILD_ARGS="${BUILD_ARGS} --build-arg ${arg}" + done + + # Labels from manifest + LABELS="" + while IFS= read -r label; do + if [[ -n "${label}" ]]; then + label_key="${label%%=*}" + label_value="${label#*=}" + label_value="${label_value%\"}" + label_value="${label_value#\"}" + LABELS="${LABELS} --label ${label_key}=${label_value}" + fi + done < <(yq e '.build.labels[]' manifest.yaml) + + # Add version label + LABELS="${LABELS} --label org.opencontainers.image.version=${IMAGE_VERSION}" + + # shellcheck disable=SC2086 + buildah build \ + --squash \ + --pull-always \ + --format "${IMAGE_FORMAT}" \ + ${BUILD_ARGS} \ + ${LABELS} \ + --tag "${IMAGE_NAME}:${IMAGE_VERSION}" \ + . + + # Save to OCI archive for scanning and pushing + mkdir -p build + buildah push "${IMAGE_NAME}:${IMAGE_VERSION}" "oci-archive:build/${IMAGE_NAME}.tar" + + # Load into Docker daemon for dive scan + docker load -i "build/${IMAGE_NAME}.tar" 2>/dev/null || true + docker tag "$(docker images -q | head -1)" "${IMAGE_NAME}:${IMAGE_VERSION}" 2>/dev/null || true + + - name: Dive filesystem scan + env: + IMAGE_NAME: ${{ steps.manifest.outputs.image_name }} + run: dive --ci --source=docker "${IMAGE_NAME}:${IMAGE_VERSION}" + + - name: Trivy vulnerability scan + env: + IMAGE_NAME: ${{ steps.manifest.outputs.image_name }} + run: | + trivy image \ + --input "build/${IMAGE_NAME}.tar" \ + --severity HIGH,CRITICAL \ + --exit-code 1 \ + "${IMAGE_NAME}:${IMAGE_VERSION}" + + - name: Login to GHCR + env: + REGISTRY: ${{ steps.manifest.outputs.registry }} + run: skopeo login ghcr.io -u "${{ github.actor }}" -p "${{ secrets.GITHUB_TOKEN }}" + + - name: Push to registry + env: + IMAGE_NAME: ${{ steps.manifest.outputs.image_name }} + REGISTRY: ${{ steps.manifest.outputs.registry }} + run: | + MAJOR_MINOR="${IMAGE_VERSION%.*}" + MAJOR="${IMAGE_VERSION%%.*}" + + # Push semantic version tag (1.2.3) + skopeo copy --all "oci-archive:build/${IMAGE_NAME}.tar" "docker://${REGISTRY}:${IMAGE_VERSION}" + + # Push major.minor tag (1.2) + skopeo copy --all "oci-archive:build/${IMAGE_NAME}.tar" "docker://${REGISTRY}:${MAJOR_MINOR}" + + # Push major tag (1) + skopeo copy --all "oci-archive:build/${IMAGE_NAME}.tar" "docker://${REGISTRY}:${MAJOR}" + + # Push latest tag + skopeo copy --all "oci-archive:build/${IMAGE_NAME}.tar" "docker://${REGISTRY}:latest" + + - name: Verify pushed image + env: + REGISTRY: ${{ steps.manifest.outputs.registry }} + run: | + skopeo inspect "docker://${REGISTRY}:${IMAGE_VERSION}" --format '{{.Labels}}' diff --git a/.github/workflows/update-tools.yaml b/.github/workflows/update-tools.yaml new file mode 100644 index 0000000..a2f9043 --- /dev/null +++ b/.github/workflows/update-tools.yaml @@ -0,0 +1,132 @@ +name: Update tool versions + +on: + schedule: + - cron: "0 8 * * 1" # Every Monday at 08:00 UTC + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + update-tools: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Fetch latest tool versions + id: versions + env: + GH_TOKEN: ${{ github.token }} + run: | + get_latest_version() { + local repo="$1" + gh api "repos/${repo}/releases/latest" --jq '.tag_name' | sed 's/^v//' + } + + ARGO_LATEST=$(get_latest_version "argoproj/argo-workflows") + KARGO_LATEST=$(get_latest_version "akuity/kargo") + PACK_LATEST=$(get_latest_version "buildpacks/pack") + DIVE_LATEST=$(get_latest_version "wagoodman/dive") + HADOLINT_LATEST=$(get_latest_version "hadolint/hadolint") + YQ_LATEST=$(get_latest_version "mikefarah/yq") + + echo "argo=${ARGO_LATEST}" >> "$GITHUB_OUTPUT" + echo "kargo=${KARGO_LATEST}" >> "$GITHUB_OUTPUT" + echo "pack=${PACK_LATEST}" >> "$GITHUB_OUTPUT" + echo "dive=${DIVE_LATEST}" >> "$GITHUB_OUTPUT" + echo "hadolint=${HADOLINT_LATEST}" >> "$GITHUB_OUTPUT" + echo "yq=${YQ_LATEST}" >> "$GITHUB_OUTPUT" + + ARGO_CURRENT=$(grep -oP 'ARGO_VERSION=\K[0-9.]+' Containerfile) + KARGO_CURRENT=$(grep -oP 'KARGO_VERSION=\K[0-9.]+' Containerfile) + PACK_CURRENT=$(grep -oP 'PACK_VERSION=\K[0-9.]+' Containerfile) + DIVE_CURRENT=$(grep -oP 'DIVE_VERSION=\K[0-9.]+' Containerfile) + HADOLINT_CURRENT=$(grep -oP 'HADOLINT_VERSION=\K[0-9.]+' Containerfile) + YQ_CURRENT=$(grep -oP 'YQ_VERSION=\K[0-9.]+' Containerfile) + + echo "argo_current=${ARGO_CURRENT}" >> "$GITHUB_OUTPUT" + echo "kargo_current=${KARGO_CURRENT}" >> "$GITHUB_OUTPUT" + echo "pack_current=${PACK_CURRENT}" >> "$GITHUB_OUTPUT" + echo "dive_current=${DIVE_CURRENT}" >> "$GITHUB_OUTPUT" + echo "hadolint_current=${HADOLINT_CURRENT}" >> "$GITHUB_OUTPUT" + echo "yq_current=${YQ_CURRENT}" >> "$GITHUB_OUTPUT" + + UPDATES="" + if [ "${ARGO_CURRENT}" != "${ARGO_LATEST}" ]; then + UPDATES="${UPDATES}- Argo Workflows CLI: ${ARGO_CURRENT} -> ${ARGO_LATEST}\n" + fi + if [ "${KARGO_CURRENT}" != "${KARGO_LATEST}" ]; then + UPDATES="${UPDATES}- Kargo CLI: ${KARGO_CURRENT} -> ${KARGO_LATEST}\n" + fi + if [ "${PACK_CURRENT}" != "${PACK_LATEST}" ]; then + UPDATES="${UPDATES}- pack (Buildpacks): ${PACK_CURRENT} -> ${PACK_LATEST}\n" + fi + if [ "${DIVE_CURRENT}" != "${DIVE_LATEST}" ]; then + UPDATES="${UPDATES}- dive: ${DIVE_CURRENT} -> ${DIVE_LATEST}\n" + fi + if [ "${HADOLINT_CURRENT}" != "${HADOLINT_LATEST}" ]; then + UPDATES="${UPDATES}- hadolint: ${HADOLINT_CURRENT} -> ${HADOLINT_LATEST}\n" + fi + if [ "${YQ_CURRENT}" != "${YQ_LATEST}" ]; then + UPDATES="${UPDATES}- yq: ${YQ_CURRENT} -> ${YQ_LATEST}\n" + fi + + if [ -z "${UPDATES}" ]; then + echo "has_updates=false" >> "$GITHUB_OUTPUT" + echo "No updates found." + else + echo "has_updates=true" >> "$GITHUB_OUTPUT" + # Use a delimiter for multiline output + { + echo "summary<> "$GITHUB_OUTPUT" + echo -e "Updates found:\n${UPDATES}" + fi + + - name: Update versions in Containerfile and manifest + if: steps.versions.outputs.has_updates == 'true' + env: + ARGO_LATEST: ${{ steps.versions.outputs.argo }} + KARGO_LATEST: ${{ steps.versions.outputs.kargo }} + PACK_LATEST: ${{ steps.versions.outputs.pack }} + DIVE_LATEST: ${{ steps.versions.outputs.dive }} + HADOLINT_LATEST: ${{ steps.versions.outputs.hadolint }} + YQ_LATEST: ${{ steps.versions.outputs.yq }} + ARGO_CURRENT: ${{ steps.versions.outputs.argo_current }} + KARGO_CURRENT: ${{ steps.versions.outputs.kargo_current }} + PACK_CURRENT: ${{ steps.versions.outputs.pack_current }} + DIVE_CURRENT: ${{ steps.versions.outputs.dive_current }} + HADOLINT_CURRENT: ${{ steps.versions.outputs.hadolint_current }} + YQ_CURRENT: ${{ steps.versions.outputs.yq_current }} + run: | + update_version() { + local name="$1" current="$2" latest="$3" + if [ "${current}" != "${latest}" ]; then + sed -i "s/${name}=${current}/${name}=${latest}/g" Containerfile manifest.yaml + fi + } + + update_version "ARGO_VERSION" "${ARGO_CURRENT}" "${ARGO_LATEST}" + update_version "KARGO_VERSION" "${KARGO_CURRENT}" "${KARGO_LATEST}" + update_version "PACK_VERSION" "${PACK_CURRENT}" "${PACK_LATEST}" + update_version "DIVE_VERSION" "${DIVE_CURRENT}" "${DIVE_LATEST}" + update_version "HADOLINT_VERSION" "${HADOLINT_CURRENT}" "${HADOLINT_LATEST}" + update_version "YQ_VERSION" "${YQ_CURRENT}" "${YQ_LATEST}" + + - name: Create pull request + if: steps.versions.outputs.has_updates == 'true' + uses: peter-evans/create-pull-request@v7 + with: + commit-message: "chore: update tool versions" + branch: chore/update-tool-versions + title: "chore: update tool versions" + body: | + Automated tool version updates: + + ${{ steps.versions.outputs.summary }} + labels: dependencies diff --git a/.hadolint.yaml b/.hadolint.yaml new file mode 100644 index 0000000..bd3f280 --- /dev/null +++ b/.hadolint.yaml @@ -0,0 +1,3 @@ +trustedRegistries: + - docker.io + - ghcr.io diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..b71bb13 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,30 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + - id: check-merge-conflict + - id: detect-private-key + + - repo: https://github.com/hadolint/hadolint + rev: v2.12.0 + hooks: + - id: hadolint-docker + entry: hadolint/hadolint hadolint + args: ["--config", ".hadolint.yaml"] + + - repo: https://github.com/shellcheck-py/shellcheck-py + rev: v0.10.0.1 + hooks: + - id: shellcheck + args: ["-e", "SC1091"] + + - repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook + rev: v9.21.0 + hooks: + - id: commitlint + stages: [commit-msg] + additional_dependencies: ["@commitlint/config-conventional"] diff --git a/.releaserc.yaml b/.releaserc.yaml new file mode 100644 index 0000000..3dfc0b8 --- /dev/null +++ b/.releaserc.yaml @@ -0,0 +1,14 @@ +branches: + - master + +plugins: + - "@semantic-release/commit-analyzer" + - "@semantic-release/release-notes-generator" + - - "@semantic-release/changelog" + - changelogFile: CHANGELOG.md + - - "@semantic-release/github" + - assets: [] + - - "@semantic-release/git" + - assets: + - CHANGELOG.md + message: "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" diff --git a/Containerfile b/Containerfile index ed783d7..dacc042 100644 --- a/Containerfile +++ b/Containerfile @@ -1,20 +1,10 @@ -ARG UBUNTU_VERSION=24.04 +ARG RUNNER_VERSION=latest -FROM docker.io/library/ubuntu:$UBUNTU_VERSION as base +FROM ghcr.io/actions/runner:${RUNNER_VERSION} as base -ARG APP_UID=1000 -ARG APP_HOME=/home/appuser +ARG APP_HOME=/home/runner -# Setup the non-root user -RUN userdel --remove ubuntu \ - && useradd \ - --no-log-init \ - --uid $APP_UID \ - --home-dir ${APP_HOME} \ - --create-home \ - --user-group \ - appuser && \ - chown -R appuser:appuser ${APP_HOME} +USER root # Update and upgrade the system RUN apt-get update \ @@ -35,6 +25,83 @@ RUN apt-get update \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* +# Install skopeo +# hadolint ignore=DL3008 +RUN apt-get update \ + && apt-get install --no-install-recommends -y skopeo \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Install buildah +# hadolint ignore=DL3008 +RUN apt-get update \ + && apt-get install --no-install-recommends -y buildah \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Configure buildah storage for container/rootless usage +RUN mkdir -p /etc/containers \ + && printf '[storage]\ndriver = "vfs"\n' > /etc/containers/storage.conf + +# Install trivy (vulnerability scanner) +# hadolint ignore=DL3008,DL4006 +RUN curl -fsSL https://aquasecurity.github.io/trivy-repo/deb/public.key \ + | gpg --dearmor -o /usr/share/keyrings/trivy.gpg \ + && echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb generic main" \ + | tee /etc/apt/sources.list.d/trivy.list \ + && apt-get update \ + && apt-get install --no-install-recommends -y trivy \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Install dive (container filesystem analysis) +ARG DIVE_VERSION=0.12.0 +# hadolint ignore=DL3008 +RUN curl -sSL -o /tmp/dive.deb \ + "https://github.com/wagoodman/dive/releases/download/v${DIVE_VERSION}/dive_${DIVE_VERSION}_linux_amd64.deb" \ + && apt-get update \ + && apt-get install --no-install-recommends -y /tmp/dive.deb \ + && rm /tmp/dive.deb \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Install hadolint (Dockerfile/Containerfile linter) +ARG HADOLINT_VERSION=2.12.0 +RUN curl -sSL -o /usr/local/bin/hadolint \ + "https://github.com/hadolint/hadolint/releases/download/v${HADOLINT_VERSION}/hadolint-Linux-x86_64" \ + && chmod +x /usr/local/bin/hadolint + +# Install yq (YAML processor) +ARG YQ_VERSION=4.45.4 +RUN curl -sSL -o /usr/local/bin/yq \ + "https://github.com/mikefarah/yq/releases/download/v${YQ_VERSION}/yq_linux_amd64" \ + && chmod +x /usr/local/bin/yq + +# Install Argo Workflows CLI +ARG ARGO_VERSION=3.6.4 +RUN curl -sSL -o /tmp/argo-linux-amd64.gz \ + "https://github.com/argoproj/argo-workflows/releases/download/v${ARGO_VERSION}/argo-linux-amd64.gz" \ + && gunzip /tmp/argo-linux-amd64.gz \ + && mv /tmp/argo-linux-amd64 /usr/local/bin/argo \ + && chmod +x /usr/local/bin/argo + +# Install Kargo CLI +ARG KARGO_VERSION=1.9.2 +RUN curl -sSL -o /usr/local/bin/kargo \ + "https://github.com/akuity/kargo/releases/download/v${KARGO_VERSION}/kargo-linux-amd64" \ + && chmod +x /usr/local/bin/kargo + +# Install pack (Cloud Native Buildpacks CLI) +ARG PACK_VERSION=0.36.4 +RUN curl -sSL -o /tmp/pack.tgz \ + "https://github.com/buildpacks/pack/releases/download/v${PACK_VERSION}/pack-v${PACK_VERSION}-linux.tgz" \ + && tar -xzf /tmp/pack.tgz -C /usr/local/bin/ \ + && rm /tmp/pack.tgz + +# Install pre-commit +# hadolint ignore=DL3013 +RUN pip install --no-cache-dir pre-commit + # Install Poetry latest version and add it to PATH # hadolint ignore=DL4006 RUN curl -sSL https://install.python-poetry.org | python3 - @@ -54,7 +121,7 @@ LABEL org.opencontainers.image.licenses="MIT" LABEL org.opencontainers.image.authors="Deerhide" LABEL org.opencontainers.image.vendor="Deerhide" -USER ${APP_UID} +USER runner WORKDIR ${APP_HOME} # Install Poetry latest version and add it to PATH @@ -67,6 +134,3 @@ RUN curl -LsSf https://astral.sh/uv/install.sh | sh # Add Poetry and UV to PATH RUN echo "export PATH=\"${APP_HOME}/.local/bin:\$PATH\"" >> ~/.bashrc - -# Placeholder command to keep the container running -# CMD ["/bin/bash", "-c", "while true; do sleep 1; done"] diff --git a/README.md b/README.md index 4075017..96aac0d 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,200 @@ -# Deerhide / Template for Container Image +# Deerhide / Python GitHub Runner -## Pre-requisites +Container image based on the [GitHub Actions Runner](https://github.com/actions/runner) with Python tooling, DevOps CLIs, and a full container build pipeline baked in. Designed to be used as a self-hosted runner that can build itself. -### Install `docker` -[See Docker documentation](https://docs.docker.com/get-docker/) +## What's included -### Install tools +### Base image + +`ghcr.io/actions/runner` (GitHub Actions Runner) + +### Python + +| Tool | Version | +|------|---------| +| Python | 3.12, 3.13, 3.14 (via deadsnakes PPA) | +| Poetry | latest | +| UV | latest | + +### DevOps CLIs + +| Tool | Description | +|------|-------------| +| [Argo Workflows CLI](https://github.com/argoproj/argo-workflows) | Workflow orchestration on Kubernetes | +| [Kargo CLI](https://github.com/akuity/kargo) | Application lifecycle orchestration | +| [pack](https://github.com/buildpacks/pack) | Cloud Native Buildpacks CLI | +| [skopeo](https://github.com/containers/skopeo) | Container image registry operations | + +### Build pipeline tools + +These tools allow the image to run its own build pipeline as a self-hosted runner. + +| Tool | Description | +|------|-------------| +| [buildah](https://github.com/containers/buildah) | OCI container image builder | +| [dive](https://github.com/wagoodman/dive) | Container filesystem analysis | +| [trivy](https://github.com/aquasecurity/trivy) | Vulnerability scanner | +| [hadolint](https://github.com/hadolint/hadolint) | Dockerfile/Containerfile linter | +| [yq](https://github.com/mikefarah/yq) | YAML processor | +| [pre-commit](https://pre-commit.com/) | Git hooks framework | + +## CI/CD + +### Workflows + +| Workflow | Trigger | Description | +|----------|---------|-------------| +| **CI** | Pull request to `master` | Commitlint, hadolint lint, test build | +| **Release** | Push to `master` | Semantic release, build, scan, push to GHCR | +| **Update tools** | Weekly (Monday 08:00 UTC) / manual | Checks for new tool versions, opens a PR | + +### Release process + +Releases are fully automated via [semantic-release](https://github.com/semantic-release/semantic-release). Pushing to `master` triggers version analysis based on [Conventional Commits](https://www.conventionalcommits.org/): + +| Commit prefix | Version bump | +|---------------|-------------| +| `fix:` | Patch (1.0.0 -> 1.0.1) | +| `feat:` | Minor (1.0.0 -> 1.1.0) | +| `feat!:` / `BREAKING CHANGE:` | Major (1.0.0 -> 2.0.0) | + +When a new version is determined, the release workflow: + +1. Creates a GitHub release with auto-generated notes +2. Updates `CHANGELOG.md` +3. Builds the image with `buildah` (OCI format, squashed layers) +4. Runs `hadolint` lint validation +5. Runs `dive` filesystem efficiency scan +6. Runs `trivy` vulnerability scan (HIGH/CRITICAL) +7. Pushes to GHCR with semver tags: `1.2.3`, `1.2`, `1`, `latest` + +### Image tags + +``` +ghcr.io/deerhide/python-github-runner:latest +ghcr.io/deerhide/python-github-runner:1 +ghcr.io/deerhide/python-github-runner:1.2 +ghcr.io/deerhide/python-github-runner:1.2.3 +``` + +## Local development + +### Pre-requisites + +Install [Docker](https://docs.docker.com/get-docker/), then install the build tools: ```bash ./scripts/install_tools.sh ``` -## How to build the container image +### Configuration -### Update `manifest.yaml` +Build configuration is defined in `manifest.yaml`: ```yaml -name: deerhide_container_example -tags: +name: python-github-runner +tags: - latest -registry: ghcr.io/deerhide/template_container_image +registry: ghcr.io/deerhide/python-github-runner build: format: oci args: - - APP_UID=1000 - - UBUNTU_VERSION=24.04 + - RUNNER_VERSION=latest + - ARGO_VERSION=3.6.4 + - KARGO_VERSION=1.9.2 + - PACK_VERSION=0.36.4 + - DIVE_VERSION=0.12.0 + - HADOLINT_VERSION=2.12.0 + - YQ_VERSION=4.45.4 + labels: + - org.opencontainers.image.source=https://github.com/deerhide/python-github-runner + - org.opencontainers.image.description="Python GitHub Runner" + - org.opencontainers.image.licenses="MIT" + - org.opencontainers.image.authors="Deerhide" + - org.opencontainers.image.vendor="Deerhide" ``` -### Authenticate to the container registry +### Build + +Authenticate to the container registry: ```bash skopeo login ghcr.io ``` -### Launch Builder +Run the full build pipeline (lint, build, scan, push): ```bash ./scripts/builder.sh ``` + +### Pre-commit hooks + +Install the git hooks locally: + +```bash +pre-commit install --hook-type pre-commit --hook-type commit-msg +``` + +Hooks run automatically on every commit: + +| Hook | Stage | Description | +|------|-------|-------------| +| trailing-whitespace | pre-commit | Remove trailing whitespace | +| end-of-file-fixer | pre-commit | Ensure files end with a newline | +| check-yaml | pre-commit | Validate YAML syntax | +| check-added-large-files | pre-commit | Prevent large files from being committed | +| check-merge-conflict | pre-commit | Detect merge conflict markers | +| detect-private-key | pre-commit | Prevent private keys from being committed | +| hadolint | pre-commit | Lint Containerfile | +| shellcheck | pre-commit | Lint shell scripts | +| commitlint | commit-msg | Validate conventional commit messages | + +Run all hooks manually against all files: + +```bash +pre-commit run --all-files +``` + +### Contributing + +This project uses [Conventional Commits](https://www.conventionalcommits.org/). Commit messages are validated by commitlint on pull requests and locally via pre-commit hooks. + +```bash +# Good +git commit -m "feat: add kubectl to image" +git commit -m "fix: correct trivy scan exit code" +git commit -m "chore: update argo to v3.7.0" + +# Bad +git commit -m "added stuff" +git commit -m "WIP" +``` + +## Project structure + +``` +. +├── Containerfile # Multi-stage container definition +├── manifest.yaml # Build configuration and metadata +├── .releaserc.yaml # Semantic release configuration +├── .hadolint.yaml # Hadolint configuration +├── .commitlintrc.yaml # Commitlint configuration +├── .pre-commit-config.yaml # Pre-commit hooks configuration +├── .containerignore # Build context exclusions +├── .dive-ci # Dive efficiency thresholds +├── .github/ +│ └── workflows/ +│ ├── ci.yaml # PR validation +│ ├── release.yaml # Semantic release + build + push +│ └── update-tools.yaml # Automated tool version updates +└── scripts/ + ├── builder.sh # Local build orchestration + ├── install_tools.sh # Build tool installer + ├── lib_utils.sh # Logging utilities + └── login_skopeo.sh # Registry authentication helper +``` + +## License + +[MIT](LICENSE) diff --git a/manifest.yaml b/manifest.yaml index 2a3b6c9..7774c8e 100644 --- a/manifest.yaml +++ b/manifest.yaml @@ -1,12 +1,17 @@ name: python-github-runner -tags: +tags: - latest registry: ghcr.io/deerhide/python-github-runner build: format: oci args: - - APP_UID=1000 - - UBUNTU_VERSION=24.04 + - RUNNER_VERSION=latest + - ARGO_VERSION=3.6.4 + - KARGO_VERSION=1.9.2 + - PACK_VERSION=0.36.4 + - DIVE_VERSION=0.12.0 + - HADOLINT_VERSION=2.12.0 + - YQ_VERSION=4.45.4 labels: - org.opencontainers.image.source=https://github.com/deerhide/python-github-runner - org.opencontainers.image.description="Python GitHub Runner"