Merge pull request #40: multi-arch (amd64 + arm64) for cac, cac-fpm, shared-httpd [skip ci]
This commit was merged in pull request #40.
This commit is contained in:
commit
4e657304c5
3 files changed
+318
-15
No files matched your search
@@ -77,6 +77,11 @@ jobs:
|
|||||||
./scripts/tests/image-label-amd64.test.sh
|
./scripts/tests/image-label-amd64.test.sh
|
||||||
./scripts/tests/index-platforms.test.sh
|
./scripts/tests/index-platforms.test.sh
|
||||||
|
|
||||||
|
# Static wiring of the multi-arch publish jobs (cac, cac-fpm, shared-httpd):
|
||||||
|
# by-digest pushes, non-fatal arm64 leg, timeouts, exact tag scheme.
|
||||||
|
- name: multi-arch workflow wiring test
|
||||||
|
run: python3 -I scripts/tests/multiarch-workflow.test.py
|
||||||
|
|
||||||
- name: lsphp probe regression test
|
- name: lsphp probe regression test
|
||||||
run: ./scripts/tests/lsphp-info-probe.test.sh
|
run: ./scripts/tests/lsphp-info-probe.test.sh
|
||||||
|
|
||||||
@@ -93,6 +98,7 @@ jobs:
|
|||||||
|
|
||||||
Build-and-Push:
|
Build-and-Push:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 75
|
||||||
strategy:
|
strategy:
|
||||||
# One PHP version failing (e.g. an EOL stream) must not cancel the
|
# One PHP version failing (e.g. an EOL stream) must not cancel the
|
||||||
# security builds of the others.
|
# security builds of the others.
|
||||||
@@ -144,6 +150,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
|
with:
|
||||||
|
platforms: arm64
|
||||||
|
|
||||||
# BuildKit (docker-container driver) ignores dockerd's registry-mirrors,
|
# BuildKit (docker-container driver) ignores dockerd's registry-mirrors,
|
||||||
# so the mirror is configured on the builder; BuildKit itself still falls
|
# so the mirror is configured on the builder; BuildKit itself still falls
|
||||||
@@ -198,23 +206,64 @@ jobs:
|
|||||||
username: ${{ secrets.CI_USER }}
|
username: ${{ secrets.CI_USER }}
|
||||||
password: ${{ secrets.CI_TOKEN }}
|
password: ${{ secrets.CI_TOKEN }}
|
||||||
|
|
||||||
- name: Build and Push Image
|
# amd64: required. Pushed untagged BY DIGEST (no tag); failure fails the
|
||||||
|
# job and publishes nothing, exactly as before multi-arch. The tags are only
|
||||||
|
# created by the assemble step below.
|
||||||
|
- name: Build amd64 (push by digest)
|
||||||
|
id: amd64
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: true
|
|
||||||
build-args: |
|
build-args: |
|
||||||
PHPVER=${{ matrix.phpver }}
|
PHPVER=${{ matrix.phpver }}
|
||||||
CACHE_BUST=${{ steps.bust.outputs.value }}
|
CACHE_BUST=${{ steps.bust.outputs.value }}
|
||||||
BASE_IMAGE=${{ steps.base.outputs.image }}
|
BASE_IMAGE=${{ steps.base.outputs.image }}
|
||||||
PKG_CACHE=${{ steps.pkgcache.outputs.url }}
|
PKG_CACHE=${{ steps.pkgcache.outputs.url }}
|
||||||
EXPECT_PHP_MIN=${{ steps.phpmin.outputs.min }}
|
EXPECT_PHP_MIN=${{ steps.phpmin.outputs.min }}
|
||||||
tags: |
|
outputs: type=image,name=repo.anhonesthost.net/cloud-hosting-platform/cac,push-by-digest=true,name-canonical=true,push=true
|
||||||
repo.anhonesthost.net/cloud-hosting-platform/cac:php${{ matrix.phpver }}
|
|
||||||
${{ matrix.phpver == '85' && 'repo.anhonesthost.net/cloud-hosting-platform/cac:latest' || '' }}
|
# arm64: best effort, built under QEMU on this same trusted x86 runner (no
|
||||||
|
# secret ever leaves it). Same Dockerfile, same build-args (incl. the php.net
|
||||||
|
# EXPECT_PHP_MIN gate, which runs `php -r` on the arm64 binary). Must never
|
||||||
|
# block or alter the amd64 tag: on failure/timeout the tag is published
|
||||||
|
# amd64-only, as before multi-arch.
|
||||||
|
- name: Build arm64 (push by digest)
|
||||||
|
id: arm64
|
||||||
|
continue-on-error: true
|
||||||
|
timeout-minutes: 45
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
platforms: linux/arm64
|
||||||
|
build-args: |
|
||||||
|
PHPVER=${{ matrix.phpver }}
|
||||||
|
CACHE_BUST=${{ steps.bust.outputs.value }}
|
||||||
|
BASE_IMAGE=${{ steps.base.outputs.image }}
|
||||||
|
PKG_CACHE=${{ steps.pkgcache.outputs.url }}
|
||||||
|
EXPECT_PHP_MIN=${{ steps.phpmin.outputs.min }}
|
||||||
|
outputs: type=image,name=repo.anhonesthost.net/cloud-hosting-platform/cac,push-by-digest=true,name-canonical=true,push=true
|
||||||
|
|
||||||
|
# Creates the index tag(s) from THIS run's digests only (a previous run's
|
||||||
|
# arm64 is never re-attached). scripts/assemble-multiarch-index.sh.
|
||||||
|
- name: Assemble index tag(s)
|
||||||
|
env:
|
||||||
|
AMD64_DIGEST: ${{ steps.amd64.outputs.digest }}
|
||||||
|
ARM64_DIGEST: ${{ steps.arm64.outputs.digest }}
|
||||||
|
ARM64_OUTCOME: ${{ steps.arm64.outcome }}
|
||||||
|
PHPVER: ${{ matrix.phpver }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
tags=(--tag "php${PHPVER}")
|
||||||
|
# :latest tracks php85 (unchanged tag scheme).
|
||||||
|
[ "$PHPVER" = 85 ] && tags+=(--tag latest)
|
||||||
|
scripts/assemble-multiarch-index.sh \
|
||||||
|
--amd64 "$AMD64_DIGEST" \
|
||||||
|
--arm64 "$ARM64_DIGEST" --arm64-outcome "$ARM64_OUTCOME" \
|
||||||
|
--repo repo.anhonesthost.net/cloud-hosting-platform/cac \
|
||||||
|
"${tags[@]}"
|
||||||
|
|
||||||
Build-FPM-Images:
|
Build-FPM-Images:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 75
|
||||||
strategy:
|
strategy:
|
||||||
# One PHP version failing (e.g. an EOL stream) must not cancel the
|
# One PHP version failing (e.g. an EOL stream) must not cancel the
|
||||||
# security builds of the others.
|
# security builds of the others.
|
||||||
@@ -266,6 +315,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
|
with:
|
||||||
|
platforms: arm64
|
||||||
|
|
||||||
# BuildKit (docker-container driver) ignores dockerd's registry-mirrors,
|
# BuildKit (docker-container driver) ignores dockerd's registry-mirrors,
|
||||||
# so the mirror is configured on the builder; BuildKit itself still falls
|
# so the mirror is configured on the builder; BuildKit itself still falls
|
||||||
@@ -320,21 +371,62 @@ jobs:
|
|||||||
username: ${{ secrets.CI_USER }}
|
username: ${{ secrets.CI_USER }}
|
||||||
password: ${{ secrets.CI_TOKEN }}
|
password: ${{ secrets.CI_TOKEN }}
|
||||||
|
|
||||||
- name: Build and Push FPM Image
|
# amd64: required. Pushed untagged BY DIGEST (no tag); failure fails the
|
||||||
|
# job and publishes nothing, exactly as before multi-arch. The tags are only
|
||||||
|
# created by the assemble step below.
|
||||||
|
- name: Build amd64 (push by digest)
|
||||||
|
id: amd64
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
file: ./Dockerfile.fpm
|
file: ./Dockerfile.fpm
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: true
|
|
||||||
build-args: |
|
build-args: |
|
||||||
PHPVER=${{ matrix.phpver }}
|
PHPVER=${{ matrix.phpver }}
|
||||||
CACHE_BUST=${{ steps.bust.outputs.value }}
|
CACHE_BUST=${{ steps.bust.outputs.value }}
|
||||||
BASE_IMAGE=${{ steps.base.outputs.image }}
|
BASE_IMAGE=${{ steps.base.outputs.image }}
|
||||||
PKG_CACHE=${{ steps.pkgcache.outputs.url }}
|
PKG_CACHE=${{ steps.pkgcache.outputs.url }}
|
||||||
EXPECT_PHP_MIN=${{ steps.phpmin.outputs.min }}
|
EXPECT_PHP_MIN=${{ steps.phpmin.outputs.min }}
|
||||||
tags: |
|
outputs: type=image,name=repo.anhonesthost.net/cloud-hosting-platform/cac-fpm,push-by-digest=true,name-canonical=true,push=true
|
||||||
repo.anhonesthost.net/cloud-hosting-platform/cac-fpm:php${{ matrix.phpver }}
|
|
||||||
${{ matrix.phpver == '85' && 'repo.anhonesthost.net/cloud-hosting-platform/cac-fpm:latest' || '' }}
|
# arm64: best effort, built under QEMU on this same trusted x86 runner (no
|
||||||
|
# secret ever leaves it). Same Dockerfile, same build-args (incl. the php.net
|
||||||
|
# EXPECT_PHP_MIN gate, which runs `php -r` on the arm64 binary). Must never
|
||||||
|
# block or alter the amd64 tag: on failure/timeout the tag is published
|
||||||
|
# amd64-only, as before multi-arch.
|
||||||
|
- name: Build arm64 (push by digest)
|
||||||
|
id: arm64
|
||||||
|
continue-on-error: true
|
||||||
|
timeout-minutes: 45
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
file: ./Dockerfile.fpm
|
||||||
|
platforms: linux/arm64
|
||||||
|
build-args: |
|
||||||
|
PHPVER=${{ matrix.phpver }}
|
||||||
|
CACHE_BUST=${{ steps.bust.outputs.value }}
|
||||||
|
BASE_IMAGE=${{ steps.base.outputs.image }}
|
||||||
|
PKG_CACHE=${{ steps.pkgcache.outputs.url }}
|
||||||
|
EXPECT_PHP_MIN=${{ steps.phpmin.outputs.min }}
|
||||||
|
outputs: type=image,name=repo.anhonesthost.net/cloud-hosting-platform/cac-fpm,push-by-digest=true,name-canonical=true,push=true
|
||||||
|
|
||||||
|
# Creates the index tag(s) from THIS run's digests only (a previous run's
|
||||||
|
# arm64 is never re-attached). scripts/assemble-multiarch-index.sh.
|
||||||
|
- name: Assemble index tag(s)
|
||||||
|
env:
|
||||||
|
AMD64_DIGEST: ${{ steps.amd64.outputs.digest }}
|
||||||
|
ARM64_DIGEST: ${{ steps.arm64.outputs.digest }}
|
||||||
|
ARM64_OUTCOME: ${{ steps.arm64.outcome }}
|
||||||
|
PHPVER: ${{ matrix.phpver }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
tags=(--tag "php${PHPVER}")
|
||||||
|
# :latest tracks php85 (unchanged tag scheme).
|
||||||
|
[ "$PHPVER" = 85 ] && tags+=(--tag latest)
|
||||||
|
scripts/assemble-multiarch-index.sh \
|
||||||
|
--amd64 "$AMD64_DIGEST" \
|
||||||
|
--arm64 "$ARM64_DIGEST" --arm64-outcome "$ARM64_OUTCOME" \
|
||||||
|
--repo repo.anhonesthost.net/cloud-hosting-platform/cac-fpm \
|
||||||
|
"${tags[@]}"
|
||||||
|
|
||||||
Build-LiteSpeed-Images:
|
Build-LiteSpeed-Images:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -648,6 +740,7 @@ jobs:
|
|||||||
|
|
||||||
Build-Shared-httpd:
|
Build-Shared-httpd:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 50
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -693,6 +786,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
|
with:
|
||||||
|
platforms: arm64
|
||||||
|
|
||||||
# BuildKit (docker-container driver) ignores dockerd's registry-mirrors,
|
# BuildKit (docker-container driver) ignores dockerd's registry-mirrors,
|
||||||
# so the mirror is configured on the builder; BuildKit itself still falls
|
# so the mirror is configured on the builder; BuildKit itself still falls
|
||||||
@@ -718,17 +813,52 @@ jobs:
|
|||||||
username: ${{ secrets.CI_USER }}
|
username: ${{ secrets.CI_USER }}
|
||||||
password: ${{ secrets.CI_TOKEN }}
|
password: ${{ secrets.CI_TOKEN }}
|
||||||
|
|
||||||
- name: Build and Push Shared httpd Image
|
# amd64: required. Pushed untagged BY DIGEST (no tag); failure fails the
|
||||||
|
# job and publishes nothing, exactly as before multi-arch. The tags are only
|
||||||
|
# created by the assemble step below.
|
||||||
|
- name: Build amd64 (push by digest)
|
||||||
|
id: amd64
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
file: ./Dockerfile.shared-httpd
|
file: ./Dockerfile.shared-httpd
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: true
|
|
||||||
build-args: |
|
build-args: |
|
||||||
BASE_IMAGE=${{ steps.base.outputs.image }}
|
BASE_IMAGE=${{ steps.base.outputs.image }}
|
||||||
PKG_CACHE=${{ steps.pkgcache.outputs.url }}
|
PKG_CACHE=${{ steps.pkgcache.outputs.url }}
|
||||||
tags: |
|
outputs: type=image,name=repo.anhonesthost.net/cloud-hosting-platform/shared-httpd,push-by-digest=true,name-canonical=true,push=true
|
||||||
repo.anhonesthost.net/cloud-hosting-platform/shared-httpd:latest
|
|
||||||
|
# arm64: best effort, built under QEMU on this same trusted x86 runner (no
|
||||||
|
# secret ever leaves it). Same Dockerfile, same build-args (incl. the php.net
|
||||||
|
# EXPECT_PHP_MIN gate, which runs `php -r` on the arm64 binary). Must never
|
||||||
|
# block or alter the amd64 tag: on failure/timeout the tag is published
|
||||||
|
# amd64-only, as before multi-arch.
|
||||||
|
- name: Build arm64 (push by digest)
|
||||||
|
id: arm64
|
||||||
|
continue-on-error: true
|
||||||
|
timeout-minutes: 25
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
file: ./Dockerfile.shared-httpd
|
||||||
|
platforms: linux/arm64
|
||||||
|
build-args: |
|
||||||
|
BASE_IMAGE=${{ steps.base.outputs.image }}
|
||||||
|
PKG_CACHE=${{ steps.pkgcache.outputs.url }}
|
||||||
|
outputs: type=image,name=repo.anhonesthost.net/cloud-hosting-platform/shared-httpd,push-by-digest=true,name-canonical=true,push=true
|
||||||
|
|
||||||
|
# Creates the index tag(s) from THIS run's digests only (a previous run's
|
||||||
|
# arm64 is never re-attached). scripts/assemble-multiarch-index.sh.
|
||||||
|
- name: Assemble index tag(s)
|
||||||
|
env:
|
||||||
|
AMD64_DIGEST: ${{ steps.amd64.outputs.digest }}
|
||||||
|
ARM64_DIGEST: ${{ steps.arm64.outputs.digest }}
|
||||||
|
ARM64_OUTCOME: ${{ steps.arm64.outcome }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
scripts/assemble-multiarch-index.sh \
|
||||||
|
--amd64 "$AMD64_DIGEST" \
|
||||||
|
--arm64 "$ARM64_DIGEST" --arm64-outcome "$ARM64_OUTCOME" \
|
||||||
|
--repo repo.anhonesthost.net/cloud-hosting-platform/shared-httpd \
|
||||||
|
--tag latest
|
||||||
|
|
||||||
Build-Shared-OLS:
|
Build-Shared-OLS:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ cat >"$T/it" <<'STUB'
|
|||||||
# "create ..." -> appended to $STUB_LOG.
|
# "create ..." -> appended to $STUB_LOG.
|
||||||
if [ -n "${STUB_LOG:-}" ]; then
|
if [ -n "${STUB_LOG:-}" ]; then
|
||||||
if [ "$1" = create ]; then echo "create $*" >>"$STUB_LOG"; exit 0; fi
|
if [ "$1" = create ]; then echo "create $*" >>"$STUB_LOG"; exit 0; fi
|
||||||
ref=$2; a=$(grep -F "$ref=" "$ARCHES" | head -1 | cut -d= -f2); [ -n "$a" ] || exit 1
|
ref=$2; a=$(grep -F -m1 "$ref=" "$ARCHES" | cut -d= -f2); [ -n "$a" ] || exit 1
|
||||||
printf '{"architecture":"%s"}' "$a"; exit 0
|
printf '{"architecture":"%s"}' "$a"; exit 0
|
||||||
fi
|
fi
|
||||||
[ "$2" = --raw ] || exit 2
|
[ "$2" = --raw ] || exit 2
|
||||||
|
|||||||
Executable
+173
@@ -0,0 +1,173 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Static wiring checks for the multi-arch publish jobs in .gitea/workflows/build-push.yaml
|
||||||
|
(Build-and-Push = cac, Build-FPM-Images = cac-fpm, Build-Shared-httpd = shared-httpd).
|
||||||
|
|
||||||
|
No YAML library (the Shell-Checks runner has none): jobs/steps are split on their indentation.
|
||||||
|
Env WORKFLOW overrides the file under test (used to show the tests fail on the old workflow).
|
||||||
|
Run: python3 -I scripts/tests/multiarch-workflow.test.py
|
||||||
|
"""
|
||||||
|
import os, re, subprocess, tempfile, unittest, stat
|
||||||
|
|
||||||
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
ROOT = os.path.normpath(os.path.join(HERE, '..', '..'))
|
||||||
|
WF = os.environ.get('WORKFLOW') or os.path.join(ROOT, '.gitea', 'workflows', 'build-push.yaml')
|
||||||
|
with open(WF) as _f:
|
||||||
|
TEXT = _f.read()
|
||||||
|
REG = 'repo.anhonesthost.net/cloud-hosting-platform'
|
||||||
|
|
||||||
|
SLICE = { # job -> (image repo, dockerfile arg or None, matrix?)
|
||||||
|
'Build-and-Push': ('cac', None, True),
|
||||||
|
'Build-FPM-Images': ('cac-fpm', './Dockerfile.fpm', True),
|
||||||
|
'Build-Shared-httpd': ('shared-httpd', './Dockerfile.shared-httpd', False),
|
||||||
|
}
|
||||||
|
OUT_OF_SLICE = ['Build-LiteSpeed-Images', 'Build-LSPHP-Images', 'Build-Shared-OLS']
|
||||||
|
|
||||||
|
|
||||||
|
def jobs():
|
||||||
|
m = re.search(r'^jobs:\n', TEXT, re.M)
|
||||||
|
body = TEXT[m.end():]
|
||||||
|
parts = re.split(r'^ ([A-Za-z0-9_-]+):\n', body, flags=re.M)
|
||||||
|
return {parts[i]: parts[i + 1] for i in range(1, len(parts), 2)}
|
||||||
|
|
||||||
|
|
||||||
|
def steps(job):
|
||||||
|
s = re.split(r'^ - name: ', job, flags=re.M)[1:]
|
||||||
|
out = []
|
||||||
|
for blk in s:
|
||||||
|
name = blk.split('\n', 1)[0].strip()
|
||||||
|
out.append((name, blk))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def step(job, prefix):
|
||||||
|
hits = [(n, b) for n, b in steps(job) if n.startswith(prefix)]
|
||||||
|
assert len(hits) == 1, (prefix, [n for n, _ in hits])
|
||||||
|
return hits[0][1]
|
||||||
|
|
||||||
|
|
||||||
|
class Wiring(unittest.TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
cls.jobs = jobs()
|
||||||
|
|
||||||
|
def test_triggers_unchanged(self):
|
||||||
|
head = TEXT.split('jobs:')[0]
|
||||||
|
self.assertRegex(head, r'on:\n push:\n branches:\n - trunk\n workflow_dispatch:')
|
||||||
|
|
||||||
|
def test_slice_jobs_exist_and_keep_matrix_and_no_fail_fast(self):
|
||||||
|
for j, (_, _, matrix) in SLICE.items():
|
||||||
|
self.assertIn(j, self.jobs, j)
|
||||||
|
if matrix:
|
||||||
|
self.assertIn('fail-fast: false', self.jobs[j], j)
|
||||||
|
self.assertIn('phpver: [82, 83, 84, 85]', self.jobs[j], j)
|
||||||
|
|
||||||
|
def test_job_timeout_bounds_the_arm64_step(self):
|
||||||
|
for j in SLICE:
|
||||||
|
job = self.jobs[j]
|
||||||
|
jt = int(re.search(r'^ timeout-minutes: (\d+)$', job, re.M).group(1))
|
||||||
|
at = int(re.search(r'timeout-minutes: (\d+)', step(job, 'Build arm64')).group(1))
|
||||||
|
self.assertGreaterEqual(at, 20, j) # T3: arm64 under qemu took 7m47s
|
||||||
|
self.assertGreater(jt, at + 20, j) # room for setup + amd64 + assemble
|
||||||
|
self.assertLessEqual(jt, 120, j)
|
||||||
|
|
||||||
|
def test_step_order_and_ids(self):
|
||||||
|
for j in SLICE:
|
||||||
|
names = [n for n, _ in steps(self.jobs[j])]
|
||||||
|
a = [i for i, n in enumerate(names) if n.startswith('Build amd64')]
|
||||||
|
r = [i for i, n in enumerate(names) if n.startswith('Build arm64')]
|
||||||
|
s = [i for i, n in enumerate(names) if n.startswith('Assemble')]
|
||||||
|
self.assertEqual((len(a), len(r), len(s)), (1, 1, 1), j)
|
||||||
|
self.assertLess(a[0], r[0], j)
|
||||||
|
self.assertLess(r[0], s[0], j)
|
||||||
|
self.assertIn('id: amd64', step(self.jobs[j], 'Build amd64'))
|
||||||
|
self.assertIn('id: arm64', step(self.jobs[j], 'Build arm64'))
|
||||||
|
self.assertLess(names.index('Login to Gitea'), a[0], j)
|
||||||
|
|
||||||
|
def test_arm64_non_fatal_amd64_fatal(self):
|
||||||
|
for j in SLICE:
|
||||||
|
self.assertIn('continue-on-error: true', step(self.jobs[j], 'Build arm64'), j)
|
||||||
|
self.assertNotIn('continue-on-error', step(self.jobs[j], 'Build amd64'), j)
|
||||||
|
self.assertNotIn('continue-on-error', step(self.jobs[j], 'Assemble'), j)
|
||||||
|
|
||||||
|
def test_platforms_and_qemu(self):
|
||||||
|
for j in SLICE:
|
||||||
|
self.assertIn('platforms: linux/amd64\n', step(self.jobs[j], 'Build amd64'), j)
|
||||||
|
self.assertIn('platforms: linux/arm64\n', step(self.jobs[j], 'Build arm64'), j)
|
||||||
|
self.assertRegex(step(self.jobs[j], 'Set up QEMU'), r'platforms: arm64', j)
|
||||||
|
|
||||||
|
def test_both_push_by_digest_untagged_to_gitea_only(self):
|
||||||
|
for j, (repo, _, _) in SLICE.items():
|
||||||
|
for which in ('Build amd64', 'Build arm64'):
|
||||||
|
st = step(self.jobs[j], which)
|
||||||
|
self.assertIn('type=image,name=%s/%s,push-by-digest=true,name-canonical=true,push=true' % (REG, repo), st, j)
|
||||||
|
self.assertNotRegex(st, r'^\s+tags:', j)
|
||||||
|
self.assertNotRegex(st, r'^\s+push: true', j)
|
||||||
|
self.assertNotIn('ghcr.io', self.jobs[j], j) # CAC has never pushed to GHCR; do not widen
|
||||||
|
|
||||||
|
def test_same_build_args_and_dockerfile_for_both_platforms(self):
|
||||||
|
for j, (_, dockerfile, _) in SLICE.items():
|
||||||
|
a = step(self.jobs[j], 'Build amd64'); r = step(self.jobs[j], 'Build arm64')
|
||||||
|
ba = lambda s: re.search(r'build-args: \|\n((?: .*\n)+)', s).group(1)
|
||||||
|
self.assertEqual(ba(a), ba(r), j)
|
||||||
|
for need in (('PKG_CACHE=${{ steps.pkgcache.outputs.url }}', 'BASE_IMAGE=${{ steps.base.outputs.image }}')):
|
||||||
|
for n in need:
|
||||||
|
self.assertIn(n, ba(a), j)
|
||||||
|
if SLICE[j][2]:
|
||||||
|
for n in ('PHPVER=${{ matrix.phpver }}', 'CACHE_BUST=${{ steps.bust.outputs.value }}',
|
||||||
|
'EXPECT_PHP_MIN=${{ steps.phpmin.outputs.min }}'):
|
||||||
|
self.assertIn(n, ba(a), j)
|
||||||
|
if dockerfile:
|
||||||
|
self.assertIn('file: %s\n' % dockerfile, a, j)
|
||||||
|
self.assertIn('file: %s\n' % dockerfile, r, j)
|
||||||
|
|
||||||
|
def test_assemble_uses_this_runs_outputs_and_repo(self):
|
||||||
|
for j, (repo, _, _) in SLICE.items():
|
||||||
|
st = step(self.jobs[j], 'Assemble')
|
||||||
|
self.assertIn('${{ steps.amd64.outputs.digest }}', st, j)
|
||||||
|
self.assertIn('${{ steps.arm64.outputs.digest }}', st, j)
|
||||||
|
self.assertIn('${{ steps.arm64.outcome }}', st, j)
|
||||||
|
self.assertIn('scripts/assemble-multiarch-index.sh', st, j)
|
||||||
|
self.assertIn('--repo %s/%s' % (REG, repo), st, j)
|
||||||
|
self.assertEqual(st.count('--repo '), 1, j)
|
||||||
|
|
||||||
|
def test_tag_scheme_unchanged(self):
|
||||||
|
"""Run the assemble step's shell with a recording stub: exact tags per version."""
|
||||||
|
for j, (repo, _, matrix) in SLICE.items():
|
||||||
|
st = step(self.jobs[j], 'Assemble')
|
||||||
|
script = re.search(r'run: \|\n((?: .*\n?)+)', st).group(1)
|
||||||
|
script = '\n'.join(l[10:] for l in script.split('\n'))
|
||||||
|
for ver in (['82', '83', '84', '85'] if matrix else ['']):
|
||||||
|
with tempfile.TemporaryDirectory() as d:
|
||||||
|
os.makedirs(os.path.join(d, 'scripts'))
|
||||||
|
stub = os.path.join(d, 'scripts', 'assemble-multiarch-index.sh')
|
||||||
|
with open(stub, 'w') as f:
|
||||||
|
f.write('#!/usr/bin/env bash\nprintf "%s\\n" "$@" > "$LOG"\n')
|
||||||
|
os.chmod(stub, 0o755)
|
||||||
|
log = os.path.join(d, 'log')
|
||||||
|
env = dict(os.environ, LOG=log, AMD64_DIGEST='sha256:' + 'a' * 64,
|
||||||
|
ARM64_DIGEST='sha256:' + 'b' * 64, ARM64_OUTCOME='success', PHPVER=ver)
|
||||||
|
subprocess.run(['bash', '-c', script], cwd=d, env=env, check=True)
|
||||||
|
with open(log) as f:
|
||||||
|
args = f.read().split('\n')[:-1]
|
||||||
|
tags = [args[i + 1] for i, x in enumerate(args) if x == '--tag']
|
||||||
|
want = ['latest'] if not matrix else (['php' + ver] + (['latest'] if ver == '85' else []))
|
||||||
|
self.assertEqual(tags, want, (j, ver))
|
||||||
|
self.assertEqual(args[args.index('--repo') + 1], '%s/%s' % (REG, repo))
|
||||||
|
self.assertEqual(args[args.index('--arm64-outcome') + 1], 'success')
|
||||||
|
|
||||||
|
def test_arm64_never_added_outside_the_first_slice(self):
|
||||||
|
for j in OUT_OF_SLICE:
|
||||||
|
self.assertIn(j, self.jobs, j)
|
||||||
|
job = self.jobs[j]
|
||||||
|
self.assertIn('platforms: linux/amd64', job, j)
|
||||||
|
self.assertNotIn('linux/arm64', job, j)
|
||||||
|
self.assertNotIn('push-by-digest', job, j)
|
||||||
|
self.assertNotIn('assemble-multiarch-index', job, j)
|
||||||
|
|
||||||
|
def test_only_the_assemble_script_creates_tags_in_slice(self):
|
||||||
|
for j in SLICE:
|
||||||
|
self.assertNotIn('imagetools create', self.jobs[j], j) # only via the tested script
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main(verbosity=2)
|
||||||
Reference in new issue
Block a user