From 41c29d7f246f0b99abb4d5277d85992e39031441 Mon Sep 17 00:00:00 2001 From: rodrigo_salvatore Date: Tue, 22 Jul 2025 10:39:01 -0300 Subject: [PATCH] adds custom deployment config support. - new customType property to indicate that should not use the prefix for default CodeDeploy config - adds unit test for the case where customType is true - adds test case where customType is true - adds readme descriptions of the new feature --- README.md | 4 +- fixtures/17.input.with-custom-deploy.json | 558 +++++++++++++++++ fixtures/17.output.with-custom-deploy.json | 655 ++++++++++++++++++++ fixtures/17.service.with-custom-deploy.json | 53 ++ lib/CfTemplateGenerators/CodeDeploy.js | 2 +- lib/CfTemplateGenerators/CodeDeploy.test.js | 17 + 6 files changed, 1287 insertions(+), 2 deletions(-) create mode 100644 fixtures/17.input.with-custom-deploy.json create mode 100644 fixtures/17.output.with-custom-deploy.json create mode 100644 fixtures/17.service.with-custom-deploy.json diff --git a/README.md b/README.md index 4d5ad6e..81d081c 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ functions: - http: GET hello deploymentSettings: type: Linear10PercentEvery1Minute + customType: false alias: Live preTrafficHook: preHook postTrafficHook: postHook @@ -60,7 +61,7 @@ You can see a working example in the [example folder](./example/). ## Configuration -* `type`: (required) defines how the traffic will be shifted between Lambda function versions. It must be one of the following: +* `type`: (required) defines how the traffic will be shifted between Lambda function versions. It must be one of the following if customType is not true: - `Canary10Percent5Minutes`: shifts 10 percent of traffic in the first increment. The remaining 90 percent is deployed five minutes later. - `Canary10Percent10Minutes`: shifts 10 percent of traffic in the first increment. The remaining 90 percent is deployed 10 minutes later. - `Canary10Percent15Minutes`: shifts 10 percent of traffic in the first increment. The remaining 90 percent is deployed 15 minutes later. @@ -70,6 +71,7 @@ You can see a working example in the [example folder](./example/). - `Linear10PercentEvery3Minutes`: shifts 10 percent of traffic every three minutes until all traffic is shifted. - `Linear10PercentEvery10Minutes`: shifts 10 percent of traffic every 10 minutes until all traffic is shifted. - `AllAtOnce`: shifts all the traffic to the new version, useful when you only need to execute the validation hooks. +* `customType`: (optional) When set to true you can use your custom CodeDeploy Deployment Configurations. (more info [here](https://docs.aws.amazon.com/codedeploy/latest/userguide/deployment-configurations.html)). * `alias`: (required) name that will be used to create the Lambda function alias. * `preTrafficHook`: (optional) validation Lambda function that runs before traffic shifting. It must use the CodeDeploy SDK to notify about this step's success or failure (more info [here](https://docs.aws.amazon.com/codedeploy/latest/userguide/reference-appspec-file-structure-hooks.html)). * `postTrafficHook`: (optional) validation Lambda function that runs after traffic shifting. It must use the CodeDeploy SDK to notify about this step's success or failure (more info [here](https://docs.aws.amazon.com/codedeploy/latest/userguide/reference-appspec-file-structure-hooks.html)) diff --git a/fixtures/17.input.with-custom-deploy.json b/fixtures/17.input.with-custom-deploy.json new file mode 100644 index 0000000..0d23337 --- /dev/null +++ b/fixtures/17.input.with-custom-deploy.json @@ -0,0 +1,558 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "The AWS CloudFormation template for this Serverless application", + "Resources": { + "ServerlessDeploymentBucket": { + "Type": "AWS::S3::Bucket" + }, + "HelloLogGroup": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "LogGroupName": "/aws/lambda/canary-deployments-test-dev-hello" + } + }, + "PreHookLogGroup": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "LogGroupName": "/aws/lambda/canary-deployments-test-dev-preHook" + } + }, + "PostHookLogGroup": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "LogGroupName": "/aws/lambda/canary-deployments-test-dev-postHook" + } + }, + "IamRoleLambdaExecution": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + }, + "Action": [ + "sts:AssumeRole" + ] + } + ] + }, + "Policies": [ + { + "PolicyName": { + "Fn::Join": [ + "-", + [ + "dev", + "canary-deployments-test", + "lambda" + ] + ] + }, + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "logs:CreateLogStream" + ], + "Resource": [ + { + "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/canary-deployments-test-dev-hello:*" + }, + { + "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/canary-deployments-test-dev-preHook:*" + }, + { + "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/canary-deployments-test-dev-postHook:*" + } + ] + }, + { + "Effect": "Allow", + "Action": [ + "logs:PutLogEvents" + ], + "Resource": [ + { + "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/canary-deployments-test-dev-hello:*:*" + }, + { + "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/canary-deployments-test-dev-preHook:*:*" + }, + { + "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/canary-deployments-test-dev-postHook:*:*" + } + ] + }, + { + "Effect": "Allow", + "Action": [ + "codedeploy:*" + ], + "Resource": [ + "*" + ] + }, + { + "Effect": "Allow", + "Action": [ + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:DescribeStream", + "dynamodb:ListStreams" + ], + "Resource": [ + { + "Fn::GetAtt": [ + "StreamsTestTable", + "StreamArn" + ] + } + ] + } + ] + } + } + ], + "Path": "/", + "RoleName": { + "Fn::Join": [ + "-", + [ + "canary-deployments-test", + "dev", + "us-east-1", + "lambdaRole" + ] + ] + } + } + }, + "HelloLambdaFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "ServerlessDeploymentBucket" + }, + "S3Key": "serverless/canary-deployments-test/dev/1520191533287-2018-03-04T19:25:33.287Z/canary-deployments-test.zip" + }, + "FunctionName": "canary-deployments-test-dev-hello", + "Handler": "handler.hello", + "MemorySize": 1024, + "Role": { + "Fn::GetAtt": [ + "IamRoleLambdaExecution", + "Arn" + ] + }, + "Runtime": "nodejs6.10", + "Timeout": 6 + }, + "DependsOn": [ + "HelloLogGroup", + "IamRoleLambdaExecution" + ] + }, + "HelloLambdaVersionFYAirphUvjV7H12yGxU1eQrqAiSBMjAi9hdLPgV62L8": { + "Type": "AWS::Lambda::Version", + "DeletionPolicy": "Retain", + "Properties": { + "FunctionName": { + "Ref": "HelloLambdaFunction" + }, + "CodeSha256": "sZvdDgxnAbKe1yaQga0XJPD82+o5jFWz+J3lR+q9UHU=" + } + }, + "PreHookLambdaFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "ServerlessDeploymentBucket" + }, + "S3Key": "serverless/canary-deployments-test/dev/1520191533287-2018-03-04T19:25:33.287Z/canary-deployments-test.zip" + }, + "FunctionName": "canary-deployments-test-dev-preHook", + "Handler": "hooks.pre", + "MemorySize": 1024, + "Role": { + "Fn::GetAtt": [ + "IamRoleLambdaExecution", + "Arn" + ] + }, + "Runtime": "nodejs6.10", + "Timeout": 6 + }, + "DependsOn": [ + "PreHookLogGroup", + "IamRoleLambdaExecution" + ] + }, + "PreHookLambdaVersionIYyrXlfQM5jjU68REvnAzRxhgq9eoLqSsDjy0": { + "Type": "AWS::Lambda::Version", + "DeletionPolicy": "Retain", + "Properties": { + "FunctionName": { + "Ref": "PreHookLambdaFunction" + }, + "CodeSha256": "sZvdDgxnAbKe1yaQga0XJPD82+o5jFWz+J3lR+q9UHU=" + } + }, + "PostHookLambdaFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "ServerlessDeploymentBucket" + }, + "S3Key": "serverless/canary-deployments-test/dev/1520191533287-2018-03-04T19:25:33.287Z/canary-deployments-test.zip" + }, + "FunctionName": "canary-deployments-test-dev-postHook", + "Handler": "hooks.post", + "MemorySize": 1024, + "Role": { + "Fn::GetAtt": [ + "IamRoleLambdaExecution", + "Arn" + ] + }, + "Runtime": "nodejs6.10", + "Timeout": 6 + }, + "DependsOn": [ + "PostHookLogGroup", + "IamRoleLambdaExecution" + ] + }, + "PostHookLambdaVersiondh0VUUAh9BrmvORqx3vDEIcHxolKWKCO1YL45mVTbg": { + "Type": "AWS::Lambda::Version", + "DeletionPolicy": "Retain", + "Properties": { + "FunctionName": { + "Ref": "PostHookLambdaFunction" + }, + "CodeSha256": "sZvdDgxnAbKe1yaQga0XJPD82+o5jFWz+J3lR+q9UHU=" + } + }, + "ApiGatewayRestApi": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Name": "dev-canary-deployments-test", + "EndpointConfiguration": { + "Types": [ + "EDGE" + ] + } + } + }, + "ApiGatewayResourceHello": { + "Type": "AWS::ApiGateway::Resource", + "Properties": { + "ParentId": { + "Fn::GetAtt": [ + "ApiGatewayRestApi", + "RootResourceId" + ] + }, + "PathPart": "hello", + "RestApiId": { + "Ref": "ApiGatewayRestApi" + } + } + }, + "ApiGatewayMethodHelloGet": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "GET", + "RequestParameters": {}, + "ResourceId": { + "Ref": "ApiGatewayResourceHello" + }, + "RestApiId": { + "Ref": "ApiGatewayRestApi" + }, + "ApiKeyRequired": false, + "AuthorizationType": "NONE", + "Integration": { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:aws:apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "HelloLambdaFunction", + "Arn" + ] + }, + "/invocations" + ] + ] + } + }, + "MethodResponses": [] + } + }, + "ApiGatewayDeployment12345": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "ApiGatewayRestApi" + }, + "StageName": "dev" + }, + "DependsOn": [ + "ApiGatewayMethodHelloGet" + ] + }, + "HelloLambdaPermissionApiGateway": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "FunctionName": { + "Fn::GetAtt": [ + "HelloLambdaFunction", + "Arn" + ] + }, + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:aws:execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "ApiGatewayRestApi" + }, + "/*/*" + ] + ] + } + } + }, + "SNSTopicSnsTopic": { + "Type": "AWS::SNS::Topic", + "Properties": { + "TopicName": "snsTopic", + "DisplayName": "", + "Subscription": [ + { + "Endpoint": { + "Fn::GetAtt": [ + "HelloLambdaFunction", + "Arn" + ] + }, + "Protocol": "lambda" + } + ] + } + }, + "HelloLambdaPermissionSnsTopicSNS": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "FunctionName": { + "Fn::GetAtt": [ + "HelloLambdaFunction", + "Arn" + ] + }, + "Action": "lambda:InvokeFunction", + "Principal": "sns.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:aws:sns:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + "snsTopic" + ] + ] + } + } + }, + "S3BucketS3SampleBucket": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": "s3SampleBucket", + "NotificationConfiguration": { + "LambdaConfigurations": [ + { + "Event": "s3:ObjectCreated:*", + "Function": { + "Fn::GetAtt": [ + "HelloLambdaFunction", + "Arn" + ] + } + } + ] + } + }, + "DependsOn": [ + "HelloLambdaPermissionS3SampleBucketS3" + ] + }, + "HelloLambdaPermissionS3SampleBucketS3": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "FunctionName": { + "Fn::GetAtt": [ + "HelloLambdaFunction", + "Arn" + ] + }, + "Action": "lambda:InvokeFunction", + "Principal": "s3.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::s3SampleBucket" + ] + ] + } + } + }, + "HelloEventSourceMappingDynamodbStreamsTestTable": { + "Type": "AWS::Lambda::EventSourceMapping", + "DependsOn": "IamRoleLambdaExecution", + "Properties": { + "BatchSize": 10, + "EventSourceArn": { + "Fn::GetAtt": [ + "StreamsTestTable", + "StreamArn" + ] + }, + "FunctionName": { + "Fn::GetAtt": [ + "HelloLambdaFunction", + "Arn" + ] + }, + "StartingPosition": "TRIM_HORIZON", + "Enabled": "True" + } + }, + "HelloFooAlarm": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "Namespace": "AWS/Lambda", + "MetricName": "Errors", + "Threshold": 1, + "Period": 60, + "EvaluationPeriods": 1, + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "OKActions": [], + "AlarmActions": [], + "InsufficientDataActions": [], + "Dimensions": [ + { + "Name": "FunctionName", + "Value": { + "Ref": "HelloLambdaFunction" + } + } + ], + "TreatMissingData": "missing", + "Statistic": "Minimum" + } + }, + "StreamsTestTable": { + "Type": "AWS::DynamoDB::Table", + "Properties": { + "TableName": "StreamsTestTable", + "AttributeDefinitions": [ + { + "AttributeName": "id", + "AttributeType": "S" + } + ], + "KeySchema": [ + { + "AttributeName": "id", + "KeyType": "HASH" + } + ], + "ProvisionedThroughput": { + "ReadCapacityUnits": 1, + "WriteCapacityUnits": 1 + }, + "StreamSpecification": { + "StreamViewType": "NEW_AND_OLD_IMAGES" + } + } + } + }, + "Outputs": { + "ServerlessDeploymentBucketName": { + "Value": { + "Ref": "ServerlessDeploymentBucket" + } + }, + "HelloLambdaFunctionQualifiedArn": { + "Description": "Current Lambda function version", + "Value": { + "Ref": "HelloLambdaVersionFYAirphUvjV7H12yGxU1eQrqAiSBMjAi9hdLPgV62L8" + } + }, + "PreHookLambdaFunctionQualifiedArn": { + "Description": "Current Lambda function version", + "Value": { + "Ref": "PreHookLambdaVersionIYyrXlfQM5jjU68REvnAzRxhgq9eoLqSsDjy0" + } + }, + "PostHookLambdaFunctionQualifiedArn": { + "Description": "Current Lambda function version", + "Value": { + "Ref": "PostHookLambdaVersiondh0VUUAh9BrmvORqx3vDEIcHxolKWKCO1YL45mVTbg" + } + }, + "ServiceEndpoint": { + "Description": "URL of the service endpoint", + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "ApiGatewayRestApi" + }, + ".execute-api.us-east-1.amazonaws.com/dev" + ] + ] + } + } + } +} \ No newline at end of file diff --git a/fixtures/17.output.with-custom-deploy.json b/fixtures/17.output.with-custom-deploy.json new file mode 100644 index 0000000..5b550d6 --- /dev/null +++ b/fixtures/17.output.with-custom-deploy.json @@ -0,0 +1,655 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "The AWS CloudFormation template for this Serverless application", + "Resources": { + "ServerlessDeploymentBucket": { + "Type": "AWS::S3::Bucket" + }, + "HelloLogGroup": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "LogGroupName": "/aws/lambda/canary-deployments-test-dev-hello" + } + }, + "PreHookLogGroup": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "LogGroupName": "/aws/lambda/canary-deployments-test-dev-preHook" + } + }, + "PostHookLogGroup": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "LogGroupName": "/aws/lambda/canary-deployments-test-dev-postHook" + } + }, + "IamRoleLambdaExecution": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + }, + "Action": [ + "sts:AssumeRole" + ] + } + ] + }, + "Policies": [ + { + "PolicyName": { + "Fn::Join": [ + "-", + [ + "dev", + "canary-deployments-test", + "lambda" + ] + ] + }, + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "logs:CreateLogStream" + ], + "Resource": [ + { + "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/canary-deployments-test-dev-hello:*" + }, + { + "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/canary-deployments-test-dev-preHook:*" + }, + { + "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/canary-deployments-test-dev-postHook:*" + } + ] + }, + { + "Effect": "Allow", + "Action": [ + "logs:PutLogEvents" + ], + "Resource": [ + { + "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/canary-deployments-test-dev-hello:*:*" + }, + { + "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/canary-deployments-test-dev-preHook:*:*" + }, + { + "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/canary-deployments-test-dev-postHook:*:*" + } + ] + }, + { + "Effect": "Allow", + "Action": [ + "codedeploy:*" + ], + "Resource": [ + "*" + ] + }, + { + "Effect": "Allow", + "Action": [ + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:DescribeStream", + "dynamodb:ListStreams" + ], + "Resource": [ + { + "Fn::GetAtt": [ + "StreamsTestTable", + "StreamArn" + ] + } + ] + }, + { + "Action": [ + "codedeploy:PutLifecycleEventHookExecutionStatus" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Sub": "arn:${AWS::Partition}:codedeploy:${AWS::Region}:${AWS::AccountId}:deploymentgroup:${CanarydeploymentstestdevDeploymentApplication}/canary-deployments-test-dev-HelloLambdaFunctionDeploymentGroup" + } + ] + } + ] + } + } + ], + "Path": "/", + "RoleName": { + "Fn::Join": [ + "-", + [ + "canary-deployments-test", + "dev", + "us-east-1", + "lambdaRole" + ] + ] + } + } + }, + "HelloLambdaFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "ServerlessDeploymentBucket" + }, + "S3Key": "serverless/canary-deployments-test/dev/1520191533287-2018-03-04T19:25:33.287Z/canary-deployments-test.zip" + }, + "FunctionName": "canary-deployments-test-dev-hello", + "Handler": "handler.hello", + "MemorySize": 1024, + "Role": { + "Fn::GetAtt": [ + "IamRoleLambdaExecution", + "Arn" + ] + }, + "Runtime": "nodejs6.10", + "Timeout": 6 + }, + "DependsOn": [ + "HelloLogGroup", + "IamRoleLambdaExecution" + ] + }, + "HelloLambdaVersionFYAirphUvjV7H12yGxU1eQrqAiSBMjAi9hdLPgV62L8": { + "Type": "AWS::Lambda::Version", + "DeletionPolicy": "Retain", + "Properties": { + "FunctionName": { + "Ref": "HelloLambdaFunction" + }, + "CodeSha256": "sZvdDgxnAbKe1yaQga0XJPD82+o5jFWz+J3lR+q9UHU=" + } + }, + "PreHookLambdaFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "ServerlessDeploymentBucket" + }, + "S3Key": "serverless/canary-deployments-test/dev/1520191533287-2018-03-04T19:25:33.287Z/canary-deployments-test.zip" + }, + "FunctionName": "canary-deployments-test-dev-preHook", + "Handler": "hooks.pre", + "MemorySize": 1024, + "Role": { + "Fn::GetAtt": [ + "IamRoleLambdaExecution", + "Arn" + ] + }, + "Runtime": "nodejs6.10", + "Timeout": 6 + }, + "DependsOn": [ + "PreHookLogGroup", + "IamRoleLambdaExecution" + ] + }, + "PreHookLambdaVersionIYyrXlfQM5jjU68REvnAzRxhgq9eoLqSsDjy0": { + "Type": "AWS::Lambda::Version", + "DeletionPolicy": "Retain", + "Properties": { + "FunctionName": { + "Ref": "PreHookLambdaFunction" + }, + "CodeSha256": "sZvdDgxnAbKe1yaQga0XJPD82+o5jFWz+J3lR+q9UHU=" + } + }, + "PostHookLambdaFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "ServerlessDeploymentBucket" + }, + "S3Key": "serverless/canary-deployments-test/dev/1520191533287-2018-03-04T19:25:33.287Z/canary-deployments-test.zip" + }, + "FunctionName": "canary-deployments-test-dev-postHook", + "Handler": "hooks.post", + "MemorySize": 1024, + "Role": { + "Fn::GetAtt": [ + "IamRoleLambdaExecution", + "Arn" + ] + }, + "Runtime": "nodejs6.10", + "Timeout": 6 + }, + "DependsOn": [ + "PostHookLogGroup", + "IamRoleLambdaExecution" + ] + }, + "PostHookLambdaVersiondh0VUUAh9BrmvORqx3vDEIcHxolKWKCO1YL45mVTbg": { + "Type": "AWS::Lambda::Version", + "DeletionPolicy": "Retain", + "Properties": { + "FunctionName": { + "Ref": "PostHookLambdaFunction" + }, + "CodeSha256": "sZvdDgxnAbKe1yaQga0XJPD82+o5jFWz+J3lR+q9UHU=" + } + }, + "ApiGatewayRestApi": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Name": "dev-canary-deployments-test", + "EndpointConfiguration": { + "Types": [ + "EDGE" + ] + } + } + }, + "ApiGatewayResourceHello": { + "Type": "AWS::ApiGateway::Resource", + "Properties": { + "ParentId": { + "Fn::GetAtt": [ + "ApiGatewayRestApi", + "RootResourceId" + ] + }, + "PathPart": "hello", + "RestApiId": { + "Ref": "ApiGatewayRestApi" + } + } + }, + "ApiGatewayMethodHelloGet": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "GET", + "RequestParameters": {}, + "ResourceId": { + "Ref": "ApiGatewayResourceHello" + }, + "RestApiId": { + "Ref": "ApiGatewayRestApi" + }, + "ApiKeyRequired": false, + "AuthorizationType": "NONE", + "Integration": { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:aws:apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", + { + "Ref": "HelloLambdaFunctionAliasLive" + }, + "/invocations" + ] + ] + } + }, + "MethodResponses": [] + } + }, + "ApiGatewayDeployment12345": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "ApiGatewayRestApi" + }, + "StageName": "dev" + }, + "DependsOn": [ + "ApiGatewayMethodHelloGet" + ] + }, + "HelloLambdaPermissionApiGateway": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "FunctionName": { + "Ref": "HelloLambdaFunctionAliasLive" + }, + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:aws:execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "ApiGatewayRestApi" + }, + "/*/*" + ] + ] + } + } + }, + "SNSTopicSnsTopic": { + "Type": "AWS::SNS::Topic", + "Properties": { + "TopicName": "snsTopic", + "DisplayName": "", + "Subscription": [ + { + "Endpoint": { + "Ref": "HelloLambdaFunctionAliasLive" + }, + "Protocol": "lambda" + } + ] + } + }, + "HelloLambdaPermissionSnsTopicSNS": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "FunctionName": { + "Ref": "HelloLambdaFunctionAliasLive" + }, + "Action": "lambda:InvokeFunction", + "Principal": "sns.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:aws:sns:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + "snsTopic" + ] + ] + } + } + }, + "S3BucketS3SampleBucket": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": "s3SampleBucket", + "NotificationConfiguration": { + "LambdaConfigurations": [ + { + "Event": "s3:ObjectCreated:*", + "Function": { + "Ref": "HelloLambdaFunctionAliasLive" + } + } + ] + } + }, + "DependsOn": [ + "HelloLambdaPermissionS3SampleBucketS3" + ] + }, + "HelloLambdaPermissionS3SampleBucketS3": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "FunctionName": { + "Ref": "HelloLambdaFunctionAliasLive" + }, + "Action": "lambda:InvokeFunction", + "Principal": "s3.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::s3SampleBucket" + ] + ] + } + } + }, + "HelloEventSourceMappingDynamodbStreamsTestTable": { + "Type": "AWS::Lambda::EventSourceMapping", + "DependsOn": "IamRoleLambdaExecution", + "Properties": { + "BatchSize": 10, + "EventSourceArn": { + "Fn::GetAtt": [ + "StreamsTestTable", + "StreamArn" + ] + }, + "FunctionName": { + "Ref": "HelloLambdaFunctionAliasLive" + }, + "StartingPosition": "TRIM_HORIZON", + "Enabled": "True" + } + }, + "HelloFooAlarm": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "Namespace": "AWS/Lambda", + "MetricName": "Errors", + "Threshold": 1, + "Period": 60, + "EvaluationPeriods": 1, + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "OKActions": [], + "AlarmActions": [], + "InsufficientDataActions": [], + "Dimensions": [ + { + "Name": "FunctionName", + "Value": { + "Ref": "HelloLambdaFunction" + } + } + ], + "TreatMissingData": "missing", + "Statistic": "Minimum" + } + }, + "StreamsTestTable": { + "Type": "AWS::DynamoDB::Table", + "Properties": { + "TableName": "StreamsTestTable", + "AttributeDefinitions": [ + { + "AttributeName": "id", + "AttributeType": "S" + } + ], + "KeySchema": [ + { + "AttributeName": "id", + "KeyType": "HASH" + } + ], + "ProvisionedThroughput": { + "ReadCapacityUnits": 1, + "WriteCapacityUnits": 1 + }, + "StreamSpecification": { + "StreamViewType": "NEW_AND_OLD_IMAGES" + } + } + }, + "CanarydeploymentstestdevDeploymentApplication": { + "Type": "AWS::CodeDeploy::Application", + "Properties": { + "ComputePlatform": "Lambda" + } + }, + "CodeDeployServiceRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSCodeDeployRoleForLambdaLimited", + "arn:aws:iam::aws:policy/AWSLambda_FullAccess" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "codedeploy.amazonaws.com" + ] + } + } + ] + } + } + }, + "HelloLambdaFunctionDeploymentGroup": { + "Type": "AWS::CodeDeploy::DeploymentGroup", + "Properties": { + "ApplicationName": { + "Ref": "CanarydeploymentstestdevDeploymentApplication" + }, + "AutoRollbackConfiguration": { + "Enabled": true, + "Events": [ + "DEPLOYMENT_FAILURE", + "DEPLOYMENT_STOP_ON_ALARM", + "DEPLOYMENT_STOP_ON_REQUEST" + ] + }, + "ServiceRoleArn": { + "Fn::GetAtt": [ + "CodeDeployServiceRole", + "Arn" + ] + }, + "DeploymentConfigName": { + "Fn::Sub": [ + "${ConfigName}", + { + "ConfigName": "MyCustomDeploymentConfig" + } + ] + }, + "DeploymentGroupName": "canary-deployments-test-dev-HelloLambdaFunctionDeploymentGroup", + "DeploymentStyle": { + "DeploymentType": "BLUE_GREEN", + "DeploymentOption": "WITH_TRAFFIC_CONTROL" + }, + "AlarmConfiguration": { + "Alarms": [ + { + "Name": { + "Ref": "HelloFooAlarm" + } + } + ], + "Enabled": true + } + } + }, + "HelloLambdaFunctionAliasLive": { + "Type": "AWS::Lambda::Alias", + "Properties": { + "FunctionVersion": { + "Fn::GetAtt": [ + "HelloLambdaVersionFYAirphUvjV7H12yGxU1eQrqAiSBMjAi9hdLPgV62L8", + "Version" + ] + }, + "FunctionName": { + "Ref": "HelloLambdaFunction" + }, + "Name": "Live" + }, + "UpdatePolicy": { + "CodeDeployLambdaAliasUpdate": { + "ApplicationName": { + "Ref": "CanarydeploymentstestdevDeploymentApplication" + }, + "AfterAllowTrafficHook": { + "Ref": "PostHookLambdaFunction" + }, + "BeforeAllowTrafficHook": { + "Ref": "PreHookLambdaFunction" + }, + "DeploymentGroupName": { + "Ref": "HelloLambdaFunctionDeploymentGroup" + } + } + } + } + }, + "Outputs": { + "ServerlessDeploymentBucketName": { + "Value": { + "Ref": "ServerlessDeploymentBucket" + } + }, + "HelloLambdaFunctionQualifiedArn": { + "Description": "Current Lambda function version", + "Value": { + "Ref": "HelloLambdaVersionFYAirphUvjV7H12yGxU1eQrqAiSBMjAi9hdLPgV62L8" + } + }, + "PreHookLambdaFunctionQualifiedArn": { + "Description": "Current Lambda function version", + "Value": { + "Ref": "PreHookLambdaVersionIYyrXlfQM5jjU68REvnAzRxhgq9eoLqSsDjy0" + } + }, + "PostHookLambdaFunctionQualifiedArn": { + "Description": "Current Lambda function version", + "Value": { + "Ref": "PostHookLambdaVersiondh0VUUAh9BrmvORqx3vDEIcHxolKWKCO1YL45mVTbg" + } + }, + "ServiceEndpoint": { + "Description": "URL of the service endpoint", + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "ApiGatewayRestApi" + }, + ".execute-api.us-east-1.amazonaws.com/dev" + ] + ] + } + } + } +} diff --git a/fixtures/17.service.with-custom-deploy.json b/fixtures/17.service.with-custom-deploy.json new file mode 100644 index 0000000..46c1e73 --- /dev/null +++ b/fixtures/17.service.with-custom-deploy.json @@ -0,0 +1,53 @@ +{ + "service": "canary-deployments-test", + "custom": { + "deploymentSettings": { + "stages": [ + "dev" + ] + } + }, + "functions": { + "hello": { + "handler": "handler.hello", + "events": [ + { + "http": "GET hello" + }, + { + "stream": { + "type": "dynamodb", + "arn": { + "Fn::GetAtt": [ + "StreamsTestTable", + "StreamArn" + ] + } + } + }, + { + "sns": "snsTopic" + }, + { + "s3": "s3SampleBucket" + } + ], + "deploymentSettings": { + "type": "MyCustomDeploymentConfig", + "customType": "true", + "alias": "Live", + "preTrafficHook": "preHook", + "postTrafficHook": "postHook", + "alarms": [ + "HelloFooAlarm" + ] + } + }, + "preHook": { + "handler": "hooks.pre" + }, + "postHook": { + "handler": "hooks.post" + } + } +} diff --git a/lib/CfTemplateGenerators/CodeDeploy.js b/lib/CfTemplateGenerators/CodeDeploy.js index c4c3a16..14ad184 100644 --- a/lib/CfTemplateGenerators/CodeDeploy.js +++ b/lib/CfTemplateGenerators/CodeDeploy.js @@ -27,7 +27,7 @@ function buildFnDeploymentGroup ({ codeDeployAppName, codeDeployGroupName, codeD DeploymentGroupName: codeDeployGroupName, DeploymentConfigName: { 'Fn::Sub': [ - 'CodeDeployDefault.Lambda${ConfigName}', + String(deploymentSettings.customType).toLowerCase() === 'true' ? '${ConfigName}' : 'CodeDeployDefault.Lambda${ConfigName}', { ConfigName: deploymentSettings.type } ] }, diff --git a/lib/CfTemplateGenerators/CodeDeploy.test.js b/lib/CfTemplateGenerators/CodeDeploy.test.js index 2a4bd6d..f560e81 100644 --- a/lib/CfTemplateGenerators/CodeDeploy.test.js +++ b/lib/CfTemplateGenerators/CodeDeploy.test.js @@ -131,5 +131,22 @@ describe('CodeDeploy', () => { expect(actual).to.deep.equal(expected) }) }) + + context('when customType configuration is true', () => { + it('should use type with no prefix for default CodeDeploy configuration', () => { + const deploymentSettings = { + type: 'Linear10PercentEvery1Minute', + customType: 'true' + } + const expected = _.pipe( + _.set('Properties.ApplicationName', { Ref: codeDeployAppName }), + _.set('Properties.DeploymentGroupName', codeDeployGroupName), + _.set('Properties.DeploymentConfigName.Fn::Sub[0]', '${ConfigName}'), + _.set('Properties.DeploymentConfigName.Fn::Sub[1].ConfigName', deploymentSettings.type) + )(baseDeploymentGroup) + const actual = CodeDeploy.buildFnDeploymentGroup({ codeDeployAppName, codeDeployGroupName, deploymentSettings }) + expect(actual).to.deep.equal(expected) + }) + }) }) })