QA Sec #22
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 |