diff --git a/.gitignore b/.gitignore index 4c3f589..01ef588 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,46 @@ application/config/environments/*.local.yml .DS_Store .AppleDouble .LSOverride + +### https://raw.github.com/github/gitignore/9f6724149b9a0a861b402683f6c50c5f085d130b/node.gitignore + +# Logs +logs +*.log +npm-debug.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules +jspm_packages + +# Optional npm cache directory +.npm + +# Optional REPL history +.node_repl_history + +infrastructure/**/built \ No newline at end of file diff --git a/infrastructure/README.md b/infrastructure/README.md new file mode 100644 index 0000000..9ac905a --- /dev/null +++ b/infrastructure/README.md @@ -0,0 +1,7 @@ +## How to Deploy + +```bash +# BUCKET_NAME is used for `aws cloudformation package` +# STACK_NAME is used for `aws cloudformation deploy` +$ BUCKET_NAME=bucket-name STACK_NAME=stack-name bin/deploy +``` diff --git a/infrastructure/bin/deploy b/infrastructure/bin/deploy new file mode 100755 index 0000000..edaeed6 --- /dev/null +++ b/infrastructure/bin/deploy @@ -0,0 +1,30 @@ +#!/bin/bash + +if [ -z "$BUCKET_NAME" ]; then + echo 'BUCKET_NAME is required' + exit 1 +fi + +if [ -z "$STACK_NAME" ]; then + echo 'STACK_NAME is required' + exit 1 +fi + +TEMPLATE_FILE=cloudformation.yml + +# Move project root +cd "$(dirname "$(perl -e 'use Cwd "abs_path";print abs_path(shift)' "$0")")/.." || exit 1 + +sh -c 'cd ./functions/RunEcsTask && npm install && npm run build && cp -rf ./node_modules ./built/' + +mkdir -p ./built + +aws cloudformation package \ + --template-file "$TEMPLATE_FILE" \ + --output-template-file ./built/cloudformation.yml \ + --s3-bucket "$BUCKET_NAME" + +aws cloudformation deploy \ + --capabilities CAPABILITY_NAMED_IAM \ + --template-file built/cloudformation.yml \ + --stack-name "$STACK_NAME" diff --git a/infrastructure/cloudformation.yml b/infrastructure/cloudformation.yml index a6ab1ed..8ef49b3 100644 --- a/infrastructure/cloudformation.yml +++ b/infrastructure/cloudformation.yml @@ -1,4 +1,5 @@ AWSTemplateFormatVersion: "2010-09-09" +Transform: AWS::Serverless-2016-10-31 Description: revieee development settings Resources: @@ -163,6 +164,53 @@ Resources: Properties: ClusterName: !Join [ "-", [ !Ref "AWS::StackName", RevieeeCluster ] ] +#------ SpotFleet ---------# + EcsSpotFleetRequest: + Type: AWS::EC2::SpotFleet + Properties: + SpotFleetRequestConfigData: + IamFleetRole: !GetAtt FleetIAMRole.Arn + SpotPrice: !Ref SpotPrice + TargetCapacity: !Ref TargetCapacity + LaunchSpecifications: + - EbsOptimized: 'false' + InstanceType: !Ref MainContainerInstanceType + ImageId: !Ref ContainerInstanceImageId + WeightedCapacity: '4' + UserData: + Fn::Base64: !Sub | + #!/bin/bash + echo ECS_CLUSTER=${ECSCluster} >> /etc/ecs/ecs.config + - EbsOptimizes: 'false' + InstanceType: !Ref SubContainerInstanceType + ImageId: !Ref ContainerInstanceImageId + SubnetId: + Ref: ContainerInstanceSubnet1c + WeightedCapacity: '1' + UserData: + Fn::Base64: !Sub | + #!/bin/bash + echo ECS_CLUSTER=${ECSCluster} >> /etc/ecs/ecs.config + + FleetIAMRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - + Effect: Allow + Principal: + Service: + - spotfleet.amazonaws.com + Action: + - sts:AssumeRole + ManagedPolicyArns: + - 'arn:aws:iam::aws:policy/service-role/AmazonEC2SpotFleetRole' + RoleName: !Join [ "-", [ !Ref "AWS::StackName", FleetIAMRole ] ] + +#------- SpotFleet/ --------# + EndpointInstanceIamRole: Type: AWS::IAM::Role Properties: @@ -199,6 +247,159 @@ Resources: Value: RevieeeEndpointInstance # UserData: [TODO] run itamae + # ------ API Gateway ------ + + ApiRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - + Effect: Allow + Principal: + Service: + - apigateway.amazonaws.com + Action: + - sts:AssumeRole + Policies: + - PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: "states:*" + Resource: "*" + PolicyName: !Join [ "-", [ !Ref "AWS::StackName", AWSStepFunctionsFullAccess ] ] + RoleName: !Join [ "-", [ !Ref "AWS::StackName", RevieeeApiRole ] ] + + RevieeeApi: + Type: AWS::ApiGateway::RestApi + Properties: + Name: !Join [ "-", [ !Ref "AWS::StackName", RevieeeApi ] ] + RevieeeApiResource: + Type: AWS::ApiGateway::Resource + Properties: + RestApiId: !Ref RevieeeApi + ParentId: !GetAtt RevieeeApi.RootResourceId + PathPart: "stage" + RevieeeApiCreateMethod: + Type: AWS::ApiGateway::Method + Properties: + RestApiId: !Ref RevieeeApi + ResourceId: !Ref RevieeeApiResource + AuthorizationType: NONE + HttpMethod: POST + Integration: + IntegrationHttpMethod: POST + IntegrationResponses: + - StatusCode: 200 + PassthroughBehavior: WHEN_NO_TEMPLATES + RequestTemplates: + application/json: !Sub + - |- + { + "input": "{}", + "name": "Hello", + "stateMachineArn": "${stateMachineArn}" + } + - { stateMachineArn: !Ref StateMachineCreate } + Type: AWS + Uri: !Join [ "", [ "arn:aws:apigateway:", !Ref "AWS::Region", ":states:action/StartExecution" ] ] + Credentials: !GetAtt ApiRole.Arn + MethodResponses: + - StatusCode: 200 + ApiDeployment: + Type: AWS::ApiGateway::Deployment + DependsOn: RevieeeApiCreateMethod + Properties: + RestApiId: !Ref RevieeeApi + StageName: pub + + # ------ /API Gateway ------ + + # ------ StepFunctions ------ + + StateMachineRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - + Effect: Allow + Principal: + Service: + - states.ap-northeast-1.amazonaws.com + Action: + - sts:AssumeRole + Policies: + - PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: "lambda:InvokeFunction" + Resource: "*" + PolicyName: !Join [ "-", [ !Ref "AWS::StackName", StatesExecutionPolicy ] ] + RoleName: !Join [ "-", [ !Ref "AWS::StackName", RevieeeStateMachineRole ] ] + + StateMachineCreate: + Type: AWS::StepFunctions::StateMachine + Properties: + DefinitionString: !Sub + - |- + { + "Comment": "Run ECS Task", + "StartAt": "RunEcsTask", + "States": { + "RunEcsTask": { + "Type": "Task", + "Resource": "${taskArn}", + "End": true + } + } + } + - { "taskArn": !Ref RunEcsTaskFunction } + RoleArn: !GetAtt StateMachineRole.Arn + + # ------ /StepFunctions ------ + + # ------ Lambda ------ + + RunEcsTaskFunctionRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - + Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Action: + - sts:AssumeRole + Policies: + - PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: "ecs:RunTask" + Resource: "*" + PolicyName: !Join [ "-", [ !Ref "AWS::StackName", RunEcsTaskFunctionPolicy ] ] + ManagedPolicyArns: + - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' + RoleName: !Join [ "-", [ !Ref "AWS::StackName", RevieeeRunEcsTaskFunctionRole ] ] + + RunEcsTaskFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs6.10 + CodeUri: ./functions/RunEcsTask/built/ + Role: !GetAtt RunEcsTaskFunctionRole.Arn + + # ------ /Lambda ------ + # Parameter Parameters: SSHPort: @@ -209,6 +410,22 @@ Parameters: Type: String AcceptCidrIp: Type: String +# Parameters for SpotFleetRequest + SpotPrice: + Default: 0.139 + Type: Number + TargetCapacity: + Default: 2 + Type: Number + MainContainerInstanceType: + Default: 'm4.large' + Type: String + SubContainerInstanceType: + Default: 'm3.large' + Type: String + ContainerInstanceImageId: + Default: 'ami-3a000e5d' + Type: 'String' Conditions: SSHKeySpecified: diff --git a/infrastructure/functions/RunEcsTask/package.json b/infrastructure/functions/RunEcsTask/package.json new file mode 100644 index 0000000..070ddb3 --- /dev/null +++ b/infrastructure/functions/RunEcsTask/package.json @@ -0,0 +1,23 @@ +{ + "name": "run-ecs-task", + "version": "0.0.1", + "description": "Lambda function that to run ECS Task", + "main": "index.js", + "scripts": { + "build": "tsc -p ./tsconfig.json", + "clean": "rm -rf ./built" + }, + "author": "", + "license": "MIT", + "repository": { + "type": "git", + "url": "git://github.com/speee/webapp-revieee" + }, + "devDependencies": { + "@types/aws-lambda": "0.0.12", + "@types/node": "^7.0.31", + "aws-sdk": "^2.71.0", + "tslint": "^5.4.3", + "typescript": "^2.3.4" + } +} \ No newline at end of file diff --git a/infrastructure/functions/RunEcsTask/src/index.ts b/infrastructure/functions/RunEcsTask/src/index.ts new file mode 100644 index 0000000..a9df5f6 --- /dev/null +++ b/infrastructure/functions/RunEcsTask/src/index.ts @@ -0,0 +1,32 @@ +import { Callback, Context } from "aws-lambda"; +import * as AWS from "aws-sdk"; + +const ecs = new AWS.ECS(); + +export function handler(event: any, context: Context, callback: Callback) { + const envBranch: AWS.ECS.KeyValuePair = { + name: "BRANCH", + value: "master", + }; + const containerOverride: AWS.ECS.ContainerOverride = { + name: "main", + environment: [envBranch], + }; + const taskOverride: AWS.ECS.TaskOverride = { + containerOverrides: [containerOverride], + }; + const params: AWS.ECS.RunTaskRequest = { + cluster: "revieee", + taskDefinition: "arn:aws:ecs:ap-northeast-1:951787653356:task-definition/im-ieul-core:3", + overrides: taskOverride, + }; + + (async () => { + return await ecs.runTask(params).promise(); + })().then((result: AWS.ECS.RunTaskResponse) => { + console.log(result.tasks); + callback(null, result.tasks[0].taskArn); + }).catch((err: AWS.AWSError) => { + callback(err); + }); +} diff --git a/infrastructure/functions/RunEcsTask/tsconfig.json b/infrastructure/functions/RunEcsTask/tsconfig.json new file mode 100644 index 0000000..2b1053b --- /dev/null +++ b/infrastructure/functions/RunEcsTask/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2015", + "noImplicitAny": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "outDir": "./built", + "allowJs": true + }, + "include": [ + "./src/**/*" + ] +} diff --git a/infrastructure/functions/RunEcsTask/tslint.json b/infrastructure/functions/RunEcsTask/tslint.json new file mode 100644 index 0000000..8ca84f2 --- /dev/null +++ b/infrastructure/functions/RunEcsTask/tslint.json @@ -0,0 +1,54 @@ +{ + "rules": { + "class-name": true, + "comment-format": [true, + "check-space" + ], + "curly": true, + "indent": [true, + "spaces" + ], + "jsdoc-format": true, + "no-inferrable-types": true, + "no-internal-module": true, + "no-switch-case-fall-through": true, + "no-trailing-whitespace": true, + "no-var-keyword": true, + "one-line": [true, + "check-open-brace", + "check-whitespace" + ], + "prefer-const": true, + "quotemark": [true, + "double", + "avoid-escape" + ], + "semicolon": [true, "always", "ignore-bound-class-methods"], + "triple-equals": true, + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + }, + { + "call-signature": "onespace", + "index-signature": "onespace", + "parameter": "onespace", + "property-declaration": "onespace", + "variable-declaration": "onespace" + } + ], + "whitespace": [true, + "check-branch", + "check-decl", + "check-operator", + "check-module", + "check-separator", + "check-type" + ] + } +} \ No newline at end of file