From 61cf62729a62c57da58236c2855e6099d99392b3 Mon Sep 17 00:00:00 2001 From: JohnBraham Date: Sat, 7 Nov 2026 22:54:39 +0000 Subject: [PATCH] Fix the Forge 1.21.1 archive audit trigger --- .github/scripts/test_release_workflow.py | 58 ++++++++++++++++++++-- .github/workflows/artifact-audit-1.21.yml | 60 +++++++++++++++++++---- .github/workflows/deploy-release.yml | 18 ++++--- 3 files changed, 116 insertions(+), 20 deletions(-) diff --git a/.github/scripts/test_release_workflow.py b/.github/scripts/test_release_workflow.py index 05fb4fe..f6617e2 100644 --- a/.github/scripts/test_release_workflow.py +++ b/.github/scripts/test_release_workflow.py @@ -59,7 +59,7 @@ def select(self, version, repository=REPOSITORY, ref='refs/heads/master-1.12'): values = dict(line.split('=', 1) for line in output.read_text().splitlines()) if output.exists() else {} return result.returncode, values - def validate(self, minecraft, names=None, mutation=None, release_exists=False, wrong_tag=False, audit_success=True, qualification=None, local_receipt=None): + def validate(self, minecraft, names=None, mutation=None, release_exists=False, wrong_tag=False, audit_success=True, qualification=None, local_receipt=None, audit_run=None): suffix, branch, forge = { '1.10.2': ('110021', '1.10.2', '12.18.3.2511'), '1.11.2': ('111021', '1.11.2-dev', '13.20.1.2588'), @@ -100,8 +100,13 @@ def command(args, **options): if qualification:row.update(qualification) return json.dumps({'workflow_runs':[row]}) if '/actions/workflows/' in args[-1]: - return json.dumps({'workflow_runs': [{'id': 1, 'display_title': 'Audit ' + minecraft + ' artifacts from ' + SHA, - 'conclusion': 'success' if audit_success else 'failure'}]}) + audit = {'id': 1, 'display_title': 'Audit ' + minecraft + ' artifacts from ' + SHA, + 'conclusion': 'success' if audit_success else 'failure', 'event': 'workflow_run', + 'head_sha': 'c' * 40, 'head_branch': 'master-1.12', + 'path': '.github/workflows/artifact-audit-1.21.yml'} + if audit_run is not None: + audit.update(audit_run) + return json.dumps({'workflow_runs': [audit]}) return json.dumps({'check_runs': [dict(name=name, id=i, conclusion='success') for i, name in enumerate(checks)]}) def existing(args, **options): if '/releases/tags/' in args[-1] and release_exists: @@ -112,7 +117,7 @@ def existing(args, **options): code = script('Validate identity, exact commit and successful checks').split("python3 - <<'PY'\n", 1)[1].rsplit('\nPY', 1)[0] with tempfile.TemporaryDirectory() as directory, patch.dict(os.environ, { 'RELEASE_VERSION': version, 'GITHUB_REPOSITORY': REPOSITORY, - 'MINECRAFT_VERSION': minecraft, 'SOURCE_BRANCH': branch, + 'MINECRAFT_VERSION': minecraft, 'SOURCE_BRANCH': branch, 'GITHUB_SHA': 'c' * 40, 'GITHUB_OUTPUT': str(Path(directory) / 'output'), 'LOCAL_QUALIFICATION':json.dumps(receipt) if receipt else ''}), \ patch('pathlib.Path.read_text', side_effect=['\n'.join(k+'='+v for k,v in values.items()),json.dumps({'files':files,'local_qualification_complete':True,'qualification_integrity_status':'verified','manual_accepted':True})]), \ @@ -143,6 +148,51 @@ def test_1211_requires_exact_archives_and_java_21(self): with self.subTest(options=options), self.assertRaises(SystemExit): self.validate('1.21.1', **options) + def test_1211_audit_receives_registered_archive_workflow_completion(self): + audit = WORKFLOW.with_name('artifact-audit-1.21.yml').read_text(encoding='utf-8') + event_names = audit.split('workflows: [', 1)[1].split(']', 1)[0].split(',') + self.assertIn('LootableBodies 1.20 CI', [name.strip() for name in event_names]) + + def test_1211_only_accepts_an_audit_from_the_active_dispatcher(self): + self.validate('1.21.1', audit_run={'event': 'workflow_dispatch'}) + for changes in ({'event': 'push'}, {'head_branch': 'master-1.21.1'}, + {'head_sha': 'd' * 40}, {'path': '.github/workflows/artifact-audit-1.20.yml'}): + with self.subTest(changes=changes), self.assertRaises(SystemExit): + self.validate('1.21.1', audit_run=changes) + + def test_1211_audit_requires_the_successful_current_source_push(self): + path = WORKFLOW.with_name('artifact-audit-1.21.yml') + text = path.read_text(encoding='utf-8') + code = text.split("python3 - <<'PY'\n", 1)[1].split('\n PY', 1)[0] + code = textwrap.dedent(code) + good = {'id': 25, 'head_repository': {'full_name': REPOSITORY}, 'event': 'push', + 'head_branch': 'master-1.21.1', 'head_sha': SHA, 'path': '.github/workflows/native120.yml', + 'status': 'completed', 'conclusion': 'success'} + + def resolve(run, branch=SHA, run_id='25', source=SHA): + def api(args): + if args[-1].endswith('/actions/runs/25'): + return json.dumps(run) + if args[-1].endswith('/git/ref/heads/master-1.21.1'): + return json.dumps({'object': {'sha': branch}}) + raise AssertionError(args) + with tempfile.TemporaryDirectory() as folder, patch('subprocess.check_output', side_effect=api): + output = Path(folder) / 'output' + with patch.dict(os.environ, SOURCE_RUN=run_id, SOURCE_SHA=source, GITHUB_OUTPUT=str(output)): + exec(compile(code, 'audit-source', 'exec'), {}) + return output.read_text() + + self.assertEqual('run=25\nsha=' + SHA + '\n', resolve(good)) + for changes in ({'event': 'pull_request'}, {'head_sha': 'b' * 40}, {'head_branch': 'master-1.20.6'}, + {'head_repository': {'full_name': 'SkyBlade1978/LootableBodies'}}, + {'path': '.github/workflows/native119.yml'}, {'conclusion': 'failure'}, {'id': 26}): + with self.subTest(changes=changes), self.assertRaises(SystemExit): + resolve(dict(good, **changes)) + with self.assertRaises(SystemExit): + resolve(good, branch='b' * 40) + with self.assertRaises(SystemExit): + resolve(good, run_id='25;false') + def test_future_12111_candidate_cannot_dispatch_publication(self): self.assertNotEqual(0, self.select('2.4.14.121111')[0]) diff --git a/.github/workflows/artifact-audit-1.21.yml b/.github/workflows/artifact-audit-1.21.yml index 6835f5c..b8050ff 100644 --- a/.github/workflows/artifact-audit-1.21.yml +++ b/.github/workflows/artifact-audit-1.21.yml @@ -1,9 +1,19 @@ name: Audit LootableBodies 1.21 artifacts -run-name: Audit 1.21.1 artifacts from ${{ github.event.workflow_run.head_sha }} +run-name: Audit 1.21.1 artifacts from ${{ github.event.workflow_run.head_sha || inputs.source_sha }} on: workflow_run: - workflows: [LootableBodies 1.21.1 CI] + workflows: [LootableBodies 1.20 CI, LootableBodies 1.20.6 CI, LootableBodies 1.21.1 CI] types: [completed] + workflow_dispatch: + inputs: + source_run: + description: Successful archive build run ID + required: true + type: string + source_sha: + description: Exact merged 1.21.1 source SHA + required: true + type: string permissions: contents: read actions: read @@ -11,23 +21,55 @@ jobs: audit: if: >- github.repository == 'MinecraftModDevelopmentMods/LootableBodies' && - github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push' && - github.event.workflow_run.head_branch == 'master-1.21.1' && github.event.workflow_run.name == 'LootableBodies 1.21.1 CI' + github.ref == 'refs/heads/master-1.12' && + (github.event_name == 'workflow_dispatch' || + (github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push' && + github.event.workflow_run.head_branch == 'master-1.21.1' && github.event.workflow_run.name == 'LootableBodies 1.21.1 CI')) runs-on: ubuntu-latest timeout-minutes: 30 steps: + - name: Verify the exact merged archive build + id: source + env: + GH_TOKEN: ${{ github.token }} + SOURCE_RUN: ${{ github.event.workflow_run.id || inputs.source_run }} + SOURCE_SHA: ${{ github.event.workflow_run.head_sha || inputs.source_sha }} + run: | + python3 - <<'PY' + import json, os, re, subprocess + + repo = 'MinecraftModDevelopmentMods/LootableBodies' + run_id = os.environ['SOURCE_RUN'] + source = os.environ['SOURCE_SHA'] + if not re.fullmatch(r'[1-9][0-9]*', run_id) or not re.fullmatch(r'[a-f0-9]{40}', source): + raise SystemExit('An exact build run ID and source SHA are required') + + def api(path): + return json.loads(subprocess.check_output(['gh', 'api', f'repos/{repo}/{path}'])) + + run = api(f'actions/runs/{run_id}') + branch = api('git/ref/heads/master-1.21.1') + if (str(run['id']) != run_id or run['head_repository']['full_name'] != repo or + run['event'] != 'push' or run['head_branch'] != 'master-1.21.1' or run['head_sha'] != source or + run['path'] != '.github/workflows/native120.yml' or run['status'] != 'completed' or + run['conclusion'] != 'success' or branch['object']['sha'] != source): + raise SystemExit('The selected run is not the successful build of the current merged 1.21.1 source') + with open(os.environ['GITHUB_OUTPUT'], 'a') as out: + out.write(f'run={run_id}\nsha={source}\n') + PY - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: - ref: ${{ github.event.workflow_run.head_sha }} + ref: ${{ steps.source.outputs.sha }} + persist-credentials: false - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: - name: LootableBodies-1.21.1-${{ github.event.workflow_run.head_sha }} - run-id: ${{ github.event.workflow_run.id }} + name: LootableBodies-1.21.1-${{ steps.source.outputs.sha }} + run-id: ${{ steps.source.outputs.run }} github-token: ${{ github.token }} path: audited - name: Independently verify every locally qualified byte env: - SOURCE_SHA: ${{ github.event.workflow_run.head_sha }} + SOURCE_SHA: ${{ steps.source.outputs.sha }} run: | python3 -B tools/native120_build.py --bundle audited python3 - <<'PY' @@ -43,6 +85,6 @@ jobs: if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: - name: native120-independent-audit-${{ github.event.workflow_run.head_sha }} + name: native120-independent-audit-${{ steps.source.outputs.sha }} if-no-files-found: error path: independent-audit.json diff --git a/.github/workflows/deploy-release.yml b/.github/workflows/deploy-release.yml index f6317e6..364bf71 100644 --- a/.github/workflows/deploy-release.yml +++ b/.github/workflows/deploy-release.yml @@ -181,9 +181,13 @@ jobs: raise SystemExit(f'The selected commit has no successful {name} check') if minecraft in ('1.11.2', '1.12.2', '1.13.2', '1.14.4', '1.15.2', '1.16.5', '1.17.1', '1.18.2', '1.19.4', '1.20.1', '1.20.6', '1.21.1'): target = minecraft.rsplit('.', 1)[0] + event_query = '' if minecraft == '1.21.1' else '&event=workflow_run' audits = json.loads(subprocess.check_output(['gh', 'api', - f'repos/{repo}/actions/workflows/artifact-audit-{target}.yml/runs?event=workflow_run&per_page=100'], text=True))['workflow_runs'] - matching = [run for run in audits if run['display_title'] == f'Audit {minecraft} artifacts from {sha}'] + f'repos/{repo}/actions/workflows/artifact-audit-{target}.yml/runs?per_page=100{event_query}'], text=True))['workflow_runs'] + matching = [run for run in audits if run['display_title'] == f'Audit {minecraft} artifacts from {sha}' + and (minecraft != '1.21.1' or (run['event'] in ('workflow_run', 'workflow_dispatch') + and run['head_branch'] == 'master-1.12' and run['head_sha'] == os.environ['GITHUB_SHA'] + and run['path'] == '.github/workflows/artifact-audit-1.21.yml'))] if not matching or max(matching, key=lambda run: run['id'])['conclusion'] != 'success': raise SystemExit('The selected commit has no successful independent artifact audit') release = subprocess.run(['gh', 'api', f'repos/{repo}/releases/tags/{version}'], capture_output=True, text=True) @@ -213,25 +217,25 @@ jobs: with: ref: ${{ needs.preflight.outputs.release_sha }} - name: Install Mavenizer Java - if: needs.preflight.outputs.minecraft_version != '1.18.2' && needs.preflight.outputs.minecraft_version != '1.19.4' && needs.preflight.outputs.minecraft_version != '1.20.1' && needs.preflight.outputs.minecraft_version != '1.20.6' && needs.preflight.outputs.minecraft_version != '1.21.1' && needs.preflight.outputs.minecraft_version != '1.21.1' && needs.preflight.outputs.minecraft_version != '1.21.1' && needs.preflight.outputs.minecraft_version != '1.21.1' && needs.preflight.outputs.minecraft_version != '1.21.1' + if: needs.preflight.outputs.minecraft_version != '1.18.2' && needs.preflight.outputs.minecraft_version != '1.19.4' && needs.preflight.outputs.minecraft_version != '1.20.1' && needs.preflight.outputs.minecraft_version != '1.20.6' && needs.preflight.outputs.minecraft_version != '1.21.1' uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 with: distribution: temurin java-version: '25.0.3+9.0.LTS' - name: Install production Java - if: needs.preflight.outputs.minecraft_version != '1.18.2' && needs.preflight.outputs.minecraft_version != '1.19.4' && needs.preflight.outputs.minecraft_version != '1.20.1' && needs.preflight.outputs.minecraft_version != '1.20.6' && needs.preflight.outputs.minecraft_version != '1.21.1' && needs.preflight.outputs.minecraft_version != '1.21.1' && needs.preflight.outputs.minecraft_version != '1.21.1' && needs.preflight.outputs.minecraft_version != '1.21.1' && needs.preflight.outputs.minecraft_version != '1.21.1' + if: needs.preflight.outputs.minecraft_version != '1.18.2' && needs.preflight.outputs.minecraft_version != '1.19.4' && needs.preflight.outputs.minecraft_version != '1.20.1' && needs.preflight.outputs.minecraft_version != '1.20.6' && needs.preflight.outputs.minecraft_version != '1.21.1' uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 with: distribution: temurin java-version: ${{ needs.preflight.outputs.minecraft_version == '1.18.2' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.19.4' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.20.1' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.17.1' && '16.0.2+7' || '8.0.502+7' }} - name: Install Gradle Java - if: needs.preflight.outputs.minecraft_version != '1.18.2' && needs.preflight.outputs.minecraft_version != '1.19.4' && needs.preflight.outputs.minecraft_version != '1.20.1' && needs.preflight.outputs.minecraft_version != '1.20.6' && needs.preflight.outputs.minecraft_version != '1.21.1' && needs.preflight.outputs.minecraft_version != '1.21.1' && needs.preflight.outputs.minecraft_version != '1.21.1' && needs.preflight.outputs.minecraft_version != '1.21.1' && needs.preflight.outputs.minecraft_version != '1.21.1' + if: needs.preflight.outputs.minecraft_version != '1.18.2' && needs.preflight.outputs.minecraft_version != '1.19.4' && needs.preflight.outputs.minecraft_version != '1.20.1' && needs.preflight.outputs.minecraft_version != '1.20.6' && needs.preflight.outputs.minecraft_version != '1.21.1' uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 with: distribution: temurin java-version: ${{ needs.preflight.outputs.minecraft_version == '1.18.2' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.19.4' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.20.1' && '17.0.20+101' || '17.0.1+12' }} - name: Pin the production toolchain - if: needs.preflight.outputs.minecraft_version != '1.18.2' && needs.preflight.outputs.minecraft_version != '1.19.4' && needs.preflight.outputs.minecraft_version != '1.20.1' && needs.preflight.outputs.minecraft_version != '1.20.6' && needs.preflight.outputs.minecraft_version != '1.21.1' && needs.preflight.outputs.minecraft_version != '1.21.1' && needs.preflight.outputs.minecraft_version != '1.21.1' && needs.preflight.outputs.minecraft_version != '1.21.1' && needs.preflight.outputs.minecraft_version != '1.21.1' + if: needs.preflight.outputs.minecraft_version != '1.18.2' && needs.preflight.outputs.minecraft_version != '1.19.4' && needs.preflight.outputs.minecraft_version != '1.20.1' && needs.preflight.outputs.minecraft_version != '1.20.6' && needs.preflight.outputs.minecraft_version != '1.21.1' run: | if [[ '${{ needs.preflight.outputs.minecraft_version }}' == '1.18.2' || '${{ needs.preflight.outputs.minecraft_version }}' == '1.19.4' || '${{ needs.preflight.outputs.minecraft_version }}' == '1.20.1' ]]; then echo "LOOTABLEBODIES_JAVA17_HOME=$JAVA_HOME_17_X64" >> "$GITHUB_ENV" @@ -242,7 +246,7 @@ jobs: fi echo "LOOTABLEBODIES_MAVENIZER_JAVA_HOME=$JAVA_HOME_25_X64" >> "$GITHUB_ENV" - name: Set up Gradle - if: needs.preflight.outputs.minecraft_version != '1.18.2' && needs.preflight.outputs.minecraft_version != '1.19.4' && needs.preflight.outputs.minecraft_version != '1.20.1' && needs.preflight.outputs.minecraft_version != '1.20.6' && needs.preflight.outputs.minecraft_version != '1.21.1' && needs.preflight.outputs.minecraft_version != '1.21.1' && needs.preflight.outputs.minecraft_version != '1.21.1' && needs.preflight.outputs.minecraft_version != '1.21.1' && needs.preflight.outputs.minecraft_version != '1.21.1' + if: needs.preflight.outputs.minecraft_version != '1.18.2' && needs.preflight.outputs.minecraft_version != '1.19.4' && needs.preflight.outputs.minecraft_version != '1.20.1' && needs.preflight.outputs.minecraft_version != '1.20.6' && needs.preflight.outputs.minecraft_version != '1.21.1' uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6 - name: Download the qualified native117 six-file bundle if: needs.preflight.outputs.minecraft_version == '1.17.1'