Skip to content

HydroServer AWS Cloud Deployment #3

HydroServer AWS Cloud Deployment

HydroServer AWS Cloud Deployment #3

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 }}"