//--- 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: "Namespace",
                description: "Module Namespace",
                type: "String",
                scope: null,
                value: _builder.Namespace,
                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
                );
            _builder.AddVariable(
                parent: moduleItem,
                name: "Info",
                description: "Module Fullname, Version, and Origin",
                type: "String",
                scope: null,
                value: _builder.ModuleInfo.ToString(),
                allow: null,
                encryptionContext: null
                );

            // add module variables
            var deploymentItem = _builder.AddVariable(
                parent: null,
                name: "Deployment",
                description: "Deployment Variables",
                type: "String",
                scope: null,
                value: "",
                allow: null,
                encryptionContext: null
                );

            // add deployment variables
            _builder.AddVariable(
                parent: deploymentItem,
                name: "Tier",
                description: "Deployment tier name",
                type: "String",
                scope: null,
                value: FnSelect("0", FnSplit("-", FnRef("DeploymentPrefix"))),
                allow: null,
                encryptionContext: null
                );
            _builder.AddVariable(
                parent: deploymentItem,
                name: "TierPrefix",
                description: "Deployment tier prefix",
                type: "String",
                scope: null,
                value: FnRef("DeploymentPrefix"),
                allow: null,
                encryptionContext: null
                );
            _builder.AddVariable(
                parent: deploymentItem,
                name: "TierLowercase",
                description: "Deployment tier name in lowercase characters",
                type: "String",
                scope: null,
                value: FnSelect("0", FnSplit("-", FnRef("DeploymentPrefixLowercase"))),
                allow: null,
                encryptionContext: null
                );
            _builder.AddVariable(
                parent: deploymentItem,
                name: "TierPrefixLowercase",
                description: "Deployment tier prefix in lowercase characters",
                type: "String",
                scope: null,
                value: FnRef("DeploymentPrefixLowercase"),
                allow: null,
                encryptionContext: null
                );
            _builder.AddVariable(
                parent: deploymentItem,
                name: "BucketName",
                description: "Deployment S3 Bucket Name",
                type: "String",
                scope: null,
                value: FnRef("DeploymentBucketName"),
                allow: null,
                encryptionContext: null
                );

            // create module IAM role used by all functions
            _builder.TryGetOverride("Module::Role.PermissionsBoundary", out var rolePermissionsBoundary);
            var moduleRoleItem = _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()
                },
                PermissionsBoundary = rolePermissionsBoundary,
                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,
                deletionPolicy: null
                );

            moduleRoleItem.DiscardIfNotReachable = true;

            // add overridable logging retention variable
            if (!_builder.TryGetOverride("Module::LogRetentionInDays", out var logRetentionInDays))
            {
                logRetentionInDays = 30;
            }
            _builder.AddVariable(
                parent: moduleItem,
                name: "LogRetentionInDays",
                description: "Number days CloudWatch Log streams 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,
                deletionPolicy: null
                );
            _builder.AddParameter(
                name: "XRayTracing",
                section: section,
                label: "Enable AWS X-Ray tracing mode for module resources",
                description: "AWS X-Ray Tracing",
                type: "String",
                scope: null,
                noEcho: null,
                defaultValue: XRayTracingLevel.Disabled.ToString(),
                constraintDescription: null,
                allowedPattern: null,
                allowedValues: new[] {
                XRayTracingLevel.Disabled.ToString(),
                XRayTracingLevel.RootModule.ToString(),
                XRayTracingLevel.AllModules.ToString()
            },
                maxLength: null,
                maxValue: null,
                minLength: null,
                minValue: null,
                allow: null,
                properties: null,
                arnAttribute: null,
                encryptionContext: null,
                pragmas: null,
                deletionPolicy: null
                ).DiscardIfNotReachable = true;
            _builder.AddCondition(
                parent: null,
                name: "XRayIsEnabled",
                description: null,
                value: FnNot(FnEquals(FnRef("XRayTracing"), XRayTracingLevel.Disabled.ToString()))
                );
            _builder.AddCondition(
                parent: null,
                name: "XRayNestedIsEnabled",
                description: null,
                value: FnEquals(FnRef("XRayTracing"), XRayTracingLevel.AllModules.ToString())
                );

            // check if module might depend on core services
            if (_builder.HasLambdaSharpDependencies || _builder.HasModuleRegistration)
            {
                _builder.AddParameter(
                    name: "LambdaSharpCoreServices",
                    section: section,
                    label: "Integrate with LambdaSharp.Core services",
                    description: "Use LambdaSharp.Core Services",
                    type: "String",
                    scope: null,
                    noEcho: null,
                    defaultValue: "Disabled",
                    constraintDescription: null,
                    allowedPattern: null,
                    allowedValues: new[] {
                    "Disabled",
                    "Enabled"
                },
                    maxLength: null,
                    maxValue: null,
                    minLength: null,
                    minValue: null,
                    allow: null,
                    properties: null,
                    arnAttribute: null,
                    encryptionContext: null,
                    pragmas: null,
                    deletionPolicy: null
                    ).DiscardIfNotReachable = true;
                _builder.AddCondition(
                    parent: null,
                    name: "UseCoreServices",
                    description: null,
                    value: FnEquals(FnRef("LambdaSharpCoreServices"), "Enabled")
                    );
            }

            // 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,
                    module: "LambdaSharp.Core",
                    encryptionContext: null,
                    out var _
                    );
                _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,
                    out var _
                    );
                _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,
                    out var _
                    );
            }

            // add module variables
            if (TryGetModuleVariable("DeadLetterQueue", out var deadLetterQueueVariable, out var deadLetterQueueCondition))
            {
                _builder.AddVariable(
                    parent: moduleItem,
                    name: "DeadLetterQueue",
                    description: "Module Dead Letter Queue (ARN)",
                    type: "String",
                    scope: null,
                    value: deadLetterQueueVariable,
                    allow: null,
                    encryptionContext: null
                    );
                _builder.AddGrant(
                    name: "DeadLetterQueue",
                    awsType: null,
                    reference: FnRef("Module::DeadLetterQueue"),
                    allow: new[] {
                    "sqs:SendMessage"
                },
                    condition: deadLetterQueueCondition
                    );
            }
            if (TryGetModuleVariable("LoggingStream", out var loggingStreamVariable, out var _))
            {
                _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, out var _))
            {
                _builder.AddVariable(
                    parent: moduleItem,
                    name: "LoggingStreamRole",
                    description: "Module Logging Stream Role (ARN)",
                    type: "String",
                    scope: null,
                    value: loggingStreamRoleVariable,
                    allow: null,
                    encryptionContext: null
                    );
            }

            // add KMS permissions for secrets in module
            if (_builder.Secrets.Any())
            {
                _builder.AddGrant(
                    name: "EmbeddedSecrets",
                    awsType: null,
                    reference: _builder.Secrets.ToList(),
                    allow: new[] {
                    "kms:Decrypt",
                    "kms:Encrypt"
                },
                    condition: null
                    );
            }

            // add decryption function for secret parameters and values
            var decryptSecretFunctionEnvironment = new Dictionary <string, object> {
                ["MODULE_ROLE_SECRETSPOLICY"] = FnIf(
                    "Module::Role::SecretsPolicy::Condition",
                    FnRef("Module::Role::SecretsPolicy"),
                    FnRef("AWS::NoValue")
                    )
            };

            _builder.AddInlineFunction(
                parent: moduleItem,
                name: "DecryptSecretFunction",
                description: "Module secret decryption function",
                environment: decryptSecretFunctionEnvironment,
                sources: null,
                condition: null,
                pragmas: new[] {
                "no-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,
                deletionPolicy: 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,
                deletionPolicy: 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,
                deletionPolicy: 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,
                deletionPolicy: 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,
                deletionPolicy: null
                );

            // add conditional KMS permissions for secrets parameter
            _builder.AddGrant(
                name: "Secrets",
                awsType: null,
                reference: FnSplit(",", FnRef("Secrets")),
                allow: new List <string> {
                "kms:Decrypt",
                "kms:Encrypt"
            },
                condition: FnNot(FnEquals(FnRef("Secrets"), ""))
                );

            // permissions needed for writing to log streams (but not for creating log groups!)
            _builder.AddGrant(
                name: "LogStream",
                awsType: null,
                reference: "arn:aws:logs:*:*:*",
                allow: new[] {
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            },
                condition: null
                );

            // permissions needed for reading state of CloudFormation stack (used by Finalizer to confirm a delete operation is happening)
            _builder.AddGrant(
                name: "CloudFormation",
                awsType: null,
                reference: FnRef("AWS::StackId"),
                allow: new[] {
                "cloudformation:DescribeStacks"
            },
                condition: null
                );

            // permissions needed for X-Ray lambda daemon to upload tracing information
            _builder.AddGrant(
                name: "AWSXRay",
                awsType: null,
                reference: "*",
                allow: new[] {
                "xray:PutTraceSegments",
                "xray:PutTelemetryRecords",
                "xray:GetSamplingRules",
                "xray:GetSamplingTargets",
                "xray:GetSamplingStatisticSummaries"
            },
                condition: null
                );

            // permission needed for posting events to the default event bus
            _builder.AddGrant(
                name: "EventBus",
                awsType: null,
                reference: FnSub("arn:${AWS::Partition}:events:${AWS::Region}:${AWS::AccountId}:event-bus/default"),
                allow: new[] {
                "events:PutEvents"
            },
                condition: null
                );

            // 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")
                    };
                }
            }

            // TODO (2020-06-30, bjorg): should we also check for function.Function.Properties["VpcConfig"]?

            // permissions needed for lambda functions to exist in a VPC
            if (functions.Any(function => function.Function.VpcConfig != null))
            {
                _builder.AddGrant(
                    name: "VpcNetworkInterfaces",
                    awsType: null,
                    reference: "*",
                    allow: new[] {
                    "ec2:DescribeNetworkInterfaces",
                    "ec2:CreateNetworkInterface",
                    "ec2:DeleteNetworkInterface"
                },
                    condition: null
                    );
            }

            // add module registration
            if (_builder.HasModuleRegistration)
            {
                // create module registration
                _builder.AddResource(
                    parent: moduleItem,
                    name: "Registration",
                    description: null,
                    type: "LambdaSharp::Registration::Module",
                    scope: null,
                    allow: null,
                    properties: new Dictionary <string, object> {
                    ["ModuleInfo"] = _builder.ModuleInfo.ToString(),
                    ["ModuleId"]   = FnRef("AWS::StackName")
                },
                    dependsOn: null,
                    arnAttribute: null,
                    condition: "UseCoreServices",
                    pragmas: null,
                    deletionPolicy: 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("Module::Id"),
                            ["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 != null)
                                ? FnAnd(FnCondition("UseCoreServices"), FnCondition(function.Condition))
                                : "UseCoreServices",
                            pragmas: null,
                            deletionPolicy: 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 != null)
                                    ? FnAnd(FnCondition("UseCoreServices"), FnCondition(function.Condition))
                                    : "UseCoreServices",
                                pragmas: null,
                                deletionPolicy: null
                                );
                        }
                    }
                }

                // add app registration
                var registeredApps = _builder.Items
                                     .OfType <AppItem>()
                                     .Where(app => app.HasAppRegistration)
                                     .ToList();
                if (registeredApps.Any())
                {
                    // create registration-related resources for functions
                    foreach (var app in registeredApps)
                    {
                        // create app log-group subscription
                        if (
                            _builder.TryGetItem("Module::LoggingStream", out _) &&
                            _builder.TryGetItem("Module::LoggingStreamRole", out _)
                            )
                        {
                            _builder.AddResource(
                                parent: app,
                                name: "LogGroupSubscription",
                                description: null,
                                scope: null,
                                resource: new Humidifier.Logs.SubscriptionFilter {
                                DestinationArn = FnRef("Module::LoggingStream"),
                                FilterPattern  = "-\"*** \"",
                                LogGroupName   = FnRef($"{app.FullName}::LogGroup"),
                                RoleArn        = FnRef("Module::LoggingStreamRole")
                            },
                                resourceExportAttribute: null,
                                dependsOn: null,
                                condition: "UseCoreServices",
                                pragmas: null,
                                deletionPolicy: null
                                );

                            // create app registration
                            _builder.AddResource(
                                parent: app,
                                name: "Registration",
                                description: null,
                                type: "LambdaSharp::Registration::App",
                                scope: null,
                                allow: null,
                                properties: new Dictionary <string, object> {
                                ["ModuleId"]     = FnRef("Module::Id"),
                                ["AppLogGroup"]  = FnRef($"{app.FullName}::LogGroup"),
                                ["AppId"]        = FnRef(app.FullName),
                                ["AppName"]      = app.Name,
                                ["AppPlatform"]  = FnRef($"{app.FullName}::AppPlatform"),
                                ["AppFramework"] = FnRef($"{app.FullName}::AppFramework"),
                                ["AppLanguage"]  = FnRef($"{app.FullName}::AppLanguage")
                            },
                                dependsOn: new[] { "Module::Registration" },
                                arnAttribute: null,
                                condition: "UseCoreServices",
                                pragmas: null,
                                deletionPolicy: null
                                );
                        }
                    }
                }
            }
        }
示例#2
0
        //--- 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
                                );
                        }
                    }
                }
            }
        }