diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 0a24199..50c726d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,7 +1,19 @@ version: 2 updates: - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" - open-pull-requests-limit: 5 +- package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + open-pull-requests-limit: 5 + groups: + actions-patch-minor: + patterns: + - '*' + update-types: + - minor + - patch + actions-major: + patterns: + - '*' + update-types: + - major diff --git a/.github/scripts/merge_action_updates.py b/.github/scripts/merge_action_updates.py new file mode 100644 index 0000000..019f080 --- /dev/null +++ b/.github/scripts/merge_action_updates.py @@ -0,0 +1,66 @@ +"""Merge Dependabot Action patch/minor updates after all head checks pass.""" +import json +import os +import re +import time +import urllib.request + +repository = os.environ['GITHUB_REPOSITORY'] +event = json.load(open(os.environ['GITHUB_EVENT_PATH'])) + +def call(path, method='GET', body=None): + req = urllib.request.Request('https://api.github.com/repos/' + repository + path, method=method, + data=None if body is None else json.dumps(body).encode(), + headers={'Authorization': 'Bearer ' + os.environ['GH_TOKEN'], 'Accept': 'application/vnd.github+json', 'Content-Type': 'application/json'}) + with urllib.request.urlopen(req, timeout=40) as response: + value = response.read() + return json.loads(value) if value else None + +def versioned_changes(files): + count = 0 + for file in files: + path = file['filename'] + if not path.startswith('.github/') or not path.endswith(('.yml', '.yaml')) or 'patch' not in file: + return False + old, new = {}, {} + for line in file['patch'].splitlines(): + if not line.startswith(('-', '+')) or line.startswith(('---', '+++')): + continue + match = re.match(r'[+-]\s*(?:-\s*)?uses:\s*([^\s@]+)@[0-9a-f]{40}\s+#\s*v?(\d+)(?:\.(\d+))?(?:\.(\d+))?\s*$', line) + if not match: + # Updating any workflow behavior or an unversioned action + # remains a reviewed change. + return False + (old if line[0] == '-' else new)[match[1]] = tuple(int(x or 0) for x in match.groups()[1:]) + if set(old) != set(new): + return False + for action in old: + if old[action][0] != new[action][0] or new[action] < old[action]: + return False + count += 1 + return count > 0 + +run = event['workflow_run'] +if run['conclusion'] == 'success' and run['event'] == 'pull_request': + for reference in run.get('pull_requests', []): + number = reference['number'] + pr = call('/pulls/' + str(number)) + if pr['state'] != 'open' or pr['user']['login'] != 'dependabot[bot]' or pr['head']['repo']['full_name'] != repository: + continue + if pr['head']['sha'] != run['head_sha'] or pr.get('draft'): + continue + files = call('/pulls/' + str(number) + '/files?per_page=100') + if pr['changed_files'] > 100 or not versioned_changes(files): + continue + sha = pr['head']['sha'] + for attempt in range(90): + checks = call('/commits/' + sha + '/check-runs?per_page=100')['check_runs'] + statuses = call('/commits/' + sha + '/status')['statuses'] + if any(x['status'] == 'completed' and x['conclusion'] not in ('success', 'neutral', 'skipped') for x in checks) or any(x['state'] in ('failure', 'error') for x in statuses): + break + if checks and all(x['status'] == 'completed' for x in checks) and all(x['state'] == 'success' for x in statuses): + latest = call('/pulls/' + str(number)) + if latest['head']['sha'] == sha and latest['state'] == 'open': + call('/pulls/' + str(number) + '/merge', 'PUT', {'sha': sha, 'merge_method': 'squash'}) + break + time.sleep(20) diff --git a/.github/workflows/action-policy.yml b/.github/workflows/action-policy.yml new file mode 100644 index 0000000..9f69281 --- /dev/null +++ b/.github/workflows/action-policy.yml @@ -0,0 +1,28 @@ +name: Action policy +'on': + pull_request: + paths: + - .github/** + workflow_dispatch: {} +permissions: + contents: read +jobs: + validate: + name: Validate Actions + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 + with: + go-version: '1.26' + cache: false + - name: Validate workflows + run: | + go install github.com/rhysd/actionlint/cmd/actionlint@v1.7.12 + "$(go env GOPATH)/bin/actionlint" -shellcheck= -pyflakes= -ignore 'label .* is unknown' + - name: Verify private output isolation + run: | + if [[ -d .github/tests ]]; then python3 -m unittest discover -s .github/tests -p "test_*.py"; fi diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 09b937a..42f0442 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,12 +17,12 @@ jobs: runs-on: ubuntu-latest steps: - name: 检出代码 - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: token: ${{ secrets.GITHUB_TOKEN }} - name: 设置 Go - uses: actions/setup-go@v7 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: go.mod diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3f383a9..77e9a34 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -18,8 +18,8 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 - - uses: actions/setup-go@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: go.mod check-latest: true diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 8238564..96c42ef 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -19,7 +19,7 @@ jobs: outputs: tag: ${{ steps.release.outputs.tag }} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ github.event_name == 'workflow_dispatch' && inputs.release_tag || github.ref }} fetch-depth: 0 @@ -40,7 +40,7 @@ jobs: version="$(sed -n 's/^const BackTraceVersion = "\(v[0-9.]*\)"$/\1/p' model/model.go)" [[ "$version" == "$tag" ]] echo "tag=$tag" >> "$GITHUB_OUTPUT" - - uses: actions/setup-go@v7 + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: go.mod check-latest: true @@ -100,10 +100,10 @@ jobs: - {goos: windows, goarch: amd64} - {goos: windows, goarch: arm64} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ github.event_name == 'workflow_dispatch' && inputs.release_tag || github.ref }} - - uses: actions/setup-go@v7 + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: go.mod check-latest: true diff --git a/.github/workflows/merge-action-updates.yml b/.github/workflows/merge-action-updates.yml new file mode 100644 index 0000000..42c93d5 --- /dev/null +++ b/.github/workflows/merge-action-updates.yml @@ -0,0 +1,23 @@ +name: Merge safe Action updates +'on': + workflow_run: + workflows: + - Action policy + types: + - completed +permissions: + contents: write + pull-requests: write +jobs: + merge: + if: github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request' + runs-on: ubuntu-24.04 + timeout-minutes: 35 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Merge validated patch and minor updates + env: + GH_TOKEN: ${{ github.token }} + run: python3 .github/scripts/merge_action_updates.py diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 70d7a04..59c38fd 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -19,12 +19,12 @@ jobs: prepare: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: main fetch-depth: 0 fetch-tags: true - - uses: actions/setup-go@v7 + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: go.mod check-latest: true diff --git a/.github/workflows/sync-asn-metadata.yml b/.github/workflows/sync-asn-metadata.yml index 30582e6..007daff 100644 --- a/.github/workflows/sync-asn-metadata.yml +++ b/.github/workflows/sync-asn-metadata.yml @@ -28,8 +28,8 @@ jobs: sync: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 - - uses: actions/setup-go@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: go.mod cache: true