refactor: extract scheduled build into reusable workflow #579
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
| on: | |
| push: | |
| branches: | |
| - main | |
| - release/** | |
| - develop | |
| - feature/** | |
| pull_request: | |
| types: [opened, synchronize, reopened] | |
| branches: | |
| - main | |
| - release/** | |
| - develop | |
| - feature/** | |
| merge_group: | |
| workflow_call: | |
| inputs: | |
| ref: | |
| description: 'Git ref to checkout (branch, tag, or SHA)' | |
| required: false | |
| type: string | |
| skip_code_scans: | |
| description: 'Skip CodeQL and Trivy security scans' | |
| required: false | |
| default: false | |
| type: boolean | |
| skip_linkcheck: | |
| description: 'Skip website link checker' | |
| required: false | |
| default: false | |
| type: boolean | |
| linkcheck_fail_on_error: | |
| description: 'a boolean flag that determines if bad links found by the link checker fail fast and stop a complete build' | |
| required: false | |
| default: true | |
| type: boolean | |
| linkcheck_create_issue: | |
| description: 'create new GitHub issue if broken links found' | |
| required: false | |
| default: false | |
| type: boolean | |
| workflow_dispatch: | |
| inputs: | |
| ref: | |
| description: 'Git ref to checkout (branch, tag, or SHA)' | |
| required: false | |
| type: string | |
| skip_code_scans: | |
| description: 'Skip CodeQL and Trivy security scans' | |
| required: false | |
| default: false | |
| type: boolean | |
| skip_linkcheck: | |
| description: 'Skip website link checker' | |
| required: false | |
| default: false | |
| type: boolean | |
| linkcheck_fail_on_error: | |
| description: 'a boolean flag that determines if bad links found by the link checker fail fast and stop a complete build' | |
| required: false | |
| default: true | |
| type: boolean | |
| linkcheck_create_issue: | |
| description: 'create new GitHub issue if broken links found' | |
| required: false | |
| default: false | |
| type: boolean | |
| name: Build and Test | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| INPUT_FAIL_ON_ERROR: ${{ github.event.inputs.linkcheck_fail_on_error || 'true' }} | |
| INPUT_ISSUE_ON_ERROR: ${{ github.event.inputs.linkcheck_create_issue || 'false' }} | |
| MAVEN_VERSION: 3.9.8 | |
| JAVA_DISTRO: 'temurin' | |
| JAVA_VERSION_FILE: .java-version | |
| # Post Maven artifacts to the artifact repo if the branch is 'develop' or 'release/*'. This avoids publishing artifacts for pull requests | |
| COMMIT_MAVEN_ARTIFACTS: ${{ (github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/heads/release/')) && github.repository_owner == 'metaschema-framework' }} | |
| # Upload security scan SARIF results for main, develop, release/* branches, or PRs targeting these branches. | |
| UPLOAD_SCAN_SARIF: ${{ (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/heads/release/')) || (github.event_name == 'pull_request' && (github.base_ref == 'main' || github.base_ref == 'develop' || startsWith(github.base_ref, 'release/'))) }} | |
| jobs: | |
| build-code: | |
| name: Code | |
| runs-on: ubuntu-24.04 | |
| permissions: | |
| actions: read | |
| contents: read | |
| security-events: write | |
| steps: | |
| - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 | |
| with: | |
| ref: ${{ inputs.ref || github.ref }} | |
| submodules: recursive | |
| filter: tree:0 | |
| - name: Checkout maven2 branch | |
| if: env.COMMIT_MAVEN_ARTIFACTS == 'true' | |
| uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 | |
| with: | |
| path: maven2 | |
| ref: main | |
| repository: metaschema-framework/maven2 | |
| token: ${{ secrets.ACCESS_TOKEN }} | |
| fetch-depth: 2 | |
| persist-credentials: true | |
| # ------------------------- | |
| # Java Environment Setup | |
| # ------------------------- | |
| - name: Set up Maven | |
| uses: stCarolas/setup-maven@d6af6abeda15e98926a57b5aa970a96bb37f97d1 | |
| with: | |
| maven-version: ${{ env.MAVEN_VERSION }} | |
| - name: Set up JDK | |
| uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e | |
| with: | |
| java-version-file: ${{ env.JAVA_VERSION_FILE }} | |
| distribution: ${{ env.JAVA_DISTRO }} | |
| cache: 'maven' | |
| - name: Initialize CodeQL | |
| if: ${{ !inputs.skip_code_scans }} | |
| uses: github/codeql-action/init@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 | |
| with: | |
| languages: java | |
| # ------------------------- | |
| # Maven Build | |
| # ------------------------- | |
| - name: Build and Test Code | |
| run: | | |
| mvn -B -e -Prelease -Psnapshots -DaltDeploymentRepository=repo-snapshot::file://${GITHUB_WORKSPACE}/maven2/ -DaltSnapshotDeploymentRepository=repo-snapshot::file://${GITHUB_WORKSPACE}/maven2/ -DrepositoryId=repo-snapshot deploy | |
| - name: Deploy Artifacts | |
| if: env.COMMIT_MAVEN_ARTIFACTS == 'true' | |
| run: | | |
| MVN_COORDS=$(echo '${project.groupId}:${project.artifactId}:${project.version}' | mvn -N -q -DforceStdout help:evaluate) | |
| cd maven2 | |
| echo "Configuring git identity" | |
| git config user.name "GitHub Action" | |
| git config user.email "action@github.com" | |
| echo "Comitting artifacts" | |
| git add -A | |
| git commit -m "[CI SKIP] Deploying artifacts for $MVN_COORDS." | |
| echo "Syncing with latest" | |
| git pull -r -s ours | |
| echo "Pushing changes" | |
| git push --force-with-lease | |
| - name: Perform CodeQL Analysis | |
| if: ${{ !inputs.skip_code_scans }} | |
| uses: github/codeql-action/analyze@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 | |
| with: | |
| upload: 'never' | |
| output: codeql-results | |
| - name: CodeQL Summary | |
| if: ${{ !inputs.skip_code_scans }} | |
| run: | | |
| echo "## CodeQL Security Scan Results" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| if [ -d "codeql-results" ]; then | |
| for sarif in codeql-results/*.sarif; do | |
| if [ -f "$sarif" ]; then | |
| FILENAME=$(basename "$sarif" .sarif) | |
| RESULTS=$(jq -r '.runs[0].results | length' "$sarif" 2>/dev/null || echo "0") | |
| # Count rules from driver and all extensions | |
| DRIVER_RULES=$(jq -r '.runs[0].tool.driver.rules // [] | length' "$sarif" 2>/dev/null || echo "0") | |
| EXT_RULES=$(jq -r '[.runs[0].tool.extensions[]?.rules // [] | length] | add // 0' "$sarif" 2>/dev/null || echo "0") | |
| RULES=$((DRIVER_RULES + EXT_RULES)) | |
| echo "**Language:** $FILENAME" >> $GITHUB_STEP_SUMMARY | |
| echo "- Results found: $RESULTS" >> $GITHUB_STEP_SUMMARY | |
| echo "- Rules checked: $RULES" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| done | |
| else | |
| echo "No CodeQL results directory found." >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| if [ "${{ env.UPLOAD_SCAN_SARIF }}" == "true" ]; then | |
| echo ":white_check_mark: Results uploaded to GitHub Security tab" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo ":information_source: Results not uploaded (branch/PR not targeting develop or release)" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| # ------------------------- | |
| # Trivy Security Scan | |
| # ------------------------- | |
| - name: Run Trivy security scanner | |
| if: ${{ !inputs.skip_code_scans }} | |
| uses: aquasecurity/trivy-action@b6643a29fecd7f34b3597bc6acb0a98b03d33ff8 | |
| with: | |
| scan-type: 'fs' | |
| scan-ref: '.' | |
| scanners: 'vuln,secret,misconfig' | |
| format: 'sarif' | |
| output: 'trivy-results.sarif' | |
| severity: 'CRITICAL,HIGH,MEDIUM,LOW,UNKNOWN' | |
| - name: Trivy Summary | |
| if: ${{ !inputs.skip_code_scans }} | |
| run: | | |
| echo "## Trivy Security Scan Results" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| if [ -f "trivy-results.sarif" ]; then | |
| TOTAL=$(jq -r '.runs[0].results | length' trivy-results.sarif 2>/dev/null || echo "0") | |
| # Trivy SARIF level mapping: error=CRITICAL, warning=HIGH, note=MEDIUM/LOW | |
| CRITICAL=$(jq -r '[.runs[0].results[] | select(.level == "error")] | length' trivy-results.sarif 2>/dev/null || echo "0") | |
| HIGH=$(jq -r '[.runs[0].results[] | select(.level == "warning")] | length' trivy-results.sarif 2>/dev/null || echo "0") | |
| MEDIUM_LOW=$(jq -r '[.runs[0].results[] | select(.level == "note")] | length' trivy-results.sarif 2>/dev/null || echo "0") | |
| echo "| Severity | Count |" >> $GITHUB_STEP_SUMMARY | |
| echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY | |
| echo "| :red_circle: Critical | $CRITICAL |" >> $GITHUB_STEP_SUMMARY | |
| echo "| :orange_circle: High | $HIGH |" >> $GITHUB_STEP_SUMMARY | |
| echo "| :yellow_circle: Medium/Low | $MEDIUM_LOW |" >> $GITHUB_STEP_SUMMARY | |
| echo "| **Total** | **$TOTAL** |" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "No Trivy results file found." >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| if [ "${{ env.UPLOAD_SCAN_SARIF }}" == "true" ]; then | |
| echo ":white_check_mark: Results uploaded to GitHub Security tab" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo ":information_source: Results not uploaded (branch/PR not targeting develop or release)" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| - name: Upload CodeQL scan results to GitHub Security tab | |
| if: ${{ !inputs.skip_code_scans && env.UPLOAD_SCAN_SARIF == 'true' }} | |
| uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 | |
| with: | |
| sarif_file: codeql-results/java.sarif | |
| category: 'codeql' | |
| - name: Upload Trivy scan results to GitHub Security tab | |
| if: ${{ !inputs.skip_code_scans && env.UPLOAD_SCAN_SARIF == 'true' }} | |
| uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 | |
| with: | |
| sarif_file: 'trivy-results.sarif' | |
| category: 'trivy' | |
| build-website: | |
| name: Website | |
| runs-on: ubuntu-24.04 | |
| permissions: | |
| actions: read | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 | |
| with: | |
| ref: ${{ inputs.ref || github.ref }} | |
| submodules: recursive | |
| filter: tree:0 | |
| # ------------------------- | |
| # Java Environment Setup | |
| # ------------------------- | |
| - name: Set up Maven | |
| uses: stCarolas/setup-maven@d6af6abeda15e98926a57b5aa970a96bb37f97d1 | |
| with: | |
| maven-version: ${{ env.MAVEN_VERSION }} | |
| - name: Set up JDK | |
| uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e | |
| with: | |
| java-version-file: ${{ env.JAVA_VERSION_FILE }} | |
| distribution: ${{ env.JAVA_DISTRO }} | |
| cache: 'maven' | |
| # ------------------------- | |
| # Maven Build | |
| # ------------------------- | |
| - name: Build and Test Website | |
| run: | | |
| mvn -B -e -PCI -Prelease install site site:stage -Dmaven.test.skip=true | |
| - name: Zip Artifacts for Upload | |
| run: | | |
| zip ${{ runner.temp }}/website.zip -r target/staging | |
| - name: Upload generated site | |
| uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f | |
| with: | |
| name: website | |
| path: | | |
| ${{ runner.temp }}/website.zip | |
| retention-days: 5 | |
| - id: linkchecker | |
| name: Link Checker | |
| if: ${{ !inputs.skip_linkcheck }} | |
| uses: lycheeverse/lychee-action@a8c4c7cb88f0c7386610c35eb25108e448569cb0 | |
| with: | |
| args: --verbose --no-progress --accept 200,206,429,503 './target/staging/**/*.html' --remap "https://github.com/metaschema-framework/liboscal-java/tree/main/ file://${GITHUB_WORKSPACE}/" --remap "https://liboscal-java.metaschema.dev/ file://${GITHUB_WORKSPACE}/target/staging/" | |
| format: markdown | |
| output: html-link-report.md | |
| debug: true | |
| fail: true | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| continue-on-error: true | |
| - name: Link Checker Summary | |
| if: ${{ !inputs.skip_linkcheck && always() }} | |
| run: | | |
| echo "<details>" >> $GITHUB_STEP_SUMMARY | |
| echo "<summary><h2>Link Checker Results</h2></summary>" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| if [ -f "html-link-report.md" ]; then | |
| # Extract summary stats from the report (lychee markdown format uses "- [Status]") | |
| ERRORS=$(grep -c "^- \[Broken\]" html-link-report.md 2>/dev/null || echo "0") | |
| TIMEOUTS=$(grep -c "^- \[Timeout\]" html-link-report.md 2>/dev/null || echo "0") | |
| if [ "$ERRORS" -gt 0 ]; then | |
| echo ":x: **Found $ERRORS broken link(s)**" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo '```' >> $GITHUB_STEP_SUMMARY | |
| grep "^- \[Broken\]" html-link-report.md >> $GITHUB_STEP_SUMMARY | |
| echo '```' >> $GITHUB_STEP_SUMMARY | |
| elif [ "$TIMEOUTS" -gt 0 ]; then | |
| echo ":warning: **$TIMEOUTS link(s) timed out** (external sites may be slow)" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo ":white_check_mark: **All links valid**" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| else | |
| echo ":warning: No link check report found." >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "</details>" >> $GITHUB_STEP_SUMMARY | |
| - name: Upload link check report | |
| if: ${{ !inputs.skip_linkcheck }} | |
| uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f | |
| with: | |
| name: html-link-report | |
| path: html-link-report.md | |
| retention-days: 5 | |
| - name: Create issue if bad links detected | |
| # Only create issues for actual broken links (exit code 2), not timeouts (exit code 1) | |
| if: ${{ !inputs.skip_linkcheck && !cancelled() && steps.linkchecker.outputs.exit_code == 2 && env.INPUT_ISSUE_ON_ERROR == 'true' }} | |
| uses: peter-evans/create-issue-from-file@fca9117c27cdc29c6c4db3b86c48e4115a786710 | |
| with: | |
| title: Scheduled Check of Website Content Found Bad Hyperlinks | |
| content-filepath: html-link-report.md | |
| labels: | | |
| bug | |
| documentation | |
| - name: Fail on link check error | |
| # Exit codes: 0=success, 1=runtime errors/timeouts, 2=broken links found | |
| # Only fail on actual broken links (exit code 2), not timeouts (exit code 1) | |
| if: ${{ !inputs.skip_linkcheck && !cancelled() && steps.linkchecker.outputs.exit_code == 2 && env.INPUT_FAIL_ON_ERROR == 'true' }} | |
| uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd | |
| with: | |
| script: | | |
| core.setFailed('Link checker detected broken or invalid links, read attached report.') |