header image #3
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: Build & Test | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| types: [opened, synchronize, reopened, labeled, unlabeled] | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| GOLANG_VERSION: 1.24.4 | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| jobs: | |
| check-label: | |
| name: Check Required Label | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'pull_request' | |
| outputs: | |
| should-run: ${{ steps.check.outputs.should-run }} | |
| steps: | |
| - name: Check for run-checks label | |
| id: check | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const labels = context.payload.pull_request.labels.map(label => label.name); | |
| const hasRunChecks = labels.includes('run-checks'); | |
| console.log('PR labels:', labels); | |
| console.log('Has run-checks label:', hasRunChecks); | |
| // Remove checks-passed label if it exists (we'll re-add it if checks pass) | |
| if (labels.includes('checks-passed')) { | |
| console.log('Removing checks-passed label'); | |
| try { | |
| await github.rest.issues.removeLabel({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| name: 'checks-passed' | |
| }); | |
| } catch (error) { | |
| console.log('Failed to remove checks-passed label:', error.message); | |
| console.log('This may be due to insufficient permissions.'); | |
| } | |
| } | |
| core.setOutput('should-run', hasRunChecks.toString()); | |
| if (!hasRunChecks) { | |
| console.log('Skipping checks - run-checks label not found'); | |
| } | |
| build: | |
| runs-on: ubuntu-latest | |
| needs: [check-label] | |
| if: github.event_name == 'push' || needs.check-label.outputs.should-run == 'true' | |
| strategy: | |
| matrix: | |
| go-version: ["1.24.4"] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: ./.github/actions/go-setup | |
| with: | |
| go-version: ${{ matrix.go-version }} | |
| - name: Cache build artifacts | |
| uses: actions/cache@v4 | |
| with: | |
| path: build/ | |
| key: build-${{ runner.os }}-${{ github.sha }}-${{ hashFiles('**/*.go') }} | |
| restore-keys: | | |
| build-${{ runner.os }}-${{ github.sha }}- | |
| build-${{ runner.os }}- | |
| - name: Verify dependencies | |
| run: task mod:verify | |
| - name: Check formatting | |
| run: task fmt:check | |
| - name: Build | |
| run: task build | |
| test: | |
| name: Unit Tests | |
| runs-on: ubuntu-latest | |
| needs: [build, lint] | |
| if: always() && !cancelled() && (needs.build.result == 'success' && needs.lint.result == 'success') | |
| strategy: | |
| matrix: | |
| go-version: ["1.24.4"] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: ./.github/actions/go-setup | |
| with: | |
| go-version: ${{ matrix.go-version }} | |
| - name: Cache test results | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| coverage.out | |
| coverage.html | |
| key: test-coverage-${{ runner.os }}-${{ github.sha }}-${{ hashFiles('**/*.go', '**/*_test.go') }} | |
| restore-keys: | | |
| test-coverage-${{ runner.os }}-${{ github.sha }}- | |
| test-coverage-${{ runner.os }}- | |
| - name: Run tests with coverage | |
| run: task test:coverage | |
| - name: Upload test results | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: test-artifact | |
| path: | | |
| coverage.out | |
| coverage.html | |
| lint: | |
| name: Lint | |
| runs-on: ubuntu-latest | |
| needs: [check-label] | |
| if: github.event_name == 'push' || needs.check-label.outputs.should-run == 'true' | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: ./.github/actions/go-setup | |
| with: | |
| go-version: "${{ env.GOLANG_VERSION }}" | |
| - name: Run golangci-lint | |
| uses: golangci/golangci-lint-action@v8 | |
| with: | |
| version: v2.1.6 | |
| args: --timeout=5m --skip-dirs=docs | |
| vulnerability-check: | |
| name: Vulnerability Check | |
| runs-on: ubuntu-latest | |
| needs: [build, lint] | |
| if: always() && !cancelled() && (needs.build.result == 'success' && needs.lint.result == 'success') | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: ./.github/actions/go-setup | |
| with: | |
| go-version: "${{ env.GOLANG_VERSION }}" | |
| - name: Cache govulncheck | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/go/bin/govulncheck | |
| key: govulncheck-${{ runner.os }}-v1.1.3 | |
| restore-keys: | | |
| govulncheck-${{ runner.os }}- | |
| - name: Install govulncheck | |
| run: | | |
| go install golang.org/x/vuln/cmd/govulncheck@v1.1.3 | |
| - name: Cache vulnerability database | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cache/go-build | |
| key: vuln-db-${{ runner.os }}-${{ hashFiles('go.sum') }} | |
| restore-keys: | | |
| vuln-db-${{ runner.os }}- | |
| - name: Run vulnerability check with docs | |
| run: task vuln:check | |
| quality-check-summary: | |
| name: Quality Check Summary | |
| runs-on: ubuntu-latest | |
| needs: [check-label, build, test, lint, vulnerability-check] | |
| if: always() && github.event_name == 'pull_request' && needs.check-label.outputs.should-run == 'true' | |
| steps: | |
| - name: Post Quality Check Results | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| }); | |
| // Find all bot comments with the quality check header | |
| const botComments = comments.filter(comment => | |
| comment.user.type === 'Bot' && | |
| comment.body.includes('## 💎 Quality Check') | |
| ); | |
| // Delete all previous quality check comments | |
| for (const comment of botComments) { | |
| await github.rest.issues.deleteComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: comment.id, | |
| }); | |
| } | |
| const buildStatus = '${{ needs.build.result }}'; | |
| const testStatus = '${{ needs.test.result }}'; | |
| const lintStatus = '${{ needs.lint.result }}'; | |
| const vulnStatus = '${{ needs.vulnerability-check.result }}'; | |
| console.log('Job statuses:', { buildStatus, testStatus, lintStatus, vulnStatus }); | |
| let statusEmoji = '✅'; | |
| let statusText = 'All quality checks passed!'; | |
| const requiredChecks = [buildStatus, testStatus, lintStatus, vulnStatus]; | |
| if (requiredChecks.some(status => status !== 'success')) { | |
| statusEmoji = '❌'; | |
| statusText = 'Some quality checks failed.'; | |
| } | |
| const body = `## 💎 Environment Manager Quality Check | |
| ${statusEmoji} **Status**: ${statusText} | |
| ### Quality Summary | |
| | Check | Status | | |
| |-------|--------| | |
| | **Build** | ${buildStatus === 'success' ? '✅ Application builds successfully' : '❌ Build failures'} | | |
| | **Unit Tests** | ${testStatus === 'success' ? '✅ All tests pass with race detection' : '❌ Test failures'} | | |
| | **Code Quality** | ${lintStatus === 'success' ? '✅ golangci-lint clean' : '❌ Linting issues'} | | |
| | **Security** | ${vulnStatus === 'success' ? '✅ No known vulnerabilities' : '❌ Vulnerabilities detected'} | | |
| ${statusEmoji === '✅' ? '### 🎉 Your code is ready to deploy!' : '### ⚠️ Please address the failing checks above.'} | |
| <details> | |
| <summary>View detailed results</summary> | |
| - **Build**: ${buildStatus} | |
| - **Unit Tests**: ${testStatus} | |
| - **Lint**: ${lintStatus} | |
| - **Vulnerability Check**: ${vulnStatus} | |
| </details> | |
| --- | |
| <sub>🤖 This comment is automatically updated on each workflow run.</sub>`; | |
| // Always create a new comment | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: body | |
| }); | |
| // Add checks-passed label if all enabled checks succeeded | |
| if (statusEmoji === '✅') { | |
| console.log('All enabled checks passed - adding checks-passed label'); | |
| try { | |
| await github.rest.issues.addLabels({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| labels: ['checks-passed'] | |
| }); | |
| } catch (error) { | |
| console.log('Failed to add checks-passed label:', error.message); | |
| console.log('This may be due to insufficient permissions. The label needs to be added manually.'); | |
| } | |
| } |