-
Notifications
You must be signed in to change notification settings - Fork 0
281 lines (243 loc) · 10.5 KB
/
Copy pathbootstrap.yml
File metadata and controls
281 lines (243 loc) · 10.5 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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
# -----------------------------------------------------------------------------------------
# GitHub Actions Workflow: Bootstrap
#
# Description:
# This workflow initiates the CI/CD pipeline by resolving the pipeline mode,
# determining the version via `semantic-release`, and triggering the downstream
# `build` workflow. It is intended to be the first stage in an automated promotion flow.
#
# Triggers:
# - push to:
# - `main`: triggers full release flow (unless overridden by commit message with `[dry-run ci]`)
# - `ci/**`: triggers CI test flow
#
# Behavior:
# - Resolves the pipeline mode based on the Git branch and commit message:
# - `main` → `release`
# - `ci/**` → `ci-test`
# - `[dry-run ci]` in commit message → overrides to `dry-run`
#
# - Installs and runs `semantic-release` to determine the next version:
# - In `release` mode: creates a real tag and release
# - In `ci-test` mode: appends timestamp and pushes CI-only tag
# - In `dry-run`: simulates release and extracts the candidate version
#
# - Outputs:
# - `ref`: Tag ref or SHA depending on mode
# - `version`: Resolved version (e.g., `1.2.3`, `1.2.3-ci-test-2025-07-31-12-00-00`)
# - `mode`: Resolved pipeline mode
#
# - Triggers the downstream `build` workflow via `repository_dispatch` with resolved values.
#
# - Adds success or failure tags for traceability:
# - `<version>/CI/bootstrap/PASS`
# - `<version>/CI/bootstrap/FAIL`
#
# Requirements:
# - `GH_PAT` secret: Required to push tags and trigger downstream workflows
# - Node.js
# - Global installation of semantic-release and plugins
# - Local scripts (e.g., `bootstrap.sh`) for logging and setup
#
# Notes:
# - Fallbacks to `GITHUB_SHA` for `ref` in dry-run mode
# - Supports local runs via `act`, skipping real workflow dispatch
# -----------------------------------------------------------------------------------------
name: Bootstrap
on:
push:
branches:
- 'main'
- 'ci/**'
jobs:
bootstrap:
name: Resolve and bootstrap pipeline
runs-on: ubuntu-latest
permissions:
contents: write
actions: write
outputs:
ref: ${{ steps.set-ref.outputs.ref }}
version: ${{ steps.ci_tag.outputs.version || steps.semantic_release.outputs.version }}
mode: ${{ steps.resolve.outputs.mode }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Update permissions for scripts
run: find ./devops/scripts -type f -name '*.sh' -exec chmod +x {} +
- name: Resolve mode
id: resolve
run: |
source ./devops/scripts/bootstrap.sh
log INFO "Resolving mode..."
BRANCH="${GITHUB_REF#refs/heads/}"
COMMIT_MSG=$(git log -1 --pretty=%B)
if [[ "${GITHUB_REF}" == "refs/heads/main" ]]; then
MODE="release"
elif [[ "$BRANCH" == ci/* ]]; then
MODE="ci-test"
else
MODE="dry-run"
fi
if [[ "$COMMIT_MSG" == *"[dry-run ci]"* ]]; then
MODE="dry-run"
fi
echo "mode=$MODE" >> $GITHUB_OUTPUT
log INFO "Resolved mode: $MODE"
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
- name: Install semantic-release and required plugins
shell: bash
run: |
source ./devops/scripts/bootstrap.sh
log INFO "📦 Installing Semantic Release dependencies..."
npm install -g semantic-release @semantic-release/git @semantic-release/commit-analyzer \
@semantic-release/exec @semantic-release/changelog conventional-changelog-conventionalcommits
- name: Tag git with first version if not present
if: ${{ steps.resolve.outputs.mode == 'release' && !github.event_name == 'pull_request' }}
shell: bash
run: |
source ./devops/scripts/bootstrap.sh
log INFO "Checking for existing semantic version tag..."
if ! git tag --list | grep -q '^v[0-9]\+\.[0-9]\+\.[0-9]\+$'; then
log INFO "No semantic version tag found – creating v0.1.0 baseline"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag v0.1.0
git push origin v0.1.0
else
log INFO "Semantic version tag already exists, skipping creation."
fi
- name: Run semantic-release
id: semantic_release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
source ./devops/scripts/bootstrap.sh
MODE="${{ steps.resolve.outputs.mode }}"
DRY_FLAG=""
if [[ "$MODE" != "release" ]]; then
DRY_FLAG="--dry-run"
fi
log INFO "Running semantic-release (mode: $MODE, dry-flag: $DRY_FLAG)..."
npx semantic-release $DRY_FLAG | tee output.log
SEMANTIC_RELEASE_EXIT_CODE=$?
# Early exit if no new version is released
if grep -qi "no new version is released" output.log; then
log INFO "No new release needed. Setting version to empty."
echo "version=" >> $GITHUB_OUTPUT
exit 0
fi
if [ "$SEMANTIC_RELEASE_EXIT_CODE" -ne 0 ]; then
log ERROR "semantic-release failed with exit code $SEMANTIC_RELEASE_EXIT_CODE"
exit $SEMANTIC_RELEASE_EXIT_CODE
fi
VERSION=""
if [[ "$MODE" == "release" ]] && [ -f ./version ]; then
VERSION=$(cat ./version)
log INFO "Extracted version from file: $VERSION"
else
VERSION=$(grep -oP '(?<=Release note for version )\d+\.\d+\.\d+' output.log | tail -n 1)
if [[ -z "$VERSION" ]]; then
VERSION=$(grep -oP '(?<=next release version is )[^ ]+' output.log | tail -n 1)
fi
if [[ -n "$VERSION" ]]; then
log INFO "Extracted version from logs: $VERSION"
fi
fi
if [[ -z "$VERSION" ]]; then
log WARN "No version could be extracted. Setting version to empty."
echo "version=" >> $GITHUB_OUTPUT
else
echo "version=$VERSION" >> $GITHUB_OUTPUT
log INFO "Resolved version: $VERSION"
fi
- name: Set version and tag repo in ci-test mode
id: ci_tag
if: ${{ steps.resolve.outputs.mode == 'ci-test' && steps.semantic_release.outputs.version != '' }}
env:
VERSION: ${{ steps.semantic_release.outputs.version }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
source ./devops/scripts/bootstrap.sh
DATE=$(date -u +"%Y-%m-%d-%H-%M-%S")
VERSION="${VERSION}-ci-test-$DATE"
TAG="v${VERSION}"
log INFO "Creating CI tag $TAG"
git config user.name "GitHub Actions"
git config user.email "actions@github.com"
git tag "$TAG"
git push "https://${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" "$TAG"
log INFO "Pushed tag: $TAG"
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Set ref
id: set-ref
if: ${{ steps.semantic_release.outputs.version != '' }}
env:
MODE: ${{ steps.resolve.outputs.mode }}
VERSION_CI: ${{ steps.ci_tag.outputs.version }}
VERSION_SR: ${{ steps.semantic_release.outputs.version }}
shell: bash
run: |
source ./devops/scripts/bootstrap.sh
# Prefer ci_tag version if present, else fall back to semantic_release
VERSION="${VERSION_CI:-$VERSION_SR}"
if [[ "$MODE" == "dry-run" ]]; then
log INFO "Running in 'dry-run' mode, setting ref to GITHUB_SHA: ${GITHUB_SHA}"
echo "ref=${GITHUB_SHA}" >> $GITHUB_OUTPUT
else
log INFO "Running in '$MODE' mode, setting ref to tag: refs/tags/v${VERSION}"
echo "ref=refs/tags/v${VERSION}" >> $GITHUB_OUTPUT
fi
- name: Debug outputs
shell: bash
run: |
source ./devops/scripts/bootstrap.sh
log INFO "Resolved ref: ${{ steps.set-ref.outputs.ref }}"
log INFO "Resolved version: ${{ steps.ci_tag.outputs.version || steps.semantic_release.outputs.version }}"
log INFO "Resolved mode: ${{ steps.resolve.outputs.mode }}"
- name: Trigger build workflow
if: ${{ steps.semantic_release.outputs.version != '' && env.ACT != 'true' }}
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.GH_PAT }}
event-type: trigger-build
client-payload: >-
{
"ref": "${{ steps.set-ref.outputs.ref }}",
"version": "${{ steps.ci_tag.outputs.version || steps.semantic_release.outputs.version }}",
"mode": "${{ steps.resolve.outputs.mode }}"
}
- name: Log the dispatch if platform is 'act'
if: ${{ env.ACT == 'true' }}
run: |
source ./devops/scripts/bootstrap.sh
log INFO "Running in 'act' mode, simulating build workflow call."
log INFO "Dispatching event 'trigger-build' with payload:"
echo " ref: ${{ steps.set-ref.outputs.ref }}"
echo " version: ${{ steps.ci_tag.outputs.version || steps.semantic_release.outputs.version }}"
echo " mode: ${{ steps.resolve.outputs.mode }}"
tag-bootstrap-success:
needs: [bootstrap]
if: ${{ success() && needs.bootstrap.outputs.version != '' }}
uses: ./.github/workflows/tag-git.yml
with:
tag_name: "${{ needs.bootstrap.outputs.version }}/CI/bootstrap/PASS"
ref: "${{ needs.bootstrap.outputs.ref }}"
message: "Bootstrap workflow succeeded for version ${{ needs.bootstrap.outputs.version }}"
dry_run: ${{ needs.bootstrap.outputs.mode == 'dry-run' }}
tag-bootstrap-fail:
needs: [ bootstrap ]
if: ${{ needs.bootstrap.outputs.version != '' && (failure() || cancelled()) }}
uses: ./.github/workflows/tag-git.yml
with:
tag_name: "${{ needs.bootstrap.outputs.version }}/CI/bootstrap/FAIL"
ref: "${{ needs.bootstrap.outputs.ref }}"
message: "Bootstrap workflow failed for version ${{ needs.bootstrap.outputs.version }}"
dry_run: ${{ needs.bootstrap.outputs.mode == 'dry-run' }}