Merge pull request #99 from asylumexp/upstream-merge #8
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: Validate scripts | |
on: | |
push: | |
branches: | |
- main | |
pull_request_target: | |
paths: | |
- "ct/*.sh" | |
- "install/*.sh" | |
jobs: | |
check-scripts: | |
name: Check changed files | |
runs-on: ubuntu-latest | |
permissions: | |
pull-requests: write | |
steps: | |
- name: Get pull request information | |
if: github.event_name == 'pull_request_target' | |
uses: actions/github-script@v7 | |
id: pr | |
with: | |
script: | | |
const { data: pullRequest } = await github.rest.pulls.get({ | |
...context.repo, | |
pull_number: context.payload.pull_request.number, | |
}); | |
return pullRequest; | |
- name: Checkout code | |
uses: actions/checkout@v4 | |
with: | |
fetch-depth: 0 # Ensure the full history is fetched for accurate diffing | |
ref: ${{ github.event_name == 'pull_request_target' && fromJSON(steps.pr.outputs.result).merge_commit_sha || '' }} | |
- name: Get changed files | |
id: changed-files | |
run: | | |
if ${{ github.event_name == 'pull_request_target' }}; then | |
echo "files=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ steps.pr.outputs.result && fromJSON(steps.pr.outputs.result).merge_commit_sha }} | xargs)" >> $GITHUB_OUTPUT | |
else | |
echo "files=$(git diff --name-only ${{ github.event.before }} ${{ github.event.after }} | grep -E '\.(sh|func)$' | xargs)" >> $GITHUB_OUTPUT | |
fi | |
- name: Check build.func line | |
if: always() && steps.changed-files.outputs.files != '' | |
id: build-func | |
run: | | |
NON_COMPLIANT_FILES="" | |
for FILE in ${{ steps.changed-files.outputs.files }}; do | |
if [[ "$FILE" == ct/* ]] && [[ $(sed -n '2p' "$FILE") != "source <(curl -s https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/build.func)" ]]; then | |
NON_COMPLIANT_FILES="$NON_COMPLIANT_FILES $FILE" | |
fi | |
done | |
if [ -n "$NON_COMPLIANT_FILES" ]; then | |
echo "files=$NON_COMPLIANT_FILES" >> $GITHUB_OUTPUT | |
echo "Build.func line missing or incorrect in files:" | |
for FILE in $NON_COMPLIANT_FILES; do | |
echo "$FILE" | |
done | |
exit 1 | |
fi | |
- name: Check executable permissions | |
if: always() && steps.changed-files.outputs.files != '' | |
id: check-executable | |
run: | | |
NON_COMPLIANT_FILES="" | |
for FILE in ${{ steps.changed-files.outputs.files }}; do | |
if [[ ! -x "$FILE" ]]; then | |
NON_COMPLIANT_FILES="$NON_COMPLIANT_FILES $FILE" | |
fi | |
done | |
if [ -n "$NON_COMPLIANT_FILES" ]; then | |
echo "files=$NON_COMPLIANT_FILES" >> $GITHUB_OUTPUT | |
echo "Files not executable:" | |
for FILE in $NON_COMPLIANT_FILES; do | |
echo "$FILE" | |
done | |
exit 1 | |
fi | |
- name: Check copyright | |
if: always() && steps.changed-files.outputs.files != '' | |
id: check-copyright | |
run: | | |
NON_COMPLIANT_FILES="" | |
for FILE in ${{ steps.changed-files.outputs.files }}; do | |
if ! sed -n '3p' "$FILE" | grep -qE "^# Copyright \(c\) [0-9]{4}(-[0-9]{4})? (tteck \| community-scripts ORG|community-scripts ORG|tteck|asylumexp| asylumexp | asylumexp|asylumexp )$"; then | |
NON_COMPLIANT_FILES="$NON_COMPLIANT_FILES $FILE" | |
fi | |
done | |
if [ -n "$NON_COMPLIANT_FILES" ]; then | |
echo "files=$NON_COMPLIANT_FILES" >> $GITHUB_OUTPUT | |
echo "Copyright header missing or not on line 3 in files:" | |
for FILE in $NON_COMPLIANT_FILES; do | |
echo "$FILE" | |
done | |
exit 1 | |
fi | |
- name: Check author | |
if: always() && steps.changed-files.outputs.files != '' | |
id: check-author | |
run: | | |
NON_COMPLIANT_FILES="" | |
for FILE in ${{ steps.changed-files.outputs.files }}; do | |
if ! sed -n '4p' "$FILE" | grep -qE "^# Author: .+"; then | |
NON_COMPLIANT_FILES="$NON_COMPLIANT_FILES $FILE" | |
fi | |
done | |
if [ -n "$NON_COMPLIANT_FILES" ]; then | |
echo "files=$NON_COMPLIANT_FILES" >> $GITHUB_OUTPUT | |
echo "Author header missing or invalid on line 4 in files:" | |
for FILE in $NON_COMPLIANT_FILES; do | |
echo "$FILE" | |
done | |
exit 1 | |
fi | |
- name: Check license | |
if: always() && steps.changed-files.outputs.files != '' | |
id: check-license | |
run: | | |
NON_COMPLIANT_FILES="" | |
for FILE in ${{ steps.changed-files.outputs.files }}; do | |
if [[ "$(sed -n '5p' "$FILE")" != "# License: MIT | https://github.com/asylumexp/Proxmox/raw/main/LICENSE" ]]; then | |
NON_COMPLIANT_FILES="$NON_COMPLIANT_FILES $FILE" | |
fi | |
done | |
if [ -n "$NON_COMPLIANT_FILES" ]; then | |
echo "files=$NON_COMPLIANT_FILES" >> $GITHUB_OUTPUT | |
echo "License header missing or not on line 5 in files:" | |
for FILE in $NON_COMPLIANT_FILES; do | |
echo "$FILE" | |
done | |
exit 1 | |
fi | |
- name: Check source | |
if: always() && steps.changed-files.outputs.files != '' | |
id: check-source | |
run: | | |
NON_COMPLIANT_FILES="" | |
for FILE in ${{ steps.changed-files.outputs.files }}; do | |
if ! sed -n '6p' "$FILE" | grep -qE "^# Source: .+"; then | |
NON_COMPLIANT_FILES="$NON_COMPLIANT_FILES $FILE" | |
fi | |
done | |
if [ -n "$NON_COMPLIANT_FILES" ]; then | |
echo "files=$NON_COMPLIANT_FILES" >> $GITHUB_OUTPUT | |
echo "Source header missing or not on line 6 in files:" | |
for FILE in $NON_COMPLIANT_FILES; do | |
echo "$FILE" | |
done | |
exit 1 | |
fi | |
- name: Post results and comment | |
if: always() && steps.changed-files.outputs.files != '' && github.event_name == 'pull_request_target' | |
uses: actions/github-script@v7 | |
with: | |
script: | | |
const result = '${{ job.status }}' === 'success' ? 'success' : 'failure'; | |
const nonCompliantFiles = { | |
'Invalid build.func source': "${{ steps.build-func.outputs.files }}", | |
'Not executable': "${{ steps.check-executable.outputs.files }}", | |
'Copyright header line missing or invalid': "${{ steps.check-copyright.outputs.files }}", | |
'Author header line missing or invalid': "${{ steps.check-author.outputs.files }}", | |
'License header line missing or invalid': "${{ steps.check-license.outputs.files }}", | |
'Source header line missing or invalid': "${{ steps.check-source.outputs.files }}" | |
}; | |
const issueNumber = context.payload.pull_request ? context.payload.pull_request.number : null; | |
const commentIdentifier = 'validate-scripts'; | |
let newCommentBody = `<!-- ${commentIdentifier}-start -->\n### Script validation\n\n`; | |
if (result === 'failure') { | |
newCommentBody += ':x: We found issues in the following changed files:\n\n'; | |
for (const [check, files] of Object.entries(nonCompliantFiles)) { | |
if (files) { | |
newCommentBody += `**${check}:**\n${files.trim().split(' ').map(file => `- ${file}`).join('\n')}\n\n`; | |
} | |
} | |
} else { | |
newCommentBody += `:rocket: All changed shell scripts passed validation!\n`; | |
} | |
newCommentBody += `\n\n<!-- ${commentIdentifier}-end -->`; | |
if (issueNumber) { | |
const { data: comments } = await github.rest.issues.listComments({ | |
...context.repo, | |
issue_number: issueNumber | |
}); | |
const existingComment = comments.find(comment => comment.user.login === 'github-actions[bot]'); | |
if (existingComment) { | |
if (existingComment.body.includes(commentIdentifier)) { | |
const re = new RegExp(String.raw`<!-- ${commentIdentifier}-start -->[\s\S]*?<!-- ${commentIdentifier}-end -->`, ""); | |
newCommentBody = existingComment.body.replace(re, newCommentBody); | |
} else { | |
newCommentBody = existingComment.body + '\n\n---\n\n' + newCommentBody; | |
} | |
await github.rest.issues.updateComment({ | |
...context.repo, | |
comment_id: existingComment.id, | |
body: newCommentBody | |
}); | |
} else { | |
await github.rest.issues.createComment({ | |
...context.repo, | |
issue_number: issueNumber, | |
body: newCommentBody | |
}); | |
} | |
} |