[KLC-1895] Optimize VM hook timeout protection with category-based strategy #29
Workflow file for this run
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: QA Sec | |
| on: | |
| pull_request: | |
| types: [opened, reopened, synchronize, ready_for_review] | |
| push: | |
| branches: [master, main, develop] | |
| workflow_dispatch: | |
| jobs: | |
| setup-and-lint: | |
| uses: ./.github/workflows/go-setup-lint.yaml | |
| if: github.event_name != 'pull_request' || github.event.pull_request.draft == false | |
| with: | |
| go-version: ${{ vars.GO_VERSION || '1.25' }} | |
| golangci-lint-version: ${{ vars.GOLANGCI_VERSION || 'v1.64.8' }} | |
| secrets: | |
| git-user: ${{ secrets.GIT_USER }} | |
| git-pass: ${{ secrets.GIT_PASS }} | |
| test: | |
| needs: setup-and-lint | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Go Setup | |
| uses: ./.github/actions/go-setup-action | |
| with: | |
| go-version: ${{ vars.GO_VERSION || '1.25' }} | |
| - name: Run tests | |
| id: run-tests | |
| continue-on-error: true | |
| run: | | |
| set +e # Disable immediate exit on error | |
| if [[ "${{ github.event_name }}" != "pull_request" ]]; then | |
| echo "Running full tests..." | |
| go test -covermode=atomic -coverprofile=coverage.out -json ./... > test-output.json | |
| else | |
| echo "Running short tests..." | |
| go test -short -covermode=atomic -coverprofile=coverage.out -json ./... > test-output.json | |
| fi | |
| echo "test_exit_code=$?" >> $GITHUB_OUTPUT | |
| - name: Process test results | |
| if: always() | |
| id: coverage | |
| run: | | |
| # Generate coverage summary | |
| if [ -f coverage.out ]; then | |
| go tool cover -func=coverage.out > coverage-summary.txt | |
| COVERAGE=$(grep "total:" coverage-summary.txt | awk '{print $3}') | |
| echo "total=$COVERAGE" >> $GITHUB_OUTPUT | |
| else | |
| echo "No coverage data generated" > coverage-summary.txt | |
| echo "total=0.0%" >> $GITHUB_OUTPUT | |
| fi | |
| # Create test summary and check for failures | |
| echo "### Test Results 🧪" >> $GITHUB_STEP_SUMMARY | |
| if [ "${{ steps.run-tests.outputs.test_exit_code }}" == "0" ]; then | |
| echo "✅ All tests passed successfully" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "❌ Some tests failed" >> $GITHUB_STEP_SUMMARY | |
| echo "::error::🔴 Test failures detected!" | |
| if [ -f test-output.json ]; then | |
| echo "#### Failed Tests" >> $GITHUB_STEP_SUMMARY | |
| echo '```' >> $GITHUB_STEP_SUMMARY | |
| jq -r 'select(.Action=="fail") | "- \(.Package) - \(.Test)"' test-output.json | tee -a $GITHUB_STEP_SUMMARY | |
| echo '```' >> $GITHUB_STEP_SUMMARY | |
| # Output failures as GitHub annotations | |
| jq -r 'select(.Action=="fail") | "::error file=\(.Package)::❌ \(.Test): \(.Action)"' test-output.json | |
| fi | |
| exit 1 | |
| fi | |
| echo "Coverage: ${{ steps.coverage.outputs.total }}" >> $GITHUB_STEP_SUMMARY | |
| - name: Upload test results | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: test-artifacts | |
| path: | | |
| coverage.out | |
| test-output.json | |
| coverage-summary.txt | |
| retention-days: 1 | |
| overwrite: true | |
| outputs: | |
| coverage: ${{ steps.coverage.outputs.total }} | |
| test_exit_code: ${{ steps.run-tests.outputs.test_exit_code }} | |
| sonarqube: | |
| needs: [setup-and-lint, test] | |
| runs-on: ubuntu-latest | |
| if: | | |
| always() && | |
| github.event_name != 'pull_request' || github.event.pull_request.draft == false | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Download lint results | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: lint-results | |
| - name: Download test results | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: test-artifacts | |
| - uses: twingate/github-action@V1.1 | |
| with: | |
| service-key: ${{ secrets.TWINGATE_SERVICE_ACCOUNT }} | |
| - name: SonarQube Scan | |
| uses: sonarsource/sonarqube-scan-action@v2.3.0 | |
| env: | |
| SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} | |
| SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} | |
| with: | |
| args: > | |
| -Dsonar.projectKey=${{ github.event.repository.name }} | |
| -Dsonar.plugins.downloadOnlyRequired=true | |
| -Dsonar.exclusions=**/*.json,**/*.xml,**/*_test.go,vendor/**,coverage.out,integrationTest/** | |
| -Dsonar.test.inclusions="**/*_test.go" | |
| -Dsonar.go.coverage.reportPaths=coverage.out | |
| -Dsonar.go.tests.reportPaths=test-output.json | |
| -Dsonar.go.golangci-lint.reportPaths=golangci-report.xml | |
| -Dsonar.branch.name=${{ github.ref_name }} | |
| if: github.event_name != 'pull_request' | |
| - name: SonarQube Go detailed scan PR | |
| uses: sonarsource/sonarqube-scan-action@v2.3.0 | |
| env: | |
| SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} | |
| SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} | |
| with: | |
| args: > | |
| -Dsonar.projectKey=${{ github.event.repository.name }} | |
| -Dsonar.plugins.downloadOnlyRequired=true | |
| -Dsonar.exclusions=**/*.json,**/*.xml,**/*_test.go,vendor/**,coverage.out,integrationTest/** | |
| -Dsonar.test.inclusions="**/*_test.go" | |
| -Dsonar.go.coverage.reportPaths=coverage.out | |
| -Dsonar.go.tests.reportPaths=test-output.json | |
| -Dsonar.go.golangci-lint.reportPaths=golangci-report.xml | |
| -Dsonar.pullrequest.branch=${{ github.head_ref }} | |
| -Dsonar.pullrequest.base=${{ github.base_ref }} | |
| -Dsonar.pullrequest.key=${{ github.event.number }} | |
| if: github.event_name == 'pull_request' | |
| - name: SonarQube Quality Gate check | |
| uses: sonarsource/sonarqube-quality-gate-action@master | |
| timeout-minutes: 5 | |
| env: | |
| SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} | |
| SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} | |
| notify: | |
| needs: [setup-and-lint, test, sonarqube] | |
| runs-on: ubuntu-latest | |
| if: | | |
| always() && | |
| github.event_name != 'pull_request' || github.event.pull_request.draft == false | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Determine job statuses | |
| id: job-status | |
| run: | | |
| echo "sonarqube_status=${{ needs.sonarqube.result == 'success' && '✅' || '❌' }}" >> $GITHUB_OUTPUT | |
| echo "test_status=${{ needs.test.outputs.test_exit_code == '0' && '✅' || '❌' }}" >> $GITHUB_OUTPUT | |
| if [[ "${{ needs.sonarqube.result }}" == "failure" || "${{ needs.test.outputs.test_exit_code }}" == "1" || "${{ needs.setup-and-lint.result }}" == "failure" ]]; then | |
| echo "overall_status=Failure" >> $GITHUB_OUTPUT | |
| echo "color=#FF0000" >> $GITHUB_OUTPUT | |
| echo "icon=❌" >> $GITHUB_OUTPUT | |
| else | |
| echo "overall_status=Success" >> $GITHUB_OUTPUT | |
| echo "color=#36a64f" >> $GITHUB_OUTPUT | |
| echo "icon=✅" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Get event context | |
| id: context | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| echo "name=${{ github.event.sender.login }}" >> $GITHUB_OUTPUT | |
| if [[ "${{ github.event_name }}" == "pull_request" ]]; then | |
| echo "event_title=${{ github.event.pull_request.title }}" >> $GITHUB_OUTPUT | |
| echo "changed_files=${{ github.event.pull_request.changed_files }}" >> $GITHUB_OUTPUT | |
| echo "additions=${{ github.event.pull_request.additions }}" >> $GITHUB_OUTPUT | |
| echo "deletions=${{ github.event.pull_request.deletions }}" >> $GITHUB_OUTPUT | |
| # Get file list for PR | |
| pr_files=$(gh api repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files --jq '.[].filename') | |
| echo "file_list<<EOF" >> $GITHUB_OUTPUT | |
| echo "$pr_files" | head -n 10 >> $GITHUB_OUTPUT | |
| echo "EOF" >> $GITHUB_OUTPUT | |
| echo "REPORT_URL=${{ secrets.SONAR_HOST_URL }}/dashboard?id=klever-go&pullRequest=${{ github.event.pull_request.number }}" >> $GITHUB_ENV | |
| echo "REVIEW_URL=${{ github.event.pull_request.html_url }}/files" >> $GITHUB_ENV | |
| echo "REVIEW_TEXT=Review PR" >> $GITHUB_ENV | |
| else | |
| echo "event_title=MERGE ${{ github.event.number }}" >> $GITHUB_OUTPUT | |
| # Use GitHub API to get commit details | |
| commit_data=$(gh api repos/${{ github.repository }}/commits/${{ github.sha }} --jq '{files:.files}') | |
| changed_files=$(echo $commit_data | jq '.files | length') | |
| echo "changed_files=$changed_files" >> $GITHUB_OUTPUT | |
| echo "file_list<<EOF" >> $GITHUB_OUTPUT | |
| echo $commit_data | jq -r '.files[].filename' | head -n 10 >> $GITHUB_OUTPUT | |
| echo "EOF" >> $GITHUB_OUTPUT | |
| additions=$(echo $commit_data | jq '[.files[].additions] | add') | |
| deletions=$(echo $commit_data | jq '[.files[].deletions] | add') | |
| echo "additions=$additions" >> $GITHUB_OUTPUT | |
| echo "deletions=$deletions" >> $GITHUB_OUTPUT | |
| echo "REPORT_URL=${{ secrets.SONAR_HOST_URL }}/dashboard?id=klever-go&branch=${{ github.ref_name }}" >> $GITHUB_ENV | |
| echo "REVIEW_URL=${{ github.event.repository.html_url }}/commits/${{ github.ref_name }}" >> $GITHUB_ENV | |
| echo "REVIEW_TEXT=View Commits" >> $GITHUB_ENV | |
| fi | |
| - name: Slack Notification | |
| run: | | |
| curl -X POST -H 'Content-type: application/json' --data ' | |
| { | |
| "text": "🚀 CI/CD Pipeline ${{ steps.job-status.outputs.icon }} Update for ${{ github.repository }}", | |
| "attachments": [ | |
| { | |
| "color": "${{ steps.job-status.outputs.color }}", | |
| "blocks": [ | |
| { | |
| "type": "section", | |
| "fields": [ | |
| { | |
| "type": "mrkdwn", | |
| "text": "*Status:*\n${{ steps.job-status.outputs.overall_status }}" | |
| }, | |
| { | |
| "type": "mrkdwn", | |
| "text": "*Branch:*\n${{ github.ref_name || github.ref }}" | |
| } | |
| ] | |
| }, | |
| { | |
| "type": "section", | |
| "fields": [ | |
| { | |
| "type": "mrkdwn", | |
| "text": "*Event:*\n${{ github.event_name }}" | |
| }, | |
| { | |
| "type": "mrkdwn", | |
| "text": "*Author:*\n${{ github.event.sender.login }}" | |
| } | |
| ] | |
| }, | |
| { | |
| "type": "section", | |
| "text": { | |
| "type": "mrkdwn", | |
| "text": "*${{ github.event_name == 'pull_request' && 'PR Title' || 'Commit Message' }}:*\n${{ steps.context.outputs.event_title }}" | |
| } | |
| }, | |
| { | |
| "type": "section", | |
| "text": { | |
| "type": "mrkdwn", | |
| "text": "*Changes:*\n• Files changed: ${{ steps.context.outputs.changed_files }} (+${{ steps.context.outputs.additions }}/-${{ steps.context.outputs.deletions }})\n• First 10 changed files:\n```${{ steps.context.outputs.file_list }}```" | |
| } | |
| }, | |
| { | |
| "type": "section", | |
| "text": { | |
| "type": "mrkdwn", | |
| "text": "*Job Results:*\n• SonarQube: ${{ steps.job-status.outputs.sonarqube_status }}\n• Tests: ${{ steps.job-status.outputs.test_status }}\n• Project Coverage: ${{ needs.test.outputs.coverage }}\n" | |
| } | |
| }, | |
| { | |
| "type": "actions", | |
| "elements": [ | |
| { | |
| "type": "button", | |
| "text": { | |
| "type": "plain_text", | |
| "text": "View Actions Run" | |
| }, | |
| "url": "${{ github.event.repository.html_url }}/actions/runs/${{ github.run_id }}" | |
| }, | |
| { | |
| "type": "button", | |
| "text": { | |
| "type": "plain_text", | |
| "text": "View SonarQube Report" | |
| }, | |
| "url": "${{ env.REPORT_URL }}" | |
| }, | |
| { | |
| "type": "button", | |
| "text": { | |
| "type": "plain_text", | |
| "text": "${{ env.REVIEW_TEXT }}" | |
| }, | |
| "url": "${{ env.REVIEW_URL }}", | |
| "style": "primary" | |
| } | |
| ] | |
| }, | |
| { | |
| "type": "context", | |
| "elements": [ | |
| { | |
| "type": "mrkdwn", | |
| "text": "Need help? <${{ github.event.repository.html_url }}/issues/new|Create an issue>" | |
| } | |
| ] | |
| } | |
| ] | |
| } | |
| ] | |
| }' ${{ secrets.SLACK_WEBHOOK_URL }} | |
| env: | |
| # build Sonar URL based on the event type | |
| SONAR_URL: ${{ secrets.SONAR_HOST_URL }} | |
| SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} | |
| REPORT_URL: ${{ env.REPORT_URL }} | |
| REVIEW_URL: ${{ env.REVIEW_URL }} | |
| REVIEW_TEXT: ${{ env.REVIEW_TEXT }} | |
| result-check: | |
| needs: [sonarqube, test] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check for failed jobs | |
| run: | | |
| if [[ "${{ needs.sonarqube.result }}" == "failure" || "${{ needs.test.outputs.test_exit_code }}" == "1" ]]; then | |
| echo "One or more jobs failed. Exiting with status 1." | |
| exit 1 | |
| fi |