github.com/mweagle/Sparta@v1.15.0/decorator/safe_deploy.go (about) 1 package decorator 2 3 import ( 4 "fmt" 5 6 "github.com/aws/aws-sdk-go/aws/session" 7 sparta "github.com/mweagle/Sparta" 8 gocf "github.com/mweagle/go-cloudformation" 9 "github.com/sirupsen/logrus" 10 ) 11 12 // codeDeployLambdaUpdateDecorator is the per-function decorator 13 // that adds the necessary information for CodeDeploy 14 func codeDeployLambdaUpdateDecorator(updateType string, 15 codeDeployApplicationName string, 16 codeDeployRoleName string) sparta.TemplateDecorator { 17 return func(serviceName string, 18 lambdaResourceName string, 19 lambdaResource gocf.LambdaFunction, 20 resourceMetadata map[string]interface{}, 21 S3Bucket string, 22 S3Key string, 23 buildID string, 24 template *gocf.Template, 25 context map[string]interface{}, 26 logger *logrus.Logger) error { 27 28 safeDeployResourceName := func(resType string) string { 29 return sparta.CloudFormationResourceName(serviceName, 30 lambdaResourceName, 31 resType) 32 } 33 // Create the AWS::Lambda::Version, with DeletionPolicy=Retain 34 versionResourceName := safeDeployResourceName("version" + buildID) 35 versionResource := &gocf.LambdaVersion{ 36 FunctionName: gocf.Ref(lambdaResourceName).String(), 37 } 38 entry := template.AddResource(versionResourceName, versionResource) 39 entry.DeletionPolicy = "Retain" 40 41 // Create the AWS::CodeDeploy::DeploymentGroup entry that includes a reference 42 // to the IAM role 43 codeDeploymentGroupResourceName := safeDeployResourceName("deploymentGroup") 44 codeDeploymentGroup := &gocf.CodeDeployDeploymentGroup{ 45 ApplicationName: gocf.Ref(codeDeployApplicationName).String(), 46 AutoRollbackConfiguration: &gocf.CodeDeployDeploymentGroupAutoRollbackConfiguration{ 47 Enabled: gocf.Bool(true), 48 Events: gocf.StringList(gocf.String("DEPLOYMENT_FAILURE"), 49 gocf.String("DEPLOYMENT_STOP_ON_ALARM"), 50 gocf.String("DEPLOYMENT_STOP_ON_REQUEST")), 51 }, 52 ServiceRoleArn: gocf.GetAtt(codeDeployRoleName, "Arn"), 53 DeploymentConfigName: gocf.String(fmt.Sprintf("CodeDeployDefault.Lambda%s", updateType)), 54 DeploymentStyle: &gocf.CodeDeployDeploymentGroupDeploymentStyle{ 55 DeploymentType: gocf.String("BLUE_GREEN"), 56 DeploymentOption: gocf.String("WITH_TRAFFIC_CONTROL"), 57 }, 58 } 59 template.AddResource(codeDeploymentGroupResourceName, codeDeploymentGroup) 60 // Create the Alias entry... 61 aliasResourceName := safeDeployResourceName("alias") 62 aliasResource := &gocf.LambdaAlias{ 63 FunctionVersion: gocf.GetAtt(versionResourceName, "Version").String(), 64 FunctionName: gocf.Ref(lambdaResourceName).String(), 65 Name: gocf.String("live"), 66 } 67 aliasEntry := template.AddResource(aliasResourceName, aliasResource) 68 aliasEntry.UpdatePolicy = &gocf.UpdatePolicy{ 69 CodeDeployLambdaAliasUpdate: &gocf.UpdatePolicyCodeDeployLambdaAliasUpdate{ 70 ApplicationName: gocf.Ref(codeDeployApplicationName).String(), 71 DeploymentGroupName: gocf.Ref(codeDeploymentGroupResourceName).String(), 72 }, 73 } 74 return nil 75 } 76 } 77 78 // CodeDeployServiceUpdateDecorator is a service level decorator that attaches 79 // the CodeDeploy safe update to an upgrade operation. 80 // Ref: https://github.com/awslabs/serverless-application-model/blob/master/docs/safe_lambda_deployments.rst 81 // 82 func CodeDeployServiceUpdateDecorator(updateType string, 83 lambdaFuncs []*sparta.LambdaAWSInfo, 84 preHook *sparta.LambdaAWSInfo, 85 postHook *sparta.LambdaAWSInfo) sparta.ServiceDecoratorHookFunc { 86 // Define the names that are shared 87 codeDeployApplicationName := sparta.CloudFormationResourceName("SafeDeploy", 88 "deployment", 89 "application") 90 codeDeployRoleResourceName := sparta.CloudFormationResourceName("SafeDeploy", 91 "deployment", 92 "role") 93 94 // Add the Execution status 95 // See: https://github.com/awslabs/serverless-application-model/blob/master/docs/safe_lambda_deployments.rst#traffic-shifting-using-codedeploy 96 for _, eachFunc := range []*sparta.LambdaAWSInfo{preHook, postHook} { 97 if eachFunc != nil { 98 eachFunc.RoleDefinition.Privileges = append(preHook.RoleDefinition.Privileges, 99 sparta.IAMRolePrivilege{ 100 Actions: []string{"codedeploy:PutLifecycleEventHookExecutionStatus"}, 101 Resource: gocf.Join("", 102 gocf.String("arn:aws:codedeploy:"), 103 gocf.Ref("AWS::Region"), 104 gocf.String(":"), 105 gocf.Ref("AWS::AccountId"), 106 gocf.String(":deploymentgroup:"), 107 gocf.String(codeDeployApplicationName), 108 gocf.String("/*"), 109 )}, 110 ) 111 } 112 } 113 114 // Add the decorator to each lambda 115 for _, eachLambda := range lambdaFuncs { 116 safeDeployDecorator := codeDeployLambdaUpdateDecorator(updateType, 117 codeDeployApplicationName, 118 codeDeployRoleResourceName) 119 eachLambda.Decorators = append(eachLambda.Decorators, 120 sparta.TemplateDecoratorHookFunc(safeDeployDecorator)) 121 } 122 123 // Return the service decorator... 124 return func(context map[string]interface{}, 125 serviceName string, 126 template *gocf.Template, 127 S3Bucket string, 128 S3Key string, 129 buildID string, 130 awsSession *session.Session, 131 noop bool, 132 logger *logrus.Logger) error { 133 // So what we really need to do is walk over all the lambda functions in the template 134 // and setup all the Deployment groups... 135 codeDeployApplication := &gocf.CodeDeployApplication{ 136 ComputePlatform: gocf.String("Lambda"), 137 } 138 template.AddResource(codeDeployApplicationName, codeDeployApplication) 139 // Create the CodeDeploy role 140 // Ensure there is an IAM role for this... 141 // CodeDeployServiceRole 142 143 codeDeployRoleResource := gocf.IAMRole{ 144 ManagedPolicyArns: gocf.StringList(gocf.String("arn:aws:iam::aws:policy/service-role/AWSCodeDeployRoleForLambda")), 145 AssumeRolePolicyDocument: sparta.ArbitraryJSONObject{ 146 "Version": "2012-10-17", 147 "Statement": []sparta.ArbitraryJSONObject{{ 148 "Action": []string{"sts:AssumeRole"}, 149 "Effect": "Allow", 150 "Principal": sparta.ArbitraryJSONObject{ 151 "Service": []string{"codedeploy.amazonaws.com"}, 152 }}, 153 }, 154 }, 155 } 156 template.AddResource(codeDeployRoleResourceName, codeDeployRoleResource) 157 158 // Ship it... 159 return nil 160 } 161 }