-
Notifications
You must be signed in to change notification settings - Fork 0
175 lines (156 loc) · 6.03 KB
/
Copy pathpublish-image.yml
File metadata and controls
175 lines (156 loc) · 6.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
name: Publish image
# Builds the reference sandbox image and pushes it to ghcr.
#
# openblox pulls an absent image on create, so publishing is what makes the
# default work on a host that has never seen it. A release tag publishes the
# matching image version; main publishes :edge so the tip is always testable
# without cutting a release.
#
# The digest is printed to the job summary. Pin THAT downstream, not the tag: an
# image is the sandbox's entire userland, and whoever controls the registry can
# repoint a tag.
#
# Every ${{ }} value is passed through env rather than interpolated into a run:
# script — workflow_dispatch inputs are attacker-controlled by anyone who can
# trigger the workflow, and this job holds a registry-write token.
on:
pull_request:
paths:
- 'image/**'
- '.github/workflows/publish-image.yml'
push:
branches: [main]
paths:
- 'image/**'
- '.github/workflows/publish-image.yml'
tags: ['v*']
workflow_dispatch:
inputs:
tag:
description: 'Version tag to publish (e.g. 0.1.0). Defaults to :edge.'
type: string
required: false
permissions:
contents: read
packages: write
concurrency:
group: publish-image
cancel-in-progress: false
defaults:
run:
shell: bash
jobs:
# A pull request builds the image and asserts the contract, but never pushes.
# Without this a broken Dockerfile is only discovered after it reaches main,
# where the failure is a missing image rather than a red check.
verify:
name: Build & Verify
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
# Single-platform and loaded into the local daemon: buildx cannot --load a
# multi-platform manifest, and running the contract check matters more here
# than proving both architectures build.
- uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: image
platforms: linux/amd64
push: false
load: true
tags: openblox-sandbox:pr
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Verify the contract
run: |
docker run --rm --entrypoint /bin/sh openblox-sandbox:pr -c \
'command -v bash && command -v python3 && command -v nc && [ "$(id -u)" -ne 0 ]'
publish:
name: Build & Push
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# Resolve the version BEFORE anything can push. A tag push publishes that
# version plus :latest; anything else is :edge. A dispatch override is
# validated, because it reaches a shell holding a write token.
- name: Resolve tags
id: meta
env:
TAG_INPUT: ${{ inputs.tag }}
REF_NAME: ${{ github.ref_name }}
REF_TYPE: ${{ github.ref_type }}
run: |
set -euo pipefail
image="ghcr.io/${GITHUB_REPOSITORY_OWNER,,}/openblox-sandbox"
version=""
if [ -n "$TAG_INPUT" ]; then
version="${TAG_INPUT#v}"
elif [ "$REF_TYPE" = tag ]; then
version="${REF_NAME#v}"
fi
if [ -n "$version" ]; then
if ! printf '%s' "$version" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$'; then
echo "refusing version '$version': expected semver like 1.2.3" >&2
exit 1
fi
printf 'tags=%s:%s,%s:latest\n' "$image" "$version" "$image" >> "$GITHUB_OUTPUT"
else
printf 'tags=%s:edge\n' "$image" >> "$GITHUB_OUTPUT"
fi
- uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
- uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
- uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
id: push
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: image
# arm64 as well as amd64: Apple Silicon is where most people will try
# this first, and emulating the whole sandbox to run a demo is not a
# first impression worth having.
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
provenance: true
sbom: true
cache-from: type=gha
cache-to: type=gha,mode=max
# Smoke-test the published image against the contract the Dockerfile
# asserts at build time. The build only proves it held on the BUILD
# platform; this proves the pushed manifest runs.
- name: Verify the pushed image
env:
TAGS: ${{ steps.meta.outputs.tags }}
run: |
set -euo pipefail
ref="${TAGS%%,*}"
docker pull "$ref"
docker run --rm --entrypoint /bin/sh "$ref" -c \
'command -v bash && command -v python3 && command -v nc && [ "$(id -u)" -ne 0 ]'
echo "contract holds for $ref"
- name: Report the digest to pin
env:
DIGEST: ${{ steps.push.outputs.digest }}
TAGS: ${{ steps.meta.outputs.tags }}
run: |
{
echo '## Sandbox image published'
echo
echo 'Tags:'
echo
printf '%s\n' "$TAGS" | tr ',' '\n' | sed 's/^/- `/; s/$/`/'
echo
echo 'Pin this digest rather than a tag:'
echo
echo '```'
printf '%s@%s\n' "ghcr.io/${GITHUB_REPOSITORY_OWNER,,}/openblox-sandbox" "$DIGEST"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"