# 供应链 CI:构建两个镜像 → 功能冒烟 → Trivy 高危阻断 → push 到 ACR。 # # 单 job(build-and-scan):镜像在 job 内 build + load,冒烟 + Trivy 过后, # master 推送时再 tag + push 到阿里云 ACR(用 ACR_USERNAME / ACR_PASSWORD # secret)。仓库:registry.example.com/dsh/ name: build on: push: branches: [master] pull_request: workflow_dispatch: permissions: contents: read jobs: build-and-scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 cache: npm - name: Install + typecheck run: | npm ci npm run typecheck - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Build control-plane image uses: docker/build-push-action@v6 with: context: . file: Dockerfile push: false load: true tags: dshs:ci cache-from: type=gha cache-to: type=gha,mode=max - name: Build dsh image uses: docker/build-push-action@v6 with: context: . file: Dockerfile.dsh push: false load: true tags: dsh:ci cache-from: type=gha cache-to: type=gha,mode=max - name: Smoke — control plane (bootstrap-admin + serve) run: | # bootstrap-admin as the non-root image user (uid 65532), default data root. docker run --rm dshs:ci bootstrap-admin \ --username admin --password 'ci-test-pass-123' # Boot the server and publish 3080 so the host can reach it, then probe. docker run --rm -d --name dsh-cp -p 3080:3080 dshs:ci \ --host 0.0.0.0 --port 3080 for i in $(seq 1 30); do curl -fsS http://127.0.0.1:3080/ >/dev/null 2>&1 && break sleep 1 done # On failure, surface the server logs before the step aborts. curl -fsS http://127.0.0.1:3080/ >/dev/null || { docker logs dsh-cp; exit 1; } echo "control plane serving OK" docker logs dsh-cp docker stop dsh-cp - name: Smoke — dsh resolves runtime plugin run: | # A patch referencing dshs/runtime must resolve and load: # the plugin's apply() logs "[dshs-runtime] role=...". # printf (not a here-doc) so the YAML stays at column 0 inside a # literal block scalar. printf '%s\n' \ '- insert:' \ ' - id: dshs-runtime' \ ' name: dshs/runtime' \ > /tmp/patch.yml # Foreground + bounded timeout so an early exit still leaves logs. # DSH_HOME mirrors the production layout (§4.3) so the parent-dir # walk reaches /var/lib/dshs/node_modules; run as root so # dsh can create that tree (the per-user uid is set by the Pod spec # at deploy time, not exercised here). timeout 25 docker run --rm --user 0 \ -v /tmp/patch.yml:/tmp/patch.yml:ro \ -e DSH_HOME=/var/lib/dshs/users/smoke/home \ dsh:ci --profile web --patch /tmp/patch.yml --host 127.0.0.1 --port 8080 \ > /tmp/dsh.log 2>&1 || true cat /tmp/dsh.log grep -q 'dshs-runtime' /tmp/dsh.log echo "runtime plugin resolved OK" - name: Trivy — control plane uses: aquasecurity/trivy-action@v0.36.0 with: image-ref: dshs:ci severity: HIGH,CRITICAL exit-code: 1 # 只阻断「可修复」的高危;收紧为 false 则连不可修复的也拦。 ignore-unfixed: true - name: Trivy — dsh uses: aquasecurity/trivy-action@v0.36.0 with: image-ref: dsh:ci severity: HIGH,CRITICAL exit-code: 1 ignore-unfixed: true # --- push to ACR (master push / manual dispatch on master only) --- - name: Login to ACR if: github.ref == 'refs/heads/master' uses: docker/login-action@v3 with: registry: registry.example.com username: ${{ secrets.ACR_USERNAME }} password: ${{ secrets.ACR_PASSWORD }} - name: Push control plane to ACR if: github.ref == 'refs/heads/master' run: | docker tag dshs:ci \ registry.example.com/dsh/dshs:0.2.0 docker push \ registry.example.com/dsh/dshs:0.2.0 - name: Push dsh to ACR if: github.ref == 'refs/heads/master' run: | docker tag dsh:ci \ registry.example.com/dsh/dsh:0.1.1-rc.2 docker push \ registry.example.com/dsh/dsh:0.1.1-rc.2