├── assets ├── infra_v2.png ├── button-ready.svg └── button-in-progress.svg ├── .gitignore ├── scripts ├── gen-passwd.sh ├── push-image.sh └── deploy-proxy.js ├── functions ├── utils │ ├── extractProjectName.js │ └── responses.js ├── services │ └── dynamodb.js ├── href.js ├── handler.js └── image-proxy.js ├── docker ├── change-deployment-bucket.py ├── Dockerfile └── run.sh ├── package.json ├── static ├── 404.html └── in-progress.html ├── README.md └── serverless.yml /assets/infra_v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RafalWilinski/deploy-with-serverless/HEAD/assets/infra_v2.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # package directories 2 | node_modules 3 | jspm_packages 4 | 5 | # Serverless directories 6 | .serverless 7 | 8 | password.yml 9 | user-data.json 10 | -------------------------------------------------------------------------------- /scripts/gen-passwd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | rm -f ./password.yml 5 | 6 | passwd=$(cat /dev/urandom | env LC_CTYPE=C tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1) 7 | printf "password: %s" "$passwd" > ./password.yml 8 | 9 | printf "Password generated!" 10 | -------------------------------------------------------------------------------- /functions/utils/extractProjectName.js: -------------------------------------------------------------------------------- 1 | /* 2 | * In: https://github.com/serverless/serverless 3 | * Out: serverless-serverless 4 | */ 5 | const extractProjectName = url => { 6 | const arr = url.split("/"); 7 | return `${arr[4]}`; 8 | }; 9 | 10 | module.exports = extractProjectName; 11 | -------------------------------------------------------------------------------- /docker/change-deployment-bucket.py: -------------------------------------------------------------------------------- 1 | import yaml 2 | import io 3 | import sys 4 | 5 | with open("serverless.yml", 'r') as stream: 6 | data_loaded = yaml.load(stream) 7 | service_name = data_loaded['service'] 8 | 9 | data_loaded['provider']['deploymentBucket'] = sys.argv[1] 10 | 11 | with io.open('serverless.yml', 'w', encoding='utf8') as outfile: 12 | yaml.dump(data_loaded, outfile, default_flow_style=False, allow_unicode=True) 13 | 14 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:8.6 2 | 3 | RUN apt-get update && \ 4 | apt-get upgrade -y 5 | 6 | RUN apt-get install -y \ 7 | less \ 8 | man \ 9 | python \ 10 | python-pip \ 11 | python-dev \ 12 | python-yaml 13 | 14 | RUN pip install awscli 15 | RUN echo "export PATH=~/.local/bin:$PATH" > ~/.bashrc 16 | RUN ["npm", "install", "-g", "serverless"] 17 | 18 | COPY ./run.sh / 19 | COPY ./change-deployment-bucket.py / 20 | ENTRYPOINT [ "/run.sh" ] 21 | -------------------------------------------------------------------------------- /functions/utils/responses.js: -------------------------------------------------------------------------------- 1 | const dataCode = (statusCode, body, callback) => { 2 | return callback(null, { 3 | statusCode, 4 | body: JSON.stringify(body) 5 | }); 6 | }; 7 | 8 | const redirect = (Location, callback) => { 9 | return callback(null, { 10 | statusCode: 307, 11 | body: "", 12 | headers: { 13 | Location, 14 | "Cache-Control": "max-age=60", 15 | ETag: String(Math.random() * 1000000) 16 | } 17 | }); 18 | }; 19 | 20 | module.exports = { 21 | dataCode, 22 | redirect 23 | }; 24 | -------------------------------------------------------------------------------- /scripts/push-image.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ACCESS_KEY=$1 3 | SECRET_KEY=$2 4 | AWS_ACCOUNT_ID=$3 5 | REGION=$4 6 | 7 | NAME=serverless-batch:latest 8 | 9 | aws configure set profile.serverless-batch.aws_access_key_id $ACCESS_KEY 10 | aws configure set profile.serverless-batch.aws_secret_access_key $SECRET_KEY 11 | aws configure set profile.serverless-batch.region $REGION 12 | 13 | eval $(aws ecr get-login --no-include-email --profile serverless-batch --region $REGION) 14 | 15 | docker build -t $NAME ./docker 16 | docker tag $NAME $AWS_ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/$NAME 17 | docker push $AWS_ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/$NAME 18 | -------------------------------------------------------------------------------- /functions/services/dynamodb.js: -------------------------------------------------------------------------------- 1 | const AWS = require("aws-sdk"); 2 | const DynamoDB = new AWS.DynamoDB.DocumentClient({ region: "us-east-1" }); 3 | 4 | const put = Item => 5 | DynamoDB.put( 6 | Object.assign( 7 | {}, 8 | { Item }, 9 | { 10 | TableName: process.env.PROJECTS_TABLE 11 | } 12 | ) 13 | ).promise(); 14 | 15 | const get = Key => 16 | DynamoDB.get( 17 | Object.assign( 18 | {}, 19 | { Key }, 20 | { 21 | TableName: process.env.PROJECTS_TABLE 22 | } 23 | ) 24 | ) 25 | .promise() 26 | .then(data => data.Item); 27 | 28 | module.exports = { 29 | put, 30 | get 31 | }; 32 | -------------------------------------------------------------------------------- /scripts/deploy-proxy.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const AWS = require('aws-sdk'); 3 | const spawn = require('child_process').spawn; 4 | const STS = new AWS.STS(); 5 | 6 | const handler = (data) => { 7 | console.log('Building & pushing docker image...'); 8 | 9 | STS.getCallerIdentity({}).promise().then((payload) => { 10 | const deploy = spawn('/bin/sh', ['./scripts/push-image.sh', data.accessKey, data.secretKey, payload.Account, 'us-east-1']); 11 | 12 | deploy.stdout.on('data', (data) => console.log(data.toString())); 13 | deploy.stderr.on('data', (data) => console.error(data.toString())); 14 | deploy.on('close', (code) => console.log(`Process exited: ${code}`)); 15 | }).catch(console.error); 16 | } 17 | 18 | module.exports = { handler } 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "deploy-with-serverless", 3 | "version": "0.0.2", 4 | "description": "Architecture behind Deploy with Serverless button", 5 | "main": "index.js", 6 | "scripts": { 7 | "deploy": "serverless deploy", 8 | "gen-passwd": "./scripts/gen-passwd.sh", 9 | "deploy-imgs": "aws s3 cp ./assets s3://deploy-with-serverless --acl public-read --recursive --exclude \"*.png\"", 10 | "deploy-static": "aws s3 cp ./static s3://deploy-with-serverless --acl public-read --recursive", 11 | "deploy-assets": "npm run deploy-imgs && npm run deploy-static" 12 | }, 13 | "author": "Rafal Wilinski ", 14 | "license": "MIT", 15 | "devDependencies": { 16 | "aws-sdk": "^2.130.0", 17 | "serverless-stack-output": "^0.2.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /static/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 404 5 | 6 | 7 | 8 | 9 | 10 | 11 | 22 | 23 | 24 |
25 |
26 | 404 🙈 27 |
28 |
29 | Sorry, we couldn't find this build... 30 |
31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /static/in-progress.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Build in progress... 5 | 6 | 7 | 8 | 9 | 10 | 11 | 22 | 23 | 24 |
25 |
26 | 🙈 27 |
28 |
29 | Sorry, this build is still in progress... 30 |
31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /docker/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | timestamp() { 4 | date +%s 5 | } 6 | 7 | aws configure set region us-east-1 8 | 9 | # Clone repository and change directory 10 | git clone $REPO_URL src 11 | cd src 12 | 13 | # Install dependencies 14 | echo 'Installing dependencies...' 15 | npm install 16 | 17 | # Apply optional build commands like babel or webpack 18 | # eval $BEFORE_CMD 19 | 20 | # Change SLS bucket 21 | python ../change-deployment-bucket.py $BUCKET 22 | 23 | # Run `serverless package --stage dev`, this might be overriden 24 | # eval $PACKAGE_CMD 25 | serverless package --stage dev 26 | 27 | # eval $AFTER_CMD 28 | 29 | # Go to artifacts & compiled Cloudformation template path 30 | cd .serverless 31 | 32 | pwd 33 | 34 | # Upload CFN Template 35 | echo 'Uploading CFN template...' 36 | aws s3 sync . s3://$BUCKET --exclude "*.zip" --acl public-read 37 | 38 | # Put dynamodb item 39 | aws dynamodb put-item \ 40 | --table-name serverless-projects \ 41 | --item '{ 42 | "url": {"S": "'"$REPO_URL"'"}, 43 | "name": {"S": "'"$REPO_NAME"'"}, 44 | "bucket": {"S": "'"$BUCKET"'"}, 45 | "inProgress": {"BOOL": false} 46 | }' 47 | -------------------------------------------------------------------------------- /functions/href.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const DynamoDB = require("./services/dynamodb"); 4 | const responses = require("./utils/responses"); 5 | 6 | const return404 = callback => 7 | responses.redirect( 8 | "https://s3.amazonaws.com/deploy-with-serverless/404.html", 9 | callback 10 | ); 11 | 12 | // Redirects to latest CFN template 13 | module.exports.run = (event, context, callback) => { 14 | DynamoDB.get({ 15 | url: event.queryStringParameters.url 16 | }) 17 | .then(item => { 18 | if (!item) { 19 | return return404(callback); 20 | } 21 | 22 | if (item.inProgress) { 23 | return responses.redirect( 24 | "https://s3.amazonaws.com/deploy-with-serverless/in-progress.html", 25 | callback 26 | ); 27 | } 28 | 29 | const url = [ 30 | "https://console.aws.amazon.com/cloudformation/home?region=us-east-1", 31 | `#/stacks/new?stackName=${item.name}`, 32 | `&templateURL=https://s3.amazonaws.com/${item.bucket}`, 33 | "/cloudformation-template-update-stack.json" 34 | ].join(""); 35 | 36 | return responses.redirect(url, callback); 37 | }) 38 | .catch(error => { 39 | console.error(error); 40 | return return404(callback); 41 | }); 42 | }; 43 | -------------------------------------------------------------------------------- /functions/handler.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const AWS = require("aws-sdk"); 4 | const fs = require("fs"); 5 | const path = require("path"); 6 | const extractProjectName = require("./utils/extractProjectName"); 7 | const response = require("./utils/responses"); 8 | const DynamoDB = require("./services/dynamodb"); 9 | 10 | const Batch = new AWS.Batch(); 11 | 12 | module.exports.run = (json, context, callback) => { 13 | const timestamp = Math.round(+new Date() / 1000); 14 | const name = extractProjectName(json.url); 15 | 16 | console.log(`URL: ${json.url}, Name: ${name}`); 17 | 18 | Batch.submitJob({ 19 | jobDefinition: `${process.env.JOB_DEFINITON_NAME}`, 20 | jobName: name, 21 | jobQueue: process.env.JOB_QUEUE, 22 | containerOverrides: { 23 | environment: [ 24 | { 25 | name: "REPO_URL", 26 | value: json.url 27 | }, 28 | { 29 | name: "REPO_NAME", 30 | value: name 31 | }, 32 | { 33 | name: "BEFORE_CMD", 34 | value: json.before || 'echo "Before cmd not specified"' 35 | }, 36 | { 37 | name: "AFTER_CMD", 38 | value: json.after || 'echo "After cmd not specified"' 39 | }, 40 | { 41 | name: "PACKAGE_CMD", 42 | value: json.package || "serverless package --stage dev" 43 | }, 44 | { 45 | name: "BUCKET", 46 | value: json.bucket 47 | } 48 | ] 49 | } 50 | }) 51 | .promise() 52 | .then(data => { 53 | console.log(data); 54 | return response.dataCode( 55 | 200, 56 | JSON.stringify({ message: "OK" }), 57 | callback 58 | ); 59 | }) 60 | .catch(error => { 61 | console.error(error); 62 | return response.dataCode(200, JSON.stringify({ error }), callback); 63 | }); 64 | }; 65 | -------------------------------------------------------------------------------- /functions/image-proxy.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const AWS = require("aws-sdk"); 4 | const DynamoDB = require("./services/dynamodb"); 5 | const response = require("./utils/responses"); 6 | const extractProjectName = require("./utils/extractProjectName"); 7 | const Lambda = new AWS.Lambda(); 8 | const S3 = new AWS.S3(); 9 | 10 | const createBucket = Bucket => 11 | S3.createBucket({ 12 | Bucket, 13 | ACL: "public-read" 14 | }).promise(); 15 | 16 | const returnReady = callback => 17 | response.redirect( 18 | "https://s3.amazonaws.com/deploy-with-serverless/button-ready.svg", 19 | callback 20 | ); 21 | 22 | module.exports.run = (event, context, callback) => { 23 | // TODO: Check if built project is up to date 24 | 25 | const timestamp = +new Date(); 26 | const url = event.queryStringParameters.url; 27 | const before = event.queryStringParameters.before; 28 | const pkg = event.queryStringParameters.package; 29 | const after = event.queryStringParameters.after; 30 | const bucket = `${extractProjectName(url)}-${timestamp}`; 31 | 32 | DynamoDB.get({ 33 | url 34 | }) 35 | .then(item => { 36 | if (!item) { 37 | console.log("Project not found, submitting job..."); 38 | 39 | console.log(bucket); 40 | 41 | createBucket(bucket) 42 | .then(() => { 43 | return Lambda.invoke({ 44 | FunctionName: "deploy-with-serverless-dev-handler", 45 | Payload: JSON.stringify({ 46 | url, 47 | before, 48 | package: pkg, 49 | after, 50 | bucket 51 | }) 52 | }) 53 | .promise() 54 | .then(() => { 55 | DynamoDB.put({ 56 | inProgress: true, 57 | url, 58 | name: extractProjectName(url), 59 | bucket 60 | }).then(data => { 61 | console.log(data); 62 | return returnReady(callback); 63 | }); 64 | }); 65 | }) 66 | .catch(error => { 67 | console.error(error); 68 | return returnReady(callback); 69 | }); 70 | } 71 | 72 | console.log("Project already built..."); 73 | 74 | return returnReady(callback); 75 | }) 76 | .catch(error => { 77 | console.error(error); 78 | return returnReady(callback); 79 | }); 80 | }; 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deploy with Serverless 2 | 3 | Infrastructure for processing Serverless projects and creating one-click deployments. 4 | 5 | ![Infra](assets/infra_v2.png?raw=true "Infrastructure Overview") 6 | 7 | ## Demo 8 | [![](https://v26mdkczs6.execute-api.us-east-1.amazonaws.com/dev/image?url=https://github.com/RafalWilinski/serverless-medium-text-to-speech)](https://v26mdkczs6.execute-api.us-east-1.amazonaws.com/dev/template?url=http://github.com/RafalWilinski/serverless-medium-text-to-speech) 9 | 10 | 11 | *Clicking button above will start deploy procedure of [serverless-medium-text-to-speech](https://github.com/RafalWilinski/serverless-medium-text-to-speech) project on your AWS Account via CloudFormation* 12 | 13 | ### Goal 14 | Goal of the project is to create a mechanism for 1-click deployments, similar to Deploy on Heroku or Create Stack (CloudFormation) buttons, for Serverless Framework based projects. 15 | 16 | ## Adding to your own project 17 | In `README.md`, add following button/image: 18 | ``` 19 | [![](https://kkcohgzei0.execute-api.us-east-1.amazonaws.com/dev/image?url=)](https://kkcohgzei0.execute-api.us-east-1.amazonaws.com/dev/template?url=) 20 | ``` 21 | 22 | All the magic like building project, uploading artifacts or creating CloudFormation template is handled automatically! 23 | 24 | You can add following params to URL in order to change build procedure: 25 | - `before` - command invoked before `serverless package --stage dev`, by default it's `null` 26 | - `package` - by default `serverless package --stage dev` 27 | - `after` - command invoked after `serverless package --stage dev`, by default it's `null` 28 | 29 | E.g.: 30 | ```url 31 | https://kkcohgzei0.execute-api.us-east-1.amazonaws.com/dev/image 32 | ?url=http://github.com/RafalWilinski/serverless-medium-text-to-speech 33 | &before=npm%20run%20build 34 | &package=serverless%20package%20--stage%20prod 35 | &after=rm%20-fr%20node_modules 36 | ``` 37 | 38 | ## Development 39 | ### Prerequisites 40 | - Node 41 | - AWS Account, AWS CLI & credentials Set 42 | - Serverless Framework 43 | - Docker 44 | 45 | ### Setup 46 | ```bash 47 | git clone https://github.com/RafalWilinski/deploy-with-serverless 48 | cd deploy-with-serverless 49 | npm install 50 | ``` 51 | 52 | ### Commands 53 | - `npm run deploy` - Deploys whole infrastructure, builds Docker image which processes projects and pushes that image to ECR 54 | - `npm run gen-passwd` - generates a random password for user which interacts with ECS 55 | - `npm run deploy-img` - deploys image asset to public bucket 56 | - `npm run deploy-static` - deploys 404 page 57 | - `npm run deploy-assets` - deploys both imgs and static pages 58 | 59 | 60 | -------------------------------------------------------------------------------- /assets/button-ready.svg: -------------------------------------------------------------------------------- 1 | 2 |
Deploy with Serverless Framework
[Not supported by viewer]
-------------------------------------------------------------------------------- /assets/button-in-progress.svg: -------------------------------------------------------------------------------- 1 | 2 |
Deploy with Serverless Framework
[Not supported by viewer]
Building...
[Not supported by viewer]
-------------------------------------------------------------------------------- /serverless.yml: -------------------------------------------------------------------------------- 1 | service: deploy-with-serverless 2 | 3 | plugins: 4 | - serverless-stack-output 5 | 6 | custom: 7 | queueName: serverless-projects-batch-queue 8 | projectsTableName: serverless-projects 9 | jobDefinitionName: serverless-batch-job-definition 10 | keyPair: RafalWilinski-personal-ssh-key 11 | 12 | output: 13 | handler: scripts/deploy-proxy.handler 14 | file: user-data.json 15 | 16 | provider: 17 | name: aws 18 | runtime: nodejs6.10 19 | region: us-east-1 20 | environment: 21 | PROJECTS_TABLE: ${self:custom.projectsTableName} 22 | JOB_QUEUE: ${self:custom.queueName} 23 | JOB_DEFINITON_NAME: ${self:custom.jobDefinitionName} 24 | iamRoleStatements: 25 | - Effect: 'Allow' 26 | Action: 27 | - 'dynamodb:GetItem' 28 | - 'dynamodb:PutItem' 29 | - 'batch:SubmitJob' 30 | - 'lambda:InvokeFunction' 31 | - 's3:*' 32 | Resource: '*' 33 | 34 | functions: 35 | handler: 36 | handler: functions/handler.run 37 | memory: 128 38 | image-proxy: 39 | handler: functions/image-proxy.run 40 | memory: 128 41 | events: 42 | - http: 43 | path: /image 44 | method: GET 45 | cors: true 46 | href: 47 | handler: functions/href.run 48 | memory: 128 49 | events: 50 | - http: 51 | path: /template 52 | method: GET 53 | cors: true 54 | 55 | resources: 56 | Resources: 57 | 58 | # S3 59 | DeployWithServerlessBucket: 60 | Type: AWS::S3::Bucket 61 | Properties: 62 | BucketName: deploy-with-serverless 63 | AccessControl: PublicRead 64 | 65 | # DynamoDB 66 | ServerlessTemplatesTable: 67 | Type: AWS::DynamoDB::Table 68 | Properties: 69 | TableName: ${self:custom.projectsTableName} 70 | AttributeDefinitions: 71 | - AttributeName: url 72 | AttributeType: S 73 | KeySchema: 74 | - AttributeName: url 75 | KeyType: HASH 76 | ProvisionedThroughput: 77 | ReadCapacityUnits: 1 78 | WriteCapacityUnits: 1 79 | 80 | # Networking 81 | ServerlessBatchVPC: 82 | Type: AWS::EC2::VPC 83 | Properties: 84 | CidrBlock: "10.0.0.0/16" 85 | EnableDnsSupport: true 86 | EnableDnsHostnames: true 87 | Tags: 88 | - Key: project 89 | Value: "deploy-with-serverless" 90 | ServerlessBatchInternetGateway: 91 | Type: AWS::EC2::InternetGateway 92 | Properties: 93 | Tags: 94 | - Key: project 95 | Value: "deploy-with-serverless" 96 | ServerlessBatchVPCIPGWAttachment: 97 | Type: AWS::EC2::VPCGatewayAttachment 98 | Properties: 99 | InternetGatewayId: 100 | Ref: ServerlessBatchInternetGateway 101 | VpcId: 102 | Ref: ServerlessBatchVPC 103 | DependsOn: 104 | - ServerlessBatchInternetGateway 105 | - ServerlessBatchVPC 106 | ServerlessBatchRouteTable: 107 | Type: AWS::EC2::RouteTable 108 | Properties: 109 | VpcId: 110 | Ref: ServerlessBatchVPC 111 | Tags: 112 | - Key: project 113 | Value: "deploy-with-serverless" 114 | ServerlessBatchSubnetRouteTableAssoc: 115 | Type: AWS::EC2::SubnetRouteTableAssociation 116 | Properties: 117 | RouteTableId: 118 | Ref: ServerlessBatchRouteTable 119 | SubnetId: 120 | Ref: ServerlessBatchSubnet 121 | ServerlessBatchInternetRoute: 122 | Type: AWS::EC2::Route 123 | Properties: 124 | DestinationCidrBlock: 0.0.0.0/0 125 | GatewayId: 126 | Ref: ServerlessBatchInternetGateway 127 | RouteTableId: 128 | Ref: ServerlessBatchRouteTable 129 | DependsOn: ServerlessBatchInternetGateway 130 | ServerlessBatchSubnet: 131 | Type: AWS::EC2::Subnet 132 | Properties: 133 | CidrBlock: "10.0.0.0/24" 134 | MapPublicIpOnLaunch: true 135 | VpcId: 136 | Ref: ServerlessBatchVPC 137 | Tags: 138 | - Key: project 139 | Value: "deploy-with-serverless" 140 | ServerlessBatchSecurityGroup: 141 | Type: AWS::EC2::SecurityGroup 142 | Properties: 143 | GroupName: ServerlessBatchSecurityGroup 144 | GroupDescription: Security Group for AWS Batch for Serverless-deploy-button project 145 | VpcId: 146 | Ref: ServerlessBatchVPC 147 | SecurityGroupEgress: 148 | - 149 | IpProtocol: tcp 150 | FromPort: "0" 151 | ToPort: "60000" 152 | CidrIp: "0.0.0.0/0" 153 | SecurityGroupIngress: 154 | - 155 | IpProtocol: tcp 156 | FromPort: "22" 157 | ToPort: "22" 158 | CidrIp: "0.0.0.0/0" 159 | Tags: 160 | - Key: project 161 | Value: "deploy-with-serverless" 162 | 163 | # ECR Repository 164 | ServerlessBatchRepository: 165 | Type: AWS::ECR::Repository 166 | Properties: 167 | RepositoryName: "serverless-batch" 168 | RepositoryPolicyText: 169 | Version: "2012-10-17" 170 | Statement: 171 | - 172 | Sid: AllowPushPull 173 | Effect: Allow 174 | Principal: 175 | AWS: 176 | - 'Fn::GetAtt': 177 | - ServerlessBatchUser 178 | - Arn 179 | Action: 180 | - "ecr:GetDownloadUrlForLayer" 181 | - "ecr:BatchGetImage" 182 | - "ecr:BatchCheckLayerAvailability" 183 | - "ecr:PutImage" 184 | - "ecr:InitiateLayerUpload" 185 | - "ecr:UploadLayerPart" 186 | - "ecr:CompleteLayerUpload" 187 | DependsOn: ServerlessBatchUser 188 | 189 | # IAM 190 | ServerlessBatchUser: 191 | Type: AWS::IAM::User 192 | Properties: 193 | LoginProfile: 194 | Password: ${file(./password.yml):password} 195 | UserName: ServerlessBatchUser 196 | ServerlessBatchUserPolicy: 197 | Type: AWS::IAM::Policy 198 | Properties: 199 | PolicyName: ServerlessBatchUserPolicy 200 | PolicyDocument: 201 | Version: "2012-10-17" 202 | Statement: 203 | - 204 | Effect: "Allow" 205 | Action: 206 | - ecr:GetAuthorizationToken 207 | - ecr:InitiateLayerUpload 208 | Resource: "*" 209 | Users: 210 | - Ref: ServerlessBatchUser 211 | DependsOn: ServerlessBatchUser 212 | ServerlessBatchUserKeys: 213 | Type: AWS::IAM::AccessKey 214 | Properties: 215 | UserName: ServerlessBatchUser 216 | DependsOn: ServerlessBatchUser 217 | ServerlessBatchServiceRole: # Standard AWS Batch Role 218 | Type: AWS::IAM::Role 219 | Properties: 220 | RoleName: ServerlessBatchServiceRole 221 | AssumeRolePolicyDocument: 222 | Version: '2012-10-17' 223 | Statement: 224 | - Effect: Allow 225 | Principal: 226 | Service: 227 | - batch.amazonaws.com 228 | Action: sts:AssumeRole 229 | Policies: 230 | - PolicyName: ServerlessBatchPolicy 231 | PolicyDocument: 232 | Version: '2012-10-17' 233 | Statement: 234 | - Effect: Allow 235 | Resource: '*' 236 | Action: 237 | - ec2:DescribeAccountAttributes 238 | - ec2:DescribeInstances 239 | - ec2:DescribeSubnets 240 | - ec2:DescribeSecurityGroups 241 | - ec2:DescribeKeyPairs 242 | - ec2:DescribeImages 243 | - ec2:DescribeImageAttribute 244 | - ec2:DescribeSpotFleetInstances 245 | - ec2:DescribeSpotFleetRequests 246 | - ec2:DescribeSpotPriceHistory 247 | - ec2:RequestSpotFleet 248 | - ec2:CancelSpotFleetRequests 249 | - ec2:ModifySpotFleetRequest 250 | - ec2:TerminateInstances 251 | - autoscaling:DescribeAccountLimits 252 | - autoscaling:DescribeAutoScalingGroups 253 | - autoscaling:DescribeLaunchConfigurations 254 | - autoscaling:DescribeAutoScalingInstances 255 | - autoscaling:CreateLaunchConfiguration 256 | - autoscaling:CreateAutoScalingGroup 257 | - autoscaling:UpdateAutoScalingGroup 258 | - autoscaling:SetDesiredCapacity 259 | - autoscaling:DeleteLaunchConfiguration 260 | - autoscaling:DeleteAutoScalingGroup 261 | - autoscaling:CreateOrUpdateTags 262 | - autoscaling:SuspendProcesses 263 | - autoscaling:PutNotificationConfiguration 264 | - autoscaling:TerminateInstanceInAutoScalingGroup 265 | - ecs:* 266 | - logs:CreateLogGroup 267 | - logs:CreateLogStream 268 | - logs:PutLogEvents 269 | - logs:DescribeLogGroups 270 | - iam:GetInstanceProfile 271 | - iam:PassRole 272 | - ecr:* 273 | - dynamodb:PutItem 274 | - dynamodb:DescribeTable 275 | 276 | # AWS Batch 277 | ServerlessBatchComputeEnvironment: 278 | Type: AWS::Batch::ComputeEnvironment 279 | Properties: 280 | Type: MANAGED 281 | ComputeEnvironmentName: ServerlessBatchComputeEnvironment 282 | ServiceRole: 283 | Fn::GetAtt: 284 | - ServerlessBatchServiceRole 285 | - Arn 286 | State: ENABLED 287 | ComputeResources: 288 | MinvCpus: 0 289 | MaxvCpus: 4 290 | DesiredvCpus: 0 291 | Ec2KeyPair: ${self:custom.keyPair} 292 | Type: "EC2" 293 | ImageId: ami-ec33cc96 # ECS Optimized AMI 294 | InstanceRole: ecsInstanceRole 295 | InstanceTypes: 296 | - m3.medium 297 | SecurityGroupIds: 298 | - Ref: ServerlessBatchSecurityGroup 299 | Subnets: 300 | - Ref: ServerlessBatchSubnet 301 | ServerlessBatchJobQueue: 302 | Type: "AWS::Batch::JobQueue" 303 | Properties: 304 | ComputeEnvironmentOrder: 305 | - ComputeEnvironment: ServerlessBatchComputeEnvironment 306 | Order: 1 307 | Priority: 1 308 | State: ENABLED 309 | JobQueueName: ${self:custom.queueName} 310 | DependsOn: ServerlessBatchComputeEnvironment 311 | ServerlessBatchJobDefinition: 312 | Type: 'AWS::Batch::JobDefinition' 313 | Properties: 314 | Type: container 315 | JobDefinitionName: ${self:custom.jobDefinitionName} 316 | ContainerProperties: 317 | Memory: 1024 318 | Privileged: true 319 | Vcpus: 1 320 | Image: 321 | Fn::Join: 322 | - "" 323 | - - Ref: AWS::AccountId 324 | - '.dkr.ecr.us-east-1.amazonaws.com/serverless-batch:latest' 325 | RetryStrategy: 326 | Attempts: 1 327 | Outputs: 328 | accessKey: 329 | Description: Access Key 330 | Value: 331 | Ref: ServerlessBatchUserKeys 332 | secretKey: 333 | Description: Secret Key 334 | Value: 335 | Fn::GetAtt: 336 | - ServerlessBatchUserKeys 337 | - SecretAccessKey 338 | --------------------------------------------------------------------------------