Skip to content

Commit d61d431

Browse files
Merge pull request #87 from microsoft/fix/workflow-permissions
Fix/workflow permissions
2 parents f63cf94 + c301f50 commit d61d431

8 files changed

Lines changed: 1881 additions & 1218 deletions

File tree

.github/instructions/cicd.instructions.md

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,24 @@
11
---
2-
applyTo: ".github/workflows/build-release.yml"
2+
applyTo: ".github/workflows/**"
33
description: "CI/CD Pipeline configuration for PyInstaller binary packaging and release workflow"
44
---
55

66
# CI/CD Pipeline Instructions
77

8+
## Workflow Architecture (Fork-safe)
9+
Three workflows split by trigger and secret requirements:
10+
11+
1. **`ci.yml`**`pull_request` trigger (all PRs, including forks)
12+
- Unit tests + build. No secrets needed. Gives fast feedback.
13+
- Uploads binary artifacts for downstream integration testing.
14+
2. **`ci-integration.yml`**`workflow_run` trigger (after CI completes, environment-gated)
15+
- Smoke tests, integration tests, release validation. Requires `integration-tests` environment approval.
16+
- Security: uses `workflow_run` (not `pull_request_target`) — PR code is NEVER checked out.
17+
- Downloads binary artifacts from ci.yml, runs test scripts from default branch (main).
18+
- Reports results back to PR via commit status API.
19+
3. **`build-release.yml`**`push` to main, tags, schedule, `workflow_dispatch`
20+
- Full pipeline for post-merge / release. Secrets always available.
21+
822
## PyInstaller Binary Packaging
923
- **CRITICAL**: Uses `--onedir` mode (NOT `--onefile`) for faster CLI startup performance
1024
- **Binary Structure**: Creates `dist/{binary_name}/apm` (nested directory containing executable + dependencies)
@@ -22,9 +36,18 @@ description: "CI/CD Pipeline configuration for PyInstaller binary packaging and
2236
3. **Path Resolution**: Use symlinks and PATH manipulation for isolated binary testing
2337

2438
## Release Flow Dependencies
25-
- **Sequential Jobs**: test → build → integration-tests → release-validation → create-release → publish-pypi → update-homebrew
39+
- **PR workflow**: ci.yml (test → build) then ci-integration.yml via workflow_run (approve → smoke-test → integration-tests → release-validation → report-status)
40+
- **Push/Release workflow**: test → build → integration-tests → release-validation → create-release → publish-pypi → update-homebrew
2641
- **Tag Triggers**: Only `v*.*.*` tags trigger full release pipeline
2742
- **Artifact Retention**: 30 days for debugging failed releases
43+
- **Cross-workflow artifacts**: ci-integration.yml downloads artifacts from ci.yml using `run-id` and `github-token`
44+
45+
## Fork PR Security Model
46+
- Fork PRs get unit tests + build via `ci.yml` (no secrets, runs PR code safely)
47+
- `ci-integration.yml` triggers via `workflow_run` after CI completes — NEVER checks out PR code
48+
- Binary artifacts from ci.yml are tested using test scripts from the default branch (main)
49+
- Environment approval gate (`integration-tests`) ensures maintainer reviews PR before integration tests run
50+
- Commit status is reported back to the PR SHA so results appear on the PR
2851

2952
## Key Environment Variables
3053
- `PYTHON_VERSION: '3.12'` - Standardized across all jobs

.github/workflows/build-release.yml

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,6 @@ on:
1111
- 'docs/**'
1212
- '.gitignore'
1313
- 'LICENSE'
14-
pull_request:
15-
branches: [ main ]
16-
paths-ignore:
17-
- 'docs/**'
18-
- '.gitignore'
19-
- 'LICENSE'
2014
schedule:
2115
# Run daily at 04:00 UTC (6 AM CEST)
2216
- cron: '0 4 * * *'
@@ -28,10 +22,16 @@ on:
2822
default: true
2923
type: boolean
3024

25+
# Restrict default GITHUB_TOKEN permissions for all jobs (principle of least privilege)
26+
permissions:
27+
contents: read
28+
3129
jobs:
3230
# Always run tests first with matrix strategy
3331
test:
3432
runs-on: ${{ matrix.os }}
33+
permissions:
34+
contents: read
3535
strategy:
3636
fail-fast: false
3737
matrix:
@@ -93,8 +93,8 @@ jobs:
9393

9494
- name: Run smoke tests
9595
env:
96-
GITHUB_TOKEN: ${{ secrets.GH_MODELS_PAT }} # Models access
97-
GITHUB_APM_PAT: ${{ secrets.GH_CLI_PAT }} # APM module access
96+
GITHUB_TOKEN: ${{ secrets.GH_MODELS_PAT }}
97+
GITHUB_APM_PAT: ${{ secrets.GH_CLI_PAT }}
9898
run: uv run pytest tests/integration/test_runtime_smoke.py -v
9999

100100
# Build binaries
@@ -123,7 +123,7 @@ jobs:
123123

124124
runs-on: ${{ matrix.os }}
125125
permissions:
126-
contents: write # Required for release uploads
126+
contents: read # Checkout code; upload-artifact uses separate Actions API
127127

128128
steps:
129129
- name: Checkout code
@@ -177,8 +177,11 @@ jobs:
177177
if-no-files-found: error
178178

179179
# Integration tests with full source code access
180+
# Skip on push-to-main: already validated by ci-integration.yml during the PR.
181+
# Run on tags (release gate), schedule (regression), and dispatch (manual).
180182
integration-tests:
181183
name: Integration Tests
184+
if: github.ref_type == 'tag' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
182185
needs: [test, build]
183186
strategy:
184187
matrix:
@@ -255,6 +258,7 @@ jobs:
255258
# Release validation tests - Final pre-release validation of shipped binary
256259
release-validation:
257260
name: Release Validation
261+
if: github.ref_type == 'tag' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
258262
needs: [test, build, integration-tests]
259263
strategy:
260264
matrix:
@@ -437,7 +441,8 @@ jobs:
437441
name: pypi
438442
url: https://pypi.org/p/apm-cli
439443
permissions:
440-
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
444+
contents: read # Required for actions/checkout
445+
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
441446

442447
steps:
443448
- name: Checkout code
@@ -470,6 +475,8 @@ jobs:
470475
runs-on: ubuntu-latest
471476
needs: [test, build, integration-tests, release-validation, create-release, publish-pypi]
472477
if: github.ref_type == 'tag' && needs.create-release.outputs.is_private_repo != 'true' && needs.create-release.outputs.is_prerelease != 'true'
478+
permissions:
479+
contents: read
473480

474481
steps:
475482
- name: Extract SHA256 checksums from GitHub release

0 commit comments

Comments
 (0)