-
Notifications
You must be signed in to change notification settings - Fork 78
136 lines (124 loc) · 5.87 KB
/
Copy pathfly-deploy.yml
File metadata and controls
136 lines (124 loc) · 5.87 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
name: Deploy to Fly
# Auto-deploy the NodeTool server to Fly.io (https://nodetool.fly.dev).
#
# The deploy unit is the GHCR image published by the "Docker" workflow
# (.github/workflows/docker.yml). We therefore trigger on that workflow's
# COMPLETION rather than on push, so we never release an image before it has
# been built + pushed. On success we release the image tag that corresponds to
# the exact triggering commit — `main-<shortsha>`, which "Docker" publishes via
# `type=sha,prefix={{branch}}-`. We deliberately do NOT deploy `:latest` on
# workflow_run: if two main builds finish out of order, `:latest` may point at a
# newer commit and we'd deploy the wrong image. `:latest` is used only for a
# manual re-deploy (workflow_dispatch).
#
# Ring 1 gate (docs/RELIABILITY_ARCHITECTURE.md §11, docs/RELIABILITY_TASKS.md
# F2): a deploy must also wait on "User Journeys"' `reliability-ring1` job —
# the full hermetic + differential + packaged-journey suite — for the SAME
# commit, not just on the Docker build. Both "Docker" and "User Journeys" fire
# independently off the same `push: main`, so this workflow now also
# workflow_run-triggers on "User Journeys" completion; whichever of the two
# finishes second is the one that actually reaches the `deploy` job, because
# the `gate` job below polls the OTHER workflow's status for the same
# head_sha and only proceeds once both are green. This is the least invasive
# option that doesn't require restructuring either upstream workflow into a
# reusable `workflow_call` (which would also entail duplicating docker.yml's
# and user-journeys.yml's own triggers).
#
# Requires a repo secret FLY_API_TOKEN scoped to the `nodetool` app:
# fly tokens create deploy -a nodetool --expiry 8760h
on:
workflow_run:
workflows: ["Docker", "User Journeys"]
types: [completed]
branches: [main]
# Manual re-deploy of the current :latest image.
workflow_dispatch:
# `gate` additionally needs `actions: read` to poll the sibling workflow's run
# status via the GitHub API (GITHUB_TOKEN, not FLY_API_TOKEN).
permissions:
contents: read
actions: read
concurrency:
group: fly-deploy
cancel-in-progress: true
jobs:
# Confirms BOTH "Docker" and "User Journeys" (specifically its
# reliability-ring1 job) succeeded for the exact commit that triggered this
# run, regardless of which of the two workflow_run events fired us. Polls
# because the two workflows run independently and finish in either order —
# this job is a no-op wait until the other one lands (or times out).
gate:
name: Wait for Docker build + reliability Ring 1
runs-on: ubuntu-latest
if: github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success'
timeout-minutes: 65
outputs:
sha: ${{ github.event.workflow_run.head_sha }}
env:
GH_TOKEN: ${{ github.token }}
steps:
- name: Poll for both workflows' success on this commit
shell: bash
run: |
set -euo pipefail
sha="${{ github.event.workflow_run.head_sha }}"
repo="${{ github.repository }}"
conclusion_for() {
local workflow_file="$1"
gh api "repos/${repo}/actions/workflows/${workflow_file}/runs?head_sha=${sha}&status=completed&per_page=1" \
--jq '.workflow_runs[0].conclusion // "pending"'
}
for attempt in $(seq 1 60); do
docker_conclusion="$(conclusion_for docker.yml)"
journeys_conclusion="$(conclusion_for user-journeys.yml)"
echo "attempt ${attempt}/60: docker.yml=${docker_conclusion} user-journeys.yml=${journeys_conclusion}"
if [ "${docker_conclusion}" = "success" ] && [ "${journeys_conclusion}" = "success" ]; then
echo "Both workflows succeeded for ${sha}."
exit 0
fi
if [ "${docker_conclusion}" = "failure" ] || [ "${docker_conclusion}" = "cancelled" ]; then
echo "::error::docker.yml concluded '${docker_conclusion}' for ${sha}; not deploying."
exit 1
fi
if [ "${journeys_conclusion}" = "failure" ] || [ "${journeys_conclusion}" = "cancelled" ]; then
echo "::error::user-journeys.yml (reliability-ring1) concluded '${journeys_conclusion}' for ${sha}; not deploying."
exit 1
fi
sleep 60
done
echo "::error::Timed out waiting for docker.yml and user-journeys.yml to both complete for ${sha}."
exit 1
deploy:
name: Deploy server
needs: [gate]
runs-on: ubuntu-latest
# Skip when the image build (or the reliability Ring 1 gate) failed/was
# cancelled. workflow_dispatch always runs (manual re-deploy); on
# workflow_run, `gate` must have completed successfully.
if: >
always() &&
(github.event_name == 'workflow_dispatch' ||
needs.gate.result == 'success')
steps:
- uses: actions/checkout@v7
with:
# Deploy the fly.toml as it was at the built commit (falls back to the
# dispatch ref for manual runs).
ref: ${{ needs.gate.outputs.sha || github.sha }}
- name: Resolve image tag
id: img
run: |
set -euo pipefail
if [ "${{ github.event_name }}" = "workflow_run" ]; then
# Pin to the exact commit that was just built (main-<7-char-sha>),
# matching docker.yml's `type=sha,prefix={{branch}}-`.
sha="${{ github.event.workflow_run.head_sha }}"
echo "ref=ghcr.io/nodetool-ai/nodetool:main-${sha:0:7}" >> "$GITHUB_OUTPUT"
else
echo "ref=ghcr.io/nodetool-ai/nodetool:latest" >> "$GITHUB_OUTPUT"
fi
- uses: superfly/flyctl-actions/setup-flyctl@v1.4
- name: fly deploy (server)
run: flyctl deploy --config fly.toml --image "${{ steps.img.outputs.ref }}" --ha=false
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}