HydroServer AWS Cloud Deployment #3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: HydroServer AWS Cloud Deployment | |
on: | |
workflow_dispatch: | |
inputs: | |
environment: | |
description: 'Enter a deployment environment to use.' | |
type: environment | |
required: true | |
action: | |
description: 'Enter the action you want to perform.' | |
type: choice | |
required: true | |
options: | |
- Initialize HydroServer Deployment | |
- Update HydroServer Deployment | |
- Teardown HydroServer Deployment | |
release: | |
description: 'Enter the HydroServer release tag to use.' | |
type: string | |
required: false | |
default: 'latest' | |
permissions: | |
contents: write | |
id-token: write | |
actions: write | |
jobs: | |
check-environment-variables: | |
runs-on: ubuntu-latest | |
environment: ${{ github.event.inputs.environment }} | |
env: | |
AWS_ACCOUNT_ID: ${{ vars.AWS_ACCOUNT_ID }} | |
AWS_IAM_ROLE: ${{ vars.AWS_IAM_ROLE }} | |
AWS_REGION: ${{ vars.AWS_REGION }} | |
AWS_ACM_CERTIFICATE_ARN: ${{ vars.AWS_ACM_CERTIFICATE_ARN }} | |
PROXY_BASE_URL: ${{ vars.PROXY_BASE_URL }} | |
TERRAFORM_BUCKET: ${{ vars.TERRAFORM_BUCKET }} | |
steps: | |
- name: Check Required Environment Variables | |
run: | | |
echo "Checking required environment variables..." | |
required_vars=(AWS_ACCOUNT_ID AWS_IAM_ROLE AWS_REGION AWS_ACM_CERTIFICATE_ARN PROXY_BASE_URL TERRAFORM_BUCKET) | |
for var in "${required_vars[@]}"; do | |
if [ -z "${!var}" ]; then | |
echo "Error: Environment variable $var is not defined." | |
exit 1 | |
fi | |
done | |
initialize-hydroserver: | |
needs: check-environment-variables | |
if: github.event.inputs.action == 'Initialize HydroServer Deployment' | |
runs-on: ubuntu-latest | |
environment: ${{ github.event.inputs.environment }} | |
defaults: | |
run: | |
working-directory: ./terraform/aws | |
steps: | |
- name: Checkout Ops Repo | |
uses: actions/checkout@v4 | |
with: | |
ref: main | |
- name: Configure AWS Credentials | |
uses: aws-actions/configure-aws-credentials@v4 | |
with: | |
role-to-assume: arn:aws:iam::${{ vars.AWS_ACCOUNT_ID }}:role/${{ vars.AWS_IAM_ROLE }} | |
role-session-name: create-hydroserver-resources | |
aws-region: ${{ vars.AWS_REGION }} | |
- name: Setup Terraform | |
uses: hashicorp/setup-terraform@v1 | |
- name: Terraform Init | |
run: | | |
terraform init \ | |
-backend-config="bucket=${{ vars.TERRAFORM_BUCKET }}" \ | |
-backend-config="region=${{ vars.AWS_REGION }}" \ | |
-backend-config="key=state/hydroserver_${{ github.event.inputs.environment }}" | |
- name: Terraform Plan | |
id: plan | |
run: | | |
terraform plan \ | |
-no-color -input=false \ | |
-var "instance=${{ github.event.inputs.environment }}" \ | |
-var "region=${{ vars.AWS_REGION }}" \ | |
-var "proxy_base_url=${{ vars.PROXY_BASE_URL }}" \ | |
-var "acm_certificate_arn=${{ vars.AWS_ACM_CERTIFICATE_ARN }}" \ | |
-var "database_url=${{ secrets.DATABASE_URL || '' }}" \ | |
-var "tag_key=${{ vars.AWS_TAG_KEY || 'hydroserver-instance' }}" \ | |
-var "tag_value=${{ vars.AWS_TAG_VALUE || github.event.inputs.environment }}" | |
continue-on-error: true | |
- name: Terraform Plan Status | |
if: steps.plan.outcome == 'failure' | |
run: exit 1 | |
- name: Terraform Apply ECR | |
run: | | |
terraform apply \ | |
-auto-approve -input=false \ | |
-target=aws_ecr_repository.api_repository \ | |
-var "instance=${{ github.event.inputs.environment }}" \ | |
-var "region=${{ vars.AWS_REGION }}" \ | |
-var "proxy_base_url=${{ vars.PROXY_BASE_URL }}" \ | |
-var "acm_certificate_arn=${{ vars.AWS_ACM_CERTIFICATE_ARN }}" \ | |
-var "database_url=${{ secrets.DATABASE_URL || '' }}" \ | |
-var "tag_key=${{ vars.AWS_TAG_KEY || 'hydroserver-instance' }}" \ | |
-var "tag_value=${{ vars.AWS_TAG_VALUE || github.event.inputs.environment }}" | |
- name: Log in to AWS ECR | |
run: | | |
aws ecr get-login-password --region ${{ vars.AWS_REGION }} | docker login --username AWS --password-stdin "${{ vars.AWS_ACCOUNT_ID }}.dkr.ecr.${{ vars.AWS_REGION }}.amazonaws.com" | |
- name: Pull image from GHCR | |
run: | | |
docker pull ghcr.io/hydroserver2/hydroserver-api-services:latest | |
- name: Tag Docker Image for ECR | |
run: | | |
docker tag ghcr.io/hydroserver2/hydroserver-api-services:latest \ | |
${{ vars.AWS_ACCOUNT_ID }}.dkr.ecr.${{ vars.AWS_REGION }}.amazonaws.com/hydroserver-api-${{ github.event.inputs.environment }}:latest | |
- name: Push Docker Image to AWS ECR | |
run: | | |
docker push ${{ vars.AWS_ACCOUNT_ID }}.dkr.ecr.${{ vars.AWS_REGION }}.amazonaws.com/hydroserver-api-${{ github.event.inputs.environment }}:latest | |
- name: Terraform Apply | |
run: | | |
terraform apply \ | |
-auto-approve -input=false \ | |
-var "instance=${{ github.event.inputs.environment }}" \ | |
-var "region=${{ vars.AWS_REGION }}" \ | |
-var "proxy_base_url=${{ vars.PROXY_BASE_URL }}" \ | |
-var "acm_certificate_arn=${{ vars.AWS_ACM_CERTIFICATE_ARN }}" \ | |
-var "database_url=${{ secrets.DATABASE_URL || '' }}" \ | |
-var "tag_key=${{ vars.AWS_TAG_KEY || 'hydroserver-instance' }}" \ | |
-var "tag_value=${{ vars.AWS_TAG_VALUE || github.event.inputs.environment }}" | |
deploy-hydroserver-api: | |
needs: | |
- check-environment-variables | |
- initialize-hydroserver | |
if: > | |
always() && | |
!contains(needs.*.result, 'failure') && | |
( | |
(github.event.inputs.action == 'Initialize HydroServer Deployment' && needs.initialize-hydroserver.result == 'success') || | |
(github.event.inputs.action == 'Update HydroServer Deployment' && needs.check-environment-variables.result == 'success') | |
) | |
runs-on: ubuntu-latest | |
environment: ${{ github.event.inputs.environment }} | |
defaults: | |
run: | |
working-directory: ./terraform/aws | |
steps: | |
- name: Checkout Ops Repo | |
uses: actions/checkout@v4 | |
with: | |
ref: main | |
- name: Configure AWS Credentials | |
uses: aws-actions/configure-aws-credentials@v4 | |
with: | |
role-to-assume: arn:aws:iam::${{ vars.AWS_ACCOUNT_ID }}:role/${{ vars.AWS_IAM_ROLE }} | |
role-session-name: create-hydroserver-resources | |
aws-region: ${{ vars.AWS_REGION }} | |
- name: Log in to AWS ECR | |
run: | | |
aws ecr get-login-password --region ${{ vars.AWS_REGION }} | docker login --username AWS --password-stdin "${{ vars.AWS_ACCOUNT_ID }}.dkr.ecr.${{ vars.AWS_REGION }}.amazonaws.com" | |
- name: Pull image from GHCR | |
run: | | |
docker pull ghcr.io/hydroserver2/hydroserver-api-services:latest | |
- name: Tag Docker Image for ECR | |
run: | | |
docker tag ghcr.io/hydroserver2/hydroserver-api-services:latest \ | |
${{ vars.AWS_ACCOUNT_ID }}.dkr.ecr.${{ vars.AWS_REGION }}.amazonaws.com/hydroserver-api-${{ github.event.inputs.environment }}:latest | |
- name: Push Docker Image to AWS ECR | |
run: | | |
docker push ${{ vars.AWS_ACCOUNT_ID }}.dkr.ecr.${{ vars.AWS_REGION }}.amazonaws.com/hydroserver-api-${{ github.event.inputs.environment }}:latest | |
- name: Update AWS SSM Parameters | |
run: | | |
ENVIRONMENT="${{ github.event.inputs.environment }}" | |
declare -A PARAM_TYPES=( | |
["/hydroserver-${ENVIRONMENT}-api/database-url"]="SecureString" | |
["/hydroserver-${ENVIRONMENT}-api/secret-key"]="SecureString" | |
["/hydroserver-${ENVIRONMENT}-api/smtp-url"]="SecureString" | |
["/hydroserver-${ENVIRONMENT}-api/debug-mode"]="String" | |
["/hydroserver-${ENVIRONMENT}-api/proxy-base-url"]="String" | |
["/hydroserver-${ENVIRONMENT}-api/account-ownership-enabled"]="String" | |
["/hydroserver-${ENVIRONMENT}-api/account-signup-enabled"]="String" | |
["/hydroserver-${ENVIRONMENT}-api/socialaccount-signup-only"]="String" | |
) | |
declare -A PARAM_VALUES=( | |
["/hydroserver-${ENVIRONMENT}-api/database-url"]="${{ secrets.DATABASE_URL }}" | |
["/hydroserver-${ENVIRONMENT}-api/secret-key"]="${{ secrets.SECRET_KEY }}" | |
["/hydroserver-${ENVIRONMENT}-api/smtp-url"]="${{ secrets.SMTP_URL }}" | |
["/hydroserver-${ENVIRONMENT}-api/debug-mode"]="${{ vars.DEBUG }}" | |
["/hydroserver-${ENVIRONMENT}-api/proxy-base-url"]="${{ vars.PROXY_BASE_URL }}" | |
["/hydroserver-${ENVIRONMENT}-api/account-ownership-enabled"]="${{ vars.ACCOUNT_OWNERSHIP_ENABLED }}" | |
["/hydroserver-${ENVIRONMENT}-api/account-signup-enabled"]="${{ vars.ACCOUNT_SIGNUP_ENABLED }}" | |
["/hydroserver-${ENVIRONMENT}-api/socialaccount-signup-only"]="${{ vars.SOCIALACCOUNT_SIGNUP_ONLY }}" | |
) | |
for PARAM in "${!PARAM_VALUES[@]}"; do | |
VALUE="${PARAM_VALUES[$PARAM]}" | |
TYPE="${PARAM_TYPES[$PARAM]}" | |
if [ -n "$VALUE" ]; then | |
ESCAPED_VALUE=$(printf "%s" "$VALUE" | jq -Rs . | sed 's/^"\(.*\)"$/\1/') | |
echo "Updating $PARAM in AWS SSM Parameter Store..." | |
aws ssm put-parameter --name "$PARAM" --value "$ESCAPED_VALUE" --type "$TYPE" --overwrite | |
else | |
echo "Skipping $PARAM as it is not defined." | |
fi | |
done | |
- name: Deploy AWS App Runner Service | |
run: | | |
APP_RUNNER_SERVICE_NAME="hydroserver-api-${{ github.event.inputs.environment }}" | |
SERVICE_ARN=$(aws apprunner list-services --query "ServiceSummaryList[?ServiceName=='$APP_RUNNER_SERVICE_NAME'].ServiceArn" --output text) | |
aws apprunner start-deployment --service-arn "$SERVICE_ARN" | |
deploy-hydroserver-data-mgmt-app: | |
needs: | |
- check-environment-variables | |
- initialize-hydroserver | |
if: > | |
always() && | |
!contains(needs.*.result, 'failure') && | |
( | |
(github.event.inputs.action == 'Initialize HydroServer Deployment' && needs.initialize-hydroserver.result == 'success') || | |
(github.event.inputs.action == 'Update HydroServer Deployment' && needs.check-environment-variables.result == 'success') | |
) | |
runs-on: ubuntu-latest | |
environment: ${{ github.event.inputs.environment }} | |
steps: | |
- name: Configure AWS Credentials | |
uses: aws-actions/configure-aws-credentials@v4 | |
with: | |
role-to-assume: arn:aws:iam::${{ vars.AWS_ACCOUNT_ID }}:role/${{ vars.AWS_IAM_ROLE }} | |
role-session-name: create-hydroserver-resources | |
aws-region: ${{ vars.AWS_REGION }} | |
# - name: Get Latest HydroServer Version | |
# id: get_latest_tag | |
# run: echo "tag=$(curl -sL https://api.github.com/repos/hydroserver2/hydroserver-data-management-app/releases/latest | jq -r '.tag_name')" >> $GITHUB_OUTPUT | |
# - name: Checkout Data Management App Repo | |
# uses: actions/checkout@v4 | |
# with: | |
# repository: hydroserver2/hydroserver-data-management-app | |
# ref: refs/tags/${{ github.event.inputs.hydroserver-version || steps.get_latest_tag.outputs.tag }} | |
# path: data_mgmt | |
- name: Checkout Data Management App Repo | |
uses: actions/checkout@v4 | |
with: | |
repository: hydroserver2/hydroserver-data-management-app | |
ref: 238-workspaces | |
path: data_mgmt | |
- name: Setup Node 18.x | |
uses: actions/setup-node@v3 | |
with: | |
node-version: 18.x | |
cache: npm | |
cache-dependency-path: data_mgmt/package-lock.json | |
- name: Install Dependencies | |
working-directory: ./data_mgmt | |
run: npm ci | |
- name: Configure Environment Variables | |
working-directory: ./data_mgmt | |
run: | | |
cat << EOF > .env | |
VITE_APP_VERSION=${{ github.event.inputs.hydroserver-version || steps.get_latest_tag.outputs.tag }} | |
VITE_APP_GOOGLE_MAPS_API_KEY=${{ secrets.GOOGLE_MAPS_API_KEY }} | |
VITE_APP_GOOGLE_MAPS_MAP_ID=${{ secrets.GOOGLE_MAPS_MAP_ID }} | |
VITE_APP_PROXY_BASE_URL=${{ vars.PROXY_BASE_URL }} | |
- name: Build Data Management App | |
working-directory: ./data_mgmt | |
run: npm run build | |
- name: Deploy to S3 | |
working-directory: ./data_mgmt | |
run: | | |
aws s3 sync ./dist s3://hydroserver-data-mgmt-app-${{ github.event.inputs.environment }}-${{ vars.AWS_ACCOUNT_ID }}/ --delete | |
- name: Get CloudFront Distribution ID | |
run: | | |
DISTRIBUTION_IDS=$(aws cloudfront list-distributions --query "DistributionList.Items[*].Id" --output text) | |
for ID in $DISTRIBUTION_IDS; do | |
TAGS_JSON=$(aws cloudfront list-tags-for-resource --resource "arn:aws:cloudfront::${{ vars.AWS_ACCOUNT_ID }}:distribution/$ID") | |
MATCHING_ID=$(echo "$TAGS_JSON" | jq -r ' | |
if any(.Tags.Items[]; .Key == "'"${{ vars.AWS_TAG_KEY || 'hydroserver-instance' }}"'" and .Value == "'"${{ vars.AWS_TAG_VALUE || github.event.inputs.environment }}"'") then "'$ID'" else empty end | |
') | |
if [ -n "$MATCHING_ID" ]; then | |
CLOUDFRONT_ID=$MATCHING_ID | |
break | |
fi | |
done | |
echo "CLOUDFRONT_ID=$CLOUDFRONT_ID" >> $GITHUB_ENV | |
- name: Invalidate CloudFront Distribution Cache | |
run: | | |
aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_ID --paths "/*" | |
teardown-hydroserver: | |
needs: check-environment-variables | |
runs-on: ubuntu-latest | |
environment: ${{ github.event.inputs.environment }} | |
if: success() && github.event.inputs.action == 'Teardown HydroServer Deployment' | |
defaults: | |
run: | |
working-directory: ./terraform/aws | |
steps: | |
- name: Checkout Ops Repo | |
uses: actions/checkout@v4 | |
with: | |
ref: main | |
- name: Configure AWS Credentials | |
uses: aws-actions/configure-aws-credentials@v4 | |
with: | |
role-to-assume: arn:aws:iam::${{ vars.AWS_ACCOUNT_ID }}:role/${{ vars.AWS_IAM_ROLE }} | |
role-session-name: create-hydroserver-resources | |
aws-region: ${{ vars.AWS_REGION }} | |
- name: Setup Terraform | |
uses: hashicorp/setup-terraform@v1 | |
- name: Terraform Init | |
run: | | |
terraform init \ | |
-backend-config="bucket=${{ vars.TERRAFORM_BUCKET }}" \ | |
-backend-config="region=${{ vars.AWS_REGION }}" \ | |
-backend-config="key=state/hydroserver_${{ github.event.inputs.environment }}" | |
- name: Terraform Plan Destroy | |
id: plan_destroy | |
run: | | |
terraform state rm aws_db_instance.rds_db_instance || true | |
terraform plan -destroy -no-color -input=false \ | |
-var "instance=${{ github.event.inputs.environment }}" \ | |
-var "region=${{ vars.AWS_REGION }}" \ | |
-var "proxy_base_url=${{ vars.PROXY_BASE_URL }}" \ | |
-var "acm_certificate_arn=${{ vars.AWS_ACM_CERTIFICATE_ARN }}" | |
continue-on-error: true | |
- name: Terraform Plan Destroy Status | |
if: steps.plan_destroy.outcome == 'failure' | |
run: exit 1 | |
- name: Terraform Destroy | |
run: | | |
terraform destroy -auto-approve -input=false \ | |
-var "instance=${{ github.event.inputs.environment }}" \ | |
-var "region=${{ vars.AWS_REGION }}" \ | |
-var "proxy_base_url=${{ vars.PROXY_BASE_URL }}" \ | |
-var "acm_certificate_arn=${{ vars.AWS_ACM_CERTIFICATE_ARN }}" |