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
22 changes: 22 additions & 0 deletions .github/scripts/test_release_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,28 @@ def test_existing_release_is_not_uploaded_again(self):
with self.assertRaises(SystemExit):
self.validate('1.10.2', release_exists=True)

def test_112_artifact_audit_skips_only_explicitly_scoped_out_builds(self):
text=WORKFLOW.with_name('artifact-audit-1.12.yml').read_text()
code=text.split('def artifact_required(jobs):',1)[1].split('\n # Fetch',1)[0]
code='def artifact_required(jobs):'+code
import textwrap
code=code.replace('\n ','\n')
namespace={};exec(compile(code,'artifact-scope','exec'),namespace)
check=namespace['artifact_required']
def job(name,conclusion,status='completed'):return dict(name=name,conclusion=conclusion,status=status)
build=job('Build, test, and audit','success')
self.assertTrue(check([build]))
skipped=[job('Check runtime inputs','success'),job('Build, test, and audit','skipped'),job('Cold Forge bootstrap','skipped')]
self.assertFalse(check(skipped))
for invalid in [[],[build,build],skipped[1:],skipped[:2],
[dict(build,status='in_progress')],
[dict(build,conclusion='failure')],
[dict(skipped[0],conclusion='failure'),*skipped[1:]],
[*skipped[:2],dict(skipped[2],conclusion='failure')]]:
with self.subTest(invalid=invalid),self.assertRaises(ValueError):check(invalid)
self.assertIn("needs: scope",text)
self.assertIn("if: needs.scope.outputs.required == 'true'",text)

def test_tag_at_another_commit_is_rejected(self):
with self.assertRaises(SystemExit):
self.validate('1.10.2', wrong_tag=True)
Expand Down
41 changes: 38 additions & 3 deletions .github/workflows/artifact-audit-1.12.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,48 @@ permissions:
actions: read

jobs:
audit:
# Pull-request merge trees are audited inside CI. Push artifacts use the
# workflow run's exact head SHA, which is also their declared artifact name.
scope:
name: Check artifact producer scope
if: >-
github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push' &&
(github.event.workflow_run.head_branch == 'master-1.12.2' || github.event.workflow_run.head_branch == 'master-1.12')
runs-on: ubuntu-latest
outputs:
required: ${{ steps.scope.outputs.required }}
steps:
- id: scope
env:
GH_TOKEN: ${{ github.token }}
PRODUCER_URL: ${{ github.event.workflow_run.jobs_url }}
REPOSITORY: ${{ github.repository }}
run: |
python3 - <<'PY'
import json,os,urllib.request
def artifact_required(jobs):
def exactly(name):
found=[j for j in jobs if j['name']==name]
if len(found)!=1 or found[0]['status']!='completed':raise ValueError('Missing, duplicate or pending producer job: '+name)
return found[0]['conclusion']
build=exactly('Build, test, and audit')
if build=='success':return True
if build=='skipped' and exactly('Check runtime inputs')=='success' and exactly('Cold Forge bootstrap')=='skipped':return False
raise ValueError('Artifact producer did not succeed')
# Fetch the exact producer jobs; absent artifacts never imply a skip.
url=os.environ['PRODUCER_URL']+'?per_page=100'
if not url.startswith('https://api.github.com/repos/'+os.environ['REPOSITORY']+'/actions/runs/'):raise ValueError('Unexpected producer API URL')
request=urllib.request.Request(url,headers={'Authorization':'Bearer '+os.environ['GH_TOKEN'],'Accept':'application/vnd.github+json'})
with urllib.request.urlopen(request,timeout=30) as response:result=json.load(response)
if result['total_count']!=len(result['jobs']):raise ValueError('Incomplete producer job list')
required=artifact_required(result['jobs'])
with open(os.environ['GITHUB_OUTPUT'],'a') as output:output.write('required='+str(required).lower()+'\n')
print('Artifact audit required:',required)
PY
audit:
# Successful builds always require their exact artifact. A dispatcher-only
# change has an explicitly skipped producer and has no release bytes to audit.
needs: scope
if: needs.scope.outputs.required == 'true'
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
Expand Down
Loading