mirror of
https://github.com/TheFunny/TelegramTwitterMediaBot.git
synced 2026-09-23 23:32:05 +00:00
Pushing master and a release tag fires two workflow runs, and `should-build` exists to keep only one of them building: a branch run skips when its commit is already tagged. It never worked — `actions/checkout` does not fetch tags (`fetch-tags` defaults to false, and `fetch-depth` does not imply it), so `git tag --points-at "$GITHUB_SHA"` came up empty and the master run built the same commit the tag run was building: two ~6 minute docker builds pushing the same image, for v1.9.0 and again for v1.9.1. The check step now fetches the tags itself, immediately before deciding, so the view is as fresh as it can be. Reproduced and fixed against this repo: a clone made the way the action makes it (`--no-tags`) reports "NO TAG -> build=true (duplicate build!)" for the tagged v1.9.1 commit, and the same clone after the step's `git fetch --tags --force origin` reports "tag(s): v1.9.1 -> build=false (skip)". A tag pushed *after* the branch run started cannot be anticipated, so the release flow is documented as one push (`git push origin master vX.Y.Z`) in both the workflow and AGENTS.md; pushing master first is exactly what made today's pair build twice. `cargo fmt --check`, `clippy`, the test suite and the workflow's YAML parse are all clean (workflow/docs only, no Rust changes).
159 lines
6.7 KiB
YAML
159 lines
6.7 KiB
YAML
name: Build Docker Image
|
|
|
|
# Release builds (master / v* tags) plus a build-only check on pull requests
|
|
# that touch anything the image depends on — the Dockerfile's stub-source
|
|
# machinery, the ffmpeg download and the entrypoint are exactly the parts that
|
|
# would otherwise break only at release time.
|
|
#
|
|
# Actions are pinned to commit SHAs (Dependabot keeps the pins current).
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- v*
|
|
branches:
|
|
- master
|
|
pull_request:
|
|
paths:
|
|
- Dockerfile
|
|
- docker-entrypoint.sh
|
|
- .dockerignore
|
|
- Cargo.toml
|
|
- Cargo.lock
|
|
- .github/workflows/docker.yml
|
|
- 'crates/**/Cargo.toml'
|
|
|
|
env:
|
|
APP_NAME: telegram-twitter-media-bot
|
|
DOCKERHUB_REPO: yoursfunny/telegram-twitter-media-bot
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
# Serialize runs per ref. Never cancel in progress: a killed run would drop a
|
|
# half-finished image push.
|
|
concurrency:
|
|
group: docker-${{ github.ref }}
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
# A tag push and a branch push to the same commit fire two workflow runs;
|
|
# build only once. Tag runs always build; master runs build only when the
|
|
# pushed commit is not already tagged (the tag run covers it) — which needs
|
|
# the tags to be present and to have been pushed by the time this runs, so
|
|
# `docker.yml`'s check step fetches them and the release flow pushes both
|
|
# refs together.
|
|
should-build:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
outputs:
|
|
build: ${{ steps.check.outputs.build }}
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
with:
|
|
fetch-depth: 0
|
|
# A release tag is the version claim: the manifests are bumped by hand,
|
|
# so `v1.5.1` with `Cargo.toml` still at 1.5.0 would publish an image
|
|
# whose tag lies about what is inside it (the binary carries no version).
|
|
- name: Verify the tag matches both crate versions
|
|
if: startsWith(github.ref, 'refs/tags/v')
|
|
shell: bash
|
|
run: |
|
|
tag="${GITHUB_REF_NAME#v}"
|
|
status=0
|
|
for manifest in crates/x-media/Cargo.toml crates/xmedia-bot/Cargo.toml; do
|
|
# tr -d '\r': a CRLF checkout (core.autocrlf on Windows) would
|
|
# otherwise yield "1.5.0\r" and false-fail every tag.
|
|
version="$(sed -n 's/^version = "\(.*\)"/\1/p' "$manifest" | head -1 | tr -d '\r')"
|
|
if [ "$version" != "$tag" ]; then
|
|
echo "::error file=$manifest::$manifest is at $version but the tag is v$tag"
|
|
status=1
|
|
else
|
|
echo "$manifest: $version matches v$tag"
|
|
fi
|
|
done
|
|
exit "$status"
|
|
- id: check
|
|
shell: bash
|
|
run: |
|
|
if [ "$GITHUB_REF_TYPE" = "branch" ]; then
|
|
# The check below is only as good as the tags in this clone, and
|
|
# `actions/checkout` does not fetch them (fetch-tags defaults to
|
|
# false, and fetch-depth does not imply it) — which is why the
|
|
# master run used to build the very commit the tag run was building
|
|
# in parallel. Fetched here, right before the decision, so a tag
|
|
# pushed moments ago is seen too. A tag pushed *after* this run
|
|
# started cannot be anticipated: push the branch and the tag
|
|
# together (`git push origin master v1.9.1`) or the tag first.
|
|
git fetch --tags --force --quiet origin
|
|
fi
|
|
if [ "$GITHUB_REF_TYPE" = "branch" ] && git tag --points-at "$GITHUB_SHA" | grep -q .; then
|
|
echo "commit already tagged; the tag run builds the image"
|
|
echo "build=false" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "build=true" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
# No `actions/checkout` here on purpose: `docker/build-push-action` defaults
|
|
# to the Git context (`https://github.com/<owner>/<repo>.git#<ref>`), so
|
|
# BuildKit clones the repo itself and authenticates with the automatic
|
|
# github.token. Adding `context: .` below without a checkout step would hand
|
|
# BuildKit an empty workspace.
|
|
docker:
|
|
needs: should-build
|
|
if: needs.should-build.outputs.build == 'true'
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 60
|
|
steps:
|
|
- name: Docker meta
|
|
id: meta
|
|
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6
|
|
with:
|
|
images: ${{ env.DOCKERHUB_REPO }}
|
|
tags: |
|
|
type=semver,pattern={{version}}
|
|
type=semver,pattern={{major}}.{{minor}}.{{patch}}
|
|
type=semver,pattern={{major}}.{{minor}}
|
|
type=semver,pattern={{major}}
|
|
type=sha
|
|
-
|
|
name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4
|
|
# Pull requests build the image to prove the Dockerfile still works, but
|
|
# must not read registry credentials (fork PRs have none).
|
|
-
|
|
name: Login to Docker Hub
|
|
if: github.event_name != 'pull_request'
|
|
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
|
|
with:
|
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
# Buildkit cache via the GitHub Actions cache backend (uses the
|
|
# automatic GITHUB_TOKEN, no extra secrets). mode=max keeps every
|
|
# stage's layers so the cargo-deps and ffmpeg layers are restored
|
|
# instead of re-downloaded/recompiled. The scope must be pinned to a
|
|
# fixed string: the gha backend defaults to the current git ref, which
|
|
# would give every new tag a cold cache on release builds. PR runs only
|
|
# read it (cache-to is empty) so they cannot evict the release cache.
|
|
#
|
|
# FFMPEG_URL/FFMPEG_SHA256 come from repository variables when set, so a
|
|
# release can pin an exact ffmpeg build (the Dockerfile default follows
|
|
# the project's `/redirect/latest/` URL, which has no sha256 sidecar).
|
|
#
|
|
# Single-arch (amd64) on purpose: adding arm64 means re-adding
|
|
# `docker/setup-qemu-action`, `platforms: linux/amd64,linux/arm64`, and
|
|
# parameterizing FFMPEG_URL by $TARGETARCH in the Dockerfile.
|
|
-
|
|
name: Build and push
|
|
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7
|
|
with:
|
|
push: ${{ github.event_name != 'pull_request' }}
|
|
build-args: |
|
|
APP_NAME=${{ env.APP_NAME }}
|
|
FFMPEG_URL=${{ vars.FFMPEG_URL || 'https://ffmpeg.martin-riedl.de/redirect/latest/linux/amd64/release/ffmpeg.zip' }}
|
|
FFMPEG_SHA256=${{ vars.FFMPEG_SHA256 }}
|
|
tags: ${{ steps.meta.outputs.tags }}
|
|
labels: ${{ steps.meta.outputs.labels }}
|
|
cache-from: type=gha,scope=tgxmb-build
|
|
cache-to: ${{ github.event_name != 'pull_request' && 'type=gha,mode=max,scope=tgxmb-build' || '' }}
|