From d36121e673608390dea80fad4604dd7cb8adb7ef Mon Sep 17 00:00:00 2001 From: Kevin Riehl Date: Wed, 13 May 2026 12:14:56 -0700 Subject: [PATCH] fix(ci): replace flaky docker/login-action, add cosign and tag back MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors the backend push-job rewrite: - Replace docker/login-action@v3 (fast 1s failure on the Gitea runner) with plain `docker login --password-stdin`. - Read VERSION from package.json (jq), SHA_SHORT from git, publish :VERSION, :SHA_SHORT, :latest. Drop floating :1.2, :1 tags. - Pre-check Harbor for an existing :VERSION artifact and fail early with a "bump package.json" message instead of overwriting silently. - Sign each pushed image with cosign. Signature is on the digest (resolved via :SHA_SHORT), so it covers all tags pointing at it. - After a successful image push, create and push a `v${VERSION}` git tag back to origin using the auto-injected token. Tag-push failure is a warning, not an error. - Drop `tags: ["v*"]` from the workflow trigger — CI now creates the tag itself, so re-triggering on tag push would loop. Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitea/workflows/ci.yml | 116 ++++++++++++++++++++++++++++++---------- 1 file changed, 89 insertions(+), 27 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 9bd3838..7a6f12c 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -3,12 +3,8 @@ name: frontend-ci on: push: branches: ["**"] - tags: ["v*"] pull_request: -env: - IMAGE: ${{ secrets.HARBOR_HOST }}/movieloop/frontend - jobs: lint: runs-on: ubuntu-latest @@ -90,29 +86,95 @@ jobs: push: runs-on: ubuntu-latest needs: [build, secrets-scan, sast, fs-scan] - if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + env: + HARBOR_HOST: ${{ secrets.HARBOR_HOST }} + HARBOR_PROJECT: movieloop + IMAGE_NAME: frontend steps: - uses: actions/checkout@v4 + with: + # Need full history so the back-pushed git tag can be created against + # the right commit, and so token-auth on push to origin works. + fetch-depth: 0 - uses: docker/setup-buildx-action@v3 - - uses: docker/login-action@v3 - with: - registry: ${{ secrets.HARBOR_HOST }} - username: ${{ secrets.MOVIELOOP_USERNAME }} - password: ${{ secrets.MOVIELOOP_PASSWORD }} - - uses: docker/metadata-action@v5 - id: meta - with: - images: ${{ env.IMAGE }} - tags: | - type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} - type=sha,format=long - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{major}} - - uses: docker/build-push-action@v5 - with: - context: . - file: Dockerfile - target: production - push: true - tags: ${{ steps.meta.outputs.tags }} + + - name: Compute tag inputs + run: | + SHA_SHORT=$(git rev-parse --short HEAD) + VERSION=$(jq -r .version package.json) + echo "SHA_SHORT=${SHA_SHORT}" >> "$GITHUB_ENV" + echo "VERSION=${VERSION}" >> "$GITHUB_ENV" + + - name: Refuse to overwrite an existing version tag in Harbor + env: + HARBOR_USERNAME: ${{ secrets.MOVIELOOP_USERNAME }} + HARBOR_PASSWORD: ${{ secrets.MOVIELOOP_PASSWORD }} + run: | + set -eu + url="https://${HARBOR_HOST}/api/v2.0/projects/${HARBOR_PROJECT}/repositories/${IMAGE_NAME}/artifacts/${VERSION}/tags" + code=$(curl -s -o /dev/null -w "%{http_code}" -u "${HARBOR_USERNAME}:${HARBOR_PASSWORD}" "${url}") + if [ "$code" = "200" ]; then + echo "::error::Tag ${HARBOR_PROJECT}/${IMAGE_NAME}:${VERSION} already exists in Harbor. Bump package.json before merging." + exit 1 + fi + if [ "$code" != "404" ]; then + echo "::warning::Unexpected status ${code} checking ${url} — proceeding." + fi + + - name: Log in to Harbor + env: + HARBOR_USERNAME: ${{ secrets.MOVIELOOP_USERNAME }} + HARBOR_PASSWORD: ${{ secrets.MOVIELOOP_PASSWORD }} + run: | + echo "${HARBOR_PASSWORD}" | docker login "${HARBOR_HOST}" -u "${HARBOR_USERNAME}" --password-stdin + + - name: Build, tag, and push image + run: | + set -eu + REPO="${HARBOR_HOST}/${HARBOR_PROJECT}/${IMAGE_NAME}" + docker buildx build --load --target production -t movieloop-${IMAGE_NAME}:${SHA_SHORT} -f Dockerfile . + docker tag movieloop-${IMAGE_NAME}:${SHA_SHORT} ${REPO}:${VERSION} + docker tag movieloop-${IMAGE_NAME}:${SHA_SHORT} ${REPO}:${SHA_SHORT} + docker tag movieloop-${IMAGE_NAME}:${SHA_SHORT} ${REPO}:latest + docker push ${REPO}:${VERSION} + docker push ${REPO}:${SHA_SHORT} + docker push ${REPO}:latest + + - name: Install cosign + run: | + curl -fsSL "https://github.com/sigstore/cosign/releases/download/v2.4.1/cosign-linux-amd64" \ + -o /usr/local/bin/cosign + chmod +x /usr/local/bin/cosign + cosign version + + - name: Sign image with cosign + env: + COSIGN_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} + COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} + run: | + set -eu + # Sign by SHA tag — cosign resolves to the underlying digest, so a + # single signature covers every tag pointing at the same digest + # (version, sha, latest). Harbor looks up signatures by digest. + # Reuses the docker login from earlier in this job for registry auth. + cosign sign --key env://COSIGN_KEY --yes \ + "${HARBOR_HOST}/${HARBOR_PROJECT}/${IMAGE_NAME}:${SHA_SHORT}" + + - name: Push back git tag + env: + # Auto-injected per-run token; has push permission to this repo. + GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -eu + git config user.name "gitea-actions[bot]" + git config user.email "gitea-actions[bot]@tehriehldeal.com" + git tag "v${VERSION}" -m "${IMAGE_NAME} v${VERSION}" 2>/dev/null || true + ORIGIN="$(git remote get-url origin | sed -E "s#https?://([^/]+)#https://${GITEA_TOKEN}@\1#")" + # A failed tag push shouldn't undo a successful image push — log + # and proceed so we don't poison subsequent retries. + git push "$ORIGIN" --tags || echo "::warning::Tag push to origin failed — image is pushed; create the tag manually if needed." + + - name: Log out of Harbor + if: always() + run: docker logout "${HARBOR_HOST}" || true