diff --git a/.gemini/styleguide.md b/.gemini/styleguide.md index 0a18357..1d7aafe 100644 --- a/.gemini/styleguide.md +++ b/.gemini/styleguide.md @@ -21,9 +21,8 @@ ## CI - Workflows MUST include weekly cron trigger for base image security updates -- Build caching is MANDATORY: `cache-from: type=gha` / `cache-to: type=gha,mode=max` -- Set `no-cache` only on non-commit builds (`schedule`, `repository_dispatch`, `workflow_dispatch`); never on `push` or `pull_request` builds -- Dual-push (Quay + GHCR) uses two separate jobs, not steps +- Container builds go through `crunchtools/constitution/.github/workflows/container-release.yml`: build once per change, gate the digest, promote by `skopeo copy --all --preserve-digests`. Outside the repos listed in `container_release_custom` (profiles/requirements.toml), flag any job that publishes an image (`push`, `skopeo copy`, `docker/build-push-action` with push) or moves `:latest`/`:main`; a local build that is never pushed (an integration-test image) is fine. Everywhere, flag `imagetools create` and a `ghcr.io/crunchtools` reference +- Quay.io is the only registry ## Versioning - Semantic Versioning 2.0.0 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 67a59bf..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,199 +0,0 @@ -name: Build, Test, and Push Container Image - -on: - push: - branches: [main, master] - tags: ["v*"] - repository_dispatch: - types: [parent-image-updated] - workflow_dispatch: -env: - REGISTRY: quay.io - IMAGE_NAME: crunchtools/ubi10-httpd-php-postgres - GHCR_IMAGE: ghcr.io/crunchtools/ubi10-httpd-php-postgres - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v7 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 - - - name: Extract metadata - id: meta - uses: docker/metadata-action@v6 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - tags: | - type=raw,value=latest,enable={{is_default_branch}} - type=raw,value=latest,enable=${{ github.ref_type == 'tag' }} - type=sha,prefix= - - - name: Create RHSM secret files - run: | - echo "${{ secrets.RHSM_ACTIVATION_KEY }}" > /tmp/RHSM_ACTIVATION_KEY - echo "${{ secrets.RHSM_ORG_ID }}" > /tmp/RHSM_ORG_ID - - - name: Build image - uses: docker/build-push-action@v7 - with: - context: . - file: Containerfile - push: false - load: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - secret-files: | - RHSM_ACTIVATION_KEY=/tmp/RHSM_ACTIVATION_KEY - RHSM_ORG_ID=/tmp/RHSM_ORG_ID - cache-from: type=gha - cache-to: type=gha,mode=max - # Non-commit rebuilds exist to pick up errata; a cached dnf layer ships none. - no-cache: ${{ github.event_name != 'push' && github.event_name != 'pull_request' }} - - - name: Save image as tarball - run: docker save ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest -o /tmp/image.tar - - - name: Upload image artifact - uses: actions/upload-artifact@v7 - with: - name: container-image - path: /tmp/image.tar - retention-days: 1 - - test: - runs-on: ubuntu-latest - needs: build - steps: - - name: Checkout repository - uses: actions/checkout@v7 - - - name: Download image artifact - uses: actions/download-artifact@v8 - with: - name: container-image - path: /tmp - - - name: Install Podman - run: | - sudo apt-get update -qq - sudo apt-get install -y -qq podman - - - name: Load image - run: podman load -i /tmp/image.tar - - - name: Start container - run: | - podman run -d --name test-container \ - --systemd=always \ - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest - - - name: Wait for services - run: | - echo "Waiting for services to start..." - for i in $(seq 1 60); do - if podman exec test-container systemctl is-active httpd >/dev/null 2>&1 && \ - podman exec test-container systemctl is-active postgresql >/dev/null 2>&1 && \ - podman exec test-container systemctl is-active php-fpm >/dev/null 2>&1; then - echo "Services are up after ${i}s" - break - fi - sleep 1 - done - - - name: Run smoke tests - run: | - podman cp tests/smoke-test.sh test-container:/tmp/smoke-test.sh - podman exec test-container chmod +x /tmp/smoke-test.sh - podman exec test-container /tmp/smoke-test.sh - - - name: Cleanup - if: always() - run: podman rm -f test-container - - build-and-push-quay: - runs-on: ubuntu-latest - needs: test - permissions: - contents: read - steps: - - name: Download image artifact - uses: actions/download-artifact@v8 - with: - name: container-image - path: /tmp - - - name: Load image - run: docker load -i /tmp/image.tar - - - name: Log in to Quay.io - uses: docker/login-action@v4 - with: - registry: ${{ env.REGISTRY }} - username: ${{ secrets.QUAY_USERNAME }} - password: ${{ secrets.QUAY_PASSWORD }} - - - name: Push image - run: | - docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest - SHA_TAG="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:$(echo ${{ github.sha }} | cut -c1-7)" - docker tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest "$SHA_TAG" || true - docker push "$SHA_TAG" || true - if [ "${{ github.ref_type }}" = "tag" ]; then - VERSION="${GITHUB_REF_NAME#v}" - docker tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:"$VERSION" - docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:"$VERSION" - fi - - - name: Run Trivy vulnerability scanner - continue-on-error: true - uses: aquasecurity/trivy-action@v0.36.0 - with: - image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest - format: "table" - exit-code: "0" - severity: "CRITICAL,HIGH" - - build-and-push-ghcr: - runs-on: ubuntu-latest - needs: build-and-push-quay - if: github.event_name != 'pull_request' - permissions: - contents: read - packages: write - - steps: - # Reuse the tarball the test job already verified rather than rebuilding, - # so Quay and GHCR carry the identical image rather than two builds that - # merely started from the same commit. - - name: Download image artifact - uses: actions/download-artifact@v8 - with: - name: container-image - path: /tmp - - - name: Load image - run: docker load -i /tmp/image.tar - - - name: Log in to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ github.token }} - - - name: Tag and push to GHCR - run: | - SHA_TAG="$(echo ${{ github.sha }} | cut -c1-7)" - docker tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest ${{ env.GHCR_IMAGE }}:latest - docker tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest ${{ env.GHCR_IMAGE }}:"$SHA_TAG" - docker push ${{ env.GHCR_IMAGE }}:latest - docker push ${{ env.GHCR_IMAGE }}:"$SHA_TAG" - if [ "${{ github.ref_type }}" = "tag" ]; then - VERSION="${GITHUB_REF_NAME#v}" - docker tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest ${{ env.GHCR_IMAGE }}:"$VERSION" - docker push ${{ env.GHCR_IMAGE }}:"$VERSION" - fi diff --git a/.github/workflows/constitution.yml b/.github/workflows/constitution.yml index 25788b8..d5881bf 100644 --- a/.github/workflows/constitution.yml +++ b/.github/workflows/constitution.yml @@ -18,4 +18,4 @@ permissions: jobs: constitution: name: Constitution - uses: crunchtools/constitution/.github/workflows/validate.yml@v1.31.0 + uses: crunchtools/constitution/.github/workflows/validate.yml@v1.39.4 diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml new file mode 100644 index 0000000..0482729 --- /dev/null +++ b/.github/workflows/container.yml @@ -0,0 +1,37 @@ +# Container release: build once per change, gate the digest, promote what was +# tested (constitution III and Container Image VII). Quay.io only. +# +# ubi10-httpd-php-postgres is part of the ubi10 cascade: it cuts no release, so `cascade: true` +# moves :latest with :main, and it is rebuilt when its parent dispatches +# parent-image-updated. + +name: Container + +on: + pull_request: + push: + branches: [main] + tags: ["v*"] + repository_dispatch: + types: [parent-image-updated] + workflow_dispatch: + +permissions: {} + +jobs: + container: + uses: crunchtools/constitution/.github/workflows/container-release.yml@v1.39.4 + permissions: + contents: read + pull-requests: read + with: + image: quay.io/crunchtools/ubi10-httpd-php-postgres + rhsm: true + boot: systemd + contract-test: tests/gate.sh + cascade: true + secrets: + QUAY_USERNAME: ${{ secrets.QUAY_USERNAME }} + QUAY_PASSWORD: ${{ secrets.QUAY_PASSWORD }} + RHSM_ACTIVATION_KEY: ${{ secrets.RHSM_ACTIVATION_KEY }} + RHSM_ORG_ID: ${{ secrets.RHSM_ORG_ID }} diff --git a/.github/workflows/dependabot-automerge.yml b/.github/workflows/dependabot-automerge.yml index 794a085..12742b3 100644 --- a/.github/workflows/dependabot-automerge.yml +++ b/.github/workflows/dependabot-automerge.yml @@ -15,4 +15,4 @@ permissions: jobs: automerge: name: Dependabot - uses: crunchtools/constitution/.github/workflows/dependabot-automerge.yml@v1.31.0 + uses: crunchtools/constitution/.github/workflows/dependabot-automerge.yml@v1.39.4 diff --git a/.specify/memory/constitution.md b/.specify/memory/constitution.md index 2a990cc..0644b51 100644 --- a/.specify/memory/constitution.md +++ b/.specify/memory/constitution.md @@ -4,7 +4,7 @@ > **Ratified:** 2026-03-10 > **Amended:** 2026-10-02 > **Status:** Active -> **Inherits:** [crunchtools/constitution](https://github.com/crunchtools/constitution) v1.31.0 +> **Inherits:** [crunchtools/constitution](https://github.com/crunchtools/constitution) v1.39.4 > **Profile:** Container Image This file holds what is specific to ubi10-httpd-php-postgres. The fleet rules and the Container diff --git a/tests/gate.sh b/tests/gate.sh new file mode 100755 index 0000000..6328dad --- /dev/null +++ b/tests/gate.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# The contract test for container-release.yml: boot $IMAGE (the gated digest) +# under systemd and run the smoke tests inside it. +set -euo pipefail +: "${IMAGE:?IMAGE is the digest under test}" +name="contract-$$" +podman run -d --name "$name" --systemd=always "$IMAGE" >/dev/null +trap 'podman rm -f "$name" >/dev/null' EXIT +for _ in $(seq 1 60); do + state=$(podman exec "$name" systemctl is-system-running 2>/dev/null || true) + case "$state" in running|degraded) break ;; esac + sleep 1 +done +podman cp tests/smoke-test.sh "$name:/tmp/smoke-test.sh" +podman exec "$name" bash /tmp/smoke-test.sh