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 # Generate SBOM using the specific image digest syft ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }} -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 }}" # Try method 1: Use oras attach with proper media type echo "Attempting to attach SBOM with oras attach..." oras attach ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${IMAGE_DIGEST} \ --artifact-type application/vnd.cyclonedx+json \ --annotation "org.opencontainers.artifact.description=SBOM for ${{ env.IMAGE_NAME }}" \ sbom.spdx.json:application/spdx+json || echo "oras attach failed, trying alternative method..." # Alternative method: Push as separate artifact with clear naming echo "Uploading SBOM as separate artifact..." oras push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sbom-${{ github.sha }} \ --artifact-type application/spdx+json \ --annotation "org.opencontainers.artifact.description=SBOM for ${{ env.IMAGE_NAME }}@${IMAGE_DIGEST}" \ --annotation "org.opencontainers.artifact.source=${IMAGE_DIGEST}" \ sbom.spdx.json:application/spdx+json echo "SBOM uploaded successfully" # 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 run: | echo "${{ secrets.HARBOR_TOKEN }}" | docker login ${{ env.REGISTRY }} -u '${{ secrets.HARBOR_USERNAME }}' --password-stdin - name: Install Trivy run: | sudo apt-get update sudo apt-get install -y wget # Get the latest Trivy version dynamically TRIVY_VERSION=$(curl -s "https://api.github.com/repos/aquasecurity/trivy/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/' | sed 's/v//') wget https://github.com/aquasecurity/trivy/releases/latest/download/trivy_${TRIVY_VERSION}_Linux-64bit.deb sudo dpkg -i trivy_${TRIVY_VERSION}_Linux-64bit.deb - name: Run Trivy scan (SARIF) run: | trivy image --format sarif --output trivy-results.sarif \ ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ needs.build.outputs.image-digest }} - name: Run Trivy scan (JSON) run: | trivy image --format json --output trivy-results.json \ ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ needs.build.outputs.image-digest }} - name: Install ORAS run: | 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/ - name: Attach scan results to Harbor image run: | IMAGE_DIGEST="${{ needs.build.outputs.image-digest }}" # Attach SARIF results oras attach ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${IMAGE_DIGEST} \ --artifact-type application/sarif+json \ --annotation "org.opencontainers.artifact.description=Trivy SARIF scan results" \ trivy-results.sarif:application/sarif+json # Attach JSON results oras attach ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${IMAGE_DIGEST} \ --artifact-type application/json \ --annotation "scan.type=vulnerability" \ --annotation "scan.tool=trivy" \ --annotation "org.opencontainers.artifact.description=Trivy JSON scan results" \ trivy-results.json:application/json echo "Scan results attached successfully" - name: Upload scan artifacts (backup) uses: actions/upload-artifact@v3 with: name: trivy-scan-results path: | trivy-results.sarif trivy-results.json - name: Check for HIGH/CRITICAL vulnerabilities run: | trivy image \ --format json \ --output trivy-critical.json \ --severity HIGH,CRITICAL \ --exit-code 1 \ ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ needs.build.outputs.image-digest }} # # 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