name: CI/CD Pipeline on: push: branches: [main, master] tags: ['v*'] pull_request: branches: [main, master] env: IMAGE_NAME: rust-user-api REGISTRY: ${{ vars.REGISTRY || 'docker.io' }} jobs: test: name: Run Tests runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Rust run: | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y source ~/.cargo/env rustup component add rustfmt clippy echo "$HOME/.cargo/bin" >> $GITHUB_PATH - name: Cache dependencies uses: actions/cache@v3 with: path: | ~/.cargo/registry ~/.cargo/git target key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Check formatting run: | source ~/.cargo/env cargo fmt -- --check - name: Run clippy run: | source ~/.cargo/env cargo clippy -- -D warnings - name: Run tests run: | source ~/.cargo/env cargo test --verbose security-scan: name: Security Scan runs-on: ubuntu-latest needs: test steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Rust run: | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y source ~/.cargo/env - name: Install and run cargo-audit run: | source ~/.cargo/env cargo install cargo-audit cargo audit build: name: Build Docker Image runs-on: ubuntu-latest needs: [test, security-scan] if: github.event_name == 'push' outputs: image: ${{ steps.meta.outputs.tags }} steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Container Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ secrets.REGISTRY_USER }} password: ${{ secrets.REGISTRY_PASS }} - name: Build metadata id: meta run: | TAGS="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest" if [[ "${{ github.ref }}" == refs/tags/* ]]; then VERSION=${GITHUB_REF#refs/tags/} TAGS="$TAGS,${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:$VERSION" else TAGS="$TAGS,${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}" fi echo "tags=$TAGS" >> $GITHUB_OUTPUT - name: Build and push Docker image uses: docker/build-push-action@v5 with: context: . platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} cache-from: type=gha cache-to: type=gha,mode=max deploy-staging: name: Deploy to Staging runs-on: ubuntu-latest needs: build if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' environment: staging steps: - name: Deploy to staging run: | echo "部署到测试环境..." echo "镜像: ${{ needs.build.outputs.image }}" # 这里添加实际的部署逻辑 deploy-production: name: Deploy to Production runs-on: ubuntu-latest needs: [build, deploy-staging] if: startsWith(github.ref, 'refs/tags/v') environment: production steps: - name: Setup SSH run: | mkdir -p ~/.ssh echo "${{ secrets.DEPLOY_KEY }}" > ~/.ssh/id_rsa chmod 600 ~/.ssh/id_rsa ssh-keyscan -H ${{ secrets.DEPLOY_HOST }} >> ~/.ssh/known_hosts - name: Deploy to production run: | ssh ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }} << 'EOF' cd /opt/rust-api # 备份当前数据库 if [ -f docker-compose.prod.yml ]; then docker-compose -f docker-compose.prod.yml exec -T rust-user-api \ sqlite3 /app/data/production.db ".backup /app/data/backup-$(date +%Y%m%d-%H%M%S).db" || true fi # 拉取最新镜像 docker pull ${{ needs.build.outputs.image }} # 更新docker-compose文件中的镜像标签 if [ -f docker-compose.prod.yml ]; then sed -i 's|image: .*rust-user-api.*|image: ${{ needs.build.outputs.image }}|' docker-compose.prod.yml docker-compose -f docker-compose.prod.yml up -d else # 如果没有 prod 配置文件,使用默认配置 docker-compose down || true docker-compose up -d fi # 等待服务启动 sleep 30 # 健康检查 if curl -f http://localhost:8080/health || curl -f http://localhost/health; then echo "✅ 部署成功!" else echo "❌ 部署失败,开始回滚..." docker-compose down exit 1 fi EOF - name: Notify deployment success if: success() run: | echo "🎉 生产环境部署成功!" echo "版本: ${{ github.ref_name }}" echo "镜像: ${{ needs.build.outputs.image }}" notify: name: Notify Results runs-on: ubuntu-latest needs: [test, security-scan, build, deploy-production] if: always() steps: - name: Notify on success if: ${{ needs.deploy-production.result == 'success' }} run: | echo "✅ 部署流水线执行成功" # 这里可以添加通知逻辑,如发送邮件、Slack消息等 - name: Notify on failure if: ${{ contains(needs.*.result, 'failure') }} run: | echo "❌ 部署流水线执行失败" # 这里可以添加失败通知逻辑