diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 0000000..721ba55 --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,233 @@ +name: Terraform CD + +on: + push: + branches: + - main + paths: + - 'terraform/**' + - '.github/workflows/cd.yml' + + workflow_dispatch: + inputs: + plan_only: + description: 'Run plan only (no apply)' + required: false + default: 'false' + type: choice + options: + - 'true' + - 'false' + + destroy: + description: 'Destroy all infrastructure (DANGEROUS)' + required: false + default: 'false' + type: choice + options: + - 'true' + - 'false' + +permissions: + contents: read + id-token: write + issues: write + +env: + AWS_REGION: us-west-2 + TF_WORKING_DIR: terraform + TERRAFORM_VERSION: 1.6.0 + +jobs: + terraform-plan: + name: Plan Infrastructure Changes + runs-on: ubuntu-latest + + environment: + name: production + + defaults: + run: + working-directory: ${{ env.TF_WORKING_DIR }} + + outputs: + has_changes: ${{ steps.plan.outputs.exitcode == 2 }} + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_SECRET_ACCESS_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ env.AWS_REGION }} + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: ${{ env.TERRAFORM_VERSION }} + terraform_wrapper: false + + - name: Terraform Format Check + run: terraform fmt -check -recursive + + - name: Terraform Init + run: terraform init -upgrade + + - name: Terraform Validate + run: terraform validate + + - name: Terraform Plan + id: plan + run: | + terraform plan -detailed-exitcode -out=tfplan -input=false || exit_code=$? + echo "exitcode=$exit_code" >> $GITHUB_OUTPUT + terraform show tfplan -no-color + continue-on-error: true + + - name: Upload Plan Artifact + if: steps.plan.outputs.exitcode == 2 + uses: actions/upload-artifact@v4 + with: + name: terraform-plan-${{ github.sha }} + path: ${{ env.TF_WORKING_DIR }}/tfplan + retention-days: 1 + + - name: Create Deployment Summary + if: always() + run: | + echo "## Terraform Plan Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Workflow:** ${{ github.workflow }}" >> $GITHUB_STEP_SUMMARY + echo "- **Triggered by:** ${{ github.actor }}" >> $GITHUB_STEP_SUMMARY + echo "- **Commit:** ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY + echo "- **Plan Status:** ${{ steps.plan.outcome }}" >> $GITHUB_STEP_SUMMARY + echo "- **Has Changes:** ${{ steps.plan.outputs.exitcode == 2 }}" >> $GITHUB_STEP_SUMMARY + + terraform-apply: + name: Apply Infrastructure Changes + runs-on: ubuntu-latest + + needs: terraform-plan + if: | + needs.terraform-plan.result == 'success' && + needs.terraform-plan.outputs.has_changes == 'true' && + github.event.inputs.plan_only != 'true' + + defaults: + run: + working-directory: ${{ env.TF_WORKING_DIR }} + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_SECRET_ACCESS_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ env.AWS_REGION }} + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: ${{ env.TERRAFORM_VERSION }} + + - name: Terraform Init + run: terraform init -upgrade + + - name: Download Plan Artifact + uses: actions/download-artifact@v4 + with: + name: terraform-plan-${{ github.sha }} + path: ${{ env.TF_WORKING_DIR }} + + - name: Terraform Apply + id: apply + run: | + echo "Applying Terraform changes..." + terraform apply -auto-approve -input=false tfplan + + - name: Get Terraform Outputs + id: outputs + if: steps.apply.outcome == 'success' + run: | + echo "instance_id=$(terraform output -raw instance_id)" >> $GITHUB_OUTPUT + echo "instance_public_ip=$(terraform output -raw instance_public_ip)" >> $GITHUB_OUTPUT + echo "instance_private_ip=$(terraform output -raw instance_private_ip)" >> $GITHUB_OUTPUT + + - name: Create Deployment Summary + if: always() + run: | + echo "## Terraform Apply Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Workflow:** ${{ github.workflow }}" >> $GITHUB_STEP_SUMMARY + echo "- **Triggered by:** ${{ github.actor }}" >> $GITHUB_STEP_SUMMARY + echo "- **Commit:** ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY + echo "- **Apply Status:** ${{ steps.apply.outcome }}" >> $GITHUB_STEP_SUMMARY + + if [ "${{ steps.apply.outcome }}" == "success" ]; then + echo "" >> $GITHUB_STEP_SUMMARY + echo "### EC2 Instance Information" >> $GITHUB_STEP_SUMMARY + echo "- **Instance ID:** ${{ steps.outputs.outputs.instance_id }}" >> $GITHUB_STEP_SUMMARY + echo "- **Public IP:** ${{ steps.outputs.outputs.instance_public_ip }}" >> $GITHUB_STEP_SUMMARY + echo "- **Private IP:** ${{ steps.outputs.outputs.instance_private_ip }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Connect to Instance" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY + echo "ssh -i ~/.ssh/ashish-test-kp.pem ec2-user@${{ steps.outputs.outputs.instance_public_ip }}" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + fi + + - name: Notify on Failure + if: failure() + run: | + echo "::error::Terraform apply failed. Please check the logs and fix the issues." + + terraform-destroy: + name: Destroy Infrastructure + runs-on: ubuntu-latest + + if: github.event_name == 'workflow_dispatch' && github.event.inputs.destroy == 'true' + + environment: + name: destroy-production + + defaults: + run: + working-directory: ${{ env.TF_WORKING_DIR }} + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_SECRET_ACCESS_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ env.AWS_REGION }} + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: ${{ env.TERRAFORM_VERSION }} + + - name: Terraform Init + run: terraform init -upgrade + + - name: Terraform Destroy + run: | + echo "Destroying all infrastructure..." + terraform destroy -auto-approve -input=false + + - name: Confirm Destruction + run: | + echo "## Infrastructure Destroyed" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "All resources have been destroyed." >> $GITHUB_STEP_SUMMARY + echo "- **Triggered by:** ${{ github.actor }}" >> $GITHUB_STEP_SUMMARY + echo "- **Timestamp:** $(date)" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..1ed5c9e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,128 @@ +name: Terraform CI + +on: + pull_request: + branches: + - '**' + paths: + - 'terraform/**' + - '.github/workflows/ci.yml' + + push: + branches: + - '**' + - '!main' + paths: + - 'terraform/**' + - '.github/workflows/ci.yml' + +permissions: + contents: read + pull-requests: write + issues: write + +env: + AWS_REGION: us-west-2 + TF_WORKING_DIR: terraform + TERRAFORM_VERSION: 1.6.0 + +jobs: + terraform-validate: + name: Validate Terraform Configuration + runs-on: ubuntu-latest + + defaults: + run: + working-directory: ${{ env.TF_WORKING_DIR }} + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_SECRET_ACCESS_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ env.AWS_REGION }} + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: ${{ env.TERRAFORM_VERSION }} + + - name: Terraform Format Check + id: fmt + run: terraform fmt -check -recursive + continue-on-error: true + + - name: Terraform Init + id: init + run: terraform init -upgrade -backend=false + + - name: Terraform Validate + id: validate + run: terraform validate -no-color + + - name: Terraform Plan + id: plan + run: | + terraform init -upgrade + terraform plan -no-color -input=false -out=tfplan + continue-on-error: true + + - name: Post Plan to PR + if: github.event_name == 'pull_request' + uses: actions/github-script@v7 + env: + PLAN_OUTPUT: ${{ steps.plan.outputs.stdout }} + 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 + + \`\`\`terraform + ${{ steps.plan.outputs.stdout }} + \`\`\` + +
+ + *Pusher: @${{ github.actor }}, Action: \`${{ github.event_name }}\`, Workflow: \`${{ github.workflow }}\`*`; + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: output + }); + + - name: Check Plan Status + if: steps.plan.outcome == 'failure' + run: exit 1 + + - name: Upload Plan Artifact + if: steps.plan.outcome == 'success' + uses: actions/upload-artifact@v4 + with: + name: terraform-plan + path: ${{ env.TF_WORKING_DIR }}/tfplan + retention-days: 5 + + terraform-security-scan: + name: Security Scan + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Run tfsec + uses: aquasecurity/tfsec-action@v1.0.3 + with: + working_directory: ${{ env.TF_WORKING_DIR }} + soft_fail: true \ No newline at end of file diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 0000000..33c179b --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,50 @@ +resource "aws_security_group" "mongodb" { + name = "mongodb-sg" + description = "Security group for MongoDB EC2 instance" + vpc_id = var.vpc_id + + ingress { + description = "SSH" + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + ingress { + description = "MongoDB" + from_port = 27017 + to_port = 27017 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + egress { + description = "Allow all outbound" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "MongoDB Security Group" + } +} + +resource "aws_instance" "mongodb" { + ami = "ami-001fbc42d7df13f78" + instance_type = var.instance_type + key_name = var.key_name + + vpc_security_group_ids = [aws_security_group.mongodb.id] + + root_block_device { + volume_size = 8 + volume_type = "gp3" + } + + tags = { + Name = "MongoDB-Instance" + } +} diff --git a/terraform/outputs.tf b/terraform/outputs.tf new file mode 100644 index 0000000..c7c4961 --- /dev/null +++ b/terraform/outputs.tf @@ -0,0 +1,14 @@ +output "instance_id" { + description = "ID of the EC2 instance" + value = aws_instance.mongodb.id +} + +output "instance_public_ip" { + description = "Public IP address of the EC2 instance" + value = aws_instance.mongodb.public_ip +} + +output "instance_private_ip" { + description = "Private IP address of the EC2 instance" + value = aws_instance.mongodb.private_ip +} \ No newline at end of file diff --git a/terraform/terraform.tf b/terraform/terraform.tf new file mode 100644 index 0000000..09b57da --- /dev/null +++ b/terraform/terraform.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } +} + +provider "aws" { + region = var.aws_region +} diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 0000000..291ff90 --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,23 @@ +variable "aws_region" { + description = "AWS region" + type = string + default = "us-west-2" +} + +variable "instance_type" { + description = "EC2 instance type" + type = string + default = "t3.large" +} + +variable "key_name" { + description = "Key pair name for SSH access" + type = string + default = "ashish-test-kp" +} + +variable "vpc_id" { + description = "VPC ID for the instance" + type = string + default = "vpc-0c4669393c94b8f86" +}