diff --git a/.github/workflows/atlantis.yml b/.github/workflows/atlantis.yml index 4bd1485..96a9dd4 100644 --- a/.github/workflows/atlantis.yml +++ b/.github/workflows/atlantis.yml @@ -1,15 +1,18 @@ -name: Terraform with Atlantis +name: Atlantis on: pull_request: - types: [opened, synchronize, reopened] + types: [opened, synchronize, reopened, closed] pull_request_review: types: [submitted] + pull_request_review_comment: + types: [created] issue_comment: types: [created] push: branches: - main + - master permissions: contents: read @@ -18,178 +21,165 @@ permissions: statuses: write jobs: - terraform: + atlantis: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - - - name: Setup Terraform - uses: hashicorp/setup-terraform@v2 - with: - terraform_version: 1.0.0 - - - name: Setup Google Cloud SDK - uses: google-github-actions/setup-gcloud@v0.6.0 with: - project_id: polished-tube-312806 - service_account_key: ${{ secrets.GCP_SA_KEY }} - export_default_credentials: true + fetch-depth: 0 - - name: Download Atlantis + # Determine if this is a PR comment and if it contains an Atlantis command + - name: Check for Atlantis command + id: check_comment + if: github.event_name == 'issue_comment' && github.event.issue.pull_request run: | - wget https://github.com/runatlantis/atlantis/releases/download/v0.19.8/atlantis_linux_amd64.zip - unzip atlantis_linux_amd64.zip - sudo mv atlantis /usr/local/bin/ + COMMENT="${{ github.event.comment.body }}" + if [[ "$COMMENT" == "atlantis "* ]]; then + echo "command=$(echo $COMMENT | cut -d' ' -f2-)" >> $GITHUB_OUTPUT + echo "Found Atlantis command: $(echo $COMMENT | cut -d' ' -f2-)" + else + echo "No Atlantis command found" + fi - - name: Create Atlantis config - run: | - cat > atlantis-config.yaml <> terraform.auto.tfvars - else - echo 'ssh_pub_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDummy"' >> terraform.auto.tfvars - fi - terraform plan -no-color - continue-on-error: true + echo "pr_number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT - - name: Update Pull Request - uses: actions/github-script@v6 + # If this is a comment, get the PR number from the issue + - name: Get PR number from comment + id: get_pr_from_comment + if: github.event_name == 'issue_comment' && github.event.issue.pull_request + run: | + echo "pr_number=${{ github.event.issue.number }}" >> $GITHUB_OUTPUT + + # Checkout the PR branch if this is a comment on a PR + - name: Checkout PR branch + if: github.event_name == 'issue_comment' && github.event.issue.pull_request && steps.check_comment.outputs.command != '' + run: | + PR_NUMBER=${{ steps.get_pr_from_comment.outputs.pr_number }} + git fetch origin pull/$PR_NUMBER/head:pr-$PR_NUMBER + git checkout pr-$PR_NUMBER + + # Run Atlantis in Docker for autoplan on PR + - name: Run Atlantis Autoplan if: github.event_name == 'pull_request' env: - PLAN: "terraform\n${{ steps.plan.outputs.stdout }}" + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }} + GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }} + run: | + # Create a directory for Atlantis data + mkdir -p ~/.atlantis + + # Set default project ID if not provided + PROJECT_ID="${GCP_PROJECT_ID:-default-project-id}" + + # Run Atlantis server in Docker with autoplan + docker run --rm \ + -e ATLANTIS_GH_TOKEN="${GITHUB_TOKEN}" \ + -e ATLANTIS_GH_USER="${{ github.repository_owner }}" \ + -e ATLANTIS_GH_WEBHOOK_SECRET="dummy-secret" \ + -e ATLANTIS_REPO_ALLOWLIST="${{ github.repository }}" \ + -e ATLANTIS_ATLANTIS_URL="https://github.com/${{ github.repository }}/pull/${{ steps.get_pr.outputs.pr_number }}" \ + -e GOOGLE_CREDENTIALS="${GCP_SA_KEY}" \ + -e TF_VAR_project_id="${PROJECT_ID}" \ + -v "$(pwd):/atlantis/repos" \ + -v ~/.atlantis:/atlantis/data \ + ghcr.io/runatlantis/atlantis:v0.22.3 \ + plan \ + --dir=. \ + --workspace=default \ + --project=terraform-gcp-vm-instance > /tmp/atlantis_output.txt 2>&1 || true + + # Display output for debugging + cat /tmp/atlantis_output.txt + + # Run Atlantis in Docker for comment commands + - name: Run Atlantis Command + if: github.event_name == 'issue_comment' && github.event.issue.pull_request && steps.check_comment.outputs.command != '' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }} + GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }} + run: | + # Create a directory for Atlantis data + mkdir -p ~/.atlantis + + # Set default project ID if not provided + PROJECT_ID="${GCP_PROJECT_ID:-default-project-id}" + + # Extract the Atlantis command + ATLANTIS_CMD=$(echo "${{ steps.check_comment.outputs.command }}" | cut -d' ' -f1) + ATLANTIS_ARGS=$(echo "${{ steps.check_comment.outputs.command }}" | cut -d' ' -f2- || echo "") + + # Run Atlantis server in Docker with the specified command + docker run --rm \ + -e ATLANTIS_GH_TOKEN="${GITHUB_TOKEN}" \ + -e ATLANTIS_GH_USER="${{ github.repository_owner }}" \ + -e ATLANTIS_GH_WEBHOOK_SECRET="dummy-secret" \ + -e ATLANTIS_REPO_ALLOWLIST="${{ github.repository }}" \ + -e ATLANTIS_ATLANTIS_URL="https://github.com/${{ github.repository }}/pull/${{ steps.get_pr_from_comment.outputs.pr_number }}" \ + -e GOOGLE_CREDENTIALS="${GCP_SA_KEY}" \ + -e TF_VAR_project_id="${PROJECT_ID}" \ + -v "$(pwd):/atlantis/repos" \ + -v ~/.atlantis:/atlantis/data \ + ghcr.io/runatlantis/atlantis:v0.22.3 \ + ${ATLANTIS_CMD} ${ATLANTIS_ARGS} \ + --dir=. \ + --workspace=default \ + --project=terraform-gcp-vm-instance > /tmp/atlantis_output.txt 2>&1 || true + + # Display output for debugging + cat /tmp/atlantis_output.txt + + # Comment on PR with autoplan output + - name: Comment on PR with autoplan output + if: github.event_name == 'pull_request' + uses: actions/github-script@v6 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | - const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\` - #### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\` - #### Terraform Validation 🤖\`${{ steps.validate.outcome }}\` - #### Terraform Plan 📖\`${{ steps.plan.outcome }}\` - -
Show Plan - - \`\`\` - ${process.env.PLAN} - \`\`\` - -
+ const fs = require('fs'); + let output; - *Pushed by: @${{ github.actor }}, Action: \`${{ github.event_name }}\`* + try { + output = fs.readFileSync('/tmp/atlantis_output.txt', 'utf8'); + } catch (error) { + output = 'Atlantis autoplan completed. No output captured.'; + } - *Note: Cost estimates will be provided by the Infracost GitHub app integration.*`; - github.rest.issues.createComment({ - issue_number: context.issue.number, + issue_number: ${{ github.event.pull_request.number }}, owner: context.repo.owner, repo: context.repo.repo, - body: output - }) - - - name: Process Atlantis Commands - id: atlantis_command - if: github.event_name == 'issue_comment' && contains(github.event.comment.body, 'atlantis') - run: | - # Set SSH key for CI/CD - use secret if available, otherwise use dummy key - if [ -n "${{ secrets.SSH_PUBLIC_KEY }}" ]; then - echo 'ssh_pub_key = "${{ secrets.SSH_PUBLIC_KEY }}"' >> terraform.auto.tfvars - else - echo 'ssh_pub_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDummy"' >> terraform.auto.tfvars - fi - - if [[ "${{ github.event.comment.body }}" == *"atlantis apply"* ]]; then - echo "command=apply" >> $GITHUB_OUTPUT - terraform apply -auto-approve | tee apply_output.txt - echo "Applied changes via Atlantis command" - elif [[ "${{ github.event.comment.body }}" == *"atlantis plan"* ]]; then - echo "command=plan" >> $GITHUB_OUTPUT - terraform plan | tee plan_output.txt - echo "Generated plan via Atlantis command" - fi - - - name: Comment on PR with Atlantis Command Result + body: `## Atlantis Autoplan Output\n\n\`\`\`\n${output}\n\`\`\`` + }); + + # Comment on PR with command output + - name: Comment on PR with command output + if: github.event_name == 'issue_comment' && github.event.issue.pull_request && steps.check_comment.outputs.command != '' uses: actions/github-script@v6 - if: github.event_name == 'issue_comment' && contains(github.event.comment.body, 'atlantis') with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const fs = require('fs'); - let commandOutput = ''; - let commandType = '${{ steps.atlantis_command.outputs.command }}'; + let output; try { - if (commandType === 'apply') { - commandOutput = fs.readFileSync('apply_output.txt', 'utf8'); - title = '🚀 Terraform Apply Completed'; - } else if (commandType === 'plan') { - commandOutput = fs.readFileSync('plan_output.txt', 'utf8'); - title = '📋 Terraform Plan Generated'; - } - - const output = `### ${title} - -
Show Output - - \`\`\` - ${commandOutput} - \`\`\` - -
- - *Command executed by: @${{ github.event.comment.user.login }} via \`${{ github.event.comment.body }}\`*`; - - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: output - }); + output = fs.readFileSync('/tmp/atlantis_output.txt', 'utf8'); } catch (error) { - console.log(`Error: ${error.message}`); - - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: `### ⚠️ Error Processing Atlantis Command - - There was an error processing the \`${{ github.event.comment.body }}\` command: - - \`\`\` - ${error.message} - \`\`\` - - Please check the GitHub Actions logs for more details.` - }); + output = 'Atlantis command completed. No output captured.'; } + + const command = "${{ steps.check_comment.outputs.command }}".split(' ')[0]; + + github.rest.issues.createComment({ + issue_number: ${{ github.event.issue.number }}, + owner: context.repo.owner, + repo: context.repo.repo, + body: `## Atlantis ${command.charAt(0).toUpperCase() + command.slice(1)} Output\n\n\`\`\`\n${output}\n\`\`\`` + }); diff --git a/README.md b/README.md index 3f2cd03..182a020 100644 --- a/README.md +++ b/README.md @@ -115,23 +115,35 @@ When prompted, type `yes` to confirm the deletion of resources. ## CI/CD with Terraform and Atlantis -This repository is configured with a GitHub Actions workflow that integrates Terraform with Atlantis-style commands. This setup automates Terraform plan and apply operations in response to pull requests and comments. +This repository is configured with a GitHub Actions workflow that integrates Terraform with Atlantis for automated infrastructure management. The workflow uses the official Atlantis Docker image to run Atlantis commands directly in response to pull requests and comments. ### How it works -1. When you create a pull request that modifies Terraform files (*.tf, *.tfvars), the workflow automatically runs `terraform plan` and posts the results as a comment on the PR. -2. To apply the changes, comment on the PR with: +1. When you create a pull request that modifies Terraform files (*.tf, *.tfvars), the workflow automatically runs Atlantis autoplan and posts the results as a comment on the PR. +2. You can use the following Atlantis commands in PR comments: ``` - atlantis apply + atlantis plan # Generate a new plan + atlantis apply # Apply the current plan + atlantis unlock # Unlock the Terraform state with the specified lock ID + atlantis approve_policies # Approve any policy checks + atlantis version # Show the Atlantis version + atlantis help # Show help information ``` -3. To generate a new plan, comment on the PR with: +3. Example usage: ``` + # To run a plan atlantis plan + + # To apply changes + atlantis apply + + # To unlock a state + atlantis unlock 1234abcd-ef56-7890 ``` -4. The workflow will process these commands and execute the corresponding Terraform operations. -5. After processing the command, the workflow will post a comment with the results, including the full output of the Terraform command. +4. The workflow runs the Atlantis Docker container with your specified command and captures the output. +5. After processing the command, the workflow posts a comment on the PR with the results, including the full output from Atlantis. -The GitHub Actions workflow is configured to respond to comments containing "atlantis" commands, providing a similar experience to the actual Atlantis server but without requiring a separate server deployment. +The GitHub Actions workflow is configured to respond to comments containing Atlantis commands, providing the same experience as a dedicated Atlantis server but without requiring a separate server deployment. It uses the official Atlantis Docker image to ensure compatibility and consistent behavior with standard Atlantis deployments. ### Infracost Integration @@ -158,9 +170,11 @@ resource "google_compute_network" "vpc_network" { ### GitHub Secrets Required -For the GitHub Actions workflow to function properly, you need to set up the following secrets in your GitHub repository: +For the Atlantis GitHub Actions workflow to function properly, you need to set up the following secrets in your GitHub repository: -- `GCP_SA_KEY`: The JSON key of a GCP service account with appropriate permissions for the resources in your Terraform configuration. +- `GCP_SA_KEY`: The JSON key of a GCP service account with appropriate permissions for the resources in your Terraform configuration. This is passed to the Atlantis Docker container as the `GOOGLE_CREDENTIALS` environment variable. +- `GCP_PROJECT_ID`: Your Google Cloud project ID. This is passed to the Atlantis Docker container as the `TF_VAR_project_id` environment variable. +- `GITHUB_TOKEN`: This is automatically provided by GitHub Actions and is used by Atlantis to comment on PRs and interact with the GitHub API. - `SSH_PUBLIC_KEY` (optional): Your SSH public key for VM access. If not provided, a dummy key will be used in CI/CD environments. You can use the provided `setup-github-secrets.sh` script to help you create a GCP service account and set up the required GitHub secrets: @@ -202,7 +216,7 @@ If you want to run Atlantis locally for testing: - `outputs.tf`: Output definitions - `terraform.tfvars`: Variable values - `atlantis.yaml`: Atlantis configuration file -- `.github/workflows/atlantis.yml`: GitHub Actions workflow for Terraform with Atlantis-style commands +- `.github/workflows/atlantis.yml`: GitHub Actions workflow for Terraform with Atlantis comment-based operations - `setup-github-secrets.sh`: Helper script to set up GitHub secrets for CI/CD ## Notes diff --git a/setup-github-secrets.sh b/setup-github-secrets.sh index ad68dd5..0766585 100755 --- a/setup-github-secrets.sh +++ b/setup-github-secrets.sh @@ -80,6 +80,10 @@ if [ "$MANUAL_SETUP" = false ]; then echo "Setting GCP_SA_KEY secret in GitHub repository..." gh secret set GCP_SA_KEY -b"$(cat gcp-sa-key.json)" -R $GITHUB_USERNAME/$REPO_NAME + # Set GCP_PROJECT_ID secret + echo "Setting GCP_PROJECT_ID secret in GitHub repository..." + gh secret set GCP_PROJECT_ID -b"$PROJECT_ID" -R $GITHUB_USERNAME/$REPO_NAME + echo "GitHub secrets set up successfully!" else echo "" @@ -88,9 +92,11 @@ else echo "1. Go to your GitHub repository: https://github.com/$GITHUB_USERNAME/$REPO_NAME" echo "2. Navigate to Settings > Secrets and variables > Actions" echo "3. Click on 'New repository secret'" - echo "4. Add the following secret:" - echo " Name: GCP_SA_KEY" - echo " Value: (Copy the contents of the gcp-sa-key.json file)" + echo "4. Add the following secrets:" + echo " a. Name: GCP_SA_KEY" + echo " Value: (Copy the contents of the gcp-sa-key.json file)" + echo " b. Name: GCP_PROJECT_ID" + echo " Value: $PROJECT_ID" echo "" fi diff --git a/variables.tf b/variables.tf index f0cefc9..303b1a7 100644 --- a/variables.tf +++ b/variables.tf @@ -19,7 +19,7 @@ variable "zone" { variable "machine_type" { description = "The machine type for VM instances" type = string - default = "e2-small" + default = "e2-medium" } variable "ssh_username" {