Skip to content

Commit 935479e

Browse files
authored
Add tools/start_integration_tests.py + periodic workflow (#2675)
## Changes - New script to start integration tests for PRs that were approved by team members. - New workflow to run this script every 10 minutes. ## Why When dependabot posts many PR you need to start the integration tests. If rebased, the results are lost and you need to do that again. This script automates starting these tests. ## Tests Manually.
1 parent 11913d4 commit 935479e

2 files changed

Lines changed: 198 additions & 0 deletions

File tree

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: start-integration-tests
2+
3+
on:
4+
schedule:
5+
- cron: '*/10 * * * *'
6+
7+
jobs:
8+
# Trigger for pull requests.
9+
#
10+
# This workflow triggers the integration test workflow in a different repository.
11+
# It requires secrets from the "test-trigger-is" environment, which are only available to authorized users.
12+
trigger:
13+
runs-on:
14+
group: databricks-deco-testing-runner-group
15+
labels: ubuntu-latest-deco
16+
17+
environment: "test-trigger-is"
18+
19+
# Only run this job for PRs from branches on the main repository and not from forks.
20+
# Workflows triggered by PRs from forks don't have access to the "test-trigger-is" environment.
21+
if: "${{ !github.event.pull_request.head.repo.fork }}"
22+
23+
steps:
24+
- name: Generate GitHub App Token
25+
id: generate-token
26+
uses: actions/create-github-app-token@136412a57a7081aa63c935a2cc2918f76c34f514 # v1.11.2
27+
with:
28+
app-id: ${{ secrets.DECO_WORKFLOW_TRIGGER_APP_ID }}
29+
private-key: ${{ secrets.DECO_WORKFLOW_TRIGGER_PRIVATE_KEY }}
30+
owner: ${{ secrets.ORG_NAME }}
31+
repositories: ${{secrets.REPO_NAME}}
32+
33+
- name: Fetch start_integration_tests.py
34+
run: wget https://raw.githubusercontent.com/databricks/cli/refs/heads/main/tools/start_integration_tests.py
35+
36+
- name: Run start_integration_tests.py
37+
env:
38+
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
39+
run: |
40+
./start_integration_tests.py -R ${{ secrets.ORG_NAME }}/${{secrets.REPO_NAME}} --yes

tools/start_integration_tests.py

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Start integration test jobs for PRs by non-team members that are approved by team members.
4+
"""
5+
6+
import argparse
7+
import json
8+
import subprocess
9+
import sys
10+
from pathlib import Path
11+
import re
12+
13+
14+
CLI_REPO = "databricks/cli"
15+
CODE_OWNERS = "pietern andrewnester shreyas-goenka denik anton-107".split()
16+
ALLOWED_HEAD_REPOSITORY = {"id": "R_kgDOHVGMwQ", "name": "cli"}
17+
ALLOWED_HEAD_OWNER = {"id": "MDEyOk9yZ2FuaXphdGlvbjQ5OTgwNTI=", "login": "databricks"}
18+
19+
20+
def run(cmd):
21+
sys.stderr.write("+ " + " ".join(cmd) + "\n")
22+
return subprocess.run(cmd, check=True)
23+
24+
25+
def run_json(cmd):
26+
sys.stderr.write("+ " + " ".join(cmd) + "\n")
27+
result = subprocess.run(cmd, stdout=subprocess.PIPE, encoding="utf-8", check=True)
28+
29+
try:
30+
return json.loads(result.stdout)
31+
except Exception:
32+
sys.stderr.write(f"Failed to JSON parse:\n{result.stdout}\n")
33+
raise
34+
35+
36+
def get_approved_prs_by_non_team():
37+
prs = run_json(
38+
["gh", "pr", "-R", CLI_REPO, "list", "--limit", "300", "--json", "number,author,reviews,headRefOid,headRepository,headRepositoryOwner"]
39+
)
40+
result = []
41+
42+
for pr in prs:
43+
pr_number = pr["number"]
44+
author = pr["author"]["login"]
45+
46+
if author in CODE_OWNERS:
47+
continue
48+
49+
head_repo = pr["headRepository"]
50+
if head_repo != ALLOWED_HEAD_REPOSITORY:
51+
print(f"#{pr_number} by {author} skipped due to headRepository: {head_repo}")
52+
continue
53+
54+
head_owner = pr["headRepositoryOwner"]
55+
if head_owner != ALLOWED_HEAD_OWNER:
56+
print(f"#{pr_number} by {author} skipped due to headRepositoryOwner: {head_owner}")
57+
continue
58+
59+
approved_by = []
60+
for review in pr.get("reviews", []):
61+
approver = review["author"]["login"]
62+
if review["state"] == "APPROVED" and approver in CODE_OWNERS:
63+
approved_by.append(approver)
64+
65+
if not approved_by:
66+
continue
67+
68+
result.append(
69+
{
70+
"number": pr_number,
71+
"commit": pr["headRefOid"],
72+
"author": author,
73+
"approved_by": approved_by,
74+
}
75+
)
76+
77+
return prs, result
78+
79+
80+
def start_job(pr_number, commit_sha, author, approved_by, workflow, repo, force=False):
81+
pr_details = run_json(["gh", "pr", "-R", CLI_REPO, "view", str(pr_number), "--json", "title,url"])
82+
pr_title = pr_details.get("title", "")
83+
commit_url = f"https://github.com/{CLI_REPO}/pull/{pr_number}/commits/{commit_sha}"
84+
approvers = ", ".join(approved_by)
85+
86+
print(f"PR: #{pr_number} {pr_title}")
87+
print(f"Author: {author}")
88+
print(f"Approvers: {approvers}")
89+
print(f"Commit: {commit_url}")
90+
91+
if force:
92+
response = "y"
93+
print("Starting integration tests.")
94+
else:
95+
response = input("Start integration tests? (y/n): ")
96+
97+
if response.lower() == "y":
98+
result = run(
99+
[
100+
"gh",
101+
"workflow",
102+
"run",
103+
workflow,
104+
"-R",
105+
repo,
106+
"-F",
107+
f"pull_request_number={pr_number}",
108+
"-F",
109+
f"commit_sha={commit_sha}",
110+
],
111+
)
112+
print(f"Started integration tests for PR #{pr_number}")
113+
114+
115+
def get_status(commit_sha):
116+
statuses = run_json(["gh", "api", f"repos/{CLI_REPO}/commits/{commit_sha}/statuses"])
117+
result = []
118+
for st in statuses:
119+
if st["context"] != "Integration Tests Check":
120+
continue
121+
result.append(f"{st['state']} {st['target_url']}")
122+
return result
123+
124+
125+
def main():
126+
parser = argparse.ArgumentParser()
127+
parser.add_argument("--yes", action="store_true", default=False)
128+
parser.add_argument("--workflow", default="cli-isolated-pr")
129+
parser.add_argument("-R", "--repo")
130+
131+
args = parser.parse_args()
132+
assert args.repo, "Must provide repo where workflow is run with -R"
133+
134+
all_prs, approved_prs = get_approved_prs_by_non_team()
135+
136+
if not approved_prs:
137+
print(f"Fetched {len(all_prs)} PRs. No approved PRs from non-team members found.")
138+
return
139+
140+
print(f"Fetched {len(all_prs)} PRs.")
141+
142+
for pr in approved_prs:
143+
pr_number = pr["number"]
144+
commit_sha = pr["commit"]
145+
146+
status = get_status(commit_sha)
147+
148+
if not status:
149+
start_job(pr_number, commit_sha, pr["author"], approved_by=pr["approved_by"], workflow=args.workflow, repo=args.repo, force=args.yes)
150+
else:
151+
commit_url = f"https://github.com/{CLI_REPO}/pull/{pr_number}/commits/{commit_sha}"
152+
print(f"Tests already running for PR #{pr_number} {commit_url}")
153+
print("\n".join(status))
154+
print()
155+
156+
157+
if __name__ == "__main__":
158+
main()

0 commit comments

Comments
 (0)