Skip to content

Commit

Permalink
Re-adding files for s3 bucket deployment
Browse files Browse the repository at this point in the history
  • Loading branch information
ssylver93 committed Nov 27, 2024
2 parents a35189b + ab62c70 commit d63729b
Show file tree
Hide file tree
Showing 3 changed files with 358 additions and 1 deletion.
181 changes: 181 additions & 0 deletions .github/workflows/client-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
name: Deploy Angular App to S3 and CloudFront

on:
push:
branches:
- main # Adjust to your deployment branch
workflow_call:
inputs:
DEFAULT_APPLICATION_ENVIRONMENT:
required: true
type: string
IMAGE_TAG:
required: true
type: string
workflow_dispatch:
inputs:
TAG:
type: string
description: Additional tag to add to built images

env:
TF_VERSION: 1.8.5
TG_VERSION: 0.48.4
TG_SRC_PATH: terraform
TFC_PROJECT: ${{ secrets.TFC_PROJECT }}
TARGET_ENV: ${{ inputs.DEFAULT_APPLICATION_ENVIRONMENT }}

jobs:
build-and-deploy:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_TERRAFORM_ROLE_TO_ASSUME }}
role-session-name: wfprev-terraform-s3
aws-region: ca-central-1

- name: Set up Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: ${{ env.TF_VERSION }}

- name: Set up Terragrunt
uses: peter-murray/[email protected]
with:
terragrunt_version: ${{ env.TG_VERSION }}

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Install dependencies for Angular project
run: |
cd client/wfprev-war/src/main/angular
npm install
- name: Build Angular app
run: |
cd client/wfprev-war/src/main/angular
npm run build -- --configuration=production --base-href=/pub/wfprev/
# Fetch CloudFront Distribution ID in order to invalidate cache
- name: Get Terraform Outputs
working-directory: ${{ env.TG_SRC_PATH }}
env:
TFC_PROJECT: ${{ env.TFC_PROJECT }}
TARGET_ENV: ${{ env.TARGET_ENV }}
APP_COUNT: ${{vars.APP_COUNT}}
LOGGING_LEVEL: ${{vars.LOGGING_LEVEL}}

# Necessary for WFPREV API
WFPREV_API_NAME: wfprev-api
WFPREV_API_IMAGE: ${{ steps.getDigestAPI.outputs.IMAGE_API_BY_DIGEST }}
WFPREV_API_CPU_UNITS: ${{vars.WFPREV_API_CPU_UNITS}}
WFPREV_API_MEMORY: 1024
WFPREV_API_PORT: 8080
TARGET_AWS_ACCOUNT_ID: ${{secrets.TARGET_AWS_ACCOUNT_ID}}
WFPREV_CLIENT_ID: ${{vars.WFPREV_CLIENT_ID}}
WFPREV_CLIENT_SECRET: ${{secrets.WFPREV_CLIENT_SECRET}}
WEBADE_OAUTH2_CHECK_TOKEN_URL: ${{vars.WEBADE_OAUTH2_CHECK_TOKEN_URL}}
WEBADE_OAUTH2_CHECK_AUTHORIZE_URL: ${{vars.WEBADE_OAUTH2_CHECK_AUTHORIZE_URL}}
WFPREV_DATASOURCE_URL: ${{vars.WFPREV_DATASOURCE_URL}}
WFPREV_DATASOURCE_USERNAME: ${{secrets.WFPREV_USERNAME}}
WFPREV_DATASOURCE_PASSWORD: ${{secrets.DB_PASS}}
server_count: ${{vars.WFPREV_SERVER_INSTANCE_COUNT}}
# WFPREV UI
CLIENT_IMAGE: ${{ steps.getDigestUI.outputs.IMAGE_UI_BY_DIGEST }}
WEBADE_OAUTH2_WFPREV_UI_CLIENT_SECRET: ${{ secrets.WEBADE_OAUTH2_WFPREV_UI_CLIENT_SECRET }}

# DB
WFPREV_USERNAME: ${{secrets.WFPREV_USERNAME}}
DB_PASS: ${{secrets.DB_PASS}}
DB_INSTANCE_TYPE: ${{vars.DB_INSTANCE_TYPE}}

#liquibase
COMMAND: ${{ steps.liquibaseCommand.outputs.LIQUIBASE_COMMAND }}
PROXY_COUNT: 1
NONPROXY_COUNT: 1
CHANGELOG_NAME: ${{ inputs.CHANGELOG_NAME }}
LIQUIBASE_IMAGE: ${{vars.REPOSITORY_HOST}}/${{ github.repository_owner }}/${{ vars.LIQUIBASE_IMAGE }}:${{ inputs.IMAGE_TAG }}
LIQUIBASE_COMMAND_USERNAME: ${{ vars.LIQUIBASE_COMMAND_USERNAME }}
LIQUIBASE_COMMAND_PASSWORD: ${{ secrets.LIQUIBASE_COMMAND_PASSWORD }}
SCHEMA_NAME: ${{ inputs.SCHEMA_NAME }}
TARGET_LIQUIBASE_TAG: ${{ steps.liquibaseCommand.outputs.TARGET_LIQUIBASE_TAG }}

run: |
# Debug current directory and files
pwd
ls -la
# Debug environment variables
echo "TFC_PROJECT: $TFC_PROJECT"
echo "TARGET_ENV: $TARGET_ENV"
echo "Expected bucket: terraform-remote-state-${TFC_PROJECT}-${TARGET_ENV}"
# Show Terragrunt State List
echo "Terragrunt State List:"
terragrunt state list
# Try to refresh state
terragrunt refresh
# Get CloudFront ID with error checking
CLOUDFRONT_ID=$(terragrunt output -raw cloudfront_distribution_id | grep -o '^[a-zA-Z0-9-]\+')
echo "CloudFront ID: $CLOUDFRONT_ID"
if [ -z "$CLOUDFRONT_ID" ]; then
echo "Error: Failed to get CloudFront Distribution ID"
exit 1
fi
echo "CLOUDFRONT_DISTRIBUTION_ID=$CLOUDFRONT_ID" >> "$GITHUB_ENV"
# Get Github Actions Account ID with error checking
GITHUB_ACTIONS_ACCOUNT_ID=$(terragrunt output -raw github_actions_account_id | grep -o '^[a-zA-Z0-9-]\+')
echo "GitHub Actions Account ID: $GITHUB_ACTIONS_ACCOUNT_ID"
if [ -z "$GITHUB_ACTIONS_ACCOUNT_ID" ]; then
echo "Error: Failed to get Github Actions Account ID"
exit 1
fi
echo "GITHUB_ACTIONS_ACCOUNT_ID=$GITHUB_ACTIONS_ACCOUNT_ID" >> "$GITHUB_ENV"
# Get Github Actions Role Name with error checking
GITHUB_ACTIONS_ROLE_NAME=$(terragrunt output -raw github_actions_role_name | grep -o '^[a-zA-Z0-9-]\+')
echo "GitHub Actions Role Name: $GITHUB_ACTIONS_ROLE_NAME"
if [ -z "$GITHUB_ACTIONS_ROLE_NAME" ]; then
echo "Error: Failed to get Github Actions Role Name"
exit 1
fi
echo "GITHUB_ACTIONS_ROLE_NAME=$GITHUB_ACTIONS_ROLE_NAME" >> "$GITHUB_ENV"
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ env.GITHUB_ACTIONS_ACCOUNT_ID }}:role/${{ env.GITHUB_ACTIONS_ROLE_NAME }}
role-session-name: wfprev-push-s3
aws-region: ca-central-1

# this will require the bucket to exist
# so terraform step will need to run first
- name: Sync files to S3
run: |
aws s3 sync ./dist/wfprev s3://wfprev_site_bucket \
--delete \
--cache-control max-age=31536000,public \
--exclude index.html
aws s3 cp ./dist/wfprev/index.html s3://wfprev_site_bucket/index.html \
--cache-control max-age=0,no-cache,no-store,must-revalidate
- name: Invalidate CloudFront Cache
run: |
aws cloudfront create-invalidation \
--distribution-id ${{ env.CLOUDFRONT_DISTRIBUTION_ID }} \
--paths "/*"
# see distribution ID section in terraform scripts
# Like the sync, this means we need to run terraform first, then
# trigger this action with the returned distribution ID
134 changes: 133 additions & 1 deletion terraform/iam.tf
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,136 @@ resource "aws_iam_role_policy" "wfprev_ecs_task_execution_cwlogs" {
]
}
EOF
}
}

# Define an IAM policy to allow access to the SSM parameter
# This policy grants permissions to retrieve the specified SecureString parameter.
resource "aws_iam_policy" "ssm_parameter_access" {
name = "SSMParameterAccess"
description = "Allows access to SecureString parameters in SSM Parameter Store"

policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Effect = "Allow",
Action = [
"ssm:GetParameter",
"ssm:GetParameters",
"ssm:DescribeParameters"
],
Resource = "arn:aws:ssm:ca-central-1:183631341627:parameter/iam_users/wfprev_github_actions_user_keys"
}
]
})
}

# Attach the SSM parameter access policy to the GitHub Actions IAM user
# This links the user with the necessary permissions to read the SSM parameter securely.
resource "aws_iam_user_policy_attachment" "ssm_parameter_access_attachment" {
user = "wfprev_github_actions_user"
policy_arn = aws_iam_policy.ssm_parameter_access.arn
}

# Define an IAM policy for GitHub Actions user to perform specific operations
# This policy grants permissions to:
# - Upload/delete objects in an S3 bucket
# - Invalidate cached content in CloudFront
resource "aws_iam_user_policy" "github_actions_policy" {
name = "github-actions-policy"
user = "wfprev_github_actions_user"

policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Effect = "Allow",
Action = ["s3:PutObject", "s3:DeleteObject"],
Resource = "${aws_s3_bucket.wfprev_site_bucket.arn}/*"
},
{
Effect = "Allow",
Action = "cloudfront:CreateInvalidation",
Resource = "*"
}
]
})
}

# Create an IAM role for GitHub Actions to assume
resource "aws_iam_role" "github_actions_role" {
name = "github-actions-role"

assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
Federated = data.aws_iam_openid_connect_provider.github_actions.arn
}
Action = "sts:AssumeRoleWithWebIdentity"
Condition = {
StringEquals = {
"${aws_iam_openid_connect_provider.github_actions.url}:aud" : "sts.amazonaws.com"
"${aws_iam_openid_connect_provider.github_actions.url}:sub" : "repo:bcgov/nr-bcws-wfprev:*"
}
}
}
]
})
}

resource "aws_iam_policy" "github_actions_policy" {
name = "github-actions-policy"
description = "Policy for GitHub Actions"
policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Effect = "Allow",
Action = "s3:*",
Resource = "arn:aws:s3:::wfprev_site_bucket/*"
}
]
})
}

resource "aws_iam_policy" "oidc_viewer_policy" {
name = "oidc-viewer-policy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"iam:GetOpenIDConnectProvider",
"iam:ListOpenIDConnectProviders"
]
Resource = "*"
}
]
})
}

resource "aws_iam_role_policy_attachment" "github_actions_policy_attach" {
role = aws_iam_role.github_actions_role.name
policy_arn = aws_iam_policy.github_actions_policy.arn
}

resource "aws_iam_user_policy_attachment" "oidc_viewer" {
user = "your-iam-user"
policy_arn = aws_iam_policy.oidc_viewer_policy.arn
}

# Output for the AWS Account ID
output "github_actions_account_id" {
value = regex("^arn:aws:iam::([0-9]+):", aws_iam_role.github_actions_role.arn)[0]
description = "AWS Account ID associated with the GitHub Actions role."
}

# Output for the Role Name
output "github_actions_role_name" {
value = regex(":role/([^:]+)$", aws_iam_role.github_actions_role.arn)[0]
description = "Name of the GitHub Actions role."
}
44 changes: 44 additions & 0 deletions terraform/s3.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Bucket create. Public-read or private?
resource "aws_s3_bucket" "wfprev_site_bucket" {
bucket = "wfprev-${var.TARGET_ENV}-site"
force_destroy = true

website {
index_document = "index.html"
error_document = "index.html"
}
}

# Uploading assets. This shouldn't be needed because we'll push them up from the
# github action, vs having terraform fetch them
#resource "aws_s3_object" "upload-assets" {
# for_each = fileset("${var.web-assets-path}", "**/*")
# bucket = aws_s3_bucket.wfprev_site_bucket.bucket
# key = each.value
# source = "${var.web-assets-path}/${each.value}"
# content_type = lookup(var.mime_types, regex("\\.[^.]+$", each.value), "application/octet-stream")
#}

# S3 Bucket Policy for public access
resource "aws_s3_bucket_policy" "wfprev_site_bucket_policy" {
bucket = aws_s3_bucket.wfprev_site_bucket.id

policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Effect = "Allow",
Principal = {
"AWS" : "${aws_cloudfront_origin_access_identity.oai.iam_arn}"
},
Action = "s3:GetObject",
Resource = "${aws_s3_bucket.wfprev_site_bucket.arn}/*"
}
]
})
}

output "s3_bucket_name" {
value = aws_s3_bucket.wfprev_site_bucket.bucket
}

0 comments on commit d63729b

Please sign in to comment.