fix: update remaining docker images and fix hadolint errors #1
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
| # Copyright (c) 2026 SnowdreamTech. All rights reserved. | |
| # Licensed under the MIT License. See LICENSE file in the project root for full license information. | |
| --- | |
| # CD (Continuous Deployment) | |
| # Purpose: Unified release pipeline covering Project Verification and Automated Release Orchestration. | |
| # Trigger: Every push to the default branch (main). | |
| # Design: | |
| # - Sequential execution: Verify -> Release Please. | |
| # - Atomic: Release only happens if verification passes. | |
| # - Secure: Eliminates workflow_run risks by using internal job dependencies. | |
| name: "π Continuous Delivery" | |
| "on": | |
| push: | |
| branches: | |
| - "main" | |
| - "dev" | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| env: | |
| UNIRTM_LOCKED: 1 | |
| # Opt into Node.js 24 now ahead of GitHub's June 16, 2026 forced migration. | |
| # See: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/ | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "1" | |
| jobs: | |
| # 1. Project Verification Stage (Verify) | |
| verify: | |
| name: "π Pre-flight Integrity Check (${{ matrix.os }})" | |
| runs-on: ${{ matrix.os }} | |
| environment: development | |
| concurrency: | |
| group: verify-${{ github.workflow }}-${{ matrix.os }}-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| permissions: | |
| contents: read | |
| statuses: write | |
| security-events: write | |
| timeout-minutes: 50 | |
| env: | |
| PYTHONUTF8: 1 | |
| STEP_SECURITY_DISABLE: ${{ matrix.os != 'ubuntu-latest' }} | |
| steps: | |
| - name: "π Harden Runner" | |
| if: matrix.os == 'ubuntu-latest' | |
| uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 | |
| with: | |
| disable-sudo: true | |
| egress-policy: block | |
| allowed-endpoints: > | |
| api.github.com:443 | |
| raw.githubusercontent.com:443 | |
| objects.githubusercontent.com:443 | |
| pkg-containers.githubusercontent.com:443 | |
| avatars.githubusercontent.com:443 | |
| github.com:443 | |
| packages.microsoft.com:443 | |
| archive.ubuntu.com:80 | |
| archive.ubuntu.com:443 | |
| security.ubuntu.com:80 | |
| security.ubuntu.com:443 | |
| ports.ubuntu.com:80 | |
| ports.ubuntu.com:443 | |
| keyserver.ubuntu.com:80 | |
| keyserver.ubuntu.com:443 | |
| changelogs.ubuntu.com:80 | |
| changelogs.ubuntu.com:443 | |
| deb.debian.org:80 | |
| deb.debian.org:443 | |
| security.debian.org:80 | |
| security.debian.org:443 | |
| snapshot.debian.org:80 | |
| snapshot.debian.org:443 | |
| dl.rockylinux.org:443 | |
| mirrors.rockylinux.org:443 | |
| mirror.centos.org:443 | |
| vault.centos.org:443 | |
| isv-data.centos.org:443 | |
| mirrorlist.centos.org:80 | |
| mirrorlist.centos.org:443 | |
| cdn.redhat.com:443 | |
| cdn-ubi.redhat.com:443 | |
| access.redhat.com:443 | |
| sso.redhat.com:443 | |
| dl-cdn.alpinelinux.org:443 | |
| registry.npmjs.org:443 | |
| registry.yarnpkg.com:443 | |
| pypi.org:443 | |
| files.pythonhosted.org:443 | |
| proxy.golang.org:443 | |
| sum.golang.org:443 | |
| index.crates.io:443 | |
| static.rust-lang.org:443 | |
| packagist.org:443 | |
| repo.maven.apache.org:443 | |
| golang.org:443 | |
| pkg.go.dev:443 | |
| dl.google.com:443 | |
| rubygems.org:443 | |
| registry.terraform.io:443 | |
| formulae.brew.sh:443 | |
| repo.yarnpkg.com:443 | |
| ghcr.io:443 | |
| production.cloudflare.docker.com:80 | |
| production.cloudflare.docker.com:443 | |
| registry-1.docker.io:443 | |
| auth.docker.io:443 | |
| docker.io:443 | |
| quay.io:443 | |
| cdn.quay.io:443 | |
| docker-images-prod.s3.us-west-2.amazonaws.com:443 | |
| docker-images-prod.s3.us-east-1.amazonaws.com:443 | |
| docker-images-prod.s3.amazonaws.com:443 | |
| s3.amazonaws.com:443 | |
| s3.us-west-2.amazonaws.com:443 | |
| s3.us-east-1.amazonaws.com:443 | |
| osv-vulnerabilities.storage.googleapis.com:443 | |
| api.osv.dev:443 | |
| get.trivy.dev:443 | |
| aquasecurity.github.io:443 | |
| tuf-repo-cdn.sigstore.dev:443 | |
| oauth2.sigstore.dev:443 | |
| rekor.sigstore.dev:443 | |
| fulcio.sigstore.dev:443 | |
| api.sigstore.dev:443 | |
| - name: "π Checkout Repository Code" | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| with: | |
| persist-credentials: false | |
| fetch-depth: 0 | |
| - name: "β‘ Setup UniRTM" | |
| uses: snowdreamtech/setup-unirtm@cacfb4d739ee46d4a4b2528b02b508df2f560706 # v0.4.0 | |
| with: | |
| unirtm-version: "0.25.0" | |
| install: true | |
| trust: true | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.WORKFLOW_SECRET || secrets.GITHUB_TOKEN }} | |
| - name: "π Validate Commit Convention (Commitlint)" | |
| shell: sh | |
| run: | | |
| unirtm exec -- commitlint --last --verbose | |
| - name: "π§ͺ Execute Full Quality & Safety Verification" | |
| shell: sh | |
| run: | | |
| unirtm run verify | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PYTHONUTF8: 1 | |
| TRIVY_CACHE_DIR: .trivycache | |
| UNIRTM_YES: 1 | |
| # - name: "π§ͺ Execute Shell Tests (Bats)" | |
| # if: matrix.os != 'windows-latest' | |
| # shell: sh | |
| # run: | | |
| # unirtm run test:shell | |
| # env: | |
| # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # UNIRTM_YES: 1 | |
| # | |
| # - name: "π§ͺ Execute PowerShell Tests (Pester)" | |
| # if: matrix.os == 'windows-latest' | |
| # shell: pwsh | |
| # run: | | |
| # unirtm run test:powershell | |
| # env: | |
| # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # UNIRTM_YES: 1 | |
| - name: "β‘ Cache Documentation Links (Lychee)" | |
| if: matrix.os == 'ubuntu-latest' | |
| uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 | |
| with: | |
| path: .lycheecache | |
| key: ${{ runner.os }}-lychee-${{ hashFiles('**/*.md') }} | |
| restore-keys: | | |
| ${{ runner.os }}-lychee- | |
| - name: "π Verify Documentation Links (Lychee)" | |
| uses: lycheeverse/lychee-action@8646ba30535128ac92d33dfc9133794bfdd9b411 # v2.8.0 | |
| if: ${{ always() && matrix.os == 'ubuntu-latest' }} | |
| continue-on-error: true # Don't block CD on link check failures (network issues, rate limits, etc.) | |
| with: | |
| args: --config lychee.toml '**/*.md' | |
| fail: true | |
| env: | |
| # Pass GitHub token to avoid rate limiting and access private repos | |
| GITHUB_TOKEN: ${{ secrets.WORKFLOW_SECRET || secrets.GITHUB_TOKEN }} | |
| - name: "π΅οΈ Detect Vulnerabilities (Trivy FS)" | |
| uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 | |
| if: ${{ always() && matrix.os == 'ubuntu-latest' }} | |
| env: | |
| TRIVY_DB_REPOSITORY: "public.ecr.aws/aquasecurity/trivy-db" | |
| TRIVY_CHECKS_BUNDLE_REPOSITORY: "public.ecr.aws/aquasecurity/trivy-checks" | |
| with: | |
| scan-type: "fs" | |
| ignore-unfixed: true | |
| format: "sarif" | |
| output: "trivy-results.sarif" | |
| severity: "CRITICAL,HIGH,MEDIUM,LOW" | |
| exit-code: "1" | |
| skip-dirs: "unirtm_data,cmd/unirtm_data" | |
| - name: "π€ Upload Security Audit (SARIF)" | |
| uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 | |
| if: always() && matrix.os == 'ubuntu-latest' | |
| with: | |
| sarif_file: "trivy-results.sarif" | |
| # 2. Release Orchestration Stage (Release Please) | |
| release-please: | |
| name: "π Release Orchestration" | |
| needs: [verify] | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| runs-on: ubuntu-latest | |
| environment: internal | |
| concurrency: | |
| group: release-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| statuses: write | |
| security-events: write | |
| timeout-minutes: 50 | |
| steps: | |
| - name: "ποΈ Checkout Repository" | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| - name: "π Orchestrate Release Lifecycle (Release Please)" | |
| id: release | |
| uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0 | |
| with: | |
| token: ${{ secrets.WORKFLOW_SECRET || secrets.GITHUB_TOKEN }} | |
| config-file: .release-please-config.json | |
| manifest-file: .release-please-manifest.json | |
| target-branch: ${{ github.ref_name }} | |
| skip-github-release: true | |
| - name: "ποΈ Checkout Release PR Branch" | |
| if: ${{ steps.release.outputs.pr }} | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| with: | |
| ref: ${{ fromJson(steps.release.outputs.pr).headBranchName }} | |
| persist-credentials: false | |
| - name: "π¦ Setup Node Environment (Syncing)" | |
| if: ${{ steps.release.outputs.pr && hashFiles('package.json') != '' }} | |
| uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: "24" | |
| cache: "" | |
| - name: "π Synchronize Manifests & Lockfiles" | |
| if: ${{ steps.release.outputs.pr && hashFiles('package.json') != '' }} | |
| shell: sh | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| if [ -f "pnpm-lock.yaml" ]; then | |
| pnpm install --no-frozen-lockfile | |
| elif [ -f "yarn.lock" ]; then | |
| yarn install --no-immutable | |
| elif [ -f "package-lock.json" ]; then | |
| npm install --package-lock-only | |
| fi | |
| git add . | |
| git commit --signoff -m "chore(release): synchronize lockfiles for version bump" || echo "No changes to sync" | |
| git push "https://${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" HEAD | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.WORKFLOW_SECRET || secrets.GITHUB_TOKEN }} | |
| - name: "π§Ή Deduplicate CHANGELOGs" | |
| if: ${{ steps.release.outputs.pr }} | |
| shell: sh | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| find . -name "CHANGELOG.md" -type f | while read -r file; do | |
| awk ' | |
| /^## \[/ { | |
| match($0, /\[([^]]+)\]/) | |
| ver = substr($0, RSTART+1, RLENGTH-2) | |
| if (seen[ver] == 1) { | |
| skip = 1 | |
| } else { | |
| seen[ver] = 1 | |
| skip = 0 | |
| } | |
| } | |
| { | |
| if (!skip) { | |
| print $0 | |
| } | |
| } | |
| ' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file" | |
| done | |
| git add . | |
| git commit --signoff -m "chore(release): deduplicate CHANGELOG headers" || echo "No changes to sync" | |
| git push "https://${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" HEAD | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.WORKFLOW_SECRET || secrets.GITHUB_TOKEN }} | |
| - name: "π Synchronize GitHub Releases" | |
| if: "${{ startsWith(github.event.head_commit.message, 'chore(release):') || startsWith(github.event.head_commit.message, 'chore: release') }}" | |
| shell: sh | |
| run: | | |
| for path in $(awk -F'"' '/":/ {print $2}' .release-please-manifest.json); do | |
| if [ "$path" = "." ]; then | |
| file="CHANGELOG.md" | |
| tag_prefix="v" | |
| else | |
| file="${path}/CHANGELOG.md" | |
| comp=$(awk -F'"' -v p="$path" ' | |
| $0 ~ "\"" p "\": \\{" { in_block = 1 } | |
| in_block && $0 ~ /"component":/ { | |
| for(i=1; i<=NF; i++) { | |
| if($i=="component") { print $(i+2); exit } | |
| } | |
| } | |
| in_block && /^ *\}/ { in_block = 0 } | |
| ' .release-please-config.json) | |
| if [ -z "$comp" ] || [ "$comp" = "null" ]; then | |
| comp=$(basename "$path") | |
| fi | |
| tag_prefix="${comp}-v" | |
| fi | |
| if [ ! -f "$file" ]; then continue; fi | |
| ver=$(awk '/^## \[/ { match($0, /\[([^]]+)\]/); print substr($0, RSTART+1, RLENGTH-2); exit }' "$file") | |
| if [ -n "$ver" ]; then | |
| tag="${tag_prefix}${ver}" | |
| awk ' | |
| /^## \[/ { | |
| count++ | |
| if (count == 2) exit | |
| } | |
| { if (count == 1) print $0 } | |
| ' "$file" > "log.tmp.md" | |
| echo "Force updating Git tag $tag to current commit..." | |
| git tag -f "$tag" | |
| git push --force "https://${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" "refs/tags/$tag" || true | |
| echo "Synchronizing GitHub Release for $tag..." | |
| gh release create "$tag" --notes-file "log.tmp.md" 2>/dev/null || \ | |
| gh release edit "$tag" --notes-file "log.tmp.md" 2>/dev/null || \ | |
| echo "Release sync skipped or failed for $tag" | |
| rm -f "log.tmp.md" | |
| fi | |
| done | |
| # Cleanup: skip-github-release prevents release-please from updating PR labels. | |
| # We must manually clear "autorelease: pending" on merged PRs to avoid blocking future releases. | |
| echo "Cleaning up pending release labels..." | |
| merged_prs=$(gh api -X GET search/issues -f q="repo:$GITHUB_REPOSITORY is:pr is:merged label:\"autorelease: pending\"" --jq '.items[].number' 2>/dev/null) || merged_prs="" | |
| for pr in $merged_prs; do | |
| # Validate that $pr is a number to avoid invalid CLI queries | |
| case "$pr" in | |
| ''|*[!0-9]*) continue ;; | |
| esac | |
| echo "Marking PR #$pr as tagged..." | |
| # Use REST API to avoid GraphQL scope errors (read:org) from 'gh pr edit' | |
| gh api -X POST "repos/$GITHUB_REPOSITORY/issues/$pr/labels" -f "labels[]=autorelease: tagged" >/dev/null 2>&1 || true | |
| gh api -X DELETE "repos/$GITHUB_REPOSITORY/issues/$pr/labels/autorelease:%20pending" >/dev/null 2>&1 || true | |
| done | |
| env: | |
| GH_TOKEN: ${{ secrets.WORKFLOW_SECRET || secrets.GITHUB_TOKEN }} |