diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index 2855267..8ddfa4d 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -3,7 +3,7 @@ name: Deploy to Development on: push: branches: - - main + - dev permissions: id-token: write diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml index 76ea1fd..8ac2534 100644 --- a/.github/workflows/deploy-staging.yml +++ b/.github/workflows/deploy-staging.yml @@ -1,4 +1,4 @@ -name: Deploy Dev +name: Deploy to Staging on: push: @@ -9,6 +9,10 @@ permissions: id-token: write contents: read +concurrency: + group: staging-deployment + cancel-in-progress: true + jobs: deploy: runs-on: ubuntu-latest @@ -17,7 +21,7 @@ jobs: defaults: run: - working-directory: vite-app + working-directory: . steps: - uses: actions/checkout@v4 @@ -25,22 +29,27 @@ jobs: - uses: actions/setup-node@v4 with: node-version: 22 + cache: npm + cache-dependency-path: package-lock.json - - run: npm install - - - run: npm run test -- --run + - name: Install dependencies and build + run: npm ci - - run: npm run build + - name: Build application + run: npm run build - - uses: aws-actions/configure-aws-credentials@v4 + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_ROLE_ARN }} aws-region: us-east-1 - - run: | + - name: Sync files to S3 + run: | aws s3 sync dist/ s3://${{ secrets.S3_BUCKET }} --delete - - run: | + - name: Invalidate CloudFront distribution + run: | aws cloudfront create-invalidation \ --distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} \ --paths "/*" diff --git a/README.md b/README.md index a57e5f3..030f331 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,8 @@ This system provisions and connects: ## ⚡ Features +This project was built with: + - Fully automated CI/CD pipeline (GitHub Actions) - Secure AWS authentication using OIDC (no long-lived AWS keys) - Environment-based deployments (dev / staging / prod) @@ -94,7 +96,7 @@ This system provisions and connects: Before using this project, ensure you have: - AWS Account -- Terraform ≥ 1.10+ +- Terraform ≥ 1.10+ (Required for S3 file lock feature introduced in v.1.10. enabling file lock in S3 allows us to lock our state file without the need for DynamoDB + S3 lock feature which is being deprecated by AWS) - Node.js ≥ 20+ - GitHub repository - AWS CLI configured (for local testing) @@ -111,20 +113,16 @@ git clone https://github.com/ecoderP/s3-static-app-terraform.git ### Before you continue, Please note: -- I have preset customisable terraform variables in .tfvarsexample. +- There are preset customisable terraform variables in .tfvarsexample. - Terraform state backend configurations are in .tfbackendexample files. -I named these this way to bypass .gitignore, because git will ignore all .tfvars and .tfbackend files. You will need to rename .tfvarsexample and .tfbackendexample to .tfvars and .tfbackend extensions respectively. +These are so named to bypass .gitignore. Gitgnore will ignore all .tfvars and .tfbackend files for security. You will need to rename .tfvarsexample and .tfbackendexample to .tfvars and .tfbackend extensions respectively. -For example, for bootstrap/ directory: +For example, for terraform/bootstrap/ directory, update configuration settings, then: ``` cd terraform/bootstrap -``` -After updating your AWS region, project name and your unique bucket name: - -``` cp terraform.tfvarsbackendexample terraform.tfvars ``` @@ -140,7 +138,8 @@ terraform init **_Important:_** Copy the bucket name from terminal output. This is the shared backend state bucket name for all environments. Use this output as bucket name in .tfbackend for all environments. 4. Configure environment - Each environment (dev/staging/prod) has its own configuration. Locate .tfbackend and .tfvars configuration files, personalise and rename for each environment. + +Each environment (dev/staging/prod) has its own configuration. Locate .tfbackend and .tfvars configuration files, personalise and rename for each environment. ``` cd terraform/environments/dev @@ -165,7 +164,9 @@ terraform apply -auto-approve This project uses GitHub Actions → AWS OIDC federation, meaning: ✔ No AWS access keys stored in GitHub + ✔ Temporary credentials issued per workflow run + ✔ Least-privilege IAM roles scoped per environment ### IAM Role Trust Relationship @@ -186,18 +187,19 @@ This project includes GitHub Actions workflows for: ### 🧪 Dev Deployment -- Trigger: push to develop +- Trigger: push to dev - Deploys to dev S3 bucket + CloudFront ### 🧱 Staging Deployment - Trigger: push to staging +- Deploys to staging S3 bucket + CloudFront - Used for pre-production validation ### 🚀 Production Deployment - Trigger: push to main -- Deploys stable build to production environment +- Deploys stable build to production environment (S3 + CloudFront) ### CI/CD Flow @@ -210,13 +212,13 @@ This project includes GitHub Actions workflows for: ### Important GitHub Actions secrets -To get your ci/cd pipeline working, add the following environment secrets to GitHub Actions: +To get your CI/CD pipeline working, add the following environment secrets to GitHub Actions: - S3_BUCKET - CLOUDFRONT_DISTRIBUTION_ID - AWS_ROLE_ARN -To get the values for your project, from each environment directory (dev, staging, prod), run: +To get the values for your secrets, from each environment directory (dev, staging, prod), run: ``` terraform output @@ -224,7 +226,7 @@ terraform output --- -## ♻️ How to Re-use This Project (Important) +## ♻️ Re-using This Project (Some Viable Options) This repo is designed as a starter backend infrastructure for any React + Vite frontend project. diff --git a/terraform/environments/staging/.terraform.lock.hcl b/terraform/environments/staging/.terraform.lock.hcl new file mode 100644 index 0000000..92a2bcc --- /dev/null +++ b/terraform/environments/staging/.terraform.lock.hcl @@ -0,0 +1,25 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "5.100.0" + constraints = "~> 5.0" + hashes = [ + "h1:H3mU/7URhP0uCRGK8jeQRKxx2XFzEqLiOq/L2Bbiaxs=", + "zh:054b8dd49f0549c9a7cc27d159e45327b7b65cf404da5e5a20da154b90b8a644", + "zh:0b97bf8d5e03d15d83cc40b0530a1f84b459354939ba6f135a0086c20ebbe6b2", + "zh:1589a2266af699cbd5d80737a0fe02e54ec9cf2ca54e7e00ac51c7359056f274", + "zh:6330766f1d85f01ae6ea90d1b214b8b74cc8c1badc4696b165b36ddd4cc15f7b", + "zh:7c8c2e30d8e55291b86fcb64bdf6c25489d538688545eb48fd74ad622e5d3862", + "zh:99b1003bd9bd32ee323544da897148f46a527f622dc3971af63ea3e251596342", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:9f8b909d3ec50ade83c8062290378b1ec553edef6a447c56dadc01a99f4eaa93", + "zh:aaef921ff9aabaf8b1869a86d692ebd24fbd4e12c21205034bb679b9caf883a2", + "zh:ac882313207aba00dd5a76dbd572a0ddc818bb9cbf5c9d61b28fe30efaec951e", + "zh:bb64e8aff37becab373a1a0cc1080990785304141af42ed6aa3dd4913b000421", + "zh:dfe495f6621df5540d9c92ad40b8067376350b005c637ea6efac5dc15028add4", + "zh:f0ddf0eaf052766cfe09dea8200a946519f653c384ab4336e2a4a64fdd6310e9", + "zh:f1b7e684f4c7ae1eed272b6de7d2049bb87a0275cb04dbb7cda6636f600699c9", + "zh:ff461571e3f233699bf690db319dfe46aec75e58726636a0d97dd9ac6e32fb70", + ] +} diff --git a/terraform/environments/staging/outputs.tf b/terraform/environments/staging/outputs.tf index 3701b68..ff2e8f3 100644 --- a/terraform/environments/staging/outputs.tf +++ b/terraform/environments/staging/outputs.tf @@ -4,4 +4,12 @@ output "distribution_domain_name" { output "bucket_name" { value = module.s3_static_site.bucket_name +} + +output "github_actions_role_arn" { + value = module.github_oidc.github_actions_role_arn +} + +output "cloudfront_distribution_id" { + value = module.cloudfront.distribution_id } \ No newline at end of file