Skip to content

Commit af94606

Browse files
committed
feat(api): ensure StackRunConfig
StackRunConfig is part of our public API, ensure stability of this datatype using a pytest snapshot test. If the pydantic model changes, it will fail. A snapshot can be re-generated via `@github-actions regenerate snapshots` by a code owner. The API conformance test will then re-run and pass. Signed-off-by: Charlie Doern <cdoern@redhat.com>
1 parent 0dbf79c commit af94606

File tree

14 files changed

+2427
-104
lines changed

14 files changed

+2427
-104
lines changed

.github/workflows/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Llama Stack uses GitHub Actions for Continuous Integration (CI). Below is a tabl
44

55
| Name | File | Purpose |
66
| ---- | ---- | ------- |
7+
| PR Bot Commands | [bot-trigger.yml](bot-trigger.yml) | Bot command for PR |
78
| Update Changelog | [changelog.yml](changelog.yml) | Creates PR for updating the CHANGELOG.md |
89
| API Conformance Tests | [conformance.yml](conformance.yml) | Run the API Conformance test suite on the changes. |
910
| Installer CI | [install-script-ci.yml](install-script-ci.yml) | Test the installation script |
@@ -12,10 +13,11 @@ Llama Stack uses GitHub Actions for Continuous Integration (CI). Below is a tabl
1213
| Integration Tests (Replay) | [integration-tests.yml](integration-tests.yml) | Run the integration test suites from tests/integration in replay mode |
1314
| Vector IO Integration Tests | [integration-vector-io-tests.yml](integration-vector-io-tests.yml) | Run the integration test suite with various VectorIO providers |
1415
| Pre-commit | [pre-commit.yml](pre-commit.yml) | Run pre-commit checks |
15-
| Pre-commit Bot | [precommit-trigger.yml](precommit-trigger.yml) | Pre-commit bot for PR |
16+
| Run Pre-commit | [precommit-trigger.yml](precommit-trigger.yml) | Run Pre-commit via PR comment |
1617
| Test Llama Stack Build | [providers-build.yml](providers-build.yml) | Test llama stack build |
1718
| Python Package Build Test | [python-build-test.yml](python-build-test.yml) | Test building the llama-stack PyPI project |
1819
| Integration Tests (Record) | [record-integration-tests.yml](record-integration-tests.yml) | Run the integration test suite from tests/integration |
20+
| Run Snapshot Regeneration | [regenerate-snapshot-trigger.yml](regenerate-snapshot-trigger.yml) | Run Snapshot Regeneration via PR comment |
1921
| Check semantic PR titles | [semantic-pr.yml](semantic-pr.yml) | Ensure that PR titles follow the conventional commit spec |
2022
| Close stale issues and PRs | [stale_bot.yml](stale_bot.yml) | Run the Stale Bot action |
2123
| Test External Providers Installed via Module | [test-external-provider-module.yml](test-external-provider-module.yml) | Test External Provider installation via Python module |

.github/workflows/bot-trigger.yml

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
name: PR Bot Commands
2+
3+
run-name: Bot command for PR #${{ github.event.issue.number }}
4+
5+
on:
6+
issue_comment:
7+
types: [created]
8+
9+
jobs:
10+
# Shared setup job for both pre-commit and snapshot regeneration
11+
setup:
12+
if: github.event.issue.pull_request && (contains(github.event.comment.body, '@github-actions run precommit') || contains(github.event.comment.body, '@github-actions regenerate snapshots'))
13+
runs-on: ubuntu-latest
14+
permissions:
15+
contents: write
16+
pull-requests: write
17+
outputs:
18+
authorized: ${{ steps.check_author.outputs.authorized }}
19+
pr_number: ${{ steps.check_author.outputs.pr_number }}
20+
pr_head_ref: ${{ steps.check_author.outputs.pr_head_ref }}
21+
pr_head_sha: ${{ steps.check_author.outputs.pr_head_sha }}
22+
pr_head_repo: ${{ steps.check_author.outputs.pr_head_repo }}
23+
pr_base_ref: ${{ steps.check_author.outputs.pr_base_ref }}
24+
is_fork: ${{ steps.check_author.outputs.is_fork }}
25+
command: ${{ steps.detect_command.outputs.command }}
26+
27+
steps:
28+
- name: Detect command
29+
id: detect_command
30+
run: |
31+
COMMENT="${{ github.event.comment.body }}"
32+
if [[ "$COMMENT" == *"@github-actions run precommit"* ]]; then
33+
echo "command=precommit" >> $GITHUB_OUTPUT
34+
elif [[ "$COMMENT" == *"@github-actions regenerate snapshots"* ]]; then
35+
echo "command=regenerate-snapshots" >> $GITHUB_OUTPUT
36+
fi
37+
- name: Check comment author and get PR details
38+
id: check_author
39+
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
40+
with:
41+
github-token: ${{ secrets.GITHUB_TOKEN }}
42+
script: |
43+
// Get PR details
44+
const pr = await github.rest.pulls.get({
45+
owner: context.repo.owner,
46+
repo: context.repo.repo,
47+
pull_number: context.issue.number
48+
});
49+
50+
// Check if commenter has write access or is the PR author
51+
const commenter = context.payload.comment.user.login;
52+
const prAuthor = pr.data.user.login;
53+
54+
let hasPermission = false;
55+
56+
// Check if commenter is PR author
57+
if (commenter === prAuthor) {
58+
hasPermission = true;
59+
console.log(`Comment author ${commenter} is the PR author`);
60+
} else {
61+
// Check if commenter has write/admin access
62+
try {
63+
const permission = await github.rest.repos.getCollaboratorPermissionLevel({
64+
owner: context.repo.owner,
65+
repo: context.repo.repo,
66+
username: commenter
67+
});
68+
69+
const level = permission.data.permission;
70+
hasPermission = ['write', 'admin', 'maintain'].includes(level);
71+
console.log(`Comment author ${commenter} has permission: ${level}`);
72+
} catch (error) {
73+
console.log(`Could not check permissions for ${commenter}: ${error.message}`);
74+
}
75+
}
76+
77+
if (!hasPermission) {
78+
await github.rest.issues.createComment({
79+
owner: context.repo.owner,
80+
repo: context.repo.repo,
81+
issue_number: context.issue.number,
82+
body: `❌ @${commenter} You don't have permission to trigger bot commands. Only PR authors or repository collaborators can run this command.`
83+
});
84+
core.setFailed(`User ${commenter} does not have permission`);
85+
return;
86+
}
87+
88+
// Save PR info for later steps
89+
core.setOutput('pr_number', context.issue.number);
90+
core.setOutput('pr_head_ref', pr.data.head.ref);
91+
core.setOutput('pr_head_sha', pr.data.head.sha);
92+
core.setOutput('pr_head_repo', pr.data.head.repo.full_name);
93+
core.setOutput('pr_base_ref', pr.data.base.ref);
94+
core.setOutput('is_fork', pr.data.head.repo.full_name !== context.payload.repository.full_name);
95+
core.setOutput('authorized', 'true');
96+
97+
- name: React to comment
98+
if: steps.check_author.outputs.authorized == 'true'
99+
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
100+
with:
101+
github-token: ${{ secrets.GITHUB_TOKEN }}
102+
script: |
103+
await github.rest.reactions.createForIssueComment({
104+
owner: context.repo.owner,
105+
repo: context.repo.repo,
106+
comment_id: context.payload.comment.id,
107+
content: 'rocket'
108+
});
109+
110+
pre-commit:
111+
needs: setup
112+
if: needs.setup.outputs.authorized == 'true' && needs.setup.outputs.command == 'precommit'
113+
uses: ./.github/workflows/precommit-trigger.yml
114+
with:
115+
pr_number: ${{ needs.setup.outputs.pr_number }}
116+
pr_head_ref: ${{ needs.setup.outputs.pr_head_ref }}
117+
pr_head_sha: ${{ needs.setup.outputs.pr_head_sha }}
118+
pr_head_repo: ${{ needs.setup.outputs.pr_head_repo }}
119+
is_fork: ${{ needs.setup.outputs.is_fork }}
120+
121+
regenerate-snapshots:
122+
needs: setup
123+
if: needs.setup.outputs.authorized == 'true' && needs.setup.outputs.command == 'regenerate-snapshots'
124+
uses: ./.github/workflows/regenerate-snapshots-trigger.yml
125+
with:
126+
pr_number: ${{ needs.setup.outputs.pr_number }}
127+
pr_head_ref: ${{ needs.setup.outputs.pr_head_ref }}
128+
pr_head_sha: ${{ needs.setup.outputs.pr_head_sha }}
129+
pr_head_repo: ${{ needs.setup.outputs.pr_head_repo }}
130+
is_fork: ${{ needs.setup.outputs.is_fork }}

.github/workflows/conformance.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ jobs:
4040
with:
4141
fetch-depth: 0
4242

43+
- name: Install dependencies
44+
uses: ./.github/actions/setup-runner
45+
with:
46+
python-version: "3.12"
47+
4348
# Check if we should skip conformance testing due to breaking changes
4449
- name: Check if conformance test should be skipped
4550
id: skip-check
@@ -137,6 +142,11 @@ jobs:
137142
run: |
138143
oasdiff breaking --fail-on ERR $BASE_SPEC $CURRENT_SPEC --match-path '^/v1/'
139144
145+
# never skip this, instead if a breaking change is properly identified -- we should regenerate our snapshot.
146+
- name: Run Pydantic Model Test
147+
run: |
148+
uv run --no-sync ./scripts/snapshot-test.sh tests/api/test_pydantic_models.py
149+
140150
# Report when test is skipped
141151
- name: Report skip reason
142152
if: steps.skip-check.outputs.skip == 'true'

0 commit comments

Comments
 (0)