public void ValidBuiltTestModule_ShouldHaveCorrectDependencyInjected() { var moduleBuilder = new ModuleBuilder <TestContext>(new DefaultParser <TestContext>().GetValidTypes()); moduleBuilder.AddDependency(new TestService(int.MaxValue)); moduleBuilder.AddDependency("Unused Dependency String."); var builtModule = moduleBuilder.BuildModule(typeof(TestModule)); Assert.Equal(int.MaxValue, ((TestModule)builtModule.Instance).Service.TestValue); }
private Module <TestContext> BuildValidModule() { var moduleBuilder = new ModuleBuilder <TestContext>(new DefaultParser <TestContext>().GetValidTypes()); moduleBuilder.AddDependency(new TestService(int.MaxValue)); return(moduleBuilder.BuildModule(typeof(TestModule))); }
private IReadOnlyList <Module <TestContext> > GetModules() { var _moduleBuilder = new ModuleBuilder <TestContext>(_parser.GetValidTypes()); _moduleBuilder.AddDependency(new TestService(0)); var module = _moduleBuilder.BuildModule(typeof(TestModule)); return(new List <Module <TestContext> > { module }); }
private void ConvertDependency(int index, ModuleDependencyNode dependency) { AtLocation($"{index}", () => { if (!dependency.Module.TryParseModuleDescriptor(out var moduleOwner, out var moduleName, out var moduleVersion, out var moduleBucketName)) { LogError("invalid module reference format"); return; } _builder.AddDependency( moduleFullName: $"{moduleOwner}.{moduleName}", minVersion: moduleVersion, maxVersion: null, bucketName: moduleBucketName ); });
//--- Methods --- public void Initialize(ModuleBuilder builder) { _builder = builder; // add module variables var moduleItem = _builder.AddVariable( parent: null, name: "Module", description: "Module Variables", type: "String", scope: null, value: "", allow: null, encryptionContext: null ); _builder.AddVariable( parent: moduleItem, name: "Id", description: "Module ID", type: "String", scope: null, value: FnRef("AWS::StackName"), allow: null, encryptionContext: null ); _builder.AddVariable( parent: moduleItem, name: "Owner", description: "Module Owner", type: "String", scope: null, value: _builder.Owner, allow: null, encryptionContext: null ); _builder.AddVariable( parent: moduleItem, name: "Name", description: "Module Name", type: "String", scope: null, value: _builder.Name, allow: null, encryptionContext: null ); _builder.AddVariable( parent: moduleItem, name: "FullName", description: "Module FullName", type: "String", scope: null, value: _builder.FullName, allow: null, encryptionContext: null ); _builder.AddVariable( parent: moduleItem, name: "Version", description: "Module Version", type: "String", scope: null, value: _builder.Version.ToString(), allow: null, encryptionContext: null ); _builder.AddCondition( parent: moduleItem, name: "IsNested", description: "Module is nested", value: FnNot(FnEquals(FnRef("DeploymentRoot"), "")) ); _builder.AddVariable( parent: moduleItem, name: "RootId", description: "Root Module ID", type: "String", scope: null, value: FnIf("Module::IsNested", FnRef("DeploymentRoot"), FnRef("Module::Id")), allow: null, encryptionContext: null ); // add overridable logging retention variable if (!_builder.TryGetOverride("Module::LogRetentionInDays", out var logRetentionInDays)) { logRetentionInDays = 30; } _builder.AddVariable( parent: moduleItem, name: "LogRetentionInDays", description: "Number days log entries are retained for", type: "Number", scope: null, value: logRetentionInDays, allow: null, encryptionContext: null ); // add LambdaSharp Module Options var section = "LambdaSharp Module Options"; _builder.AddParameter( name: "Secrets", section: section, label: "Comma-separated list of additional KMS secret keys", description: "Secret Keys (ARNs)", type: "String", scope: null, noEcho: null, defaultValue: "", constraintDescription: null, allowedPattern: null, allowedValues: null, maxLength: null, maxValue: null, minLength: null, minValue: null, allow: null, properties: null, arnAttribute: null, encryptionContext: null, pragmas: null ); var secretsIsEmpty = _builder.AddCondition( parent: null, name: "SecretsIsEmpty", description: null, value: FnEquals(FnRef("Secrets"), "") ); _builder.AddParameter( name: "XRayTracing", section: section, label: "AWS X-Ray tracing mode for module functions", description: "AWS X-Ray Tracing Mode", type: "String", scope: null, noEcho: null, defaultValue: "PassThrough", constraintDescription: null, allowedPattern: null, allowedValues: new[] { "Active", "PassThrough" }, maxLength: null, maxValue: null, minLength: null, minValue: null, allow: null, properties: null, arnAttribute: null, encryptionContext: null, pragmas: null ); // import lambdasharp dependencies (unless requested otherwise) if (_builder.HasLambdaSharpDependencies) { // add LambdaSharp Module Internal resource imports var lambdasharp = _builder.AddVariable( parent: null, name: "LambdaSharp", description: "LambdaSharp Core Imports", type: "String", scope: null, value: "", allow: null, encryptionContext: null ); _builder.AddImport( parent: lambdasharp, name: "DeadLetterQueue", description: null, // TODO (2018-12-01, bjorg): consider using 'AWS::SQS::Queue' type: "String", scope: null, allow: null /* new[] { * "sqs:SendMessage" * }*/, module: "LambdaSharp.Core", encryptionContext: null ); _builder.AddImport( parent: lambdasharp, name: "LoggingStream", description: null, // NOTE (2018-12-11, bjorg): we use type 'String' to be more flexible with the type of values we're willing to take type: "String", scope: null, allow: null, module: "LambdaSharp.Core", encryptionContext: null ); _builder.AddImport( parent: lambdasharp, name: "LoggingStreamRole", description: null, // NOTE (2018-12-11, bjorg): we use type 'String' to be more flexible with the type of values we're willing to take type: "String", scope: null, allow: null, module: "LambdaSharp.Core", encryptionContext: null ); _builder.AddImport( parent: lambdasharp, name: "DefaultSecretKey", description: null, // TODO (2018-12-01, bjorg): consider using 'AWS::KMS::Key' type: "String", scope: null, // NOTE (2018-12-11, bjorg): we grant decryption access later as part of a bulk permissioning operation allow: null, module: "LambdaSharp.Core", encryptionContext: null ); } // add module variables if (TryGetModuleVariable("DeadLetterQueue", out var deadLetterQueueVariable)) { _builder.AddVariable( parent: moduleItem, name: "DeadLetterQueue", description: "Module Dead Letter Queue (ARN)", type: "String", scope: null, value: deadLetterQueueVariable, allow: null, encryptionContext: null ); _builder.AddGrant( sid: "ModuleDeadLetterQueueLogging", awsType: null, reference: FnRef("Module::DeadLetterQueue"), allow: new[] { "sqs:SendMessage" } ); } if (TryGetModuleVariable("LoggingStream", out var loggingStreamVariable)) { _builder.AddVariable( parent: moduleItem, name: "LoggingStream", description: "Module Logging Stream (ARN)", type: "String", scope: null, value: loggingStreamVariable, allow: null, encryptionContext: null ); } if (TryGetModuleVariable("LoggingStreamRole", out var loggingStreamRoleVariable)) { _builder.AddVariable( parent: moduleItem, name: "LoggingStreamRole", description: "Module Logging Stream Role (ARN)", type: "String", scope: null, value: loggingStreamRoleVariable, allow: null, encryptionContext: null ); } if (TryGetModuleVariable("DefaultSecretKey", out var defaultSecretKeyVariable)) { _builder.AddVariable( parent: moduleItem, name: "DefaultSecretKey", description: "Module Default Secret Key (ARN)", type: "String", scope: null, value: defaultSecretKeyVariable, allow: null, encryptionContext: null ); _builder.AddSecret(FnRef("Module::DefaultSecretKey")); } // add decryption permission for secrets var secretsReference = _builder.Secrets.Any() ? FnSplit( ",", FnIf( secretsIsEmpty.FullName, FnJoin(",", _builder.Secrets), FnJoin( ",", _builder.Secrets.Append(FnRef("Secrets")).ToList() ) ) ) : FnIf( secretsIsEmpty.FullName, // TODO (2018-11-26, bjorg): this hack does not work to bypass the error of an empty list :( "arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/12345678-1234-1234-1234-123456789012", FnSplit(",", FnRef("Secrets")) ); _builder.AddGrant( sid: "SecretsDecryption", awsType: null, reference: secretsReference, allow: new[] { "kms:Decrypt", "kms:Encrypt", "kms:GenerateDataKey", "kms:GenerateDataKeyWithoutPlaintext" } ); // add decryption function for secret parameters and values _builder.AddInlineFunction( parent: moduleItem, name: "DecryptSecretFunction", description: "Module secret decryption function", environment: null, sources: null, condition: null, pragmas: new[] { "no-function-registration", "no-dead-letter-queue", "no-wildcard-scoped-variables" }, timeout: "30", memory: "128", code: DecryptSecretFunctionCode ).DiscardIfNotReachable = true; // add LambdaSharp Deployment Settings section = "LambdaSharp Deployment Settings (DO NOT MODIFY)"; _builder.AddParameter( name: "DeploymentBucketName", section: section, label: "Deployment S3 bucket name", description: "Deployment S3 Bucket Name", type: "String", scope: null, noEcho: null, defaultValue: null, constraintDescription: null, allowedPattern: null, allowedValues: null, maxLength: null, maxValue: null, minLength: null, minValue: null, allow: null, properties: null, arnAttribute: null, encryptionContext: null, pragmas: null ); _builder.AddParameter( name: "DeploymentPrefix", section: section, label: "Deployment tier prefix", description: "Deployment Tier Prefix", type: "String", scope: null, noEcho: null, defaultValue: null, constraintDescription: null, allowedPattern: null, allowedValues: null, maxLength: null, maxValue: null, minLength: null, minValue: null, allow: null, properties: null, arnAttribute: null, encryptionContext: null, pragmas: null ); _builder.AddParameter( name: "DeploymentPrefixLowercase", section: section, label: "Deployment tier prefix (lowercase)", description: "Deployment Tier Prefix (lowercase)", type: "String", scope: null, noEcho: null, defaultValue: null, constraintDescription: null, allowedPattern: null, allowedValues: null, maxLength: null, maxValue: null, minLength: null, minValue: null, allow: null, properties: null, arnAttribute: null, encryptionContext: null, pragmas: null ); _builder.AddParameter( name: "DeploymentRoot", section: section, label: "Root stack name for nested deployments, blank otherwise", description: "Root Stack Name", type: "String", scope: null, noEcho: null, defaultValue: "", constraintDescription: null, allowedPattern: null, allowedValues: null, maxLength: null, maxValue: null, minLength: null, minValue: null, allow: null, properties: null, arnAttribute: null, encryptionContext: null, pragmas: null ); _builder.AddParameter( name: "DeploymentChecksum", section: section, label: "CloudFormation template MD5 checksum", description: "Deployment Checksum", type: "String", scope: null, noEcho: null, defaultValue: "", constraintDescription: null, allowedPattern: null, allowedValues: null, maxLength: null, maxValue: null, minLength: null, minValue: null, allow: null, properties: null, arnAttribute: null, encryptionContext: null, pragmas: null ); // create module IAM role used by all functions _builder.AddResource( parent: moduleItem, name: "Role", description: null, scope: null, resource: new Humidifier.IAM.Role { AssumeRolePolicyDocument = new Humidifier.PolicyDocument { Version = "2012-10-17", Statement = new[] { new Humidifier.Statement { Sid = "ModuleLambdaPrincipal", Effect = "Allow", Principal = new Humidifier.Principal { Service = "lambda.amazonaws.com" }, Action = "sts:AssumeRole" } }.ToList() }, Policies = new[] { new Humidifier.IAM.Policy { PolicyName = FnSub("${AWS::StackName}ModulePolicy"), PolicyDocument = new Humidifier.PolicyDocument { Version = "2012-10-17", Statement = new List <Humidifier.Statement>() } } }.ToList() }, resourceExportAttribute: null, dependsOn: null, condition: null, pragmas: null ).DiscardIfNotReachable = true; // permissions needed for writing to log streams (but not for creating log groups!) _builder.AddGrant( sid: "ModuleLogStreamAccess", awsType: null, reference: "arn:aws:logs:*:*:*", allow: new[] { "logs:CreateLogStream", "logs:PutLogEvents" } ); // permissions needed for X-Ray daemon to upload tracing information _builder.AddGrant( sid: "AWSXRayWriteAccess", awsType: null, reference: "*", allow: new[] { "xray:PutTraceSegments", "xray:PutTelemetryRecords", "xray:GetSamplingRules", "xray:GetSamplingTargets", "xray:GetSamplingStatisticSummaries" } ); // check if lambdasharp specific resources need to be initialized var functions = _builder.Items.OfType <FunctionItem>().ToList(); if (_builder.TryGetItem("Module::DeadLetterQueue", out _)) { foreach (var function in functions.Where(f => f.HasDeadLetterQueue)) { // initialize dead-letter queue function.Function.DeadLetterConfig = new Humidifier.Lambda.FunctionTypes.DeadLetterConfig { TargetArn = FnRef("Module::DeadLetterQueue") }; } } // permissions needed for lambda functions to exist in a VPC if (functions.Any(function => function.Function.VpcConfig != null)) { _builder.AddGrant( sid: "ModuleVpcNetworkInterfaces", awsType: null, reference: "*", allow: new[] { "ec2:DescribeNetworkInterfaces", "ec2:CreateNetworkInterface", "ec2:DeleteNetworkInterface" } ); } // add module registration if (_builder.HasModuleRegistration) { _builder.AddDependency("LambdaSharp.Core", Settings.ToolVersion.GetCompatibleBaseVersion(), maxVersion: null, bucketName: null); // create module registration _builder.AddResource( parent: moduleItem, name: "Registration", description: null, type: "LambdaSharp::Registration::Module", scope: null, allow: null, properties: new Dictionary <string, object> { ["Module"] = _builder.Info, ["ModuleId"] = FnRef("AWS::StackName") }, dependsOn: null, arnAttribute: null, condition: null, pragmas: null ); // handle function registrations var registeredFunctions = _builder.Items .OfType <FunctionItem>() .Where(function => function.HasFunctionRegistration) .ToList(); if (registeredFunctions.Any()) { // create registration-related resources for functions foreach (var function in registeredFunctions) { // create function registration _builder.AddResource( parent: function, name: "Registration", description: null, type: "LambdaSharp::Registration::Function", scope: null, allow: null, properties: new Dictionary <string, object> { ["ModuleId"] = FnRef("AWS::StackName"), ["FunctionId"] = FnRef(function.FullName), ["FunctionName"] = function.Name, ["FunctionLogGroupName"] = FnSub($"/aws/lambda/${{{function.FullName}}}"), ["FunctionPlatform"] = "AWS Lambda", ["FunctionFramework"] = function.Function.Runtime, ["FunctionLanguage"] = function.Language, ["FunctionMaxMemory"] = function.Function.MemorySize, ["FunctionMaxDuration"] = function.Function.Timeout }, dependsOn: new[] { "Module::Registration" }, arnAttribute: null, condition: function.Condition, pragmas: null ); // create function log-group subscription if ( _builder.TryGetItem("Module::LoggingStream", out _) && _builder.TryGetItem("Module::LoggingStreamRole", out _) ) { _builder.AddResource( parent: function, name: "LogGroupSubscription", description: null, scope: null, resource: new Humidifier.Logs.SubscriptionFilter { DestinationArn = FnRef("Module::LoggingStream"), FilterPattern = "-\"*** \"", LogGroupName = FnRef($"{function.FullName}::LogGroup"), RoleArn = FnRef("Module::LoggingStreamRole") }, resourceExportAttribute: null, dependsOn: null, condition: function.Condition, pragmas: null ); } } } } }