Skip to content

Commit dda68f6

Browse files
ci: fix offline-bundle workflow overwriting its own tooling + harden CodeArtifact creds (#2017)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 5aa129b commit dda68f6

2 files changed

Lines changed: 108 additions & 69 deletions

File tree

.github/workflows/build-offline-bundle.yml

Lines changed: 74 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -116,16 +116,18 @@ jobs:
116116
exit 1
117117
fi
118118
119-
# Platform catalog.
120-
# Fields: tag | runner | pip_platform | pip_platform_fallback
121-
# pip_platform forces the wheel tag pip downloads so Linux wheelhouses
122-
# stay manylinux2014-compatible (broad glibc support) regardless of runner.
119+
# Platform catalog. Fields: tag | runner
120+
# Each build runs on its native runner and uses `pip wheel`, so the
121+
# wheelhouse ends up tagged for the runner's platform (e.g. Linux ->
122+
# manylinux_2_28). Cross-compat via --platform was dropped because
123+
# it required --only-binary=:all:, which breaks on sdist-only deps
124+
# like pylatex.
123125
declare -a all_platforms=(
124-
"linux-x86_64|ubuntu-22.04|manylinux2014_x86_64|manylinux_2_28_x86_64"
125-
"linux-aarch64|ubuntu-22.04-arm|manylinux2014_aarch64|manylinux_2_28_aarch64"
126-
"macos-arm64|macos-14|macosx_11_0_arm64|"
127-
"macos-x86_64|macos-13|macosx_10_13_x86_64|"
128-
"windows-x86_64|windows-2022|win_amd64|"
126+
"linux-x86_64|ubuntu-22.04"
127+
"linux-aarch64|ubuntu-22.04-arm"
128+
"macos-arm64|macos-14"
129+
"macos-x86_64|macos-13"
130+
"windows-x86_64|windows-2022"
129131
)
130132
131133
case "$INPUT_PLATFORMS" in
@@ -155,9 +157,9 @@ jobs:
155157
156158
entries=()
157159
for entry in "${selected[@]}"; do
158-
IFS='|' read -r tag runner pip_platform pip_fallback <<< "$entry"
160+
IFS='|' read -r tag runner <<< "$entry"
159161
for py in "${python_list[@]}"; do
160-
entries+=("{\"platform_tag\":\"${tag}\",\"runner\":\"${runner}\",\"pip_platform\":\"${pip_platform}\",\"pip_platform_fallback\":\"${pip_fallback}\",\"python\":\"${py}\"}")
162+
entries+=("{\"platform_tag\":\"${tag}\",\"runner\":\"${runner}\",\"python\":\"${py}\"}")
161163
done
162164
done
163165
@@ -175,17 +177,30 @@ jobs:
175177
matrix: ${{ fromJSON(needs.plan-matrix.outputs.matrix) }}
176178
runs-on: ${{ matrix.runner }}
177179
steps:
178-
- uses: actions/checkout@v4
180+
# Two checkouts on purpose: the "tooling" checkout (default ref, i.e.
181+
# wherever this workflow is dispatched from — typically main) provides
182+
# the build scripts and composite actions that evolve over time. The
183+
# "source" checkout is the historical snapshot the user wants to build,
184+
# which may predate any of our tooling files.
185+
- name: Checkout workflow tooling (from dispatch ref)
186+
uses: actions/checkout@v4
187+
with:
188+
path: workflow-tooling
189+
fetch-depth: 1
190+
191+
- name: Checkout source snapshot to build
192+
uses: actions/checkout@v4
179193
with:
180194
ref: ${{ needs.resolve-ref.outputs.commit_sha }}
195+
path: source
181196
fetch-depth: 1
182197

183198
- uses: actions/setup-python@v5
184199
with:
185200
python-version: ${{ matrix.python }}
186201

187202
- name: Configure CodeArtifact auth
188-
uses: ./.github/actions/setup-codeartifact-poetry-auth
203+
uses: ./workflow-tooling/.github/actions/setup-codeartifact-poetry-auth
189204
with:
190205
aws-access-key-id: ${{ secrets.AWS_CODEARTIFACT_READ_ACCESS_KEY }}
191206
aws-secret-access-key: ${{ secrets.AWS_CODEARTIFACT_READ_ACCESS_SECRET }}
@@ -202,42 +217,33 @@ jobs:
202217
203218
- name: Make tools scripts executable
204219
shell: bash
205-
run: chmod +x tools/build_offline_wheelhouse.sh tools/verify_offline_bundle.sh
220+
run: chmod +x workflow-tooling/tools/build_offline_wheelhouse.sh workflow-tooling/tools/verify_offline_bundle.sh
206221

207222
- name: Build wheelhouse
208223
id: build
209224
shell: bash
225+
working-directory: source
210226
env:
211227
BUNDLE_NAME: ${{ needs.resolve-ref.outputs.bundle_name }}
212228
PLATFORM_TAG: ${{ matrix.platform_tag }}
213229
PY: ${{ matrix.python }}
214-
PIP_PLATFORM: ${{ matrix.pip_platform }}
215-
PIP_PLATFORM_FALLBACK: ${{ matrix.pip_platform_fallback }}
216230
POETRY_VIRTUALENVS_CREATE: "false"
231+
BUILD_SCRIPT: ${{ github.workspace }}/workflow-tooling/tools/build_offline_wheelhouse.sh
217232
run: |
218233
set -euo pipefail
219-
bundle_dir="bundle/${BUNDLE_NAME}-${PLATFORM_TAG}-py${PY}"
234+
bundle_dir="${{ github.workspace }}/bundle/${BUNDLE_NAME}-${PLATFORM_TAG}-py${PY}"
220235
mkdir -p "$bundle_dir"
221236
echo "bundle_dir=${bundle_dir}" >> "$GITHUB_OUTPUT"
222237
223-
args=(
224-
--python python
225-
--output "$bundle_dir"
226-
--python-version "${PY}"
227-
--pip-platform "${PIP_PLATFORM}"
228-
)
229-
if [[ -n "${PIP_PLATFORM_FALLBACK}" ]]; then
230-
args+=(--pip-platform-fallback "${PIP_PLATFORM_FALLBACK}")
231-
fi
232-
233-
tools/build_offline_wheelhouse.sh "${args[@]}"
238+
"$BUILD_SCRIPT" --python python --output "$bundle_dir"
234239
235240
- name: Verify offline installation (smoke test)
236241
shell: bash
237242
env:
238243
BUNDLE_DIR: ${{ steps.build.outputs.bundle_dir }}
244+
VERIFY_SCRIPT: ${{ github.workspace }}/workflow-tooling/tools/verify_offline_bundle.sh
239245
run: |
240-
tools/verify_offline_bundle.sh \
246+
"$VERIFY_SCRIPT" \
241247
--python python \
242248
--wheelhouse "${BUNDLE_DIR}/wheelhouse"
243249
@@ -249,11 +255,21 @@ jobs:
249255
COMMIT_SHA: ${{ needs.resolve-ref.outputs.commit_sha }}
250256
SHORT_SHA: ${{ needs.resolve-ref.outputs.short_sha }}
251257
PLATFORM_TAG: ${{ matrix.platform_tag }}
252-
PIP_PLATFORM: ${{ matrix.pip_platform }}
258+
RUNNER: ${{ matrix.runner }}
253259
PY: ${{ matrix.python }}
254260
run: |
255261
set -euo pipefail
256262
built_at="$(date -u +'%Y-%m-%dT%H:%M:%SZ')"
263+
264+
# Derive platform-specific compatibility note.
265+
case "${PLATFORM_TAG}" in
266+
linux-*) compat_note="Linux with glibc >= 2.28 (Ubuntu 18.04+ / RHEL 8+ / Debian 10+)." ;;
267+
macos-x86_64) compat_note="macOS Intel (matches macosx_* tags in the wheelhouse)." ;;
268+
macos-arm64) compat_note="macOS Apple Silicon (matches macosx_*_arm64 tags in the wheelhouse)." ;;
269+
windows-*) compat_note="Windows x86_64." ;;
270+
*) compat_note="See wheel filenames in \`wheelhouse/\` for the exact platform tags." ;;
271+
esac
272+
257273
cat > "${BUNDLE_DIR}/INSTALL.md" <<EOF
258274
# Flow360 Offline Bundle
259275
@@ -262,10 +278,12 @@ jobs:
262278
| Source ref (input) | \`${INPUT_REF}\` |
263279
| Source commit | \`${COMMIT_SHA}\` |
264280
| Target platform | \`${PLATFORM_TAG}\` |
265-
| pip platform tag | \`${PIP_PLATFORM}\` |
281+
| Built on runner | \`${RUNNER}\` |
266282
| Target Python | \`${PY}\` |
267283
| Built at (UTC) | \`${built_at}\` |
268284
285+
**Compatibility:** ${compat_note}
286+
269287
## Install
270288
271289
\`\`\`bash
@@ -278,6 +296,29 @@ jobs:
278296
- \`requirements.txt\` — exact pinned versions (exported from \`poetry.lock\`)
279297
EOF
280298
299+
- name: Scan bundle for leaked credentials
300+
shell: bash
301+
env:
302+
BUNDLE_DIR: ${{ steps.build.outputs.bundle_dir }}
303+
run: |
304+
set -euo pipefail
305+
# Refuse to ship a bundle that contains CodeArtifact auth tokens,
306+
# AWS-style user:pass URL patterns, or index-url directives.
307+
# -I skips binary files (e.g. wheelhouse/*.whl), whose METADATA
308+
# can legitimately mention pip flags in upstream README text and
309+
# would otherwise trigger false positives.
310+
bad=$(grep -rEnI \
311+
-e 'aws:[A-Za-z0-9+/=._-]{20,}@' \
312+
-e '--(extra-)?index-url' \
313+
-e 'codeartifact\.[a-z0-9.-]+amazonaws\.com.*:.*@' \
314+
"$BUNDLE_DIR" || true)
315+
if [[ -n "$bad" ]]; then
316+
echo "::error ::Bundle contains credential-like content; refusing to package."
317+
echo "$bad"
318+
exit 1
319+
fi
320+
echo "Bundle credential scan clean."
321+
281322
- name: Package tarball
282323
id: pack
283324
shell: bash
@@ -304,7 +345,7 @@ jobs:
304345
COMMIT_SHA: ${{ needs.resolve-ref.outputs.commit_sha }}
305346
SHORT_SHA: ${{ needs.resolve-ref.outputs.short_sha }}
306347
PLATFORM_TAG: ${{ matrix.platform_tag }}
307-
PIP_PLATFORM: ${{ matrix.pip_platform }}
348+
RUNNER: ${{ matrix.runner }}
308349
PY: ${{ matrix.python }}
309350
TARBALL: ${{ steps.pack.outputs.tarball }}
310351
SHA256: ${{ steps.pack.outputs.sha256 }}
@@ -323,7 +364,7 @@ jobs:
323364
echo "| Source ref (input) | \`${INPUT_REF}\` |"
324365
echo "| Commit SHA | \`${COMMIT_SHA}\` (${SHORT_SHA}) |"
325366
echo "| Platform | \`${PLATFORM_TAG}\` |"
326-
echo "| pip platform tag | \`${PIP_PLATFORM}\` |"
367+
echo "| Built on runner | \`${RUNNER}\` |"
327368
echo "| Python | \`${PY}\` |"
328369
echo "| Wheels packaged | ${wheel_count} |"
329370
echo "| Tarball | \`${TARBALL}\` (${size_mb} MB) |"

tools/build_offline_wheelhouse.sh

Lines changed: 34 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,44 @@
11
#!/usr/bin/env bash
2-
# Build an offline wheelhouse for flow360 targeting a specific (platform, Python).
2+
# Build an offline wheelhouse for flow360 using the current runner's Python.
33
#
44
# Produces:
55
# <output_dir>/
66
# wheelhouse/*.whl -- every runtime dep wheel + flow360 wheel
77
# requirements.txt -- pinned versions exported from poetry.lock
88
#
99
# Usage:
10-
# tools/build_offline_wheelhouse.sh \
11-
# --python <python_bin> \
12-
# --output <bundle_dir> \
13-
# --python-version <3.10|3.11|3.12|3.13> \
14-
# --pip-platform <manylinux2014_x86_64|macosx_11_0_arm64|win_amd64|...> \
15-
# [--pip-platform-fallback <tag>]
10+
# tools/build_offline_wheelhouse.sh --python <python_bin> --output <bundle_dir>
1611
#
17-
# --pip-platform explicitly pins the wheel platform tag pip downloads. Using
18-
# pip download --platform ensures Linux wheelhouses are manylinux2014-compatible
19-
# (wide glibc support) even when built on a newer runner.
12+
# Uses `pip wheel` which transparently handles both wheel-only deps and
13+
# sdist-only deps (building the latter locally into a wheel). The resulting
14+
# wheelhouse contains wheels tagged for the runner's native platform:
15+
#
16+
# Linux x86_64 / aarch64 -> manylinux_2_28 (glibc >= 2.28)
17+
# macOS x86_64 / arm64 -> macosx_*
18+
# Windows x86_64 -> win_amd64
19+
#
20+
# Pure-python deps (including any locally-built sdists) land as py3-none-any.
2021

2122
set -euo pipefail
2223

2324
PYTHON_BIN=""
2425
OUTPUT_DIR=""
25-
PY_VER=""
26-
PIP_PLATFORM=""
27-
PIP_PLATFORM_FALLBACK=""
2826

2927
while [[ $# -gt 0 ]]; do
3028
case "$1" in
31-
--python) PYTHON_BIN="$2"; shift 2 ;;
32-
--output) OUTPUT_DIR="$2"; shift 2 ;;
33-
--python-version) PY_VER="$2"; shift 2 ;;
34-
--pip-platform) PIP_PLATFORM="$2"; shift 2 ;;
35-
--pip-platform-fallback) PIP_PLATFORM_FALLBACK="$2"; shift 2 ;;
29+
--python) PYTHON_BIN="$2"; shift 2 ;;
30+
--output) OUTPUT_DIR="$2"; shift 2 ;;
3631
*) echo "Unknown arg: $1" >&2; exit 2 ;;
3732
esac
3833
done
3934

40-
[[ -z "$PYTHON_BIN" ]] && { echo "ERROR: --python required" >&2; exit 2; }
41-
[[ -z "$OUTPUT_DIR" ]] && { echo "ERROR: --output required" >&2; exit 2; }
42-
[[ -z "$PY_VER" ]] && { echo "ERROR: --python-version required" >&2; exit 2; }
43-
[[ -z "$PIP_PLATFORM" ]] && { echo "ERROR: --pip-platform required" >&2; exit 2; }
35+
[[ -z "$PYTHON_BIN" ]] && { echo "ERROR: --python required" >&2; exit 2; }
36+
[[ -z "$OUTPUT_DIR" ]] && { echo "ERROR: --output required" >&2; exit 2; }
4437

4538
wheelhouse="${OUTPUT_DIR}/wheelhouse"
4639
req_file="${OUTPUT_DIR}/requirements.txt"
4740
mkdir -p "$wheelhouse"
4841

49-
platform_args=(--platform "$PIP_PLATFORM")
50-
if [[ -n "$PIP_PLATFORM_FALLBACK" ]]; then
51-
platform_args+=(--platform "$PIP_PLATFORM_FALLBACK")
52-
fi
53-
5442
echo "::group::Python and pip versions"
5543
"$PYTHON_BIN" --version
5644
"$PYTHON_BIN" -m pip --version
@@ -67,22 +55,32 @@ echo "::group::Export pinned runtime requirements from poetry.lock"
6755
--only main \
6856
--format requirements.txt \
6957
--output "$req_file"
58+
59+
# Strip any index-url directives poetry may have emitted for private sources
60+
# (e.g. CodeArtifact). Credentials live in the PIP_EXTRA_INDEX_URL env var
61+
# instead, so requirements.txt stays clean and safe to ship inside the bundle.
62+
python_strip='
63+
import re, sys, pathlib
64+
p = pathlib.Path(sys.argv[1])
65+
orig = p.read_text()
66+
cleaned = re.sub(r"^--(extra-)?index-url[ \t].*\n", "", orig, flags=re.M)
67+
p.write_text(cleaned)
68+
sys.stderr.write(f"stripped index-url directives: {orig.count(chr(10)) - cleaned.count(chr(10))} line(s)\n")
69+
'
70+
"$PYTHON_BIN" -c "$python_strip" "$req_file"
71+
7072
echo "Exported $(wc -l < "$req_file") lines to ${req_file}"
7173
echo "--- first 40 lines ---"
7274
head -40 "$req_file"
7375
echo "::endgroup::"
7476

75-
echo "::group::Download dependency wheels for ${PIP_PLATFORM} py${PY_VER}"
76-
"$PYTHON_BIN" -m pip download \
77-
--dest "$wheelhouse" \
78-
--requirement "$req_file" \
79-
--only-binary=:all: \
80-
--python-version "$PY_VER" \
81-
--implementation cp \
82-
"${platform_args[@]}"
77+
echo "::group::Build dependency wheelhouse"
78+
"$PYTHON_BIN" -m pip wheel \
79+
--wheel-dir "$wheelhouse" \
80+
--requirement "$req_file"
8381
echo "::endgroup::"
8482

85-
echo "::group::Build flow360 wheel (pure-python, platform-agnostic)"
83+
echo "::group::Build flow360 wheel"
8684
"$PYTHON_BIN" -m pip wheel \
8785
--wheel-dir "$wheelhouse" \
8886
--no-deps \

0 commit comments

Comments
 (0)