Primitives for qemu data disk reuse #98
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| # Consolidated pipeline per RELEASE_PIPELINE_PLAN.md. | |
| # | |
| # One workflow, one `build` matrix, one source of truth for binaries. | |
| # Every consumer (e2e-serve, image, release-assets) pulls from the | |
| # uploaded artifacts so a tag run ships the same bytes everywhere. | |
| # | |
| # Triggers: | |
| # push main → lint, test, build, e2e-serve, image(sha) | |
| # push tag v* → all of the above + image(tag) + release-assets | |
| # pull_request → lint, test, build, e2e-serve only | |
| # workflow_dispatch → same as push main | |
| # | |
| # Image is only published under Yolean (not YoleanAgents) — the `if:` | |
| # on the image/release-assets jobs is belt-and-braces; the human owner | |
| # mirrors manually when ready to open-source. | |
| on: | |
| push: | |
| branches: [main] | |
| tags: ['v*'] | |
| pull_request: | |
| workflow_dispatch: | |
| jobs: | |
| # --- PR and main checks --- | |
| lint: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 | |
| with: | |
| go-version-file: go.mod | |
| cache: true | |
| - uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0 | |
| with: | |
| version: v2.11.4 | |
| args: --timeout=5m | |
| test: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 | |
| with: | |
| go-version-file: go.mod | |
| cache: true | |
| - run: go test -count=1 ./... | |
| - run: go vet ./... | |
| e2e: | |
| # Runs every test gated by `//go:build e2e` (kwok harness) | |
| # plus the `//go:build e2e && docker` matrix (docker | |
| # provisioner -- the k3s-in-docker airgap proof). Build tags | |
| # compose, so a single `go test` call covers both. The kvm | |
| # tag is intentionally omitted: github-hosted runners don't | |
| # expose /dev/kvm, and adding it would force a self-hosted | |
| # runner. Local devs run the kvm leg via test.sh. | |
| # | |
| # Gated on `test` so a broken unit suite short-circuits this | |
| # ~5-minute job. | |
| runs-on: ubuntu-latest | |
| needs: test | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 | |
| with: | |
| go-version-file: go.mod | |
| cache: true | |
| - run: go test -tags "e2e,docker" -count=1 -timeout=20m ./e2e/ | |
| # No e2e-multipass job here. The Linux multipass install path is | |
| # snap-only, and snapcraft.io reachability from ubuntu-latest is | |
| # too flaky to gate releases on (multiple consecutive runs on | |
| # 2026-05-01 failed every retry against the snap store). The | |
| # //go:build e2e && multipass test in e2e/multipass_test.go still | |
| # exists and runs locally via: | |
| # | |
| # go test -tags 'e2e,multipass' -count=1 -timeout=30m ./e2e/ | |
| # | |
| # so a developer or self-hosted runner with multipass already | |
| # installed can verify the multipass provisioner end-to-end. | |
| # The e2e (docker) job above is the release/image gate. | |
| generate-drift: | |
| # `go generate` regenerates the provisioner schema files from | |
| # struct tags + pkg/provision/config/k3s.yaml. If the | |
| # working tree differs after a fresh generate, the committed | |
| # schemas have drifted from their source -- fail loudly so the | |
| # PR carries the regenerated files. | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 | |
| with: | |
| go-version-file: go.mod | |
| cache: true | |
| - run: go generate ./... | |
| - name: Fail if generate produced changes | |
| run: | | |
| if ! git diff --exit-code; then | |
| echo "::error::go generate produced changes; commit the regenerated files." | |
| exit 1 | |
| fi | |
| # --- Single source of truth for y-cluster binaries --- | |
| build: | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - goos: linux | |
| goarch: amd64 | |
| - goos: linux | |
| goarch: arm64 | |
| - goos: darwin | |
| goarch: amd64 | |
| - goos: darwin | |
| goarch: arm64 | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 | |
| with: | |
| go-version-file: go.mod | |
| cache: true | |
| - name: Build y-cluster | |
| env: | |
| CGO_ENABLED: "0" | |
| GOOS: ${{ matrix.goos }} | |
| GOARCH: ${{ matrix.goarch }} | |
| run: | | |
| mkdir -p cmd/y-cluster/target/${GOOS}/${GOARCH} | |
| go build -trimpath \ | |
| -ldflags "-s -w -X main.version=${GITHUB_REF_NAME}" \ | |
| -o cmd/y-cluster/target/${GOOS}/${GOARCH}/y-cluster \ | |
| ./cmd/y-cluster | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: y-cluster-${{ matrix.goos }}-${{ matrix.goarch }} | |
| path: cmd/y-cluster/target/${{ matrix.goos }}/${{ matrix.goarch }}/y-cluster | |
| retention-days: 7 | |
| if-no-files-found: error | |
| # --- E2e against the freshly-built linux/amd64 binary --- | |
| e2e-serve: | |
| runs-on: ubuntu-latest | |
| needs: build | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: y-cluster-linux-amd64 | |
| path: bin | |
| - name: Run serve e2e against the built binary | |
| env: | |
| Y_CLUSTER_BIN: ${{ github.workspace }}/bin/y-cluster | |
| run: | | |
| chmod +x "$Y_CLUSTER_BIN" | |
| ./scripts/e2e-serve-against-binary.sh | |
| # --- Multi-arch image via contain, blocked on all prior jobs --- | |
| image: | |
| runs-on: ubuntu-latest | |
| needs: | |
| - lint | |
| - test | |
| - e2e | |
| - build | |
| - e2e-serve | |
| if: github.repository_owner == 'Yolean' && github.event_name != 'pull_request' | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Download linux/amd64 binary | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: y-cluster-linux-amd64 | |
| path: cmd/y-cluster/target/linux/amd64 | |
| - name: Download linux/arm64 binary | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: y-cluster-linux-arm64 | |
| path: cmd/y-cluster/target/linux/arm64 | |
| - uses: solsson/setup-contain@49cc4cce1498df8a59e88fff52c1a9b747f11f08 # v1 | |
| with: | |
| version: v0.9.0 | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.repository_owner }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Build and push image (SHA tag) | |
| working-directory: cmd/y-cluster | |
| env: | |
| IMAGE: ghcr.io/yolean/y-cluster:${{ github.sha }} | |
| run: contain build | |
| - name: Build and push image (release tag) | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| working-directory: cmd/y-cluster | |
| env: | |
| IMAGE: ghcr.io/yolean/y-cluster:${{ github.ref_name }} | |
| run: contain build | |
| # --- Release assets on tag push: raw binaries, no compression --- | |
| release-assets: | |
| runs-on: ubuntu-latest | |
| needs: image | |
| if: startsWith(github.ref, 'refs/tags/v') && github.repository_owner == 'Yolean' | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| pattern: y-cluster-* | |
| path: downloaded | |
| - name: Rename binaries (goreleaser naming) and checksum | |
| id: assets | |
| run: | | |
| set -euo pipefail | |
| tag="${GITHUB_REF_NAME}" | |
| mkdir -p release | |
| for d in downloaded/y-cluster-*; do | |
| # Each artifact dir contains a single `y-cluster` binary. | |
| base=$(basename "$d") # y-cluster-linux-amd64 | |
| suffix=${base#y-cluster-} # linux-amd64 | |
| os=${suffix%-*} # linux | |
| arch=${suffix#*-} # amd64 | |
| cp "$d/y-cluster" "release/y-cluster_${tag}_${os}_${arch}" | |
| chmod +x "release/y-cluster_${tag}_${os}_${arch}" | |
| done | |
| (cd release && sha256sum y-cluster_${tag}_* > "y-cluster_${tag}_checksums.txt") | |
| ls -l release/ | |
| - name: Publish release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| tag="${GITHUB_REF_NAME}" | |
| # Idempotent on re-runs and race-free with the | |
| # `release: published` event that e2e-release.yaml | |
| # listens for: | |
| # - First run for a tag: `gh release create ... <files>` | |
| # uploads atomically, so publish fires only after the | |
| # assets are present. The previous shape called | |
| # `release create` (empty publish, fires the event) | |
| # THEN `release upload`; the event-driven validator | |
| # raced in and saw an empty release ("no assets to | |
| # download"). | |
| # - Re-run for an existing tag: skip create, use | |
| # `release upload --clobber` to refresh the assets | |
| # without retriggering publish. | |
| if gh release view "$tag" >/dev/null 2>&1; then | |
| gh release upload "$tag" release/y-cluster_${tag}_* --clobber | |
| else | |
| gh release create "$tag" --title "$tag" --generate-notes --verify-tag \ | |
| release/y-cluster_${tag}_* | |
| fi |