|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
name: Approve Test Queue |
|
|
|
|
|
on: |
|
|
schedule: |
|
|
- cron: '*/5 * * * *' |
|
|
workflow_dispatch: |
|
|
|
|
|
jobs: |
|
|
approve-queue: |
|
|
runs-on: ubuntu-latest |
|
|
environment: main |
|
|
steps: |
|
|
- name: Checkout repository |
|
|
uses: actions/checkout@v4 |
|
|
|
|
|
- name: Set up Python |
|
|
uses: actions/setup-python@v5 |
|
|
with: |
|
|
python-version: "3.12" |
|
|
|
|
|
- name: Install dependencies |
|
|
run: | |
|
|
python -m pip install --upgrade pip |
|
|
pip install requests |
|
|
|
|
|
- name: Approve waiting deployments |
|
|
env: |
|
|
GITHUB_TOKEN: ${{ secrets.PAT }} |
|
|
MAX_CONCURRENCY: ${{ vars.MAX_CONCURRENCY || 1 }} |
|
|
run: | |
|
|
python - <<EOF |
|
|
import os |
|
|
import requests |
|
|
|
|
|
|
|
|
|
|
|
GITHUB_TOKEN = os.environ["GITHUB_TOKEN"] |
|
|
REPO = os.environ["GITHUB_REPOSITORY"] |
|
|
MAX_CONCURRENCY = int(os.environ["MAX_CONCURRENCY"]) |
|
|
API_BASE = f"https://api.github.com/repos/{REPO}" |
|
|
|
|
|
|
|
|
headers = { |
|
|
"Authorization": f"token {GITHUB_TOKEN}", |
|
|
"Accept": "application/vnd.github.v3+json", |
|
|
"X-GitHub-Api-Version": "2022-11-28", |
|
|
} |
|
|
|
|
|
def make_request(endpoint, method="GET", data=None): |
|
|
"""Make a request to the GitHub API with error handling.""" |
|
|
url = f"{API_BASE}/{endpoint}" |
|
|
try: |
|
|
if method == "GET": |
|
|
response = requests.get(url, headers=headers) |
|
|
else: |
|
|
response = requests.post(url, headers=headers, json=data) |
|
|
response.raise_for_status() |
|
|
response_json = response.json() |
|
|
if hasattr(response, "links") and "actions/runs?status" in endpoint: |
|
|
response_json["next"] = response.links.get("next", {}).get("url") |
|
|
|
|
|
return response_json |
|
|
except requests.exceptions.RequestException as e: |
|
|
print(f"Error making request to {endpoint}: {str(e)}") |
|
|
if hasattr(e.response, 'text'): |
|
|
print(f"Response: {e.response.text}") |
|
|
return None |
|
|
|
|
|
|
|
|
def get_workflow_runs(status): |
|
|
"""Get all workflow runs for a given status.""" |
|
|
all_results = [] |
|
|
endpoint = f"actions/runs?status={status}" |
|
|
while endpoint: |
|
|
response = make_request(endpoint) |
|
|
if not response: |
|
|
break |
|
|
|
|
|
all_results.extend(response.get("workflow_runs", [])) |
|
|
endpoint = None |
|
|
next_url = response.get("next") |
|
|
if next_url: |
|
|
endpoint = f"actions/runs?{next_url.split('?')[1]}" |
|
|
|
|
|
return all_results |
|
|
|
|
|
|
|
|
|
|
|
print("Fetching workflow runs...") |
|
|
queued_workflow_runs = get_workflow_runs("queued") |
|
|
in_progress_workflow_runs = get_workflow_runs("in_progress") |
|
|
|
|
|
|
|
|
queued_workflows = sum(1 for run in queued_workflow_runs if run["name"] == "CICD NeMo") |
|
|
in_progress_workflows = sum(1 for run in in_progress_workflow_runs if run["name"] == "CICD NeMo") |
|
|
|
|
|
total_workflows = queued_workflows + in_progress_workflows |
|
|
print(f"Current queued workflows: {queued_workflows}") |
|
|
print(f"Current running workflows: {in_progress_workflows}") |
|
|
print(f"Total workflows: {total_workflows}") |
|
|
print(f"Max concurrency: {MAX_CONCURRENCY}") |
|
|
|
|
|
if total_workflows >= MAX_CONCURRENCY: |
|
|
print("Maximum concurrency reached, no new approvals will be made") |
|
|
exit(0) |
|
|
|
|
|
|
|
|
print("Fetching deployments...") |
|
|
pending_workflows = get_workflow_runs("waiting") |
|
|
pending_workflows = [run for run in pending_workflows if run["name"] == "CICD NeMo"] |
|
|
|
|
|
|
|
|
print("Sorting workflows...") |
|
|
pending_workflows = sorted(pending_workflows, key=lambda x: x["created_at"]) |
|
|
|
|
|
|
|
|
print("Processing ...") |
|
|
for workflow in pending_workflows: |
|
|
if total_workflows >= MAX_CONCURRENCY: |
|
|
print("Maximum concurrency reached, stopping approvals") |
|
|
break |
|
|
|
|
|
workflow_id = workflow["id"] |
|
|
workflow_name = workflow["display_title"] |
|
|
print(f"Approving workflow {workflow_name} with Run Id: {workflow_id}") |
|
|
|
|
|
deployment_url = f"actions/runs/{workflow_id}/pending_deployments" |
|
|
deployment = make_request(deployment_url)[0] |
|
|
environment_id = deployment["environment"]["id"] |
|
|
|
|
|
|
|
|
status_data = { |
|
|
"environment_ids": [environment_id], |
|
|
"state": "approved", |
|
|
"comment": "Automatically approved by queue manager" |
|
|
} |
|
|
result = make_request(deployment_url, method="POST", data=status_data) |
|
|
|
|
|
if result: |
|
|
total_workflows += 1 |
|
|
else: |
|
|
print(f"Failed to approve deployment {deployment['id']}") |
|
|
exit(1) |
|
|
|
|
|
EOF |
|
|
notify: |
|
|
if: failure() |
|
|
runs-on: ubuntu-latest |
|
|
needs: [approve-queue] |
|
|
steps: |
|
|
- name: Notify |
|
|
env: |
|
|
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} |
|
|
SLACK_WEBHOOK_ADMIN: <!subteam^${{ secrets.SLACK_WEBHOOK_ADMIN }}> |
|
|
GITHUB_RUN_ID: ${{ github.run_id }} |
|
|
GITHUB_REPOSITORY: ${{ github.repository }} |
|
|
run: | |
|
|
curl -X POST \ |
|
|
-H 'Content-type: application/json' \ |
|
|
--data "{\"text\":\":robot_joy: <https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}|Test-queue-approval-bot workflow> failed. Please review manually.\n\ncc ${SLACK_WEBHOOK_ADMIN}\"}" \ |
|
|
$SLACK_WEBHOOK |
|
|
|