-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #32 from rapidsai/feat/check_nightly_ci
Add an action to check whether nightlies have succeeded recently
- Loading branch information
Showing
7 changed files
with
197 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
*.swp |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
name: check-nightly-success | ||
description: Check if the nightlies have succeeded recently. | ||
inputs: | ||
repo: | ||
description: "The repository to check" | ||
required: true | ||
type: string | ||
repo_owner: | ||
description: "The org that owns the repo (default: rapidsai)" | ||
required: false | ||
default: "rapidsai" | ||
type: string | ||
workflow_id: | ||
description: "The workflow whose runs to check" | ||
required: false | ||
default: "test.yaml" | ||
type: string | ||
max_days_without_success: | ||
description: "The number of consecutive days that may go by without a successful CI run" | ||
required: false | ||
default: 7 | ||
type: integer | ||
|
||
runs: | ||
using: composite | ||
steps: | ||
- name: Run the Python script | ||
shell: bash | ||
env: | ||
REPO: ${{ inputs.repo }} | ||
REPO_OWNER: ${{ inputs.repo_owner }} | ||
WORKFLOW_ID: ${{ inputs.workflow_id }} | ||
MAX_DAYS_WITHOUT_SUCCESS: ${{ inputs.max_days_without_success }} | ||
run: | | ||
python -m pip install requests | ||
python shared-actions/check_nightly_success/check-nightly-success/check.py ${REPO} --repo-owner ${REPO_OWNER} --workflow-id ${WORKFLOW_ID} --max-days-without-success ${MAX_DAYS_WITHOUT_SUCCESS} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
"""Check whether a GHA workflow has run successfully in the last N days.""" | ||
# ruff: noqa: INP001 | ||
|
||
import argparse | ||
import itertools | ||
import os | ||
import re | ||
import sys | ||
from datetime import datetime | ||
|
||
import requests | ||
|
||
# Constants | ||
GITHUB_TOKEN = os.environ["RAPIDS_GH_TOKEN"] | ||
GOOD_STATUSES = {"success"} | ||
|
||
|
||
def main( | ||
repo: str, | ||
repo_owner: str, | ||
workflow_id: str, | ||
max_days_without_success: int, | ||
num_attempts: int = 5, | ||
) -> bool: | ||
"""Check whether a GHA workflow has run successfully in the last N days.""" | ||
headers = {"Authorization": f"token {GITHUB_TOKEN}"} | ||
url = f"https://api.github.com/repos/{repo_owner}/{repo}/actions/workflows/{workflow_id}/runs" | ||
exceptions = [] | ||
for _ in range(num_attempts): | ||
try: | ||
response = requests.get(url, headers=headers, timeout=10) | ||
response.raise_for_status() | ||
break | ||
except requests.RequestException as e: | ||
exceptions.append(e) | ||
else: | ||
sep = "\n\t" | ||
msg = ( | ||
f"Failed to fetch {url} after {num_attempts} attempts with the following " | ||
f"errors: {sep}{'{sep}'.join(exceptions)}" | ||
) | ||
raise RuntimeError(msg) | ||
|
||
runs = response.json()["workflow_runs"] | ||
tz = datetime.fromisoformat(runs[0]["run_started_at"]).tzinfo | ||
now = datetime.now(tz=tz) | ||
|
||
branch_ok = {} | ||
for branch, branch_runs in itertools.groupby(runs, key=lambda r: r["head_branch"]): | ||
if not re.match("branch-[0-9]{2}.[0-9]{2}", branch): | ||
continue | ||
|
||
branch_ok[branch] = False | ||
for run in sorted(branch_runs, key=lambda r: r["run_started_at"], reverse=True): | ||
if ( | ||
now - datetime.fromisoformat(run["run_started_at"]) | ||
).days > max_days_without_success: | ||
break | ||
if run["conclusion"] in GOOD_STATUSES: | ||
branch_ok[branch] = True | ||
break | ||
|
||
failed_branches = [k for k, v in branch_ok.items() if not v] | ||
if failed_branches: | ||
print( # noqa: T201 | ||
f"Branches with no successful runs of {workflow_id} in the last " | ||
f"{max_days_without_success} days: " | ||
f"{', '.join(failed_branches)}", | ||
) | ||
return len(failed_branches) > 0 | ||
|
||
|
||
if __name__ == "__main__": | ||
parser = argparse.ArgumentParser() | ||
parser.add_argument("repo", type=str, help="Repository name") | ||
parser.add_argument( | ||
"--repo-owner", | ||
default="rapidsai", | ||
help="Repository organization/owner", | ||
) | ||
parser.add_argument("--workflow-id", default="test.yaml", help="Workflow ID") | ||
parser.add_argument( | ||
"--max-days-without-success", | ||
type=int, | ||
default=7, | ||
help="Maximum number of days without a successful run", | ||
) | ||
args = parser.parse_args() | ||
|
||
sys.exit( | ||
main( | ||
args.repo, | ||
args.repo_owner, | ||
args.workflow_id, | ||
args.max_days_without_success, | ||
), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
name: dispatch-check-nightly-success | ||
description: Clone shared-actions and dispatch to the check-nightly-success action. | ||
inputs: | ||
repo: | ||
description: "The repository to check" | ||
required: true | ||
type: string | ||
repo_owner: | ||
description: "The org that owns the repo (default: rapidsai)" | ||
required: false | ||
default: "rapidsai" | ||
type: string | ||
workflow_id: | ||
description: "The workflow whose runs to check" | ||
required: false | ||
default: "test.yaml" | ||
type: string | ||
max_days_without_success: | ||
description: "The number of consecutive days that may go by without a successful CI run" | ||
required: false | ||
default: 7 | ||
type: integer | ||
|
||
runs: | ||
using: 'composite' | ||
steps: | ||
- name: Clone shared-actions repo | ||
uses: actions/checkout@v4 | ||
with: | ||
repository: ${{ env.SHARED_ACTIONS_REPO || 'rapidsai/shared-actions' }} | ||
ref: ${{ env.SHARED_ACTIONS_REF || 'main' }} | ||
path: ./shared-actions | ||
- name: Run check-nightly-success | ||
uses: ./shared-actions/check_nightly_success/check-nightly-success | ||
with: | ||
repo: ${{ inputs.repo }} | ||
repo_owner: ${{ inputs.repo_owner }} | ||
workflow_id: ${{ inputs.workflow_id }} | ||
max_days_without_success: ${{ inputs.max_days_without_success }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# Copyright (c) 2024, NVIDIA CORPORATION. | ||
|
||
[tool.ruff] | ||
target-version = "py310" | ||
|
||
[tool.ruff.lint] | ||
select = ["ALL"] | ||
ignore = [ | ||
# Incompatible with D211 | ||
"D203", | ||
# Incompatible with D213 | ||
"D213", | ||
# Incompatible with ruff-format | ||
"COM812", | ||
"ISC001", | ||
] | ||
fixable = ["ALL"] |