diff --git a/README.md b/README.md index e657b3195..ea863f9e6 100644 --- a/README.md +++ b/README.md @@ -55,14 +55,17 @@ After running this action, apply Terraform with the companion action, [github-ac ### Prerequisites -This GitHub Action requires AWS access for two different purposes. This action will attempt to first run `terraform plan` against a given component and -then will use another role to save that given Terraform Plan to an S3 Bucket with metadata in a DynamoDB table. We recommend configuring -[OpenID Connect with AWS](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services) -to allow GitHub to assume roles in AWS and then deploying both a Terraform Plan role and a Terraform State role. -For Cloud Posse documentation on setting up GitHub OIDC, see our [`github-oidc-provider` component](https://docs.cloudposse.com/components/library/aws/github-oidc-provider/). +This GitHub Action requires cloud provider access for two different purposes. This action will attempt to first run `terraform plan` against a given component and +then will use credentials to save that given Terraform Plan to cloud storage with metadata. -In order to store Terraform State, we configure an S3 Bucket to store plan files and a DynamoDB table to track plan metadata. Both will need to be deployed before running -this action. For more on setting up those components, see the `gitops` component (__documentation pending__). This action will then use the [github-action-terraform-plan-storage](https://github.com/cloudposse/github-action-terraform-plan-storage) action to update these resources. +For AWS, we recommend configuring [OpenID Connect with AWS](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services) +to allow GitHub to assume roles in AWS and then deploying both a Terraform Plan role and a Terraform State role. +For Cloud Posse documentation on setting up GitHub OIDC with AWS, see our [`github-oidc-provider` component](https://docs.cloudposse.com/components/library/aws/github-oidc-provider/). + +For Google Cloud, we recommend configuring [Workload Identity Federation](https://cloud.google.com/iam/docs/workload-identity-federation-with-GitHub-actions) +to allow GitHub Actions to authenticate with Google Cloud services. This will enable access to Google Cloud Storage for plan files and Firestore for metadata storage. + +For AWS storage, we configure an S3 Bucket to store plan files and a DynamoDB table to track plan metadata. For Google Cloud storage, we use Google Cloud Storage buckets for plan files and Firestore for metadata. These resources will need to be deployed before running this action. For more on setting up those components, see the `gitops` component (__documentation pending__). This action will then use the [github-action-terraform-plan-storage](https://github.com/cloudposse/github-action-terraform-plan-storage) action to update these resources. ### Config @@ -81,6 +84,7 @@ Depending on the cloud provider, the following fields should be set in the `atmo The config should have the following structure: +#### AWS ```yaml integrations: github: @@ -129,6 +133,22 @@ integrations: group-by: .stack_slug | split("-") | [.[0], .[2]] | join("-") ``` +#### Google Cloud +```yaml +integrations: + github: + gitops: + ... + artifact-storage: + ... + bucket: cptest-core-ue2-auto-gitops + google-service-account: terraform@project-id.iam.gserviceaccount.com + google-workload-identity-provider: projects/project-id/locations/global/workloadIdentityPools/github-actions/providers/github-provider + google-project-id: cptest-core-ue2-auto-gitops + google-firestore-database-name: cptest-core-ue2-auto-gitops + google-firestore-collection-name: terraform-plan-storage +``` + ### Stack level configuration > [!IMPORTANT] diff --git a/README.yaml b/README.yaml index 860f4bbfa..d65834358 100644 --- a/README.yaml +++ b/README.yaml @@ -44,14 +44,17 @@ references: usage: |- ### Prerequisites - This GitHub Action requires AWS access for two different purposes. This action will attempt to first run `terraform plan` against a given component and - then will use another role to save that given Terraform Plan to an S3 Bucket with metadata in a DynamoDB table. We recommend configuring - [OpenID Connect with AWS](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services) - to allow GitHub to assume roles in AWS and then deploying both a Terraform Plan role and a Terraform State role. - For Cloud Posse documentation on setting up GitHub OIDC, see our [`github-oidc-provider` component](https://docs.cloudposse.com/components/library/aws/github-oidc-provider/). + This GitHub Action requires cloud provider access for two different purposes. This action will attempt to first run `terraform plan` against a given component and + then will use credentials to save that given Terraform Plan to cloud storage with metadata. - In order to store Terraform State, we configure an S3 Bucket to store plan files and a DynamoDB table to track plan metadata. Both will need to be deployed before running - this action. For more on setting up those components, see the `gitops` component (__documentation pending__). This action will then use the [github-action-terraform-plan-storage](https://github.com/cloudposse/github-action-terraform-plan-storage) action to update these resources. + For AWS, we recommend configuring [OpenID Connect with AWS](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services) + to allow GitHub to assume roles in AWS and then deploying both a Terraform Plan role and a Terraform State role. + For Cloud Posse documentation on setting up GitHub OIDC with AWS, see our [`github-oidc-provider` component](https://docs.cloudposse.com/components/library/aws/github-oidc-provider/). + + For Google Cloud, we recommend configuring [Workload Identity Federation](https://cloud.google.com/iam/docs/workload-identity-federation-with-GitHub-actions) + to allow GitHub Actions to authenticate with Google Cloud services. This will enable access to Google Cloud Storage for plan files and Firestore for metadata storage. + + For AWS storage, we configure an S3 Bucket to store plan files and a DynamoDB table to track plan metadata. For Google Cloud storage, we use Google Cloud Storage buckets for plan files and Firestore for metadata. These resources will need to be deployed before running this action. For more on setting up those components, see the `gitops` component (__documentation pending__). This action will then use the [github-action-terraform-plan-storage](https://github.com/cloudposse/github-action-terraform-plan-storage) action to update these resources. ### Config @@ -70,6 +73,7 @@ usage: |- The config should have the following structure: + #### AWS ```yaml integrations: github: @@ -118,6 +122,22 @@ usage: |- group-by: .stack_slug | split("-") | [.[0], .[2]] | join("-") ``` + #### Google Cloud + ```yaml + integrations: + github: + gitops: + ... + artifact-storage: + ... + bucket: cptest-core-ue2-auto-gitops + google-service-account: terraform@project-id.iam.gserviceaccount.com + google-workload-identity-provider: projects/project-id/locations/global/workloadIdentityPools/github-actions/providers/github-provider + google-project-id: cptest-core-ue2-auto-gitops + google-firestore-database-name: cptest-core-ue2-auto-gitops + google-firestore-collection-name: terraform-plan-storage + ``` + ### Stack level configuration > [!IMPORTANT] diff --git a/action.yml b/action.yml index 43615b668..ebad884a3 100644 --- a/action.yml +++ b/action.yml @@ -91,6 +91,40 @@ runs: token: ${{ inputs.token }} install-wrapper: false + - name: Get terraform and opentofu versions + id: terraform-versions + uses: cloudposse/github-action-atmos-get-setting@v2 + with: + process-templates: false + settings: | + - component: ${{ inputs.component }} + stack: ${{ inputs.stack }} + settingsPath: settings.integrations.github.gitops.opentofu-version + outputPath: opentofu-version + - component: ${{ inputs.component }} + stack: ${{ inputs.stack }} + settingsPath: settings.integrations.github.gitops.terraform-version + outputPath: terraform-version + + - name: Install Terraform + if: ${{ fromJson(steps.terraform-versions.outputs.settings).terraform-version != '' && fromJson(steps.terraform-versions.outputs.settings).terraform-version != 'null' }} + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: ${{ fromJson(steps.terraform-versions.outputs.settings).terraform-version }} + terraform_wrapper: false + + - name: Install Dependencies + uses: cloudposse-github-actions/install-gh-releases@v1 + with: + cache: true + config: |- + opentofu/opentofu: + tag: ${{ startsWith(fromJson(steps.terraform-versions.outputs.settings).opentofu-version, 'v') && fromJson(steps.terraform-versions.outputs.settings).opentofu-version || format('v{0}', fromJson(steps.terraform-versions.outputs.settings).opentofu-version) }} + skip: ${{ fromJson(steps.terraform-versions.outputs.settings).opentofu-version == '' || fromJson(steps.terraform-versions.outputs.settings).opentofu-version == 'null' }} + suzuki-shunsuke/tfcmt: + tag: v4.14.0 + + - name: Get atmos settings id: atmos-settings uses: cloudposse/github-action-atmos-get-setting@v2 @@ -112,14 +146,6 @@ runs: stack: ${{ inputs.stack }} settingsPath: command outputPath: command - - component: ${{ inputs.component }} - stack: ${{ inputs.stack }} - settingsPath: settings.integrations.github.gitops.opentofu-version - outputPath: opentofu-version - - component: ${{ inputs.component }} - stack: ${{ inputs.stack }} - settingsPath: settings.integrations.github.gitops.terraform-version - outputPath: terraform-version - component: ${{ inputs.component }} stack: ${{ inputs.stack }} settingsPath: settings.integrations.github.gitops.infracost-enabled @@ -172,24 +198,26 @@ runs: stack: ${{ inputs.stack }} settingsPath: settings.integrations.github.gitops.artifact-storage.cosmos-endpoint outputPath: cosmos-endpoint - - - name: Install Terraform - if: ${{ fromJson(steps.atmos-settings.outputs.settings).terraform-version != '' && fromJson(steps.atmos-settings.outputs.settings).terraform-version != 'null' }} - uses: hashicorp/setup-terraform@v3 - with: - terraform_version: ${{ fromJson(steps.atmos-settings.outputs.settings).terraform-version }} - terraform_wrapper: false - - - name: Install Dependencies - uses: cloudposse-github-actions/install-gh-releases@v1 - with: - cache: true - config: |- - opentofu/opentofu: - tag: ${{ startsWith(fromJson(steps.atmos-settings.outputs.settings).opentofu-version, 'v') && fromJson(steps.atmos-settings.outputs.settings).opentofu-version || format('v{0}', fromJson(steps.atmos-settings.outputs.settings).opentofu-version) }} - skip: ${{ fromJson(steps.atmos-settings.outputs.settings).opentofu-version == '' || fromJson(steps.atmos-settings.outputs.settings).opentofu-version == 'null' }} - suzuki-shunsuke/tfcmt: - tag: v4.14.0 + - component: ${{ inputs.component }} + stack: ${{ inputs.stack }} + settingsPath: settings.integrations.github.gitops.artifact-storage.google-project-id + outputPath: google-project-id + - component: ${{ inputs.component }} + stack: ${{ inputs.stack }} + settingsPath: settings.integrations.github.gitops.artifact-storage.google-service-account + outputPath: google-service-account + - component: ${{ inputs.component }} + stack: ${{ inputs.stack }} + settingsPath: settings.integrations.github.gitops.artifact-storage.google-workload-identity-provider + outputPath: google-workload-identity-provider + - component: ${{ inputs.component }} + stack: ${{ inputs.stack }} + settingsPath: settings.integrations.github.gitops.artifact-storage.google-firestore-database-name + outputPath: google-firestore-database-name + - component: ${{ inputs.component }} + stack: ${{ inputs.stack }} + settingsPath: settings.integrations.github.gitops.artifact-storage.google-firestore-collection-name + outputPath: google-firestore-collection-name - name: Configure Plan AWS Credentials uses: aws-actions/configure-aws-credentials@v4 @@ -203,6 +231,16 @@ runs: role-session-name: "atmos-terraform-plan-gitops" mask-aws-account-id: "no" + - name: Configure Google Credentials + if: ${{ fromJson(steps.atmos-settings.outputs.settings).google-workload-identity-provider != '' && + fromJson(steps.atmos-settings.outputs.settings).google-workload-identity-provider != 'null' && + fromJson(steps.atmos-settings.outputs.settings).google-service-account != '' && + fromJson(steps.atmos-settings.outputs.settings).google-service-account != 'null' }} + uses: google-github-actions/auth@v2 + with: + workload_identity_provider: ${{ fromJson(steps.atmos-settings.outputs.settings).google-workload-identity-provider }} + service_account: ${{ fromJson(steps.atmos-settings.outputs.settings).google-service-account }} + - name: Set atmos cli base path vars if: ${{ fromJson(steps.atmos-settings.outputs.settings).enabled }} shell: bash @@ -224,9 +262,13 @@ runs: run: | STACK_NAME=$(echo "${{ inputs.stack }}" | sed 's#/#_#g') COMPONENT_PATH=${{ fromJson(steps.atmos-settings.outputs.settings).component-path }} + # Normalize absolute component_path to relative to GITHUB_WORKSPACE so ./$COMPONENT_PATH resolves correctly + if [[ "$COMPONENT_PATH" == /* ]]; then + COMPONENT_PATH=".${COMPONENT_PATH#$GITHUB_WORKSPACE}" + fi COMPONENT_NAME=$(echo "${{ inputs.component }}" | sed 's#/#_#g') COMPONENT_SLUG="$STACK_NAME-$COMPONENT_NAME" - COMPONENT_CACHE_KEY=$(basename "${{ fromJson(steps.atmos-settings.outputs.settings).component-path }}") + COMPONENT_CACHE_KEY=$(basename "$COMPONENT_PATH") PLAN_FILE="$( realpath ${COMPONENT_PATH})/$COMPONENT_SLUG-${{ inputs.sha }}.planfile" LOCK_FILE="$( realpath ${COMPONENT_PATH})/.terraform.lock.hcl" @@ -375,7 +417,7 @@ runs: - name: Store New Plan if: ${{ steps.atmos-plan.outputs.error == 'false' }} - uses: cloudposse/github-action-terraform-plan-storage@v1 + uses: cloudposse/github-action-terraform-plan-storage@v2 with: action: storePlan commitSHA: ${{ inputs.sha }} @@ -389,12 +431,15 @@ runs: cosmosContainerName: ${{ fromJson(steps.atmos-settings.outputs.settings).cosmos-container-name }} cosmosDatabaseName: ${{ fromJson(steps.atmos-settings.outputs.settings).cosmos-database-name }} cosmosEndpoint: ${{ fromJson(steps.atmos-settings.outputs.settings).cosmos-endpoint }} + gcpProjectId: ${{ fromJson(steps.atmos-settings.outputs.settings).google-project-id }} + gcpFirestoreDatabaseName: ${{ fromJson(steps.atmos-settings.outputs.settings).google-firestore-database-name }} + gcpFirestoreCollectionName: ${{ fromJson(steps.atmos-settings.outputs.settings).google-firestore-collection-name }} tableName: ${{ fromJson(steps.atmos-settings.outputs.settings).terraform-state-table }} bucketName: ${{ fromJson(steps.atmos-settings.outputs.settings).terraform-state-bucket }} - name: Store Lockfile for New Plan if: ${{ steps.atmos-plan.outputs.error == 'false' }} - uses: cloudposse/github-action-terraform-plan-storage@v1 + uses: cloudposse/github-action-terraform-plan-storage@v2 with: action: storePlan commitSHA: ${{ inputs.sha }} @@ -408,6 +453,9 @@ runs: cosmosContainerName: ${{ fromJson(steps.atmos-settings.outputs.settings).cosmos-container-name }} cosmosDatabaseName: ${{ fromJson(steps.atmos-settings.outputs.settings).cosmos-database-name }} cosmosEndpoint: ${{ fromJson(steps.atmos-settings.outputs.settings).cosmos-endpoint }} + gcpProjectId: ${{ fromJson(steps.atmos-settings.outputs.settings).google-project-id }} + gcpFirestoreDatabaseName: ${{ fromJson(steps.atmos-settings.outputs.settings).google-firestore-database-name }} + gcpFirestoreCollectionName: ${{ fromJson(steps.atmos-settings.outputs.settings).google-firestore-collection-name }} tableName: ${{ fromJson(steps.atmos-settings.outputs.settings).terraform-state-table }} bucketName: ${{ fromJson(steps.atmos-settings.outputs.settings).terraform-state-bucket }}