From b08aa76acc7faef3774cbe451c454beda95b5087 Mon Sep 17 00:00:00 2001 From: Niklas Fondberg Date: Thu, 27 Feb 2020 13:06:58 +0100 Subject: [PATCH] P5-4836 deploy api to production (new hostname) --- .gitignore | 1 + Dockerfile | 8 + Makefile | 18 ++ api/resources/server.py | 2 +- cloudformation/Makefile | 92 ++++++ cloudformation/ecs-service.yaml | 503 ++++++++++++++++++++++++++++++++ docker-compose.yml | 21 ++ fabfile.py | 37 ++- prestart.sh | 15 + 9 files changed, 677 insertions(+), 20 deletions(-) create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 cloudformation/Makefile create mode 100644 cloudformation/ecs-service.yaml create mode 100644 docker-compose.yml create mode 100644 prestart.sh diff --git a/.gitignore b/.gitignore index cbc6788..d3396e4 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ venv/ *.sublime-* ideas.py +.fab_tasks~ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5f21bec --- /dev/null +++ b/Dockerfile @@ -0,0 +1,8 @@ +FROM tiangolo/uwsgi-nginx-flask:python3.6 + +COPY ./requirements.txt /app +RUN pip install -r requirements.txt + +COPY . /app/ +COPY ./wsgi.py /app/main.py +RUN rm /app/wsgi.py diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..675f101 --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +publish-to-staging: + @current_branch=$$(git rev-parse --abbrev-ref HEAD); \ + git branch -D staging 2>/dev/null; \ + git checkout -b staging; \ + git merge $$current_branch && \ + git push -f origin staging && \ + git checkout $$current_branch; \ + echo Pushed $$current_branch to staging + +sync-master-to-staging: + @current_branch=$$(git rev-parse --abbrev-ref HEAD); \ + git branch -D staging 2>/dev/null; \ + git checkout master && \ + git pull origin master && \ + git checkout -b staging && \ + git push -f origin staging && \ + git checkout $$current_branch && \ + echo Pushed master to staging diff --git a/api/resources/server.py b/api/resources/server.py index d39bc0b..8a4e093 100644 --- a/api/resources/server.py +++ b/api/resources/server.py @@ -5,7 +5,7 @@ mod = Blueprint("server", __name__) @mod.route("/", methods=["GET"]) def status(): - data = {"version": "0.0.1"} + data = {"version": "0.0.2"} return jsonify(data) diff --git a/cloudformation/Makefile b/cloudformation/Makefile new file mode 100644 index 0000000..b012584 --- /dev/null +++ b/cloudformation/Makefile @@ -0,0 +1,92 @@ +## +# Example: +# make create-stack GITHUB_TOKEN=xxxxx ENVIRONMENT_NAME=staging STACK_NAME=api +# make create-stack GITHUB_TOKEN=xxxxx ENVIRONMENT_NAME=production STACK_NAME=api +# +# # make update-stack ENVIRONMENT_NAME=staging STACK_NAME=api +## + +GITHUB_REPO := api2 +GITHUB_USER := Photowall +# HOSTED_ZONE=photowall.com default in the template +# Cert ARN for the hosted zone +CERTIFICATE_ARN='arn:aws:acm:eu-west-1:954747537408:certificate/1278df32-006d-48a4-abd6-2bfccabb11fd' +DB_SECURITY_GROUP_ID_PRODUCTION=sg-0b5bafc1e0a97e848 +DB_SECURITY_GROUP_ID_STAGING=sg-001b3667edf19e1ce + +AWS_REGION=$(shell aws configure get region) +AWS_ACCOUNT_ID=$(shell aws sts get-caller-identity --query Account --output text) + +checkdef = $(if $(value $(1)),,$(error $(1) not set)) + +DOCKER_IMAGE=$(AWS_ACCOUNT_ID).dkr.ecr.$(AWS_REGION).amazonaws.com/$(STACK_NAME)-$(ENVIRONMENT_NAME) + +ifeq ($(ENVIRONMENT_NAME),production) + SUBDOMAIN=$(STACK_NAME) + GITHUB_BRANCH=master + DB_SECURITY_GROUP_ID=$(DB_SECURITY_GROUP_ID_PRODUCTION) + CPU_SIZE=1024 + MEMORY_SIZE=4GB +else + SUBDOMAIN=$(STACK_NAME)-$(ENVIRONMENT_NAME) + GITHUB_BRANCH=$(ENVIRONMENT_NAME) + DB_SECURITY_GROUP_ID=$(DB_SECURITY_GROUP_ID_STAGING) + CPU_SIZE=512 + MEMORY_SIZE=2GB +endif + +check-create-initial-ecr-image: + @echo Checking for inital image $(DOCKER_IMAGE) + @aws ecr list-images --repository-name $(STACK_NAME)-$(ENVIRONMENT_NAME) 2>/dev/null | grep imageTag | grep -q latest; \ + status=$$?; \ + if [ $$status != 0 ]; \ + then \ + echo ""; \ + echo "ECR Repo for initial image does not exist, creating"; \ + echo ""; \ + aws ecr create-repository --repository $(STACK_NAME)-$(ENVIRONMENT_NAME); \ + $$(aws ecr get-login --no-include-email --region eu-west-1); \ + docker pull nginx; \ + docker tag nginx $(AWS_ACCOUNT_ID).dkr.ecr.$(AWS_REGION).amazonaws.com/$(STACK_NAME)-$(ENVIRONMENT_NAME):latest; \ + docker push $(AWS_ACCOUNT_ID).dkr.ecr.$(AWS_REGION).amazonaws.com/$(STACK_NAME)-$(ENVIRONMENT_NAME):latest; \ + echo ""; \ + fi + +check-create-variables-set: + $(call checkdef,STACK_NAME) + $(call checkdef,ENVIRONMENT_NAME) + $(call checkdef,GITHUB_TOKEN) + $(call checkdef,GITHUB_USER) + $(call checkdef,GITHUB_REPO) + +create-stack: check-create-variables-set check-create-initial-ecr-image + aws cloudformation create-stack --stack-name $(STACK_NAME)-$(ENVIRONMENT_NAME) --template-body file://ecs-service.yaml \ + --capabilities CAPABILITY_NAMED_IAM \ + --parameters ParameterKey="EnvironmentName",ParameterValue="$(ENVIRONMENT_NAME)" \ + ParameterKey="Image",ParameterValue="$(DOCKER_IMAGE)" \ + ParameterKey="Subdomain",ParameterValue="$(SUBDOMAIN)" \ + ParameterKey="Certificate",ParameterValue="$(CERTIFICATE_ARN)" \ + ParameterKey="DatabaseSecurityGroup",ParameterValue="$(DB_SECURITY_GROUP_ID)" \ + ParameterKey="GitHubRepo",ParameterValue="$(GITHUB_REPO)" \ + ParameterKey="GitHubUser",ParameterValue="$(GITHUB_USER)" \ + ParameterKey="GitHubBranch",ParameterValue="$(GITHUB_BRANCH)" \ + ParameterKey="GitHubToken",ParameterValue="$(GITHUB_TOKEN)" \ + ParameterKey="CpuSize",ParameterValue="$(CPU_SIZE)" \ + ParameterKey="MemorySize",ParameterValue="$(MEMORY_SIZE)" + +update-stack: + $(call checkdef,STACK_NAME) + $(call checkdef,ENVIRONMENT_NAME) + aws cloudformation update-stack --stack-name $(STACK_NAME)-$(ENVIRONMENT_NAME) --template-body file://ecs-service.yaml \ + --capabilities CAPABILITY_NAMED_IAM \ + --parameters ParameterKey="EnvironmentName",UsePreviousValue=true \ + ParameterKey="Image",UsePreviousValue=true \ + ParameterKey="Subdomain",UsePreviousValue=true \ + ParameterKey="Certificate",UsePreviousValue=true \ + ParameterKey="DatabaseSecurityGroup",,UsePreviousValue=true \ + ParameterKey="GitHubRepo",UsePreviousValue=true \ + ParameterKey="GitHubUser",UsePreviousValue=true \ + ParameterKey="GitHubBranch",UsePreviousValue=true \ + ParameterKey="GitHubToken",UsePreviousValue=true \ + ParameterKey="CpuSize",ParameterValue="$(CPU_SIZE)" \ + ParameterKey="MemorySize",ParameterValue="$(MEMORY_SIZE)" diff --git a/cloudformation/ecs-service.yaml b/cloudformation/ecs-service.yaml new file mode 100644 index 0000000..8fc9d69 --- /dev/null +++ b/cloudformation/ecs-service.yaml @@ -0,0 +1,503 @@ +AWSTemplateFormatVersion: 2010-09-09 +Description: CloudFormation template for a full E2E ECS service with CI/CD on Fargate. +Parameters: + VPC: + Type: AWS::EC2::VPC::Id + Default: vpc-6f596904 + Description: The id of the VPC + SubnetA: + Type: AWS::EC2::Subnet::Id + Default: subnet-6e596905 + Description: The id of the first subnet + SubnetB: + Type: AWS::EC2::Subnet::Id + Default: subnet-6d596906 + Description: The id of the second subnet + + DatabaseSecurityGroup: + Type: String + Description: Security group id to access the database + + Image: + Type: String + # Docker image for this service. It must have a latest tag + # Example 260851616142.dkr.ecr.eu-west-1.amazonaws.com/nginx + EnvironmentName: + Type: String + Description: Name of env such as staging or production + + # CPU + # 256 (.25 vCPU) - Available memory values: 0.5GB, 1GB, 2GB + # 512 (.5 vCPU) - Available memory values: 1GB, 2GB, 3GB, 4GB + # 1024 (1 vCPU) - Available memory values: 2GB, 3GB, 4GB, 5GB, 6GB, 7GB, 8GB + # 2048 (2 vCPU) - Available memory values: Between 4GB and 16GB in 1GB increments + # 4096 (4 vCPU) - Available memory values: Between 8GB and 30GB in 1GB increments + CpuSize: + Type: Number + + # Memory + # 0.5GB, 1GB, 2GB - Available cpu values: 256 (.25 vCPU) + # 1GB, 2GB, 3GB, 4GB - Available cpu values: 512 (.5 vCPU) + # 2GB, 3GB, 4GB, 5GB, 6GB, 7GB, 8GB - Available cpu values: 1024 (1 vCPU) + # Between 4GB and 16GB in 1GB increments - Available cpu values: 2048 (2 vCPU) + # Between 8GB and 30GB in 1GB increments - Available cpu values: 4096 (4 vCPU) + MemorySize: + Type: String + + ContainerPort: + Type: Number + Default: 80 + LoadBalancerPort: + Type: Number + Default: 443 + HealthCheckPath: + Type: String + Default: / + + Certificate: + Type: String + # Update with the certificate ARN from Certificate Manager, which must exist in the same region. + HostedZoneName: + Type: String + Default: photowall.com + Subdomain: + Type: String + + # for autoscaling + MinContainers: + Type: Number + Default: 1 + # for autoscaling + MaxContainers: + Type: Number + Default: 4 + # target CPU utilization (%) + AutoScalingTargetValue: + Type: Number + Default: 50 + + # Github info for code pipeline + GitHubRepo: + Type: String + GitHubBranch: + Type: String + GitHubToken: + Type: String + NoEcho: true + GitHubUser: + Type: String + +Resources: + Cluster: + Type: AWS::ECS::Cluster + Properties: + ClusterName: !Join ['', [!Ref 'AWS::StackName', Cluster]] + + TaskDefinition: + Type: AWS::ECS::TaskDefinition + # Makes sure the log group is created before it is used. + DependsOn: LogGroup + Properties: + # Name of the task definition. Subsequent versions of the task definition are grouped together under this name. + Family: !Join ['', [!Ref 'AWS::StackName', TaskDefinition]] + # awsvpc is required for Fargate + NetworkMode: awsvpc + RequiresCompatibilities: + - FARGATE + Cpu: !Ref CpuSize + Memory: !Ref MemorySize + # A role needed by ECS. + # "The ARN of the task execution role that containers in this task can assume. All containers in this task are granted the permissions that are specified in this role." + # "There is an optional task execution IAM role that you can specify with Fargate to allow your Fargate tasks to make API calls to Amazon ECR." + ExecutionRoleArn: !Ref ExecutionRole + # "The Amazon Resource Name (ARN) of an AWS Identity and Access Management (IAM) role that grants containers in the task permission to call AWS APIs on your behalf." + TaskRoleArn: !Ref TaskRole + ContainerDefinitions: + - Name: !Ref AWS::StackName + Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${AWS::StackName}:latest + Environment: + - Name: DEBUG + Value: True + PortMappings: + - ContainerPort: !Ref ContainerPort + # Define which parameters should be fetched + Secrets: + - { Name: "SQLALCHEMY_DATABASE_URI", ValueFrom: !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${EnvironmentName}/SQLALCHEMY_DATABASE_URI" } + - { Name: "API_KEYS", ValueFrom: !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${EnvironmentName}/API2_API_KEYS" } + - { Name: "BERNARD_QUEUE_URL", ValueFrom: !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${EnvironmentName}/BERNARD_QUEUE_URL" } + # Send logs to CloudWatch Logs + LogConfiguration: + LogDriver: awslogs + Options: + awslogs-region: !Ref AWS::Region + awslogs-group: !Ref LogGroup + awslogs-stream-prefix: ecs + + # A role needed by ECS + ExecutionRole: + Type: AWS::IAM::Role + Properties: + RoleName: !Join ['', [!Ref 'AWS::StackName', ExecutionRole]] + AssumeRolePolicyDocument: + Statement: + - Effect: Allow + Principal: + Service: ecs-tasks.amazonaws.com + Action: 'sts:AssumeRole' + ManagedPolicyArns: + - 'arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy' + - arn:aws:iam::aws:policy/AmazonSSMReadOnlyAccess + # A role for the containers + TaskRole: + Type: AWS::IAM::Role + Properties: + RoleName: !Join ['', [!Ref 'AWS::StackName', TaskRole]] + AssumeRolePolicyDocument: + Statement: + - Effect: Allow + Principal: + Service: ecs-tasks.amazonaws.com + Action: 'sts:AssumeRole' + + # A role needed for auto scaling + AutoScalingRole: + Type: AWS::IAM::Role + Properties: + RoleName: !Join ['', [!Ref 'AWS::StackName', AutoScalingRole]] + AssumeRolePolicyDocument: + Statement: + - Effect: Allow + Principal: + Service: ecs-tasks.amazonaws.com + Action: 'sts:AssumeRole' + ManagedPolicyArns: + - 'arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceAutoscaleRole' + + ContainerSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: !Join ['', [!Ref 'AWS::StackName', ContainerSecurityGroup]] + VpcId: !Ref VPC + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: !Ref ContainerPort + ToPort: !Ref ContainerPort + SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup + + LoadBalancerSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: !Join ['', [!Ref 'AWS::StackName', LoadBalancerSecurityGroup]] + VpcId: !Ref VPC + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: !Ref LoadBalancerPort + ToPort: !Ref LoadBalancerPort + CidrIp: 0.0.0.0/0 + + Service: + Type: AWS::ECS::Service + # This dependency is needed so that the load balancer is setup correctly in time + DependsOn: + - ListenerHTTPS + Properties: + ServiceName: !Ref AWS::StackName + Cluster: !Ref Cluster + TaskDefinition: !Ref TaskDefinition + DeploymentConfiguration: + MinimumHealthyPercent: 100 + MaximumPercent: 200 + DesiredCount: 1 + # This may need to be adjusted if the container takes a while to start up + HealthCheckGracePeriodSeconds: 30 + LaunchType: FARGATE + NetworkConfiguration: + AwsvpcConfiguration: + # change to DISABLED if you're using private subnets that have access to a NAT gateway + AssignPublicIp: ENABLED + Subnets: + - !Ref SubnetA + - !Ref SubnetB + SecurityGroups: + - !Ref ContainerSecurityGroup + - !Ref DatabaseSecurityGroup + LoadBalancers: + - ContainerName: !Ref AWS::StackName + ContainerPort: !Ref ContainerPort + TargetGroupArn: !Ref TargetGroup + + TargetGroup: + Type: AWS::ElasticLoadBalancingV2::TargetGroup + Properties: + HealthCheckIntervalSeconds: 10 + # will look for a 200 status code by default unless specified otherwise + HealthCheckPath: !Ref HealthCheckPath + HealthCheckTimeoutSeconds: 5 + UnhealthyThresholdCount: 2 + HealthyThresholdCount: 2 + Name: !Join ['', [!Ref 'AWS::StackName', TargetGroup]] + Port: !Ref ContainerPort + Protocol: HTTP + TargetGroupAttributes: + - Key: deregistration_delay.timeout_seconds + Value: 60 # default is 300 + TargetType: ip + VpcId: !Ref VPC + + ListenerHTTPS: + Type: AWS::ElasticLoadBalancingV2::Listener + Properties: + DefaultActions: + - TargetGroupArn: !Ref TargetGroup + Type: forward + LoadBalancerArn: !Ref LoadBalancer + Port: !Ref LoadBalancerPort + Protocol: HTTPS + Certificates: + - CertificateArn: !Ref Certificate + + LoadBalancer: + Type: AWS::ElasticLoadBalancingV2::LoadBalancer + Properties: + LoadBalancerAttributes: + # this is the default, but is specified here in case it needs to be changed + - Key: idle_timeout.timeout_seconds + Value: 60 + Name: !Join ['', [!Ref 'AWS::StackName', LoadBalancer]] + # "internal" is also an option + Scheme: internet-facing + SecurityGroups: + - !Ref LoadBalancerSecurityGroup + Subnets: + - !Ref SubnetA + - !Ref SubnetB + + DNSRecord: + Type: AWS::Route53::RecordSet + Properties: + HostedZoneName: !Join ['', [!Ref HostedZoneName, .]] + Name: !Join ['', [!Ref Subdomain, ., !Ref HostedZoneName, .]] + Type: A + AliasTarget: + DNSName: !GetAtt LoadBalancer.DNSName + HostedZoneId: !GetAtt LoadBalancer.CanonicalHostedZoneID + + LogGroup: + Type: AWS::Logs::LogGroup + Properties: + LogGroupName: !Join ['', [/ecs/, !Ref 'AWS::StackName', TaskDefinition]] + + AutoScalingTarget: + Type: AWS::ApplicationAutoScaling::ScalableTarget + Properties: + MinCapacity: !Ref MinContainers + MaxCapacity: !Ref MaxContainers + ResourceId: !Join ['/', [service, !Ref Cluster, !GetAtt Service.Name]] + ScalableDimension: ecs:service:DesiredCount + ServiceNamespace: ecs + # "The Amazon Resource Name (ARN) of an AWS Identity and Access Management (IAM) role that allows Application Auto Scaling to modify your scalable target." + RoleARN: !GetAtt AutoScalingRole.Arn + AutoScalingPolicy: + Type: AWS::ApplicationAutoScaling::ScalingPolicy + Properties: + PolicyName: !Join ['', [!Ref 'AWS::StackName', AutoScalingPolicy]] + PolicyType: TargetTrackingScaling + ScalingTargetId: !Ref AutoScalingTarget + TargetTrackingScalingPolicyConfiguration: + PredefinedMetricSpecification: + PredefinedMetricType: ECSServiceAverageCPUUtilization + ScaleInCooldown: 10 + ScaleOutCooldown: 10 + # Keep things at or lower than 50% CPU utilization, for example + TargetValue: !Ref AutoScalingTargetValue + + ## Deployment pipeline below + CodeBuildServiceRole: + Type: AWS::IAM::Role + Properties: + Path: / + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: codebuild.amazonaws.com + Action: sts:AssumeRole + Policies: + - PolicyName: root + PolicyDocument: + Version: 2012-10-17 + Statement: + - Resource: "*" + Effect: Allow + Action: + - logs:CreateLogGroup + - logs:CreateLogStream + - logs:PutLogEvents + - ecr:GetAuthorizationToken + - Resource: !Sub arn:aws:s3:::${ArtifactBucket}/* + Effect: Allow + Action: + - s3:GetObject + - s3:PutObject + - s3:GetObjectVersion + - Resource: !Sub arn:aws:ecr:${AWS::Region}:${AWS::AccountId}:repository/${AWS::StackName} + Effect: Allow + Action: + - ecr:GetDownloadUrlForLayer + - ecr:BatchGetImage + - ecr:BatchCheckLayerAvailability + - ecr:PutImage + - ecr:InitiateLayerUpload + - ecr:UploadLayerPart + - ecr:CompleteLayerUpload + + CodePipelineServiceRole: + Type: AWS::IAM::Role + Properties: + Path: / + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: codepipeline.amazonaws.com + Action: sts:AssumeRole + Policies: + - PolicyName: root + PolicyDocument: + Version: 2012-10-17 + Statement: + - Resource: + - !Sub arn:aws:s3:::${ArtifactBucket}/* + Effect: Allow + Action: + - s3:PutObject + - s3:GetObject + - s3:GetObjectVersion + - s3:GetBucketVersioning + - Resource: "*" + Effect: Allow + Action: + - ecs:List* + - ecs:Describe* + - ecs:RegisterTaskDefinition + - ecs:UpdateService + - codebuild:StartBuild + - codebuild:BatchGetBuilds + - iam:PassRole + + ArtifactBucket: + Type: AWS::S3::Bucket + DeletionPolicy: Retain + + CodeBuildProject: + Type: AWS::CodeBuild::Project + Properties: + Artifacts: + Type: CODEPIPELINE + Source: + Type: CODEPIPELINE + BuildSpec: | + version: 0.2 + phases: + pre_build: + commands: + - $(aws ecr get-login --no-include-email) + - TAG="$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | head -c 8)" + - IMAGE_URI="${REPOSITORY_URI}:${TAG}" + - IMAGE_LATEST_URI="${REPOSITORY_URI}:latest" + build: + commands: + - docker build --tag "$IMAGE_URI" . + - docker tag "$IMAGE_URI" "$IMAGE_LATEST_URI" + post_build: + commands: + - docker push "$IMAGE_URI" + - docker push "$IMAGE_LATEST_URI" + - printf '[{"name":"%s","imageUri":"%s"}]' "$IMAGE_NAME" "$IMAGE_LATEST_URI" > images.json + artifacts: + files: images.json + Environment: + ComputeType: BUILD_GENERAL1_SMALL + Image: aws/codebuild/docker:17.09.0 + Type: LINUX_CONTAINER + EnvironmentVariables: + - Name: AWS_DEFAULT_REGION + Value: !Ref AWS::Region + - Name: REPOSITORY_URI + Value: !Ref Image + - Name: IMAGE_NAME + Value: !Ref AWS::StackName + Name: !Ref AWS::StackName + ServiceRole: !Ref CodeBuildServiceRole + + Pipeline: + Type: AWS::CodePipeline::Pipeline + Properties: + RoleArn: !GetAtt CodePipelineServiceRole.Arn + Name: !Join ['', [!Ref 'AWS::StackName', Pipeline]] + ArtifactStore: + Type: S3 + Location: !Ref ArtifactBucket + Stages: + - Name: Source + Actions: + - Name: App + ActionTypeId: + Category: Source + Owner: ThirdParty + Version: 1 + Provider: GitHub + Configuration: + Owner: !Ref GitHubUser + Repo: !Ref GitHubRepo + Branch: !Ref GitHubBranch + OAuthToken: !Ref GitHubToken + OutputArtifacts: + - Name: App + RunOrder: 1 + - Name: Build + Actions: + - Name: Build + ActionTypeId: + Category: Build + Owner: AWS + Version: 1 + Provider: CodeBuild + Configuration: + ProjectName: !Ref CodeBuildProject + InputArtifacts: + - Name: App + OutputArtifacts: + - Name: BuildOutput + RunOrder: 1 + - Name: Deploy + Actions: + - Name: Deploy + ActionTypeId: + Category: Deploy + Owner: AWS + Version: 1 + Provider: ECS + Configuration: + ClusterName: !Ref Cluster + ServiceName: !Ref Service + FileName: images.json + InputArtifacts: + - Name: BuildOutput + RunOrder: 1 + +# What outputs to expose +Outputs: + Endpoint: + Description: Endpoint + Value: !Join ['', ['https://', !Ref DNSRecord]] + Service: + Value: Service + PipelineUrl: + Value: !Sub https://console.aws.amazon.com/codepipeline/home?region=${AWS::Region}#/view/${Pipeline} + ContainerSecurityGroup: + Value: !Ref ContainerSecurityGroup + TaskDefinition: + Value: !Ref TaskDefinition diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..75ee833 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,21 @@ +version: '3.2' + +services: + api22: + build: + context: ./ + dockerfile: Dockerfile + container_name: api2.uwsgi.web + hostname: api2-uwsgi.local + ports: + - "1881:80" + environment: + - SQLALCHEMY_DATABASE_URI=postgresql://fondberg:@docker.for.mac.localhost/photowall + - API_KEYS=[] + - DEBUG=True + # - SQLALCHEMY_TRACK_MODIFICATIONS=True # change in prestart.sh + +networks: + default: + external: + name: photowall-docker-network diff --git a/fabfile.py b/fabfile.py index 46d1256..9ee3bd1 100644 --- a/fabfile.py +++ b/fabfile.py @@ -1,24 +1,23 @@ -from fabric.api import * +from fabric.api import env, lcd, local, abort -env.user = 'ubuntu' -env.key_filename = '~/.ssh/ukad.pem' +def basic_environment_settings(): + env.stack_name = 'api' -env.hosts = ( - 'ec2-52-50-174-196.eu-west-1.compute.amazonaws.com', #api2-dev -) +def staging(): + basic_environment_settings() + env.environment = 'staging' +def production(): + basic_environment_settings() + env.environment = 'production' + +def cloudformation_update(): + if env.environment != 'staging': + abort('Only staging is deployed soo far') + with lcd('./cloudformation/'): + local('make update-stack ENVIRONMENT_NAME={0} STACK_NAME={1}'.format(env.environment, env.stack_name)) def deploy(): - branch = local('git rev-parse --abbrev-ref HEAD', capture=True) - local('git archive --format tar.gz -o /tmp/api.tar.gz {0}'.format(branch), capture=False) - put('/tmp/api.tar.gz', '/tmp/api.tar.gz') - with warn_only(): - sudo('service nginx stop') - sudo('service api-uwsgi stop') - with cd('/srv/api'): - run('tar xzf /tmp/api.tar.gz') - run('rm /tmp/api.tar.gz') - with warn_only(): - sudo('service api-uwsgi start') - sudo('service nginx start') - local('rm /tmp/api.tar.gz') + if env.environment != 'staging': + abort('Local deploy to staging only. Production deploy is automatic when merging to master.') + local('make publish-to-staging') diff --git a/prestart.sh b/prestart.sh new file mode 100644 index 0000000..4cf2eff --- /dev/null +++ b/prestart.sh @@ -0,0 +1,15 @@ +echo "SQLALCHEMY_DATABASE_URI = '$SQLALCHEMY_DATABASE_URI'" > /app/config.py +echo "SQLALCHEMY_TRACK_MODIFICATIONS = True" >> /app/config.py +echo "API_KEYS = $API_KEYS" >> /app/config.py +echo "BERNARD_QUEUE_URL = '$BERNARD_QUEUE_URL'" >> /app/config.py + +if [ -z "$DEBUG" ] +then + echo "DEBUG = False" >> /app/config.py +else + echo "DEBUG = True" >> /app/config.py + echo "SQLALCHEMY_ENGINE_OPTIONS = {" >> /app/config.py + echo ' "pool_pre_ping": True,' >> /app/config.py + echo ' "pool_recycle": 300,' >> /app/config.py + echo '} ' >> /app/config.py +fi