using System; using System.Collections.Generic; using System.Runtime.CompilerServices; namespace Amazon.Lambda.Tools.TemplateProcessor { /// /// Class that identifies the fields and the delegates to access those fields in a given IUpdatableResourceDataSource. /// public class UpdatableResourceDefinition { public static readonly UpdatableResourceDefinition DEF_LAMBDA_FUNCTION = new UpdatableResourceDefinition( "AWS::Lambda::Function", FieldDefinition.CreateFieldDefinitionForLambda(false) ); public static readonly UpdatableResourceDefinition DEF_SERVERLESS_FUNCTION = new UpdatableResourceDefinition( "AWS::Serverless::Function", FieldDefinition.CreateFieldDefinitionForLambda(true) ); public static readonly UpdatableResourceDefinition DEF_APIGATEWAY_RESTAPI = new UpdatableResourceDefinition( "AWS::ApiGateway::RestApi", FieldDefinition.CreateFieldDefinitionS3LocationStyle(false, "BodyS3Location", "Bucket", "Key") ); public static readonly UpdatableResourceDefinition DEF_APPSYNC_GRAPHQLSCHEMA = new UpdatableResourceDefinition( "AWS::AppSync::GraphQLSchema", FieldDefinition.CreateFieldDefinitionUrlStyle(false, "DefinitionS3Location") ); public static readonly UpdatableResourceDefinition DEF_APPSYNC_RESOLVER = new UpdatableResourceDefinition( "AWS::AppSync::Resolver", FieldDefinition.CreateFieldDefinitionUrlStyle(false, "ResponseMappingTemplateS3Location"), FieldDefinition.CreateFieldDefinitionUrlStyle(false, "RequestMappingTemplateS3Location") ); public static readonly UpdatableResourceDefinition DEF_SERVERLESS_API = new UpdatableResourceDefinition( "AWS::Serverless::Api", FieldDefinition.CreateFieldDefinitionUrlStyle(false, "DefinitionUri") ); public static readonly UpdatableResourceDefinition DEF_ELASTICBEANSTALK_APPLICATIONVERSION = new UpdatableResourceDefinition( "AWS::ElasticBeanstalk::ApplicationVersion", FieldDefinition.CreateFieldDefinitionS3LocationStyle(false, "SourceBundle", "S3Bucket", "S3Key") ); public static readonly UpdatableResourceDefinition DEF_CLOUDFORMATION_STACK = new UpdatableResourceDefinition( "AWS::CloudFormation::Stack", FieldDefinition.CreateFieldDefinitionUrlStyle(false, "TemplateUrl") ); /// /// All of the known CloudFormation resources that have fields pointing to S3 locations that can be updated from a local path. /// public static IDictionary ValidUpdatableResourceDefinitions = new Dictionary { {DEF_APIGATEWAY_RESTAPI.Name, DEF_APIGATEWAY_RESTAPI}, {DEF_LAMBDA_FUNCTION.Name, DEF_LAMBDA_FUNCTION}, {DEF_SERVERLESS_FUNCTION.Name, DEF_SERVERLESS_FUNCTION}, {DEF_APPSYNC_GRAPHQLSCHEMA.Name, DEF_APPSYNC_GRAPHQLSCHEMA}, {DEF_APPSYNC_RESOLVER.Name, DEF_APPSYNC_RESOLVER}, {DEF_SERVERLESS_API.Name, DEF_SERVERLESS_API}, {DEF_ELASTICBEANSTALK_APPLICATIONVERSION.Name, DEF_ELASTICBEANSTALK_APPLICATIONVERSION}, {DEF_CLOUDFORMATION_STACK.Name, DEF_CLOUDFORMATION_STACK}, }; public UpdatableResourceDefinition(string name, params FieldDefinition[] fields) { this.Name = name; this.Fields = fields; } public string Name { get; } public FieldDefinition[] Fields { get; } /// /// FieldDefinition identity what fields in a CloudFormation resource can be updated and the delegates to retrieve /// and set the information in the datasource. /// public class FieldDefinition { public string Name { get; set; } public bool IsCode { get; set; } /// /// The Func that knows how to get the local path for the field from the datasource. /// public Func GetLocalPath { get; set; } /// /// The Action that knows how to set the S3 location for the field into this datasource. /// public Action SetS3Location { get; set; } /// /// The Action that knows how to set the ImageUri for the field into this datasource. /// public Action SetImageUri { get; set; } /// /// Creates a field definition that handles AWS::Lambda::Function or AWS::Serverless::Function resources. It will /// take care of settting either the S3 location or the ImageUri depending on the package type specified for the resource. /// /// True if the resource is AWS::Serverless::Function otherwise AWS::Lambda::Resource /// public static FieldDefinition CreateFieldDefinitionForLambda(bool isServerlessResource) { return new FieldDefinition { Name = "CodeUri-Or-ImageUri", IsCode = true, GetLocalPath = (s) => { string localPath; var packageType = s.GetValue(LambdaConstants.CF_LAMBDA_PACKAGE_TYPE); if(isServerlessResource) { if (string.Equals(PackageType.Image.Value, packageType, StringComparison.OrdinalIgnoreCase)) { localPath = s.GetValueFromResource(LambdaConstants.CF_SERVERLESS_METADATA, LambdaConstants.CF_SERVERLESS_DOCKERCONTEXT); if (string.IsNullOrEmpty(localPath)) { localPath = s.GetValue(LambdaConstants.CF_LAMBDA_IMAGEURI); } } else { localPath = s.GetValue(LambdaConstants.CF_LAMBDA_CODEURI); } } else { if (string.Equals(PackageType.Image.Value, packageType, StringComparison.OrdinalIgnoreCase)) { localPath = s.GetValue(LambdaConstants.CF_LAMBDA_CODE, LambdaConstants.CF_LAMBDA_IMAGEURI); } else { var bucket = s.GetValue(LambdaConstants.CF_LAMBDA_CODE, LambdaConstants.CF_LAMBDA_S3BUCKET); // If the bucket has a value then that means the CF template is referencing already // uploaded resource. if (!string.IsNullOrEmpty(bucket)) return null; localPath = s.GetValue(LambdaConstants.CF_LAMBDA_CODE, LambdaConstants.CF_LAMBDA_S3KEY); } } if (string.IsNullOrEmpty(localPath)) { localPath = "."; } else if (localPath.StartsWith("s3://")) { localPath = null; } return localPath; }, SetS3Location = (s, b, k) => { if(isServerlessResource) { var s3Url = $"s3://{b}/{k}"; s.SetValue(s3Url, LambdaConstants.CF_LAMBDA_CODEURI); } else { s.SetValue(b, LambdaConstants.CF_LAMBDA_CODE, LambdaConstants.CF_LAMBDA_S3BUCKET); s.SetValue(k, LambdaConstants.CF_LAMBDA_CODE, LambdaConstants.CF_LAMBDA_S3KEY); } }, SetImageUri = (s, i) => { if (isServerlessResource) { s.SetValue(i, LambdaConstants.CF_LAMBDA_IMAGEURI); } else { s.SetValue(i, LambdaConstants.CF_LAMBDA_CODE, LambdaConstants.CF_LAMBDA_IMAGEURI); } } }; } /// /// Creates a field definition that is storing the S3 location as a S3 path like s3://mybucket/myfile.zip /// /// /// /// public static FieldDefinition CreateFieldDefinitionUrlStyle(bool isCode, string propertyName) { return new FieldDefinition { Name = propertyName, IsCode = isCode, GetLocalPath = (s) => { var localPath = s.GetValue(propertyName); if (string.IsNullOrEmpty(localPath)) { if (isCode) { localPath = "."; } } else if (localPath.StartsWith("s3://")) { localPath = null; } return localPath; }, SetS3Location = (s, b, k) => { var s3Url = $"s3://{b}/{k}"; s.SetValue(s3Url, propertyName); } }; } /// /// Creates a field definition that is storing the S3 location using separate fields for bucket and key. /// /// /// /// /// /// public static FieldDefinition CreateFieldDefinitionS3LocationStyle(bool isCode, string containerName, string s3BucketProperty, string s3KeyProperty) { return new FieldDefinition { Name = containerName, IsCode = isCode, GetLocalPath = (s) => { var bucket = s.GetValue(containerName, s3BucketProperty); if (!string.IsNullOrEmpty(bucket)) return null; var value = s.GetValue(containerName, s3KeyProperty); if (string.IsNullOrEmpty(value) && isCode) { value = "."; } return value; }, SetS3Location = (s, b, k) => { s.SetValue(b, containerName, s3BucketProperty); s.SetValue(k, containerName, s3KeyProperty); } }; } } } }