Run GitHub Actions Workflow Generator #29
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Run GitHub Actions Workflow Generator | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| generator-version: | |
| description: 'Version of github-actions-workflow-generator to use (e.g. 0.0.5). Defaults to latest release.' | |
| required: false | |
| type: string | |
| default: '' | |
| sha: | |
| description: 'Commit SHA of this repo to copy release-train action files from. Defaults to the commit that triggered this workflow.' | |
| required: false | |
| type: string | |
| default: '' | |
| spring-release: | |
| description: 'Spring release train version (e.g. 2026.1). When set, also processes release/[version] branches found in the spring-io/release-train README.adoc for that version.' | |
| required: false | |
| type: string | |
| default: '' | |
| projects: | |
| description: 'Comma-separated list of Spring Cloud project names to run against (e.g. spring-cloud-build,spring-cloud-config). When empty, all projects in projects.json are processed.' | |
| required: false | |
| type: string | |
| default: '' | |
| token: | |
| description: 'GitHub token with write access to all target repos. Falls back to GH_ACTIONS_REPO_TOKEN.' | |
| required: false | |
| type: string | |
| default: '' | |
| permissions: | |
| contents: read | |
| jobs: | |
| setup: | |
| name: Build Matrix | |
| runs-on: ubuntu-latest | |
| outputs: | |
| matrix: ${{ steps.build-matrix.outputs.matrix }} | |
| generator-version: ${{ steps.find-generator.outputs.version }} | |
| generator-url: ${{ steps.find-generator.outputs.url }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Find generator version and download URL | |
| id: find-generator | |
| env: | |
| GH_TOKEN: ${{ inputs.token || secrets.GH_ACTIONS_REPO_TOKEN }} | |
| run: | | |
| if [[ -n "${{ inputs.generator-version }}" ]]; then | |
| version="${{ inputs.generator-version }}" | |
| tag="v${version}" | |
| else | |
| response=$(gh api repos/spring-io/github-actions-workflow-generator/releases/latest) | |
| tag=$(echo "$response" | jq -r '.tag_name') | |
| version="${tag#v}" | |
| fi | |
| url="https://github.com/spring-io/github-actions-workflow-generator/releases/download/${tag}/github-actions-workflow-generator-${version}.jar" | |
| echo "version=${version}" >> "$GITHUB_OUTPUT" | |
| echo "url=${url}" >> "$GITHUB_OUTPUT" | |
| echo "Generator version : ${version}" | |
| echo "Download URL : ${url}" | |
| - name: Build matrix | |
| id: build-matrix | |
| env: | |
| GH_TOKEN: ${{ inputs.token || secrets.GH_ACTIONS_REPO_TOKEN }} | |
| SPRING_RELEASE: ${{ inputs.spring-release }} | |
| PROJECTS_FILTER: ${{ inputs.projects }} | |
| run: | | |
| node - << 'JSEOF' | |
| const { execFileSync } = require('child_process'); | |
| const fs = require('fs'); | |
| // Fetch projects.json from the main branch | |
| const b64 = execFileSync('gh', [ | |
| 'api', 'repos/spring-cloud/spring-cloud-github-actions/contents/config/projects.json', | |
| '-X', 'GET', '-f', 'ref=main', '--jq', '.content' | |
| ], { encoding: 'utf8' }).trim(); | |
| const projects = JSON.parse(Buffer.from(b64, 'base64').toString('utf8')); | |
| const defaults = projects.defaults || {}; | |
| const projectsFilterRaw = (process.env.PROJECTS_FILTER || '').trim(); | |
| const projectsFilter = projectsFilterRaw | |
| ? new Set(projectsFilterRaw.split(',').map(p => p.trim()).filter(Boolean)) | |
| : new Set(); | |
| function getJdks(projectKey, typeKey, branch) { | |
| const typeCfg = (projects[projectKey] || {})[typeKey] || {}; | |
| const jdkmap = typeCfg.jdkVersions || {}; | |
| if (jdkmap[branch]) return jdkmap[branch]; | |
| if (jdkmap['default']) return jdkmap['default']; | |
| const defJdkmap = (defaults[typeKey] || {}).jdkVersions || {}; | |
| if (defJdkmap[branch]) return defJdkmap[branch]; | |
| return defJdkmap['default'] || ['17', '21', '25']; | |
| } | |
| function primaryJdk(jdks) { | |
| return jdks.includes('8') ? '8' : '17'; | |
| } | |
| const entries = []; | |
| const seen = new Set(); | |
| function addEntry(repo, branch, typeKey, projectKey, jdkLookupBranch) { | |
| const key = `${repo}@${branch}`; | |
| if (seen.has(key)) return; | |
| seen.add(key); | |
| const jdks = getJdks(projectKey, typeKey, jdkLookupBranch || branch); | |
| entries.push({ repo, branch, primary_jdk: primaryJdk(jdks) }); | |
| } | |
| // Build entries from projects.json | |
| for (const [projectKey, config] of Object.entries(projects)) { | |
| if (projectKey === 'defaults') continue; | |
| if (projectsFilter.size > 0 && !projectsFilter.has(projectKey)) continue; | |
| for (const typeKey of ['oss', 'commercial']) { | |
| const typeCfg = config[typeKey]; | |
| if (!typeCfg) continue; | |
| const repo = typeKey === 'commercial' | |
| ? `spring-cloud/${projectKey}-commercial` | |
| : `spring-cloud/${projectKey}`; | |
| for (const branch of (typeCfg.branches || {}).scheduled || []) { | |
| addEntry(repo, branch, typeKey, projectKey); | |
| } | |
| } | |
| } | |
| // Optionally add release/[version] branches from the Spring release train README | |
| const springRelease = (process.env.SPRING_RELEASE || '').trim(); | |
| if (springRelease) { | |
| let readmeB64; | |
| try { | |
| readmeB64 = execFileSync('gh', [ | |
| 'api', 'repos/spring-io/release-train/contents/README.adoc', | |
| '-X', 'GET', '-f', `ref=${springRelease}`, '--jq', '.content' | |
| ], { encoding: 'utf8' }).trim(); | |
| } catch (err) { | |
| console.error(`Warning: could not fetch README.adoc for spring release ${springRelease}: ${err.message}`); | |
| } | |
| if (readmeB64) { | |
| const content = Buffer.from(readmeB64, 'base64').toString('utf8'); | |
| let currentRepo = null; | |
| let currentProjectKey = null; | |
| for (const line of content.split('\n')) { | |
| if (/^== /.test(line)) { | |
| if (!line.includes('Spring Cloud')) { | |
| currentRepo = null; | |
| currentProjectKey = null; | |
| } | |
| continue; | |
| } | |
| const releasingMatch = line.match( | |
| /\*\*Releasing from:\*\* https:\/\/github\.com\/spring-cloud\/([^\[]+)\[/ | |
| ); | |
| if (releasingMatch) { | |
| const repoName = releasingMatch[1].trim().replace(/\/$/, ''); | |
| currentRepo = `spring-cloud/${repoName}`; | |
| currentProjectKey = repoName.replace(/-commercial$/, ''); | |
| continue; | |
| } | |
| if (currentRepo) { | |
| const versionMatch = line.match(/=== .+ (\d+\.\d+(?:\.\d+(?:\.\d+)?)?)\s*$/); | |
| if (versionMatch) { | |
| const version = versionMatch[1]; | |
| const parts = version.split('.'); | |
| const parentBranch = parts.slice(0, -1).join('.') + '.x'; | |
| if (projectsFilter.size > 0 && !projectsFilter.has(currentProjectKey)) continue; | |
| addEntry(currentRepo, `release/${version}`, 'commercial', currentProjectKey, parentBranch); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| const matrix = { include: entries }; | |
| fs.appendFileSync(process.env.GITHUB_OUTPUT, `matrix=${JSON.stringify(matrix)}\n`); | |
| console.log(`Matrix built: ${entries.length} entries`); | |
| JSEOF | |
| generate: | |
| name: "Generate — ${{ matrix.repo }}@${{ matrix.branch }}" | |
| needs: setup | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJson(needs.setup.outputs.matrix) }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ inputs.sha || github.sha }} | |
| token: ${{ inputs.token || secrets.GH_ACTIONS_REPO_TOKEN }} | |
| - name: Generate workflows | |
| id: generate | |
| uses: ./.github/actions/generate-workflows-for-branch | |
| with: | |
| repo: ${{ matrix.repo }} | |
| branch: ${{ matrix.branch }} | |
| primary-jdk: ${{ matrix.primary_jdk }} | |
| generator-version: ${{ needs.setup.outputs.generator-version }} | |
| token: ${{ inputs.token || secrets.GH_ACTIONS_REPO_TOKEN }} | |
| - name: Record result | |
| if: always() | |
| id: record | |
| env: | |
| REPO: ${{ matrix.repo }} | |
| BRANCH: ${{ matrix.branch }} | |
| CHANGED: ${{ steps.generate.outputs.changed }} | |
| run: | | |
| safe="${REPO//\//-}-${BRANCH//\//-}" | |
| safe="${safe//./-}" | |
| echo "safe-name=${safe}" >> "$GITHUB_OUTPUT" | |
| echo '{"repo":"'"$REPO"'","branch":"'"$BRANCH"'","changed":'"${CHANGED:-false}"'}' \ | |
| > "result-${safe}.json" | |
| - name: Upload result | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: result-${{ steps.record.outputs.safe-name }} | |
| path: result-${{ steps.record.outputs.safe-name }}.json | |
| summary: | |
| name: Summary | |
| needs: generate | |
| runs-on: ubuntu-latest | |
| if: always() | |
| steps: | |
| - name: Download results | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: result-* | |
| merge-multiple: true | |
| path: results | |
| - name: Write summary | |
| run: | | |
| node - << 'JSEOF' | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| const files = fs.readdirSync('results').filter(f => f.endsWith('.json')); | |
| const results = files | |
| .map(f => JSON.parse(fs.readFileSync(path.join('results', f), 'utf8'))) | |
| .sort((a, b) => { | |
| if (a.changed !== b.changed) return a.changed ? -1 : 1; | |
| return `${a.repo}@${a.branch}`.localeCompare(`${b.repo}@${b.branch}`); | |
| }); | |
| const updated = results.filter(r => r.changed); | |
| const unchanged = results.filter(r => !r.changed); | |
| let md = '## Workflow Generator Results\n\n'; | |
| if (updated.length > 0) { | |
| md += `### Updated (${updated.length})\n\n`; | |
| md += '| Repository | Branch |\n|---|---|\n'; | |
| for (const r of updated) { | |
| md += `| \`${r.repo}\` | \`${r.branch}\` |\n`; | |
| } | |
| md += '\n'; | |
| } else { | |
| md += '> No changes were made.\n\n'; | |
| } | |
| if (unchanged.length > 0) { | |
| md += `<details><summary>Unchanged (${unchanged.length})</summary>\n\n`; | |
| md += '| Repository | Branch |\n|---|---|\n'; | |
| for (const r of unchanged) { | |
| md += `| \`${r.repo}\` | \`${r.branch}\` |\n`; | |
| } | |
| md += '\n</details>\n'; | |
| } | |
| fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, md); | |
| console.log(md); | |
| JSEOF |