[maven-release-plugin] prepare release v7.0.0 #623
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: ${{ inputs.linkcheck_fail_on_error || 'true' }} | |
| INPUT_ISSUE_ON_ERROR: ${{ inputs.linkcheck_create_issue || 'false' }} | |
| # Effective ref for link checker remap: use inputs.ref if provided, else base_ref (PRs) or ref_name (pushes), fallback to develop | |
| EFFECTIVE_REF: ${{ inputs.ref || github.base_ref || github.ref_name || 'develop' }} | |
| 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 | |
| config-file: .github/codeql-config.yml | |
| # ------------------------- | |
| # 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 | |
| # Show details if there are findings | |
| if [ "$RESULTS" -gt 0 ]; then | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "<details>" >> $GITHUB_STEP_SUMMARY | |
| echo "<summary>View $RESULTS finding(s)</summary>" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "| Severity | Sec-Sev | Rule | Location | Message |" >> $GITHUB_STEP_SUMMARY | |
| echo "|----------|---------|------|----------|---------|" >> $GITHUB_STEP_SUMMARY | |
| # Join results with rules to get security-severity (which is on rule definitions, not results) | |
| jq -r ' | |
| (.runs[0].tool.driver.rules // []) as $driver_rules | | |
| ([.runs[0].tool.extensions[]?.rules // []] | add // []) as $ext_rules | | |
| ($driver_rules + $ext_rules | map({(.id): (.properties["security-severity"] // null)}) | add // {}) as $severities | | |
| .runs[0].results[] | | |
| "| \(if .level == "error" then "Error" elif .level == "warning" then "Warning" elif .level == "note" then "Note" else .level end) | \($severities[.ruleId] // "N/A") | \(.ruleId // "unknown") | `\(.locations[0].physicalLocation.artifactLocation.uri // "unknown"):\(.locations[0].physicalLocation.region.startLine // "?")` | \(.message.text | gsub("\n"; " ") | gsub("\\|"; "\\\\|") | .[0:120]) |" | |
| ' "$sarif" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "</details>" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| 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 main, 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' | |
| # Exclude submodule (has its own security scanning in upstream repo) | |
| skip-dirs: 'oscal' | |
| - 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 (from trivy source): CRITICAL+HIGH -> error, MEDIUM -> warning, LOW+UNKNOWN -> note | |
| CRITICAL_HIGH=$(jq -r '[.runs[0].results[] | select(.level == "error")] | length' trivy-results.sarif 2>/dev/null || echo "0") | |
| MEDIUM=$(jq -r '[.runs[0].results[] | select(.level == "warning")] | length' trivy-results.sarif 2>/dev/null || echo "0") | |
| 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/High | $CRITICAL_HIGH |" >> $GITHUB_STEP_SUMMARY | |
| echo "| :orange_circle: Medium | $MEDIUM |" >> $GITHUB_STEP_SUMMARY | |
| echo "| :yellow_circle: Low | $LOW |" >> $GITHUB_STEP_SUMMARY | |
| echo "| **Total** | **$TOTAL** |" >> $GITHUB_STEP_SUMMARY | |
| # Show details if there are findings | |
| if [ "$TOTAL" -gt 0 ]; then | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "<details>" >> $GITHUB_STEP_SUMMARY | |
| echo "<summary>View $TOTAL finding(s)</summary>" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "| Severity | Rule | Location | Message |" >> $GITHUB_STEP_SUMMARY | |
| echo "|----------|------|----------|---------|" >> $GITHUB_STEP_SUMMARY | |
| jq -r '.runs[0].results[] | "| \(if .level == "error" then "Critical/High" elif .level == "warning" then "Medium" elif .level == "note" then "Low" else .level end) | \(.ruleId // "unknown") | `\(.locations[0].physicalLocation.artifactLocation.uri // "unknown"):\(.locations[0].physicalLocation.region.startLine // "?")` | \(.message.text | gsub("\n"; " ") | gsub("\\|"; "\\\\|") | .[0:120]) |"' trivy-results.sarif >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "</details>" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| 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 main, 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 | |
| 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' | |
| - name: Fail on critical/high security findings | |
| if: ${{ !inputs.skip_code_scans }} | |
| run: | | |
| FAILED=false | |
| # Check CodeQL for error level (critical/high) findings | |
| if [ -d "codeql-results" ]; then | |
| for sarif in codeql-results/*.sarif; do | |
| if [ -f "$sarif" ]; then | |
| # Check for error level OR security-severity >= 7.0 (high/critical) | |
| # Note: security-severity is on rule definitions, not results, so we join via ruleId | |
| CODEQL_CRITICAL=$(jq -r ' | |
| # Collect security-severity from driver and extension rules | |
| (.runs[0].tool.driver.rules // []) as $driver_rules | | |
| ([.runs[0].tool.extensions[]?.rules // []] | add // []) as $ext_rules | | |
| ($driver_rules + $ext_rules | map({(.id): (.properties["security-severity"] // "0")}) | add // {}) as $severities | | |
| [.runs[0].results[] | select( | |
| .level == "error" or | |
| (($severities[.ruleId] // "0") | tonumber >= 7.0) | |
| )] | length | |
| ' "$sarif" 2>/dev/null || echo "0") | |
| if [ "$CODEQL_CRITICAL" -gt 0 ]; then | |
| echo "::error::CodeQL found $CODEQL_CRITICAL critical/high severity issue(s)" | |
| FAILED=true | |
| fi | |
| fi | |
| done | |
| fi | |
| # Check Trivy for critical/high (error) and medium (warning) findings | |
| # Note: Trivy SARIF mapping: CRITICAL+HIGH -> error, MEDIUM -> warning, LOW -> note | |
| if [ -f "trivy-results.sarif" ]; then | |
| TRIVY_CRITICAL_HIGH=$(jq -r '[.runs[0].results[] | select(.level == "error")] | length' trivy-results.sarif 2>/dev/null || echo "0") | |
| TRIVY_MEDIUM=$(jq -r '[.runs[0].results[] | select(.level == "warning")] | length' trivy-results.sarif 2>/dev/null || echo "0") | |
| if [ "$TRIVY_CRITICAL_HIGH" -gt 0 ]; then | |
| echo "::error::Trivy found $TRIVY_CRITICAL_HIGH critical/high severity issue(s)" | |
| FAILED=true | |
| fi | |
| if [ "$TRIVY_MEDIUM" -gt 0 ]; then | |
| echo "::warning::Trivy found $TRIVY_MEDIUM medium severity issue(s)" | |
| fi | |
| fi | |
| if [ "$FAILED" = true ]; then | |
| echo "::error::Build failed due to critical/high security findings. See summaries above for details." | |
| exit 1 | |
| fi | |
| 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/${{ env.EFFECTIVE_REF }}/ 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 "## Link Checker Results" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| if [ -f "html-link-report.md" ]; then | |
| # Extract summary stats from the report | |
| # Lychee uses [ERROR], [4xx], [5xx] for broken links and [TIMEOUT] for timeouts | |
| # Note: grep -c exits 1 when no matches, so we capture output first then handle exit code | |
| ERRORS=$(grep -cE "^\[ERROR\]|^\[[45][0-9]{2}\]" html-link-report.md 2>/dev/null) || ERRORS=0 | |
| TIMEOUTS=$(grep -c "^\[TIMEOUT\]" html-link-report.md 2>/dev/null) || TIMEOUTS=0 | |
| if [ "$ERRORS" -gt 0 ]; then | |
| echo ":x: **Found $ERRORS broken link(s)**" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "<details>" >> $GITHUB_STEP_SUMMARY | |
| echo "<summary>View broken links</summary>" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo '```' >> $GITHUB_STEP_SUMMARY | |
| grep -E "^\[ERROR\]|^\[[45][0-9]{2}\]" html-link-report.md >> $GITHUB_STEP_SUMMARY | |
| echo '```' >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "</details>" >> $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 | |
| - 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 | |
| # Check report for actual broken links (ERROR, 4xx, 5xx), not timeouts | |
| if: ${{ !inputs.skip_linkcheck && !cancelled() && env.INPUT_FAIL_ON_ERROR == 'true' }} | |
| run: | | |
| if [ -f "html-link-report.md" ]; then | |
| ERRORS=$(grep -cE "^\[ERROR\]|^\[[45][0-9]{2}\]" html-link-report.md 2>/dev/null) || ERRORS=0 | |
| if [ "$ERRORS" -gt 0 ]; then | |
| echo "::error::Link checker found $ERRORS broken link(s). See report for details." | |
| exit 1 | |
| fi | |
| fi |