Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 12 additions & 116 deletions .github/workflows/octo-ci-status.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,23 @@ on:

permissions:
actions: read
contents: read

jobs:
notify:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout reusable-workflow scripts
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
repository: Mininglamp-OSS/.github
ref: ${{ github.workflow_sha }}
path: _shared
sparse-checkout: scripts
sparse-checkout-cone-mode: false
persist-credentials: false

- name: Check state change and notify Octo
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand All @@ -46,119 +57,4 @@ jobs:
RUN_ID: ${{ inputs.run_id }}
RUN_URL: ${{ inputs.run_url }}
PROJECT_GROUP_ID: ${{ inputs.project_group_id }}
run: |
python3 << 'PYEOF'
import os, json, urllib.request, urllib.error, sys

conclusion = os.environ['CONCLUSION']
# Cancelled runs are not real failures; skip silently
if conclusion == 'cancelled':
print('Cancelled run, skipping.')
sys.exit(0)

repo = os.environ['REPO_NAME']
wf_name = os.environ['WORKFLOW_NAME']
run_url = os.environ['RUN_URL']
proj_gid = os.environ['PROJECT_GROUP_ID']
gh_token = os.environ['GITHUB_TOKEN']
bot_token = os.environ['OCTO_BOT_TOKEN']

# Fetch recent completed runs on main branch
api_url = (
f'https://api.github.com/repos/Mininglamp-OSS/{repo}/actions/runs'
f'?branch=main&status=completed&per_page=10'
)
req = urllib.request.Request(api_url, headers={
'Authorization': f'Bearer {gh_token}',
'Accept': 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28',
})
try:
with urllib.request.urlopen(req, timeout=15) as r:
runs = json.load(r)['workflow_runs']
except (urllib.error.HTTPError, urllib.error.URLError) as e:
print(f'ERROR: failed to fetch workflow runs: {e}')
sys.exit(1)

# Filter to the same workflow by name
same_wf = [r for r in runs if r['name'] == wf_name]

if not same_wf:
print('No matching runs found, skipping.')
sys.exit(0)

# Use run_id to precisely identify the current run (avoids race conditions
# when two runs complete close together).
run_id = int(os.environ.get('RUN_ID', 0))
matched = [r for r in same_wf if r['id'] == run_id]
if not matched:
print('WARN: run_id %s not found in recent runs window, falling back to same_wf[0]' % run_id)
current = matched[0] if matched else same_wf[0]
# Only consider runs created before current to avoid picking a later
# concurrent run as "previous" (which would flip alert/recovery semantics).
older = [r for r in same_wf
if r['id'] != current['id']
and r['created_at'] < current['created_at']]
previous = older[0] if older else None

curr_conclusion = current['conclusion']
prev_conclusion = previous['conclusion'] if previous else None

print(f'State: {prev_conclusion} → {curr_conclusion}')

# Only act on state changes
if curr_conclusion == prev_conclusion:
print('No state change, silent.')
sys.exit(0)

# Guard: first-ever run has no previous history — skip silently
if prev_conclusion is None:
print('First run detected (no previous history), skipping notification.')
sys.exit(0)

# Determine message
if curr_conclusion == 'failure':
msg = (
f'❌ [{repo}] main CI 挂了\n\n'
f'工作流:{wf_name}\n'
f'🔗 {run_url}'
)
elif curr_conclusion == 'success' and prev_conclusion == 'failure':
msg = (
f'✅ [{repo}] main CI 已恢复\n\n'
f'工作流:{wf_name}\n'
f'🔗 {run_url}'
)
else:
print(f'Unhandled transition {prev_conclusion!r} → {curr_conclusion!r}, skipping.')
sys.exit(0)

# Send to Octo IM
send_url = 'https://im.deepminer.com.cn/api/v1/bot/sendMessage'
headers = {
'Authorization': f'Bearer {bot_token}',
'Content-Type': 'application/json',
}

failed = []

def send(group_id, message):
body = json.dumps({
'channel_id': group_id,
'channel_type': 2,
'payload': {'type': 1, 'content': message},
}).encode()
req = urllib.request.Request(send_url, data=body, headers=headers, method='POST')
try:
with urllib.request.urlopen(req, timeout=15) as r:
print(f' → {group_id[:8]}... HTTP {r.status}')
except (urllib.error.HTTPError, urllib.error.URLError) as e:
print(f'ERROR: failed to send message to {group_id[:8]}...: {e}')
failed.append(group_id)

# Push to ci-status group and the repo's project group
send('4ade985d984e432eb7fbdd0ad4f8118a', msg)
send(proj_gid, msg)
if failed:
sys.exit(1)
PYEOF
run: python3 _shared/scripts/octo_ci_status_notify.py
58 changes: 13 additions & 45 deletions .github/workflows/octo-issue-feed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,24 @@ on:
OCTO_BOT_TOKEN:
required: true

permissions: {}
permissions:
contents: read

jobs:
notify:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout reusable-workflow scripts
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
repository: Mininglamp-OSS/.github
ref: ${{ github.workflow_sha }}
path: _shared
sparse-checkout: scripts
sparse-checkout-cone-mode: false
persist-credentials: false

- name: Send to Octo
env:
OCTO_BOT_TOKEN: ${{ secrets.OCTO_BOT_TOKEN }}
Expand All @@ -51,47 +62,4 @@ jobs:
ISSUE_LABELS: ${{ inputs.issue_labels }}
EVENT_ACTION: ${{ inputs.event_action }}
PROJECT_GROUP_ID: ${{ inputs.project_group_id }}
run: |
python3 << 'PYEOF'
import os, json, sys, urllib.request, urllib.error

action = os.environ['EVENT_ACTION']
emoji = {'opened': '🆕', 'closed': '✅', 'reopened': '🔄', 'labeled': '🏷️'}.get(action, 'ℹ️')

try:
labels = json.loads(os.environ['ISSUE_LABELS'])
labels_part = ' · 🏷️ ' + ', '.join(labels) if labels else ''
except Exception:
labels_part = ''

repo = os.environ['REPO_NAME']
num = os.environ['ISSUE_NUMBER']
title = os.environ['ISSUE_TITLE']
url = os.environ['ISSUE_URL']
author = os.environ['ISSUE_AUTHOR']

feed_msg = f"{emoji} [{repo}] Issue #{num} · {title}\n👤 {author}{labels_part}\n🔗 {url}"
proj_msg = f"{emoji} Issue #{num} · {title}\n👤 {author}{labels_part}\n🔗 {url}"

api = 'https://im.deepminer.com.cn/api/v1/bot/sendMessage'
token = os.environ['OCTO_BOT_TOKEN']
headers = {'Authorization': f'Bearer {token}', 'Content-Type': 'application/json'}

failed = []

def send(group_id, msg):
body = json.dumps({'channel_id': group_id, 'channel_type': 2,
'payload': {'type': 1, 'content': msg}}).encode()
req = urllib.request.Request(api, data=body, headers=headers, method='POST')
try:
with urllib.request.urlopen(req, timeout=15) as r:
print(f'→ {group_id[:8]}... {r.status}')
except (urllib.error.HTTPError, urllib.error.URLError) as e:
print(f'ERROR: failed to send message to {group_id[:8]}...: {e}')
failed.append(group_id)

send('151a45970e1546afa9e947ac36a5c4e5', feed_msg)
send(os.environ['PROJECT_GROUP_ID'], proj_msg)
if failed:
sys.exit(1)
PYEOF
run: python3 _shared/scripts/octo_issue_feed_notify.py
63 changes: 13 additions & 50 deletions .github/workflows/octo-pr-feed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,24 @@ on:
OCTO_BOT_TOKEN:
required: true

permissions: {}
permissions:
contents: read

jobs:
notify:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout reusable-workflow scripts
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
repository: Mininglamp-OSS/.github
ref: ${{ github.workflow_sha }}
path: _shared
sparse-checkout: scripts
sparse-checkout-cone-mode: false
persist-credentials: false

- name: Send to Octo
env:
OCTO_BOT_TOKEN: ${{ secrets.OCTO_BOT_TOKEN }}
Expand All @@ -63,52 +74,4 @@ jobs:
PR_CHANGED_FILES: ${{ inputs.pr_changed_files }}
EVENT_ACTION: ${{ inputs.event_action }}
PROJECT_GROUP_ID: ${{ inputs.project_group_id }}
run: |
python3 << 'PYEOF'
import os, json, sys, urllib.request, urllib.error

action = os.environ['EVENT_ACTION']
merged = os.environ['PR_MERGED'].lower() == 'true'

if action == 'closed':
emoji = '🟢' if merged else '🔴'
else:
emoji = {'opened': '🔵', 'reopened': '🔄',
'review_requested': '👀', 'ready_for_review': '✅'}.get(action, 'ℹ️')

adds = int(os.environ.get('PR_ADDITIONS', 0) or 0)
dels = int(os.environ.get('PR_DELETIONS', 0) or 0)
files = int(os.environ.get('PR_CHANGED_FILES', 0) or 0)
stats_part = f' · +{adds} -{dels} · {files} files' if (adds or dels or files) else ''

repo = os.environ['REPO_NAME']
num = os.environ['PR_NUMBER']
title = os.environ['PR_TITLE']
url = os.environ['PR_URL']
author = os.environ['PR_AUTHOR']

feed_msg = f"{emoji} [{repo}] PR #{num} · {title}\n👤 {author}{stats_part}\n🔗 {url}"
proj_msg = f"{emoji} PR #{num} · {title}\n👤 {author}{stats_part}\n🔗 {url}"

api = 'https://im.deepminer.com.cn/api/v1/bot/sendMessage'
token = os.environ['OCTO_BOT_TOKEN']
headers = {'Authorization': f'Bearer {token}', 'Content-Type': 'application/json'}

failed = []

def send(group_id, msg):
body = json.dumps({'channel_id': group_id, 'channel_type': 2,
'payload': {'type': 1, 'content': msg}}).encode()
req = urllib.request.Request(api, data=body, headers=headers, method='POST')
try:
with urllib.request.urlopen(req, timeout=15) as r:
print(f'→ {group_id[:8]}... {r.status}')
except (urllib.error.HTTPError, urllib.error.URLError) as e:
print(f'ERROR: failed to send message to {group_id[:8]}...: {e}')
failed.append(group_id)

send('1c303c142e9840f2a9b46c10b0972e8d', feed_msg)
send(os.environ['PROJECT_GROUP_ID'], proj_msg)
if failed:
sys.exit(1)
PYEOF
run: python3 _shared/scripts/octo_pr_feed_notify.py
Loading