-
Notifications
You must be signed in to change notification settings - Fork 99
256 lines (220 loc) · 9.1 KB
/
ci-integration.yml
File metadata and controls
256 lines (220 loc) · 9.1 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
# Integration tests for PRs (including forks).
# Uses workflow_run to safely access secrets without checking out untrusted PR code.
# Security model:
# - ci.yml builds the PR's code in an unprivileged context and uploads binary artifacts
# - This workflow downloads those artifacts and runs tests from the DEFAULT BRANCH (main)
# - PR code is never checked out in a privileged context
# - Fork PRs require manual environment approval before tests run (security gate)
# - Internal PRs (same repo) skip the approval gate — contributors already have write access
name: Integration Tests (PR)
env:
PYTHON_VERSION: '3.12'
on:
workflow_run:
workflows: ["CI"]
types: [completed]
permissions:
contents: read
actions: read
statuses: write
jobs:
# Fork PRs: require manual approval via environment protection rules.
# A maintainer must review the fork's code before secrets are exposed to its artifacts.
approve-fork:
if: >
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.head_repository.full_name != github.repository
runs-on: ubuntu-latest
environment: integration-tests
steps:
- run: echo "Fork PR approved for ${{ github.event.workflow_run.head_branch }} from ${{ github.event.workflow_run.head_repository.full_name }}"
# Internal PRs: skip approval gate — contributors already have write access to the repo.
approve-internal:
if: >
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.head_repository.full_name == github.repository
runs-on: ubuntu-latest
steps:
- run: echo "Internal PR auto-approved for ${{ github.event.workflow_run.head_branch }}"
# Linux smoke test
smoke-test:
needs: [approve-fork, approve-internal]
# Run if either approval job succeeded (the other will be skipped)
if: always() && (needs.approve-fork.result == 'success' || needs.approve-internal.result == 'success')
runs-on: ubuntu-24.04
permissions:
contents: read
actions: read
steps:
# Checkout default branch (main) — never PR code
- uses: actions/checkout@v4
# O8: PR traceability — annotate with originating PR URL
- name: Annotate originating PR
run: |
PR_NUMBER=$(echo '${{ toJSON(github.event.workflow_run.pull_requests) }}' | jq -r '.[0].number // empty')
if [ -n "$PR_NUMBER" ]; then
echo "::notice::🔗 Originating PR: https://github.com/${{ github.repository }}/pull/${PR_NUMBER}"
else
echo "::notice::Triggered by push to ${{ github.event.workflow_run.head_branch }}"
fi
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
- name: Install dependencies
run: uv sync --extra dev
- name: Run smoke tests
env:
GITHUB_TOKEN: ${{ secrets.GH_MODELS_PAT }}
GITHUB_APM_PAT: ${{ secrets.GH_CLI_PAT }}
run: uv run pytest tests/integration/test_runtime_smoke.py -v
# Linux integration tests — downloads the Linux binary artifact from ci.yml.
integration-tests:
name: Integration Tests (Linux)
needs: [smoke-test]
if: always() && needs.smoke-test.result == 'success'
runs-on: ubuntu-24.04
permissions:
contents: read
actions: read
steps:
# Checkout default branch (main) for test scripts — never PR code
- name: Checkout code
uses: actions/checkout@v4
# Download binary artifact built from PR code by ci.yml
- name: Download APM binary from CI workflow
uses: actions/download-artifact@v4
with:
name: apm-linux-x86_64
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
- name: Install test dependencies
run: uv sync --extra dev
- name: Run integration tests
env:
APM_E2E_TESTS: "1"
GITHUB_APM_PAT: ${{ secrets.GH_CLI_PAT }}
ADO_APM_PAT: ${{ secrets.ADO_APM_PAT }}
run: |
chmod +x scripts/test-integration.sh
uv run ./scripts/test-integration.sh
timeout-minutes: 20
# Linux release validation — validates the Linux binary in isolation.
release-validation:
name: Release Validation (Linux)
needs: [integration-tests]
if: always() && needs.integration-tests.result == 'success'
runs-on: ubuntu-24.04
permissions:
contents: read
actions: read
steps:
# Checkout default branch for test scripts — never PR code
- name: Checkout test scripts
uses: actions/checkout@v4
with:
sparse-checkout: scripts
path: repo
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
# Download binary artifact to isolated test directory
- name: Download APM binary from CI workflow
uses: actions/download-artifact@v4
with:
name: apm-linux-x86_64
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
path: /tmp/apm-isolated-test/
- name: Make binary executable and verify isolation
run: |
cd /tmp/apm-isolated-test
echo "Downloaded structure:"
find . -name "apm" -type f
ls -la ./dist/
chmod +x ./dist/apm-linux-x86_64/apm
- name: Create APM symlink for testing
run: |
cd /tmp/apm-isolated-test
ln -s "$(pwd)/dist/apm-linux-x86_64/apm" "$(pwd)/apm"
echo "/tmp/apm-isolated-test" >> $GITHUB_PATH
- name: Prepare test scripts from default branch
run: |
# Copy test scripts from main branch checkout into isolated test dir
cp -r repo/scripts /tmp/apm-isolated-test/scripts
- name: Run release validation tests
env:
APM_E2E_TESTS: "1"
GITHUB_APM_PAT: ${{ secrets.GH_CLI_PAT }}
ADO_APM_PAT: ${{ secrets.ADO_APM_PAT }}
run: |
cd /tmp/apm-isolated-test
chmod +x scripts/test-release-validation.sh
./scripts/test-release-validation.sh
timeout-minutes: 20
# Report integration test results back to the PR as a commit status
report-status:
needs: [smoke-test, integration-tests, release-validation]
if: always()
runs-on: ubuntu-latest
permissions:
statuses: write
steps:
- name: Report status to PR
uses: actions/github-script@v7
with:
script: |
const ciConclusion = '${{ github.event.workflow_run.conclusion }}';
const smokeResult = '${{ needs.smoke-test.result }}';
const integResult = '${{ needs.integration-tests.result }}';
const relvalResult = '${{ needs.release-validation.result }}';
// O7: Detect CI circular dependency — when ci.yml fails, all approval
// jobs are skipped (their `if: conclusion == 'success'` fails), which
// cascades to skip smoke-test → integration-tests → release-validation.
// Without this check, we'd report 'failure' and block the CI-fixing PR.
const allSkipped = smokeResult === 'skipped' && integResult === 'skipped' && relvalResult === 'skipped';
let state, description;
if (allSkipped && ciConclusion !== 'success') {
// Upstream CI failed — all jobs were skipped via approval guards, not due to test failures
state = 'pending';
description = `CI workflow ${ciConclusion} — integration tests skipped (not blocking)`;
core.notice(`CI workflow concluded with '${ciConclusion}' — integration tests were skipped. This is expected when ci.yml itself has an error.`);
} else {
const success = relvalResult === 'success'
&& integResult === 'success'
&& smokeResult === 'success';
state = success ? 'success' : 'failure';
description = success ? 'All integration tests passed' : 'Integration tests failed';
}
await github.rest.repos.createCommitStatus({
owner: context.repo.owner,
repo: context.repo.repo,
sha: context.payload.workflow_run.head_sha,
state,
target_url: `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`,
context: 'Integration Tests (PR)',
description
});