Skip to content

Build and push images #63

Build and push images

Build and push images #63

Workflow file for this run

name: Build and push images
on:
workflow_dispatch:
inputs:
dockerhubImageName:
description: 'dockerhub image name'
required: true
default: 'index.docker.io/dtyq/php-dockerfile'
ghcrImageName:
description: 'ghcr.io image name'
required: true
default: 'ghcr.io/dtyq/php-dockerfile'
awsEcrImageName:
description: 'AWS ECR image name'
required: true
default: 'public.ecr.aws/dtyq/php-dockerfile'
schedule:
# every Wednesday at 3:42 AM(UTC, Beijing time: 11:42 AM)
- cron: '42 3 * * 3'
env:
TRY_MIRRORS: ""
PUBLIC_MIRROR: "https://dl-cdn.alpinelinux.org" # no mirror needed at github actions
IMAGE_NAME: php-dockerfile # only used for default local image name
jobs:
prepare:
name: Prepare matrix
runs-on: ubuntu-latest
outputs:
tasks: ${{ steps.gentasks.outputs.tasks }}
images: ${{ steps.gentasks.outputs.images }}
outputs: ${{ steps.gentasks.outputs.outputs }}
steps:
- name: Generate tasks
shell: python3 {0}
id: gentasks
run: |
import os, json
from distutils.version import LooseVersion
phpAlpineVersions = {
"8.4": "3.22",
"8.3": "3.22",
"8.2": "3.22",
"8.1": "3.18",
"8.0": "3.16",
}
extVersions = [
# swoole master branch
("swoole-master", "8.1", None),
# swoole 6.0 branch
("swoole-6.0.2", "8.1", None),
# swoole 5.1 branch
("swoole-5.1.8", "8.0", "8.3"),
# swoole 4.8 branch
("swoole-4.8.13", "8.0", "8.2"),
# swow ci branch
("swow-ci", "8.0", None),
# swow
("swow-1.6.1", "8.0", "8.4"),
]
extraExts = [
"jsonpath",
"parle",
"xlswriter",
]
extraExtTag = "-".join(sorted(extraExts))
githubOutput = open(os.environ["GITHUB_OUTPUT"], "w")
tags = []
index = 0
for phpVer, alpineVer in phpAlpineVersions.items():
for extVersion in extVersions:
ext, minPhpVer, maxPhpVer = extVersion
if minPhpVer and LooseVersion(phpVer) < LooseVersion(minPhpVer):
continue
if maxPhpVer and LooseVersion(phpVer) > LooseVersion(maxPhpVer):
continue
tag = f"{phpVer}-alpine-{alpineVer}-{ext}-{extraExtTag}"
tags.append({
"tag": tag,
"outputVar": f"output{index}",
})
print(f"Add tag {tag}")
index += 1
serialized = json.dumps({"tag": tags})
githubOutput.write(f"tasks={serialized}\n")
images = []
images.append("${{ inputs.ghcrImageName }}" or "ghcr.io/dtyq/php-dockerfile")
if "${{ secrets.DOCKERHUB_CRED }}":
images.append("${{ inputs.dockerhubImageName }}" or "index.docker.io/dtyq/php-dockerfile")
if "${{ secrets.AWS_ECR_CRED }}":
images.append("${{ inputs.awsEcrImageName }}" or "public.ecr.aws/dtyq/php-dockerfile")
serialized = json.dumps(images)
githubOutput.write(f"images={serialized}\n")
githubOutput.close()
build:
name: ${{ matrix.tag.tag }} build and push
runs-on: ubuntu-latest
needs: prepare
strategy:
matrix: ${{ fromJson(needs.prepare.outputs.tasks) }}
max-parallel: 5
fail-fast: false
permissions:
contents: write
packages: write
# NOTE: needs more if more output var added
# this is dumb, but we cannot generate dynamic outputs via needs.prepare.outputs.outputs
outputs:
output1: ${{ steps.push.outputs.output1 }}
output2: ${{ steps.push.outputs.output2 }}
output3: ${{ steps.push.outputs.output3 }}
output4: ${{ steps.push.outputs.output4 }}
output5: ${{ steps.push.outputs.output5 }}
output6: ${{ steps.push.outputs.output6 }}
output7: ${{ steps.push.outputs.output7 }}
output8: ${{ steps.push.outputs.output8 }}
output9: ${{ steps.push.outputs.output9 }}
output10: ${{ steps.push.outputs.output10 }}
output11: ${{ steps.push.outputs.output11 }}
output12: ${{ steps.push.outputs.output12 }}
output13: ${{ steps.push.outputs.output13 }}
output14: ${{ steps.push.outputs.output14 }}
output15: ${{ steps.push.outputs.output15 }}
output16: ${{ steps.push.outputs.output16 }}
output17: ${{ steps.push.outputs.output17 }}
output18: ${{ steps.push.outputs.output18 }}
output19: ${{ steps.push.outputs.output19 }}
output20: ${{ steps.push.outputs.output20 }}
output21: ${{ steps.push.outputs.output21 }}
output22: ${{ steps.push.outputs.output22 }}
output23: ${{ steps.push.outputs.output23 }}
output24: ${{ steps.push.outputs.output24 }}
output25: ${{ steps.push.outputs.output25 }}
output26: ${{ steps.push.outputs.output26 }}
output27: ${{ steps.push.outputs.output27 }}
output28: ${{ steps.push.outputs.output28 }}
output29: ${{ steps.push.outputs.output29 }}
output30: ${{ steps.push.outputs.output30 }}
steps:
# checkout source code
- name: Checkout
uses: actions/checkout@v4
- name: Set up secrets
shell: python3 {0}
run: |
import os, json, base64
fw = open(os.path.expanduser("~/.docker/config.json"), "w")
config = {
"auths": {},
}
# GHCR
cred = base64.b64encode(b'whatever:${{ github.token }}').decode()
config["auths"]["ghcr.io"] = {
"auth": cred,
}
# AWS ECR
cred = "${{ secrets.AWS_ECR_CRED }}"
if cred:
config["auths"]["public.ecr.aws"] = {
"auth": cred,
}
# DockerHub
cred = "${{ secrets.DOCKERHUB_CRED }}"
if cred:
config["auths"]["https://index.docker.io/v1/"] = {
"auth": cred,
}
fw.write(json.dumps(config))
fw.close()
- name: Build image
shell: bash
id: build
run: |
./build.py "${{ matrix.tag.tag }}"
- name: Push image
shell: python {0}
id: push
run: |
import sys, os, json, subprocess
imageName = os.environ["IMAGE_NAME"]
images = json.loads('${{ needs.prepare.outputs.images }}')
digests = {}
for image in images:
for tag in ("${{ matrix.tag.tag }}", "${{ matrix.tag.tag }}-debuggable"):
print(f"tagging {imageName}:{tag} to {image}:{tag}")
proc = subprocess.run(
["docker", "tag", f"{imageName}:{tag}", f"{image}:{tag}"]
)
if proc.returncode != 0:
print(f"failed to tag {imageName}:{tag} to {image}:{tag}")
sys.exit(1)
print(f"pushing {image}:{tag}")
proc = subprocess.run(["docker", "push", f"{image}:{tag}"])
if proc.returncode != 0:
print(f"failed to push {image}:{tag}")
sys.exit(1)
print(f"get {image}:{tag} digest")
proc = subprocess.run(
[
"docker",
"inspect",
f"{image}:{tag}",
"--format",
"{{index .RepoDigests 0}}",
],
capture_output=True,
)
if proc.returncode != 0:
print(f"failed to inspect {image}:{tag}")
sys.exit(1)
digest = proc.stdout.decode().strip().split("@")[1]
if digest:
print(f"got {image}:{tag} digest: {digest}")
else:
print(f"failed to get {image}:{tag} digest")
sys.exit(1)
digests[f"{image}:{tag}"] = digest
githubOutput = open(os.environ["GITHUB_OUTPUT"], "w")
githubOutput.write(f"${{ matrix.tag.outputVar }}={json.dumps(digests)}\n")
githubOutput.close()
gen-attests:
name: Generate attestation jobs
runs-on: ubuntu-latest
needs: build
if: ${{ always() }}
outputs:
tasks: ${{ steps.gen-attests.outputs.tasks }}
steps:
- name: Duplicate json
shell: cp {0} ./attests.json
run: |
${{ toJson(needs.build.outputs) }}
- name: Generate attestation jobs
shell: python3 {0}
id: gen-attests
run: |
import json, os
hashes = json.load(open("./attests.json"))
imageDigests = {}
for _, content in hashes.items():
if not content:
continue
for image, digest in json.loads(content).items():
imageName = image.split(":")[0]
imageDigests[imageName] = imageDigests.get(imageName, [])
imageDigests[imageName].append(digest)
attests = []
for imageName, digests in imageDigests.items():
for digest in digests:
attests.append({
"image": imageName,
"digest": digest,
})
serialized = json.dumps({"attests": attests})
githubOutput = open(os.environ["GITHUB_OUTPUT"], "w")
githubOutput.write(f"tasks={serialized}\n")
githubOutput.close()
attest:
name: ${{ matrix.attests.image }} artifact attestation
runs-on: ubuntu-latest
needs: gen-attests
strategy:
matrix: ${{ fromJson(needs.gen-attests.outputs.tasks) }}
max-parallel: 10
permissions:
contents: write
packages: write
id-token: write
attestations: write
steps:
- name: Set up secrets
shell: python3 {0}
run: |
import os, json, base64
fw = open(os.path.expanduser("~/.docker/config.json"), "w")
config = {
"auths": {},
}
# GHCR
cred = base64.b64encode(b'whatever:${{ github.token }}').decode()
config["auths"]["ghcr.io"] = {
"auth": cred,
}
# AWS ECR
cred = "${{ secrets.AWS_ECR_CRED }}"
if cred:
config["auths"]["public.ecr.aws"] = {
"auth": cred,
}
# DockerHub
cred = "${{ secrets.DOCKERHUB_CRED }}"
if cred:
config["auths"]["https://index.docker.io/v1/"] = {
"auth": cred,
}
fw.write(json.dumps(config))
fw.close()
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v2
with:
subject-name: ${{ matrix.attests.image }}
subject-digest: ${{ matrix.attests.digest }}
push-to-registry: true