From 9f4683bca9323d0f1946a06fe4e6635a8f9709d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastiaan?= Date: Sun, 5 Jan 2025 12:47:03 +0100 Subject: [PATCH] Combine all headers checks into workflow with steps that end with a PR comment --- .github/check-script.yml | 54 ------- .github/workflows/check-metadata.yml | 55 ------- .github/workflows/validate-headers.yml | 214 +++++++++++++++++++++++++ 3 files changed, 214 insertions(+), 109 deletions(-) delete mode 100644 .github/check-script.yml delete mode 100644 .github/workflows/check-metadata.yml create mode 100644 .github/workflows/validate-headers.yml diff --git a/.github/check-script.yml b/.github/check-script.yml deleted file mode 100644 index 560bc3965b..0000000000 --- a/.github/check-script.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Check Shell Scripts - -on: - pull_request: - paths: - - '**/*.sh' # Führt den Check nur für Shell-Skripte aus - -jobs: - check-scripts: - runs-on: ubuntu-latest - - steps: - - name: Checkout Code - uses: actions/checkout@v3 - - - name: Check `source` Line in Scripts - shell: bash - run: | - set -e - ERROR_COUNT=0 - FILES=$(find . -name "*.sh") - - for FILE in $FILES; do - # Check for exact match of the source line in line 2 - if [[ $(sed -n '2p' "$FILE") =~ ^source[[:space:]]+<(curl -s https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func) ]]; then - echo "Check passed for: $FILE" - else - echo "Error in $FILE: Line 2 must be exactly 'source <(curl -s https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)' if a source line is used." - ERROR_COUNT=$((ERROR_COUNT + 1)) - fi - - # Check for shebang line at the top - if [[ $(head -n 1 "$FILE") != "#!/usr/bin/env bash" ]]; then - echo "Error in $FILE: The first line must be '#!/usr/bin/env bash'." - ERROR_COUNT=$((ERROR_COUNT + 1)) - fi - - # Check for executable permissions - if [[ ! -x "$FILE" ]]; then - echo "Warning in $FILE: This script is not executable. Consider running 'chmod +x $FILE'." - fi - - # Check for empty lines at the beginning of the script - if [[ $(head -n 10 "$FILE" | grep -c '^$') -gt 0 ]]; then - echo "Warning in $FILE: There are empty lines at the beginning of the script. Consider removing them." - fi - done - - if [[ "$ERROR_COUNT" -gt 0 ]]; then - echo "$ERROR_COUNT script(s) failed validation." - exit 1 - else - echo "All scripts passed." - fi diff --git a/.github/workflows/check-metadata.yml b/.github/workflows/check-metadata.yml deleted file mode 100644 index 67159febd9..0000000000 --- a/.github/workflows/check-metadata.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: Check Metadata -on: - pull_request: - paths: - - '/ct/*.sh' - - '/install/*.sh' -jobs: - check-metadata: - runs-on: ubuntu-latest - steps: - - name: Checkout Code - uses: actions/checkout@v4 - - name: Check Metadata Lines in Scripts - shell: bash - run: | - set -e - ERROR_COUNT=0 - FILES=$(find . -name "*.sh") - - for FILE in $FILES; do - if [[ "$(sed -n '3p' "$FILE")" == "# Copyright (c) 2021-2024 community-scripts ORG" ]]; then - echo "Check for Copyright metadata passed for line 3 in: $FILE" - else - echo "Error in $FILE: Copyright metadata missing or not on line 3" - ERROR_COUNT=$((ERROR_COUNT + 1)) - fi - - if sed -n '4p' "$FILE" | grep -qE "^# Author: .+"; then - echo "Check for Author metadata passed for line 4 in: $FILE" - else - echo "Error in $FILE: Author metadata missing or invalid on line 4" - ERROR_COUNT=$((ERROR_COUNT + 1)) - fi - - if [[ "$(sed -n '5p' "$FILE")" == "# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE" ]]; then - echo "Check for License metadata passed for line 5 in: $FILE" - else - echo "Error in $FILE: License metadata missing or not on line 5" - ERROR_COUNT=$((ERROR_COUNT + 1)) - fi - - if sed -n '6p' "$FILE" | grep -qE "^# Source: .+"; then - echo "Check for Source metadata passed for line 6 in: $FILE" - else - echo "Error in $FILE: Source metadata missing or invalid on line 6" - ERROR_COUNT=$((ERROR_COUNT + 1)) - fi - done - - if [[ "$ERROR_COUNT" -gt 0 ]]; then - echo "$ERROR_COUNT script(s) failed validation." - exit 1 - else - echo "All scripts passed." - fi diff --git a/.github/workflows/validate-headers.yml b/.github/workflows/validate-headers.yml new file mode 100644 index 0000000000..2ea059d7d0 --- /dev/null +++ b/.github/workflows/validate-headers.yml @@ -0,0 +1,214 @@ +name: Validate script headers +on: + push: + branches: + - main + pull_request: + paths: + - "ct/*.sh" + - "install/*.sh" + - ".github/workflows/validate-headers.yml" + +jobs: + check-scripts: + name: Check changed files + runs-on: ubuntu-latest + permissions: + pull-requests: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: ${{ github.event_name == 'pull_request' && 2 || 0 }} + + - name: Get changed files + id: changed-files + run: | + if ${{ github.event_name == 'pull_request' }}; then + echo "files=$(git diff --name-only -r HEAD^1 HEAD | grep -E '\.(sh|func)$' | 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 [[ $(sed -n '2p' "$FILE") != "source <(curl -s https://raw.githubusercontent.com/community-scripts/ProxmoxVE/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")" != "# Copyright (c) 2021-2025 community-scripts ORG" ]]; 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/community-scripts/ProxmoxVE/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' + 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-headers'; + let newCommentBody = `\n### Script header 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 header validation!\n`; + } + + newCommentBody += `\n\n`; + + 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`[\s\S]*?`, ""); + 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 + }); + } + }