From: Jaroslav Kysela Date: Thu, 16 Apr 2026 13:03:33 +0000 (+0200) Subject: github: workflow: move things to separate alsa-project/github-workflows repo X-Git-Url: https://git.alsa-project.org/?a=commitdiff_plain;h=d10fcd2dca5d1cf9d042713becb3f135517d9825;p=alsa-ucm-conf.git github: workflow: move things to separate alsa-project/github-workflows repo It is a preparation to be reused for other ALSA repos. Signed-off-by: Jaroslav Kysela --- diff --git a/.github/label-descriptions.yml b/.github/label-descriptions.yml deleted file mode 100644 index 6cd51e3..0000000 --- a/.github/label-descriptions.yml +++ /dev/null @@ -1,62 +0,0 @@ -# Label descriptions configuration -# Each label can have a description that will be posted as a comment when the label is applied - -labels: - - name: kernel driver - description: | - ## ALSA Linux kernel driver work - - **Note:** This issue does not appear to be related to this repository, but rather seems to be an ALSA kernel driver issue. - - Please direct ALSA driver related discussions to the linux-sound@vger.kernel.org mailing list. - - **Bugtracker:** https://bugzilla.kernel.org (Audio component) - - For kernel patches, please see: https://www.kernel.org/doc/html/latest/process/submitting-patches.html - - - name: signed off by - description: | - ## Missing correct Signed-off-by line - - This pull request has commits that are missing proper `Signed-off-by` lines or use invalid email addresses. - - **Requirements:** - - Each commit must include at least one `Signed-off-by: Your Name ` line - - Commit author email must appear in at least one Signed-off-by line - - Commit committer email must appear in at least one Signed-off-by line (if not a GitHub bot) - - Multiple Signed-off-by lines are allowed for co-authored commits - - Anonymous GitHub emails (like `@users.noreply.github.com`) are not allowed - - The Signed-off-by line indicates that you agree to the Developer Certificate of Origin (DCO) - - **How to fix:** - ```bash - # Configure your git identity first (if needed): - git config user.name "Your Name" - git config user.email "your.email@example.com" - - # For the last commit: - git commit --amend --signoff - - # For multiple commits, use interactive rebase: - git rebase -i HEAD~N --signoff - - # For co-authored commits, add multiple Signed-off-by lines manually: - git commit --amend - # Then add in the commit message: - # Signed-off-by: Author One - # Signed-off-by: Author Two - - # Then force push: - git push --force-with-lease - ``` - - **Reference:** [Developer Certificate of Origin](https://developercertificate.org/) - -# Signed-off-by validation configuration -sob_validation: - # Email patterns to deny (supports wildcards) - denied_emails: - - '*@users.noreply.github.com' - # Add more patterns as needed: - # - '*@example-blocked-domain.com' - # - 'noreply@*' diff --git a/.github/workflows/label-automation.yml b/.github/workflows/label-automation.yml index 783a334..71a9876 100644 --- a/.github/workflows/label-automation.yml +++ b/.github/workflows/label-automation.yml @@ -1,5 +1,8 @@ -# Example workflow for using the label commenter + Example workflow for using the label commenter # Place this file in your repository at: .github/workflows/label-automation.yml +# +# You also need to create .github/label-descriptions.yml in your repository +# Example config: https://github.com/alsa-project/github-workflows/blob/main/repo/config/label-descriptions.yml name: Label Automation @@ -13,11 +16,14 @@ on: jobs: handle-label: - uses: ./.github/workflows/reusable-label-commenter.yml + uses: alsa-project/github-workflows/repo/workflows/label-commenter.yml@main permissions: contents: read pull-requests: write issues: write with: - config-path: '.github/label-descriptions.yml' + # Path to your local config file OR URL to remote config (required) + config-path: 'https://raw.githubusercontent.com/alsa-project/github-workflows/main/repo/config/label-descriptions.yml' + # Local file example: '.github/label-descriptions.yml' + # Remote URL example: 'https://raw.githubusercontent.com/alsa-project/github-workflows/main/repo/config/label-descriptions.yml' secrets: inherit diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml index bbce703..2fab1a4 100644 --- a/.github/workflows/pr-validation.yml +++ b/.github/workflows/pr-validation.yml @@ -1,5 +1,8 @@ # Example workflow for automatic Signed-off-by validation # Place this file in your repository at: .github/workflows/pr-validation.yml +# +# You also need to create .github/label-descriptions.yml in your repository +# Example config: https://github.com/alsa-project/github-workflows/blob/main/repo/config/label-descriptions.yml name: PR Validation @@ -17,13 +20,16 @@ on: jobs: validate-commits: - uses: ./.github/workflows/reusable-sob-validator.yml + uses: alsa-project/github-workflows/repo/workflows/sob-validator.yml@main permissions: contents: read pull-requests: write issues: write with: - config-path: '.github/label-descriptions.yml' + # Path to your local config file OR URL to remote config (required) + config-path: 'https://raw.githubusercontent.com/alsa-project/github-workflows/main/repo/config/label-descriptions.yml' + # Local file example: '.github/label-descriptions.yml' + # Remote URL example: 'https://raw.githubusercontent.com/alsa-project/github-workflows/main/repo/config/label-descriptions.yml' sob-label: 'signed off by' pr-number: ${{ github.event_name == 'workflow_dispatch' && format('{0}', inputs.pr_number) || '' }} secrets: inherit diff --git a/.github/workflows/reusable-label-commenter.yml b/.github/workflows/reusable-label-commenter.yml deleted file mode 100644 index ac7638f..0000000 --- a/.github/workflows/reusable-label-commenter.yml +++ /dev/null @@ -1,126 +0,0 @@ -name: Reusable Label Commenter - -on: - workflow_call: - inputs: - config-path: - description: 'Path to label descriptions config file' - required: false - type: string - default: '.github/label-descriptions.yml' - -jobs: - add-label-comment: - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - issues: write - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Read and parse config - id: read-config - run: | - if [ ! -f "${{ inputs.config-path }}" ]; then - echo "exists=false" >> $GITHUB_OUTPUT - echo "Warning: Config file ${{ inputs.config-path }} not found" - exit 0 - fi - - echo "exists=true" >> $GITHUB_OUTPUT - - # Convert YAML to JSON using Python - python3 << 'PYTHON_EOF' > /tmp/config.json - import yaml - import json - with open('${{ inputs.config-path }}', 'r') as f: - config = yaml.safe_load(f) - print(json.dumps(config)) - PYTHON_EOF - - # Store JSON as output (escaped for multiline) - echo "config<> $GITHUB_OUTPUT - cat /tmp/config.json >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT - - - name: Handle label action - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - // Get the label and action - const label = context.payload.label.name; - const action = context.payload.action; - - // Check if issue or PR - const issueNumber = context.payload.issue?.number || context.payload.pull_request?.number; - if (!issueNumber) { - console.log('No issue or PR number found'); - return; - } - - // Create unique identifier for this label comment - const commentId = ``; - - // Get existing comments - const { data: comments } = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issueNumber, - }); - - // Find existing comment with this label ID - const existingComment = comments.find(comment => - comment.body?.includes(commentId) - ); - - if (action === 'unlabeled') { - // Label was removed - delete the comment if it exists - if (existingComment) { - await github.rest.issues.deleteComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: existingComment.id, - }); - console.log(`Deleted comment for removed label: ${label}`); - } else { - console.log(`No comment found for removed label: ${label}`); - } - } else if (action === 'labeled') { - // Label was added - add or update comment - const config = JSON.parse(process.env.CONFIG_JSON || '{}'); - - // Find description for this label - const labelConfig = config.labels?.find(l => l.name === label); - - if (!labelConfig || !labelConfig.description) { - console.log(`No description configured for label: ${label}`); - return; - } - - const commentBody = `${commentId}\n${labelConfig.description}`; - - if (existingComment) { - // Update existing comment - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: existingComment.id, - body: commentBody, - }); - console.log(`Updated comment for label: ${label}`); - } else { - // Create new comment - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issueNumber, - body: commentBody, - }); - console.log(`Created comment for label: ${label}`); - } - } - env: - CONFIG_JSON: ${{ steps.read-config.outputs.config }} diff --git a/.github/workflows/reusable-sob-validator.yml b/.github/workflows/reusable-sob-validator.yml deleted file mode 100644 index 97cc681..0000000 --- a/.github/workflows/reusable-sob-validator.yml +++ /dev/null @@ -1,295 +0,0 @@ -name: Reusable Signed-off-by Validator - -on: - workflow_call: - inputs: - config-path: - description: 'Path to label descriptions config file' - required: false - type: string - default: '.github/label-descriptions.yml' - sob-label: - description: 'Label to add when SOB is missing or invalid' - required: false - type: string - default: 'signed off by' - pr-number: - description: 'PR number to validate (optional, auto-detects from event if not provided)' - required: false - type: string - default: '' - -jobs: - validate-signedoff: - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - issues: write - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Determine PR number - id: pr-number - uses: actions/github-script@v7 - with: - script: | - // Get PR number from input (if manually triggered) or from event - const inputPrNumber = '${{ inputs.pr-number }}'; - const prNumber = (inputPrNumber && inputPrNumber.trim() !== '') - ? inputPrNumber - : context.payload.pull_request?.number; - - if (!prNumber) { - core.setFailed('No PR number available from input or event'); - return; - } - core.setOutput('number', prNumber); - console.log(`Using PR number: ${prNumber}`); - - - name: Read and parse config - id: read-config - run: | - if [ ! -f "${{ inputs.config-path }}" ]; then - echo "exists=false" >> $GITHUB_OUTPUT - echo "{}" > /tmp/config.json - else - echo "exists=true" >> $GITHUB_OUTPUT - # Convert YAML to JSON using Python - python3 << 'PYTHON_EOF' > /tmp/config.json - import yaml - import json - with open('${{ inputs.config-path }}', 'r') as f: - config = yaml.safe_load(f) - print(json.dumps(config)) - PYTHON_EOF - fi - - # Store JSON as output (escaped for multiline) - echo "config<> $GITHUB_OUTPUT - cat /tmp/config.json >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT - - - name: Validate Signed-off-by - id: validate - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - // Parse config from JSON - const config = JSON.parse(process.env.CONFIG_JSON); - let deniedEmails = config.sob_validation?.denied_emails || []; - - // Add default denied patterns - if (!deniedEmails.includes('*@users.noreply.github.com')) { - deniedEmails.push('*@users.noreply.github.com'); - } - - const prNumber = ${{ steps.pr-number.outputs.number }}; - - // Get all commits in the PR - const { data: commits } = await github.rest.pulls.listCommits({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: prNumber, - }); - - const issues = []; - - for (const commit of commits) { - const message = commit.commit.message; - const sha = commit.sha.substring(0, 7); - const authorEmail = commit.commit.author.email; - const committerEmail = commit.commit.committer.email; - - // Find all Signed-off-by lines (case-insensitive) - const sobPattern = /^Signed-off-by:\s+(.+)\s+<(.+)>$/gim; - const sobMatches = [...message.matchAll(sobPattern)]; - - if (sobMatches.length === 0) { - issues.push(`- Commit ${sha}: Missing Signed-off-by line`); - continue; - } - - // Extract all SOB emails - const sobEmails = sobMatches.map(match => match[2]); - - // Check if author email is in at least one Signed-off-by line - if (!sobEmails.includes(authorEmail)) { - issues.push(`- Commit ${sha}: Commit author email (${authorEmail}) not found in any Signed-off-by line`); - continue; - } - - // Check if committer email is in at least one Signed-off-by line - // (Skip check for GitHub web-flow and noreply bots which are common for web commits) - const isGitHubBot = committerEmail.includes('noreply.github.com') || - committerEmail === 'noreply@github.com'; - if (!isGitHubBot && !sobEmails.includes(committerEmail)) { - issues.push(`- Commit ${sha}: Commit committer email (${committerEmail}) not found in any Signed-off-by line`); - continue; - } - - // Check all SOB emails against denied patterns - let deniedEmailFound = false; - for (const sobEmail of sobEmails) { - for (const pattern of deniedEmails) { - const regex = new RegExp('^' + pattern.replace('*', '.*') + '$'); - if (regex.test(sobEmail)) { - issues.push(`- Commit ${sha}: Invalid email in Signed-off-by: ${sobEmail}`); - deniedEmailFound = true; - break; - } - } - if (deniedEmailFound) break; - } - if (deniedEmailFound) continue; - - // Check author email against denied patterns - let authorDenied = false; - for (const pattern of deniedEmails) { - const regex = new RegExp('^' + pattern.replace('*', '.*') + '$'); - if (regex.test(authorEmail)) { - issues.push(`- Commit ${sha}: Invalid commit author email: ${authorEmail}`); - authorDenied = true; - break; - } - } - if (authorDenied) continue; - - // Check committer email against denied patterns (skip GitHub bots) - if (!isGitHubBot) { - for (const pattern of deniedEmails) { - const regex = new RegExp('^' + pattern.replace('*', '.*') + '$'); - if (regex.test(committerEmail)) { - issues.push(`- Commit ${sha}: Invalid commit committer email: ${committerEmail}`); - break; - } - } - } - } - - // Store results - core.setOutput('has_issues', issues.length > 0); - core.setOutput('issues', issues.join('\n')); - - return issues.length > 0; - env: - CONFIG_JSON: ${{ steps.read-config.outputs.config }} - - - name: Add label if issues found - if: steps.validate.outputs.has_issues == 'true' - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: ${{ steps.pr-number.outputs.number }}, - labels: ['${{ inputs.sob-label }}'], - }); - - - name: Add comment with issues - if: steps.validate.outputs.has_issues == 'true' - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const commentId = ''; - const issues = process.env.SOB_ISSUES; - - // Try to get custom message from config - let customMessage = ''; - const config = JSON.parse(process.env.CONFIG_JSON); - const labelConfig = config.labels?.find(l => l.name === '${{ inputs.sob-label }}'); - if (labelConfig?.description) { - customMessage = '\n\n' + labelConfig.description; - } - - const commentBody = commentId + '\n' + - '## ⚠️ Signed-off-by Validation Issues\n\n' + - 'The following commits have issues with their Signed-off-by lines:\n\n' + - issues + '\n\n' + - 'Please add a proper `Signed-off-by: Your Name ` line to each commit message.' + - customMessage; - - // Check for existing comment - const { data: comments } = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: ${{ steps.pr-number.outputs.number }}, - }); - - const existingComment = comments.find(c => c.body?.includes(commentId)); - - if (existingComment) { - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: existingComment.id, - body: commentBody, - }); - } else { - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: ${{ steps.pr-number.outputs.number }}, - body: commentBody, - }); - } - env: - SOB_ISSUES: ${{ steps.validate.outputs.issues }} - CONFIG_JSON: ${{ steps.read-config.outputs.config }} - - - name: Remove label and comment if no issues - if: steps.validate.outputs.has_issues == 'false' - uses: actions/github-script@v7 - continue-on-error: true - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const prNumber = parseInt('${{ steps.pr-number.outputs.number }}', 10); - const labelName = '${{ inputs.sob-label }}'; - - // Remove label - try { - await github.rest.issues.removeLabel({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: prNumber, - name: labelName, - }); - console.log(`Removed label: ${labelName}`); - } catch (error) { - if (error.status === 404) { - console.log('Label not present or already removed'); - } else { - console.log(`Error removing label: ${error.message}`); - } - } - - // Remove validation comment if it exists - try { - const commentId = ''; - const { data: comments } = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: prNumber, - }); - - const existingComment = comments.find(c => c.body?.includes(commentId)); - - if (existingComment) { - await github.rest.issues.deleteComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: existingComment.id, - }); - console.log('Removed validation comment'); - } - } catch (error) { - console.log(`Error removing comment: ${error.message}`); - }