diff --git a/.github/workflows/terraform.yaml b/.github/workflows/terraform.yaml new file mode 100644 index 0000000..f686aac --- /dev/null +++ b/.github/workflows/terraform.yaml @@ -0,0 +1,196 @@ +name: "Terraform CICD - AWS ECS Instance" + +on: + pull_request: + branches: + - main + push: + branches: + - main + +permissions: + contents: read + issues: write + pull-requests: write + +env: + # Verbosity setting for Terraform logs + TF_LOG: ERROR + # Credentials for deployment to AWS + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + # S3 bucket for the Terraform state + BUCKET_TF_STATE: ${{ secrets.BUCKET_TF_STATE}} + +jobs: +# Terraform Dev CICD + terraform-dev: + name: "Terraform Infra CICD Dev" + runs-on: ubuntu-latest + defaults: + run: + shell: bash + working-directory: infra + environment: dev + + steps: + - name: Checkout the repository to the runner + uses: actions/checkout@v4 + + - name: Setup Terraform with specified version on the runner + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: 1.11.3 + + - name: Terraform init dev + id: init-dev + run: terraform init -reconfigure -backend-config=bucket=$BUCKET_TF_STATE + + # Quality checks DEV + - name: Terraform format + id: fmt-dev + if: github.event_name == 'pull_request' + run: terraform fmt -check + + - name: Terraform validate + id: validate-dev + if: github.event_name == 'pull_request' + run: terraform validate + + - name: Terraform plan - dev + id: plan-dev + if: github.event_name == 'pull_request' + run: terraform plan -var-file=envs/dev.tfvars -no-color -input=false + continue-on-error: true + + - uses: actions/github-script@v7 + if: github.event_name == 'pull_request' + env: + PLAN: "terraform\n${{ steps.plan-dev.outputs.stdout }}" + with: + script: | + const output = `#### Terraform Format and Style - DEV πŸ–Œ\`${{ steps.fmt-dev.outcome }}\` + #### Terraform Initialization - DEV βš™οΈ\`${{ steps.init-dev.outcome }}\` + #### Terraform Validation - DEV πŸ€–\`${{ steps.validate-dev.outcome }}\` + #### Terraform Plan - DEV πŸ“–\`${{ steps.plan-dev.outcome }}\` + +
Show Plan + + \`\`\`\n + ${process.env.PLAN} + \`\`\` + +
+ *Pushed by: @${{ github.actor }}, Action: \`${{ github.event_name }}\`*`; + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: output + }) + + - name: Terraform Plan Status + if: steps.plan-dev.outcome == 'failure' + run: exit 1 + + - name: Terraform Apply + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + run: terraform apply -auto-approve -input=false + +# # Terraform Prod CI +# terraform-prod-ci: +# name: "Terraform Infra CI Prod" +# runs-on: ubuntu-latest +# defaults: +# run: +# shell: bash +# environment: prod + +# steps: +# - name: Checkout the repository to the runner +# uses: actions/checkout@v4 + +# - name: Setup Terraform with specified version on the runner +# uses: hashicorp/setup-terraform@v3 +# with: +# terraform_version: 1.11.3 + +# - name: Terraform init prod +# id: init-prod +# run: terraform init -reconfigure -backend-config=bucket=$BUCKET_TF_STATE #Create new bucket for prod + +# # Quality checks PROD +# - name: Terraform format +# id: fmt-prod +# if: github.event_name == 'pull_request' +# run: terraform fmt -check + +# - name: Terraform validate +# id: validate-prod +# if: github.event_name == 'pull_request' +# run: terraform validate + +# - name: Terraform plan - prod +# id: plan-prod +# if: github.event_name == 'pull_request' +# run: terraform plan -var-file=envs/prod.tfvars -no-color -input=false +# continue-on-error: true + +# - uses: actions/github-script@v6 +# if: github.event_name == 'pull_request' +# env: +# PLAN: "terraform\n${{ steps.plan-prod.outputs.stdout }}" +# with: +# script: | +# const output = `#### Terraform Format and Style - PROD πŸ–Œ\`${{ steps.fmt-prod.outcome }}\` +# #### Terraform Initialization - PROD βš™οΈ\`${{ steps.init-prod.outcome }}\` +# #### Terraform Validation - PROD πŸ€–\`${{ steps.validate-prod.outcome }}\` +# #### Terraform Plan - PROD πŸ“–\`${{ steps.plan-prod.outcome }}\` + +#
Show Plan + +# \`\`\`\n +# ${process.env.PLAN} +# \`\`\` + +#
+# *Pushed by: @${{ github.actor }}, Action: \`${{ github.event_name }}\`*`; + +# github.rest.issues.createComment({ +# issue_number: context.issue.number, +# owner: context.repo.owner, +# repo: context.repo.repo, +# body: output +# }) + +# - name: Terraform Plan Status +# if: steps.plan-prod.outcome == 'failure' +# run: exit 1 + +# # Terraform PROD CD +# terraform-prod-cd: +# name: "Terraform Infra CD Prod" +# needs: [terraform-dev, terraform-prod-ci] +# runs-on: ubuntu-latest +# defaults: +# run: +# shell: bash +# environment: prod + +# steps: +# - name: Checkout the repository to the runner +# uses: actions/checkout@v3 + +# - name: Setup Terraform with specified version on the runner +# uses: hashicorp/setup-terraform@v2 +# with: +# terraform_version: 1.3.9 + +# - name: Terraform init prod +# id: init-prod +# run: terraform init -reconfigure -backend-config=bucket=$BUCKET_TF_STATE + +# - name: Terraform Apply +# if: github.ref == 'refs/heads/main' && github.event_name == 'push' # only on push/merge to main +# run: terraform apply -auto-approve -input=false \ No newline at end of file diff --git a/README.md b/README.md index ac1d408..81618a7 100644 --- a/README.md +++ b/README.md @@ -17,45 +17,25 @@ The VPC, public subnets, Internet Gateway, and Terraform remote-state bucket (S3 --- -## Repo structure - -``` -infra/ -β”œβ”€ _backend.tf # remote state (S3 + DynamoDB) -β”œβ”€ _providers.tf # AWS provider / default tags -β”œβ”€ _variables.tf # all inputs -β”œβ”€ cluster.tf # ECS cluster (awsvpc) -β”œβ”€ asg_capacity.tf # ASG + capacity provider -β”œβ”€ task_definition.tf # image, ports, health check, logs -β”œβ”€ ecs_service.tf # service + load balancer attachment -β”œβ”€ cloudwatch_logs.tf -└─ envs/ - β”œβ”€ dev.tfvars - └─ prod.tfvars -``` - ---- - ## 1Β Β· Initialise Terraform (one‑time per env) ```bash cd infra -terraform init -backend-config="bucket=node-app-infra-tfstate-dev" -backend-config="profile=node-app-terraform-dev" +terraform init -reconfigure -backend-config=bucket=node-app-infra-tfstate-dev -backend-config=profile=node-app-terraform-dev ``` --- -## 2Β Β· Build & push the container image +## 2Β Β· Build & push the container image (Apply new version tag where appropriate) ```bash -docker buildx create --name multi --use 2>/dev/null || true -docker buildx build --platform linux/amd64 -t nrampling/demo-node-app:1.0.0 --push . +docker buildx build --platform linux/amd64 -t nrampling/demo-node-app:1.0.2 --push . ``` Update the image tag in `infra/envs/dev.tfvars`: ```hcl -docker_image = "nrampling/demo-node-app:1.0.0" +node_app_image = "nrampling/demo-node-app:1.0.2" ``` --- @@ -63,12 +43,12 @@ docker_image = "nrampling/demo-node-app:1.0.0" ## 3Β Β· Deploy with Terraform ```bash -AWS_PROFILE=node-app-terraform-dev terraform plan -var-file=envs/dev.tfvars +AWS_PROFILE=node-app-terraform-dev terraform plan -var-file=envs/dev.tfvars AWS_PROFILE=node-app-terraform-dev terraform apply -var-file=envs/dev.tfvars ``` -### Outputs (example) +### Outputs (example only - plug in aws account) ```text alb_dns_name = dev-app-alb-123456.ap-southeast-2.elb.amazonaws.com diff --git a/infra/envs/dev.tfvars b/infra/envs/dev.tfvars index e4e19ab..84cd5d3 100644 --- a/infra/envs/dev.tfvars +++ b/infra/envs/dev.tfvars @@ -6,4 +6,4 @@ alb_public_subnet_ids = ["subnet-055583b9b74d44b56", "subnet-0e9b56625d00f6c88", vpc_id = "vpc-0fabd74c01d8c9d4a" -node_app_image = "nrampling/demo-node-app:1.0.2" +node_app_image = "nrampling/demo-node-app:1.0.3" diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml index 62be5b7..a6ea2a0 100644 --- a/k8s/deployment.yaml +++ b/k8s/deployment.yaml @@ -14,7 +14,7 @@ spec: spec: containers: - name: demo-node-app - image: nrampling/demo-node-app:1.0.0 + image: nrampling/demo-node-app:1.0.3 ports: - containerPort: 3000 ---