Skip to content

Commit

Permalink
Merge pull request #48 from alliance-genome/API-deployment-KANBAN-558
Browse files Browse the repository at this point in the history
added API infra and deployment (KANBAN-558)
  • Loading branch information
mluypaert committed May 23, 2024
2 parents 4ef62c7 + 95b1450 commit 36713ee
Show file tree
Hide file tree
Showing 28 changed files with 776 additions and 39 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/PR-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -425,4 +425,4 @@ jobs:
role-session-name: gh-actions-${{github.run_id}}.${{github.run_number}}.${{github.run_attempt}}-cdk-test
aws-region: us-east-1
- name: Validate production CDK stack code
run: make validate
run: make validate-all
49 changes: 48 additions & 1 deletion .github/workflows/main-build-and-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ jobs:
role-session-name: gh-actions-${{github.run_id}}.${{github.run_number}}.${{github.run_attempt}}-cdk-deploy
aws-region: us-east-1
- name: CDK validations (resource assertions and cdk diff)
run: make validate
run: make validate-image-stack
- name: cdk deploy
run: cdk deploy PaviApiImageRepoCdkStack --require-approval never
pipeline-seq-retrieval-build-and-push-docker-image:
Expand Down Expand Up @@ -189,3 +189,50 @@ jobs:
${{ steps.login-ecr.outputs.registry }}/agr_pavi/api:${{ env.tagname }}
${{ steps.login-ecr.outputs.registry }}/agr_pavi/api:${{ github.event.pull_request.base.ref }}
platforms: linux/amd64
api-deploy-application:
name: Deploy application (version) for API
needs:
- on-merge-and-deploy
- api-build-and-push-docker-image
- pipeline-alignment-build-and-push-docker-image
- pipeline-seq-retrieval-build-and-push-docker-image
permissions:
id-token: write # This is required for requesting the JWT for gaining permissions to assume the IAM role to perform AWS actions
runs-on: ubuntu-22.04
defaults:
run:
working-directory: api/aws_infra
steps:
- name: Check out repository code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Store release tag in env
shell: bash
run: |
echo "tagname=$(git describe --tags)" >> $GITHUB_ENV
- name: Setup node.js (CDK requirement)
uses: actions/setup-node@v4
with:
node-version: "18"
- name: Install CDK
run: npm install -g aws-cdk
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install CDK stack dependencies
run: pip install -r requirements.txt
- name: AWS credentials configuration
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{secrets.GH_ACTIONS_AWS_ROLE}}
role-session-name: gh-actions-${{github.run_id}}.${{github.run_number}}.${{github.run_attempt}}-api-cdk-deploy
aws-region: us-east-1
- name: CDK validations (resource assertions and cdk diff)
run: make validate-application-stack validate-environment-stack
- name: Deploy application (and version)
run: make deploy-application PAVI_DEPLOY_VERSION_LABEL="${{ env.tagname }}" ADD_CDK_ARGS="--require-approval never"
- name: Deploy to main environment
run: make deploy-environment PAVI_DEPLOY_VERSION_LABEL="${{ env.tagname }}" PAVI_IMAGE_TAG="${{ env.tagname }}" \
DEPLOY_ENVIRONMENT=PaviApiEbMainStack ADD_CDK_ARGS="--require-approval never"
2 changes: 2 additions & 0 deletions api/Dockerfile.dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ api/src/seq_regions*.json
api/src/.nextflow*
api/src/pipeline-results*/
api/src/work/
# AWS infra code and config files
api/aws_infra/
8 changes: 4 additions & 4 deletions api/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ container-image:

run-container-dev:
export API_PIPELINE_IMAGE_TAG=main && \
docker-compose --env-file dev.env up agr.pavi.dev.api
docker-compose -f docker-compose-dev.yml --env-file dev.env up agr.pavi.dev-local.api

nextflow.sh:
make -f ../pipeline/workflow/Makefile nextflow.sh
Expand Down Expand Up @@ -64,9 +64,9 @@ run-tests-dev: python-dependencies python-test-dependencies nextflow.sh protein-

run-integration-test-container: python-dependencies python-test-dependencies
export API_PIPELINE_IMAGE_TAG=${TAG_NAME} && \
docker-compose --env-file dev.env up -d agr.pavi.dev.api
docker-compose -f docker-compose-dev.yml --env-file dev.env up -d agr.pavi.dev-local.api
sleep 30 # Allow container some startup time before attempting to connect
export EXTERNAL_API_BASE_URL="http://localhost:8080" && \
python -m pytest tests/integration \
|| (echo "Container logs:" && docker logs agr.pavi.dev.api.server && exit 1)
docker-compose down
|| (echo "Container logs:" && docker logs agr.pavi.dev-local.api.server && exit 1)
docker-compose -f docker-compose-dev.yml down
7 changes: 7 additions & 0 deletions api/aws_infra/.ebextensions/autoscaling.yml.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
option_settings:
aws:ec2:instances:
InstanceTypes: t2.micro
aws:autoscaling:asg:
Availability Zones: Any
MinSize: 1
MaxSize: 1
17 changes: 17 additions & 0 deletions api/aws_infra/.ebextensions/aws-ec2-vpc.yml.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
option_settings:
aws:ec2:vpc:
AssociatePublicIpAddress: false
Subnets:
- subnet-0d4703177afb1797d
- subnet-04262fc338f638054
- subnet-044457c061edf85f2
- subnet-04019d42d5c9e6fb9
- subnet-049778993fb504a7c
ELBSubnets:
- subnet-0d4703177afb1797d
- subnet-04262fc338f638054
- subnet-044457c061edf85f2
- subnet-04019d42d5c9e6fb9
- subnet-049778993fb504a7c
VPCId: vpc-55522232
ELBScheme: internal
98 changes: 98 additions & 0 deletions api/aws_infra/.ebextensions/cloudwatch.yml.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
files:
"/opt/aws/amazon-cloudwatch-agent/bin/config.json":
mode: "000600"
owner: root
group: root
content: |
{
"agent": {
"metrics_collection_interval": 60,
"run_as_user": "root"
},
"metrics": {
"namespace": "PAVI/ApiServer",
"append_dimensions": {
"AutoScalingGroupName": "${aws:AutoScalingGroupName}",
"InstanceId": "${aws:InstanceId}",
"InstanceType": "${aws:InstanceType}"
},
"metrics_collected": {
"cpu": {
"totalcpu": true,
"measurement": [
"usage_active",
"usage_idle",
"usage_iowait",
"usage_guest",
"usage_system",
"usage_user"
]
},
"disk": {
"ignore_file_system_types": [
"tmpfs",
"devtmpfs"
],
"measurement": [
"free",
"used",
"used_percent",
"inodes_used",
"inodes_free"
]
},
"diskio": {
"measurement": [
"reads",
"writes",
"read_bytes",
"write_bytes",
"iops_in_progress"
]
},
"mem": {
"measurement": [
"available",
"available_percent",
"free",
"used",
"used_percent"
]
},
"swap": {
"measurement": [
"free",
"used",
"used_percent"
]
},
"net": {
"resources": ["eth0", "docker0"],
"measurement": [
"bytes_sent",
"bytes_recv",
"drop_in",
"drop_out",
"err_in",
"err_out"
]
},
"processes": {
"measurement": [
"blocked",
"dead",
"paging",
"running",
"sleeping",
"wait",
"zombies",
"total",
"total_threads"
]
}
}
}
}
container_commands:
start_cloudwatch_agent:
command: /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -s -c file:/opt/aws/amazon-cloudwatch-agent/bin/config.json
7 changes: 7 additions & 0 deletions api/aws_infra/.ebextensions/eb-platform.yml.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
option_settings:
aws:elasticbeanstalk:managedactions:
ManagedActionsEnabled: true
PreferredStartTime: "Tue:02:00"
aws:elasticbeanstalk:managedactions:platformupdate:
UpdateLevel: minor
InstanceRefreshEnabled: false
35 changes: 35 additions & 0 deletions api/aws_infra/.ebextensions/loadbalancer.yml.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
Resources:
AWSEBV2LoadBalancerListener:
Type: 'AWS::ElasticLoadBalancingV2::Listener'
Properties:
DefaultActions:
- Type: redirect
RedirectConfig:
Protocol: HTTPS
Port: '443'
Host: '#{host}'
Path: '/#{path}'
Query: '#{query}'
StatusCode: HTTP_301
LoadBalancerArn:
Ref: AWSEBV2LoadBalancer
Port: 80
Protocol: HTTP
option_settings:
# As noted in the AWS docs, the following option cannot be set through the .ebextensions configuration files,
# and thus is defined directly in the CDK definitions (see parent directory).
#
# aws:elasticbeanstalk:environment:
# LoadBalancerType: application
#
# https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/environments-cfg-alb.html#environments-cfg-alb-namespaces

aws:elbv2:listener:443:
SSLCertificateArns: arn:aws:acm:us-east-1:100225593120:certificate/047a56a2-09dd-4857-9f28-32d23650d4da
Protocol: HTTPS
DefaultProcess: api
aws:elasticbeanstalk:environment:process:api:
Port: '8080'
Protocol: HTTP
aws:elbv2:loadbalancer:
IdleTimeout: 600
3 changes: 3 additions & 0 deletions api/aws_infra/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ node_modules/
package.json
package-lock.json

## Deploy artefacts
eb_app.zip

# Python files
## Byte-compiled / optimized / DLL files
__pycache__/
Expand Down
42 changes: 38 additions & 4 deletions api/aws_infra/Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
.PHONY: check-venv-active check-node

SUPPORTED_NODE := ^v18\.

AWS_DEFAULT_REGION := us-east-1
AWS_ACCT_NR := 100225593120

PAVI_DEPLOY_VERSION_LABEL ?= $(shell git describe --tags --dirty)-$(shell git rev-parse --abbrev-ref HEAD)-$(shell date +%Y%m%d-%H%M%S)
DEPLOY_ENVIRONMENT ?= PaviApiEbDevStack
PAVI_IMAGE_TAG ?= main
PAVI_IMAGE_REGISTRY ?= ${AWS_ACCT_NR}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/
ADD_CDK_ARGS ?=

check-venv-active:
ifeq ($(VIRTUAL_ENV),)
@echo 'No active python virtual environment found.'\
Expand All @@ -27,7 +37,7 @@ python-dependencies:
python-test-dependencies:
pip install -r tests/requirements.txt

run-unit-tests: python-dependencies python-test-dependencies
run-unit-tests: python-dependencies python-test-dependencies check-node
python -m pytest

run-unit-tests-dev: check-venv-active run-unit-tests
Expand All @@ -39,9 +49,33 @@ run-python-type-check: python-dependencies python-test-dependencies
run-python-style-check: python-dependencies python-test-dependencies
flake8 ./

validate: check-node run-unit-tests
# Validate production stack code
validate-image-stack: check-node run-unit-tests
cdk diff PaviApiImageRepoCdkStack

validate-dev: check-venv-active validate
validate-application-stack: check-node run-unit-tests
cdk diff PaviApiEbApplicationCdkStack

validate-environment-stack: check-node run-unit-tests
export PAVI_DEPLOY_VERSION_LABEL=${PAVI_DEPLOY_VERSION_LABEL} && \
export PAVI_IMAGE_TAG=${PAVI_IMAGE_TAG} && \
export PAVI_IMAGE_REGISTRY=${PAVI_IMAGE_REGISTRY} && \
cdk diff PaviApiEbDevStack

validate-all: check-node run-unit-tests validate-image-stack validate-application-stack validate-environment-stack
@:

validate-all-dev: check-venv-active validate-all
@:

deploy-application:
cdk deploy PaviApiEbApplicationCdkStack ${ADD_CDK_ARGS}
python -m aws_helpers.deploy_eb_app_version --eb_app_name PAVI-api --version_label ${PAVI_DEPLOY_VERSION_LABEL}

deploy-environment:
export PAVI_DEPLOY_VERSION_LABEL=${PAVI_DEPLOY_VERSION_LABEL} && \
export PAVI_IMAGE_TAG=${PAVI_IMAGE_TAG} && \
export PAVI_IMAGE_REGISTRY=${PAVI_IMAGE_REGISTRY} && \
cdk deploy ${DEPLOY_ENVIRONMENT} ${ADD_CDK_ARGS}

print-deploy-version-label:
@echo ${PAVI_DEPLOY_VERSION_LABEL}
Empty file added api/aws_infra/__init__.py
Empty file.
Empty file.
58 changes: 58 additions & 0 deletions api/aws_infra/aws_helpers/deploy_eb_app_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import click
from os import listdir, path
from zipfile import ZipFile

from .eb.eb_app_version import eb_app_version_exists, create_eb_app_version
from .s3.eb_assets import upload_application_bundle


@click.command(context_settings={'show_default': True})
@click.option("--eb_app_name", type=click.STRING, required=True,
help="The Elasticbeanstalk application name to deploy a new version for.")
@click.option("--version_label", type=click.STRING, required=True,
help="The version label to assign to the EB application version.")
def main(eb_app_name: str, version_label: str) -> None:
"""
Main method to deploy EB application versions. Receives input args from click.
Checks if an EB application version already exists with the defined version_label,
and deploys the current working directory as a new application version with that label if not.
"""
## Search EB application version by label
## Note: EB application version management is done external to CDK,
## as Cloudformation/CDK does not support custom labels at current (2024/05/17).
if not eb_app_version_exists(eb_app_name=eb_app_name, version_label=version_label):
print(f'Creating new application version with label "{version_label}".')
# Create app zip
dir_path = path.dirname(path.realpath(__file__))
app_zip_path = 'eb_app.zip'
with ZipFile(app_zip_path, 'w') as zipObj:
## Add docker-compose file
docker_compose_file = f'{dir_path}/../../docker-compose.yml'
zipObj.write(docker_compose_file, path.basename(path.normpath(docker_compose_file)))

## Add all files in .ebextensions/
ebextensions_path = f'{dir_path}/../.ebextensions/'
for filename in listdir(ebextensions_path):
full_file_path = path.join(ebextensions_path, filename)
if path.isfile(full_file_path):
zipObj.write(full_file_path, path.join('.ebextensions/', filename))

# Upload app zip as s3 source bundle
source_bundle = upload_application_bundle(
eb_app_name=eb_app_name,
version_label=version_label,
bundle_path=app_zip_path)

# Create new application version with label
create_eb_app_version(
eb_app_name=eb_app_name, version_label=version_label,
source_bundle=source_bundle,
tags=[{'Key': 'Product', 'Value': 'PAVI'},
{'Key': 'Managed_by', 'Value': 'PAVI'}])
else:
print(f'Application version with label "{version_label}" already exists.')


if __name__ == '__main__':
main()
Empty file.
Loading

0 comments on commit 36713ee

Please sign in to comment.