Repository navigation
212 lines (201 loc) · 8.21 KB
/
Copy pathpython-integration-test.yml
File metadata and controls
212 lines (201 loc) · 8.21 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
name: "Python: Integration Test"
on:
pull_request_target:
branches: [main]
paths:
- 'strands-py/**'
- '.github/workflows/python-*'
merge_group:
types: [checks_requested]
# workflow_call entry — TRUST INVARIANT.
#
# Lets another workflow in this repo reuse the integ tests against an
# arbitrary `ref` while keeping AWS_*. The authorization-check below is
# skipped on this path because the only callers
# (release-python.yml / release-typescript.yml) are workflow_dispatch-
# gated, which already requires repo write access.
#
# Before adding another caller, check all of:
# 1. Cannot be triggered by an unauthenticated user or a fork
# (no pull_request; no pull_request_target without an auth gate;
# no push from non-main branches).
# 2. Cannot be coerced into a fork-PR ref. validate-call-ref below is
# the runtime guard, but condition 1 should make that unnecessary.
# 3. AWS_ROLE / STRANDS_INTEG_TEST_ROLE are scoped to least privilege.
workflow_call:
inputs:
ref:
required: true
type: string
concurrency:
group: python-integration-test-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ inputs.ref == '' && github.event_name == 'pull_request_target' }}
jobs:
validate-call-ref:
name: Validate workflow_call ref
if: inputs.ref != ''
runs-on: strands-agents_ubuntu-latest_4-core
permissions:
contents: read
# Reject ref shapes that could check out untrusted code:
# `refs/pull/*` (fork-PR merge refs) and `owner:branch` (cross-repo
# refs) fail; plain branches and SHAs pass.
#
# We do not also block on `github.event.repository.fork`. That flag is
# only `true` when this workflow runs on a fork's own runner — which
# is a legitimate case (release-python.yml's `run_integ_tests: true`
# lets a fork-owner test the pipeline with their own secrets). The
# ref-shape check above is what actually rejects untrusted refs.
steps:
- name: Reject untrusted-shaped refs
env:
REF: ${{ inputs.ref }}
run: |
set -euo pipefail
case "$REF" in
refs/pull/*|*:*)
echo "::error::workflow_call ref '$REF' looks like a fork PR or cross-repo ref."
exit 1
;;
esac
echo "ref '$REF' passed shape check."
authorization-check:
name: Check access
needs: [validate-call-ref]
# `needs` only runs after non-skipped predecessors. validate-call-ref is
# skipped on pull_request_target / merge_group (inputs.ref is empty), so
# we use `if: always()` to ensure auth-check still runs there. On
# workflow_call, validate-call-ref must have succeeded.
if: |
always() &&
(needs.validate-call-ref.result == 'success' || needs.validate-call-ref.result == 'skipped')
permissions:
contents: read
runs-on: strands-agents_ubuntu-latest_4-core
outputs:
approval-env: ${{ steps.auth.outputs.approval-env }}
steps:
- name: Check Authorization
id: auth
uses: strands-agents/devtools/authorization-check@main
with:
# Skip on merge_group (gated by branch protection) and on
# workflow_call (the caller — release.yml — is workflow_dispatch
# gated, see the trigger comment above).
# `github.event_name` can't identify the release here — inside a
# reusable workflow it reports the caller's event, never
# 'workflow_call' — so key off `inputs.ref`, which only a
# workflow_call caller supplies (empty on pull_request_target /
# merge_group). Matches the validate-call-ref guard above.
skip-check: ${{ github.event_name == 'merge_group' || inputs.ref != '' }}
username: ${{ github.event.pull_request.user.login || 'invalid' }}
allowed-roles: 'maintain,triage,write,admin'
check-access-and-checkout:
name: Run integration tests - Python ${{ matrix.python-version }} - ${{ matrix.scope }}
runs-on: strands-agents_ubuntu-latest_4-core
needs: authorization-check
if: always() && needs.authorization-check.result == 'success'
strategy:
fail-fast: false
matrix:
include:
- python-version: '3.10'
scope: all
test-path: tests_integ
artifact-name: test-results
- python-version: '3.12'
scope: bidi
test-path: tests_integ/bidi
artifact-name: bidi-test-results
# The 1.x-era MCP integration tests build servers and transports from `mcp` names that 2.x renamed, so the
# main scope (which resolves mcp 2.x) ignores them and this scope reruns them on a forced mcp 1.x.
- python-version: '3.10'
scope: mcp-1x
test-path: tests_integ/mcp
artifact-name: mcp-1x-test-results
environment:
name: ${{ needs.authorization-check.outputs.approval-env }}
deployment: false
permissions:
id-token: write
pull-requests: read
contents: read
defaults:
run:
working-directory: strands-py
steps:
- name: Configure Credentials
uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: ${{ secrets.STRANDS_INTEG_TEST_ROLE }}
aws-region: us-east-1
mask-aws-account-id: true
- name: Checkout head commit
uses: actions/checkout@v7
with:
ref: ${{ inputs.ref || github.event.pull_request.head.sha }}
persist-credentials: false
allow-unsafe-pr-checkout: true # opt back into fork-PR checkout, blocked by default in checkout@v7
- name: Set up Python
uses: actions/setup-python@v7.0.0
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
pip install --no-cache-dir hatch
- name: Run integration tests
env:
AWS_REGION: us-east-1
AWS_REGION_NAME: us-east-1
STRANDS_TEST_API_KEYS_SECRET_NAME: ${{ secrets.STRANDS_TEST_API_KEYS_SECRET_NAME }}
# Caps the hatch-test env at mcp 1.x via the pyproject override.
STRANDS_TEST_MCP_V1: ${{ matrix.scope == 'mcp-1x' && '1' || '' }}
id: tests
run: |
hatch test ${{ matrix.test-path }}
- name: Upload test results
if: always()
uses: actions/upload-artifact@v7.0.1
with:
name: ${{ matrix.artifact-name }}
path: strands-py/build/test-results.xml
upload-metrics:
runs-on: strands-agents_ubuntu-latest_4-core
needs: check-access-and-checkout
# Publishes to the team's CloudWatch via STRANDS_INTEG_TEST_ROLE. Only run
# when the integration-test job actually executed; on fork PRs the auth
# environment gate skips the test job and no test-results artifact is
# produced, so this job would otherwise fail on artifact download. The
# previous github.event.repository.fork guard did not catch this case
# because pull_request_target always evaluates that field against the base
# repo, which is never a fork.
if: always() && (needs.check-access-and-checkout.result == 'success' || needs.check-access-and-checkout.result == 'failure')
permissions:
id-token: write
contents: read
steps:
- name: Configure Credentials
uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: ${{ secrets.STRANDS_INTEG_TEST_ROLE }}
aws-region: us-east-1
mask-aws-account-id: true
- name: Checkout main
uses: actions/checkout@v7
with:
ref: main
sparse-checkout: |
.github/scripts
persist-credentials: false
- name: Download test results
uses: actions/download-artifact@v8.0.1
with:
name: test-results
path: ${{ runner.temp }}/test-results
- name: Publish test metrics to CloudWatch
env:
REPO_NAME: ${{ github.event.repository.name }}
TEST_RESULTS_FILE: ${{ runner.temp }}/test-results/test-results.xml
run: |
pip install --no-cache-dir boto3
python .github/scripts/upload-integ-test-metrics.py "$TEST_RESULTS_FILE" "$REPO_NAME"