name: validate-milestone permissions: contents: read pull-requests: read on: pull_request: types: [opened, synchronize, milestoned, demilestoned, edited] jobs: validate-milestone: runs-on: ubuntu-24.04 timeout-minutes: 5 steps: - name: Validate milestone matches docker next version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: MILESTONE: ${{ github.event.pull_request.milestone.title }} with: script: | const files = await github.paginate(github.rest.pulls.listFiles, { owner: context.repo.owner, repo: context.repo.repo, pull_number: context.payload.pull_request.number, }); core.info(`Modified files: ${files.map(f => f.filename).join(', ')}`); const touchesVersions = files.some(f => f.filename === 'releases/versions.yaml'); core.info(`Touches version: ${touchesVersions}`); // Use the PR's version when it bumps the file, base branch otherwise. // It's fine to trust the author in this case, it's not meant to be // a security gate, just a helpful check for maintainers. const ref = touchesVersions ? context.payload.pull_request.head.sha : context.payload.pull_request.base.ref; core.info(`Base ref: ${ref}`); const resp = await github.rest.repos.getContent({ owner: context.repo.owner, repo: context.repo.repo, path: 'releases/versions.yaml', ref, }); const content = Buffer.from(resp.data.content, resp.data.encoding).toString('utf8'); const line = content.split('\n').find(l => l.includes('next:')); if (!line) { core.setFailed('Could not find docker.next in releases/versions.yaml'); return; } const expected = line.trim().replace('next: ', '').replaceAll('"', ''); const milestone = process.env.MILESTONE; if (!milestone) { core.setFailed(`PR must have a milestone set (expected: ${expected})`); return; } if (milestone !== expected) { core.setFailed(`Milestone '${milestone}' does not match docker next version '${expected}'`); return; } core.info(`Milestone: ${milestone} ✓`);