private void AddFunction(Function function, IDictionary <string, object> environmentRefVariables) { var environmentVariables = function.Environment.ToDictionary(kv => "STR_" + kv.Key.ToUpperInvariant(), kv => (dynamic)kv.Value); environmentVariables["TIER"] = Settings.Tier; environmentVariables["MODULE"] = _module.Name; environmentVariables["DEADLETTERQUEUE"] = Settings.DeadLetterQueueUrl; environmentVariables["LOGGINGTOPIC"] = Settings.LoggingTopicArn; environmentVariables["LAMBDARUNTIME"] = function.Runtime; foreach (var environmentRefVariable in environmentRefVariables) { environmentVariables[environmentRefVariable.Key] = environmentRefVariable.Value; } // check if function as a VPC configuration Lambda.FunctionTypes.VpcConfig vpcConfig = null; if (function.VPC != null) { vpcConfig = new Lambda.FunctionTypes.VpcConfig { SubnetIds = function.VPC.SubnetIds, SecurityGroupIds = function.VPC.SecurityGroupIds }; } // create function definition _stack.Add(function.Name, new Lambda.Function { FunctionName = ToAppResourceName(function.Name), Description = function.Description, Runtime = function.Runtime, Handler = function.Handler, Timeout = function.Timeout, MemorySize = function.Memory, ReservedConcurrentExecutions = function.ReservedConcurrency, Role = Fn.GetAtt("ModuleRole", "Arn"), Code = new Lambda.FunctionTypes.Code { S3Bucket = Settings.DeploymentBucketName, S3Key = $"{_module.Name}/{Path.GetFileName(function.PackagePath)}" }, DeadLetterConfig = new Lambda.FunctionTypes.DeadLetterConfig { TargetArn = Settings.DeadLetterQueueArn }, Environment = new Lambda.FunctionTypes.Environment { Variables = environmentVariables }, VpcConfig = vpcConfig, Tags = new List <Tag> { new Tag { Key = "lambdasharp:tier", Value = Settings.Tier }, new Tag { Key = "lambdasharp:module", Value = _module.Name } } }); // check if function is exported if (function.Export != null) { var export = function.Export.StartsWith("/") ? function.Export : $"/{Settings.Tier}/{_module.Name}/{function.Export}"; _stack.Add(function.Name + "SsmParameter", new SSM.Parameter { Name = export, Description = function.Description, Type = "String", Value = Fn.Ref(function.Name) }); } // check if function has any SNS topic event sources var topicSources = function.Sources.OfType <TopicSource>(); if (topicSources.Any()) { foreach (var topicSource in topicSources) { // find the resource that matches the declared topic name var parameter = _module.Parameters .OfType <AResourceParameter>() .FirstOrDefault(p => p.Name == topicSource.TopicName); // determine how to reference the resource object resourceReference; switch (parameter) { case ReferencedResourceParameter reference: resourceReference = reference.Resource.ResourceArn; break; case CloudFormationResourceParameter cloudformation: resourceReference = Fn.Ref(topicSource.TopicName); break; default: throw new ArgumentOutOfRangeException( nameof(parameter), parameter?.GetType().Name ?? "<null>", "parameter resource type must be either ReferencedResourceParameter or CloudFormationResourceParameter" ); } _stack.Add($"{function.Name}{topicSource.TopicName}SnsPermission", new Lambda.Permission { Action = "lambda:InvokeFunction", SourceArn = resourceReference, FunctionName = Fn.GetAtt(function.Name, "Arn"), Principal = "sns.amazonaws.com" }); _stack.Add($"{function.Name}{topicSource.TopicName}Subscription", new SNS.Subscription { Endpoint = Fn.GetAtt(function.Name, "Arn"), Protocol = "lambda", TopicArn = resourceReference }); } } // check if function has any API gateway event sources var scheduleSources = function.Sources.OfType <ScheduleSource>().ToList(); if (scheduleSources.Any()) { for (var i = 0; i < scheduleSources.Count; ++i) { var name = function.Name + "ScheduleEvent" + (i + 1).ToString("00"); _stack.Add(name, new Events.Rule { ScheduleExpression = scheduleSources[i].Expression, Targets = new List <Events.RuleTypes.Target> { new Events.RuleTypes.Target { Id = ToAppResourceName(name), Arn = Fn.GetAtt(function.Name, "Arn"), InputTransformer = new Events.RuleTypes.InputTransformer { InputPathsMap = new Dictionary <string, dynamic> { ["version"] = "$.version", ["id"] = "$.id", ["source"] = "$.source", ["account"] = "$.account", ["time"] = "$.time", ["region"] = "$.region" }, InputTemplate = @"{ ""Version"": <version>, ""Id"": <id>, ""Source"": <source>, ""Account"": <account>, ""Time"": <time>, ""Region"": <region>, ""tName"": """ + scheduleSources[i].Name + @""" }" } } } }); } _stack.Add(function.Name + "ScheduleEventPermission", new Lambda.Permission { Action = "lambda:InvokeFunction", SourceAccount = Settings.AwsAccountId, FunctionName = Fn.GetAtt(function.Name, "Arn"), Principal = "events.amazonaws.com" }); } // check if function has any API gateway event sources var apiSources = function.Sources.OfType <ApiGatewaySource>().ToList(); if (apiSources.Any()) { foreach (var apiEvent in apiSources) { _apiGatewayRoutes.Add(new ApiRoute { Method = apiEvent.Method, Path = apiEvent.Path, Integration = apiEvent.Integration, Function = function, OperationName = apiEvent.OperationName, ApiKeyRequired = apiEvent.ApiKeyRequired }); } } // check if function has any S3 event sources var s3Sources = function.Sources.OfType <S3Source>().ToList(); if (s3Sources.Any()) { foreach (var grp in s3Sources.ToLookup(source => source.Bucket)) { var functionS3Permission = $"{function.Name}{grp.Key}S3Permission"; var functionS3Subscription = $"{function.Name}{grp.Key}S3Subscription"; _stack.Add(functionS3Permission, new Lambda.Permission { Action = "lambda:InvokeFunction", SourceAccount = Settings.AwsAccountId, SourceArn = Fn.GetAtt(grp.Key, "Arn"), FunctionName = Fn.GetAtt(function.Name, "Arn"), Principal = "s3.amazonaws.com" }); _stack.Add(functionS3Subscription, new Model.CustomResource("Custom::LambdaSharpS3Subscriber") { ["ServiceToken"] = Settings.S3SubscriberCustomResourceTopicArn, ["BucketName"] = Fn.Ref(grp.Key), ["FunctionArn"] = Fn.GetAtt(function.Name, "Arn"), ["Filters"] = grp.Select(source => { var filter = new Dictionary <string, object>() { ["Events"] = source.Events, }; if (source.Prefix != null) { filter["Prefix"] = source.Prefix; } if (source.Suffix != null) { filter["Suffix"] = source.Suffix; } return(filter); }).ToList() }); _stack.AddDependsOn(functionS3Subscription, functionS3Permission); } } // check if function has any SQS event sources var sqsSources = function.Sources.OfType <SqsSource>().ToList(); if (sqsSources.Any()) { foreach (var source in sqsSources) { _stack.Add($"{function.Name}{source.Queue}EventMapping", new Lambda.EventSourceMapping { BatchSize = source.BatchSize, Enabled = true, EventSourceArn = Fn.GetAtt(source.Queue, "Arn"), FunctionName = Fn.Ref(function.Name) }); } } // check if function has any Alexa event sources var alexaSources = function.Sources.OfType <AlexaSource>().ToList(); if (alexaSources.Any()) { var index = 0; foreach (var source in alexaSources) { ++index; var suffix = (source.EventSourceToken != null) ? source.EventSourceToken.ToMD5Hash().Substring(0, 7) : index.ToString(); _stack.Add($"{function.Name}AlexaPermission{suffix}", new Lambda.Permission { Action = "lambda:InvokeFunction", FunctionName = Fn.GetAtt(function.Name, "Arn"), Principal = "alexa-appkit.amazon.com", EventSourceToken = source.EventSourceToken }); } } // check if function has any DynamoDB event sources var dynamoDbSources = function.Sources.OfType <DynamoDBSource>().ToList(); if (dynamoDbSources.Any()) { foreach (var source in dynamoDbSources) { _stack.Add($"{function.Name}{source.DynamoDB}EventMapping", new Lambda.EventSourceMapping { BatchSize = source.BatchSize, StartingPosition = source.StartingPosition, Enabled = true, EventSourceArn = Fn.GetAtt(source.DynamoDB, "StreamArn"), FunctionName = Fn.Ref(function.Name) }); } } // check if function has any Kinesis event sources var kinesisSources = function.Sources.OfType <KinesisSource>().ToList(); if (kinesisSources.Any()) { foreach (var source in kinesisSources) { _stack.Add($"{function.Name}{source.Kinesis}EventMapping", new Lambda.EventSourceMapping { BatchSize = source.BatchSize, StartingPosition = source.StartingPosition, Enabled = true, EventSourceArn = Fn.GetAtt(source.Kinesis, "Arn"), FunctionName = Fn.Ref(function.Name) }); } } // check if function has any CloudFormation Macro event sources var macroSources = function.Sources.OfType <MacroSource>().ToList(); if (macroSources.Any()) { foreach (var source in macroSources) { _stack.Add($"{function.Name}{source.MacroName}Macro", new CustomResource("AWS::CloudFormation::Macro") { ["Name"] = $"{Settings.Tier}-{source.MacroName}", ["FunctionName"] = Fn.Ref(function.Name) }); } } }
private void AddFunction(Function function, IDictionary <string, object> environmentRefVariables) { var environmentVariables = function.Environment.ToDictionary(kv => kv.Key, kv => (dynamic)kv.Value); environmentVariables["TIER"] = _module.Settings.Tier; environmentVariables["MODULE"] = _module.Name; environmentVariables["DEADLETTERQUEUE"] = _module.Settings.DeadLetterQueueUrl; environmentVariables["LOGGINGTOPIC"] = _module.Settings.LoggingTopicArn; environmentVariables["LAMBDARUNTIME"] = function.Runtime; foreach (var environmentRefVariable in environmentRefVariables) { environmentVariables[environmentRefVariable.Key] = environmentRefVariable.Value; } // check if function as a VPC configuration Lambda.FunctionTypes.VpcConfig vpcConfig = null; if (function.VPC != null) { vpcConfig = new Lambda.FunctionTypes.VpcConfig { SubnetIds = function.VPC.SubnetIds, SecurityGroupIds = function.VPC.SecurityGroupIds }; } // create function definition _stack.Add(function.Name, new Lambda.Function { FunctionName = ToAppResourceName(function.Name), Description = function.Description, Runtime = function.Runtime, Handler = function.Handler, Timeout = function.Timeout, MemorySize = function.Memory, ReservedConcurrentExecutions = function.ReservedConcurrency, Role = Fn.GetAtt("ModuleRole", "Arn"), Code = new Lambda.FunctionTypes.Code { S3Bucket = _module.Settings.DeploymentBucketName, S3Key = function.PackageS3Key }, DeadLetterConfig = new Lambda.FunctionTypes.DeadLetterConfig { TargetArn = _module.Settings.DeadLetterQueueArn }, Environment = new Lambda.FunctionTypes.Environment { Variables = environmentVariables }, VpcConfig = vpcConfig, Tags = new List <Tag> { new Tag { Key = "lambdasharp:tier", Value = _module.Settings.Tier }, new Tag { Key = "lambdasharp:module", Value = _module.Name } } }); // check if function has any SNS topic event sources var topicSources = function.Sources.OfType <TopicSource>(); if (topicSources.Any()) { foreach (var topicSource in topicSources) { // find the resource that matches the declared topic name var parameter = _module.Parameters .OfType <AResourceParameter>() .FirstOrDefault(p => p.Name == topicSource.TopicName); // determine how to reference the resource object resourceReference; switch (parameter) { case ReferencedResourceParameter reference: resourceReference = reference.Resource.ResourceArn; break; case CloudFormationResourceParameter cloudformation: resourceReference = Fn.Ref(topicSource.TopicName); break; default: throw new ArgumentOutOfRangeException( nameof(parameter), parameter?.GetType().Name ?? "<null>", "parameter resource type must be either ReferencedResourceParameter or CloudFormationResourceParameter" ); } _stack.Add($"{function.Name}{topicSource.TopicName}SnsPermission", new Lambda.Permission { Action = "lambda:InvokeFunction", SourceArn = resourceReference, FunctionName = Fn.GetAtt(function.Name, "Arn"), Principal = "sns.amazonaws.com" }); _stack.Add($"{function.Name}{topicSource.TopicName}Subscription", new SNS.Subscription { Endpoint = Fn.GetAtt(function.Name, "Arn"), Protocol = "lambda", TopicArn = resourceReference }); } } // check if function has any API gateway event sources var scheduleSources = function.Sources.OfType <ScheduleSource>().ToList(); if (scheduleSources.Any()) { for (var i = 0; i < scheduleSources.Count; ++i) { var name = function.Name + "ScheduleEvent" + (i + 1).ToString("00"); _stack.Add(name, new Events.Rule { ScheduleExpression = scheduleSources[i].Expression, Targets = new List <Events.RuleTypes.Target> { new Events.RuleTypes.Target { Id = ToAppResourceName(name), Arn = Fn.GetAtt(function.Name, "Arn"), InputTransformer = new Events.RuleTypes.InputTransformer { InputPathsMap = new Dictionary <string, dynamic> { ["version"] = "$.version", ["id"] = "$.id", ["source"] = "$.source", ["account"] = "$.account", ["time"] = "$.time", ["region"] = "$.region" }, InputTemplate = @"{ ""Version"": <version>, ""Id"": <id>, ""Source"": <source>, ""Account"": <account>, ""Time"": <time>, ""Region"": <region>, ""tName"": """ + scheduleSources[i].Name + @""" }" } } } }); } _stack.Add(function.Name + "ScheduleEventPermission", new Lambda.Permission { Action = "lambda:InvokeFunction", SourceAccount = _module.Settings.AwsAccountId, FunctionName = Fn.GetAtt(function.Name, "Arn"), Principal = "events.amazonaws.com" }); } // check if function has any API gateway event sources var apiSources = function.Sources.OfType <ApiGatewaySource>().ToList(); if (apiSources.Any()) { foreach (var apiEvent in apiSources) { _apiGatewayRoutes.Add(new ApiRoute { Method = apiEvent.Method, Path = apiEvent.Path, Integration = apiEvent.Integration, Function = function }); } } // check if function has any S3 event sources var s3Sources = function.Sources.OfType <S3Source>().ToList(); if (s3Sources.Any()) { foreach (var source in s3Sources.Distinct(source => source.BucketArn)) { var permissionLogicalId = $"{function.Name}{source.Bucket}S3Permission"; _stack.Add(permissionLogicalId, new Lambda.Permission { Action = "lambda:InvokeFunction", SourceAccount = _module.Settings.AwsAccountId, SourceArn = source.BucketArn, FunctionName = Fn.GetAtt(function.Name, "Arn"), Principal = "s3.amazonaws.com" }); _stack.AddDependsOn(source.Bucket, permissionLogicalId); } } // check if function has any SQS event sources var sqsSources = function.Sources.OfType <SqsSource>().ToList(); if (sqsSources.Any()) { foreach (var source in sqsSources) { _stack.Add($"{function.Name}{source.Queue}EventMapping", new Lambda.EventSourceMapping { BatchSize = source.BatchSize, Enabled = true, EventSourceArn = Fn.GetAtt(source.Queue, "Arn"), FunctionName = Fn.Ref(function.Name) }); } } // check if function has any Alexa event sources var alexaSources = function.Sources.OfType <AlexaSource>().ToList(); if (alexaSources.Any()) { var index = 0; foreach (var source in alexaSources) { ++index; var suffix = (source.EventSourceToken != null) ? source.EventSourceToken.ToMD5Hash().Substring(0, 7) : index.ToString(); _stack.Add($"{function.Name}AlexaPermission{suffix}", new Lambda.Permission { Action = "lambda:InvokeFunction", FunctionName = Fn.GetAtt(function.Name, "Arn"), Principal = "alexa-appkit.amazon.com", EventSourceToken = source.EventSourceToken }); } } }