Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion .github/workflows/action_publish-images-beta.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,21 @@ jobs:
with:
ref: ${{ needs.release.outputs.tag }}

build:
# PHP compiled from php.net, so a release never waits for Docker Hub.
base:
needs: [release, setup]
uses: ./.github/workflows/service_build-base-images.yml
permissions:
contents: read
packages: write
id-token: write
with:
matrix: ${{ needs.setup.outputs.matrix }}
image-prefix: "ghcr.io/serversideup/php-base:"
ref: ${{ needs.release.outputs.tag }}

build:
needs: [release, setup, base]
name: build ${{ matrix.variation }}
strategy:
fail-fast: false
Expand All @@ -65,14 +78,18 @@ jobs:
permissions:
contents: read
id-token: write
packages: read
uses: ./.github/workflows/service_build-images.yml
with:
variation: ${{ matrix.variation }}
matrix: ${{ needs.setup.outputs.matrix }}
ref: ${{ needs.release.outputs.tag }}
registry-repositories: "docker.io/serversideup/php,ghcr.io/serversideup/php"
base-images: ${{ needs.base.outputs.base-images }}
tag-prefix: "beta"
release-type: "testing"
# Only the prerelease that fired this gets release tags. Rebuilds refresh the beta floating tags.
release-tag: ${{ github.event_name == 'release' && needs.release.outputs.tag || '' }}
secrets: inherit

test:
Expand Down
126 changes: 106 additions & 20 deletions .github/workflows/action_publish-images-production.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
name: Docker Publish (Production Images)
run-name: >-
Production images (${{
github.event.release.tag_name ||
(github.ref_type == 'tag' && github.ref_name) ||
(github.event_name == 'schedule' && 'weekly rebuild') ||
'manual rebuild'
}})

# Releasing: write the notes as a draft release (optional), then push the version tag from the
# branch to release. The images are built, tested, and published before the release is, because
# publishing a release locks its tag. If the build fails, delete the tag, fix it, and push it again.
on:
workflow_dispatch:
release:
types: [released]
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
schedule:
- cron: '0 8 * * 2'

# A release event and the weekly rebuild must not publish the same tags at the same time.
# A release and the weekly rebuild must not publish the same tags at the same time.
concurrency:
group: production-images

Expand All @@ -27,36 +31,96 @@ jobs:
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
contents: read
contents: write
outputs:
tag: ${{ steps.release.outputs.tag }}
ref: ${{ steps.release.outputs.ref }}
release-tag: ${{ steps.release.outputs.release-tag }}
floating-tags: ${{ steps.release.outputs.floating-tags }}
publish-release: ${{ steps.release.outputs.publish-release }}
steps:
# A release event builds the release that fired it. Scheduled and manual runs rebuild the latest release.
- name: Find the release to build
id: release
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
PUSHED_TAG: ${{ github.ref_type == 'tag' && github.ref_name || '' }}
run: |
TAG="${RELEASE_TAG:-$(gh release view --json tagName --jq .tagName)}"
if [ -z "$TAG" ] || [ "$TAG" = "null" ]; then
echo "::error title=No release found::Could not find a published release to build."
exit 1
# GitHub's "Latest" label can't decide this: a pushed release isn't published yet.
published_releases() {
gh release list --exclude-drafts --exclude-pre-releases --limit 100 --json tagName --jq '.[].tagName' \
| grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$'
}

if [ -z "$PUSHED_TAG" ]; then
TAG=$(published_releases | sort -V | tail -n1)
if [ -z "$TAG" ]; then
echo "::error title=No release found::Could not find a published release to build."
exit 1
fi
echo "Rebuilding release $TAG"
{
echo "ref=$TAG"
echo "release-tag="
echo "floating-tags=true"
echo "publish-release=false"
} >> "$GITHUB_OUTPUT"
exit 0
fi
echo "Building release $TAG"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"

SORTED=$( (published_releases; echo "$PUSHED_TAG") | sort -uV)
NEWEST=$(echo "$SORTED" | tail -n1)
PREVIOUS=$(echo "$SORTED" | grep -B1 -xF "$PUSHED_TAG" | grep -vxF "$PUSHED_TAG" || true)

# Publishing a release in the GitHub UI also pushes its tag. Its images still get built.
if published_releases | grep -xF "$PUSHED_TAG" > /dev/null; then
echo "::warning title=Release already published::$PUSHED_TAG was published before its images were built, so its tag is locked even if this build fails. Next time, push the tag instead."
PUBLISH_RELEASE=false
elif gh release view "$PUSHED_TAG" > /dev/null 2>&1; then
echo "Using the draft release notes for $PUSHED_TAG"
PUBLISH_RELEASE=true
else
gh release create "$PUSHED_TAG" --draft --verify-tag --title "$PUSHED_TAG" --generate-notes ${PREVIOUS:+--notes-start-tag "$PREVIOUS"}
echo "Created a draft release for $PUSHED_TAG with generated notes"
PUBLISH_RELEASE=true
fi

if [ "$NEWEST" = "$PUSHED_TAG" ]; then
echo "Releasing $PUSHED_TAG as the newest release"
FLOATING=true
else
echo "Releasing $PUSHED_TAG with release tags only, because $NEWEST is newer"
FLOATING=false
fi
{
echo "ref=$PUSHED_TAG"
echo "release-tag=$PUSHED_TAG"
echo "floating-tags=$FLOATING"
echo "publish-release=$PUBLISH_RELEASE"
} >> "$GITHUB_OUTPUT"

setup:
needs: release
uses: ./.github/workflows/service_setup-matrix.yml
permissions:
contents: read
with:
ref: ${{ needs.release.outputs.tag }}
ref: ${{ needs.release.outputs.ref }}

build:
# PHP compiled from php.net, so a release never waits for Docker Hub.
base:
needs: [release, setup]
uses: ./.github/workflows/service_build-base-images.yml
permissions:
contents: read
packages: write
id-token: write
with:
matrix: ${{ needs.setup.outputs.matrix }}
image-prefix: "ghcr.io/serversideup/php-base:"
ref: ${{ needs.release.outputs.ref }}

build:
needs: [release, setup, base]
name: build ${{ matrix.variation }}
strategy:
fail-fast: false
Expand All @@ -65,14 +129,18 @@ jobs:
permissions:
contents: read
id-token: write
packages: read
uses: ./.github/workflows/service_build-images.yml
with:
variation: ${{ matrix.variation }}
matrix: ${{ needs.setup.outputs.matrix }}
ref: ${{ needs.release.outputs.tag }}
ref: ${{ needs.release.outputs.ref }}
registry-repositories: "docker.io/serversideup/php,ghcr.io/serversideup/php"
base-images: ${{ needs.base.outputs.base-images }}
tag-prefix: ''
release-type: "latest"
release-tag: ${{ needs.release.outputs.release-tag }}
floating-tags: ${{ needs.release.outputs.floating-tags == 'true' }}
secrets: inherit

test:
Expand All @@ -83,7 +151,7 @@ jobs:
contents: read
id-token: write
with:
ref: ${{ needs.release.outputs.tag }}
ref: ${{ needs.release.outputs.ref }}

# Only images that passed the image tests reach a public registry.
publish:
Expand All @@ -95,9 +163,27 @@ jobs:
packages: write
id-token: write
with:
ref: ${{ needs.release.outputs.tag }}
ref: ${{ needs.release.outputs.ref }}
secrets: inherit

# Publishing the release locks its tag, so it waits until every image is live.
finalize:
needs: [release, publish]
if: needs.release.outputs.publish-release == 'true'
name: publish release
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
contents: write
steps:
- name: Publish the draft release
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
VERSION: ${{ needs.release.outputs.release-tag }}
LATEST: ${{ needs.release.outputs.floating-tags }}
run: gh release edit "$VERSION" --draft=false --latest="$LATEST"

report:
needs: [release, setup, build, test, publish]
if: always() && needs.setup.result == 'success'
Expand All @@ -106,4 +192,4 @@ jobs:
contents: read
with:
matrix: ${{ needs.setup.outputs.matrix }}
ref: ${{ needs.release.outputs.tag }}
ref: ${{ needs.release.outputs.ref }}
26 changes: 25 additions & 1 deletion .github/workflows/action_publish-images-prs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,30 @@ jobs:
with:
ref: ${{ inputs.pr_number && format('refs/pull/{0}/head', inputs.pr_number) || github.ref }}

build:
# PHP compiled from php.net, so a release never waits for Docker Hub.
base:
needs: [setup]
if: >-
github.event_name == 'workflow_dispatch' ||
(
github.event.pull_request.head.repo.full_name == github.repository &&
github.actor != 'dependabot[bot]'
)
uses: ./.github/workflows/service_build-base-images.yml
permissions:
contents: read
packages: write
id-token: write
with:
matrix: ${{ needs.setup.outputs.matrix }}
image-prefix: "docker.io/serversideup/php-dev:php-base-"
ref: ${{ inputs.pr_number && format('refs/pull/{0}/head', inputs.pr_number) || github.ref }}
secrets: inherit

build:
needs: [setup, base]
# Pull requests from forks skip the base images and build on the official ones.
if: always() && needs.setup.result == 'success' && contains(fromJson('["success", "skipped"]'), needs.base.result)
name: build ${{ matrix.variation }}
strategy:
fail-fast: false
Expand All @@ -48,12 +70,14 @@ jobs:
permissions:
contents: read
id-token: write
packages: read
uses: ./.github/workflows/service_build-images.yml
with:
variation: ${{ matrix.variation }}
matrix: ${{ needs.setup.outputs.matrix }}
ref: ${{ inputs.pr_number && format('refs/pull/{0}/head', inputs.pr_number) || github.ref }}
registry-repositories: "docker.io/serversideup/php-dev"
base-images: ${{ needs.base.outputs.base-images || '{}' }}
tag-prefix: ${{ inputs.pr_number || github.event.pull_request.number }}
release-type: testing
# Only save and publish when the code comes from this repository or a maintainer asked for it.
Expand Down
Loading