-
Notifications
You must be signed in to change notification settings - Fork 0
163 lines (147 loc) · 6.51 KB
/
Copy pathpublish.yml
File metadata and controls
163 lines (147 loc) · 6.51 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
name: Publish Images
# The automatic path is CI -> Publish Images -> Deploy Delta, chained on
# workflow_run. That chain has a single point of failure outside our control:
# when GitHub throttles webhook delivery during an Actions incident, no
# workflow_run event fires and nothing publishes, so Deploy Delta has no image
# to deploy and main sits undeployed with no way to force it. workflow_dispatch
# goes through the REST API instead, which stays up independently, so this is
# the manual door for exactly that case.
on:
workflow_run:
workflows:
- CI
branches:
- main
types:
- completed
workflow_dispatch:
inputs:
sha:
description: Full commit SHA on main to build (defaults to the tip of main)
required: false
concurrency:
group: publish-${{ github.event.workflow_run.head_sha || inputs.sha || github.sha }}
cancel-in-progress: false
permissions:
contents: read
packages: write
jobs:
publish:
# Both paths publish only main. The automatic one gets that from the
# workflow_run fields; the manual one is pinned to the main ref here and
# then has its resolved SHA checked for ancestry below, so a dispatch
# cannot publish a commit that never landed on main.
if: >
(github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main') ||
(
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.name == 'CI' &&
github.event.workflow_run.head_branch == 'main' &&
github.event.workflow_run.event == 'push'
)
runs-on: ubuntu-latest
steps:
- name: Resolve SHA
id: sha
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
sha="${{ inputs.sha }}"
if [[ -z "$sha" ]]; then
sha="${{ github.sha }}"
fi
else
sha="${{ github.event.workflow_run.head_sha }}"
fi
if [[ ! "$sha" =~ ^[0-9a-fA-F]{40}$ ]]; then
echo "invalid full commit SHA: $sha" >&2
exit 1
fi
# A hand-supplied SHA is the one input here that is not already
# constrained to main, so confirm it is on main before we build an
# image Deploy Delta would treat as a production release. Comparing
# main to the commit reports "identical" when it is the tip and
# "behind" when it is an earlier commit on main; anything else means
# it lives on some other branch.
if [[ "${{ github.event_name }}" == "workflow_dispatch" && -n "${{ inputs.sha }}" ]]; then
status="$(gh api "repos/${GITHUB_REPOSITORY}/compare/main...${sha}" --jq .status)"
if [[ "$status" != "identical" && "$status" != "behind" ]]; then
echo "refusing to publish $sha: not a commit on main (compare status: $status)" >&2
exit 1
fi
fi
echo "value=$sha" >> "$GITHUB_OUTPUT"
- uses: actions/checkout@v4
with:
ref: ${{ steps.sha.outputs.value }}
- name: Compute image names
id: image
run: |
repo="${GITHUB_REPOSITORY,,}"
echo "backend=ghcr.io/${repo}-backend" >> "$GITHUB_OUTPUT"
echo "frontend=ghcr.io/${repo}-frontend" >> "$GITHUB_OUTPUT"
echo "embeddings=ghcr.io/${repo}-embeddings" >> "$GITHUB_OUTPUT"
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# The sidecar image is tagged by content rather than by commit: the git
# tree hash of embeddings/ changes only when something in that directory
# changes. deploy/scripts/deploy.sh derives the identical value, so the two
# cannot disagree.
- name: Compute the embeddings tag
id: embeddings_tag
run: echo "value=$(git rev-parse HEAD:embeddings)" >> "$GITHUB_OUTPUT"
# This image bakes in ~130MB of model weights, so building it is by far the
# slowest step here, and it was rebuilt on every push to main, including
# the overwhelming majority that touch nothing but Go or TypeScript. A tag
# that already exists is by definition built from identical content.
- name: Check whether the embeddings image already exists
id: embeddings_exists
run: |
if docker manifest inspect "${{ steps.image.outputs.embeddings }}:${{ steps.embeddings_tag.outputs.value }}" >/dev/null 2>&1; then
echo "skipping the embeddings build: ${{ steps.embeddings_tag.outputs.value }} is already published"
echo "value=true" >> "$GITHUB_OUTPUT"
else
echo "value=false" >> "$GITHUB_OUTPUT"
fi
- name: Build and publish backend
uses: docker/build-push-action@v6
with:
context: ./server
file: ./server/Dockerfile
push: true
tags: ${{ steps.image.outputs.backend }}:${{ steps.sha.outputs.value }}
labels: |
org.opencontainers.image.source=https://github.com/${{ github.repository }}
org.opencontainers.image.revision=${{ steps.sha.outputs.value }}
cache-from: type=gha,scope=backend
cache-to: type=gha,mode=max,scope=backend
- name: Build and publish frontend
uses: docker/build-push-action@v6
with:
context: ./frontend
file: ./frontend/Dockerfile
push: true
tags: ${{ steps.image.outputs.frontend }}:${{ steps.sha.outputs.value }}
labels: |
org.opencontainers.image.source=https://github.com/${{ github.repository }}
org.opencontainers.image.revision=${{ steps.sha.outputs.value }}
cache-from: type=gha,scope=frontend
cache-to: type=gha,mode=max,scope=frontend
- name: Build and publish embeddings sidecar
if: steps.embeddings_exists.outputs.value != 'true'
uses: docker/build-push-action@v6
with:
context: ./embeddings
file: ./embeddings/Dockerfile
push: true
tags: ${{ steps.image.outputs.embeddings }}:${{ steps.embeddings_tag.outputs.value }}
labels: |
org.opencontainers.image.source=https://github.com/${{ github.repository }}
org.opencontainers.image.revision=${{ steps.sha.outputs.value }}
cache-from: type=gha,scope=embeddings
cache-to: type=gha,mode=max,scope=embeddings