fix(ci): repair draft PR command dispatch (#9271)

* fix(ci): repair draft PR command dispatch

Signed-off-by: WenyXu <wenymedia@gmail.com>

* fix(ci): dispatch command workflows by branch ref

Signed-off-by: WenyXu <wenymedia@gmail.com>

* fix(ci): admit PR authors and writers for CI commands

Signed-off-by: WenyXu <wenymedia@gmail.com>

* fix(ci): preserve dispatch guard dependency semantics

Signed-off-by: WenyXu <wenymedia@gmail.com>

* fix(ci): configure slash command permissions individually

Signed-off-by: WenyXu <wenymedia@gmail.com>

* refactor(ci): run command workflows at dispatch branch head

Signed-off-by: WenyXu <wenymedia@gmail.com>

* fix(ci): rerun fork PR checks and report command failures

Signed-off-by: WenyXu <wenymedia@gmail.com>

---------

Signed-off-by: WenyXu <wenymedia@gmail.com>
This commit is contained in:
Weny Xu
2026-09-23 10:32:19 +00:00
committed by GitHub
parent d910183cc4
commit 37fed9f12b
12 changed files with 239 additions and 50 deletions
+34 -9
View File
@@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""Admit `/ci` comments and dispatch a pinned draft-PR CI workflow."""
"""Admit `/ci` comments and dispatch a draft-PR CI workflow."""
import json, os, re, sys, urllib.request
COMMAND = re.compile(r'^/ci(?:\s+(.+))?$')
@@ -29,7 +29,7 @@ OPTIONS = {
'fuzz chaos': ('integration.yml', 'chaos', 'chaos fuzz'),
'fuzz all': ('integration.yml', 'all', 'all fuzz'),
}
HELP = '''Available draft-PR CI commands:\n\n- `/ci` or `/ci rust` — Rust CI\n- `/ci integration` — integration CI without fuzz\n- `/ci checks` or `/ci docs`\n- `/ci fuzz standalone|distributed|chaos`\n- `/ci fuzz all` — all fuzz suites (admin only)\n\nCommands require repository admin permission and a same-repository open draft PR. CI is pinned to the current head SHA; comment again after a push.'''
HELP = '''Available draft-PR CI commands:\n\n- `/ci` — standard CI\n- `/ci rust` — Rust CI\n- `/ci integration` — integration CI without fuzz\n- `/ci checks` or `/ci docs`\n- `/ci fuzz standalone|distributed|chaos`\n- `/ci fuzz all` — all fuzz suites (admin only)\n\nCommands require the PR author or repository write/maintain/admin permission and an open draft PR. Fork PRs require repository write permission and rerun existing pull-request CI; fuzz commands are same-repository only. `/ci fuzz all` requires admin permission. Same-repository CI runs the branch head at dispatch time. Fork CI reruns the original pull-request revision (within GitHub rerun limits); sync your branch with the updated CI configuration first.'''
def api(path):
req=urllib.request.Request(os.environ['GITHUB_API_URL']+path, headers={'Authorization':'Bearer '+os.environ['GITHUB_TOKEN'],'Accept':'application/vnd.github+json'})
@@ -46,7 +46,7 @@ def reject(number, text): out(skip='true',pr_number=number,reply='CI command ign
def main():
if os.environ.get('DISPATCH_SENDER') != 'github-actions[bot]': return reject('', 'invalid dispatch sender.')
comment=api('/repos/'+os.environ['GITHUB_REPOSITORY']+'/issues/comments/'+os.environ['COMMENT_ID'])
body=comment.get('body','').splitlines()[0].strip(); m=COMMAND.fullmatch(body)
body=(comment.get('body') or '').partition('\n')[0].strip(); m=COMMAND.fullmatch(body)
number=str(comment.get('issue_url','').rstrip('/').split('/')[-1])
if not m: return reject(number,'comment is not a `/ci` command.')
arg=(m.group(1) or '').strip().lower()
@@ -54,13 +54,38 @@ def main():
if arg=='help': out(skip='true',pr_number=number,reply=HELP); return 0
pr=api('/repos/'+os.environ['GITHUB_REPOSITORY']+'/pulls/'+number)
if pr.get('state')!='open' or not pr.get('draft'): return reject(number,'PR must be open and draft.')
if pr.get('head',{}).get('repo',{}).get('full_name') != os.environ['GITHUB_REPOSITORY']: return reject(number,'fork PRs are not admitted.')
head=pr.get('head',{}).get('sha','')
if head != os.environ.get('DISPATCH_HEAD_SHA') or not re.fullmatch('[0-9a-f]{40}',head): return reject(number,'PR head changed; comment again.')
fork=pr.get('head',{}).get('repo',{}).get('full_name') != os.environ['GITHUB_REPOSITORY']
if fork and arg.startswith('fuzz '): return reject(number,'fuzz commands require a same-repository PR.')
head=pr.get('head',{})
head_sha=head.get('sha','')
head_ref=head.get('ref','')
if head_sha != os.environ.get('DISPATCH_HEAD_SHA') or not re.fullmatch('[0-9a-f]{40}',head_sha) or not head_ref: return reject(number,'PR head changed; comment again.')
actor=comment.get('user',{}).get('login','')
permission=api('/repos/'+os.environ['GITHUB_REPOSITORY']+'/collaborators/'+actor+'/permission').get('user',{}).get('permission')
if permission!='admin': return reject(number,'repository admin permission is required.')
if not actor: return reject(number,'comment author is missing.')
is_author=actor==pr.get('user',{}).get('login')
if fork or arg=='fuzz all' or not is_author:
permission=api('/repos/'+os.environ['GITHUB_REPOSITORY']+'/collaborators/'+actor+'/permission').get('permission')
if arg=='fuzz all':
if permission!='admin': return reject(number,'repository admin permission is required for `/ci fuzz all`.')
elif permission not in ('write','maintain','admin'):
return reject(number,'PR author or repository write permission is required.')
workflow, profile, label=OPTIONS[arg]
out(skip='false',pr_number=number,head_sha=head,workflow=workflow,fuzz_profile=profile,reply=f'Dispatched {label} for `{head}`.')
if fork:
runs=api('/repos/'+os.environ['GITHUB_REPOSITORY']+'/actions/runs?event=pull_request&head_sha='+head_sha+'&per_page=100')['workflow_runs']
selected={}
for run in runs:
name=run.get('path','').split('@',1)[0].removeprefix('.github/workflows/')
if name not in workflow.split(',') or name in selected: continue
if run.get('head_sha') != head_sha or run.get('head_repository',{}).get('full_name') != head['repo']['full_name'] or run.get('head_branch') != head_ref: continue
selected[name]=run
if not selected: return reject(number,'no pull-request CI runs found for this head; push a new commit first.')
if arg and workflow not in selected: return reject(number,'selected CI has no pull-request run for this head.')
if any(run['status'] != 'completed' for run in selected.values()): return reject(number,'pull-request CI is still pending; approve or wait for it, then comment again.')
for run in selected.values():
original=run if run.get('run_attempt',1)==1 else api('/repos/'+os.environ['GITHUB_REPOSITORY']+'/actions/runs/'+str(run['id'])+'/attempts/1')
if original.get('conclusion') != 'skipped': return reject(number,'only originally skipped draft CI runs can be requested; push a new commit while draft.')
out(skip='false',pr_number=number,run_ids=','.join(str(run['id']) for run in selected.values()),reply=f'Requested rerun of {", ".join(selected)} for `{head_sha}`.')
return 0
out(skip='false',pr_number=number,head_sha=head_sha,head_ref=head_ref,workflow=workflow,fuzz_profile=profile,reply=f'Dispatched {label} for branch `{head_ref}`.')
return 0
if __name__ == '__main__': sys.exit(main())
+132 -4
View File
@@ -14,8 +14,11 @@
# limitations under the License.
import importlib.util
import json
import os
import tempfile
import subprocess
import textwrap
import unittest
from pathlib import Path
from unittest.mock import patch
@@ -27,6 +30,9 @@ ci = importlib.util.module_from_spec(spec)
spec.loader.exec_module(ci)
OPTIONS_TO_TEST = ["/ci", "/ci rust", "/ci fuzz chaos", "/ci fuzz all"]
class CiSlashTest(unittest.TestCase):
def setUp(self):
self.output = tempfile.NamedTemporaryFile(delete=False)
@@ -54,23 +60,145 @@ class CiSlashTest(unittest.TestCase):
self.assertIn("skip=true", output)
self.assertIn("Available draft-PR CI commands", output)
def test_dispatches_full_suite_at_verified_head(self):
def test_dispatches_full_suite_with_top_level_admin_permission(self):
output = self.run_main([
{"body": "/ci", "issue_url": "https://api.github.test/repos/GreptimeTeam/greptimedb/issues/42", "user": {"login": "admin"}},
{"state": "open", "draft": True, "head": {"sha": "a" * 40, "repo": {"full_name": "GreptimeTeam/greptimedb"}}},
{"user": {"permission": "admin"}},
{"state": "open", "draft": True, "head": {"sha": "a" * 40, "ref": "fix/draft-ci", "repo": {"full_name": "GreptimeTeam/greptimedb"}}},
{"permission": "admin", "user": {"login": "admin"}},
])
self.assertIn("skip=false", output)
self.assertIn("head_ref=fix/draft-ci", output)
self.assertIn("workflow=rust.yml,integration.yml,checks.yml,docs.yml", output)
def test_rejects_non_draft_before_permission_lookup(self):
output = self.run_main([
{"body": "/ci fuzz chaos", "issue_url": "https://api.github.test/repos/GreptimeTeam/greptimedb/issues/42", "user": {"login": "admin"}},
{"state": "open", "draft": False, "head": {"sha": "a" * 40, "repo": {"full_name": "GreptimeTeam/greptimedb"}}},
{"state": "open", "draft": False, "head": {"sha": "a" * 40, "ref": "fix/draft-ci", "repo": {"full_name": "GreptimeTeam/greptimedb"}}},
])
self.assertIn("skip=true", output)
self.assertIn("PR must be open and draft", output)
def test_rejects_missing_head_ref_before_permission_lookup(self):
output = self.run_main([
{"body": "/ci", "issue_url": "https://api.github.test/repos/GreptimeTeam/greptimedb/issues/42", "user": {"login": "admin"}},
{"state": "open", "draft": True, "head": {"sha": "a" * 40, "repo": {"full_name": "GreptimeTeam/greptimedb"}}},
])
self.assertIn("skip=true", output)
self.assertIn("PR head changed; comment again.", output)
def test_permission_matrix(self):
for command in OPTIONS_TO_TEST:
for author in (True, False):
for permission in ("admin", "maintain", "write", "triage", "read", "none", None):
with self.subTest(command=command, author=author, permission=permission):
Path(self.output.name).write_text("")
responses = [
{"body": command, "issue_url": "https://api.github.test/repos/GreptimeTeam/greptimedb/issues/42", "user": {"login": "actor"}},
{"state": "open", "draft": True, "user": {"login": "actor" if author else "other"}, "head": {"sha": "a" * 40, "ref": "fix/draft-ci", "repo": {"full_name": "GreptimeTeam/greptimedb"}}},
]
if command == "/ci fuzz all" or not author:
responses.append({"permission": permission})
output = self.run_main(responses)
allowed = permission == "admin" if command == "/ci fuzz all" else author or permission in ("admin", "maintain", "write")
self.assertIn("skip=false" if allowed else "skip=true", output)
def test_reply_uses_issue_comment_endpoint(self):
workflow = SCRIPT.parents[1] / "workflows" / "ci-slash.yml"
step = workflow.read_text().split(" - name: Reply with command result\n", 1)[1]
command = textwrap.dedent(step.split(" run: |\n", 1)[1])
reply = "Denied: `example`\nSecond line with $variables and 'quotes'"
with tempfile.TemporaryDirectory() as directory:
gh = Path(directory) / "gh"
gh.write_text("#!/usr/bin/env python3\nimport json, sys\nprint(json.dumps(sys.argv[1:]))\n")
gh.chmod(0o755)
env = {**os.environ, "PATH": directory + os.pathsep + os.environ["PATH"], "GITHUB_REPOSITORY": "GreptimeTeam/greptimedb", "PR_NUMBER": "42", "REPLY": reply, "ADMIT_OUTCOME": "success", "DISPATCH_OUTCOME": "success"}
result = subprocess.run(["bash", "-eu", "-c", command], env=env, check=True, capture_output=True, text=True)
self.assertEqual(json.loads(result.stdout), ["api", "--method", "POST", "/repos/GreptimeTeam/greptimedb/issues/42/comments", "-f", "body=" + reply])
def test_failure_reply_and_partial_dispatch(self):
workflow = (SCRIPT.parents[1] / "workflows" / "ci-slash.yml").read_text()
dispatch = textwrap.dedent(workflow.split(" - name: Dispatch selected CI workflow\n", 1)[1].split(" run: |\n", 1)[1].split(" - name: Reply", 1)[0])
reply = textwrap.dedent(workflow.split(" - name: Reply with command result\n", 1)[1].split(" run: |\n", 1)[1])
with tempfile.TemporaryDirectory() as directory:
gh = Path(directory) / "gh"
log = Path(directory) / "calls"
gh.write_text("#!/usr/bin/env python3\nimport json, os, sys\nwith open(os.environ['CALLS'], 'a') as f: f.write(json.dumps(sys.argv[1:]) + '\\n')\nsys.exit(1 if '/runs/8/rerun' in sys.argv[-1] else 0)\n")
gh.chmod(0o755)
env = {**os.environ, "PATH": directory + os.pathsep + os.environ["PATH"], "CALLS": str(log), "GITHUB_REPOSITORY": "GreptimeTeam/greptimedb", "RUN_IDS": "7,8,9", "PR_NUMBER": "42", "REPLY": "Success", "ADMIT_OUTCOME": "success", "DISPATCH_OUTCOME": "failure", "GITHUB_SERVER_URL": "https://github.com", "GITHUB_RUN_ID": "123"}
result = subprocess.run(["bash", "-eu", "-c", dispatch], env=env, capture_output=True)
self.assertNotEqual(result.returncode, 0)
subprocess.run(["bash", "-eu", "-c", reply], env=env, check=True, capture_output=True)
calls = [json.loads(line) for line in log.read_text().splitlines()]
self.assertEqual([call[-1] for call in calls[:2]], ["/repos/GreptimeTeam/greptimedb/actions/runs/7/rerun", "/repos/GreptimeTeam/greptimedb/actions/runs/8/rerun"])
self.assertIn("CI trigger failed; some workflows may already have been requested", calls[2][-1])
self.assertIn("/actions/runs/123", calls[2][-1])
self.assertIn("!cancelled()", workflow.split(" - name: Reply with command result", 1)[1])
def test_fork_rerun_gates_cover_standard_jobs(self):
import re
for name, count in [("rust", 7), ("integration", 5), ("checks", 5), ("docs", 3)]:
workflow = (SCRIPT.parents[1] / "workflows" / (name + ".yml")).read_text()
gates = re.findall(r"^ if:.*github.run_attempt > 1.*$", workflow, re.MULTILINE)
self.assertEqual(len(gates), count, name)
self.assertTrue(all("always()" not in gate for gate in gates))
def test_command_permissions(self):
workflow = SCRIPT.parents[1] / "workflows" / "slash-command-dispatch.yml"
config = workflow.read_text().split("config: >-", 1)[1].split(" - name:", 1)[0]
self.assertEqual(json.loads(config), [
{"command": "query-regression", "permission": "admin", "issue_type": "pull-request"},
{"command": "ci", "permission": "none", "issue_type": "pull-request"},
])
def test_fork_requires_writer_even_for_author(self):
for permission in ("read", "write", "maintain", "admin"):
with self.subTest(permission=permission):
Path(self.output.name).write_text("")
responses = [
{"body": "/ci", "issue_url": "https://api.github.test/issues/42", "user": {"login": "actor"}},
{"state": "open", "draft": True, "user": {"login": "actor"}, "head": {"sha": "a" * 40, "ref": "fork-branch", "repo": {"full_name": "actor/greptimedb"}}},
{"permission": permission},
]
if permission != "read":
responses.append({"workflow_runs": [
{"id": 7, "path": ".github/workflows/rust.yml", "head_sha": "a" * 40, "head_branch": "fork-branch", "head_repository": {"full_name": "actor/greptimedb"}, "status": "completed", "conclusion": "skipped"},
{"id": 8, "path": ".github/workflows/checks.yml", "head_sha": "a" * 40, "head_branch": "fork-branch", "head_repository": {"full_name": "someone/greptimedb"}, "status": "completed", "conclusion": "skipped"},
]})
output = self.run_main(responses)
if permission == "read":
self.assertIn("skip=true", output)
else:
self.assertIn("run_ids=7\n", output)
self.assertNotIn("head_ref=", output)
def test_fork_rejects_non_draft_original_run(self):
output = self.run_main([
{"body": "/ci rust", "issue_url": "https://api.github.test/issues/42", "user": {"login": "writer"}},
{"state": "open", "draft": True, "head": {"sha": "a" * 40, "ref": "fork-branch", "repo": {"full_name": "actor/greptimedb"}}},
{"permission": "write"},
{"workflow_runs": [{"id": 7, "path": ".github/workflows/rust.yml@main", "head_sha": "a" * 40, "head_branch": "fork-branch", "head_repository": {"full_name": "actor/greptimedb"}, "status": "completed", "conclusion": "success", "run_attempt": 2}]},
{"conclusion": "success"},
])
self.assertIn("only originally skipped draft CI runs", output)
self.assertIn("skip=true", output)
def test_author_cannot_bypass_pr_guards(self):
for change, reason in [
({"state": "closed"}, "PR must be open and draft"),
({"draft": False}, "PR must be open and draft"),
({"head": {"sha": "b" * 40, "ref": "fix/draft-ci", "repo": {"full_name": "GreptimeTeam/greptimedb"}}}, "PR head changed"),
]:
with self.subTest(change=change):
Path(self.output.name).write_text("")
pr = {"state": "open", "draft": True, "user": {"login": "actor"}, "head": {"sha": "a" * 40, "ref": "fix/draft-ci", "repo": {"full_name": "GreptimeTeam/greptimedb"}}}
pr.update(change)
output = self.run_main([
{"body": "/ci rust", "issue_url": "https://api.github.test/repos/GreptimeTeam/greptimedb/issues/42", "user": {"login": "actor"}},
pr,
])
self.assertIn("skip=true", output)
self.assertIn(reason, output)
if __name__ == "__main__":
unittest.main()
+1
View File
@@ -5,6 +5,7 @@ name: Cargo.lock Diff Check
# posts/updates a PR comment when the threshold is exceeded.
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
paths:
- "Cargo.lock"
+1
View File
@@ -2,6 +2,7 @@ name: Check Git Dependencies on Main Branch
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
branches: [main]
paths:
- 'Cargo.toml'
+5 -5
View File
@@ -32,7 +32,7 @@ concurrency:
jobs:
check-typos-and-docs:
if: ${{ github.repository == 'GreptimeTeam/greptimedb' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }}
if: ${{ github.repository == 'GreptimeTeam/greptimedb' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false || github.run_attempt > 1) }}
name: Check typos and docs
runs-on: ubuntu-latest
steps:
@@ -47,7 +47,7 @@ jobs:
|| (echo "'config/config.md' is not up-to-date, please run 'make config-docs'." && exit 1)
license-header-check:
if: ${{ github.repository == 'GreptimeTeam/greptimedb' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }}
if: ${{ github.repository == 'GreptimeTeam/greptimedb' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false || github.run_attempt > 1) }}
runs-on: ubuntu-latest
name: Check License Header
steps:
@@ -63,7 +63,7 @@ jobs:
config: licenserc-enterprise.toml
github-script-tests:
if: ${{ github.repository == 'GreptimeTeam/greptimedb' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }}
if: ${{ github.repository == 'GreptimeTeam/greptimedb' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false || github.run_attempt > 1) }}
name: GitHub Script Tests
runs-on: ubuntu-latest
timeout-minutes: 5
@@ -100,7 +100,7 @@ jobs:
python3 tests/perf/test_agent_observability_summary.py
check:
if: ${{ github.repository == 'GreptimeTeam/greptimedb' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }}
if: ${{ github.repository == 'GreptimeTeam/greptimedb' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false || github.run_attempt > 1) }}
name: Check
runs-on: ${{ matrix.os }}
strategy:
@@ -129,7 +129,7 @@ jobs:
run: cargo check --locked --workspace --all-targets --all-features
toml:
if: ${{ github.repository == 'GreptimeTeam/greptimedb' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }}
if: ${{ github.repository == 'GreptimeTeam/greptimedb' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false || github.run_attempt > 1) }}
name: Toml Check
runs-on: ubuntu-latest
timeout-minutes: 60
+32 -9
View File
@@ -30,23 +30,46 @@ jobs:
DISPATCH_SENDER: ${{ github.event.sender.login }}
DISPATCH_HEAD_SHA: ${{ github.event.client_payload.pull_request.head.sha }}
run: python3 .github/scripts/ci-slash.py
- name: Reply with command result
if: ${{ steps.admit.outputs.reply != '' }}
env:
GH_TOKEN: ${{ github.token }}
REPLY: ${{ steps.admit.outputs.reply }}
PR_NUMBER: ${{ steps.admit.outputs.pr_number }}
run: printf '%s\n' "${REPLY}" | gh pr comment "${PR_NUMBER}" --body-file -
- name: Dispatch selected CI workflow
id: dispatch
if: ${{ steps.admit.outputs.skip == 'false' }}
env:
GH_TOKEN: ${{ github.token }}
RUN_IDS: ${{ steps.admit.outputs.run_ids }}
WORKFLOWS: ${{ steps.admit.outputs.workflow }}
HEAD_SHA: ${{ steps.admit.outputs.head_sha }}
HEAD_REF: ${{ steps.admit.outputs.head_ref }}
FUZZ_PROFILE: ${{ steps.admit.outputs.fuzz_profile }}
run: |
if [[ -n "${RUN_IDS}" ]]; then
IFS=, read -ra runs <<<"${RUN_IDS}"
for run in "${runs[@]}"; do
gh api --method POST "/repos/${GITHUB_REPOSITORY}/actions/runs/${run}/rerun"
done
exit 0
fi
IFS=, read -ra workflows <<<"${WORKFLOWS}"
for workflow in "${workflows[@]}"; do
inputs=(-F 'inputs[ci_command]=true')
if [[ "${workflow}" == 'integration.yml' ]]; then
inputs+=(-f "inputs[fuzz_profile]=${FUZZ_PROFILE}")
fi
gh api --method POST "/repos/${GITHUB_REPOSITORY}/actions/workflows/${workflow}/dispatches" \
-f ref="${HEAD_SHA}" -F 'inputs[ci_command]=true' -f "inputs[fuzz_profile]=${FUZZ_PROFILE}"
-f ref="${HEAD_REF}" "${inputs[@]}"
done
- name: Reply with command result
if: ${{ !cancelled() && github.event.sender.login == 'github-actions[bot]' }}
env:
GH_TOKEN: ${{ github.token }}
REPLY: ${{ steps.admit.outputs.reply }}
PR_NUMBER: ${{ steps.admit.outputs.pr_number || github.event.client_payload.github.payload.issue.number }}
ADMIT_OUTCOME: ${{ steps.admit.outcome }}
DISPATCH_OUTCOME: ${{ steps.dispatch.outcome }}
# Use the issue-comment endpoint with the existing issues: write permission.
run: |
[[ "${PR_NUMBER}" =~ ^[0-9]+$ ]] || exit 1
if [[ "${ADMIT_OUTCOME}" == failure || "${DISPATCH_OUTCOME}" == failure ]]; then
REPLY="CI trigger failed; some workflows may already have been requested. See ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID} for details."
fi
[[ -n "${REPLY}" ]] || exit 1
gh api --method POST "/repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" \
-f "body=${REPLY}"
+1
View File
@@ -2,6 +2,7 @@ name: Check Dependencies
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
branches:
- main
+3 -3
View File
@@ -38,7 +38,7 @@ name: Docs CI
jobs:
typos:
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false || github.run_attempt > 1 }}
name: Spell Check with Typos
runs-on: ubuntu-latest
steps:
@@ -48,7 +48,7 @@ jobs:
- uses: crate-ci/typos@v1.50.2
license-header-check:
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false || github.run_attempt > 1 }}
runs-on: ubuntu-latest
name: Check License Header
steps:
@@ -58,7 +58,7 @@ jobs:
- uses: korandoru/hawkeye@v5
required-checks:
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false || github.run_attempt > 1 }}
name: ${{ matrix.check }}
runs-on: ubuntu-slim
strategy:
+1
View File
@@ -2,6 +2,7 @@ name: Check Grafana Panels
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
branches:
- main
paths:
+5 -5
View File
@@ -43,7 +43,7 @@ concurrency:
jobs:
build:
if: ${{ github.repository == 'GreptimeTeam/greptimedb' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }}
if: ${{ github.repository == 'GreptimeTeam/greptimedb' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false || github.run_attempt > 1) }}
name: Build GreptimeDB binaries
runs-on: ${{ matrix.os }}
strategy:
@@ -104,7 +104,7 @@ jobs:
version: current
sqlness:
if: ${{ github.repository == 'GreptimeTeam/greptimedb' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }}
if: ${{ github.repository == 'GreptimeTeam/greptimedb' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false || github.run_attempt > 1) }}
name: Sqlness Test (${{ matrix.mode.name }})
needs: build
runs-on: ${{ matrix.os }}
@@ -184,7 +184,7 @@ jobs:
retention-days: 3
export-import-v2-e2e:
if: ${{ github.repository == 'GreptimeTeam/greptimedb' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }}
if: ${{ github.repository == 'GreptimeTeam/greptimedb' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false || github.run_attempt > 1) }}
name: Export/Import V2 E2E Test
needs: build
runs-on: ubuntu-latest
@@ -254,7 +254,7 @@ jobs:
retention-days: 3
run-multi-lang-tests:
if: ${{ github.repository == 'GreptimeTeam/greptimedb' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }}
if: ${{ github.repository == 'GreptimeTeam/greptimedb' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false || github.run_attempt > 1) }}
name: Run Multi-language SDK Tests
needs: build
uses: ./.github/workflows/run-multi-lang-tests.yml
@@ -262,7 +262,7 @@ jobs:
artifact-name: bins
compat-updater-check:
if: ${{ github.repository == 'GreptimeTeam/greptimedb' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) && (github.event_name == 'merge_group' || github.event_name == 'pull_request') }}
if: ${{ github.repository == 'GreptimeTeam/greptimedb' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false || github.run_attempt > 1) && (github.event_name == 'merge_group' || github.event_name == 'pull_request') }}
name: Check compat version updater
runs-on: ubuntu-latest
timeout-minutes: 10
+7 -7
View File
@@ -32,7 +32,7 @@ concurrency:
jobs:
fmt:
if: ${{ github.repository == 'GreptimeTeam/greptimedb' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }}
if: ${{ github.repository == 'GreptimeTeam/greptimedb' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false || github.run_attempt > 1) }}
name: Rustfmt
runs-on: ubuntu-latest
timeout-minutes: 60
@@ -50,7 +50,7 @@ jobs:
run: make fmt-check
clippy:
if: ${{ github.repository == 'GreptimeTeam/greptimedb' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }}
if: ${{ github.repository == 'GreptimeTeam/greptimedb' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false || github.run_attempt > 1) }}
name: Clippy
runs-on: ubuntu-latest
timeout-minutes: 60
@@ -76,7 +76,7 @@ jobs:
run: make clippy
check-udeps:
if: ${{ github.repository == 'GreptimeTeam/greptimedb' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }}
if: ${{ github.repository == 'GreptimeTeam/greptimedb' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false || github.run_attempt > 1) }}
name: Check Unused Dependencies
runs-on: ubuntu-latest
timeout-minutes: 60
@@ -94,7 +94,7 @@ jobs:
run: make check-udeps
check-riscv64:
if: ${{ github.repository == 'GreptimeTeam/greptimedb' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }}
if: ${{ github.repository == 'GreptimeTeam/greptimedb' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false || github.run_attempt > 1) }}
name: Check riscv64 build
runs-on: ubuntu-latest
timeout-minutes: 90
@@ -120,7 +120,7 @@ jobs:
run: cargo check --locked --workspace --all-targets --features servers/dashboard --target riscv64gc-unknown-linux-gnu
check-windows:
if: ${{ github.repository == 'GreptimeTeam/greptimedb' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }}
if: ${{ github.repository == 'GreptimeTeam/greptimedb' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false || github.run_attempt > 1) }}
name: Check Windows build
runs-on: windows-2022
timeout-minutes: 60
@@ -191,7 +191,7 @@ jobs:
run: cargo nextest run --locked --workspace -F dashboard --no-fail-fast
conflict-check:
if: ${{ github.repository == 'GreptimeTeam/greptimedb' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }}
if: ${{ github.repository == 'GreptimeTeam/greptimedb' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false || github.run_attempt > 1) }}
name: Check for conflict
runs-on: ubuntu-latest
steps:
@@ -202,7 +202,7 @@ jobs:
uses: olivernybroe/action-conflict-finder@v4.0
test:
if: ${{ github.repository == 'GreptimeTeam/greptimedb' && github.event_name != 'merge_group' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }}
if: ${{ github.repository == 'GreptimeTeam/greptimedb' && github.event_name != 'merge_group' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false || github.run_attempt > 1) }}
runs-on: ubuntu-22.04-arm
timeout-minutes: 60
needs: [conflict-check, clippy, fmt, check-udeps]
+17 -8
View File
@@ -1,13 +1,13 @@
name: Slash Command Dispatch
# ChatOps front door: parse `/command` on PR comments, check admin
# permission, and repository_dispatch to a per-command handler. Handlers
# ChatOps front door: parse `/command` on PR comments, apply command-specific
# permissions, and repository_dispatch to a per-command handler. Handlers
# own allowlists, SHA admission, and the actual work. Same-repo dispatch
# uses github.token with contents: write; GitHub starts the handler run
# for GITHUB_TOKEN-created repository_dispatch events. Do not pass the
# long-lived GH_PERSONAL_ACCESS_TOKEN into this third-party action.
#
# To add a command: list it under `commands` and add a workflow with
# To add a command: add it to `config` and add a workflow with
# `on.repository_dispatch.types: ["<command>-command"]`.
on:
@@ -30,8 +30,17 @@ jobs:
uses: peter-evans/slash-command-dispatch@9bdcd7914ec1b75590b790b844aa3b8eee7c683a # v5.0.2
with:
token: ${{ github.token }}
permission: admin
issue-type: pull-request
commands: |
query-regression
ci
# CI admission re-fetches the comment and checks author/member permissions.
config: >-
[
{"command": "query-regression", "permission": "admin", "issue_type": "pull-request"},
{"command": "ci", "permission": "none", "issue_type": "pull-request"}
]
- name: Report CI dispatch failure
if: ${{ failure() && (github.event.comment.body == '/ci' || startsWith(github.event.comment.body, '/ci ')) }}
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.issue.number }}
run: |
gh api --method POST "/repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" \
-f "body=CI command dispatch failed. See ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID} for details."