From 64ce80ecc3dcb416d9bee526b35d9b7fc6418883 Mon Sep 17 00:00:00 2001 From: Jeeva Kandasamy Date: Fri, 18 Sep 2026 07:51:49 +0530 Subject: [PATCH] run the whole release on one runner and reject a source UI pack Split jobs only existed to pass artifacts between VMs. That overwrite web-console/build with the Vite source index.html and shipped a binary whose UI never loaded. Build, pack, release, and push images on the same host. Fail pack if index.html still points at /src/index.js. --- .github/workflows/release.yaml | 316 ++++----------------------- CHANGELOG.md | 2 +- scripts/cmd/pack_web_console/main.go | 11 +- scripts/publish_container_images.sh | 20 +- 4 files changed, 73 insertions(+), 276 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 394ab354..521a72ed 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -20,12 +20,14 @@ env: YARN_VERSION: "4.18.0" jobs: - build-console: - name: Build web console + release: + name: Release runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v7 + with: + fetch-depth: 0 - name: Enable yarn run: corepack enable && corepack prepare yarn@${{ env.YARN_VERSION }} --activate @@ -40,52 +42,16 @@ jobs: - name: Build web console run: ./scripts/build_web_console.sh - - name: Upload web console - uses: actions/upload-artifact@v6 - with: - name: web-console - path: web-console/build - if-no-files-found: error - - build-binaries: - name: Build binaries - needs: build-console - runs-on: ubuntu-latest - outputs: - version: ${{ steps.meta.outputs.version }} - steps: - - name: Checkout - uses: actions/checkout@v7 - with: - fetch-depth: 0 - - name: Set up Go uses: actions/setup-go@v7 with: go-version: ${{ env.GO_VERSION }} check-latest: true - - name: Download web console - uses: actions/download-artifact@v6 - with: - name: web-console - path: web-console/build - - - name: Verify web console + - name: Build binaries run: | - # upload-artifact stores the contents of web-console/build at the zip root - if [ -f web-console/index.html ]; then - mkdir -p web-console/build - find web-console -mindepth 1 -maxdepth 1 ! -name build -exec mv {} web-console/build/ \; - fi - if [ ! -f web-console/build/index.html ] && [ -f web-console/build/build/index.html ]; then - mv web-console/build/build/* web-console/build/ - rmdir web-console/build/build - fi test -f web-console/build/index.html - - - name: Build binaries - run: ./scripts/generate_executables.sh + ./scripts/generate_executables.sh - name: Set version and checksums id: meta @@ -98,246 +64,60 @@ jobs: sha256sum *.tar.gz > ./SHA256SUMS.txt sha256sum *.zip >> ./SHA256SUMS.txt - - name: Upload archives - uses: actions/upload-artifact@v6 - with: - name: archives - path: | - builds/*.tar.gz - builds/*.zip - builds/build_timestamp.txt - builds/SHA256SUMS.txt - if-no-files-found: error - - - name: Upload container binaries - uses: actions/upload-artifact@v6 - with: - name: container-binaries - path: builds/binary - if-no-files-found: error - - github-release: - name: GitHub release - needs: build-binaries - if: > - (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')) && - github.repository == 'mycontroller-org/server' - runs-on: ubuntu-latest - steps: - - name: Download archives - uses: actions/download-artifact@v6 - with: - name: archives - path: . - - - name: Verify archives - run: | - if [ -f SHA256SUMS.txt ]; then - mkdir -p builds - mv ./*.tar.gz ./*.zip ./build_timestamp.txt ./SHA256SUMS.txt builds/ 2>/dev/null || true - fi - if [ ! -f builds/SHA256SUMS.txt ] && [ -f builds/builds/SHA256SUMS.txt ]; then - mv builds/builds/* builds/ - rmdir builds/builds - fi - test -f builds/SHA256SUMS.txt - ls builds/*.tar.gz builds/*.zip - - - name: Release (tagged version) - if: startsWith(github.ref, 'refs/tags/') - uses: softprops/action-gh-release@v3 - env: - GITHUB_TOKEN: ${{ secrets.GH_TOKEN || github.token }} - with: - fail_on_unmatched_files: true - name: ${{ github.ref_name }} - files: | - builds/*.tar.gz - builds/*.zip - builds/build_timestamp.txt - builds/SHA256SUMS.txt - - - name: Delete existing development release - if: github.ref == 'refs/heads/main' + - name: GitHub release (tagged version) + if: > + startsWith(github.ref, 'refs/tags/') && + github.repository == 'mycontroller-org/server' env: GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }} run: | - gh release delete development --yes || true - gh api --method DELETE "/repos/${GITHUB_REPOSITORY}/git/refs/tags/development" || true + gh release create "${GITHUB_REF_NAME}" \ + --title "${GITHUB_REF_NAME}" \ + --target "${GITHUB_SHA}" \ + builds/*.tar.gz builds/*.zip builds/build_timestamp.txt builds/SHA256SUMS.txt - - name: Release (development rolling) - if: github.ref == 'refs/heads/main' - uses: softprops/action-gh-release@v3 - env: - GITHUB_TOKEN: ${{ secrets.GH_TOKEN || github.token }} - with: - fail_on_unmatched_files: true - tag_name: development - target_commitish: ${{ github.sha }} - name: Development Build - Pre Release - prerelease: true - make_latest: false - body: | - Rolling build from `main` at ${{ github.sha }}. - Not guaranteed to be stable. Use a tagged release for production. - files: | - builds/*.tar.gz - builds/*.zip - builds/build_timestamp.txt - builds/SHA256SUMS.txt - - - name: Point development tag at this commit - if: github.ref == 'refs/heads/main' + - name: GitHub release (development rolling) + if: github.ref == 'refs/heads/main' && github.repository == 'mycontroller-org/server' env: GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }} run: | - # GitHub does not move an existing tag via target_commitish. - if gh api "/repos/${GITHUB_REPOSITORY}/git/refs/tags/development" >/dev/null 2>&1; then - gh api --method PATCH "/repos/${GITHUB_REPOSITORY}/git/refs/tags/development" \ - -f sha="${GITHUB_SHA}" \ - -F force=true - else - gh api --method POST "/repos/${GITHUB_REPOSITORY}/git/refs" \ - -f ref="refs/tags/development" \ - -f sha="${GITHUB_SHA}" - fi - - container-images: - name: Container image - needs: build-binaries - if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - component: [server, gateway, handler] - target: - - { platform: linux/amd64, binary_dir: linux-amd64 } - - { platform: linux/arm64, binary_dir: linux-arm64 } - - { platform: linux/arm/v7, binary_dir: linux-armv7 } - - { platform: linux/arm/v6, binary_dir: linux-armv6 } - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Download container binaries - uses: actions/download-artifact@v6 - with: - name: container-binaries - path: builds - - - name: Verify container binary - run: | - if [ ! -d builds/binary ] && [ -d builds/linux-amd64 ]; then - mkdir -p builds/binary - mv builds/linux-* builds/binary/ - fi - test -f "builds/binary/${{ matrix.target.binary_dir }}/mycontroller-${{ matrix.component }}" + gh release delete development --yes || true + gh api --method DELETE "/repos/${GITHUB_REPOSITORY}/git/refs/tags/development" || true + gh release create development \ + --prerelease \ + --title "Development Build - Pre Release" \ + --target "${GITHUB_SHA}" \ + --notes "Rolling build from \`main\` at ${GITHUB_SHA}. Not guaranteed to be stable. Use a tagged release for production." \ + builds/*.tar.gz builds/*.zip builds/build_timestamp.txt builds/SHA256SUMS.txt + gh api --method PATCH "/repos/${GITHUB_REPOSITORY}/git/refs/tags/development" \ + -f sha="${GITHUB_SHA}" \ + -F force=true - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 - - - name: Login to Docker Hub - if: github.repository == 'mycontroller-org/server' - uses: docker/login-action@v4 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_TOKEN }} + if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') + run: docker buildx create --use --name mc-builder --driver docker-container - - name: Login to Quay - if: github.repository == 'mycontroller-org/server' - uses: docker/login-action@v4 - with: - registry: quay.io - username: ${{ secrets.QUAY_USERNAME }} - password: ${{ secrets.QUAY_TOKEN }} - - - name: Login to GHCR - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Image tags - id: tags + - name: Login to container registries + if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') env: - VERSION: ${{ needs.build-binaries.outputs.version }} - COMPONENT: ${{ matrix.component }} - BINARY_DIR: ${{ matrix.target.binary_dir }} - OWNER: ${{ github.repository_owner }} + GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }} + QUAY_USERNAME: ${{ secrets.QUAY_USERNAME }} + QUAY_TOKEN: ${{ secrets.QUAY_TOKEN }} run: | - { - echo 'list<> "$GITHUB_OUTPUT" - - - name: Build and push image - uses: docker/build-push-action@v6 - with: - context: . - file: docker/${{ matrix.component }}.Dockerfile - platforms: ${{ matrix.target.platform }} - push: true - provenance: false - sbom: false - build-args: | - BINARY_PATH=builds/binary/${{ matrix.target.binary_dir }}/mycontroller-${{ matrix.component }} - tags: ${{ steps.tags.outputs.list }} - - container-manifests: - name: Container manifests - needs: [build-binaries, container-images] - if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - component: [server, gateway, handler] - registry: - - quay.io/mycontroller - - docker.io/mycontroller - - ghcr.io/${{ github.repository_owner }} - steps: - - name: Login to Docker Hub - if: github.repository == 'mycontroller-org/server' && startsWith(matrix.registry, 'docker.io/') - uses: docker/login-action@v4 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_TOKEN }} - - - name: Login to Quay - if: github.repository == 'mycontroller-org/server' && startsWith(matrix.registry, 'quay.io/') - uses: docker/login-action@v4 - with: - registry: quay.io - username: ${{ secrets.QUAY_USERNAME }} - password: ${{ secrets.QUAY_TOKEN }} - - - name: Login to GHCR - if: startsWith(matrix.registry, 'ghcr.io/') - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 + echo "${GHCR_TOKEN}" | docker login ghcr.io -u "${GITHUB_ACTOR}" --password-stdin + if [ "${GITHUB_REPOSITORY}" = "mycontroller-org/server" ]; then + echo "${DOCKER_TOKEN}" | docker login docker.io -u "${DOCKER_USERNAME}" --password-stdin + echo "${QUAY_TOKEN}" | docker login quay.io -u "${QUAY_USERNAME}" --password-stdin + fi - - name: Create multi-arch manifest - if: github.repository == 'mycontroller-org/server' || startsWith(matrix.registry, 'ghcr.io/') + - name: Push container images + if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') env: - IMAGE: ${{ matrix.registry }}/${{ matrix.component }}:${{ needs.build-binaries.outputs.version }} + PUSH_DOCKER_QUAY: ${{ github.repository == 'mycontroller-org/server' && '1' || '0' }} run: | - docker buildx imagetools create \ - --tag "${IMAGE}" \ - "${IMAGE}-linux-amd64" \ - "${IMAGE}-linux-arm64" \ - "${IMAGE}-linux-armv7" \ - "${IMAGE}-linux-armv6" + for TARGET_BUILD in server gateway handler; do + export TARGET_BUILD + ./scripts/publish_container_images.sh + done diff --git a/CHANGELOG.md b/CHANGELOG.md index 065acafe..44c29828 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,7 @@ Draft for **2.3.0** (`versions.txt`). Compared to [v2.2.0](https://github.com/my ### Changed -- Release workflow builds host binaries (Go 1.27.1 in Actions) and copies only those binaries into Alpine 3.24 images. Images are pushed to Docker Hub, Quay, and GHCR. `main` republishes the `development` pre-release by deleting and recreating it. +- Release workflow builds host binaries (Go 1.27.1 in Actions) and copies only those binaries into Alpine 3.24 images on the same runner (no artifact upload/download). Images are pushed to GHCR, Quay, and Docker Hub. `main` republishes the `development` pre-release by deleting and recreating it. - Policy **Import** uses `storage.Upsert` so `createdOn` / `modifiedOn` come from the backup file (same as other resources). Built-in policies are still skipped. - Built-in policies (admin / readwrite / readonly) are no longer rewritten on every start. `ModifiedOn` stays unless the code definition changed. Existing rows without `CreatedOn` are backfilled from `ModifiedOn` once. - `make setup-release` now requires both versions: `make setup-release VERSION=x.y.z NEXT_VERSION=x.y.z`. The PR writes `NEXT_VERSION` into `versions.txt`. diff --git a/scripts/cmd/pack_web_console/main.go b/scripts/cmd/pack_web_console/main.go index 9d5edf84..723332b3 100644 --- a/scripts/cmd/pack_web_console/main.go +++ b/scripts/cmd/pack_web_console/main.go @@ -2,6 +2,7 @@ package main import ( "archive/zip" + "bytes" "fmt" "io" "io/fs" @@ -17,9 +18,17 @@ func main() { } src := filepath.Join(root, "web-console", "build") - if _, err := os.Stat(filepath.Join(src, "index.html")); err != nil { + indexPath := filepath.Join(src, "index.html") + if _, err := os.Stat(indexPath); err != nil { fatal(fmt.Errorf("web-console/build/index.html not found; run ./scripts/build_web_console.sh first: %w", err)) } + indexHTML, err := os.ReadFile(indexPath) + if err != nil { + fatal(err) + } + if bytes.Contains(indexHTML, []byte("/src/index.js")) { + fatal(fmt.Errorf("web-console/build/index.html is the Vite source page, not a production build")) + } destDir := filepath.Join(root, "pkg", "http_router", "web-console", "assets") if err := os.MkdirAll(destDir, 0o755); err != nil { diff --git a/scripts/publish_container_images.sh b/scripts/publish_container_images.sh index bb323710..612bf9af 100755 --- a/scripts/publish_container_images.sh +++ b/scripts/publish_container_images.sh @@ -7,12 +7,15 @@ set -euo pipefail source ./scripts/version.sh -REGISTRY='quay.io/mycontroller' -ALT_REGISTRY='docker.io/mycontroller' GHCR_REGISTRY="ghcr.io/${GITHUB_REPOSITORY_OWNER:-mycontroller-org}" IMAGE_TAG=${VERSION} TARGET_BINARY=${TARGET_BUILD:-server} +REGISTRIES=("${GHCR_REGISTRY}") +if [ "${PUSH_DOCKER_QUAY:-0}" = "1" ]; then + REGISTRIES+=("quay.io/mycontroller" "docker.io/mycontroller") +fi + PLATFORMS=( "linux/amd64:linux-amd64" "linux/arm64:linux-arm64" @@ -29,18 +32,23 @@ for spec in "${PLATFORMS[@]}"; do exit 1 fi + tags=() + for registry in "${REGISTRIES[@]}"; do + tags+=(--tag "${registry}/${TARGET_BINARY}:${IMAGE_TAG}-${binary_dir}") + done + docker buildx build --push \ --progress=plain \ + --provenance=false \ + --sbom=false \ --platform "${platform}" \ --file "docker/${TARGET_BINARY}.Dockerfile" \ --build-arg "BINARY_PATH=${binary_path}" \ - --tag "${REGISTRY}/${TARGET_BINARY}:${IMAGE_TAG}-${binary_dir}" \ - --tag "${ALT_REGISTRY}/${TARGET_BINARY}:${IMAGE_TAG}-${binary_dir}" \ - --tag "${GHCR_REGISTRY}/${TARGET_BINARY}:${IMAGE_TAG}-${binary_dir}" \ + "${tags[@]}" \ . done -for registry in "${REGISTRY}" "${ALT_REGISTRY}" "${GHCR_REGISTRY}"; do +for registry in "${REGISTRIES[@]}"; do docker buildx imagetools create \ --tag "${registry}/${TARGET_BINARY}:${IMAGE_TAG}" \ "${registry}/${TARGET_BINARY}:${IMAGE_TAG}-linux-amd64" \