Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
198 lines
8.3 KiB
YAML
198 lines
8.3 KiB
YAML
on:
|
|
push:
|
|
branches:
|
|
- master
|
|
pull_request:
|
|
types: [opened, reopened, synchronize, ready_for_review]
|
|
branches:
|
|
- master
|
|
name: Push container
|
|
|
|
permissions:
|
|
contents: read
|
|
packages: write
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.ref }}
|
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
|
|
jobs:
|
|
buildx:
|
|
name: Push branches and PRs
|
|
runs-on: ubuntu-latest
|
|
if: ${{ !github.event.repository.fork && !github.event.pull_request.head.repo.fork && (github.event_name == 'push' || github.event.pull_request.head.repo.full_name == 'amir20/dozzle') }}
|
|
steps:
|
|
- name: Set up Docker Buildx
|
|
id: buildx
|
|
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
|
|
- name: Login to DockerHub
|
|
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
|
with:
|
|
username: ${{ secrets.DOCKER_USERNAME }}
|
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
|
- name: Log in to the Container registry
|
|
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
- name: Checkout
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
- name: Docker meta
|
|
id: meta
|
|
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
|
|
with:
|
|
images: |
|
|
amir20/dozzle
|
|
ghcr.io/amir20/dozzle
|
|
- name: Docker meta for alpine variant
|
|
id: meta-alpine
|
|
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
|
|
with:
|
|
images: |
|
|
amir20/dozzle
|
|
ghcr.io/amir20/dozzle
|
|
flavor: |
|
|
suffix=-alpine,onlatest=true
|
|
- name: Short SHA
|
|
id: sha
|
|
run: echo "short=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
|
|
- name: Writing certs to file
|
|
run: |
|
|
echo "${{ secrets.TTL_KEY }}" > shared_key.pem
|
|
echo "${{ secrets.TTL_CERT }}" > shared_cert.pem
|
|
# BuildKit cache mounts live in the builder's local state and cache-to:gha
|
|
# does not export them. Every run got a fresh builder, so /go/pkg/mod and
|
|
# /root/.cache/go-build started empty and the whole dependency tree was
|
|
# recompiled from scratch for both platforms, ~135s of a 3m30s build. Note
|
|
# `go mod download` looks CACHED in the log while leaving its mount empty,
|
|
# so the modules were re-fetched inside `go build` too.
|
|
#
|
|
# Both mounts default to id=<target>, so the amd64 and arm64 builds share
|
|
# one copy and a single injection reaches both. That is safe for Go (GOCACHE
|
|
# keys entries by GOOS/GOARCH, the module cache is just source) and would
|
|
# not be for apt/apk, which is why those need id=...-${TARGETARCH}.
|
|
- name: Restore Go caches
|
|
id: go-cache
|
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
with:
|
|
path: |
|
|
go-build-cache
|
|
go-mod-cache
|
|
# Keyed on go.sum with no run-unique suffix so a hit skips the save.
|
|
# Dependencies are the expensive part and they only move when go.sum
|
|
# does; recompiling dozzle's own packages each run costs a few seconds.
|
|
# A per-SHA key would instead upload the whole cache on every build.
|
|
key: go-docker-${{ hashFiles('go.sum') }}
|
|
restore-keys: go-docker-
|
|
- name: Inject Go caches into the builder
|
|
uses: reproducible-containers/buildkit-cache-dance@5422eac04292c961a382e0f584ea0f03ad9da723 # v3.4.0
|
|
with:
|
|
builder: ${{ steps.buildx.outputs.name }}
|
|
cache-map: |
|
|
{
|
|
"go-build-cache": "/root/.cache/go-build",
|
|
"go-mod-cache": "/go/pkg/mod"
|
|
}
|
|
# On a hit the mounts would be written back unchanged, so skip the
|
|
# extraction post-step that tars them back out of the builder.
|
|
skip-extraction: ${{ steps.go-cache.outputs.cache-hit == 'true' }}
|
|
- name: Build and push
|
|
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
|
with:
|
|
context: .
|
|
push: true
|
|
platforms: linux/amd64,linux/arm64/v8
|
|
tags: ${{ steps.meta.outputs.tags }}
|
|
build-args: TAG=${{ steps.meta.outputs.version }}-${{ steps.sha.outputs.short }}
|
|
labels: ${{ steps.meta.outputs.labels }}
|
|
cache-from: type=gha
|
|
cache-to: type=gha,mode=max
|
|
- name: Build and push alpine variant
|
|
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
|
with:
|
|
context: .
|
|
push: true
|
|
target: alpine
|
|
platforms: linux/amd64,linux/arm64/v8
|
|
tags: ${{ steps.meta-alpine.outputs.tags }}
|
|
build-args: TAG=${{ steps.meta.outputs.version }}-${{ steps.sha.outputs.short }}
|
|
labels: ${{ steps.meta-alpine.outputs.labels }}
|
|
# No cache-to. See the note in deploy.yml.
|
|
cache-from: type=gha
|
|
|
|
comment:
|
|
name: Comment image tag
|
|
needs: buildx
|
|
if: ${{ github.event_name == 'pull_request' }}
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
pull-requests: write
|
|
issues: write
|
|
steps:
|
|
- name: Post pr-${{ github.event.pull_request.number }} image tag
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
REPO: ${{ github.repository }}
|
|
PR: ${{ github.event.pull_request.number }}
|
|
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
|
IS_DRAFT: ${{ github.event.pull_request.draft }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
short_sha="${HEAD_SHA:0:7}"
|
|
pr_marker="<!-- dozzle-pr-image -->"
|
|
issue_marker="<!-- dozzle-pr-image:${PR} -->"
|
|
|
|
run_cmd="docker run -v /var/run/docker.sock:/var/run/docker.sock -p 8080:8080 amir20/dozzle:pr-${PR}"
|
|
|
|
pr_body=$(printf '%s\n%s\n\n```\n%s\n```\n\n%s\n' \
|
|
"$pr_marker" \
|
|
"Try this PR without waiting for a release:" \
|
|
"$run_cmd" \
|
|
"Also on \`ghcr.io/amir20/dozzle:pr-${PR}\` and as \`amir20/dozzle:pr-${PR}-alpine\`. Rebuilt on every push, currently \`${short_sha}\`.")
|
|
|
|
# Sticky comment on the PR itself.
|
|
existing=$(gh api "repos/${REPO}/issues/${PR}/comments" --paginate \
|
|
--jq ".[] | select(.body != null and (.body | contains(\"${pr_marker}\"))) | .id" | head -1)
|
|
if [ -n "$existing" ]; then
|
|
gh api -X PATCH "repos/${REPO}/issues/comments/${existing}" -f body="$pr_body" > /dev/null
|
|
else
|
|
gh api -X POST "repos/${REPO}/issues/${PR}/comments" -f body="$pr_body" > /dev/null
|
|
fi
|
|
|
|
if [ "$IS_DRAFT" = "true" ]; then
|
|
echo "Draft PR, skipping linked issues."
|
|
exit 0
|
|
fi
|
|
|
|
# One-time comment on every issue this PR closes.
|
|
linked=$(gh api "repos/${REPO}/pulls/${PR}" --jq '.body // ""' \
|
|
| grep -oiE '(close[sd]?|fix(e[sd])?|resolve[sd]?)[[:space:]]*:?[[:space:]]+#[0-9]+' \
|
|
| grep -oE '[0-9]+' | sort -u || true)
|
|
|
|
for issue in $linked; do
|
|
if [ "$issue" = "$PR" ]; then
|
|
continue
|
|
fi
|
|
kind=$(gh api "repos/${REPO}/issues/${issue}" --jq 'if .pull_request then "pr" else "issue" end' 2>/dev/null || echo "missing")
|
|
if [ "$kind" != "issue" ]; then
|
|
echo "#${issue} is not an issue, skipping."
|
|
continue
|
|
fi
|
|
already=$(gh api "repos/${REPO}/issues/${issue}/comments" --paginate \
|
|
--jq ".[] | select(.body != null and (.body | contains(\"${issue_marker}\"))) | .id" | head -1)
|
|
if [ -n "$already" ]; then
|
|
echo "#${issue} already has a pr-${PR} comment, skipping."
|
|
continue
|
|
fi
|
|
issue_body=$(printf '%s\n%s\n\n```\n%s\n```\n\n%s\n' \
|
|
"$issue_marker" \
|
|
"A fix for this is in #${PR}. CI builds an image per PR, so you can test it before the next release:" \
|
|
"$run_cmd" \
|
|
"Report back on #${PR} if it still happens.")
|
|
gh api -X POST "repos/${REPO}/issues/${issue}/comments" -f body="$issue_body" > /dev/null
|
|
echo "Commented on #${issue}."
|
|
done
|