diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml new file mode 100644 index 0000000..c55df81 --- /dev/null +++ b/.github/actions/build/action.yml @@ -0,0 +1,81 @@ +name: Build +description: Build artifacts for each service and platform. +inputs: + service: + description: Target service name + required: true + platform: + description: Target platform (e.g., linux/amd64) + required: true + environment: + description: Deployment environment (dev or prod) + required: true +runs: + using: composite + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Prepare Go toolchain + if: inputs.service != 'dashboard' + uses: actions/setup-go@v4 + with: + go-version: '1.22' + + - name: Build Go binaries + if: inputs.service != 'dashboard' + shell: bash + run: | + set -euo pipefail + platform="${{ inputs.platform }}" + goos="${platform%%/*}" + goarch="${platform##*/}" + mkdir -p build/${{ inputs.service }}/"${goos}-${goarch}" + declare -a targets + if [[ "${{ inputs.service }}" == "rag-server" ]]; then + targets=("rag-server/cmd/xcontrol-server" "rag-server/cmd/rag-server-cli") + elif [[ "${{ inputs.service }}" == "account" ]]; then + targets=("account/cmd/accountsvc") + else + targets=("./...") + fi + for target in "${targets[@]}"; do + binary_name=$(basename "$target") + GOOS="$goos" GOARCH="$goarch" go build -o build/${{ inputs.service }}/"${goos}-${goarch}"/"${binary_name}" "$target" + done + + - name: Upload Go artifacts + if: inputs.service != 'dashboard' + uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.service }}-${{ inputs.platform }}-${{ inputs.environment }} + path: build/${{ inputs.service }}/ + + - name: Set up Node.js + if: inputs.service == 'dashboard' + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: yarn + cache-dependency-path: dashboard/yarn.lock + + - name: Install dashboard dependencies + if: inputs.service == 'dashboard' + working-directory: dashboard + shell: bash + run: yarn install --frozen-lockfile + + - name: Build dashboard + if: inputs.service == 'dashboard' + working-directory: dashboard + shell: bash + env: + NEXT_PUBLIC_ENV: ${{ inputs.environment }} + run: yarn build + + - name: Upload dashboard build output + if: inputs.service == 'dashboard' + uses: actions/upload-artifact@v4 + with: + name: dashboard-${{ inputs.platform }}-${{ inputs.environment }} + path: dashboard/.next diff --git a/.github/actions/code-quality/action.yml b/.github/actions/code-quality/action.yml new file mode 100644 index 0000000..268588e --- /dev/null +++ b/.github/actions/code-quality/action.yml @@ -0,0 +1,62 @@ +name: Code Quality +description: Run linting and basic quality checks per service/platform/environment matrix entry. +inputs: + service: + description: Target service name + required: true + platform: + description: Target platform (e.g., linux/amd64) + required: true + environment: + description: Deployment environment (dev or prod) + required: true +runs: + using: composite + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install git-secrets + shell: bash + run: | + set -euo pipefail + git clone https://github.com/awslabs/git-secrets.git + sudo make install -C git-secrets + git secrets --install + git secrets --scan + + - name: Set up Go + if: inputs.service != 'dashboard' + uses: actions/setup-go@v4 + with: + go-version: '1.22' + + - name: Go vet + if: inputs.service != 'dashboard' + shell: bash + run: go vet ./... + + - name: Go unit tests (quality gate) + if: inputs.service != 'dashboard' + shell: bash + run: go test ./... + + - name: Set up Node.js + if: inputs.service == 'dashboard' + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: yarn + cache-dependency-path: dashboard/yarn.lock + + - name: Install dashboard dependencies + if: inputs.service == 'dashboard' + working-directory: dashboard + shell: bash + run: yarn install --frozen-lockfile + + - name: Dashboard lint + if: inputs.service == 'dashboard' + working-directory: dashboard + shell: bash + run: yarn lint diff --git a/.github/actions/deploy/action.yml b/.github/actions/deploy/action.yml new file mode 100644 index 0000000..9858e8c --- /dev/null +++ b/.github/actions/deploy/action.yml @@ -0,0 +1,41 @@ +name: Deploy +description: Coordinate deployments per service/environment. +inputs: + service: + description: Target service name + required: true + platform: + description: Target platform (e.g., linux/amd64) + required: true + environment: + description: Deployment environment (dev or prod) + required: true +runs: + using: composite + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Prepare rollout context + id: context + shell: bash + run: | + set -euo pipefail + echo "service=${{ inputs.service }}" >> "$GITHUB_OUTPUT" + echo "environment=${{ inputs.environment }}" >> "$GITHUB_OUTPUT" + echo "platform=${{ inputs.platform }}" >> "$GITHUB_OUTPUT" + + - name: Deploy placeholder + shell: bash + env: + TARGET_ENV: ${{ steps.context.outputs.environment }} + TARGET_SERVICE: ${{ steps.context.outputs.service }} + TARGET_PLATFORM: ${{ steps.context.outputs.platform }} + run: | + echo "Deploying ${TARGET_SERVICE} (${TARGET_PLATFORM}) to ${TARGET_ENV} namespace" + echo "Hook in Helm/kubectl/ArgoCD rollouts here" + + - name: Rollback plan + shell: bash + run: | + echo "Rollback can be re-run per matrix entry by dispatching with allow_deploy=true" diff --git a/.github/actions/security/action.yml b/.github/actions/security/action.yml new file mode 100644 index 0000000..908de76 --- /dev/null +++ b/.github/actions/security/action.yml @@ -0,0 +1,85 @@ +name: Security +description: Security scanning per service/platform/environment. +inputs: + service: + description: Target service name + required: true + platform: + description: Target platform (e.g., linux/amd64) + required: true + environment: + description: Deployment environment (dev or prod) + required: true +runs: + using: composite + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Go + if: inputs.service != 'dashboard' + uses: actions/setup-go@v4 + with: + go-version: '1.22' + + - name: Run golangci-lint + if: inputs.service != 'dashboard' + uses: golangci/golangci-lint-action@v6 + with: + version: latest + args: ./... + + - name: Install gosec + if: inputs.service != 'dashboard' + shell: bash + run: go install github.com/securego/gosec/v2/cmd/gosec@latest + + - name: Run gosec + if: inputs.service != 'dashboard' + shell: bash + run: gosec ./... + + - name: Trivy filesystem scan + if: inputs.service != 'dashboard' + uses: aquasecurity/trivy-action@0.24.0 + with: + scan-type: fs + scan-ref: . + severity: HIGH,CRITICAL + ignore-unfixed: true + format: table + exit-code: "0" + + - name: Set up Node.js + if: inputs.service == 'dashboard' + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: yarn + cache-dependency-path: dashboard/yarn.lock + + - name: Install dashboard dependencies + if: inputs.service == 'dashboard' + working-directory: dashboard + shell: bash + run: yarn install --frozen-lockfile + + - name: Run ESLint + if: inputs.service == 'dashboard' + working-directory: dashboard + shell: bash + run: yarn lint + + - name: Semgrep security rules + if: inputs.service == 'dashboard' + uses: returntocorp/semgrep-action@v1 + with: + config: p/ci + paths: dashboard + + - name: npm audit (production) + if: inputs.service == 'dashboard' + working-directory: dashboard + shell: bash + run: npm audit --production + continue-on-error: true diff --git a/.github/actions/test/action.yml b/.github/actions/test/action.yml new file mode 100644 index 0000000..89faa66 --- /dev/null +++ b/.github/actions/test/action.yml @@ -0,0 +1,61 @@ +name: Test +description: Run service-specific tests. +inputs: + service: + description: Target service name + required: true + platform: + description: Target platform (e.g., linux/amd64) + required: true + environment: + description: Deployment environment (dev or prod) + required: true +runs: + using: composite + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Go + if: inputs.service != 'dashboard' + uses: actions/setup-go@v4 + with: + go-version: '1.22' + + - name: Run Go integration tests + if: inputs.service != 'dashboard' + shell: bash + run: | + set -euo pipefail + go test ./... -run Integration -count=1 + + - name: Set up Node.js + if: inputs.service == 'dashboard' + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: yarn + cache-dependency-path: dashboard/yarn.lock + + - name: Install dashboard dependencies + if: inputs.service == 'dashboard' + working-directory: dashboard + shell: bash + run: yarn install --frozen-lockfile + + - name: Run dashboard unit tests + if: inputs.service == 'dashboard' + working-directory: dashboard + shell: bash + env: + NODE_ENV: ${{ inputs.environment }} + run: yarn test:unit + + - name: Run dashboard e2e tests + if: inputs.service == 'dashboard' + working-directory: dashboard + shell: bash + env: + PORT: 3100 + NODE_ENV: ${{ inputs.environment }} + run: yarn test:e2e diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml deleted file mode 100644 index 1aef787..0000000 --- a/.github/workflows/build-and-release.yml +++ /dev/null @@ -1,114 +0,0 @@ -name: Build and Release - -on: - push: - branches: [main] - pull_request: - branches: [main] - workflow_dispatch: - inputs: - deploy_environment: - description: "Deployment target environment" - type: choice - options: [staging, production] - default: staging - allow_deploy: - description: "Trigger deployment stage when dispatching manually" - type: boolean - default: true - -permissions: - contents: read - packages: write - id-token: write - -jobs: - static-security: - name: 🛡️ Static checks (lint, SAST) - uses: ./.github/workflows/security-check.yml - secrets: inherit - - code-quality: - name: 🧰 Unit tests & code vetting - uses: ./.github/workflows/code-analysis.yml - secrets: inherit - - build-go-matrix: - name: 🎧 Build Go artifacts (multi-arch) - needs: - - static-security - - code-quality - runs-on: ubuntu-latest - strategy: - matrix: - include: - - { goos: linux, goarch: amd64 } - - { goos: linux, goarch: arm64 } - - { goos: darwin, goarch: amd64 } - - { goos: windows, goarch: amd64 } - steps: - - uses: actions/checkout@v4 - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: "1.22" - - name: Run Go tests (single representative target) - if: matrix.goos == 'linux' && matrix.goarch == 'amd64' - run: go test ./... - - name: Build server and CLI - run: | - set -euo pipefail - mkdir -p build - GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} go build -o build/xcontrol-server-${{ matrix.goos }}-${{ matrix.goarch }} ./cmd/xcontrol-server - GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} go build -o build/xcontrol-cli-${{ matrix.goos }}-${{ matrix.goarch }} ./client - - name: Upload artifacts - uses: actions/upload-artifact@v4 - with: - name: xcontrol-${{ matrix.goos }}-${{ matrix.goarch }} - path: | - build/xcontrol-server-${{ matrix.goos }}-${{ matrix.goarch }} - build/xcontrol-cli-${{ matrix.goos }}-${{ matrix.goarch }} - - base-images: - name: 🛢️ Build base images (multi-arch) - needs: build-go-matrix - uses: ./.github/actions/build-images/base.yml - with: - push_images: ${{ github.event_name != 'pull_request' }} - secrets: inherit - - service-images: - name: 🛢️ Build service images (multi-arch + SBOM) - needs: - - build-go-matrix - - base-images - uses: ./.github/actions/build-images/serive.yml - with: - push_images: ${{ github.event_name != 'pull_request' }} - secrets: inherit - - deploy-and-rollback: - name: 🚀 Deploy & rollback hooks - needs: - - base-images - - service-images - if: (github.event_name == 'workflow_dispatch' && inputs.allow_deploy == true) || github.ref == 'refs/heads/main' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Prepare rollout context - id: context - run: | - set -euo pipefail - env_name="${{ github.event.inputs.deploy_environment || 'staging' }}" - echo "environment=${env_name}" >> "$GITHUB_OUTPUT" - - name: Deploy (placeholder) - env: - TARGET_ENV: ${{ steps.context.outputs.environment }} - run: | - echo "Deploying images to ${TARGET_ENV} via existing GitOps/Ansible pipelines" - echo "Add environment-specific rollout commands here" - - name: Rollback plan ready - run: | - echo "Rollback can be triggered by re-running this workflow with allow_deploy=true" - echo "and by pointing deploy_environment to the target to restore" diff --git a/.github/workflows/cms-config.yml b/.github/workflows/cms-config.yml deleted file mode 100644 index bcd4fee..0000000 --- a/.github/workflows/cms-config.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Validate CMS configuration - -on: - pull_request: - paths: - - 'config/cms.json' - - 'config/cms.schema.json' - - 'scripts/validate_cms_config.py' - - '.github/workflows/cms-config.yml' - push: - branches: [main] - paths: - - 'config/cms.json' - - 'config/cms.schema.json' - - 'scripts/validate_cms_config.py' - -jobs: - validate: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - name: Install dependencies - run: python -m pip install --upgrade pip jsonschema - - name: Validate cms.json - run: python scripts/validate_cms_config.py diff --git a/.github/workflows/code-analysis.yml b/.github/workflows/code-analysis.yml deleted file mode 100644 index 3ba44d2..0000000 --- a/.github/workflows/code-analysis.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: Code Analysis - -on: - workflow_call: - pull_request: - branches: [main] - workflow_dispatch: - -jobs: - analyze: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Install git-secrets - run: | - git clone https://github.com/awslabs/git-secrets.git - sudo make install -C git-secrets - git secrets --install - git secrets --scan - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: 1.21 - - name: Vet - run: go vet ./... - - name: Run tests - run: go test ./... - - dashboard-tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: yarn - cache-dependency-path: dashboard/yarn.lock - - name: Install dashboard dependencies - working-directory: dashboard - run: yarn install --frozen-lockfile - - name: Install Playwright browsers - working-directory: dashboard - run: npx playwright install --with-deps chromium - - name: Run unit tests - working-directory: dashboard - run: yarn test:unit - - name: Run end-to-end tests - working-directory: dashboard - env: - PORT: 3100 - run: yarn test:e2e diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml new file mode 100644 index 0000000..9818640 --- /dev/null +++ b/.github/workflows/pipeline.yml @@ -0,0 +1,94 @@ +name: CloudNativeSuite Pipeline + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + inputs: + allow_deploy: + description: "Allow deployment stage to run" + type: boolean + default: true + +permissions: + contents: read + packages: write + id-token: write + +x-matrix: &matrix + platform: [linux/amd64, linux/arm64] + service: [dashboard, rag-server, account] + environment: [dev, prod] + +jobs: + code-quality: + name: "Code quality • ${{ matrix.service }} @ ${{ matrix.platform }} (${{ matrix.environment }})" + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: *matrix + steps: + - uses: ./.github/actions/code-quality + with: + service: ${{ matrix.service }} + platform: ${{ matrix.platform }} + environment: ${{ matrix.environment }} + + build: + name: "Build • ${{ matrix.service }} @ ${{ matrix.platform }} (${{ matrix.environment }})" + runs-on: ubuntu-latest + needs: code-quality + strategy: + fail-fast: false + matrix: *matrix + steps: + - uses: ./.github/actions/build + with: + service: ${{ matrix.service }} + platform: ${{ matrix.platform }} + environment: ${{ matrix.environment }} + + test: + name: "Test • ${{ matrix.service }} @ ${{ matrix.platform }} (${{ matrix.environment }})" + runs-on: ubuntu-latest + needs: build + strategy: + fail-fast: false + matrix: *matrix + steps: + - uses: ./.github/actions/test + with: + service: ${{ matrix.service }} + platform: ${{ matrix.platform }} + environment: ${{ matrix.environment }} + + security: + name: "Security • ${{ matrix.service }} @ ${{ matrix.platform }} (${{ matrix.environment }})" + runs-on: ubuntu-latest + needs: test + strategy: + fail-fast: false + matrix: *matrix + steps: + - uses: ./.github/actions/security + with: + service: ${{ matrix.service }} + platform: ${{ matrix.platform }} + environment: ${{ matrix.environment }} + + deploy: + name: "Deploy • ${{ matrix.service }} (${{ matrix.environment }})" + runs-on: ubuntu-latest + needs: security + if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.allow_deploy == 'true') + strategy: + fail-fast: false + matrix: *matrix + steps: + - uses: ./.github/actions/deploy + with: + service: ${{ matrix.service }} + platform: ${{ matrix.platform }} + environment: ${{ matrix.environment }} diff --git a/.github/workflows/ragbench.yml b/.github/workflows/ragbench.yml deleted file mode 100644 index e7c564f..0000000 --- a/.github/workflows/ragbench.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: RAG Benchmark -on: - pull_request: - push: - branches: [ main, 'release/**' ] - -jobs: - bench: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Setup Go - uses: actions/setup-go@v5 - with: { go-version: '1.22' } - - - name: Run ragbench - env: - API_BASE: ${{ secrets.RAG_API_BASE }} - run: | - cd ragbench - if [ -n "${API_BASE}" ]; then API_FLAG="-api ${API_BASE}"; fi - go run ./cmd/ragbench ${API_FLAG} -in queries.yaml -out report.md - - - name: Upload report artifact - uses: actions/upload-artifact@v4 - with: - name: rag-benchmark-report - path: ragbench/report.md - if-no-files-found: error - - - name: Comment report to PR - if: github.event_name == 'pull_request' - uses: marocchino/sticky-pull-request-comment@v2 - with: - path: ragbench/report.md diff --git a/.github/workflows/require-cherrypick.yml b/.github/workflows/require-cherrypick.yml deleted file mode 100644 index ab627f5..0000000 --- a/.github/workflows/require-cherrypick.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: Require Cherry-pick to release/* -on: - pull_request: - branches: ['release/**'] - types: [opened, synchronize, reopened] - -jobs: - verify: - runs-on: ubuntu-latest - steps: - - name: Checkout PR HEAD with history - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Fetch main - run: git fetch origin main --quiet - - - name: Get PR commit SHAs (base..head) - id: list - run: | - echo "COMMITS=$(git log --format=%H origin/${{ github.base_ref }}..HEAD | tr '\n' ' ')" >> $GITHUB_OUTPUT - - - name: Verify each commit is a cherry-pick from main - run: | - set -euo pipefail - for C in ${{ steps.list.outputs.COMMITS }}; do - MSG="$(git log -1 --pretty=%B "$C")" - echo "Checking $C" - - # 1) 必须含有 -x 生成的 trailer - if ! echo "$MSG" | grep -qiE 'cherry picked from commit [0-9a-f]{7,40}'; then - echo "::error::Commit $C lacks 'cherry picked from commit ' (use 'git cherry-pick -x')." - exit 1 - fi - - # 2) 取出来源 SHA - ORIG=$(printf "%s" "$MSG" | sed -nE 's/.*cherry picked from commit ([0-9a-f]{7,40}).*/\1/ip' | head -n1 | tr -d '[:space:]') - if [ -z "$ORIG" ]; then - echo "::error::Cannot parse original commit SHA from $C message." - exit 1 - fi - - # 3) 来源必须在 main 上(backport/forward-port 均可换成目标分支) - if ! git merge-base --is-ancestor "$ORIG" origin/main; then - echo "::error::Original commit $ORIG not found on origin/main." - exit 1 - fi - done diff --git a/.github/workflows/security-check.yml b/.github/workflows/security-check.yml deleted file mode 100644 index 980e0d7..0000000 --- a/.github/workflows/security-check.yml +++ /dev/null @@ -1,76 +0,0 @@ -name: Security Checks - -on: - workflow_call: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - nextjs-security: - name: nextjs-security - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: yarn - cache-dependency-path: ui/dashboard/yarn.lock - - - name: Install dependencies - working-directory: ui/dashboard - run: yarn install --frozen-lockfile - - - name: Run ESLint - working-directory: ui/dashboard - run: yarn lint - - - name: Run npm audit (production) - working-directory: ui/dashboard - run: npm audit --production - continue-on-error: true - - - name: Run Semgrep security rules - uses: returntocorp/semgrep-action@v1 - with: - config: p/ci - paths: ui/dashboard - - go-security: - name: go-security - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: "1.22" - - - name: Run golangci-lint - uses: golangci/golangci-lint-action@v6 - with: - version: latest - args: ./... - - - name: Install gosec - run: go install github.com/securego/gosec/v2/cmd/gosec@latest - - - name: Run gosec - run: gosec ./... - - - name: Run Trivy filesystem scan - uses: aquasecurity/trivy-action@0.24.0 - with: - scan-type: fs - scan-ref: . - severity: HIGH,CRITICAL - ignore-unfixed: true - format: table - exit-code: "0"