Skip to content

Commit 9fa64fa

Browse files
committed
initial commit
0 parents  commit 9fa64fa

File tree

8 files changed

+626
-0
lines changed

8 files changed

+626
-0
lines changed

.github/workflows/release.yml

Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
name: Release
2+
3+
concurrency:
4+
group: ${{ github.workflow }}-${{ github.ref }}
5+
cancel-in-progress: true
6+
7+
on:
8+
schedule:
9+
- cron: '0 3 * * *'
10+
workflow_dispatch:
11+
inputs:
12+
version:
13+
description: 'PHP version'
14+
required: true
15+
default: 'all'
16+
type: choice
17+
options:
18+
- 'all'
19+
- '8.1'
20+
- '8.2'
21+
- '8.3'
22+
- '8.4'
23+
force:
24+
type: choice
25+
description: 'Force recreate images'
26+
required: false
27+
default: 'false'
28+
options:
29+
- 'true'
30+
- 'false'
31+
32+
env:
33+
PHP_VERSIONS: '8.1,8.2,8.3,8.4'
34+
GHCR_SLUG: ghcr.io/toshy/php
35+
36+
jobs:
37+
prepare:
38+
runs-on: ubuntu-latest
39+
outputs:
40+
variants: ${{ steps.matrix.outputs.variants }}
41+
platforms: ${{ steps.matrix.outputs.platforms }}
42+
targets: ${{ steps.matrix.outputs.targets }}
43+
flavors: ${{ steps.matrix.outputs.flavors }}
44+
metadata: ${{ steps.matrix.outputs.metadata }}
45+
php_versions: ${{ steps.check_image.outputs.php_versions }}
46+
steps:
47+
- name: Checkout
48+
uses: actions/checkout@v4
49+
50+
- name: Skopeo
51+
uses: supplypike/setup-bin@v4
52+
with:
53+
uri: 'https://github.com/lework/skopeo-binary/releases/download/v1.17.0/skopeo-linux-amd64'
54+
name: 'skopeo'
55+
version: 'v1.17.0'
56+
57+
- name: Get PHP versions
58+
id: check_version
59+
run: |
60+
if [ -n "${{ github.event.inputs.version }}" ] && [ "${{ github.event.inputs.version }}" != "all" ]; then
61+
echo "PHP 'version' set to '${{ github.event.inputs.version }}'."
62+
SELECTED_PHP_VERSIONS=(${{ github.event.inputs.VERSION }})
63+
else
64+
echo "PHP 'version' set to '${{ env.PHP_VERSIONS }}' (default)."
65+
IFS=',' read -ra SELECTED_PHP_VERSIONS <<< "${{ env.PHP_VERSIONS }}"
66+
fi
67+
68+
PHP_VERSIONS=()
69+
for pv in ${SELECTED_PHP_VERSIONS[@]}; do
70+
PHP_VERSIONS+=($(skopeo inspect "docker://docker.io/library/php:$pv" --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")'))
71+
done
72+
73+
CONCATENATED_PHP_VERSIONS="$(printf "%s," "${PHP_VERSIONS[@]}" | cut -d "," -f 1-${#PHP_VERSIONS[@]})"
74+
echo "Images will be build for the following versions: '$CONCATENATED_PHP_VERSIONS'."
75+
76+
{
77+
echo php_versions=$CONCATENATED_PHP_VERSIONS
78+
} >> "${GITHUB_OUTPUT}"
79+
80+
- name: Check if PHP images already exists
81+
id: check_image
82+
env:
83+
GHCR_SLUG: ${{ env.GHCR_SLUG }}
84+
run: |
85+
# Retrieve current registry tags
86+
ENCODED_TOKEN=$(echo -n "${{ secrets.GITHUB_TOKEN }}" | base64 -w 0)
87+
RESPONSE=$(curl -s -H "Authorization: Bearer ${ENCODED_TOKEN}" https://ghcr.io/v2/${GHCR_SLUG#ghcr.io/}/tags/list)
88+
89+
# In case of a new GitHub repository, or has registry but without any tags without any tags yet
90+
if echo "$RESPONSE" | jq -e '.errors' >/dev/null 2>&1 || echo "$RESPONSE" | jq -e '.tags == null' >/dev/null 2>&1; then
91+
if echo "$RESPONSE" | jq -e '.errors' >/dev/null 2>&1; then
92+
MESSAGE=$(echo "$RESPONSE" | jq -e '.errors[0].message')
93+
else
94+
MESSAGE=$(echo "$RESPONSE" | jq -e '.tags // "empty"')
95+
fi
96+
97+
echo "No tags found (Response: $MESSAGE). Proceed to build step."
98+
99+
# Re-export php_versions
100+
{
101+
echo php_versions=${{ steps.check_version.outputs.php_versions }}
102+
} >> "${GITHUB_OUTPUT}"
103+
exit 0
104+
fi
105+
106+
# Current released php versions from GitHub packages
107+
PACKAGE_PHP_VERSIONS=$(echo "$RESPONSE" | jq -r '.tags[]' | grep -oP '^\d+(\.\d+){0,2}(?=-)' | sort -uV | jq -R . | jq -s .)
108+
109+
# Check which PHP tags already exists; remove already existing ones from list if "force" input was not provided
110+
CURRENT_PHP_VERSIONS=${{ steps.check_version.outputs.php_versions }}
111+
IFS=',' read -ra PHP_VERSIONS_ARRAY <<< "${{ steps.check_version.outputs.php_versions }}"
112+
for pv in ${PHP_VERSIONS_ARRAY[@]}; do
113+
TAG_EXISTS=$(echo "$PACKAGE_PHP_VERSIONS" | jq 'index("'"$pv"'") != null')
114+
if [ "$TAG_EXISTS" = "true" ]; then
115+
if [ "${{ github.event.inputs.force }}" == "true" ]; then
116+
echo "Image with tag '$pv' already exists. Force build step."
117+
else
118+
echo "Image with tag '$pv' already exists. Skip build step for specific tag."
119+
CURRENT_PHP_VERSIONS=$(echo "$CURRENT_PHP_VERSIONS" | sed "s/,${pv}//;s/${pv},//;s/^${pv}$//")
120+
fi
121+
else
122+
echo "Image with tag '$pv' not found. Proceed to build step."
123+
fi
124+
done
125+
126+
# Re-export php_versions
127+
{
128+
echo php_versions=$(echo "$CURRENT_PHP_VERSIONS" | xargs)
129+
} >> "${GITHUB_OUTPUT}"
130+
131+
- name: Set up Docker Buildx
132+
if: ${{ steps.check_image.outputs.php_versions != ''}}
133+
uses: docker/setup-buildx-action@v3
134+
135+
- name: Create variants matrix
136+
if: ${{ steps.check_image.outputs.php_versions != ''}}
137+
id: matrix
138+
shell: bash
139+
run: |
140+
METADATA="$(docker buildx bake --print | jq -c)"
141+
FLAVORS="$(jq -c '.group.default.targets|map(split("-")[-3])|unique' <<< "${METADATA}")"
142+
TARGETS="$(jq -c '.group.default.targets|map(split("-")[-1])|unique' <<< "${METADATA}")"
143+
_FORMATTED_TARGETS="$(jq -cr 'map("-" + split("-")[0])|join("|")' <<< "${TARGETS}")"
144+
VARIANTS="$(jq -c ".group.default.targets|map(sub(\"$_FORMATTED_TARGETS\"; \"\"))|unique" <<< "${METADATA}")"
145+
PLATFORMS="$(jq -c 'first(.target[]) | .platforms' <<< "${METADATA}")"
146+
{
147+
echo metadata="$METADATA"
148+
echo targets="$TARGETS"
149+
echo flavors="$FLAVORS"
150+
echo variants="$VARIANTS"
151+
echo platforms="$PLATFORMS"
152+
} >> "${GITHUB_OUTPUT}"
153+
env:
154+
SHA: ${{ github.sha }}
155+
VERSION: ${{ (github.ref_type == 'tag' && github.ref_name) || steps.check.outputs.ref || github.event.repository.default_branch }}
156+
PHP_VERSIONS: ${{ steps.check_image.outputs.php_versions }}
157+
158+
build:
159+
runs-on: ubuntu-latest
160+
needs:
161+
- prepare
162+
if: ${{ needs.prepare.outputs.php_versions != '' }}
163+
permissions:
164+
contents: read
165+
packages: write
166+
strategy:
167+
fail-fast: false
168+
matrix:
169+
variant: ${{ fromJson(needs.prepare.outputs.variants) }}
170+
platform: ${{ fromJson(needs.prepare.outputs.platforms) }}
171+
steps:
172+
- name: Checkout
173+
uses: actions/checkout@v4
174+
175+
- name: Set up QEMU
176+
uses: docker/setup-qemu-action@v3
177+
178+
- name: Set up Docker Buildx
179+
uses: docker/setup-buildx-action@v3
180+
181+
- name: Login to GHCR
182+
if: github.event_name != 'pull_request'
183+
uses: docker/login-action@v3
184+
with:
185+
registry: ghcr.io
186+
username: ${{ github.repository_owner }}
187+
password: ${{ secrets.GITHUB_TOKEN }}
188+
189+
- name: Build images and push
190+
id: bake
191+
uses: docker/bake-action@v5
192+
with:
193+
targets: |
194+
${{ matrix.variant }}-base
195+
${{ matrix.variant }}-ffmpeg
196+
provenance: true
197+
push: ${{ github.event_name != 'pull_request' }}
198+
set: |
199+
*.tags=
200+
*.platform=${{ matrix.platform }}
201+
${{ matrix.variant }}-base.cache-from=type=gha,scope=${{ matrix.variant }}-base-${{ needs.prepare.outputs.ref || github.ref }}-${{ matrix.platform }}
202+
${{ matrix.variant }}-base.cache-to=type=gha,scope=${{ matrix.variant }}-base-${{ needs.prepare.outputs.ref || github.ref }}-${{ matrix.platform }},ignore-error=true
203+
${{ matrix.variant }}-ffmpeg.cache-from=type=gha,scope=${{ matrix.variant }}-ffmpeg-${{ needs.prepare.outputs.ref || github.ref }}-${{ matrix.platform }}
204+
${{ matrix.variant }}-ffmpeg.cache-to=type=gha,scope=${{ matrix.variant }}-ffmpeg-${{ needs.prepare.outputs.ref || github.ref }}-${{ matrix.platform }},ignore-error=true
205+
*.output=type=image,"name=${{ env.GHCR_SLUG }}",push-by-digest=true,name-canonical=true
206+
env:
207+
SHA: ${{ github.sha }}
208+
VERSION: ${{ (github.ref_type == 'tag' && github.ref_name) || steps.check.outputs.ref || github.event.repository.default_branch }}
209+
PHP_VERSIONS: ${{ needs.prepare.outputs.php_versions }}
210+
211+
# Workaround for https://github.com/actions/runner/pull/2477#issuecomment-1501003600
212+
- name: Export digests
213+
run: |
214+
TARGETS=($(echo '${{ needs.prepare.outputs.targets }}' | jq -r '.[]'))
215+
for tgt in "${TARGETS[@]}"; do
216+
mkdir -p "/tmp/digest/$tgt"
217+
targetDigest=$(jq -r ".\"${{ matrix.variant }}-$tgt\".\"containerimage.digest\"" <<< "${METADATA}")
218+
echo "Digest for $tgt: ${targetDigest#sha256:}"
219+
touch "/tmp/digest/$tgt/${targetDigest#sha256:}"
220+
done
221+
env:
222+
METADATA: ${{ steps.bake.outputs.metadata }}
223+
224+
- name: Prepare path safe platform name
225+
run: |
226+
platform=${{ matrix.platform }}
227+
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
228+
229+
- name: Upload base digest
230+
uses: actions/upload-artifact@v4
231+
with:
232+
name: digest-${{ matrix.variant }}-base-${{ env.PLATFORM_PAIR }}
233+
path: /tmp/digest/base/*
234+
if-no-files-found: error
235+
retention-days: 1
236+
237+
- name: Upload ffmpeg digest
238+
uses: actions/upload-artifact@v4
239+
with:
240+
name: digest-${{ matrix.variant }}-ffmpeg-${{ env.PLATFORM_PAIR }}
241+
path: /tmp/digest/ffmpeg/*
242+
if-no-files-found: error
243+
retention-days: 1
244+
245+
merge:
246+
runs-on: ubuntu-latest
247+
if: github.event_name != 'pull_request'
248+
needs:
249+
- prepare
250+
- build
251+
permissions:
252+
contents: read
253+
packages: write
254+
strategy:
255+
fail-fast: false
256+
matrix:
257+
variant: ${{ fromJson(needs.prepare.outputs.variants) }}
258+
target: ${{ fromJson(needs.prepare.outputs.targets) }}
259+
steps:
260+
- name: Download digests
261+
uses: actions/download-artifact@v4
262+
with:
263+
path: /tmp/digest
264+
pattern: digest-${{ matrix.variant }}-${{ matrix.target }}-*
265+
merge-multiple: true
266+
267+
- name: Display structure of downloaded files
268+
run: ls -R /tmp/digest
269+
270+
- name: Set up Docker Buildx
271+
uses: docker/setup-buildx-action@v3
272+
273+
- name: Login to GHCR
274+
uses: docker/login-action@v3
275+
with:
276+
registry: ghcr.io
277+
username: ${{ github.repository_owner }}
278+
password: ${{ secrets.GITHUB_TOKEN }}
279+
280+
- name: Create manifest list and push
281+
working-directory: /tmp/digest
282+
run: |
283+
# shellcheck disable=SC2046,SC2086
284+
docker buildx imagetools create $(jq -cr '.target."${{ matrix.variant }}-${{ matrix.target }}".tags|map(select(startswith("${{ env.GHCR_SLUG }}"))| "-t " + .)|join(" ")' <<< ${METADATA}) \
285+
$(printf "${{ env.GHCR_SLUG }}@sha256:%s " *)
286+
env:
287+
METADATA: ${{ needs.prepare.outputs.metadata }}
288+
289+
- name: Inspect image
290+
run: |
291+
# shellcheck disable=SC2046,SC2086
292+
docker buildx imagetools inspect $(jq -cr '.target."${{ matrix.variant }}-${{ matrix.target }}".tags|first' <<< ${METADATA})
293+
env:
294+
METADATA: ${{ needs.prepare.outputs.metadata }}

.github/workflows/security.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: Security Check
2+
3+
on:
4+
schedule:
5+
- cron: '0 5 1,8,15,22 * *'
6+
workflow_dispatch:
7+
8+
env:
9+
BASE_IMAGE: ghcr.io/toshy/php:fpm-bookworm
10+
FFMPEG_IMAGE: ghcr.io/toshy/php:fpm-bookworm-ffmpeg
11+
12+
jobs:
13+
check:
14+
name: Image security check
15+
runs-on: ubuntu-latest
16+
steps:
17+
- name: Base - Unknown, Low, Medium and High Severity
18+
uses: aquasecurity/[email protected]
19+
with:
20+
image-ref: ${{ env.BASE_IMAGE }}
21+
severity: UNKNOWN,LOW,MEDIUM,HIGH
22+
exit-code: 0
23+
24+
- name: Base - Critical Severity
25+
uses: aquasecurity/[email protected]
26+
with:
27+
image-ref: ${{ env.BASE_IMAGE }}
28+
ignore-unfixed: true
29+
severity: CRITICAL
30+
exit-code: 1
31+
32+
- name: FFmpeg - Unknown, Low, Medium and High Severity
33+
uses: aquasecurity/[email protected]
34+
with:
35+
image-ref: ${{ env.FFMPEG_IMAGE }}
36+
severity: UNKNOWN,LOW,MEDIUM,HIGH
37+
exit-code: 0
38+
39+
- name: FFmpeg - Critical Severity
40+
uses: aquasecurity/[email protected]
41+
with:
42+
image-ref: ${{ env.FFMPEG_IMAGE }}
43+
ignore-unfixed: true
44+
severity: CRITICAL
45+
exit-code: 1

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.secrets

0 commit comments

Comments
 (0)