diff --git a/README.md b/README.md index 98ef1f3..c30cc94 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ **Declare cloud infrastructure in YAML. Deploy with `git push`.** -A GitHub Marketplace composite action that discovers YAML configuration from your `.rabbit/` directory and deploys cloud infrastructure across AWS, GCP, and Kubernetes using Terraform — all from a single `uses:` step. +A GitHub Marketplace composite action that discovers YAML configuration from your `.rabbit/` directory and deploys cloud infrastructure across AWS, GCP, Azure, and Kubernetes using OpenTofu — all from a single `uses:` step. --- @@ -22,21 +22,14 @@ on: branches: ["production", "staging", "develop-*"] paths: [".rabbit/**"] delete: - schedule: - - cron: "0 2 * * *" workflow_dispatch: inputs: plan_only: description: "Plan only (no apply)" type: boolean default: true - environment: - description: "Target environment" - type: choice - options: [development, staging, production] - default: development terraform_action: - description: "Terraform action" + description: "Action" type: choice options: [apply, destroy] default: apply @@ -50,19 +43,77 @@ jobs: deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 + + # Authenticate with your cloud provider(s) before calling the action + - uses: google-github-actions/auth@v3 + with: + workload_identity_provider: ${{ vars.GCP_WORKLOAD_IDENTITY_PROVIDER }} + service_account: ${{ vars.GCP_SERVICE_ACCOUNT }} + + - uses: aws-actions/configure-aws-credentials@v6 + with: + role-to-assume: ${{ secrets.AWS_GITHUB_ACTIONS_ROLE_ARN }} + aws-region: us-east-1 - - uses: udx/github-rabbit-action@v1 + - uses: udx/github-rabbit-action@v5 with: project_id: ${{ vars.GCP_PROJECT_ID }} - gcp_auth_provider: ${{ vars.GCP_AUTH_PROVIDER }} - gcp_service_account: ${{ vars.GCP_SERVICE_ACCOUNT }} - aws_region: ${{ vars.AWS_REGION }} - aws_role_arn: ${{ secrets.AWS_GITHUB_ACTIONS_ROLE_ARN }} - slack_webhook: ${{ secrets.SLACK_WEBHOOK_ROUTINE }} dockerhub_username: ${{ vars.DOCKERHUB_USER_LOGIN }} dockerhub_token: ${{ secrets.DOCKERHUB_TOKEN_PULL_R2A }} - r2a_version: "4.8.0" + slack_webhook: ${{ secrets.SLACK_WEBHOOK_ROUTINE }} + terraform_action: ${{ inputs.terraform_action || 'apply' }} +``` + +#### AWS-only with S3 state backend + +```yaml +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - uses: aws-actions/configure-aws-credentials@v6 + with: + role-to-assume: ${{ secrets.AWS_GITHUB_ACTIONS_ROLE_ARN }} + aws-region: us-east-1 + + - uses: udx/github-rabbit-action@v5 + with: + project_id: my-project + state_backend: s3 + state_backend_config: | + bucket = "my-tfstate-bucket" + region = "us-east-1" + state_prefix_key: key + terraform_action: ${{ inputs.terraform_action || 'apply' }} +``` + +#### Azure with azurerm state backend + +```yaml +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - uses: azure/login@v2 + with: + client-id: ${{ vars.AZURE_CLIENT_ID }} + tenant-id: ${{ vars.AZURE_TENANT_ID }} + subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }} + + - uses: udx/github-rabbit-action@v5 + with: + project_id: my-project + state_backend: azurerm + state_backend_config: | + storage_account_name = "mytfstate" + container_name = "tfstate" + resource_group_name = "my-rg" + state_prefix_key: key terraform_action: ${{ inputs.terraform_action || 'apply' }} ``` @@ -76,13 +127,14 @@ Create YAML files inside `.rabbit//`: services: - module: aws-route53 id: my-domain - domain: example.com - records: - - type: A - name: "" - alias: - name: d1234.cloudfront.net - zone_id: Z2FDTNDATAQYW2 + configurations: + domain: example.com + records: + - type: A + name: "" + alias: + name: d1234.cloudfront.net + zone_id: Z2FDTNDATAQYW2 ``` #### `.rabbit/production/20-cdn.yaml` @@ -91,30 +143,12 @@ services: services: - module: aws-cloudfront-distribution id: my-cdn-#{Environment} - domain: example.com - origins: - - domain_name: my-app.example.com - origin_id: app-origin -``` - -#### `.rabbit/production/30-app.yaml` - -```yaml -services: - - module: k8s-namespace - id: my-app - - - module: k8s-deployment - id: my-app - image: my-org/my-app:latest - replicas: 2 - ports: - - containerPort: 8080 - - - module: k8s-http-gateway-route - id: my-app - hostname: my-app.example.com - service_port: 8080 + configurations: + domain: example.com + origins: + app: + domain_name: my-app.example.com + origin_id: app-origin ``` ### 3. Push and watch @@ -125,6 +159,36 @@ services: --- +## Authentication + +Cloud authentication is **your workflow's responsibility**. The action auto-detects credentials from the environment: + +| Provider | Auth Action | Detected Via | +| --- | --- | --- | +| GCP | `google-github-actions/auth@v3` | `GOOGLE_APPLICATION_CREDENTIALS` file | +| AWS | `aws-actions/configure-aws-credentials@v6` | `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` / `AWS_SESSION_TOKEN` | +| Azure | `azure/login@v2` | `ARM_CLIENT_ID` / `ARM_CLIENT_SECRET` / `ARM_TENANT_ID` / `ARM_SUBSCRIPTION_ID` | + +Only include auth steps for the providers you need. The action passes detected credentials to the IaC engine container automatically. + +--- + +## State Backend + +By default, Terraform/OpenTofu state is stored in GCS (Google Cloud Storage). Override with any supported backend: + +| Backend | `state_backend` | `state_prefix_key` | Config Keys | +| --- | --- | --- | --- | +| GCS (default) | — | `prefix` | `bucket` | +| S3 | `s3` | `key` | `bucket`, `region` | +| Azure Blob | `azurerm` | `key` | `storage_account_name`, `container_name`, `resource_group_name` | +| HTTP | `http` | — | `address`, `lock_address`, `unlock_address` | +| Consul | `consul` | — | `address`, `path` | + +The backend override is injected at runtime using OpenTofu override files — no module changes needed. + +--- + ## How It Works ``` @@ -147,20 +211,16 @@ services: └─────────────┬──────────────┘ │ ┌─────────────▼──────────────┐ - │ 3. Cloud Auth │ - │ GCP Workload Identity │ - │ AWS OIDC (optional) │ - └─────────────┬──────────────┘ - │ - ┌─────────────▼──────────────┐ - │ 4. Terraform Engine │ + │ 3. IaC Engine │ │ Docker: r2a container │ + │ OpenTofu (default) or │ + │ Terraform (via IAC_TOOL) │ │ Per-service init/plan/ │ │ apply in deploy order │ └─────────────┬──────────────┘ │ ┌─────────────▼──────────────┐ - │ 5. Reporting │ + │ 4. Reporting │ │ Plan summary table │ │ PR comment │ │ GitHub step summary │ @@ -285,7 +345,8 @@ Each service in your YAML config follows this structure: services: - module: # Required: Terraform module to use id: # Required: Unique identifier for this service - # ... module-specific fields + configurations: + # ... module-specific fields ``` ### Placeholders @@ -302,16 +363,6 @@ Use `#{Variable}` syntax in YAML values — they're replaced at runtime: | `#{Namespace}` | Kubernetes namespace (derived from repo name) | | `#{SharedProject}` | Shared GCP project ID | -Example: - -```yaml -services: - - module: k8s-deployment - id: my-app-#{Environment} - namespace: #{Namespace} - image: gcr.io/#{GcpProject}/my-app:latest -``` - ### GCP Secret Manager References Reference secrets directly in your YAML: @@ -320,132 +371,12 @@ Reference secrets directly in your YAML: services: - module: k8s-secret id: app-secrets - data: - DATABASE_URL: gcp://projects/my-project/secrets/db-url/versions/latest + configurations: + data: + DATABASE_URL: gcp://projects/my-project/secrets/db-url/versions/latest ``` -The action automatically resolves `gcp://` prefixed values to actual secret values at deploy time. - ---- - -## Setup Guide - -### Required GitHub Variables - -Set these in your repository or organization settings → Variables: - -| Variable | Description | -| --- | --- | -| `GCP_PROJECT_ID` | Google Cloud project ID | -| `GCP_AUTH_PROVIDER` | GCP Workload Identity Provider resource name | -| `GCP_SERVICE_ACCOUNT` | GCP Service Account email | - -### Optional GitHub Variables - -| Variable | Description | -| --- | --- | -| `AWS_REGION` | AWS region (e.g., `us-east-1`) | -| `DOCKERHUB_USER_LOGIN` | Docker Hub username for image pulls | -| `K8S_CLUSTER_NAME` | GKE cluster name | -| `NEWRELIC_ACCOUNT_ID` | New Relic account ID | -| `SHARED_PROJECT` | Shared GCP project for cross-project access | - -### Required GitHub Secrets - -| Secret | Description | -| --- | --- | -| `SLACK_WEBHOOK_ROUTINE` | Slack incoming webhook for notifications | - -### Optional GitHub Secrets - -| Secret | Description | -| --- | --- | -| `AWS_GITHUB_ACTIONS_ROLE_ARN` | AWS IAM OIDC role for Route53/CloudFront/WAF/ACM | -| `DOCKERHUB_TOKEN_PULL_R2A` | Docker Hub token (paired with `DOCKERHUB_USER_LOGIN`) | -| `DOCKERHUB_HELM_TOKEN` | Docker Hub token for Helm OCI charts | -| `NEWRELIC_API_KEY` | New Relic API key | - -### Workflow Permissions - -```yaml -permissions: - contents: read - pull-requests: write # For PR comments with plan summary - id-token: write # For GCP Workload Identity & AWS OIDC -``` - ---- - -## Advanced Usage - -### Multi-Environment Overrides - -Override specific values per environment using subdirectories: - -``` -.rabbit/ -└── production/ - ├── 10-infra.yaml # Base production config - └── us-east-1/ - └── 10-infra.yaml # Overrides for us-east-1 environment -``` - -Files in subdirectories are deep-merged on top of the parent directory files. Services with matching `module::id` pairs are merged, not duplicated. - -### Multi-Repo Projects - -For monorepo or multi-repo setups where multiple repositories share infrastructure: - -```yaml -- uses: udx/github-rabbit-action@v1 - with: - multi_repo: "true" - shared_project: "shared-infra-project" - # ... other inputs -``` - -This isolates Terraform state per repository while allowing shared GCP project access. - -### Pinning R2A Version - -Always pin to a specific version for reproducible builds: - -```yaml -- uses: udx/github-rabbit-action@v1 - with: - r2a_version: "4.8.0" -``` - -### Ephemeral Environments (Branch Delete → Destroy) - -Add `delete` to your workflow triggers and matching feature branches: - -```yaml -on: - delete: # Triggers destroy when branch is deleted - push: - branches: ["production", "staging", "develop-*"] -``` - -When a `develop-*` branch is deleted, the action automatically runs `terraform destroy` for that environment. - -### Debug Mode - -Enable detailed config output in logs: - -```yaml -- uses: udx/github-rabbit-action@v1 - with: - print_config: "true" -``` - -### Manual Dispatch with Safety Controls - -The workflow dispatch inputs provide safe manual control: - -- **Plan only = true** → preview changes without applying -- **Environment = production** + **Plan only = false** → blocked (safety guardrail) -- **Terraform action = destroy** + **Environment = production** → blocked +The action automatically resolves `gcp://` prefixed values to actual secret values at deploy time (requires GCP auth). --- @@ -453,11 +384,7 @@ The workflow dispatch inputs provide safe manual control: | Input | Required | Default | Description | | --- | --- | --- | --- | -| `project_id` | ✅ | — | GCP project ID | -| `gcp_auth_provider` | ✅ | — | GCP Workload Identity Provider | -| `gcp_service_account` | ✅ | — | GCP Service Account email | -| `aws_role_arn` | — | — | AWS IAM OIDC role ARN | -| `aws_region` | — | — | AWS region | +| `project_id` | yes | — | Project identifier for state isolation | | `dockerhub_username` | — | — | Docker Hub username | | `dockerhub_token` | — | — | Docker Hub pull token | | `dockerhub_helm_token` | — | — | Docker Hub Helm OCI token | @@ -472,6 +399,9 @@ The workflow dispatch inputs provide safe manual control: | `newrelic_account_id` | — | — | New Relic account ID | | `newrelic_api_key` | — | — | New Relic API key | | `slack_webhook` | — | — | Slack webhook URL | +| `state_backend` | — | `gcs` | Backend type — defaults to GCS when unset (`s3`, `azurerm`, `http`, `consul`) | +| `state_backend_config` | — | — | Backend config as key=value lines | +| `state_prefix_key` | — | `prefix` | Backend key for state path | | `source_dir` | — | `.rabbit` | Config source directory | | `github_token` | — | `github.token` | GitHub token for PR comments | @@ -491,48 +421,6 @@ The workflow dispatch inputs provide safe manual control: --- -## What You'll See - -### GitHub Step Summary - -Every run writes a configuration summary and deployment results to the GitHub Actions step summary — visible directly on the Actions run page. - -### PR Comments - -Pull requests get an automatically updated comment with a detailed Terraform plan breakdown: - -``` -## Terraform Plan Summary - -| Module / Service | Add | Change | Destroy | -| --- | --- | --- | --- | -| **Total** | 5 | 2 | 0 | -| `aws-cloudfront-distribution/my-cdn` | 0 | 1 | 0 | -| `k8s-deployment/my-app` | 3 | 1 | 0 | -| `k8s-http-gateway-route/my-app` | 2 | 0 | 0 | -``` - -### Slack Notifications - -Notifications are sent when: -- Infrastructure changes are detected or applied -- Any step fails - -Notifications include environment, change counts, failure stage, and a link to the action run. - ---- - -## Pro Tips - -- **Use a Docker Hub token** (`dockerhub_username` + `dockerhub_token`) to prevent rate limits when pulling the R2A image -- **Pin `r2a_version`** to a specific tag for reproducible deploys (e.g., `4.8.0` instead of `latest`) -- **Name files with numeric prefixes** (`10-dns.yaml`, `20-cdn.yaml`, `30-app.yaml`) for deterministic ordering -- **Use `#{Environment}` placeholders** in service IDs to keep configs environment-aware -- **Schedule nightly runs** (`cron: "0 2 * * *"`) to detect infrastructure drift -- **Keep `.rabbit/` configs small and focused** — one concern per file - ---- - ## License GPL-2.0 diff --git a/action.yml b/action.yml index 8301afc..4a826fd 100644 --- a/action.yml +++ b/action.yml @@ -9,22 +9,8 @@ branding: inputs: # Required project_id: - description: "GCP project ID for Terraform state and resource operations" + description: "Project identifier for state isolation and resource scoping" required: true - gcp_auth_provider: - description: "GCP Workload Identity Federation provider resource name" - required: true - gcp_service_account: - description: "GCP service account email for Workload Identity" - required: true - - # Auth - aws_role_arn: - description: "AWS IAM OIDC role ARN for Route53, CloudFront, WAF, ACM" - required: false - aws_region: - description: "AWS region" - required: false # Docker dockerhub_username: @@ -47,7 +33,7 @@ inputs: required: false default: "apply" plan_only: - description: "Override plan mode (auto-detected: PRs and schedules → true, pushes → false)" + description: "Override plan mode (auto-detected: PRs and schedules -> true, pushes -> false)" required: false environment: description: "Override environment name (auto-detected from branch name)" @@ -82,6 +68,19 @@ inputs: description: "Slack incoming webhook URL for deployment notifications" required: false + # State backend + state_backend: + description: "Override state backend type (e.g. s3, http, consul, azurerm). Unset = default gcs." + required: false + default: "gcs" + state_backend_config: + description: "Backend config as key=value lines (e.g. bucket=my-bucket newline region=us-east-1). Required when state_backend is set." + required: false + state_prefix_key: + description: "Backend-specific key for state path prefix (gcs=prefix, s3=key, azurerm=key). Default: prefix" + required: false + default: "prefix" + # Config source_dir: description: "Source directory for .rabbit configs" @@ -361,7 +360,6 @@ runs: TRIGGER: ${{ github.event_name }} R2A_VERSION: ${{ inputs.r2a_version }} MULTI_REPO: ${{ inputs.multi_repo }} - HAS_AWS: ${{ inputs.aws_role_arn != '' && inputs.aws_region != '' }} run: | config_items="$(printf '%s' "${DETECTED_CONFIGS:-none}" | sed "s/#{Environment}/$ENV_NAME/g" | tr ',' '\n' | sed 's/^ *//; s/ *$//' | sed '/^$/d')" @@ -379,7 +377,6 @@ runs: echo "| Lifecycle | \`${LIFECYCLE:-unknown}\` |" echo "| Plan Only | \`${PLAN_ONLY:-true}\` |" echo "| Multi Repo | \`${MULTI_REPO:-false}\` |" - echo "| AWS Enabled | \`${HAS_AWS}\` |" echo "" echo "### Detected Configs" echo "" @@ -401,28 +398,7 @@ runs: } >> "$GITHUB_STEP_SUMMARY" # ========================================================================= - # 10. Authenticate with Google Cloud - # ========================================================================= - - name: Authenticate with Google Cloud - id: gcp-auth - if: steps.safety.outputs.should_deploy == 'true' - uses: google-github-actions/auth@v3 - with: - workload_identity_provider: ${{ inputs.gcp_auth_provider }} - service_account: ${{ inputs.gcp_service_account }} - - # ========================================================================= - # 11. Configure AWS credentials (optional) - # ========================================================================= - - name: Configure AWS credentials - if: steps.safety.outputs.should_deploy == 'true' && inputs.aws_role_arn != '' && inputs.aws_region != '' - uses: aws-actions/configure-aws-credentials@v6 - with: - role-to-assume: ${{ inputs.aws_role_arn }} - aws-region: ${{ inputs.aws_region }} - - # ========================================================================= - # 12. Prepare artifacts directory + # 10. Prepare artifacts directory # ========================================================================= - name: Prepare artifacts directory if: steps.safety.outputs.should_deploy == 'true' @@ -432,22 +408,7 @@ runs: chmod -R 777 terraform/plans # ========================================================================= - # 13. Prepare GCP credentials for Docker volume mount - # ========================================================================= - - name: Prepare Workload Identity credentials - id: prepare-creds - if: steps.safety.outputs.should_deploy == 'true' - shell: bash - env: - CRED_FILE_PATH: ${{ steps.gcp-auth.outputs.credentials_file_path }} - run: | - set -euo pipefail - cp "$CRED_FILE_PATH" gcp-credentials.json - chmod 644 gcp-credentials.json - echo "credentials_path=$(pwd)/gcp-credentials.json" >> "$GITHUB_OUTPUT" - - # ========================================================================= - # 14. Docker login and pull R2A image + # 11. Docker login and pull R2A image # ========================================================================= - name: Docker login and pull R2A image if: steps.safety.outputs.should_deploy == 'true' @@ -461,16 +422,13 @@ runs: IMAGE="usabilitydynamics/rabbit-automation-action:${R2A_VERSION}" if [[ -n "$DOCKERHUB_USERNAME" && -n "$DOCKERHUB_TOKEN" ]]; then - echo "🔐 Logging in to Docker Hub..." echo "$DOCKERHUB_TOKEN" | docker login -u "$DOCKERHUB_USERNAME" --password-stdin fi - echo "📦 Pulling $IMAGE..." docker pull "$IMAGE" - echo "✓ Image pulled successfully" # ========================================================================= - # 15. Run Rabbit Automation Action (Terraform engine) + # 12. Run Rabbit Automation Action (IaC engine) # ========================================================================= - name: Run Rabbit Automation Action id: terraform @@ -494,6 +452,9 @@ runs: DOCKERHUB_USERNAME: ${{ inputs.dockerhub_username }} DOCKERHUB_TOKEN: ${{ inputs.dockerhub_helm_token || inputs.dockerhub_token }} SHARED_PROJECT: ${{ inputs.shared_project }} + STATE_BACKEND: ${{ inputs.state_backend }} + STATE_BACKEND_CONFIG: ${{ inputs.state_backend_config }} + STATE_PREFIX_KEY: ${{ inputs.state_prefix_key }} REPO_OWNER: ${{ github.repository_owner }} REPO_NAME: ${{ github.event.repository.name }} run: | @@ -501,21 +462,36 @@ runs: IMAGE="usabilitydynamics/rabbit-automation-action:${R2A_VERSION}" ARTIFACTS_PATH="terraform/plans" - # Build AWS env vars (may be empty if AWS not configured) + # Build cloud credential env vars from caller's auth steps + # AWS: auto-detected from aws-actions/configure-aws-credentials aws_env=() if [[ -n "${AWS_ACCESS_KEY_ID:-}" ]]; then aws_env+=(-e "AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}") - aws_env+=(-e "AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}") - aws_env+=(-e "AWS_SESSION_TOKEN=${AWS_SESSION_TOKEN}") + aws_env+=(-e "AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-}") + aws_env+=(-e "AWS_SESSION_TOKEN=${AWS_SESSION_TOKEN:-}") aws_env+=(-e "AWS_REGION=${AWS_REGION:-}") - else - aws_env+=(-e "AWS_ACCESS_KEY_ID=") - aws_env+=(-e "AWS_SECRET_ACCESS_KEY=") - aws_env+=(-e "AWS_SESSION_TOKEN=") - aws_env+=(-e "AWS_REGION=") fi - echo "🐰 Running Rabbit Automation Action..." + # GCP: auto-detected from google-github-actions/auth + gcp_env=() + if [[ -n "${GOOGLE_APPLICATION_CREDENTIALS:-}" && -f "${GOOGLE_APPLICATION_CREDENTIALS}" ]]; then + cp "${GOOGLE_APPLICATION_CREDENTIALS}" "$(pwd)/gcp-credentials.json" + chmod 644 "$(pwd)/gcp-credentials.json" + gcp_env+=(-e "GOOGLE_APPLICATION_CREDENTIALS=/github/workspace/gcp-credentials.json") + fi + + # Azure: auto-detected from azure/login + azure_env=() + if [[ -n "${ARM_CLIENT_ID:-}" ]]; then + azure_env+=(-e "ARM_CLIENT_ID=${ARM_CLIENT_ID}") + azure_env+=(-e "ARM_CLIENT_SECRET=${ARM_CLIENT_SECRET:-}") + azure_env+=(-e "ARM_TENANT_ID=${ARM_TENANT_ID:-}") + azure_env+=(-e "ARM_SUBSCRIPTION_ID=${ARM_SUBSCRIPTION_ID:-}") + fi + if [[ -n "${ARM_ACCESS_KEY:-}" ]]; then + azure_env+=(-e "ARM_ACCESS_KEY=${ARM_ACCESS_KEY}") + fi + docker run --rm \ -v "$(pwd):/github/workspace" \ -e "GCP_PROJECT=${PROJECT_ID}" \ @@ -537,13 +513,17 @@ runs: -e "DOCKERHUB_USERNAME=${DOCKERHUB_USERNAME:-}" \ -e "DOCKERHUB_TOKEN=${DOCKERHUB_TOKEN:-}" \ -e "SHARED_PROJECT=${SHARED_PROJECT:-}" \ - -e "GOOGLE_APPLICATION_CREDENTIALS=/github/workspace/gcp-credentials.json" \ - "${aws_env[@]}" \ + -e "STATE_BACKEND=${STATE_BACKEND:-}" \ + -e "STATE_BACKEND_CONFIG=${STATE_BACKEND_CONFIG:-}" \ + -e "STATE_PREFIX_KEY=${STATE_PREFIX_KEY:-prefix}" \ + ${gcp_env[@]+"${gcp_env[@]}"} \ + ${aws_env[@]+"${aws_env[@]}"} \ + ${azure_env[@]+"${azure_env[@]}"} \ "$IMAGE" \ /usr/local/bin/entrypoint.sh echo "" - echo "📋 Artifacts generated:" + echo "Artifacts generated:" ls -la "$ARTIFACTS_PATH" 2>/dev/null || echo "No artifacts directory found" # Extract CloudFront distribution ID from terraform outputs @@ -564,19 +544,19 @@ runs: echo "aws_cloudfront_distribution_id=${cloudfront_distribution_id}" >> "$GITHUB_OUTPUT" # ========================================================================= - # 16. Upload terraform artifacts + # 13. Upload terraform artifacts # ========================================================================= - name: Upload terraform artifacts if: steps.safety.outputs.should_deploy == 'true' continue-on-error: true - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: terraform-artifacts-${{ steps.resolve-env.outputs.environment }} path: terraform/plans if-no-files-found: warn # ========================================================================= - # 17. Render plan summary + # 14. Render plan summary # ========================================================================= - name: Render plan summary id: plan-summary @@ -625,7 +605,7 @@ runs: bash "${GITHUB_ACTION_PATH}/bin/render-plan-summary.sh" "$summary_file" "$ARTIFACTS_PATH/plan-summary.md" # ========================================================================= - # 18. Write deployment summary + # 15. Write deployment summary # ========================================================================= - name: Write deployment summary if: always() && steps.safety.outputs.should_deploy == 'true' @@ -669,7 +649,7 @@ runs: fi # ========================================================================= - # 19. Post PR comment with plan summary + # 16. Post PR comment with plan summary # ========================================================================= - name: Post PR comment with plan summary if: github.event_name == 'pull_request' && steps.plan-summary.outputs.has_changes == 'true' @@ -730,19 +710,19 @@ runs: } # ========================================================================= - # 20. Upload terraform plan files + # 17. Upload terraform plan files # ========================================================================= - name: Upload terraform plans if: steps.plan-summary.outputs.has_changes == 'true' continue-on-error: true - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: terraform-plans-${{ steps.resolve-env.outputs.environment }} path: terraform/plans/*.tfplan if-no-files-found: ignore # ========================================================================= - # 21. Send Slack notification + # 18. Send Slack notification # ========================================================================= - name: Send Slack notification if: always() && inputs.slack_webhook != '' && (steps.plan-summary.outputs.has_changes == 'true' || steps.terraform.outcome == 'failure' || steps.safety.outcome == 'failure')