ubuntu 25.04 SEV version 3.0-0 #37
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: Update Certification Matrix | |
| on: | |
| issues: | |
| types: [opened] | |
| concurrency: | |
| group: update-certification-matrix | |
| cancel-in-progress: false | |
| jobs: | |
| update-matrix: | |
| if: contains(github.event.issue.labels.*.name, 'certificate') && github.event.issue.milestone != null | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Update README | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| // Get issue details | |
| const issue = context.payload.issue; | |
| const milestone = issue.milestone.title; | |
| const readmePath = 'README.md'; | |
| /// Find distribution label (format: "os-<string>", "os-<string>-<number>", or "os-<string>-<string>") | |
| const distroLabel = issue.labels.find(label => | |
| label.name.match(/^os-[a-zA-Z]+(?:-[a-zA-Z0-9.]+)?$/i) | |
| ); | |
| if (!distroLabel) { | |
| console.log('Could not find distribution label in format "os-<string>", "os-<string>-<number>", or "os-<string>-<string>"'); | |
| return; | |
| } | |
| // Convert label to OS name (e.g., "os-ubuntu-25.04" to "Ubuntu 25.04" or "os-debian-forky" to "Debian forky") | |
| const labelParts = distroLabel.name.split('-'); | |
| if (labelParts.length < 2) { | |
| console.log('Distribution label does not have expected format "os-<string>" or "os-<string>-<number>"'); | |
| return; | |
| } | |
| const os = labelParts[1]; | |
| const version = labelParts.length > 2 ? labelParts.slice(2).join('-') : ''; | |
| // Capitalize first letter of OS and version (if version is alphabetical) | |
| const capitalize = str => str ? str.charAt(0).toUpperCase() + str.slice(1) : ''; | |
| const osName = version ? `${capitalize(os)} ${capitalize(version)}` : capitalize(os); | |
| const osPattern = version ? new RegExp(`^\\|\\s*${os}\\s+${version}\\s*\\|`, 'i') : new RegExp(`^\\|\\s*${os}\\s*\\|`, 'i'); | |
| // Use a single branch for all certification matrix updates | |
| const branchName = `update-certification-matrix`; | |
| const commitMessage = `Update certification matrix for ${osName}`; | |
| // Get the default branch | |
| const { data: repo } = await github.rest.repos.get({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo | |
| }); | |
| const defaultBranch = repo.default_branch; | |
| // Get the SHA of the default branch | |
| const { data: refData } = await github.rest.git.getRef({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| ref: `heads/${defaultBranch}` | |
| }); | |
| const baseSha = refData.object.sha; | |
| // Check if branch already exists | |
| let branchExists = false; | |
| try { | |
| await github.rest.git.getRef({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| ref: `heads/${branchName}` | |
| }); | |
| branchExists = true; | |
| console.log(`Branch ${branchName} already exists, will fast-forward to main`); | |
| } catch (error) { | |
| if (error.status !== 404) throw error; | |
| console.log(`Creating new branch ${branchName}`); | |
| } | |
| // Create or update branch to point to latest main | |
| if (!branchExists) { | |
| await github.rest.git.createRef({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| ref: `refs/heads/${branchName}`, | |
| sha: baseSha | |
| }); | |
| console.log(`Created branch ${branchName} from main`); | |
| } else { | |
| // Try to rebase the branch onto main | |
| try { | |
| await github.rest.git.updateRef({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| ref: `heads/${branchName}`, | |
| sha: baseSha, | |
| force: false | |
| }); | |
| console.log(`Rebased ${branchName} onto main`); | |
| } catch (error) { | |
| // If update fails, it means branch is ahead or diverged - this is fine | |
| console.log(`Branch ${branchName} is already up to date or ahead of main`); | |
| } | |
| } | |
| // Get current file content from the branch (after fast-forward) | |
| const { data: fileData } = await github.rest.repos.getContent({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| path: readmePath, | |
| ref: branchName | |
| }); | |
| const content = Buffer.from(fileData.content, 'base64').toString('utf8'); | |
| // Create new table row content | |
| const newStatus = '✅'; // Assuming new certification issues indicate passing | |
| const certificationCell = `[${milestone}](${issue.html_url})`; | |
| const newRow = `| ${osName} | ${newStatus} | ${certificationCell} |`; | |
| // Replace existing row or add new row | |
| const lines = content.split('\n'); | |
| let tableStart = -1; | |
| let tableEnd = -1; | |
| let updated = false; | |
| // Find the table boundaries | |
| for (let i = 0; i < lines.length; i++) { | |
| if (lines[i].includes('| OS | Status | Certification Level |')) { | |
| tableStart = i; | |
| } else if (tableStart !== -1 && lines[i].trim() === '') { | |
| tableEnd = i; | |
| break; | |
| } | |
| } | |
| if (tableStart === -1) { | |
| console.log('Could not find certification matrix table'); | |
| return; | |
| } | |
| // Update existing row or add new row | |
| for (let i = tableStart + 2; i < tableEnd; i++) { | |
| if (osPattern.test(lines[i])) { | |
| // Extract existing OS name from the current line | |
| const existingName = lines[i].match(/^\|\s*([^|]+?)\s*\|/)[1]; | |
| // Create new row with existing OS name | |
| const updatedRow = `| ${existingName} | ${newStatus} | ${certificationCell} |`; | |
| lines[i] = updatedRow; | |
| updated = true; | |
| break; | |
| } | |
| } | |
| if (!updated) { | |
| // Add new row before table end | |
| lines.splice(tableEnd, 0, newRow); | |
| } | |
| // Commit changes to the branch | |
| await github.rest.repos.createOrUpdateFileContents({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| path: readmePath, | |
| message: commitMessage, | |
| content: Buffer.from(lines.join('\n')).toString('base64'), | |
| sha: fileData.sha, | |
| branch: branchName | |
| }); | |
| // Check if PR already exists for this branch | |
| const { data: existingPRs } = await github.rest.pulls.list({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| state: 'open', | |
| head: `${context.repo.owner}:${branchName}` | |
| }); | |
| if (existingPRs.length > 0) { | |
| console.log(`PR already exists: #${existingPRs[0].number}`); | |
| // Add a comment to the existing PR | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: existingPRs[0].number, | |
| body: `Updated certification matrix for **${osName}** (${milestone}) - refs #${issue.number}` | |
| }); | |
| } else { | |
| // Create new PR | |
| const { data: pr } = await github.rest.pulls.create({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| title: `Update certification matrix`, | |
| head: branchName, | |
| base: defaultBranch, | |
| body: `This PR automatically updates the certification matrix based on certification issues.\n\nStarted with: **${osName}** (${milestone}) - refs #${issue.number}` | |
| }); | |
| console.log(`Created PR #${pr.number}`); | |
| } | |
| close-duplicates: | |
| if: contains(github.event.issue.labels.*.name, 'certificate') && github.event.issue.milestone != null | |
| runs-on: ubuntu-latest | |
| permissions: | |
| issues: write | |
| steps: | |
| - name: Close older certification issues with same OS label | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const issue = context.payload.issue; | |
| const milestone = issue.milestone.title; | |
| // Find the OS label (format: "os-<string>", "os-<string>-<number>", or "os-<string>-<string>") | |
| const osLabel = issue.labels.find(label => | |
| label.name.match(/^os-[a-zA-Z]+(?:-[a-zA-Z0-9.]+)?$/i) | |
| ); | |
| if (!osLabel) { | |
| console.log('No OS label found, skipping duplicate check.'); | |
| return; | |
| } | |
| // Get all open issues in the same milestone | |
| const { data: allIssues } = await github.rest.issues.listForRepo({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| state: 'open', | |
| milestone: issue.milestone.number, | |
| per_page: 100 | |
| }); | |
| for (const other of allIssues) { | |
| if (other.number === issue.number) continue; | |
| // Check if other issue has the same two labels | |
| const hasCertLabel = other.labels.some(l => l.name === 'certificate'); | |
| const hasSameOsLabel = other.labels.some(l => l.name === osLabel.name); | |
| if (hasCertLabel && hasSameOsLabel) { | |
| console.log(`Closing older duplicate issue #${other.number} for ${osLabel.name}`); | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: other.number, | |
| body: `Closing as duplicate of #${issue.number} (newer certification issue for **${osLabel.name}** on milestone **${milestone}**).` | |
| }); | |
| await github.rest.issues.update({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: other.number, | |
| state: 'closed' | |
| }); | |
| } | |
| } |