name: CI/CD Pipeline - Build, Test, and Deploy on: push: branches: [main, master, develop] pull_request: branches: [main, master] workflow_dispatch: env: NODE_VERSION: '18' REGISTRY: ${{ secrets.HARBOR_REGISTRY }} IMAGE_NAME: infrastructure/monitoring-dashboard jobs: # Job 1: Lint and Test test: name: ๐Ÿงช Test & Lint runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install dependencies run: npm ci - name: Run linting run: npm run lint - name: Run tests run: npm run test:coverage - name: Upload test results uses: actions/upload-artifact@v3 if: always() with: name: test-results path: | coverage/ test-results.xml # Job 2: Security Scan security: name: ๐Ÿ”’ Security Scan runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install dependencies run: npm ci - name: Run security audit run: npm audit --audit-level=high - name: Check for vulnerabilities run: | if npm audit --audit-level=moderate --json | jq '.vulnerabilities | length' | grep -v '^0$'; then echo "Vulnerabilities found!" npm audit --audit-level=moderate exit 1 fi # Job 3: Build and Push Docker Image build: name: ๐Ÿ—๏ธ Build & Push Image runs-on: ubuntu-latest needs: [test, security] if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' outputs: image-tag: ${{ steps.meta.outputs.tags }} image-digest: ${{ steps.build.outputs.digest }} steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Harbor Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ secrets.HARBOR_USERNAME }} password: ${{ secrets.HARBOR_TOKEN }} - name: Extract metadata id: meta uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | type=ref,event=branch type=ref,event=pr type=sha,prefix={{branch}}- type=raw,value=latest,enable={{is_default_branch}} type=raw,value={{date 'YYYYMMDD-HHmmss'}} - name: Build and push Docker image id: build uses: docker/build-push-action@v5 with: context: . platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max - name: Generate SBOM run: | # Install syft curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin echo "${{ secrets.HARBOR_TOKEN }}" echo "${{ env.REGISTRY }}" echo "${{ secrets.HARBOR_USERNAME }}" echo "${{ env.IMAGE_NAME }}" # Login to registry - use the REGISTRY variable for the URL echo "${{ secrets.HARBOR_TOKEN }}" | docker login ${{ env.REGISTRY }} -u '${{ secrets.HARBOR_USERNAME }}' --password-stdin # Generate SBOM using latest tag syft ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest -o spdx-json > sbom.spdx.json # Verify SBOM was created if [ ! -f sbom.spdx.json ]; then echo "Failed to generate SBOM" exit 1 fi echo "SBOM generated successfully" - name: Attach SBOM to Docker image run: | # Install ORAS curl -LO https://github.com/oras-project/oras/releases/download/v1.1.0/oras_1.1.0_linux_amd64.tar.gz tar -xzf oras_1.1.0_linux_amd64.tar.gz sudo mv oras /usr/local/bin/ # Get the image digest from the build step IMAGE_DIGEST="${{ steps.build.outputs.digest }}" # Attach SBOM to the specific image digest oras attach ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${IMAGE_DIGEST} \ --artifact-type application/spdx+json \ sbom.spdx.json:application/spdx+json echo "SBOM attached successfully to image digest: ${IMAGE_DIGEST}" # Job 4: Image Security Scan scan: name: ๐Ÿ›ก๏ธ Image Security Scan runs-on: ubuntu-latest needs: build if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' steps: - name: Login to Harbor Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ secrets.HARBOR_USERNAME }} password: ${{ secrets.HARBOR_TOKEN }} - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@master with: image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} format: 'sarif' output: 'trivy-results.sarif' - name: Generate JSON scan results uses: aquasecurity/trivy-action@master with: image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} format: 'json' output: 'trivy-results.json' - name: Upload scan results artifacts uses: actions/upload-artifact@v3 with: name: trivy-scan-results path: | trivy-results.sarif trivy-results.json - name: Attach scan results to Harbor image run: | # Install ORAS curl -LO https://github.com/oras-project/oras/releases/download/v1.1.0/oras_1.1.0_linux_amd64.tar.gz tar -xzf oras_1.1.0_linux_amd64.tar.gz sudo mv oras /usr/local/bin/ # Get the image digest from the build job IMAGE_DIGEST="${{ needs.build.outputs.digest }}" # Attach SARIF scan results oras attach ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${IMAGE_DIGEST} \ --artifact-type application/sarif+json \ trivy-results.sarif:application/sarif+json # Attach JSON scan results oras attach ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${IMAGE_DIGEST} \ --artifact-type application/json \ trivy-results.json:application/json \ --annotation "scan.type=vulnerability" \ --annotation "scan.tool=trivy" echo "Scan results attached successfully to image digest: ${IMAGE_DIGEST}" - name: Check for HIGH/CRITICAL vulnerabilities uses: aquasecurity/trivy-action@master with: image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} format: 'json' output: 'trivy-critical.json' exit-code: '1' severity: 'HIGH,CRITICAL' # # Job 5: Deploy to Development # deploy-dev: # name: ๐Ÿš€ Deploy to Development # runs-on: ubuntu-latest # needs: [build, scan] # if: github.ref == 'refs/heads/develop' # environment: development # steps: # - name: Deploy to development environment # run: | # echo "๐Ÿš€ Deploying to development environment" # echo "Image: ${{ needs.build.outputs.image-tag }}" # echo "Digest: ${{ needs.build.outputs.image-digest }}" # # Add actual deployment commands here # # For example: kubectl, docker-compose, or API calls # # Job 6: Deploy to Production # deploy-prod: # name: ๐Ÿญ Deploy to Production # runs-on: ubuntu-latest # needs: [build, scan] # if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' # environment: production # steps: # - name: Checkout code # uses: actions/checkout@v4 # - name: Deploy to production # run: | # echo "๐Ÿญ Deploying to production environment" # echo "Image: ${{ needs.build.outputs.image-tag }}" # echo "Digest: ${{ needs.build.outputs.image-digest }}" # # Example deployment script # # In a real scenario, you might: # # 1. SSH to your server # # 2. Pull the new image # # 3. Update docker-compose.yml # # 4. Restart the service # # 5. Run health checks # - name: Health check after deployment # run: | # echo "๐Ÿ” Running post-deployment health checks" # # Add health check commands here # # curl -f http://your-app-url/health || exit 1 # - name: Notify deployment success # run: | # echo "โœ… Deployment completed successfully!" # echo "๐ŸŒ Application URL: https://your-domain.com" # echo "๐Ÿ“Š Monitoring: https://your-domain.com/health/detailed" # Job 7: Cleanup cleanup: name: ๐Ÿงน Cleanup runs-on: ubuntu-latest needs: [deploy-dev, deploy-prod] if: always() steps: - name: Clean up old images run: | echo "๐Ÿงน Cleaning up old container images" # Add cleanup logic here # For example, remove images older than 30 days