Complete CI/CD Pipeline #229
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Complete CI/CD Pipeline | |
| # Comprehensive workflow combining code quality, security, Docker build/publish, and release management | |
| # Optimized to eliminate duplicate builds and utilize best practices from all source workflows | |
| on: | |
| push: | |
| branches: [main, develop] | |
| tags: ['v*.*.*'] | |
| pull_request: | |
| branches: [main, develop] | |
| schedule: | |
| # Daily build at 4:22 AM UTC for security updates | |
| - cron: '22 4 * * *' | |
| workflow_dispatch: | |
| # Default permissions for all jobs | |
| permissions: | |
| contents: read | |
| env: | |
| NODE_VERSION: '20.18' | |
| REGISTRY: ghcr.io | |
| # Fix case sensitivity issue: convert repository name to lowercase | |
| IMAGE_NAME: ${{ github.repository_owner }}/subpilot-app | |
| jobs: | |
| # Job 1: Code Quality, Build & Test | |
| build-and-test: | |
| name: Build, Test & Quality Checks | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| security-events: write | |
| outputs: | |
| build-success: ${{ steps.build-status.outputs.success }} | |
| package-version: ${{ steps.package-version.outputs.version }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'npm' | |
| - name: Extract version from package.json | |
| id: package-version | |
| run: | | |
| VERSION=$(node -p "require('./package.json').version") | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "📦 Package version: $VERSION" | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Validate project configuration | |
| run: | | |
| echo "🔍 Validating project configuration..." | |
| test -f tsconfig.json || { echo "❌ tsconfig.json missing"; exit 1; } | |
| test -f next.config.js || { echo "❌ next.config.js missing"; exit 1; } | |
| test -f package.json || { echo "❌ package.json missing"; exit 1; } | |
| test -f Dockerfile || { echo "❌ Dockerfile missing"; exit 1; } | |
| echo "✅ Project configuration valid" | |
| - name: Run security audit | |
| run: | | |
| echo "🔒 Running npm security audit..." | |
| if npm audit --audit-level=moderate; then | |
| echo "✅ No security vulnerabilities found" | |
| else | |
| echo "⚠️ Security vulnerabilities detected" | |
| npm audit --audit-level=moderate --json | jq '.metadata.vulnerabilities' || true | |
| echo "ℹ️ Consider running 'npm audit fix' to address fixable issues" | |
| fi | |
| continue-on-error: true | |
| - name: Run filesystem security scan | |
| uses: aquasecurity/trivy-action@master | |
| with: | |
| scan-type: 'fs' | |
| scan-ref: '.' | |
| format: 'sarif' | |
| output: 'trivy-fs-results.sarif' | |
| - name: Upload filesystem scan results | |
| uses: github/codeql-action/upload-sarif@v3 | |
| if: always() | |
| with: | |
| sarif_file: 'trivy-fs-results.sarif' | |
| - name: Run code quality checks | |
| env: | |
| SKIP_ENV_VALIDATION: true | |
| NEXTAUTH_SECRET: 'ci-test-secret' | |
| NEXTAUTH_URL: 'http://localhost:3000' | |
| run: | | |
| echo "🔍 Running code quality checks..." | |
| # TypeScript check | |
| if npm run type-check; then | |
| echo "✅ TypeScript compilation successful" | |
| else | |
| echo "❌ TypeScript compilation failed" | |
| exit 1 | |
| fi | |
| # Linting (non-blocking for development) | |
| if npm run lint; then | |
| echo "✅ No linting errors" | |
| else | |
| echo "⚠️ Linting issues found (continuing in development mode)" | |
| echo "🔧 Run 'npm run lint:fix' to auto-fix issues" | |
| fi | |
| # Formatting check (non-blocking for development) | |
| if npm run format:check; then | |
| echo "✅ Code formatting is correct" | |
| else | |
| echo "⚠️ Formatting issues found (continuing in development mode)" | |
| echo "🔧 Run 'npm run format' to fix formatting" | |
| fi | |
| continue-on-error: ${{ github.event_name != 'push' || github.ref != 'refs/heads/main' }} | |
| - name: Restore Next.js cache | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ${{ github.workspace }}/.next/cache | |
| key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }} | |
| restore-keys: | | |
| ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}- | |
| - name: Build application | |
| run: | | |
| echo "🏗️ Building Next.js application..." | |
| export SKIP_ENV_VALIDATION=true | |
| export NODE_ENV=production | |
| export NEXTAUTH_SECRET="ci-test-secret" | |
| export NEXTAUTH_URL="http://localhost:3000" | |
| npm run build:ci | |
| echo "✅ Build completed successfully" | |
| - name: Create build artifacts | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| run: | | |
| echo "📦 Creating build artifacts..." | |
| mkdir -p artifacts | |
| # Create production build archive | |
| tar -czf artifacts/subpilot-${{ github.ref_name }}-build.tar.gz \ | |
| .next public package.json package-lock.json prisma next.config.js tsconfig.json | |
| # Create source code archive | |
| git archive --format=tar.gz --prefix=subpilot-${{ github.ref_name }}/ \ | |
| -o artifacts/subpilot-${{ github.ref_name }}-source.tar.gz HEAD | |
| # Create checksums | |
| cd artifacts && sha256sum *.tar.gz > checksums.sha256 | |
| echo "✅ Artifacts created successfully" | |
| - name: Upload build artifacts | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: release-artifacts | |
| path: artifacts/ | |
| retention-days: 30 | |
| - name: Set build status | |
| id: build-status | |
| run: echo "success=true" >> $GITHUB_OUTPUT | |
| # Job 2: Docker Build, Test & Publish | |
| docker-build-publish: | |
| name: Docker Build & Publish | |
| runs-on: ubuntu-latest | |
| needs: [build-and-test] | |
| if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) || github.event_name == 'schedule' | |
| permissions: | |
| contents: read | |
| packages: write | |
| security-events: write | |
| id-token: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Install cosign for image signing | |
| if: github.event_name != 'pull_request' | |
| uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 #v3.5.0 | |
| with: | |
| cosign-release: 'v2.2.4' | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 | |
| with: | |
| platforms: linux/amd64,linux/arm64 | |
| - name: Log into GitHub Container Registry | |
| uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Extract Docker metadata | |
| id: meta | |
| uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0 | |
| with: | |
| images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} | |
| tags: | | |
| type=ref,event=branch | |
| type=ref,event=pr | |
| type=semver,pattern={{version}} | |
| type=semver,pattern={{major}}.{{minor}} | |
| type=semver,pattern={{major}} | |
| type=sha,enable=${{ github.event_name != 'release' }} | |
| type=raw,value=latest,enable={{is_default_branch}} | |
| labels: | | |
| org.opencontainers.image.title=SubPilot | |
| org.opencontainers.image.description=Your command center for recurring finances | |
| org.opencontainers.image.vendor=SubPilot | |
| org.opencontainers.image.licenses=MIT | |
| maintainer=SubPilot Team | |
| org.opencontainers.image.documentation=https://github.com/doublegate/SubPilot-App | |
| org.opencontainers.image.source=https://github.com/doublegate/SubPilot-App | |
| - name: Build and push Docker image | |
| id: build-and-push | |
| uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0 | |
| with: | |
| context: . | |
| file: ./Dockerfile | |
| # Only build multi-platform for releases, single platform for regular pushes | |
| platforms: ${{ startsWith(github.ref, 'refs/tags/v') && 'linux/amd64,linux/arm64' || 'linux/amd64' }} | |
| push: true | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| build-args: | | |
| SKIP_ENV_VALIDATION=true | |
| NODE_ENV=production | |
| DATABASE_URL=postgresql://placeholder:password@localhost:5432/placeholder | |
| NEXTAUTH_URL=http://localhost:3000 | |
| BUILD_AUTH_TOKEN=placeholder-token-for-build | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| provenance: true | |
| sbom: true | |
| - name: Test Docker image functionality | |
| run: | | |
| echo "🧪 Testing Docker image functionality..." | |
| # Extract the first tag from the metadata output for testing | |
| FIRST_TAG=$(echo "${{ steps.meta.outputs.tags }}" | head -n 1) | |
| echo "Testing with tag: $FIRST_TAG" | |
| # Pull the built image using the first available tag from the build | |
| docker pull "$FIRST_TAG" | |
| # Run container with unique name to avoid conflicts | |
| CONTAINER_NAME="subpilot-test-${{ github.run_id }}" | |
| docker run -d \ | |
| --name "$CONTAINER_NAME" \ | |
| -p 3000:3000 \ | |
| -e SKIP_ENV_VALIDATION=true \ | |
| -e NODE_ENV=production \ | |
| -e DATABASE_URL=postgresql://test:test@localhost:5432/test \ | |
| -e NEXTAUTH_URL=http://localhost:3000 \ | |
| -e NEXTAUTH_SECRET=test-secret-for-docker-test-${{ github.run_id }} \ | |
| -e DOCKER_HEALTH_CHECK_MODE=basic \ | |
| "$FIRST_TAG" | |
| # Wait for container to become healthy with timeout | |
| echo "⏳ Waiting for container to become healthy..." | |
| timeout 120 bash -c " | |
| while true; do | |
| HEALTH=\$(docker inspect --format='{{.State.Health.Status}}' $CONTAINER_NAME 2>/dev/null || echo 'none') | |
| echo \"Health status: \$HEALTH\" | |
| if [[ \"\$HEALTH\" == \"healthy\" ]]; then | |
| break | |
| fi | |
| if [[ \"\$HEALTH\" == \"unhealthy\" ]]; then | |
| echo \"❌ Container became unhealthy\" | |
| docker logs $CONTAINER_NAME | |
| exit 1 | |
| fi | |
| sleep 3 | |
| done | |
| " | |
| # Test health endpoint with retries | |
| echo "🔍 Testing health endpoint..." | |
| sleep 5 | |
| MAX_ATTEMPTS=10 | |
| for i in $(seq 1 $MAX_ATTEMPTS); do | |
| echo "Health check attempt $i/$MAX_ATTEMPTS..." | |
| if curl -f -s http://localhost:3000/api/health > /dev/null; then | |
| echo "✅ Health check passed on attempt $i" | |
| break | |
| fi | |
| if [ $i -eq $MAX_ATTEMPTS ]; then | |
| echo "❌ Health check failed after $MAX_ATTEMPTS attempts" | |
| echo "Container logs:" | |
| docker logs "$CONTAINER_NAME" | |
| exit 1 | |
| fi | |
| sleep 5 | |
| done | |
| echo "✅ All Docker image tests passed successfully!" | |
| # Cleanup | |
| docker stop "$CONTAINER_NAME" || true | |
| docker rm "$CONTAINER_NAME" || true | |
| - name: Run container security scan | |
| uses: aquasecurity/trivy-action@master | |
| with: | |
| image-ref: ${{ fromJSON(steps.meta.outputs.json).tags[0] }} | |
| format: 'sarif' | |
| output: 'trivy-image-results.sarif' | |
| - name: Upload container scan results | |
| uses: github/codeql-action/upload-sarif@v3 | |
| if: always() | |
| with: | |
| sarif_file: 'trivy-image-results.sarif' | |
| - name: Sign published Docker image | |
| env: | |
| TAGS: ${{ steps.meta.outputs.tags }} | |
| DIGEST: ${{ steps.build-and-push.outputs.digest }} | |
| run: | | |
| echo "🔐 Signing Docker image for supply chain security..." | |
| echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST} | |
| echo "✅ Image signing completed" | |
| - name: Export Docker artifacts for releases | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| run: | | |
| echo "🐳 Creating Docker release artifacts..." | |
| mkdir -p docker-artifacts | |
| # Create Docker compose file | |
| cat > docker-artifacts/docker-compose.yml << 'EOF' | |
| version: '3.8' | |
| services: | |
| app: | |
| image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }} | |
| ports: | |
| - "3000:3000" | |
| environment: | |
| - DATABASE_URL=${DATABASE_URL} | |
| - NEXTAUTH_SECRET=${NEXTAUTH_SECRET} | |
| - NEXTAUTH_URL=${NEXTAUTH_URL} | |
| - SKIP_ENV_VALIDATION=true | |
| restart: unless-stopped | |
| healthcheck: | |
| test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"] | |
| interval: 30s | |
| timeout: 10s | |
| retries: 3 | |
| start_period: 40s | |
| EOF | |
| # Create deployment README | |
| cat > docker-artifacts/README.md << 'EOF' | |
| # SubPilot Docker Deployment | |
| Version: ${{ github.ref_name }} | |
| Image: `${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}` | |
| ## Quick Start | |
| 1. Create a `.env` file with required variables: | |
| ```bash | |
| DATABASE_URL=postgresql://username:password@host:5432/database | |
| NEXTAUTH_SECRET=your-secret-key | |
| NEXTAUTH_URL=https://your-domain.com | |
| ``` | |
| 2. Run with docker-compose: | |
| ```bash | |
| docker-compose up -d | |
| ``` | |
| ## Direct Docker Run | |
| ```bash | |
| docker run -d \ | |
| --name subpilot \ | |
| -p 3000:3000 \ | |
| -e DATABASE_URL=your-database-url \ | |
| -e NEXTAUTH_SECRET=your-secret \ | |
| -e NEXTAUTH_URL=https://your-domain.com \ | |
| ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }} | |
| ``` | |
| ## Health Check | |
| ```bash | |
| curl http://localhost:3000/api/health | |
| ``` | |
| EOF | |
| echo "✅ Docker artifacts created successfully" | |
| - name: Upload Docker artifacts | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: docker-artifacts | |
| path: docker-artifacts/ | |
| retention-days: 30 | |
| # Job 3: Deployment | |
| deploy: | |
| name: Deploy to Environments | |
| runs-on: ubuntu-latest | |
| needs: [build-and-test, docker-build-publish] | |
| if: success() && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) | |
| strategy: | |
| matrix: | |
| include: | |
| - environment: staging | |
| condition: github.ref == 'refs/heads/main' | |
| image_tag: latest | |
| - environment: production | |
| condition: startsWith(github.ref, 'refs/tags/v') | |
| image_tag: ${{ github.ref_name }} | |
| environment: ${{ matrix.environment }} | |
| steps: | |
| - name: Deploy to ${{ matrix.environment }} | |
| if: ${{ matrix.condition }} | |
| run: | | |
| echo "🚀 Deploying to ${{ matrix.environment }}" | |
| echo "Image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ matrix.image_tag }}" | |
| echo "Environment: ${{ matrix.environment }}" | |
| echo "This deployment would typically involve:" | |
| if [[ "${{ matrix.environment }}" == "staging" ]]; then | |
| echo " - Rolling deployment to staging cluster" | |
| echo " - Running smoke tests" | |
| echo " - Updating staging monitoring dashboards" | |
| else | |
| echo " - Blue/green deployment strategy" | |
| echo " - Database migrations (if needed)" | |
| echo " - Health checks and rollback capability" | |
| echo " - Updating production monitoring" | |
| fi | |
| # Placeholder for actual deployment commands: | |
| # kubectl set image deployment/subpilot-app subpilot-app=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ matrix.image_tag }} | |
| # helm upgrade subpilot ./chart --set image.tag=${{ matrix.image_tag }} | |
| echo "✅ ${{ matrix.environment }} deployment completed" | |
| # Job 4: Release Management | |
| release: | |
| name: Create GitHub Release | |
| runs-on: ubuntu-latest | |
| needs: [build-and-test, docker-build-publish, deploy] | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Download build artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: release-artifacts | |
| path: artifacts/ | |
| - name: Download Docker artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: docker-artifacts | |
| path: docker-artifacts/ | |
| continue-on-error: true | |
| - name: Check if release exists | |
| id: check_release | |
| run: | | |
| if gh release view ${{ github.ref_name }} &>/dev/null; then | |
| echo "exists=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "exists=false" >> $GITHUB_OUTPUT | |
| fi | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Create or update GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| body: | | |
| 🚀 **SubPilot Release ${{ github.ref_name }}** | |
| ## 📦 What's Included | |
| - ✅ Production-ready application build | |
| - 🐳 Multi-platform Docker images (amd64, arm64) | |
| - 🔒 Security-scanned and signed container images | |
| - 📋 Complete deployment documentation | |
| ## 🚀 Quick Start | |
| ### Docker Deployment | |
| ```bash | |
| docker run -d \ | |
| --name subpilot \ | |
| -p 3000:3000 \ | |
| -e DATABASE_URL=your-database-url \ | |
| -e NEXTAUTH_SECRET=your-secret \ | |
| -e NEXTAUTH_URL=https://your-domain.com \ | |
| ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }} | |
| ``` | |
| ### Source Installation | |
| ```bash | |
| git clone https://github.com/doublegate/SubPilot-App.git | |
| cd SubPilot-App | |
| git checkout ${{ github.ref_name }} | |
| npm install && npm run build | |
| ``` | |
| ## 🔍 Release Assets | |
| - **Source Archives**: Complete source code in tar.gz and zip formats | |
| - **Build Artifacts**: Pre-built application files | |
| - **Docker Compose**: Ready-to-use deployment configuration | |
| - **Documentation**: Setup and deployment guides | |
| - **Checksums**: SHA256 verification for all artifacts | |
| ## 📋 Technical Details | |
| - **Package Version**: v${{ needs.build-and-test.outputs.package-version }} | |
| - **Docker Images**: Available at `${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}` | |
| - **Security**: All images are vulnerability-scanned and signed | |
| - **Platforms**: Supports linux/amd64 and linux/arm64 | |
| --- | |
| For detailed setup instructions, see the [README](https://github.com/doublegate/SubPilot-App#readme). | |
| For release notes and changelog, see [CHANGELOG.md](https://github.com/doublegate/SubPilot-App/blob/main/CHANGELOG.md). | |
| draft: false | |
| prerelease: ${{ contains(github.ref, '-') }} | |
| files: | | |
| artifacts/subpilot-${{ github.ref_name }}-build.tar.gz | |
| artifacts/subpilot-${{ github.ref_name }}-source.tar.gz | |
| artifacts/checksums.sha256 | |
| docker-artifacts/docker-compose.yml | |
| docker-artifacts/README.md | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # Job 5: Cleanup and Monitoring | |
| cleanup: | |
| name: Cleanup & Monitoring | |
| runs-on: ubuntu-latest | |
| needs: [deploy, release] | |
| if: always() && (success() || failure()) | |
| steps: | |
| - name: Generate pipeline summary | |
| run: | | |
| echo "## 🎯 CI/CD Pipeline Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Repository:** ${{ github.repository }}" >> $GITHUB_STEP_SUMMARY | |
| echo "**Branch/Tag:** ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY | |
| echo "**Package Version:** v${{ needs.build-and-test.outputs.package-version || 'N/A' }}" >> $GITHUB_STEP_SUMMARY | |
| echo "**Commit SHA:** ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY | |
| echo "**Trigger:** ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Pipeline Status:**" >> $GITHUB_STEP_SUMMARY | |
| echo "- Build & Test: ${{ needs.build-and-test.result == 'success' && '✅' || '❌' }} ${{ needs.build-and-test.result }}" >> $GITHUB_STEP_SUMMARY | |
| echo "- Docker Build: ${{ needs.docker-build-publish.result == 'success' && '✅' || needs.docker-build-publish.result == 'skipped' && '⏭️' || '❌' }} ${{ needs.docker-build-publish.result }}" >> $GITHUB_STEP_SUMMARY | |
| echo "- Deployment: ${{ needs.deploy.result == 'success' && '✅' || needs.deploy.result == 'skipped' && '⏭️' || '❌' }} ${{ needs.deploy.result }}" >> $GITHUB_STEP_SUMMARY | |
| echo "- Release: ${{ needs.release.result == 'success' && '✅' || needs.release.result == 'skipped' && '⏭️' || '❌' }} ${{ needs.release.result }}" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" == "refs/heads/main" ]]; then | |
| echo "**Docker Images Published:**" >> $GITHUB_STEP_SUMMARY | |
| echo "- \`${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest\`" >> $GITHUB_STEP_SUMMARY | |
| echo "- \`${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:main-${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY | |
| elif [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" =~ ^refs/tags/v ]]; then | |
| echo "**Release Published:**" >> $GITHUB_STEP_SUMMARY | |
| echo "- Tag: ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY | |
| echo "- Docker: \`${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}\`" >> $GITHUB_STEP_SUMMARY | |
| echo "- GitHub Release: [View Release](https://github.com/${{ github.repository }}/releases/tag/${{ github.ref_name }})" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Build Time:** $(date -u '+%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_STEP_SUMMARY | |
| echo "**Workflow:** [${{ github.workflow }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})" >> $GITHUB_STEP_SUMMARY | |
| - name: Container registry maintenance | |
| if: github.event_name == 'schedule' | |
| run: | | |
| echo "🧹 Container registry maintenance would run here" | |
| echo "This scheduled job would typically:" | |
| echo " - Remove images older than 30 days" | |
| echo " - Keep the latest 10 versions for rollback capability" | |
| echo " - Clean up untagged images and manifests" | |
| echo " - Generate maintenance reports" | |
| echo "✅ Maintenance planning completed" |