Skip to content

Commit 1b9e10a

Browse files
committed
feat(ci): add manual network test triggers for PRs
Pin ci hashes, improve ci security.
1 parent 4a9b8fc commit 1b9e10a

6 files changed

Lines changed: 293 additions & 26 deletions

File tree

.github/actions/init/action.yaml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,26 @@ inputs:
2626
runs:
2727
using: "composite"
2828
steps:
29-
- uses: actions/checkout@v4
29+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
30+
with:
31+
persist-credentials: false
3032

3133
- name: Install Clang
3234
if: ${{ inputs.install-clang == 'true' }}
3335
run: sudo apt-get update && sudo apt-get install -y clang
3436
shell: bash
3537

36-
- uses: dtolnay/rust-toolchain@stable
38+
- uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b # stable
3739
with:
3840
toolchain: ${{ inputs.toolchain }}
3941
components: ${{ inputs.components }}
4042

41-
- uses: Swatinem/rust-cache@v2
43+
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2
4244
if: ${{ inputs.setup-cache == 'true' }}
4345
with:
4446
cache-on-failure: true
4547

46-
- uses: extractions/setup-just@v2
48+
- uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6 # v2
4749

4850
- name: Create fake procfs values
4951
if: ${{ inputs.fake-procfs == 'true' }}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Post network test instructions to PR comments.
4+
"""
5+
6+
import os
7+
import sys
8+
import requests
9+
import json
10+
import yaml
11+
from typing import Optional, Dict, Any, List
12+
13+
14+
def get_github_context() -> Dict[str, Any]:
15+
"""Get GitHub context from environment variables."""
16+
github_token = os.getenv('GITHUB_TOKEN')
17+
github_repository = os.getenv('GITHUB_REPOSITORY')
18+
github_event_path = os.getenv('GITHUB_EVENT_PATH')
19+
20+
if not all([github_token, github_repository, github_event_path]):
21+
raise ValueError("Missing required GitHub environment variables")
22+
23+
with open(github_event_path, 'r') as f:
24+
event_data = json.load(f)
25+
26+
return {
27+
'token': github_token,
28+
'repository': github_repository,
29+
'event': event_data
30+
}
31+
32+
33+
def get_pr_number(event_data: Dict[str, Any]) -> int:
34+
"""Extract PR number from GitHub event data."""
35+
if 'pull_request' in event_data:
36+
return event_data['pull_request']['number']
37+
elif 'number' in event_data:
38+
return event_data['number']
39+
else:
40+
raise ValueError("Unable to determine PR number from event data")
41+
42+
43+
def get_test_names_from_yaml() -> List[str]:
44+
"""Extract test names from network-tests.yml workflow file."""
45+
try:
46+
workflow_path = os.path.join(os.path.dirname(__file__), '..', 'workflows', 'network-tests.yml')
47+
with open(workflow_path, 'r') as f:
48+
workflow_data = yaml.safe_load(f)
49+
50+
matrix_include = workflow_data.get('jobs', {}).get('network-tests', {}).get('strategy', {}).get('matrix', {}).get('include', [])
51+
test_names = [item.get('test_name') for item in matrix_include if item.get('test_name')]
52+
53+
if not test_names:
54+
# Fallback to hardcoded list if parsing fails
55+
return ['destroyable', 'ping-pong', 'one-to-many-internal-messages', 'fq-deploy', 'nft-index']
56+
57+
return test_names
58+
except Exception as e:
59+
print(f"Warning: Could not parse test names from YAML, using fallback: {e}", file=sys.stderr)
60+
# Fallback to hardcoded list if parsing fails
61+
return ['destroyable', 'ping-pong', 'one-to-many-internal-messages', 'fq-deploy', 'nft-index']
62+
63+
64+
def create_comment_body(pr_number: int) -> str:
65+
"""Create the comment body with network test instructions."""
66+
test_names = get_test_names_from_yaml()
67+
test_types_str = ', '.join(f'`{name}`' for name in test_names)
68+
69+
return f"""## 🧪 Network Tests
70+
71+
To run network tests for this PR, use:
72+
```bash
73+
gh workflow run network-tests.yml -f pr_number={pr_number}
74+
```
75+
76+
**Available test options:**
77+
- Run all tests: `gh workflow run network-tests.yml -f pr_number={pr_number}`
78+
- Run specific test: `gh workflow run network-tests.yml -f pr_number={pr_number} -f test_selection=ping-pong`
79+
80+
**Test types:** {test_types_str}
81+
82+
Results will be posted as workflow runs in the Actions tab."""
83+
84+
85+
def find_existing_comment(github_token: str, repo: str, pr_number: int) -> Optional[int]:
86+
"""Find existing network test instructions comment."""
87+
url = f"https://api.github.com/repos/{repo}/issues/{pr_number}/comments"
88+
headers = {
89+
'Authorization': f'token {github_token}',
90+
'Accept': 'application/vnd.github.v3+json'
91+
}
92+
93+
response = requests.get(url, headers=headers)
94+
response.raise_for_status()
95+
96+
comments = response.json()
97+
98+
for comment in comments:
99+
if (comment.get('user', {}).get('type') == 'Bot' and
100+
'🧪 Network Tests' in comment.get('body', '')):
101+
return comment['id']
102+
103+
return None
104+
105+
106+
def post_or_update_comment(github_token: str, repo: str, pr_number: int, body: str) -> None:
107+
"""Post new comment or update existing one."""
108+
headers = {
109+
'Authorization': f'token {github_token}',
110+
'Accept': 'application/vnd.github.v3+json'
111+
}
112+
113+
existing_comment_id = find_existing_comment(github_token, repo, pr_number)
114+
115+
if existing_comment_id:
116+
# Update existing comment
117+
url = f"https://api.github.com/repos/{repo}/issues/comments/{existing_comment_id}"
118+
data = {'body': body}
119+
response = requests.patch(url, headers=headers, json=data)
120+
print(f"Updated existing comment {existing_comment_id}")
121+
else:
122+
# Create new comment
123+
url = f"https://api.github.com/repos/{repo}/issues/{pr_number}/comments"
124+
data = {'body': body}
125+
response = requests.post(url, headers=headers, json=data)
126+
print(f"Created new comment on PR {pr_number}")
127+
128+
response.raise_for_status()
129+
130+
131+
def main():
132+
"""Main function."""
133+
try:
134+
context = get_github_context()
135+
pr_number = get_pr_number(context['event'])
136+
137+
print(f"Processing PR #{pr_number}")
138+
139+
comment_body = create_comment_body(pr_number)
140+
post_or_update_comment(
141+
context['token'],
142+
context['repository'],
143+
pr_number,
144+
comment_body
145+
)
146+
147+
print("Successfully posted network test instructions")
148+
149+
except Exception as e:
150+
print(f"Error: {e}", file=sys.stderr)
151+
sys.exit(1)
152+
153+
154+
if __name__ == '__main__':
155+
main()

.github/workflows/ci.yml

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ on:
99
branches:
1010
- "**"
1111

12+
permissions:
13+
contents: read
14+
1215
concurrency:
1316
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
1417
cancel-in-progress: true
@@ -28,8 +31,10 @@ jobs:
2831
shell: ${{ steps.filter.outputs.shell }}
2932
proto: ${{ steps.filter.outputs.proto }}
3033
steps:
31-
- uses: actions/checkout@v4
32-
- uses: dorny/paths-filter@v3
34+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
35+
with:
36+
persist-credentials: false
37+
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3
3338
id: filter
3439
with:
3540
filters: |
@@ -47,7 +52,9 @@ jobs:
4752
if: ${{ needs.changes.outputs.rust == 'true' }}
4853
runs-on: [self-hosted, linux]
4954
steps:
50-
- uses: actions/checkout@v4
55+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
56+
with:
57+
persist-credentials: false
5158
- uses: ./.github/actions/init
5259
with:
5360
toolchain: nightly
@@ -63,7 +70,9 @@ jobs:
6370
if: ${{ needs.changes.outputs.rust == 'true' }} # we duplicate it so gh actions will skip the job and mark it as success
6471
runs-on: [self-hosted, linux]
6572
steps:
66-
- uses: actions/checkout@v4
73+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
74+
with:
75+
persist-credentials: false
6776
- uses: ./.github/actions/init
6877
with:
6978
components: "clippy,rustfmt"
@@ -75,20 +84,22 @@ jobs:
7584
needs: [rustfmt, changes]
7685
if: ${{ needs.changes.outputs.rust == 'true' }}
7786
steps:
78-
- uses: actions/checkout@v4
87+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
88+
with:
89+
persist-credentials: false
7990
- uses: ./.github/actions/init
8091
with:
8192
components: llvm-tools-preview
8293
fake-procfs: true
83-
- uses: taiki-e/install-action@cargo-llvm-cov
84-
- uses: taiki-e/install-action@nextest
94+
- uses: taiki-e/install-action@9185c192a96ba09167ad8663015b3fbbf007ec79 # v2.56.2
95+
- uses: taiki-e/install-action@9185c192a96ba09167ad8663015b3fbbf007ec79 # v2.56.2
8596
- name: Run tests
8697
env:
8798
RUSTC_WRAPPER: scripts/coverage.py
8899
RUST_LOG: warn
89100
run: just test_cov
90101
- name: Upload coverage to Codecov
91-
uses: codecov/codecov-action@v3
102+
uses: codecov/codecov-action@ab904c41d6ece82784817410c45d8b8c02684457 # v3
92103
if: github.repository == 'broxus/tycho'
93104
with:
94105
token: ${{ secrets.CODECOV_TOKEN }}
@@ -101,7 +112,9 @@ jobs:
101112
if: ${{ needs.changes.outputs.rust == 'true' }}
102113
needs: [rustfmt, changes]
103114
steps:
104-
- uses: actions/checkout@v4
115+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
116+
with:
117+
persist-credentials: false
105118
- uses: ./.github/actions/init
106119
with:
107120
fake-procfs: true
@@ -114,11 +127,13 @@ jobs:
114127
name: Metrics
115128
runs-on: ubuntu-latest
116129
steps:
117-
- uses: actions/checkout@v4
130+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
131+
with:
132+
persist-credentials: false
118133
- uses: actions/setup-python@v5
119134
with:
120135
python-version: "3.12"
121-
- uses: extractions/setup-just@v2
136+
- uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6 # v2
122137
- name: Check dashboard
123138
run: just check_dashboard
124139

@@ -128,11 +143,13 @@ jobs:
128143
runs-on: self-hosted
129144
needs: [changes]
130145
steps:
131-
- uses: actions/checkout@v4
146+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
147+
with:
148+
persist-credentials: false
132149
- uses: ./.github/actions/init
133150
with:
134151
install-clang: false
135-
- uses: taiki-e/install-action@v2
152+
- uses: taiki-e/install-action@9185c192a96ba09167ad8663015b3fbbf007ec79 # v2.56.2
136153
with:
137154
tool: cargo-msrv
138155
- name: Check semver
@@ -144,7 +161,9 @@ jobs:
144161
if: ${{ needs.changes.outputs.proto == 'true' }}
145162
runs-on: [self-hosted, linux]
146163
steps:
147-
- uses: actions/checkout@v4
164+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
165+
with:
166+
persist-credentials: false
148167
- uses: ./.github/actions/init
149168
with:
150169
install-clang: false
@@ -162,7 +181,9 @@ jobs:
162181
if: ${{ needs.changes.outputs.rust == 'true' }}
163182
runs-on: [self-hosted, linux]
164183
steps:
165-
- uses: actions/checkout@v4
184+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
185+
with:
186+
persist-credentials: false
166187
- uses: ./.github/actions/init
167188
- name: Update CLI reference
168189
run: just update_cli_reference

0 commit comments

Comments
 (0)