{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "Creates resources needed to run a Datomic query group.",
  "Metadata": {
    "AWS::CloudFormation::Interface": {
      "ParameterGroups": [
        {
          "Parameters": [
            "SystemName",
            "InstanceType",
            "KeyName",
            "ApplicationName",
            "EnvironmentMap",
            "PreloadDb",
            "MetricsLevel"
          ]
        },
        {
          "Parameters": [
            "DesiredCapacity",
            "MinSize",
            "MaxSize",
            "MinInstancesInService"
          ],
          "Label": {
            "default": "Auto Scaling Configuration"
          }
        },
        {
          "Parameters": [
            "ClientApi",
            "ClientApiConcurrentProcessing",
            "Ions"
          ],
          "Label": {
            "default": "API Gateways"
          }
        },
        {
          "Parameters": [
            "NodePolicyArn"
          ],
          "Label": {
            "default": "Optional Configuration"
          }
        },
        {
          "Parameters": [
            "OverrideSettings"
          ],
          "Label": {
            "default": "Support Configuration"
          }
        }
      ],
      "ParameterLabels": {
        "OverrideSettings": {
          "default": "Override Settings"
        },
        "ClientApi": {
          "default": "Client API"
        },
        "SystemName": {
          "default": "System Name"
        },
        "Ions": {
          "default": "Ions"
        },
        "InstanceType": {
          "default": "Instance Type"
        },
        "NodePolicyArn": {
          "default": "IAM managed policy"
        },
        "ClientApiConcurrentProcessing": {
          "default": "Maximum number of concurrent Client API operations"
        },
        "EnvironmentMap": {
          "default": "Environment Map"
        },
        "KeyName": {
          "default": "AWS EC2 Key Pair"
        },
        "MinInstancesInService": {
          "default": "Minimum number of query group instances during update"
        },
        "ApplicationName": {
          "default": "Application Name"
        },
        "PreloadDb": {
          "default": "Preload Database"
        },
        "MetricsLevel": {
          "default": "Metrics Level"
        },
        "MinSize": {
          "default": "Minimum query group instances"
        },
        "MaxSize": {
          "default": "Maximum query group instances"
        },
        "DesiredCapacity": {
          "default": "Desired capacity"
        }
      }
    }
  },
  "Parameters": {
    "OverrideSettings": {
      "Description": "Leave blank unless directed by Datomic Support.",
      "Type": "String",
      "Default": ""
    },
    "ClientApi": {
      "Description": "Client API access for developers and applications outside the Datomic VPC.",
      "Type": "String",
      "AllowedValues": [
        "yes",
        "no"
      ],
      "Default": "yes"
    },
    "SystemName": {
      "Description": "System name that this stack will serve. This is the name you gave the Storage when you created it.",
      "Type": "String"
    },
    "Ions": {
      "Description": "HTTP access for ion web services",
      "Type": "String",
      "AllowedValues": [
        "yes",
        "no"
      ],
      "Default": "yes"
    },
    "InstanceType": {
      "Description": "Cluster node instance type.",
      "Default": "t3.small",
      "AllowedValues": [
        "t3.small",
        "t3.medium",
        "t3.large",
        "t3.xlarge",
        "t3.2xlarge",
        "i3.large",
        "i3.xlarge",
        "i3.2xlarge",
        "i3.4xlarge",
        "i3.8xlarge",
        "i3.16xlarge",
        "i3.metal"
      ],
      "Type": "String"
    },
    "NodePolicyArn": {
      "Description": "Optional. The ARN of an IAM managed policy to add to the role that group nodes run with.",
      "Type": "String",
      "Default": ""
    },
    "ClientApiConcurrentProcessing": {
      "AllowedPattern": "(Default)|(\\d+)",
      "ConstraintDescription": "must be a positive integer or 'Default'",
      "Default": "Default",
      "Description": "Maximum number of concurrent Client API operations.",
      "Type": "String"
    },
    "EnvironmentMap": {
      "Description": "A map of k/v pairs that can be accessed via ion/get-env.",
      "Type": "String",
      "Default": "{:env :dev-or-prod}"
    },
    "KeyName": {
      "Description": "The key pair to assign to cluster nodes.",
      "Type": "AWS::EC2::KeyPair::KeyName"
    },
    "MinInstancesInService": {
      "AllowedPattern": "(Default)|(\\d+)",
      "ConstraintDescription": "must be a positive integer or 'Default'",
      "Default": "Default",
      "Description": "Minimum number of instances that must be in service within Auto Scaling group while AWS CloudFormation updates old instances. ('Default' to accept recommended settings for your instance type, or override with an integer.)",
      "Type": "String"
    },
    "ApplicationName": {
      "Description": "Code Deploy application name for ion applications. Defaults to the stack name.",
      "Type": "String",
      "Default": ""
    },
    "PreloadDb": {
      "Description": "Name of a database to be preloaded.",
      "Type": "String",
      "Default": ""
    },
    "MetricsLevel": {
      "Description": "Level of detail for CloudWatch metrics.",
      "Type": "String",
      "AllowedValues": [
        "Default",
        "None",
        "Basic",
        "Detailed"
      ],
      "Default": "Default"
    },
    "MinSize": {
      "AllowedPattern": "(Default)|(\\d+)",
      "ConstraintDescription": "must be a positive integer or 'Default'",
      "Default": "Default",
      "Description": "Minimum size of the Auto Scaling group. ('Default' to accept recommended settings for your instance type, or override with an integer.)",
      "Type": "String"
    },
    "MaxSize": {
      "AllowedPattern": "(Default)|(\\d+)",
      "ConstraintDescription": "must be a positive integer or 'Default'",
      "Default": "Default",
      "Description": "Maximum size of the Auto Scaling group. ('Default' to accept recommended settings for your instance type, or override with an integer.)",
      "Type": "String"
    },
    "DesiredCapacity": {
      "AllowedPattern": "(Default)|(\\d+)",
      "ConstraintDescription": "must be a positive integer or 'Default'",
      "Default": "Default",
      "Description": "Desired capacity for the Auto Scaling group. ('Default' to accept recommended settings for your instance type, or override with an integer.)",
      "Type": "String"
    }
  },
  "Mappings": {
    "Constants": {
      "Cft": {
        "NodejsRuntime": "nodejs22.x"
      }
    },
    "Datomic": {
      "defaults": {
        "DisableSsl": "true",
        "EfsValsPath": "datomic/vals",
        "JvmFlags": "-Dclojure.spec.skip-macros=true -XX:+UseBiasedLocking -XX:+UseG1GC -XX:MaxGCPauseMillis=50",
        "KmsCmk": "alias/datomic",
        "OsReserveMb": 512,
        "PctJvmMemForHeap": 70,
        "PctMemForJvm": 100
      }
    },
    "InstanceSettings": {
      "t3.small": {
        "JvmFlags": "-XX:MaxDirectMemorySize=256m -Xss512k",
        "DesiredCapacity": "1"
      },
      "t3.2xlarge": {
        "JvmFlags": "-XX:MaxDirectMemorySize=2048m",
        "DesiredCapacity": "2"
      },
      "t3.xlarge": {
        "JvmFlags": "-XX:MaxDirectMemorySize=2048m",
        "DesiredCapacity": "2"
      },
      "t3.large": {
        "JvmFlags": "-XX:MaxDirectMemorySize=2048m",
        "DesiredCapacity": "2"
      },
      "i3.16xlarge": {
        "JvmFlags": "-XX:MaxDirectMemorySize=2048m",
        "DesiredCapacity": "2"
      },
      "i3.4xlarge": {
        "JvmFlags": "-XX:MaxDirectMemorySize=2048m",
        "DesiredCapacity": "2"
      },
      "i3.metal": {
        "JvmFlags": "-XX:MaxDirectMemorySize=2048m",
        "DesiredCapacity": "2"
      },
      "i3.8xlarge": {
        "JvmFlags": "-XX:MaxDirectMemorySize=2048m",
        "DesiredCapacity": "2"
      },
      "t3.medium": {
        "JvmFlags": "-XX:MaxDirectMemorySize=512m",
        "DesiredCapacity": "1"
      },
      "i3.2xlarge": {
        "JvmFlags": "-XX:MaxDirectMemorySize=2048m",
        "DesiredCapacity": "2"
      },
      "i3.xlarge": {
        "JvmFlags": "-XX:MaxDirectMemorySize=2048m",
        "DesiredCapacity": "2"
      },
      "i3.large": {
        "JvmFlags": "-XX:MaxDirectMemorySize=2048m",
        "DesiredCapacity": "2"
      }
    },
    "RegionMap": {
      "ap-northeast-1": {
        "Datomic": "ami-014a6c397e17910c5"
      },
      "eu-west-1": {
        "Datomic": "ami-09c37b4e1c88ed39c"
      },
      "us-east-2": {
        "Datomic": "ami-0a32a1f94240c0a2a"
      },
      "ap-southeast-2": {
        "Datomic": "ami-0ff92c597a73772b7"
      },
      "sa-east-1": {
        "Datomic": "ami-01802d8eae4dfd862"
      },
      "ap-southeast-1": {
        "Datomic": "ami-016ab4cdd66f6e4fa"
      },
      "ca-central-1": {
        "Datomic": "ami-04be6582826f12de6"
      },
      "eu-central-1": {
        "Datomic": "ami-0db5eb7e0e6670d97"
      },
      "eu-west-2": {
        "Datomic": "ami-0253109f68c4565f3"
      },
      "us-west-2": {
        "Datomic": "ami-0d3a31f98c1cbb6eb"
      },
      "us-east-1": {
        "Datomic": "ami-068299149f8def719"
      },
      "ap-south-1": {
        "Datomic": "ami-0a822d333e750f2f7"
      },
      "eu-north-1": {
        "Datomic": "ami-0da53b463984400e9"
      }
    }
  },
  "Conditions": {
    "CreateHttpDirectApiGateway": {
      "Fn::Equals": [
        {
          "Ref": "Ions"
        },
        "yes"
      ]
    },
    "ApplicationNameProvided": {
      "Fn::Not": [
        {
          "Fn::Equals": [
            {
              "Ref": "ApplicationName"
            },
            ""
          ]
        }
      ]
    },
    "CreateClientApiGateway": {
      "Fn::Equals": [
        {
          "Ref": "ClientApi"
        },
        "yes"
      ]
    },
    "AttachManagedPolicy": {
      "Fn::Not": [
        {
          "Fn::Equals": [
            {
              "Ref": "NodePolicyArn"
            },
            ""
          ]
        }
      ]
    },
    "DefaultDesiredCapacity": {
      "Fn::Equals": [
        {
          "Ref": "DesiredCapacity"
        },
        "Default"
      ]
    },
    "BasicMetrics": {
      "Fn::Or": [
        {
          "Fn::Equals": [
            {
              "Ref": "MetricsLevel"
            },
            "Basic"
          ]
        },
        {
          "Fn::And": [
            {
              "Fn::Equals": [
                {
                  "Ref": "MetricsLevel"
                },
                "Default"
              ]
            },
            {
              "Fn::Equals": [
                {
                  "Ref": "InstanceType"
                },
                "t3.medium"
              ]
            }
          ]
        }
      ]
    },
    "DefaultMaxSize": {
      "Fn::Equals": [
        {
          "Ref": "MaxSize"
        },
        "Default"
      ]
    },
    "CreateDashboard": {
      "Fn::Or": [
        {
          "Condition": "BasicMetrics"
        },
        {
          "Condition": "DetailedMetrics"
        }
      ]
    },
    "DefaultMinInstancesInService": {
      "Fn::Equals": [
        {
          "Ref": "MinInstancesInService"
        },
        "Default"
      ]
    },
    "DefaultMinSize": {
      "Fn::Equals": [
        {
          "Ref": "MinSize"
        },
        "Default"
      ]
    },
    "UseSsd": {
      "Fn::Equals": [
        {
          "Fn::Select": [
            "0",
            {
              "Fn::Split": [
                ".",
                {
                  "Ref": "InstanceType"
                }
              ]
            }
          ]
        },
        "i3"
      ]
    },
    "NoMetrics": {
      "Fn::Or": [
        {
          "Fn::Equals": [
            {
              "Ref": "MetricsLevel"
            },
            "None"
          ]
        },
        {
          "Fn::And": [
            {
              "Fn::Equals": [
                {
                  "Ref": "MetricsLevel"
                },
                "Default"
              ]
            },
            {
              "Fn::Equals": [
                {
                  "Ref": "InstanceType"
                },
                "t3.small"
              ]
            }
          ]
        }
      ]
    },
    "DetailedMetrics": {
      "Fn::Not": [
        {
          "Fn::Or": [
            {
              "Condition": "BasicMetrics"
            },
            {
              "Condition": "NoMetrics"
            }
          ]
        }
      ]
    }
  },
  "Resources": {
    "DeployStateMachine": {
      "Type": "AWS::StepFunctions::StateMachine",
      "DependsOn": [
        "StatesInvokePolicy"
      ],
      "Properties": {
        "StateMachineName": {
          "Fn::Sub": "datomic-${AWS::StackName}"
        },
        "DefinitionString": {
          "Fn::Sub": [
            "{\"Comment\":\"Datomic Deploy State Machine\",\"StartAt\":\"Create CodeDeploy Deployment\",\"States\":{\"Get Deployment Status\":{\"Type\":\"Task\",\"Resource\":\"${GetDeploymentStatusArn}\",\"TimeoutSeconds\":60,\"Next\":\"Deployment Complete?\",\"Retry\":[{\"ErrorEquals\":[\"States.TaskFailed\"],\"IntervalSeconds\":1,\"MaxAttempts\":3,\"BackoffRate\":2}]},\"Delete Lambda\":{\"Type\":\"Task\",\"Resource\":\"${DeleteLambdaFromArrayArn}\",\"TimeoutSeconds\":60,\"Next\":\"Delete Lambdas?\",\"Retry\":[{\"ErrorEquals\":[\"States.TaskFailed\"],\"IntervalSeconds\":1,\"MaxAttempts\":3,\"BackoffRate\":2}]},\"Wait For Lambdas\":{\"Type\":\"Map\",\"ItemsPath\":\"$.lambda.c\",\"Iterator\":{\"StartAt\":\"Lambda Status\",\"States\":{\"Lambda Status\":{\"Type\":\"Task\",\"Resource\":\"${GetLambdaStatusArn}\",\"TimeoutSeconds\":60,\"Next\":\"Lambda Pending?\",\"ResultPath\":\"$.state\",\"Retry\":[{\"ErrorEquals\":[\"States.TaskFailed\"],\"IntervalSeconds\":1,\"MaxAttempts\":3,\"BackoffRate\":2}]},\"Lambda Pending?\":{\"Type\":\"Choice\",\"Choices\":[{\"Variable\":\"$.state\",\"StringEquals\":\"Pending\",\"Next\":\"Wait For Lambda\"}],\"Default\":\"Lambda Ready\"},\"Wait For Lambda\":{\"Type\":\"Wait\",\"Seconds\":15,\"Next\":\"Lambda Status\"},\"Lambda Ready\":{\"Type\":\"Succeed\",\"OutputPath\":\"$.state\"}}},\"Next\":\"Update Lambdas?\",\"ResultPath\":\"$.lambda.states\"},\"Update Lambda\":{\"Type\":\"Task\",\"Resource\":\"${UpdateLambdaFromArrayArn}\",\"TimeoutSeconds\":60,\"Next\":\"Update Lambdas?\",\"Retry\":[{\"ErrorEquals\":[\"States.TaskFailed\"],\"IntervalSeconds\":1,\"MaxAttempts\":3,\"BackoffRate\":2}]},\"Deployment Complete?\":{\"Type\":\"Choice\",\"Choices\":[{\"Variable\":\"$.codeDeploy.deployment.status\",\"StringEquals\":\"Failed\",\"Next\":\"Deployment Failed\"},{\"Variable\":\"$.codeDeploy.deployment.status\",\"StringEquals\":\"Stopped\",\"Next\":\"Deployment Failed\"},{\"Variable\":\"$.codeDeploy.deployment.status\",\"StringEquals\":\"Succeeded\",\"Next\":\"Create Lambdas?\"}],\"Default\":\"Wait For Deployment\"},\"Succeeded\":{\"Type\":\"Succeed\"},\"Create CodeDeploy Deployment\":{\"Type\":\"Task\",\"Resource\":\"${CreateCodeDeployDeploymentArn}\",\"TimeoutSeconds\":60,\"Next\":\"Wait For Deployment\",\"Retry\":[{\"ErrorEquals\":[\"States.TaskFailed\"],\"IntervalSeconds\":1,\"MaxAttempts\":3,\"BackoffRate\":2}]},\"Update Lambdas?\":{\"Type\":\"Choice\",\"Choices\":[{\"Variable\":\"$.lambda.uI\",\"NumericGreaterThanEquals\":0,\"Next\":\"Update Lambda\"}],\"Default\":\"Delete Lambdas?\"},\"Wait For Deployment\":{\"Type\":\"Wait\",\"Seconds\":15,\"Next\":\"Get Deployment Status\"},\"Delete Lambdas?\":{\"Type\":\"Choice\",\"Choices\":[{\"Variable\":\"$.lambda.dI\",\"NumericGreaterThanEquals\":0,\"Next\":\"Delete Lambda\"}],\"Default\":\"Succeeded\"},\"Create Lambdas?\":{\"Type\":\"Choice\",\"Choices\":[{\"Variable\":\"$.lambda.cI\",\"NumericGreaterThanEquals\":0,\"Next\":\"Create Lambda\"}],\"Default\":\"Wait For Lambdas\"},\"Deployment Failed\":{\"Type\":\"Fail\",\"Cause\":\"Code Deploy Deployment Failed\",\"Error\":\"$.codeDeploy.deployment.errorInformation\"},\"Create Lambda\":{\"Type\":\"Task\",\"Resource\":\"${CreateLambdaFromArrayArn}\",\"TimeoutSeconds\":60,\"Next\":\"Create Lambdas?\",\"Retry\":[{\"ErrorEquals\":[\"States.TaskFailed\"],\"IntervalSeconds\":1,\"MaxAttempts\":3,\"BackoffRate\":2}]}}}",
            {
              "CreateCodeDeployDeploymentArn": {
                "Fn::GetAtt": [
                  "CreateCodeDeployDeployment",
                  "Arn"
                ]
              },
              "CreateLambdaFromArrayArn": {
                "Fn::GetAtt": [
                  "CreateLambdaFromArray",
                  "Arn"
                ]
              },
              "DeleteLambdaFromArrayArn": {
                "Fn::GetAtt": [
                  "DeleteLambdaFromArray",
                  "Arn"
                ]
              },
              "GetDeploymentStatusArn": {
                "Fn::GetAtt": [
                  "GetDeploymentStatus",
                  "Arn"
                ]
              },
              "GetLambdaStatusArn": {
                "Fn::GetAtt": [
                  "GetLambdaStatus",
                  "Arn"
                ]
              },
              "UpdateLambdaFromArrayArn": {
                "Fn::GetAtt": [
                  "UpdateLambdaFromArray",
                  "Arn"
                ]
              }
            }
          ]
        },
        "RoleArn": {
          "Fn::GetAtt": [
            "StatesExecutionRole",
            "Arn"
          ]
        },
        "Tags": [
          {
            "Key": "datomic:system",
            "Value": {
              "Ref": "SystemName"
            }
          }
        ]
      }
    },
    "DeleteDatomicLambdaEnis": {
      "Type": "Custom::Resource",
      "DependsOn": [
        "DeleteEnisLogGroup"
      ],
      "Properties": {
        "ServiceToken": {
          "Fn::GetAtt": [
            "DeleteEnis",
            "Arn"
          ]
        },
        "SecurityGroups": [
          {
            "Ref": "LambdaSecurityGroup"
          }
        ]
      }
    },
    "HttpDirectApiIntegration": {
      "Type": "AWS::ApiGatewayV2::Integration",
      "Condition": "CreateHttpDirectApiGateway",
      "Properties": {
        "ApiId": {
          "Ref": "HttpDirectApiGateway"
        },
        "ConnectionId": {
          "Fn::ImportValue": {
            "Fn::Sub": "${SystemName}-VpcLinkId"
          }
        },
        "ConnectionType": "VPC_LINK",
        "Description": {
          "Fn::Sub": "${AWS::StackName} Datomic Ions"
        },
        "IntegrationMethod": "ANY",
        "IntegrationType": "HTTP_PROXY",
        "IntegrationUri": {
          "Ref": "HttpDirectListener"
        },
        "PayloadFormatVersion": "1.0"
      }
    },
    "DeployLambdaLogPolicy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "logs",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
              ],
              "Resource": [
                "arn:aws:logs:*:*:*"
              ],
              "Sid": "LamdaLogs"
            }
          ]
        },
        "Roles": [
          {
            "Ref": "DeployLambdaRole"
          }
        ]
      }
    },
    "LoadBalancerSecurityGroup": {
      "Type": "AWS::EC2::SecurityGroup",
      "Properties": {
        "GroupDescription": {
          "Fn::Sub": "${AWS::StackName} Datomic Load Balancer"
        },
        "SecurityGroupIngress": [
          {
            "CidrIp": {
              "Fn::ImportValue": {
                "Fn::Sub": "${SystemName}-Subnet0-CIDRBlock"
              }
            },
            "Description": "CIDRBlock for Subnet 0",
            "FromPort": 8182,
            "IpProtocol": "tcp",
            "ToPort": 8184
          },
          {
            "CidrIp": {
              "Fn::ImportValue": {
                "Fn::Sub": "${SystemName}-Subnet1-CIDRBlock"
              }
            },
            "Description": "CIDRBlock for Subnet 1",
            "FromPort": 8182,
            "IpProtocol": "tcp",
            "ToPort": 8184
          },
          {
            "CidrIp": {
              "Fn::ImportValue": {
                "Fn::Sub": "${SystemName}-Subnet2-CIDRBlock"
              }
            },
            "Description": "CIDRBlock for Subnet 2",
            "FromPort": 8182,
            "IpProtocol": "tcp",
            "ToPort": 8184
          }
        ],
        "Tags": [
          {
            "Key": "Name",
            "Value": {
              "Fn::Sub": "datomic-${AWS::StackName}-load-balancer"
            }
          },
          {
            "Key": "datomic:system",
            "Value": {
              "Ref": "SystemName"
            }
          }
        ],
        "VpcId": {
          "Fn::ImportValue": {
            "Fn::Sub": "${SystemName}-VpcId"
          }
        }
      }
    },
    "GetLambdaStatus": {
      "Type": "AWS::Lambda::Function",
      "DependsOn": [
        "DeployDeployFunctionsPolicy",
        "DeployLambdaLogPolicy"
      ],
      "Properties": {
        "Handler": "index.handler",
        "Role": {
          "Fn::GetAtt": [
            "DeployLambdaRole",
            "Arn"
          ]
        },
        "Code": {
          "ZipFile": {
            "Fn::Join": [
              "\n",
              [
                "const {",
                "  Lambda",
                "} = require(\"@aws-sdk/client-lambda\");",
                "var lambda = new Lambda();",
                "",
                "exports.handler = (event, context, callback) => {",
                "  console.log({event: event});",
                "  var params = {",
                "    FunctionName: event.FunctionName",
                "  };",
                "",
                "  lambda.getFunctionConfiguration(params, function(err, data) {",
                "    if (err) {",
                "      console.error(err.stack);",
                "      callback(err);",
                "    }",
                "    else  {",
                "      callback(null, data.State);",
                "    }",
                "  });",
                "};"
              ]
            ]
          }
        },
        "Runtime": {
          "Fn::FindInMap": [
            "Constants",
            "Cft",
            "NodejsRuntime"
          ]
        },
        "Tags": [
          {
            "Key": "datomic:system",
            "Value": {
              "Ref": "SystemName"
            }
          }
        ],
        "Timeout": 10
      }
    },
    "LambdaLogsPolicy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "logs",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents"
              ],
              "Resource": [
                "arn:aws:logs:*:*:*"
              ]
            }
          ]
        },
        "Roles": [
          {
            "Ref": "QueryGroupLambdaExecutionRole"
          }
        ]
      }
    },
    "DeployLambdaRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "Service": [
                  "lambda.amazonaws.com"
                ]
              },
              "Action": [
                "sts:AssumeRole"
              ],
              "Sid": "LambdaTrustPolicy"
            }
          ]
        },
        "Path": "/",
        "Tags": [
          {
            "Key": "datomic:system",
            "Value": {
              "Ref": "SystemName"
            }
          }
        ]
      }
    },
    "CreateCodeDeployDeployment": {
      "Type": "AWS::Lambda::Function",
      "DependsOn": [
        "DeployCreateDeploymentPolicy",
        "DeployLambdaLogPolicy"
      ],
      "Properties": {
        "Handler": "index.handler",
        "Role": {
          "Fn::GetAtt": [
            "DeployLambdaRole",
            "Arn"
          ]
        },
        "Code": {
          "ZipFile": {
            "Fn::Join": [
              "\n",
              [
                "const {",
                "  CodeDeploy",
                "} = require(\"@aws-sdk/client-codedeploy\");",
                "var cd = new CodeDeploy();",
                "",
                "// Creates a CodeDeploy Deployment with params in event.codeDeploy.deployment",
                "exports.handler = (event, context, callback) => {",
                "  console.log({event: event});",
                "",
                "  cd.createDeployment(event.codeDeploy.deployment, function(err, data) {",
                "    if (err) {",
                "      console.error(err.stack);",
                "      callback(err);",
                "    }",
                "",
                "    else {",
                "      event.codeDeploy.deployment.deploymentId = data.deploymentId;",
                "      callback(null, event);",
                "    }",
                "  });",
                "};"
              ]
            ]
          }
        },
        "Runtime": {
          "Fn::FindInMap": [
            "Constants",
            "Cft",
            "NodejsRuntime"
          ]
        },
        "Tags": [
          {
            "Key": "datomic:system",
            "Value": {
              "Ref": "SystemName"
            }
          }
        ],
        "Timeout": 10
      }
    },
    "QueryGroupLambdaExecutionRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "Service": [
                  "lambda.amazonaws.com"
                ]
              },
              "Action": [
                "sts:AssumeRole"
              ],
              "Sid": "LambdaTrustPolicy"
            }
          ]
        },
        "Path": "/",
        "Tags": [
          {
            "Key": "datomic:system",
            "Value": {
              "Ref": "SystemName"
            }
          }
        ]
      }
    },
    "CreateLambdaFromArray": {
      "Type": "AWS::Lambda::Function",
      "DependsOn": [
        "DeployDeployFunctionsPolicy",
        "DeployLambdaLogPolicy"
      ],
      "Properties": {
        "Handler": "index.handler",
        "Role": {
          "Fn::GetAtt": [
            "DeployLambdaRole",
            "Arn"
          ]
        },
        "Code": {
          "ZipFile": {
            "Fn::Join": [
              "\n",
              [
                "const {",
                "  Lambda",
                "} = require(\"@aws-sdk/client-lambda\");",
                "var l = new Lambda();",
                "",
                "function updateConcurrency(lambda) {",
                "  if (lambda.Concurrency) {",
                "    var concurrencyParams = {",
                "      FunctionName: lambda.FunctionName,",
                "      ReservedConcurrentExecutions: lambda.Concurrency};",
                "    return l.putFunctionConcurrency(concurrencyParams).then(function(concurrencyData) {",
                "      return null;",
                "    });",
                "  }",
                "",
                "  else return null;",
                "}",
                "",
                "// Expects a lambda map with FunctionName, Description, Variables, Timeout, and Concurrency",
                "// and a common map with S3Bucket, S3Key, Role, Runtime, DeadLetterConfig",
                "// MemorySize, Tags, and VpcConfig.",
                "// Creates a lambda with those params and returns a promise",
                "function createFunction (lambda, common) {",
                "  var createParams = {",
                "    //common params",
                "    Code: {",
                "      S3Bucket: common.S3Bucket,",
                "      S3Key: common.S3Key},",
                "    Handler: common.Handler,",
                "    Role: common.Role,",
                "    Runtime: common.Runtime,",
                "    DeadLetterConfig: common.DeadLetterConfig,",
                "    MemorySize: common.MemorySize,",
                "    Tags: common.Tags,",
                "    VpcConfig: common.VpcConfig,",
                "    // lambda-specific params",
                "    FunctionName: lambda.FunctionName,",
                "    Description: lambda.Description,",
                "    Environment: { Variables: lambda.Variables },",
                "    Timeout: lambda.Timeout};",
                "",
                "  return l.createFunction(createParams).then(function(createData) {",
                "    return updateConcurrency(lambda);",
                "  });",
                "}",
                "",
                "// Expects event.lambda.c, an array, each entry a lambda map",
                "// And event.lambda.common, a map of the common elements of each lambda",
                "// Will create the lambda pointed to by event.lambda.cI index",
                "// Decrements the index in return value",
                "exports.handler = (event, context, callback) => {",
                "  console.log({event: event});",
                "  var index = event.lambda.cI;",
                "  if (index >= 0) {",
                "    var lambda = event.lambda.c[index];",
                "",
                "    return createFunction(lambda, event.lambda.common).then(function() {",
                "      event.lambda.cI--;",
                "      callback(null, event);",
                "    })",
                "    .catch((err) => {",
                "      if(err.code === \"ResourceConflictException\"",
                "        && err.message.startsWith(\"Function already exist\")) {",
                "        // mark this create as done, and copy it to update",
                "        event.lambda.cI--;",
                "        event.lambda.uI++;",
                "        event.lambda.u.push(Object.assign({isTagUpd: true}, lambda));",
                "        callback(null, event);",
                "      } else {",
                "        console.error(err.stack);",
                "        callback(err);",
                "      }",
                "    });",
                "  }",
                "  else {",
                "    callback(null, event);",
                "  }",
                "};"
              ]
            ]
          }
        },
        "Runtime": {
          "Fn::FindInMap": [
            "Constants",
            "Cft",
            "NodejsRuntime"
          ]
        },
        "Tags": [
          {
            "Key": "datomic:system",
            "Value": {
              "Ref": "SystemName"
            }
          }
        ],
        "Timeout": 10
      }
    },
    "EndpointService": {
      "Type": "Custom::Resource",
      "DependsOn": [
        "CreateEndpointServiceLogGroup"
      ],
      "Properties": {
        "ServiceToken": {
          "Fn::GetAtt": [
            "CreateEndpointService",
            "Arn"
          ]
        },
        "NetworkLoadBalancerArns": [
          {
            "Ref": "LoadBalancer"
          }
        ],
        "Version": 2
      }
    },
    "QueryGroupS3AccessPolicy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "s3-access",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:Delete*",
                "s3:List*"
              ],
              "Resource": [
                {
                  "Fn::ImportValue": {
                    "Fn::Sub": "${SystemName}-S3DatomicArn"
                  }
                },
                {
                  "Fn::Sub": [
                    "${S3DatomicArn}/*",
                    {
                      "S3DatomicArn": {
                        "Fn::ImportValue": {
                          "Fn::Sub": "${SystemName}-S3DatomicArn"
                        }
                      }
                    }
                  ]
                }
              ]
            }
          ]
        },
        "Roles": [
          {
            "Ref": "QueryGroupRole"
          }
        ]
      }
    },
    "DatomicLambdaRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "Service": [
                  "lambda.amazonaws.com"
                ]
              },
              "Action": [
                "sts:AssumeRole"
              ]
            }
          ]
        },
        "ManagedPolicyArns": [
          "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
        ],
        "Tags": [
          {
            "Key": "datomic:system",
            "Value": {
              "Ref": "SystemName"
            }
          }
        ]
      }
    },
    "StatesExecutionRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "Service": [
                  {
                    "Fn::Sub": "states.${AWS::Region}.amazonaws.com"
                  }
                ]
              },
              "Action": [
                "sts:AssumeRole"
              ],
              "Sid": "StatesTrustPolicy"
            }
          ]
        },
        "Path": "/",
        "Tags": [
          {
            "Key": "datomic:system",
            "Value": {
              "Ref": "SystemName"
            }
          }
        ]
      }
    },
    "ComputeSecurityGroup": {
      "Type": "Custom::ResourceQuery",
      "DependsOn": [
        "GetTaggedResourcesLogGroup"
      ],
      "Properties": {
        "Min": 1,
        "Max": 1,
        "ServiceToken": {
          "Fn::GetAtt": [
            "GetTaggedResources",
            "Arn"
          ]
        },
        "TypeFilters": [
          "ec2:security-group"
        ],
        "TagFilterKey": "Name",
        "TagFilterValues": [
          {
            "Fn::Sub": "${SystemName}-nodes"
          }
        ]
      }
    },
    "DeleteEntryRecordSet": {
      "Type": "Custom::ResourceQuery",
      "DependsOn": [
        "DeleteRecordSetsLogGroup"
      ],
      "Properties": {
        "ServiceToken": {
          "Fn::GetAtt": [
            "DeleteRecordSets",
            "Arn"
          ]
        },
        "HostedZoneId": {
          "Ref": "HostedZone"
        },
        "DeleteName": {
          "Fn::Sub": "entry.${AWS::StackName}.${AWS::Region}.datomic.net."
        }
      }
    },
    "ClientDefaultStage": {
      "Type": "AWS::ApiGatewayV2::Stage",
      "Condition": "CreateClientApiGateway",
      "Properties": {
        "ApiId": {
          "Ref": "ClientApiGateway"
        },
        "AutoDeploy": true,
        "Description": {
          "Fn::Sub": "${AWS::StackName} Datomic Client API"
        },
        "StageName": "$default",
        "Tags": {
          "datomic:system": {
            "Ref": "SystemName"
          }
        }
      }
    },
    "HttpDirectApiGateway": {
      "Type": "AWS::ApiGatewayV2::Api",
      "Condition": "CreateHttpDirectApiGateway",
      "Properties": {
        "Description": {
          "Fn::Sub": "${AWS::StackName} Datomic Ions"
        },
        "Name": {
          "Fn::Sub": "datomic-${AWS::StackName}-ions"
        },
        "ProtocolType": "HTTP",
        "Tags": {
          "datomic:system": {
            "Ref": "SystemName"
          }
        }
      }
    },
    "CodeDeployServiceRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "Service": [
                  "codedeploy.amazonaws.com"
                ]
              },
              "Action": [
                "sts:AssumeRole"
              ],
              "Sid": "CodeDeployTrustPolicy"
            }
          ]
        },
        "ManagedPolicyArns": [
          "arn:aws:iam::aws:policy/service-role/AWSCodeDeployRole"
        ],
        "Path": "/",
        "Tags": [
          {
            "Key": "datomic:system",
            "Value": {
              "Ref": "SystemName"
            }
          }
        ]
      }
    },
    "LambdaChangeRoute53Policy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "change-route53",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "route53:ChangeResourceRecordSets"
              ],
              "Resource": [
                {
                  "Fn::Sub": "arn:aws:route53:::hostedzone/${HostedZone}"
                }
              ],
              "Sid": "Route53Change"
            }
          ]
        },
        "Roles": [
          {
            "Ref": "QueryGroupLambdaExecutionRole"
          }
        ]
      }
    },
    "ClientApiGateway": {
      "Type": "AWS::ApiGatewayV2::Api",
      "Condition": "CreateClientApiGateway",
      "Properties": {
        "Description": {
          "Fn::Sub": "${AWS::StackName} Datomic Client API"
        },
        "Name": {
          "Fn::Sub": "datomic-${AWS::StackName}-client-api"
        },
        "ProtocolType": "HTTP",
        "Tags": {
          "datomic:system": {
            "Ref": "SystemName"
          }
        }
      }
    },
    "Dashboard": {
      "Type": "AWS::CloudWatch::Dashboard",
      "Condition": "CreateDashboard",
      "Properties": {
        "DashboardName": {
          "Fn::Sub": "datomic-${AWS::StackName}-${AWS::Region}"
        },
        "DashboardBody": {
          "Fn::Join": [
            "\n",
            [
              "{\"widgets\":[",
              {
                "Fn::Sub": "{\"type\":\"metric\",\"width\":8,\"properties\":{\"title\":\"OpsPending, OpsThrottled, OpsTimeout\",\"metrics\":[[\"DatomicCloud\",\"HttpEndpointOpsPending\",\"system\",\"${SystemName}\",\"stack\",\"${AWS::StackName}\",{\"label\":\"OpsPending\",\"stat\":\"Average\",\"yAxis\":\"left\"}],[\"DatomicCloud\",\"HttpEndpointThrottled\",\"system\",\"${SystemName}\",\"stack\",\"${AWS::StackName}\",{\"label\":\"OpsThrottled\",\"stat\":\"Sum\",\"yAxis\":\"right\"}],[\"DatomicCloud\",\"HttpEndpointAsyncTimeout\",\"system\",\"${SystemName}\",\"stack\",\"${AWS::StackName}\",{\"label\":\"OpsTimeout\",\"stat\":\"Sum\",\"yAxis\":\"right\"}]],\"period\":60,\"region\":\"${AWS::Region}\",\"view\":\"timeSeries\",\"yAxis\":{\"left\":{\"min\":0},\"right\":{\"min\":0}}}}"
              },
              ",",
              {
                "Fn::Sub": "{\"type\":\"metric\",\"width\":8,\"properties\":{\"title\":\"Query Result Counts\",\"metrics\":[[\"DatomicCloud\",\"QueryTuples\",\"system\",\"${SystemName}\",\"stack\",\"${AWS::StackName}\",{\"label\":\"Mean\",\"stat\":\"Average\",\"yAxis\":\"left\"}],[\"DatomicCloud\",\"QueryTuples\",\"system\",\"${SystemName}\",\"stack\",\"${AWS::StackName}\",{\"label\":\"Max\",\"stat\":\"Maximum\",\"yAxis\":\"right\"}]],\"period\":60,\"region\":\"${AWS::Region}\",\"view\":\"timeSeries\",\"yAxis\":{\"left\":{\"min\":0},\"right\":{\"min\":0}}}}"
              },
              ",",
              {
                "Fn::Sub": "{\"type\":\"metric\",\"width\":8,\"properties\":{\"metrics\":[[\"DatomicCloud\",\"JvmFreeMb\",\"system\",\"${SystemName}\",\"stack\",\"${AWS::StackName}\",{\"stat\":\"Minimum\",\"yAxis\":\"left\"}]],\"period\":60,\"region\":\"${AWS::Region}\",\"view\":\"timeSeries\",\"yAxis\":{\"left\":{\"min\":0},\"right\":{\"min\":0}}}}"
              },
              ",",
              {
                "Fn::Sub": "{\"type\":\"metric\",\"width\":8,\"properties\":{\"metrics\":[[\"AWS\\/EC2\",\"CPUUtilization\",\"AutoScalingGroupName\",\"${QueryGroupAutoScalingGroup}\",{\"label\":\"min\",\"stat\":\"Minimum\",\"yAxis\":\"left\"}],[\"AWS\\/EC2\",\"CPUUtilization\",\"AutoScalingGroupName\",\"${QueryGroupAutoScalingGroup}\",{\"label\":\"max\",\"stat\":\"Maximum\",\"yAxis\":\"right\"}]],\"period\":60,\"region\":\"${AWS::Region}\",\"view\":\"timeSeries\",\"yAxis\":{\"left\":{\"min\":0},\"right\":{\"min\":0}}}}"
              },
              ",",
              {
                "Fn::Sub": "{\"type\":\"metric\",\"width\":8,\"properties\":{\"metrics\":[[\"DatomicCloud\",\"Events\",\"system\",\"${SystemName}\",\"stack\",\"${AWS::StackName}\",{\"yAxis\":\"left\"}],[\"DatomicCloud\",\"Alerts\",\"system\",\"${SystemName}\",\"stack\",\"${AWS::StackName}\",{\"yAxis\":\"right\"}]],\"period\":60,\"region\":\"${AWS::Region}\",\"stat\":\"Sum\",\"view\":\"timeSeries\",\"yAxis\":{\"left\":{\"min\":0},\"right\":{\"min\":0}}}}"
              },
              ",",
              {
                "Fn::Sub": "{\"type\":\"metric\",\"width\":8,\"properties\":{\"title\":\"HitRatio\",\"metrics\":[[\"DatomicCloud\",\"ObjectCacheHits\",\"system\",\"${SystemName}\",\"stack\",\"${AWS::StackName}\",{\"label\":\"HitRatio\",\"yAxis\":\"left\"}]],\"period\":60,\"region\":\"${AWS::Region}\",\"stat\":\"Average\",\"view\":\"timeSeries\",\"yAxis\":{\"left\":{\"min\":0},\"right\":{\"min\":0}}}}"
              },
              ",",
              {
                "Fn::Sub": "{\"type\":\"metric\",\"width\":12,\"properties\":{\"title\":\"Cache Hit Counts\",\"metrics\":[[\"DatomicCloud\",\"EfsHits\",\"system\",\"${SystemName}\",\"stack\",\"${AWS::StackName}\",{\"label\":\"EFS\",\"yAxis\":\"left\"}]],\"period\":60,\"region\":\"${AWS::Region}\",\"stat\":\"SampleCount\",\"view\":\"timeSeries\",\"yAxis\":{\"left\":{\"min\":0}}}}"
              },
              ",",
              {
                "Fn::Sub": "{\"type\":\"metric\",\"width\":12,\"properties\":{\"title\":\"Cache Hit Ratios\",\"metrics\":[[\"DatomicCloud\",\"EfsHits\",\"system\",\"${SystemName}\",\"stack\",\"${AWS::StackName}\",{\"label\":\"EFS\",\"yAxis\":\"left\"}]],\"period\":60,\"region\":\"${AWS::Region}\",\"stat\":\"Average\",\"view\":\"timeSeries\",\"yAxis\":{\"left\":{\"min\":0}}}}"
              },
              "]}"
            ]
          ]
        }
      }
    },
    "CodeDeployDeploymentGroup": {
      "Type": "AWS::CodeDeploy::DeploymentGroup",
      "Properties": {
        "ApplicationName": {
          "Fn::If": [
            "ApplicationNameProvided",
            {
              "Ref": "ApplicationName"
            },
            {
              "Ref": "AWS::StackName"
            }
          ]
        },
        "AutoRollbackConfiguration": {
          "Enabled": true,
          "Events": [
            "DEPLOYMENT_FAILURE",
            "DEPLOYMENT_STOP_ON_ALARM",
            "DEPLOYMENT_STOP_ON_REQUEST"
          ]
        },
        "AutoScalingGroups": [
          {
            "Ref": "QueryGroupAutoScalingGroup"
          }
        ],
        "DeploymentGroupName": {
          "Ref": "AWS::StackName"
        },
        "DeploymentStyle": {
          "DeploymentOption": "WITHOUT_TRAFFIC_CONTROL",
          "DeploymentType": "IN_PLACE"
        },
        "LoadBalancerInfo": {
          "TargetGroupInfoList": [
            {
              "Name": {
                "Fn::GetAtt": [
                  "TargetGroup",
                  "TargetGroupName"
                ]
              }
            }
          ]
        },
        "ServiceRoleArn": {
          "Fn::GetAtt": [
            "CodeDeployServiceRole",
            "Arn"
          ]
        }
      }
    },
    "DeployReadS3Policy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "read-s3",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "s3:GetObject"
              ],
              "Resource": [
                {
                  "Fn::Sub": [
                    "${CodeBucketArn}/*",
                    {
                      "CodeBucketArn": {
                        "Fn::ImportValue": {
                          "Fn::Sub": "${SystemName}-CodeBucketArn"
                        }
                      }
                    }
                  ]
                }
              ],
              "Sid": "ReadS3"
            }
          ]
        },
        "Roles": [
          {
            "Ref": "DeployLambdaRole"
          }
        ]
      }
    },
    "HostedZone": {
      "Type": "AWS::Route53::HostedZone",
      "Properties": {
        "HostedZoneConfig": {
          "Comment": "Datomic Hosted Zone Config"
        },
        "Name": {
          "Fn::Sub": "${AWS::StackName}.${AWS::Region}.datomic.net."
        },
        "VPCs": [
          {
            "VPCId": {
              "Fn::ImportValue": {
                "Fn::Sub": "${SystemName}-VpcId"
              }
            },
            "VPCRegion": {
              "Ref": "AWS::Region"
            }
          }
        ],
        "HostedZoneTags": [
          {
            "Key": "datomic:system",
            "Value": {
              "Ref": "SystemName"
            }
          }
        ]
      }
    },
    "HttpDirectDefaultStage": {
      "Type": "AWS::ApiGatewayV2::Stage",
      "Condition": "CreateHttpDirectApiGateway",
      "Properties": {
        "ApiId": {
          "Ref": "HttpDirectApiGateway"
        },
        "AutoDeploy": true,
        "Description": {
          "Fn::Sub": "${AWS::StackName} Datomic Ions"
        },
        "StageName": "$default",
        "Tags": {
          "datomic:system": {
            "Ref": "SystemName"
          }
        }
      }
    },
    "HttpDirectTargetGroup": {
      "Type": "AWS::ElasticLoadBalancingV2::TargetGroup",
      "Properties": {
        "HealthyThresholdCount": 2,
        "HealthCheckTimeoutSeconds": 5,
        "Tags": [
          {
            "Key": "datomic:system",
            "Value": {
              "Ref": "SystemName"
            }
          }
        ],
        "UnhealthyThresholdCount": 2,
        "Protocol": "HTTP",
        "Port": 8184,
        "VpcId": {
          "Fn::ImportValue": {
            "Fn::Sub": "${SystemName}-VpcId"
          }
        },
        "HealthCheckIntervalSeconds": 10,
        "HealthCheckPort": "8182",
        "HealthCheckProtocol": "HTTP",
        "TargetGroupAttributes": [
          {
            "Key": "deregistration_delay.timeout_seconds",
            "Value": "30"
          },
          {
            "Key": "load_balancing.algorithm.type",
            "Value": "least_outstanding_requests"
          }
        ],
        "HealthCheckPath": "/"
      }
    },
    "HttpDirectDefaultRoute": {
      "Type": "AWS::ApiGatewayV2::Route",
      "Condition": "CreateHttpDirectApiGateway",
      "Properties": {
        "ApiId": {
          "Ref": "HttpDirectApiGateway"
        },
        "RouteKey": "$default",
        "Target": {
          "Fn::Sub": "integrations/${HttpDirectApiIntegration}"
        }
      }
    },
    "QueryGroupLaunchTemplate": {
      "Type": "AWS::EC2::LaunchTemplate",
      "Properties": {
        "LaunchTemplateData": {
          "IamInstanceProfile": {
            "Arn": {
              "Fn::GetAtt": [
                "QueryGroupInstanceProfile",
                "Arn"
              ]
            }
          },
          "ImageId": {
            "Fn::FindInMap": [
              "RegionMap",
              {
                "Ref": "AWS::Region"
              },
              "Datomic"
            ]
          },
          "InstanceType": {
            "Ref": "InstanceType"
          },
          "KeyName": {
            "Ref": "KeyName"
          },
          "MetadataOptions": {
            "HttpTokens": "optional"
          },
          "Monitoring": {
            "Enabled": {
              "Fn::If": [
                "DetailedMetrics",
                true,
                false
              ]
            }
          },
          "NetworkInterfaces": [
            {
              "AssociatePublicIpAddress": true,
              "DeviceIndex": 0,
              "Groups": [
                {
                  "Fn::Select": [
                    0,
                    {
                      "Fn::GetAtt": [
                        "ComputeSecurityGroup",
                        "ResourceList"
                      ]
                    }
                  ]
                }
              ]
            }
          ],
          "UserData": {
            "Fn::Base64": {
              "Fn::Join": [
                "\n",
                [
                  {
                    "Fn::Sub": [
                      "export DDB_LOG_TABLE=\"${LogTable}\"",
                      {
                        "LogTable": {
                          "Fn::ImportValue": {
                            "Fn::Sub": "${SystemName}-LogTable"
                          }
                        }
                      }
                    ]
                  },
                  {
                    "Fn::Sub": [
                      "export DDB_CATALOG_TABLE=\"${CatalogTable}\"",
                      {
                        "CatalogTable": {
                          "Fn::ImportValue": {
                            "Fn::Sub": "${SystemName}-CatalogTable"
                          }
                        }
                      }
                    ]
                  },
                  {
                    "Fn::Sub": [
                      "export CW_LOG_GROUP=\"${LogGroup}\"",
                      {
                        "LogGroup": {
                          "Fn::ImportValue": {
                            "Fn::Sub": "${SystemName}-LogGroup"
                          }
                        }
                      }
                    ]
                  },
                  {
                    "Fn::Sub": [
                      "export S3_AUTH_PATH=\"${S3Datomic}\"",
                      {
                        "S3Datomic": {
                          "Fn::ImportValue": {
                            "Fn::Sub": "${SystemName}-S3Datomic"
                          }
                        }
                      }
                    ]
                  },
                  {
                    "Fn::Sub": [
                      "export S3_VALS_PATH=\"${S3Datomic}/${SystemName}/datomic/vals\"",
                      {
                        "S3Datomic": {
                          "Fn::ImportValue": {
                            "Fn::Sub": "${SystemName}-S3Datomic"
                          }
                        }
                      }
                    ]
                  },
                  {
                    "Fn::Sub": [
                      "export S3_CERTS_PATH=\"${S3Datomic}/${SystemName}/datomic/access/certs\"",
                      {
                        "S3Datomic": {
                          "Fn::ImportValue": {
                            "Fn::Sub": "${SystemName}-S3Datomic"
                          }
                        }
                      }
                    ]
                  },
                  {
                    "Fn::Sub": [
                      "export EFS_DNS=\"${FileSystemId}.efs.${AWS::Region}.amazonaws.com\"",
                      {
                        "FileSystemId": {
                          "Fn::ImportValue": {
                            "Fn::Sub": "${SystemName}-FileSystemId"
                          }
                        }
                      }
                    ]
                  },
                  {
                    "Fn::Sub": [
                      "export EFS_VALS_PATH=\"${EfsValsPath}\"",
                      {
                        "EfsValsPath": {
                          "Fn::FindInMap": [
                            "Datomic",
                            "defaults",
                            "EfsValsPath"
                          ]
                        }
                      }
                    ]
                  },
                  {
                    "Fn::Sub": [
                      "export KMS_CMK=\"${KmsCmkPath}\"",
                      {
                        "KmsCmkPath": {
                          "Fn::FindInMap": [
                            "Datomic",
                            "defaults",
                            "KmsCmk"
                          ]
                        }
                      }
                    ]
                  },
                  {
                    "Fn::Sub": [
                      "export DISABLE_SSL=\"${DisableSsl}\"",
                      {
                        "DisableSsl": {
                          "Fn::FindInMap": [
                            "Datomic",
                            "defaults",
                            "DisableSsl"
                          ]
                        }
                      }
                    ]
                  },
                  {
                    "Fn::Sub": [
                      "export PCT_JVM_MEM_FOR_HEAP=\"${PctJvmMemForHeap}\"",
                      {
                        "PctJvmMemForHeap": {
                          "Fn::FindInMap": [
                            "Datomic",
                            "defaults",
                            "PctJvmMemForHeap"
                          ]
                        }
                      }
                    ]
                  },
                  {
                    "Fn::Sub": [
                      "export PCT_MEM_FOR_JVM=\"${PctMemForJvm}\"",
                      {
                        "PctMemForJvm": {
                          "Fn::FindInMap": [
                            "Datomic",
                            "defaults",
                            "PctMemForJvm"
                          ]
                        }
                      }
                    ]
                  },
                  {
                    "Fn::Sub": [
                      "export OS_RESERVE_MB=\"${OsReserveMb}\"",
                      {
                        "OsReserveMb": {
                          "Fn::FindInMap": [
                            "Datomic",
                            "defaults",
                            "OsReserveMb"
                          ]
                        }
                      }
                    ]
                  },
                  {
                    "Fn::Sub": [
                      "export JVM_FLAGS=\"${JvmFlags}\"",
                      {
                        "JvmFlags": {
                          "Fn::Join": [
                            " ",
                            [
                              {
                                "Fn::FindInMap": [
                                  "Datomic",
                                  "defaults",
                                  "JvmFlags"
                                ]
                              },
                              {
                                "Fn::FindInMap": [
                                  "InstanceSettings",
                                  {
                                    "Ref": "InstanceType"
                                  },
                                  "JvmFlags"
                                ]
                              }
                            ]
                          ]
                        }
                      }
                    ]
                  },
                  {
                    "Fn::Sub": "export DATOMIC_CODE_DEPLOY_APPLICATION='${CodeDeployApplication.ApplicationName}'"
                  },
                  {
                    "Fn::Sub": "export DATOMIC_DEPLOYMENT_GROUP='${AWS::StackName}'"
                  },
                  {
                    "Fn::Sub": "export DATOMIC_ENV_MAP='${EnvironmentMap}'"
                  },
                  {
                    "Fn::Sub": "export DATOMIC_TX_GROUP=\"${SystemName}\""
                  },
                  {
                    "Fn::Sub": "export DATOMIC_INDEX_GROUP=\"${SystemName}\""
                  },
                  {
                    "Fn::Sub": "export DATOMIC_CACHE_GROUP=\"${SystemName}\""
                  },
                  {
                    "Fn::Sub": [
                      "export DATOMIC_METRICS_LEVEL=\"${DatomicMetricsLevel}\"",
                      {
                        "DatomicMetricsLevel": {
                          "Fn::If": [
                            "DetailedMetrics",
                            "Detailed",
                            {
                              "Fn::If": [
                                "BasicMetrics",
                                "Basic",
                                "None"
                              ]
                            }
                          ]
                        }
                      }
                    ]
                  },
                  {
                    "Fn::Sub": "export DATOMIC_QUERY_GROUP=\"${AWS::StackName}\""
                  },
                  {
                    "Fn::Sub": "export DATOMIC_PRELOAD_DB=\"${PreloadDb}\""
                  },
                  {
                    "Fn::Sub": "export DATOMIC_CLIENT_API_CONCURRENT_PROCESSING=\"${ClientApiConcurrentProcessing}\""
                  },
                  {
                    "Fn::Sub": [
                      "export DATOMIC_PRODUCTION_COMPUTE=\"${DatomicProductionCompute}\"",
                      {
                        "DatomicProductionCompute": {
                          "Fn::ImportValue": {
                            "Fn::Sub": "Datomic-${SystemName}-Production-Compute"
                          }
                        }
                      }
                    ]
                  },
                  {
                    "Fn::Sub": "export DATOMIC_HOSTED_ZONE_ID=\"${HostedZone}\""
                  },
                  "export DATOMIC_HEALTH_CHECK_PORT=\"8182\"",
                  {
                    "Fn::Sub": "export DATOMIC_TARGET_GROUP_ARN=\"${TargetGroup}\""
                  },
                  "export DATOMIC_CLUSTER_NODE_PORT=\"8182\"",
                  {
                    "Fn::Sub": "export DATOMIC_HTTP_DIRECT_TARGET_GROUP_ARN=\"${HttpDirectTargetGroup}\""
                  },
                  "export DATOMIC_HTTP_DIRECT_PORT=\"8184\"",
                  {
                    "Fn::Sub": "export DATOMIC_DISPATCH_ADDRESS=\"entry.${AWS::StackName}.${AWS::Region}.datomic.net\""
                  },
                  "export DATOMIC_SHUTDOWN_PORT=\"8190\"",
                  {
                    "Fn::If": [
                      "UseSsd",
                      "export DATOMIC_USE_SSD=\"true\"",
                      ""
                    ]
                  },
                  "export DATOMIC_CLUSTER_NODE=\"true\"",
                  {
                    "Fn::Sub": "${OverrideSettings}"
                  }
                ]
              ]
            }
          }
        }
      }
    },
    "LambdaDeleteLambdaPolicy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "delete-lambda",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "lambda:DeleteFunction"
              ],
              "Resource": [
                {
                  "Fn::Sub": "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:*"
                }
              ],
              "Sid": "LambdaDelete"
            }
          ]
        },
        "Roles": [
          {
            "Ref": "QueryGroupLambdaExecutionRole"
          }
        ]
      }
    },
    "DeployDeployFunctionsPolicy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "deploy-functions",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "lambda:CreateFunction",
                "lambda:DeleteFunction",
                "lambda:DeleteFunctionConcurrency",
                "lambda:GetFunction",
                "lambda:GetFunctionConfiguration",
                "lambda:PutFunctionConcurrency",
                "lambda:UpdateFunctionConfiguration",
                "lambda:UpdateFunctionCode"
              ],
              "Resource": [
                {
                  "Fn::Sub": "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${AWS::StackName}-*"
                }
              ],
              "Sid": "DeployFunctions"
            }
          ]
        },
        "Roles": [
          {
            "Ref": "DeployLambdaRole"
          }
        ]
      }
    },
    "UpdateLambdaFromArray": {
      "Type": "AWS::Lambda::Function",
      "DependsOn": [
        "DeployDeployFunctionsPolicy",
        "DeployLambdaLogPolicy"
      ],
      "Properties": {
        "Handler": "index.handler",
        "Role": {
          "Fn::GetAtt": [
            "DeployLambdaRole",
            "Arn"
          ]
        },
        "Code": {
          "ZipFile": {
            "Fn::Join": [
              "\n",
              [
                "const {",
                "  Lambda",
                "} = require(\"@aws-sdk/client-lambda\");",
                "var l = new Lambda();",
                "",
                "// Updates lambda Concurrency if lambda.Concurrency",
                "// non-positive concurrency means delete concurrency",
                "// Returns promise",
                "function updateConcurrency(lambda) {",
                "  if (lambda.Concurrency === undefined) return Promise.resolve();",
                "  else {",
                "    if (lambda.Concurrency > 0) {",
                "      var concurrencyParams = {",
                "        FunctionName: lambda.FunctionName,",
                "        ReservedConcurrentExecutions: lambda.Concurrency",
                "      };",
                "      return l.putFunctionConcurrency(concurrencyParams);",
                "    }",
                "    else {",
                "      return l.deleteFunctionConcurrency({ FunctionName: lambda.FunctionName });",
                "    }",
                "  }",
                "}",
                "",
                "// Updates lambda tags if lambda.isTagUpd",
                "// Returns promise",
                "function updateTags (lambda, common) {",
                "  if (lambda.isTagUpd) {",
                "",
                "    return l.getFunction({FunctionName: lambda.FunctionName})",
                "    .then(function(functionData) {",
                "      var tagParams = {",
                "        Resource: functionData.Configuration.FunctionArn,",
                "        Tags: common.Tags",
                "      };",
                "      return l.tagResource(tagParams);",
                "    });",
                "  }",
                "  else return Promise.resolve();",
                "}",
                "",
                "// Updates lambda code if lambda.isCodeUpd",
                "// Returns promise",
                "function updateCode (lambda, common) {",
                "  if (lambda.isCodeUpd) {",
                "    var codeParams = {",
                "      FunctionName: lambda.FunctionName,",
                "      S3Bucket: common.S3Bucket,",
                "      S3Key: common.S3Key",
                "    };",
                "    return l.updateFunctionCode(codeParams);",
                "  }",
                "  else return Promise.resolve();",
                "}",
                "",
                "// Updates lambda config if lambda.isConfigUpd",
                "// Returns promise",
                "function updateConfig (lambda, common) {",
                "  if (lambda.isConfigUpd) {",
                "    var updateParams = {",
                "      // common params",
                "      Handler: common.Handler,",
                "      Role: common.Role,",
                "      Runtime: common.Runtime,",
                "      DeadLetterConfig: common.DeadLetterConfig,",
                "      MemorySize: common.MemorySize,",
                "      VpcConfig: common.VpcConfig,",
                "      // lambda-specific params",
                "      FunctionName: lambda.FunctionName,",
                "      Description: lambda.Description,",
                "      Environment: { Variables: lambda.Variables },",
                "      Timeout: lambda.Timeout};",
                "      return l.updateFunctionConfiguration(updateParams);",
                "  }",
                "  else return Promise.resolve();",
                "}",
                "",
                "// Expects a map with a u array, each entry a lambda map (see create-one docs)",
                "// And flags of which parts of lambda need to be updated",
                "// (see updateConfig, updateCode, updateTags, and updateConcurrency for those flags)",
                "// Will update the lambda pointed to by event.lambda.uI index",
                "// Decrements the index in return value",
                "exports.handler = (event, context, callback) => {",
                "  console.log({event: event});",
                "  var index = event.lambda.uI;",
                "  if (index >= 0) {",
                "    var lambda = event.lambda.u[index];",
                "    var common = event.lambda.common;",
                "",
                "    return updateConfig(lambda, common)",
                "    .then(function() { return updateCode(lambda, common) })",
                "    .then(function() { return updateTags(lambda, common) })",
                "    .then(function() { return updateConcurrency(lambda) })",
                "    .then(function() {",
                "      event.lambda.uI = index - 1;",
                "      callback(null, event);",
                "    }).catch(function(err) {",
                "      console.error(err.stack);",
                "      callback(err);",
                "    });",
                "  }",
                "  else {",
                "    callback(null, event);",
                "  }",
                "};"
              ]
            ]
          }
        },
        "Runtime": {
          "Fn::FindInMap": [
            "Constants",
            "Cft",
            "NodejsRuntime"
          ]
        },
        "Tags": [
          {
            "Key": "datomic:system",
            "Value": {
              "Ref": "SystemName"
            }
          }
        ],
        "Timeout": 10
      }
    },
    "DeployReadCodedeployPolicy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "read-codedeploy",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "codedeploy:GetApplicationRevision",
                "codedeploy:GetDeploymentConfig",
                "codedeploy:GetDeployment"
              ],
              "Resource": [
                "*"
              ],
              "Sid": "ReadCodeDeploy"
            }
          ]
        },
        "Roles": [
          {
            "Ref": "DeployLambdaRole"
          }
        ]
      }
    },
    "ClientDefaultRoute": {
      "Type": "AWS::ApiGatewayV2::Route",
      "Condition": "CreateClientApiGateway",
      "Properties": {
        "ApiId": {
          "Ref": "ClientApiGateway"
        },
        "RouteKey": "$default",
        "Target": {
          "Fn::Sub": "integrations/${ClientApiIntegration}"
        }
      }
    },
    "DeleteLambdas": {
      "Type": "AWS::Lambda::Function",
      "DependsOn": [
        "LambdaSecurityGroup",
        "LambdaGetTagsPolicy",
        "LambdaDeleteLambdaPolicy",
        "LambdaLogsPolicy"
      ],
      "Properties": {
        "Handler": "index.handler",
        "Role": {
          "Fn::GetAtt": [
            "QueryGroupLambdaExecutionRole",
            "Arn"
          ]
        },
        "Code": {
          "ZipFile": {
            "Fn::Join": [
              "\n",
              [
                "// minified cfn-response",
                "var response={SUCCESS:\"SUCCESS\",FAILED:\"FAILED\",send:function(event,context,responseStatus,responseData,physicalResourceId,noEcho){var responseBody=JSON.stringify({Status:responseStatus,Reason:\"See the details in CloudWatch Log Stream: \"+context.logStreamName,PhysicalResourceId:physicalResourceId||context.logStreamName,StackId:event.StackId,RequestId:event.RequestId,LogicalResourceId:event.LogicalResourceId,NoEcho:noEcho||false,Data:responseData});console.log(\"Response body:\\n\",responseBody);var https=require(\"https\");var url=require(\"url\");var parsedUrl=url.parse(event.ResponseURL);var options={hostname:parsedUrl.hostname,port:443,path:parsedUrl.path,method:\"PUT\",headers:{\"content-type\":\"\",\"content-length\":responseBody.length}};var request=https.request(options,function(response){console.log(\"Status code: \"+response.statusCode);console.log(\"Status message: \"+response.statusMessage);context.done()});request.on(\"error\",function(error){console.log(\"send(..) failed executing https.request(..): \"+error);context.done()});request.write(responseBody);request.end()}};",
                "",
                "const {",
                "  Lambda",
                "} = require(\"@aws-sdk/client-lambda\");",
                "",
                "const {",
                "  ResourceGroupsTaggingAPI",
                "} = require(\"@aws-sdk/client-resource-groups-tagging-api\");",
                "",
                "var resourceGroupsTaggingApi = new ResourceGroupsTaggingAPI();",
                "var lambda = new Lambda();",
                "",
                "",
                "function deleteFunction (tagKey, tagValues, paginationToken) {",
                "  var params = {",
                "    ResourceTypeFilters: ['lambda'],",
                "    TagFilters: [{Key: tagKey,",
                "                  Values: tagValues}]",
                "  };",
                "",
                "  if (paginationToken) {",
                "    params.PaginationToken = paginationToken;",
                "  }",
                "",
                "  return resourceGroupsTaggingApi.getResources(params).then(function(tagData) {",
                "    console.log({tagData: tagData});",
                "    var deletePromise=Promise.all(tagData.ResourceTagMappingList.map(function(l) {",
                "      console.log({\"lambdas\": l});",
                "      return lambda.deleteFunction({FunctionName: l.ResourceARN});",
                "    }));",
                "",
                "    if (tagData.PaginationToken) {",
                "      return deletePromise.then(function(data) {",
                "        return deleteFunction(tagKey, tagValues, tagData.PaginationToken);",
                "      });",
                "    }",
                "    else {",
                "      return deletePromise;",
                "    }",
                "  });",
                "}",
                "",
                "// Deletes Lambda tagged w/TagKey and TagValues when event.RequestType is Delete",
                "// expects event.ResourceProperties.TagKey and event.ResourceProperties.TagValues",
                "exports.handler = (event, context, callback) => {",
                "  if (event.RequestType !== 'Delete') {",
                "    return response.send(event, context, response.SUCCESS);",
                "  }",
                "",
                "  console.log({event: event});",
                "",
                "  deleteFunction(event.ResourceProperties.TagKey, event.ResourceProperties.TagValues).then(function(data) {",
                "    console.log(\"Success\");",
                "    return response.send(event, context, response.SUCCESS, {\"data\": data});",
                "  }).catch(function(err) {",
                "    console.error(err.stack);",
                "    return response.send(event, context, response.FAILED, {'error': err});",
                "  });",
                "};"
              ]
            ]
          }
        },
        "Tags": [
          {
            "Key": "datomic:system",
            "Value": {
              "Ref": "SystemName"
            }
          }
        ],
        "Timeout": 300,
        "Runtime": {
          "Fn::FindInMap": [
            "Constants",
            "Cft",
            "NodejsRuntime"
          ]
        }
      }
    },
    "DeployCreateDeploymentPolicy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "create-deployment",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "codedeploy:CreateDeployment"
              ],
              "Resource": [
                {
                  "Fn::Sub": [
                    "arn:aws:codedeploy:${AWS::Region}:${AWS::AccountId}:deploymentgroup:${Application}/${CodeDeployDeploymentGroup}",
                    {
                      "Application": {
                        "Fn::If": [
                          "ApplicationNameProvided",
                          {
                            "Ref": "ApplicationName"
                          },
                          {
                            "Ref": "AWS::StackName"
                          }
                        ]
                      }
                    }
                  ]
                }
              ],
              "Sid": "CreateDeployment"
            }
          ]
        },
        "Roles": [
          {
            "Ref": "DeployLambdaRole"
          }
        ]
      }
    },
    "QueryGroupElbRegisterPolicy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "elb-register",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "elasticloadbalancing:DeregisterTargets",
                "elasticloadbalancing:DescribeTargetHealth",
                "elasticloadbalancing:RegisterTargets"
              ],
              "Resource": [
                "*"
              ]
            }
          ]
        },
        "Roles": [
          {
            "Ref": "QueryGroupRole"
          }
        ]
      }
    },
    "LambdaCreateVpcPolicy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "create-vpc",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "ec2:DeleteVpcEndpointServiceConfigurations",
                "ec2:DescribeVpcEndpointConnections",
                "ec2:DescribeVpcEndpointServiceConfigurations"
              ],
              "Resource": [
                "*"
              ],
              "Sid": "VPCEndpointService"
            }
          ]
        },
        "Roles": [
          {
            "Ref": "QueryGroupLambdaExecutionRole"
          }
        ]
      }
    },
    "LambdaSecurityGroup": {
      "Type": "AWS::EC2::SecurityGroup",
      "Properties": {
        "GroupDescription": {
          "Fn::Sub": "Security group for ${AWS::StackName} lambdas"
        },
        "VpcId": {
          "Fn::ImportValue": {
            "Fn::Sub": "${SystemName}-VpcId"
          }
        },
        "Tags": [
          {
            "Key": "Name",
            "Value": {
              "Fn::Sub": "${AWS::StackName}-lambda"
            }
          },
          {
            "Key": "datomic:system",
            "Value": {
              "Ref": "SystemName"
            }
          }
        ]
      }
    },
    "DeleteRecordSets": {
      "Type": "AWS::Lambda::Function",
      "DependsOn": [
        "LambdaListRoute53Policy",
        "LambdaLogsPolicy",
        "LambdaChangeRoute53Policy"
      ],
      "Properties": {
        "Handler": "index.handler",
        "Role": {
          "Fn::GetAtt": [
            "QueryGroupLambdaExecutionRole",
            "Arn"
          ]
        },
        "Code": {
          "ZipFile": {
            "Fn::Join": [
              "\n",
              [
                "// minified cfn-response",
                "var response={SUCCESS:\"SUCCESS\",FAILED:\"FAILED\",send:function(event,context,responseStatus,responseData,physicalResourceId,noEcho){var responseBody=JSON.stringify({Status:responseStatus,Reason:\"See the details in CloudWatch Log Stream: \"+context.logStreamName,PhysicalResourceId:physicalResourceId||context.logStreamName,StackId:event.StackId,RequestId:event.RequestId,LogicalResourceId:event.LogicalResourceId,NoEcho:noEcho||false,Data:responseData});console.log(\"Response body:\\n\",responseBody);var https=require(\"https\");var url=require(\"url\");var parsedUrl=url.parse(event.ResponseURL);var options={hostname:parsedUrl.hostname,port:443,path:parsedUrl.path,method:\"PUT\",headers:{\"content-type\":\"\",\"content-length\":responseBody.length}};var request=https.request(options,function(response){console.log(\"Status code: \"+response.statusCode);console.log(\"Status message: \"+response.statusMessage);context.done()});request.on(\"error\",function(error){console.log(\"send(..) failed executing https.request(..): \"+error);context.done()});request.write(responseBody);request.end()}};",
                "",
                "const {",
                "  Route53",
                "} = require(\"@aws-sdk/client-route-53\");",
                "var route53 = new Route53();",
                "",
                "function shouldDelete (event) {",
                "  return ((event.RequestType == 'Delete') ||",
                "          (event.RequestType == \"Update\" &&",
                "           event.OldResourceProperties.Topology == \"solo\"));",
                "}",
                "",
                "// Deletes RecordSets specified by hostedZoneId and recordName",
                "// Retries n times",
                "async function deleteRS(hostedZoneId, recordName, n = 5) {",
                "  try {",
                "    const rrs = await route53.listResourceRecordSets({HostedZoneId: hostedZoneId});",
                "    console.log(JSON.stringify(rrs));",
                "    const changes = [];",
                "    rrs.ResourceRecordSets.filter(function (v) {return v.Name === recordName}).forEach(function(record) {",
                "      changes.push({Action: \"DELETE\",",
                "        ResourceRecordSet: {",
                "        Name: record.Name,",
                "        Type: record.Type,",
                "        ResourceRecords: record.ResourceRecords,",
                "        TTL: record.TTL",
                "      }})});",
                "    console.log({changes: changes, recordName: JSON.stringify(recordName)});",
                "    if (changes.length > 0) {",
                "      const deleteParams = {ChangeBatch: {",
                "        Changes: changes},",
                "        HostedZoneId: hostedZoneId};",
                "      return await route53.changeResourceRecordSets(deleteParams);",
                "    }",
                "    else {",
                "      console.log(\"No entry to delete.\");",
                "      return rrs;",
                "    }",
                "  } catch (err) {",
                "    console.error(err.stack);",
                "    if (n == 1) throw err;",
                "    else {",
                "      console.log({\"Retry\": n});",
                "      return deleteRS(hostedZoneId, recordName, n - 1);",
                "   }",
                "  }",
                "}",
                "",
                "/**",
                " * Handler for deleting resource record sets",
                " * Expects an event param with a ResourceProperties property with the following properties:",
                " * - HostedZoneId - The HostedZone to delete the record set from",
                " * - DeleteName - The name of the record to be deleted on delete event",
                " * - Topology - If topology changes update event will delete",
                " * - UpdateName - The name of the record to be deleted on update event",
                " */",
                "",
                "exports.handler = async function(event, context) {",
                "  console.log({event: event, shouldDelete: shouldDelete(event)});",
                "  if (shouldDelete(event)) {",
                "    const recordName = ((event.RequestType == 'Delete') ? event.ResourceProperties.DeleteName : event.ResourceProperties.UpdateName);",
                "    const resp = await deleteRS(event.ResourceProperties.HostedZoneId, recordName);",
                "    console.log(JSON.stringify(resp));",
                "    return new Promise (() => {response.send(event, context, 'SUCCESS', resp, event.PhysicalResourceId)});",
                "  }",
                "  else {",
                "    return new Promise(() => {response.send(event, context, response.SUCCESS, {}, event.PhysicalResourceId)});",
                "  }",
                "};"
              ]
            ]
          }
        },
        "Runtime": {
          "Fn::FindInMap": [
            "Constants",
            "Cft",
            "NodejsRuntime"
          ]
        },
        "Tags": [
          {
            "Key": "datomic:system",
            "Value": {
              "Ref": "SystemName"
            }
          }
        ],
        "Timeout": 10
      }
    },
    "GetDeploymentStatus": {
      "Type": "AWS::Lambda::Function",
      "DependsOn": [
        "DeployReadCodedeployPolicy",
        "DeployLambdaLogPolicy"
      ],
      "Properties": {
        "Handler": "index.handler",
        "Role": {
          "Fn::GetAtt": [
            "DeployLambdaRole",
            "Arn"
          ]
        },
        "Code": {
          "ZipFile": {
            "Fn::Join": [
              "\n",
              [
                "const {",
                "  CodeDeploy",
                "} = require(\"@aws-sdk/client-codedeploy\");",
                "var cd = new CodeDeploy();",
                "",
                "exports.handler = (event, context, callback) => {",
                "  console.log({event: event});",
                "  var params = {",
                "    deploymentId: event.codeDeploy.deployment.deploymentId",
                "   };",
                "",
                "  cd.getDeployment(params, function(err, data) {",
                "    if (err) {",
                "      console.error(err.stack);",
                "      callback(err);",
                "    }",
                "    else  {",
                "      event.codeDeploy.deployment.status = data.deploymentInfo.status;",
                "      event.codeDeploy.deployment.errorInformation = data.deploymentInfo.errorInformation;",
                "      callback(null, event);",
                "    }",
                "  });",
                "};"
              ]
            ]
          }
        },
        "Runtime": {
          "Fn::FindInMap": [
            "Constants",
            "Cft",
            "NodejsRuntime"
          ]
        },
        "Tags": [
          {
            "Key": "datomic:system",
            "Value": {
              "Ref": "SystemName"
            }
          }
        ],
        "Timeout": 10
      }
    },
    "GetTaggedResources": {
      "Type": "AWS::Lambda::Function",
      "DependsOn": [
        "LambdaGetTagsPolicy",
        "LambdaLogsPolicy"
      ],
      "Properties": {
        "Handler": "index.handler",
        "Role": {
          "Fn::GetAtt": [
            "QueryGroupLambdaExecutionRole",
            "Arn"
          ]
        },
        "Code": {
          "ZipFile": {
            "Fn::Join": [
              "\n",
              [
                "var response={SUCCESS:\"SUCCESS\",FAILED:\"FAILED\",send:function(event,context,responseStatus,responseData,physicalResourceId,noEcho){var responseBody=JSON.stringify({Status:responseStatus,Reason:\"See the details in CloudWatch Log Stream: \"+context.logStreamName,PhysicalResourceId:physicalResourceId||context.logStreamName,StackId:event.StackId,RequestId:event.RequestId,LogicalResourceId:event.LogicalResourceId,NoEcho:noEcho||false,Data:responseData});console.log(\"Response body:\\n\",responseBody);var https=require(\"https\");var url=require(\"url\");var parsedUrl=url.parse(event.ResponseURL);var options={hostname:parsedUrl.hostname,port:443,path:parsedUrl.path,method:\"PUT\",headers:{\"content-type\":\"\",\"content-length\":responseBody.length}};var request=https.request(options,function(response){console.log(\"Status code: \"+response.statusCode);console.log(\"Status message: \"+response.statusMessage);context.done()});request.on(\"error\",function(error){console.log(\"send(..) failed executing https.request(..): \"+error);context.done()});request.write(responseBody);request.end()}};",
                "const {",
                "  ResourceGroupsTaggingAPI",
                "} = require(\"@aws-sdk/client-resource-groups-tagging-api\");",
                "var resourceGroupsTaggingApi = new ResourceGroupsTaggingAPI();",
                "",
                "function getArns (options, callback) {",
                "  var params = {ResourceTypeFilters: options.typeFilters,",
                "    TagFilters: options.tagFilters,",
                "    TagsPerPage: options.tagsPerPage,",
                "    PaginationToken: options.paginationToken};",
                "  resourceGroupsTaggingApi.getResources(params, function (err, data) {",
                "    if (err) return callback(err);",
                "    else if (data.err) return callback(new Error(data.err));",
                "    var arns = data.ResourceTagMappingList.map(function(mapping) {",
                "      return mapping.ResourceARN;",
                "    });",
                "    var pt = data.PaginationToken;",
                "    callback(null, pt, arns);",
                "  });",
                "}",
                "",
                "function getAllArns (options, callback) {",
                "  var arns = [];",
                "",
                "  function getAllArnsRecursively (paginationToken) {",
                "    options.PaginationToken = paginationToken;",
                "",
                "    getArns(options, function (error, paginationToken, page) {",
                "      if (error) return callback(error, page);",
                "      arns = arns.concat(page);",
                "",
                "      if (paginationToken) getAllArnsRecursively(paginationToken);",
                "      else callback(null, arns);",
                "    });",
                "  }",
                "  getAllArnsRecursively();",
                "}",
                "",
                "exports.handler = function(event, context, callback) {",
                "  if (event.RequestType == 'Delete') return response.send(event, context, response.SUCCESS);",
                "  else {",
                "    console.log({event: event});",
                "    var min = event.ResourceProperties.Min;",
                "    var max = event.ResourceProperties.Max;",
                "    getAllArns({typeFilters: event.ResourceProperties.TypeFilters,",
                "      tagFilters: [{Key: event.ResourceProperties.TagFilterKey,",
                "        Values: event.ResourceProperties.TagFilterValues}],",
                "      tagsPerPage: 100},",
                "      function (error, arns) {",
                "        if (error) {",
                "          console.error(error.stack);",
                "          return response.send(event, context, response.FAILED, {'error': error});",
                "        }",
                "        else {",
                "          console.log({arns: arns});",
                "          var numFound = arns.length;",
                "          if ((typeof min !== \"undefined\"",
                "              && numFound < min)",
                "             ||",
                "             (typeof max !== \"undefined\"",
                "              && numFound > max)) {",
                "              error = {\"msg\": \"Unexpected number of resources\", \"min\": min, \"max\": max, \"found\": arns.length, \"typeFilterKey\": event.ResourceProperties.TypeFilters, \"tagFilterValues\": event.ResourceProperties.TagFilterValues, \"arns\": arns};",
                "              console.error(error);",
                "              return response.send(event, context, response.FAILED, {'error': error});",
                "          }",
                "          else {",
                "            var resources = [];",
                "            arns = arns.sort();",
                "            arns.map(function(arn) {",
                "              var [a, part, serv, reg, acc, res] = arn.split(':');",
                "              res = res.split(\"/\").slice(-1)[0];",
                "              resources.push(res);",
                "            });",
                "            return response.send(event, context, response.SUCCESS, {\"ResourceList\": resources,\"ArnList\": arns});",
                "          }",
                "        }});",
                "  }",
                "};"
              ]
            ]
          }
        },
        "Runtime": {
          "Fn::FindInMap": [
            "Constants",
            "Cft",
            "NodejsRuntime"
          ]
        },
        "Tags": [
          {
            "Key": "datomic:system",
            "Value": {
              "Ref": "SystemName"
            }
          }
        ],
        "Timeout": 10
      }
    },
    "QueryGroupCodedeployReadPolicy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "codedeploy-read",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "s3:Get*",
                "s3:List*"
              ],
              "Resource": [
                {
                  "Fn::Sub": "arn:aws:s3:::aws-codedeploy-${AWS::Region}/*"
                }
              ]
            }
          ]
        },
        "Roles": [
          {
            "Ref": "QueryGroupRole"
          }
        ]
      }
    },
    "QueryGroupDdbAccessPolicy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "ddb-access",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "dynamodb:GetItem",
                "dynamodb:PutItem",
                "dynamodb:UpdateItem",
                "dynamodb:DeleteItem",
                "dynamodb:Query",
                "dynamodb:Scan",
                "dynamodb:DescribeTable",
                "dynamodb:BatchWriteItem"
              ],
              "Resource": [
                {
                  "Fn::ImportValue": {
                    "Fn::Sub": "${SystemName}-LogTableArn"
                  }
                },
                {
                  "Fn::ImportValue": {
                    "Fn::Sub": "${SystemName}-CatalogTableArn"
                  }
                }
              ]
            }
          ]
        },
        "Roles": [
          {
            "Ref": "QueryGroupRole"
          }
        ]
      }
    },
    "DeployDescribeEc2Policy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "describe-ec2",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "ec2:DescribeSecurityGroups",
                "ec2:DescribeSubnets",
                "ec2:DescribeVpcs"
              ],
              "Resource": [
                "*"
              ],
              "Sid": "DescribeEC2"
            }
          ]
        },
        "Roles": [
          {
            "Ref": "DeployLambdaRole"
          }
        ]
      }
    },
    "DeleteLambdasLogGroup": {
      "Type": "AWS::Logs::LogGroup",
      "DeletionPolicy": "Retain",
      "UpdateReplacePolicy": "Retain",
      "Properties": {
        "LogGroupName": {
          "Fn::Sub": "/aws/lambda/${DeleteLambdas}"
        },
        "RetentionInDays": 7
      }
    },
    "DeleteLambdaFromArray": {
      "Type": "AWS::Lambda::Function",
      "DependsOn": [
        "DeployDeployFunctionsPolicy",
        "DeployLambdaLogPolicy"
      ],
      "Properties": {
        "Handler": "index.handler",
        "Role": {
          "Fn::GetAtt": [
            "DeployLambdaRole",
            "Arn"
          ]
        },
        "Code": {
          "ZipFile": {
            "Fn::Join": [
              "\n",
              [
                "const {",
                "  Lambda",
                "} = require(\"@aws-sdk/client-lambda\");",
                "var l = new Lambda();",
                "",
                "function deleteFunction (lambda) {",
                "  var deleteParams = {",
                "    FunctionName: lambda.FunctionName",
                "  };",
                "",
                "  return l.deleteFunction(deleteParams).then(function(deleteData) {",
                "      return null;",
                "  });",
                "}",
                "",
                "// Expects a map with a d array, each entry a lambda map with a FunctionName",
                "// Deletes the lambda pointed to by event.lambda.dI index",
                "// Decrements the index in return value",
                "exports.handler = (event, context, callback) => {",
                "  console.log({event: event});",
                "  var index = event.lambda.dI;",
                "  if (index >= 0) {",
                "    var lambda = event.lambda.d[index];",
                "",
                "    return deleteFunction(lambda).then(function() {",
                "      event.lambda.dI--;",
                "      callback(null, event);",
                "    })",
                "    .catch((err) => {",
                "      if(err.code === \"ResourceNotFoundException\") {",
                "        event.lambda.dI--;",
                "        callback(null, event);",
                "      } else {",
                "        console.error(err.stack);",
                "        callback(err);",
                "      }",
                "    });",
                "  }",
                "  else {",
                "    callback(null, event);",
                "  }",
                "};"
              ]
            ]
          }
        },
        "Runtime": {
          "Fn::FindInMap": [
            "Constants",
            "Cft",
            "NodejsRuntime"
          ]
        },
        "Tags": [
          {
            "Key": "datomic:system",
            "Value": {
              "Ref": "SystemName"
            }
          }
        ],
        "Timeout": 10
      }
    },
    "DeleteDatomicLambda": {
      "Type": "Custom::Resource",
      "DependsOn": [
        "DeleteLambdasLogGroup"
      ],
      "Properties": {
        "ServiceToken": {
          "Fn::GetAtt": [
            "DeleteLambdas",
            "Arn"
          ]
        },
        "TagKey": "datomic:qgroup",
        "TagValues": [
          {
            "Ref": "AWS::StackName"
          }
        ]
      }
    },
    "QueryGroupAutoScalingGroup": {
      "Type": "AWS::AutoScaling::AutoScalingGroup",
      "DependsOn": [
        "DeleteEntryRecordSet"
      ],
      "Properties": {
        "VPCZoneIdentifier": [
          {
            "Fn::ImportValue": {
              "Fn::Sub": "${SystemName}-Subnet0"
            }
          },
          {
            "Fn::ImportValue": {
              "Fn::Sub": "${SystemName}-Subnet1"
            }
          },
          {
            "Fn::ImportValue": {
              "Fn::Sub": "${SystemName}-Subnet2"
            }
          }
        ],
        "LaunchTemplate": {
          "LaunchTemplateId": {
            "Ref": "QueryGroupLaunchTemplate"
          },
          "Version": {
            "Fn::GetAtt": [
              "QueryGroupLaunchTemplate",
              "LatestVersionNumber"
            ]
          }
        },
        "Tags": [
          {
            "Key": "Name",
            "Value": {
              "Ref": "AWS::StackName"
            },
            "PropagateAtLaunch": true
          },
          {
            "Key": "datomic:system",
            "Value": {
              "Ref": "SystemName"
            },
            "PropagateAtLaunch": true
          },
          {
            "Key": "datomic:subscriber",
            "Value": {
              "Ref": "SystemName"
            },
            "PropagateAtLaunch": true
          }
        ],
        "TargetGroupARNs": [
          {
            "Ref": "TargetGroup"
          },
          {
            "Ref": "HttpDirectTargetGroup"
          }
        ],
        "HealthCheckType": "EC2",
        "MinSize": {
          "Fn::If": [
            "DefaultMinSize",
            {
              "Fn::FindInMap": [
                "InstanceSettings",
                {
                  "Ref": "InstanceType"
                },
                "DesiredCapacity"
              ]
            },
            {
              "Ref": "MinSize"
            }
          ]
        },
        "MaxSize": {
          "Fn::If": [
            "DefaultMaxSize",
            "3",
            {
              "Ref": "MaxSize"
            }
          ]
        },
        "DesiredCapacity": {
          "Fn::If": [
            "DefaultDesiredCapacity",
            {
              "Fn::FindInMap": [
                "InstanceSettings",
                {
                  "Ref": "InstanceType"
                },
                "DesiredCapacity"
              ]
            },
            {
              "Ref": "DesiredCapacity"
            }
          ]
        },
        "HealthCheckGracePeriod": 300
      },
      "UpdatePolicy": {
        "AutoScalingRollingUpdate": {
          "MaxBatchSize": 1,
          "MinInstancesInService": {
            "Fn::If": [
              "DefaultMinInstancesInService",
              {
                "Fn::FindInMap": [
                  "InstanceSettings",
                  {
                    "Ref": "InstanceType"
                  },
                  "DesiredCapacity"
                ]
              },
              {
                "Ref": "MinInstancesInService"
              }
            ]
          }
        }
      }
    },
    "DatomicLambdaSnsPublishPolicy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "sns-publish",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "sns:Publish"
              ],
              "Resource": [
                {
                  "Ref": "DeadLetterQueue"
                }
              ]
            }
          ]
        },
        "Roles": [
          {
            "Ref": "DatomicLambdaRole"
          }
        ]
      }
    },
    "CreateEndpointServiceLogGroup": {
      "Type": "AWS::Logs::LogGroup",
      "DeletionPolicy": "Retain",
      "UpdateReplacePolicy": "Retain",
      "Properties": {
        "LogGroupName": {
          "Fn::Sub": "/aws/lambda/${CreateEndpointService}"
        },
        "RetentionInDays": 7
      }
    },
    "DeleteEnisLogGroup": {
      "Type": "AWS::Logs::LogGroup",
      "DeletionPolicy": "Retain",
      "UpdateReplacePolicy": "Retain",
      "Properties": {
        "LogGroupName": {
          "Fn::Sub": "/aws/lambda/${DeleteEnis}"
        },
        "RetentionInDays": 7
      }
    },
    "QueryGroupGetParametersPolicy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "get-parameters",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "ssm:GetParametersByPath"
              ],
              "Resource": [
                {
                  "Fn::Sub": "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/datomic-shared/*"
                }
              ]
            }
          ]
        },
        "Roles": [
          {
            "Ref": "QueryGroupRole"
          }
        ]
      }
    },
    "LambdaGetTagsPolicy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "get-tags",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "tag:getResources"
              ],
              "Resource": [
                "*"
              ],
              "Sid": "GetTaggedResources"
            }
          ]
        },
        "Roles": [
          {
            "Ref": "QueryGroupLambdaExecutionRole"
          }
        ]
      }
    },
    "QueryGroupInstanceProfile": {
      "Type": "AWS::IAM::InstanceProfile",
      "DependsOn": [
        "QueryGroupEc2DescribePolicy",
        "QueryGroupAsgDescribePolicy",
        "QueryGroupElbRegisterPolicy",
        "QueryGroupCloudwatchPutPolicy",
        "QueryGroupLogsCreatePolicy",
        "QueryGroupDdbAccessPolicy",
        "QueryGroupS3AccessPolicy",
        "QueryGroupKmsDecryptPolicy",
        "QueryGroupCodedeployReadPolicy",
        "QueryGroupDeploymentGroupReadPolicy",
        "QueryGroupDeploymentReadPolicy",
        "QueryGroupRoute53ChangePolicy"
      ],
      "Properties": {
        "Roles": [
          {
            "Ref": "QueryGroupRole"
          }
        ]
      }
    },
    "QueryGroupCloudwatchPutPolicy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "cloudwatch-put",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "cloudwatch:PutMetricData",
                "cloudwatch:PutMetricDataBatch"
              ],
              "Resource": "*",
              "Condition": {
                "Bool": {
                  "aws:SecureTransport": "true"
                }
              }
            }
          ]
        },
        "Roles": [
          {
            "Ref": "QueryGroupRole"
          }
        ]
      }
    },
    "QueryGroupAsgDescribePolicy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "asg-describe",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "autoscaling:Describe*"
              ],
              "Resource": "*"
            }
          ]
        },
        "Roles": [
          {
            "Ref": "QueryGroupRole"
          }
        ]
      }
    },
    "QueryGroupDeploymentReadPolicy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "deployment-read",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "codedeploy:ListDeploymentGroups",
                "codedeploy:ListApplications"
              ],
              "Resource": [
                {
                  "Fn::Sub": "arn:aws:codedeploy:${AWS::Region}:${AWS::AccountId}:application:*"
                }
              ]
            }
          ]
        },
        "Roles": [
          {
            "Ref": "QueryGroupRole"
          }
        ]
      }
    },
    "QueryGroupRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "RoleName": {
          "Fn::Sub": "${AWS::StackName}-${AWS::Region}"
        },
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "Service": [
                  "ec2.amazonaws.com"
                ]
              },
              "Action": [
                "sts:AssumeRole"
              ]
            }
          ]
        },
        "ManagedPolicyArns": [
          {
            "Fn::If": [
              "AttachManagedPolicy",
              {
                "Ref": "NodePolicyArn"
              },
              {
                "Ref": "AWS::NoValue"
              }
            ]
          },
          {
            "Fn::ImportValue": {
              "Fn::Sub": "${SystemName}-CodeBucketPolicyArn"
            }
          },
          "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess",
          "arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy"
        ],
        "Tags": [
          {
            "Key": "datomic:system",
            "Value": {
              "Ref": "SystemName"
            }
          }
        ]
      }
    },
    "Listener": {
      "Type": "AWS::ElasticLoadBalancingV2::Listener",
      "Properties": {
        "DefaultActions": [
          {
            "Type": "forward",
            "TargetGroupArn": {
              "Ref": "TargetGroup"
            }
          }
        ],
        "LoadBalancerArn": {
          "Ref": "LoadBalancer"
        },
        "Port": 8182,
        "Protocol": "HTTP"
      }
    },
    "ClientApiIntegration": {
      "Type": "AWS::ApiGatewayV2::Integration",
      "Condition": "CreateClientApiGateway",
      "Properties": {
        "ConnectionId": {
          "Fn::ImportValue": {
            "Fn::Sub": "${SystemName}-VpcLinkId"
          }
        },
        "RequestParameters": {
          "overwrite:header.x-cognitect-apig-auth": "$request.header.authorization",
          "overwrite:header.x-cognitect-apig-host": "$request.header.host"
        },
        "IntegrationMethod": "ANY",
        "PayloadFormatVersion": "1.0",
        "IntegrationUri": {
          "Ref": "Listener"
        },
        "IntegrationType": "HTTP_PROXY",
        "Description": {
          "Fn::Sub": "${AWS::StackName} Datomic Client API"
        },
        "ConnectionType": "VPC_LINK",
        "ApiId": {
          "Ref": "ClientApiGateway"
        }
      }
    },
    "LoadBalancerEgressNodes": {
      "Type": "AWS::EC2::SecurityGroupEgress",
      "Properties": {
        "FromPort": 8182,
        "Description": "Egress to Datomic nodes",
        "GroupId": {
          "Ref": "LoadBalancerSecurityGroup"
        },
        "IpProtocol": "tcp",
        "DestinationSecurityGroupId": {
          "Fn::Select": [
            0,
            {
              "Fn::GetAtt": [
                "ComputeSecurityGroup",
                "ResourceList"
              ]
            }
          ]
        },
        "ToPort": 8184
      }
    },
    "QueryGroupRoute53ChangePolicy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "route53-change",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "route53:ChangeResourceRecordSets"
              ],
              "Resource": [
                {
                  "Fn::Sub": "arn:aws:route53:::hostedzone/${HostedZone}"
                }
              ]
            }
          ]
        },
        "Roles": [
          {
            "Ref": "QueryGroupRole"
          }
        ]
      }
    },
    "EntryHttpRecordSet": {
      "Type": "AWS::Route53::RecordSet",
      "Properties": {
        "AliasTarget": {
          "DNSName": {
            "Fn::GetAtt": [
              "LoadBalancer",
              "DNSName"
            ]
          },
          "HostedZoneId": {
            "Fn::GetAtt": [
              "LoadBalancer",
              "CanonicalHostedZoneID"
            ]
          }
        },
        "HostedZoneId": {
          "Ref": "HostedZone"
        },
        "Name": {
          "Fn::Sub": "entry-http.${AWS::StackName}.${AWS::Region}.datomic.net."
        },
        "Type": "A"
      }
    },
    "LambdaCodedeployApplicationPolicy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "codedeploy-application",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "codedeploy:BatchGetApplications"
              ],
              "Resource": [
                {
                  "Fn::Sub": "arn:aws:codedeploy:${AWS::Region}:${AWS::AccountId}:application:*"
                }
              ],
              "Sid": "CodeDeployRead"
            },
            {
              "Effect": "Allow",
              "Action": [
                "codedeploy:CreateApplication"
              ],
              "Resource": [
                {
                  "Fn::Sub": [
                    "arn:aws:codedeploy:${AWS::Region}:${AWS::AccountId}:application:${Application}",
                    {
                      "Application": {
                        "Fn::If": [
                          "ApplicationNameProvided",
                          {
                            "Ref": "ApplicationName"
                          },
                          {
                            "Ref": "AWS::StackName"
                          }
                        ]
                      }
                    }
                  ]
                }
              ],
              "Sid": "CodeDeployCreate"
            }
          ]
        },
        "Roles": [
          {
            "Ref": "QueryGroupLambdaExecutionRole"
          }
        ]
      }
    },
    "LoadBalancer": {
      "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer",
      "Properties": {
        "Scheme": "internal",
        "Type": "application",
        "Tags": [
          {
            "Key": "datomic:system",
            "Value": {
              "Ref": "SystemName"
            }
          }
        ],
        "SecurityGroups": [
          {
            "Ref": "LoadBalancerSecurityGroup"
          }
        ],
        "Subnets": [
          {
            "Fn::ImportValue": {
              "Fn::Sub": "${SystemName}-Subnet0"
            }
          },
          {
            "Fn::ImportValue": {
              "Fn::Sub": "${SystemName}-Subnet1"
            }
          },
          {
            "Fn::ImportValue": {
              "Fn::Sub": "${SystemName}-Subnet2"
            }
          }
        ]
      }
    },
    "QueryGroupKmsDecryptPolicy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "kms-decrypt",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "kms:Decrypt",
                "kms:GenerateDataKey"
              ],
              "Resource": [
                {
                  "Fn::Sub": "arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/*"
                }
              ]
            }
          ]
        },
        "Roles": [
          {
            "Ref": "QueryGroupRole"
          }
        ]
      }
    },
    "LambdaDeleteEniPolicy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "delete-eni",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "ec2:DeleteNetworkInterface",
                "ec2:DescribeNetworkInterfaces",
                "ec2:DetachNetworkInterface"
              ],
              "Resource": [
                "*"
              ],
              "Sid": "ENI"
            }
          ]
        },
        "Roles": [
          {
            "Ref": "QueryGroupLambdaExecutionRole"
          }
        ]
      }
    },
    "DeployLambdaPassRolePolicy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "iam-pass-role",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "iam:PassRole"
              ],
              "Resource": [
                {
                  "Fn::GetAtt": [
                    "DatomicLambdaRole",
                    "Arn"
                  ]
                }
              ],
              "Sid": "LambdaPassRole"
            }
          ]
        },
        "Roles": [
          {
            "Ref": "DeployLambdaRole"
          }
        ]
      }
    },
    "GetTaggedResourcesLogGroup": {
      "Type": "AWS::Logs::LogGroup",
      "DeletionPolicy": "Retain",
      "UpdateReplacePolicy": "Retain",
      "Properties": {
        "LogGroupName": {
          "Fn::Sub": "/aws/lambda/${GetTaggedResources}"
        },
        "RetentionInDays": 7
      }
    },
    "QueryGroupDeploymentGroupReadPolicy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "deployment-group-read",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "codedeploy:BatchGetDeploymentGroups",
                "codedeploy:GetDeploymentGroup"
              ],
              "Resource": [
                {
                  "Fn::Sub": "arn:aws:codedeploy:${AWS::Region}:${AWS::AccountId}:deploymentgroup:*"
                }
              ]
            }
          ]
        },
        "Roles": [
          {
            "Ref": "QueryGroupRole"
          }
        ]
      }
    },
    "DeleteRecordSetsLogGroup": {
      "Type": "AWS::Logs::LogGroup",
      "DeletionPolicy": "Retain",
      "UpdateReplacePolicy": "Retain",
      "Properties": {
        "LogGroupName": {
          "Fn::Sub": "/aws/lambda/${DeleteRecordSets}"
        },
        "RetentionInDays": 7
      }
    },
    "LambdaListRoute53Policy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "list-route53",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "route53:ListResourceRecordSets"
              ],
              "Resource": [
                "*"
              ],
              "Sid": "Route53List"
            }
          ]
        },
        "Roles": [
          {
            "Ref": "QueryGroupLambdaExecutionRole"
          }
        ]
      }
    },
    "QueryGroupEc2DescribePolicy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "ec2-describe",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "ec2:DescribeInstances"
              ],
              "Resource": "*"
            }
          ]
        },
        "Roles": [
          {
            "Ref": "QueryGroupRole"
          }
        ]
      }
    },
    "TargetGroup": {
      "Type": "AWS::ElasticLoadBalancingV2::TargetGroup",
      "Properties": {
        "HealthyThresholdCount": 2,
        "HealthCheckTimeoutSeconds": 5,
        "Tags": [
          {
            "Key": "datomic:system",
            "Value": {
              "Ref": "SystemName"
            }
          }
        ],
        "UnhealthyThresholdCount": 2,
        "Protocol": "HTTP",
        "Port": 8182,
        "VpcId": {
          "Fn::ImportValue": {
            "Fn::Sub": "${SystemName}-VpcId"
          }
        },
        "HealthCheckIntervalSeconds": 10,
        "HealthCheckPort": "8182",
        "HealthCheckProtocol": "HTTP",
        "TargetGroupAttributes": [
          {
            "Key": "deregistration_delay.timeout_seconds",
            "Value": "30"
          },
          {
            "Key": "load_balancing.algorithm.type",
            "Value": "least_outstanding_requests"
          }
        ],
        "HealthCheckPath": "/"
      }
    },
    "DeleteEnis": {
      "Type": "AWS::Lambda::Function",
      "DependsOn": [
        "LambdaSecurityGroup",
        "LambdaDeleteEniPolicy",
        "LambdaLogsPolicy"
      ],
      "Properties": {
        "Handler": "index.handler",
        "Role": {
          "Fn::GetAtt": [
            "QueryGroupLambdaExecutionRole",
            "Arn"
          ]
        },
        "Code": {
          "ZipFile": {
            "Fn::Join": [
              "\n",
              [
                "// minified cfn-response",
                "var response={SUCCESS:\"SUCCESS\",FAILED:\"FAILED\",send:function(event,context,responseStatus,responseData,physicalResourceId,noEcho){var responseBody=JSON.stringify({Status:responseStatus,Reason:\"See the details in CloudWatch Log Stream: \"+context.logStreamName,PhysicalResourceId:physicalResourceId||context.logStreamName,StackId:event.StackId,RequestId:event.RequestId,LogicalResourceId:event.LogicalResourceId,NoEcho:noEcho||false,Data:responseData});console.log(\"Response body:\\n\",responseBody);var https=require(\"https\");var url=require(\"url\");var parsedUrl=url.parse(event.ResponseURL);var options={hostname:parsedUrl.hostname,port:443,path:parsedUrl.path,method:\"PUT\",headers:{\"content-type\":\"\",\"content-length\":responseBody.length}};var request=https.request(options,function(response){console.log(\"Status code: \"+response.statusCode);console.log(\"Status message: \"+response.statusMessage);context.done()});request.on(\"error\",function(error){console.log(\"send(..) failed executing https.request(..): \"+error);context.done()});request.write(responseBody);request.end()}};",
                "",
                "const {",
                "  EC2,",
                "  waitUntilNetworkInterfaceAvailable",
                "} = require(\"@aws-sdk/client-ec2\");",
                "var ec2 = new EC2();",
                "",
                "function delete_with_retry(params, n, nextToken) {",
                "  if (nextToken) {",
                "    params.NextToken = nextToken;",
                "  }",
                "",
                "  return ec2.describeNetworkInterfaces(params).then(function(descData) {",
                "    console.log({\"Interfaces\": descData});",
                "    var deletePromise = Promise.all(descData.NetworkInterfaces.map(function(networkInterface) {",
                "      console.log({\"networkInterface\": networkInterface});",
                "      var networkInterfaceId = networkInterface.NetworkInterfaceId;",
                "      console.log({\"networkInterfaceId\": networkInterfaceId});",
                "      if (networkInterface.Attachment) {",
                "        var attachmentId = networkInterface.Attachment.AttachmentId;",
                "        console.log({\"attachmentId\": attachmentId});",
                "        return ec2.detachNetworkInterface({AttachmentId: attachmentId}).then(function(data) {",
                "          return waitUntilNetworkInterfaceAvailable({",
                "            client: ec2,",
                "            maxWaitTime: 200",
                "          }, {NetworkInterfaceIds: [networkInterfaceId]});",
                "        }).then(function(data) {",
                "          console.log({\"Detached\": networkInterfaceId});",
                "          return ec2.deleteNetworkInterface({NetworkInterfaceId: networkInterfaceId});",
                "        }).catch(function(err) {",
                "          if(err.code === \"InvalidNetworkInterfaceID.NotFound\") {",
                "            return Promise.resolve({});",
                "          }",
                "          else {",
                "            throw err;",
                "          }",
                "        });",
                "      }",
                "      else {",
                "        return ec2.deleteNetworkInterface({NetworkInterfaceId: networkInterfaceId}).then(function(data) {",
                "          return data;",
                "        }).catch(function(err) {",
                "          if(err.code === \"InvalidNetworkInterfaceID.NotFound\") {",
                "            return Promise.resolve({});",
                "          }",
                "          else {",
                "            throw err;",
                "          }",
                "        });",
                "      }",
                "    }));",
                "",
                "    if (descData.NextToken) {",
                "      return deletePromise.then(function(data) {",
                "        return delete_with_retry(params, n, descData.NextToken);",
                "      });",
                "    }",
                "    else {",
                "      return deletePromise;",
                "    }",
                "  }).catch(function(err) {",
                "    console.error(err.stack);",
                "    if (n == 1) {",
                "      throw err;",
                "    }",
                "    else {",
                "      console.log({\"Retry\": n});",
                "      return delete_with_retry(params, n - 1);",
                "    }",
                "  });",
                "}",
                "",
                "// Deletes ENI for SecurityGroups when event.RequestType is Delete",
                "// expects event.ResourceProperties.SecurityGroups",
                "exports.handler = (event, context, callback) => {",
                "  if (event.RequestType !== 'Delete') {",
                "    return response.send(event, context, response.SUCCESS);",
                "  }",
                "",
                "  console.log({event: event});",
                "  var params = {Filters: [{Name: 'group-id',",
                "                           Values: event.ResourceProperties.SecurityGroups}]};",
                "",
                "  delete_with_retry(params, 5).then(function(data) {",
                "    console.log(\"Success\");",
                "    return response.send(event, context, response.SUCCESS, {\"data\": data});",
                "  }).catch(function(err) {",
                "    console.error(err.stack);",
                "    return response.send(event, context, response.FAILED, {'error': err});",
                "  });",
                "};"
              ]
            ]
          }
        },
        "Tags": [
          {
            "Key": "datomic:system",
            "Value": {
              "Ref": "SystemName"
            }
          }
        ],
        "Timeout": 300,
        "Runtime": {
          "Fn::FindInMap": [
            "Constants",
            "Cft",
            "NodejsRuntime"
          ]
        }
      }
    },
    "EnsureCodeDeployApplication": {
      "Type": "AWS::Lambda::Function",
      "DependsOn": [
        "LambdaCodedeployApplicationPolicy",
        "LambdaLogsPolicy"
      ],
      "Properties": {
        "Handler": "index.handler",
        "Role": {
          "Fn::GetAtt": [
            "QueryGroupLambdaExecutionRole",
            "Arn"
          ]
        },
        "Code": {
          "ZipFile": {
            "Fn::Join": [
              "\n",
              [
                "// minified cfn-response",
                "var response={SUCCESS:\"SUCCESS\",FAILED:\"FAILED\",send:function(event,context,responseStatus,responseData,physicalResourceId,noEcho){var responseBody=JSON.stringify({Status:responseStatus,Reason:\"See the details in CloudWatch Log Stream: \"+context.logStreamName,PhysicalResourceId:physicalResourceId||context.logStreamName,StackId:event.StackId,RequestId:event.RequestId,LogicalResourceId:event.LogicalResourceId,NoEcho:noEcho||false,Data:responseData});console.log(\"Response body:\\n\",responseBody);var https=require(\"https\");var url=require(\"url\");var parsedUrl=url.parse(event.ResponseURL);var options={hostname:parsedUrl.hostname,port:443,path:parsedUrl.path,method:\"PUT\",headers:{\"content-type\":\"\",\"content-length\":responseBody.length}};var request=https.request(options,function(response){console.log(\"Status code: \"+response.statusCode);console.log(\"Status message: \"+response.statusMessage);context.done()});request.on(\"error\",function(error){console.log(\"send(..) failed executing https.request(..): \"+error);context.done()});request.write(responseBody);request.end()}};",
                "",
                "const {",
                "  CodeDeploy",
                "} = require(\"@aws-sdk/client-codedeploy\");",
                "var codedeploy = new CodeDeploy();",
                "",
                "// Creates Code Deploy Application if it doesn't exist",
                "exports.handler = (event, context, callback) => {",
                "  if (event.RequestType === 'Delete') {",
                "    return response.send(event, context, response.SUCCESS);",
                "  }",
                "",
                "  console.log({event: event});",
                "  if (event.RequestType === 'Update') {",
                "    var oldApplicationName = event.OldResourceProperties.ApplicationName;",
                "    var newApplicationName = event.ResourceProperties.ApplicationName;",
                "",
                "    if (oldApplicationName !== newApplicationName) {",
                "      var error = new Error('Application Name cannot be updated. Current Application Name: ' + oldApplicationName);",
                "      console.error(error.stack);",
                "      return response.send(event, context, response.FAILED, {'error': error}, \"\");",
                "    }",
                "  }",
                "",
                "  var applicationName = event.ResourceProperties.ApplicationName;",
                "  console.log(\"Ensuring \" + applicationName);",
                "",
                "  codedeploy.batchGetApplications({applicationNames: [applicationName]}, function(err, data) {",
                "    if (err) {",
                "      console.error(err.stack);",
                "      return response.send(event, context, response.FAILED, {'error': err});",
                "    }",
                "    else if (data.applicationsInfo.length < 1) {",
                "      console.log(\"Creating \" + applicationName);",
                "      var createParams = {applicationName: applicationName,",
                "                          computePlatform: 'Server'};",
                "      codedeploy.createApplication(createParams, function(err, data) {",
                "        if (err) {",
                "          console.error(err.stack);",
                "          return response.send(event, context, response.FAILED, {'error': err});",
                "        }",
                "        else {",
                "          return response.send(event, context, response.SUCCESS, {\"ApplicationName\": applicationName});",
                "        }",
                "      });",
                "    }",
                "    else {",
                "      var applicationInfo = data.applicationsInfo[0];",
                "      if (applicationInfo.computePlatform != 'Server') {",
                "        var error = new Error(applicationName + ' already exists, incorrectly configured for Lambda compute platform');",
                "        console.error(error.stack);",
                "        return response.send(event, context, response.FAILED, {'error': error});",
                "      }",
                "",
                "      else {",
                "        console.log(applicationName + \" already exists\");",
                "        return response.send(event, context, response.SUCCESS, {\"ApplicationName\": applicationName});",
                "      }",
                "    }",
                "  });",
                "};"
              ]
            ]
          }
        },
        "Runtime": {
          "Fn::FindInMap": [
            "Constants",
            "Cft",
            "NodejsRuntime"
          ]
        },
        "Tags": [
          {
            "Key": "datomic:system",
            "Value": {
              "Ref": "SystemName"
            }
          }
        ],
        "Timeout": 10
      }
    },
    "StatesInvokePolicy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "lambda-invoke",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "lambda:invokeFunction"
              ],
              "Resource": [
                {
                  "Fn::GetAtt": [
                    "CreateCodeDeployDeployment",
                    "Arn"
                  ]
                },
                {
                  "Fn::GetAtt": [
                    "CreateLambdaFromArray",
                    "Arn"
                  ]
                },
                {
                  "Fn::GetAtt": [
                    "DeleteLambdaFromArray",
                    "Arn"
                  ]
                },
                {
                  "Fn::GetAtt": [
                    "GetDeploymentStatus",
                    "Arn"
                  ]
                },
                {
                  "Fn::GetAtt": [
                    "GetLambdaStatus",
                    "Arn"
                  ]
                },
                {
                  "Fn::GetAtt": [
                    "UpdateLambdaFromArray",
                    "Arn"
                  ]
                }
              ],
              "Sid": "LambdaInvoke"
            }
          ]
        },
        "Roles": [
          {
            "Ref": "StatesExecutionRole"
          }
        ]
      }
    },
    "QueryGroupLogsCreatePolicy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "logs-create",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents"
              ],
              "Resource": "*",
              "Condition": {
                "Bool": {
                  "aws:SecureTransport": "true"
                }
              }
            }
          ]
        },
        "Roles": [
          {
            "Ref": "QueryGroupRole"
          }
        ]
      }
    },
    "CreateEndpointService": {
      "Type": "AWS::Lambda::Function",
      "DependsOn": [
        "LambdaCreateVpcPolicy",
        "LambdaLogsPolicy"
      ],
      "Properties": {
        "Handler": "index.handler",
        "Role": {
          "Fn::GetAtt": [
            "QueryGroupLambdaExecutionRole",
            "Arn"
          ]
        },
        "Code": {
          "ZipFile": {
            "Fn::Join": [
              "\n",
              [
                "// minified cfn-response",
                "var response={SUCCESS:\"SUCCESS\",FAILED:\"FAILED\",send:function(event,context,responseStatus,responseData,physicalResourceId,noEcho){var responseBody=JSON.stringify({Status:responseStatus,Reason:\"See the details in CloudWatch Log Stream: \"+context.logStreamName,PhysicalResourceId:physicalResourceId||context.logStreamName,StackId:event.StackId,RequestId:event.RequestId,LogicalResourceId:event.LogicalResourceId,NoEcho:noEcho||false,Data:responseData});console.log(\"Response body:\\n\",responseBody);var https=require(\"https\");var url=require(\"url\");var parsedUrl=url.parse(event.ResponseURL);var options={hostname:parsedUrl.hostname,port:443,path:parsedUrl.path,method:\"PUT\",headers:{\"content-type\":\"\",\"content-length\":responseBody.length}};var request=https.request(options,function(response){console.log(\"Status code: \"+response.statusCode);console.log(\"Status message: \"+response.statusMessage);context.done()});request.on(\"error\",function(error){console.log(\"send(..) failed executing https.request(..): \"+error);context.done()});request.write(responseBody);request.end()}};",
                "",
                "const {",
                " EC2",
                "} = require(\"@aws-sdk/client-ec2\");",
                "var ec2 = new EC2();",
                "",
                "var noServiceReturn = {ServiceId: 'None'};",
                "",
                "// Returns promise w/the Service Config for NlbArns",
                "function getService (NlbArns, nextToken) {",
                "  var params = {};",
                "  if (nextToken) {",
                "    params.NextToken = nextToken;",
                "  }",
                "  return ec2.describeVpcEndpointServiceConfigurations(params).then(function(data) {",
                "    console.log(data);",
                "    var sc = data.ServiceConfigurations.find(function (sc) {",
                "      return sc.NetworkLoadBalancerArns.some(function (nlb) {",
                "        return NlbArns.indexOf(nlb) >= 0;",
                "      });",
                "    });",
                "",
                "    if (sc) {",
                "     return {ServiceId: sc.ServiceId};",
                "    } else if (data.NextToken) {",
                "      return getService(NlbArns, data.NextToken);",
                "    }",
                "    else return null;",
                "  });",
                "}",
                "",
                "// Delete the endpoint if it's not used, returns promise",
                "function deleteEndpoint(props) {",
                " return getService(props.NetworkLoadBalancerArns).then(function(service) {",
                "  if (service) {",
                "   var params = {Filters: [{Name: \"service-id\", Values: [service.ServiceId]}]};",
                "   return ec2.describeVpcEndpointConnections(params).then(function(data) {",
                "    console.log(data);",
                "    if (data.VpcEndpointConnections.length > 0) {",
                "     return Promise.resolve(service);",
                "    } else {",
                "     console.log(\"Deleting \" + service.ServiceId);",
                "     var params = {ServiceIds: [ service.ServiceId ]};",
                "     return ec2.deleteVpcEndpointServiceConfigurations(params).then(function(data) {",
                "      if (data.Unsuccessful.length > 0) throw new Error(data.Unsuccessful[0].Error.Message);",
                "      else return noServiceReturn;",
                "     });",
                "    }",
                "   });",
                "  }",
                "  else {",
                "   console.log('Endpoint not found');",
                "   return Promise.resolve(noServiceReturn);",
                "  }",
                " });",
                "}",
                "",
                "exports.handler = (event, context, callback) => {",
                " console.log({event: event});",
                " if (event.RequestType === 'Create') {",
                "  return response.send(event, context, response.SUCCESS, noServiceReturn);",
                " } else if (event.RequestType === 'Delete' || event.RequestType === 'Update' ) {",
                "  deleteEndpoint(event.ResourceProperties).then(function(data) {",
                "   console.log({\"Success\": data});",
                "   return response.send(event, context, response.SUCCESS, data, event.PhysicalResourceId);",
                "  }).catch(function(err) {",
                "   console.error(err.stack);",
                "   return response.send(event, context, response.FAILED, {'error': err});",
                "  });",
                " } else return response.send(event, context, response.FAILED, {'error': 'Unknown RequestType'});",
                "};"
              ]
            ]
          }
        },
        "Runtime": {
          "Fn::FindInMap": [
            "Constants",
            "Cft",
            "NodejsRuntime"
          ]
        },
        "Tags": [
          {
            "Key": "datomic:system",
            "Value": {
              "Ref": "SystemName"
            }
          }
        ],
        "Timeout": 300
      }
    },
    "CodeDeployApplication": {
      "Type": "Custom::Resource",
      "DependsOn": [
        "EnsureCodeDeployApplicationLogGroup"
      ],
      "Properties": {
        "ServiceToken": {
          "Fn::GetAtt": [
            "EnsureCodeDeployApplication",
            "Arn"
          ]
        },
        "ApplicationName": {
          "Fn::If": [
            "ApplicationNameProvided",
            {
              "Ref": "ApplicationName"
            },
            {
              "Ref": "AWS::StackName"
            }
          ]
        }
      }
    },
    "EnsureCodeDeployApplicationLogGroup": {
      "Type": "AWS::Logs::LogGroup",
      "DeletionPolicy": "Retain",
      "UpdateReplacePolicy": "Retain",
      "Properties": {
        "LogGroupName": {
          "Fn::Sub": "/aws/lambda/${EnsureCodeDeployApplication}"
        },
        "RetentionInDays": 7
      }
    },
    "HttpDirectListener": {
      "Type": "AWS::ElasticLoadBalancingV2::Listener",
      "Properties": {
        "DefaultActions": [
          {
            "Type": "forward",
            "TargetGroupArn": {
              "Ref": "HttpDirectTargetGroup"
            }
          }
        ],
        "LoadBalancerArn": {
          "Ref": "LoadBalancer"
        },
        "Port": 8184,
        "Protocol": "HTTP"
      }
    },
    "EntryRecordSet": {
      "Type": "AWS::Route53::RecordSet",
      "Properties": {
        "AliasTarget": {
          "DNSName": {
            "Fn::GetAtt": [
              "LoadBalancer",
              "DNSName"
            ]
          },
          "HostedZoneId": {
            "Fn::GetAtt": [
              "LoadBalancer",
              "CanonicalHostedZoneID"
            ]
          }
        },
        "HostedZoneId": {
          "Ref": "HostedZone"
        },
        "Name": {
          "Fn::Sub": "entry.${AWS::StackName}.${AWS::Region}.datomic.net."
        },
        "Type": "A"
      }
    },
    "DeployTagFunctionsPolicy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "tag-functions",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "lambda:TagResource"
              ],
              "Resource": [
                "*"
              ],
              "Sid": "TagFunctions"
            }
          ]
        },
        "Roles": [
          {
            "Ref": "DeployLambdaRole"
          }
        ]
      }
    },
    "DeadLetterQueue": {
      "Type": "AWS::SNS::Topic",
      "Properties": {
        "DisplayName": "Datomic Dead Letter Queue",
        "Tags": [
          {
            "Key": "datomic:system",
            "Value": {
              "Ref": "SystemName"
            }
          }
        ],
        "TopicName": {
          "Fn::Sub": "datomic-${AWS::StackName}-dlq"
        }
      }
    }
  },
  "Outputs": {
    "DeployStateMachine": {
      "Description": "State Machine",
      "Value": {
        "Ref": "DeployStateMachine"
      }
    },
    "IonApiGatewayEndpoint": {
      "Condition": "CreateHttpDirectApiGateway",
      "Description": "Ion API Gateway Endpoint",
      "Value": {
        "Fn::GetAtt": [
          "HttpDirectApiGateway",
          "ApiEndpoint"
        ]
      },
      "Export": {
        "Name": {
          "Fn::Sub": "${AWS::StackName}-IonApiGatewayEndpoint"
        }
      }
    },
    "SystemName": {
      "Description": "System Name",
      "Value": {
        "Ref": "SystemName"
      }
    },
    "NodeSecurityGroup": {
      "Description": "Node security group",
      "Value": {
        "Fn::Select": [
          0,
          {
            "Fn::GetAtt": [
              "ComputeSecurityGroup",
              "ResourceList"
            ]
          }
        ]
      }
    },
    "QueryGroupASG": {
      "Description": "Query Group Auto Scaling Group",
      "Value": {
        "Ref": "QueryGroupAutoScalingGroup"
      }
    },
    "VpcEndpointServiceId": {
      "Description": "VPC Endpoint Service Id",
      "Value": {
        "Fn::GetAtt": [
          "EndpointService",
          "ServiceId"
        ]
      }
    },
    "LambdaLogGroups": {
      "Description": "Lambda Log Groups",
      "Value": {
        "Fn::Join": [
          ",",
          [
            {
              "Ref": "EnsureCodeDeployApplicationLogGroup"
            },
            {
              "Ref": "CreateEndpointServiceLogGroup"
            },
            {
              "Ref": "GetTaggedResourcesLogGroup"
            },
            {
              "Ref": "DeleteLambdasLogGroup"
            },
            {
              "Ref": "DeleteEnisLogGroup"
            },
            {
              "Ref": "DeleteRecordSetsLogGroup"
            }
          ]
        ]
      }
    },
    "LoadBalancerName": {
      "Description": "Load Balancer Name",
      "Export": {
        "Name": {
          "Fn::Sub": "${AWS::StackName}-LoadBalancerName"
        }
      },
      "Value": {
        "Fn::GetAtt": [
          "LoadBalancer",
          "LoadBalancerName"
        ]
      }
    },
    "DeadLetterQueueArn": {
      "Description": "Datomic Dead Letter Queue Arn",
      "Value": {
        "Ref": "DeadLetterQueue"
      }
    },
    "DatomicStratum": {
      "Description": "Datomic Stratum",
      "Value": "query-group"
    },
    "Subnet2": {
      "Description": "Subnet2 Id",
      "Value": {
        "Fn::ImportValue": {
          "Fn::Sub": "${SystemName}-Subnet2"
        }
      }
    },
    "HostedZoneName": {
      "Description": "System's Route53 hosted zone name",
      "Value": {
        "Fn::Sub": "${AWS::StackName}.${AWS::Region}.datomic.net."
      }
    },
    "CodeDeployDeploymentGroup": {
      "Description": "CodeDeploy Deployment Group",
      "Value": {
        "Ref": "CodeDeployDeploymentGroup"
      }
    },
    "HostedZone": {
      "Description": "System's Route53 hosted zone",
      "Value": {
        "Ref": "HostedZone"
      }
    },
    "Subnet0": {
      "Description": "Subnet0 Id",
      "Value": {
        "Fn::ImportValue": {
          "Fn::Sub": "${SystemName}-Subnet0"
        }
      }
    },
    "HttpDirectTargetGroup": {
      "Description": "HTTP Direct Target Group",
      "Value": {
        "Ref": "HttpDirectTargetGroup"
      }
    },
    "QueryGroupDashboardName": {
      "Condition": "CreateDashboard",
      "Description": "Query Group Dashboard Name",
      "Value": {
        "Ref": "Dashboard"
      }
    },
    "DatomicCloudVersion": {
      "Description": "Datomic Cloud Version",
      "Value": "9399"
    },
    "LambdaSecurityGroup": {
      "Description": "Lambda Security Group",
      "Value": {
        "Ref": "LambdaSecurityGroup"
      }
    },
    "LoadBalancerSecurityGroupId": {
      "Description": "Load Balancer Security Group Id",
      "Value": {
        "Ref": "LoadBalancerSecurityGroup"
      },
      "Export": {
        "Name": {
          "Fn::Sub": "${AWS::StackName}-LoadBalancerSecurityGroupId"
        }
      }
    },
    "IonApiGatewayId": {
      "Condition": "CreateHttpDirectApiGateway",
      "Description": "Ion API Gateway Id",
      "Value": {
        "Ref": "HttpDirectApiGateway"
      },
      "Export": {
        "Name": {
          "Fn::Sub": "${AWS::StackName}-IonApiGatewayId"
        }
      }
    },
    "QueryGroupLT": {
      "Description": "Query Group Launch Template",
      "Value": {
        "Ref": "QueryGroupLaunchTemplate"
      }
    },
    "DatomicCodeBucketArn": {
      "Description": "Code Bucket Arn",
      "Value": {
        "Fn::ImportValue": {
          "Fn::Sub": "${SystemName}-CodeBucketArn"
        }
      }
    },
    "DatomicLambdaRoleArn": {
      "Description": "Datomic Lambda Role ARN",
      "Value": {
        "Fn::GetAtt": [
          "DatomicLambdaRole",
          "Arn"
        ]
      }
    },
    "DispatchAddress": {
      "Description": "Dispatch address",
      "Value": {
        "Fn::Sub": "entry.${AWS::StackName}.${AWS::Region}.datomic.net"
      }
    },
    "Listener": {
      "Description": "Listener",
      "Export": {
        "Name": {
          "Fn::Sub": "${AWS::StackName}-Listener"
        }
      },
      "Value": {
        "Ref": "Listener"
      }
    },
    "LoadBalancerHttpDirectEndpoint": {
      "Description": "Load Balancer HTTP Direct Endpoint",
      "Export": {
        "Name": {
          "Fn::Sub": "${AWS::StackName}-LoadBalancerHttpDirectEndpoint"
        }
      },
      "Value": {
        "Fn::Sub": "http://entry-http.${AWS::StackName}.${AWS::Region}.datomic.net:8184"
      }
    },
    "ClientApiGatewayEndpoint": {
      "Condition": "CreateClientApiGateway",
      "Description": "Client API Gateway Endpoint",
      "Value": {
        "Fn::GetAtt": [
          "ClientApiGateway",
          "ApiEndpoint"
        ]
      },
      "Export": {
        "Name": {
          "Fn::Sub": "${AWS::StackName}-ClientApiGatewayEndpoint"
        }
      }
    },
    "DatomicCodeBucketPolicyArn": {
      "Description": "Code Bucket Policy Arn",
      "Value": {
        "Fn::ImportValue": {
          "Fn::Sub": "${SystemName}-CodeBucketPolicyArn"
        }
      }
    },
    "EntryHttpRecordSet": {
      "Description": "Entry HTTP Record Set",
      "Value": {
        "Ref": "EntryHttpRecordSet"
      }
    },
    "LoadBalancer": {
      "Description": "Load Balancer",
      "Export": {
        "Name": {
          "Fn::Sub": "${AWS::StackName}-LoadBalancerArn"
        }
      },
      "Value": {
        "Ref": "LoadBalancer"
      }
    },
    "DatomicCFTVersion": {
      "Description": "Datomic CFT Version",
      "Value": 1217
    },
    "IonApiIntegrationId": {
      "Condition": "CreateHttpDirectApiGateway",
      "Description": "Ion API Gateway Integration Id",
      "Value": {
        "Ref": "HttpDirectApiIntegration"
      }
    },
    "CodeDeployApplicationName": {
      "Description": "CodeDeploy Application Name",
      "Value": {
        "Fn::GetAtt": [
          "CodeDeployApplication",
          "ApplicationName"
        ]
      },
      "Export": {
        "Name": {
          "Fn::Sub": "${AWS::StackName}-ApplicationName"
        }
      }
    },
    "TargetGroup": {
      "Description": "Target Group",
      "Value": {
        "Ref": "TargetGroup"
      }
    },
    "EndpointAddress": {
      "Description": "Stable entry address",
      "Value": {
        "Fn::Sub": "http://entry-http.${AWS::StackName}.${AWS::Region}.datomic.net:8182"
      }
    },
    "Subnet1": {
      "Description": "Subnet1 Id",
      "Value": {
        "Fn::ImportValue": {
          "Fn::Sub": "${SystemName}-Subnet1"
        }
      }
    },
    "LoadBalancerAddress": {
      "Description": "Load balancer DNS name",
      "Value": {
        "Fn::GetAtt": [
          "LoadBalancer",
          "DNSName"
        ]
      }
    },
    "HttpDirectListener": {
      "Description": "HTTP Direct Listener",
      "Export": {
        "Name": {
          "Fn::Sub": "${AWS::StackName}-HttpDirectListener"
        }
      },
      "Value": {
        "Ref": "HttpDirectListener"
      }
    },
    "ClientApiGatewayId": {
      "Condition": "CreateClientApiGateway",
      "Description": "Client API Gateway Id",
      "Value": {
        "Ref": "ClientApiGateway"
      },
      "Export": {
        "Name": {
          "Fn::Sub": "${AWS::StackName}-ClientApiGatewayId"
        }
      }
    },
    "EntryRecordSet": {
      "Description": "Entry Record Set",
      "Value": {
        "Ref": "EntryRecordSet"
      }
    }
  }
}
