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  }