{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "ArcGIS CloudFormation Template: Provisions an ArcGIS Notebook Server site on EC2 instances running Ubuntu. **WARNING** You will be billed by AWS for the AWS resources if you create a stack from this template.",
  "Metadata": {
    "AWS::CloudFormation::Interface": {
      "ParameterGroups": [
        {
          "Label": {
            "default": "Network Configuration"
          },
          "Parameters": [
            "VPCId",
            "Subnet",
            "SiteEIPAllocationID",
            "SiteDomain",
            "SSLCertificateFile",
            "SSLCertPassword"
          ]
        },
        {
          "Label": {
            "default": "Amazon EC2 Configuration"
          },
          "Parameters": [
            "BaseAMI",
            "InstanceType",
            "NodeInstances",
            "DriveSizeRoot",
            "KeyName"
          ]
        },
        {
          "Label": {
            "default": "ArcGIS Enterprise Configuration"
          },
          "Parameters": [
            "DeploymentBucket",
            "ServerLicenseFile",
            "SiteAdmin",
            "SiteAdminPassword"
          ]
        }
      ]
    }
  },
  "Parameters": {
    "DeploymentBucket": {
      "Type": "String",
      "Description": "S3 bucket with ArcGIS Server authorization file",
      "AllowedPattern": "^([a-z]|(\\d(?!\\d{0,2}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})))([a-z\\d]|(\\.(?!(\\.|-)))|(-(?!\\.))){1,61}[a-z\\d\\.]$",
      "ConstraintDescription": "A Bucket's name can be between 6 and 63 characters long, containing lowercase characters, numbers, periods, and dashes and it must start with a lowercase letter or number."
    },
    "DriveSizeRoot": {
      "Type": "Number",
      "Default": 100,
      "Description": "Size of root drive in GB.",
      "MinValue": 100,
      "MaxValue": 1024,
      "ConstraintDescription": "Must be between 100 and 1024 GB."
    },
    "InstanceType": {
      "Type": "String",
      "Description": "ArcGIS Notebook Server EC2 instance type",
      "Default": "c5.2xlarge",
      "AllowedValues": [
        "c4.2xlarge",
        "c4.4xlarge",
        "c4.8xlarge",
        "c5.2xlarge",
        "c5.4xlarge",
        "c5.9xlarge",
        "c5.18xlarge",
        "c5.xlarge",
        "c5d.2xlarge",
        "c5d.4xlarge",
        "c5d.9xlarge",
        "c5d.18xlarge",
        "c5n.2xlarge",
        "c5n.4xlarge",
        "c5n.9xlarge",
        "c5n.18xlarge",
        "m4.xlarge",
        "m4.2xlarge",
        "m4.4xlarge",
        "m4.10xlarge",
        "m5.xlarge",
        "m5.2xlarge",
        "m5.4xlarge",
        "m5.12xlarge",
        "m5.24xlarge",
        "m5.metal",
        "m5d.xlarge",
        "m5d.2xlarge",
        "m5d.4xlarge",
        "m5d.12xlarge",
        "m5d.24xlarge",
        "m5d.metal",
        "m5a.xlarge",
        "m5a.2xlarge",
        "m5a.4xlarge",
        "m5a.12xlarge",
        "m5a.24xlarge",
        "m4.xlarge",
        "m4.2xlarge",
        "m4.4xlarge",
        "m4.10xlarge",
        "m4.16xlarge",
        "p3.2xlarge",
        "p3.8xlarge",
        "p3.16xlarge",
        "r4.xlarge",
        "r4.2xlarge",
        "r4.4xlarge",
        "r4.8xlarge",
        "r4.16xlarge",
        "r5.large",
        "r5.xlarge",
        "r5.2xlarge",
        "r5.4xlarge",
        "r5.12xlarge",
        "r5.24xlarge",
        "r5.metal",
        "r5a.large",
        "r5a.xlarge",
        "r5a.2xlarge",
        "r5a.4xlarge",
        "r5a.12xlarge",
        "r5a.24xlarge",
        "t2.xlarge",
        "t2.2xlarge",
        "t3.xlarge",
        "t3.2xlarge",
        "t3a.xlarge",
        "t3a.2xlarge",
        "x1.16xlarge",
        "x1.32xlarge",
        "x1e.xlarge",
        "x1e.2xlarge",
        "x1e.4xlarge",
        "x1e.8xlarge",
        "x1e.16xlarge",
        "x1e.32xlarge"
      ]
    },
    "AdditionalInstances": {
      "Type": "Number",
      "Default": 0,
      "Description": "Number of additional EC2 instances to join ArcGIS Notebook Server site"
    },
    "KeyName": {
      "Type": "AWS::EC2::KeyPair::KeyName",
      "Description": "EC2 Key Pair to allow RemoteDesktop access to the instances"
    },
    "ServerLicenseFile": {
      "Type": "String",
      "Description": "ArcGIS Server authorization file (must be uploaded to DeploymentBucket)",
      "AllowedPattern": "^([/\\w\\-\\.]+)+\\.(ecp|prvc)$",
      "ConstraintDescription": "License file name must be alphanumeric. It can contain dash ('-'), dot ('.'), and underscore ('_') characters. The file name must end with '.ecp' or '.prvc'."
    },
    "SiteAdmin": {
      "Type": "String",
      "Default": "siteadmin",
      "Description": "User name of ArcGIS Notebook Server primary site administrator",
      "AllowedPattern": "^[a-zA-Z][a-zA-Z0-9_]{6,}$",
      "ConstraintDescription": "User name must be 6 or more alphanumeric or underscore (_) characters and must start with a letter."
    },
    "SiteAdminPassword": {
      "Type": "String",
      "Description": "Password of ArcGIS Notebook Server primary site administrator",
      "NoEcho": true,
      "AllowedPattern": "^[a-zA-Z0-9_\\.@]{8,}$",
      "ConstraintDescription": "Password must be 8 or more alphanumeric, underscore (_), at ('@'), or dot (.) characters."
    },
    "SSLCertificateFile": {
      "Type": "String",
      "Description": "SSL certificate file issued to the site domain (must be uploaded to DeploymentBucket)",
      "AllowedPattern": "^([/\\w\\-\\.]+)+\\.(pfx)$",
      "ConstraintDescription": "Certificate file name must be alphanumeric. It can contain slash ('/'), dash ('-'), dot ('.'), and underscore ('_') characters. The file name must end with '.pfx'"
    },
    "SSLCertPassword": {
      "Type": "String",
      "Description": "SSL certificate file password",
      "NoEcho": true,
      "AllowedPattern": "[^\\\"]{1,128}",
      "ConstraintDescription": "Password must be between 1 and 128 characters and must not contain backslashes (\\) or quotation marks (\")."
    },
    "VPCId": {
      "Type": "AWS::EC2::VPC::Id",
      "Description": "VPC ID"
    },
    "Subnet": {
      "Type": "AWS::EC2::Subnet::Id",
      "Description": "VPC Subnet"
    },
    "SiteEIPAllocationID": {
      "Type": "String",
      "Default": "",
      "Description": "Allocation ID of Elastic IP address for VPC (eipalloc-XXXXXXXX)",
      "AllowedPattern": "^$|eipalloc-.*"
    },
    "SiteDomain": {
      "Type": "String",
      "Description": "Domain name of ArcGIS Notebook Server site",
      "AllowedPattern": "^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9-]*[a-zA-Z0-9]).)*([a-zA-Z]|[a-zA-Z][a-zA-Z0-9-]*[a-zA-Z0-9])$",
      "ConstraintDescription": "The domain name is invalid."
    },
    "BaseAMI": {
      "Type": "String",
      "Description": "(Optional) Base AMI Id derived from Canonical Ubuntu 18.04 LTS amd64 image",
      "Default": ""
    },
    "PostInstallationScript": {
      "Type": "String",
      "Default": "none",
      "Description": "(Optional) ZIP archive file with custom post installation script",
      "AllowedPattern": "[^\"]{1,1024}",
      "ConstraintDescription": "S3 object key name must be between 1 and 1024 characters."
    }
  },
  "Mappings": {
    "Repositories": {
      "ap-south-1": {
        "bucket": "arcgisstore108-ap-south-1",
        "region": "ap-south-1"
      },
      "eu-north-1": {
        "bucket": "arcgisstore108-eu-north-1",
        "region": "eu-north-1"
      },
      "eu-west-3": {
        "bucket": "arcgisstore108-eu-west-3",
        "region": "eu-west-3"
      },
      "eu-west-2": {
        "bucket": "arcgisstore108-eu-west-2",
        "region": "eu-west-2"
      },
      "eu-west-1": {
        "bucket": "arcgisstore108-eu-west-1",
        "region": "eu-west-1"
      },
      "ap-northeast-2": {
        "bucket": "arcgisstore108-ap-northeast-2",
        "region": "ap-northeast-2"
      },
      "ap-northeast-1": {
        "bucket": "arcgisstore108-ap-northeast-1",
        "region": "ap-northeast-1"
      },
      "sa-east-1": {
        "bucket": "arcgisstore108-sa-east-1",
        "region": "sa-east-1"
      },
      "ca-central-1": {
        "bucket": "arcgisstore108-ca-central-1",
        "region": "ca-central-1"
      },
      "ap-southeast-1": {
        "bucket": "arcgisstore108-ap-southeast-1",
        "region": "ap-southeast-1"
      },
      "ap-southeast-2": {
        "bucket": "arcgisstore108-ap-southeast-2",
        "region": "ap-southeast-2"
      },
      "eu-central-1": {
        "bucket": "arcgisstore108-eu-central-1",
        "region": "eu-central-1"
      },
      "us-east-1": {
        "bucket": "arcgisstore108-us-east-1",
        "region": "us-east-1"
      },
      "us-east-2": {
        "bucket": "arcgisstore108-us-east-2",
        "region": "us-east-2"
      },
      "us-west-1": {
        "bucket": "arcgisstore108-us-west-1",
        "region": "us-west-1"
      },
      "us-west-2": {
        "bucket": "arcgisstore108-us-west-2",
        "region": "us-west-2"
      },
      "us-gov-east-1": {
        "bucket": "arcgisstore108-us-east-1",
        "region": "us-east-1"
      },
      "us-gov-west-1": {
        "bucket": "arcgisstore108-us-west-1",
        "region": "us-west-1"
      }
    },
    "RegionMap": {
      "ap-east-1": {
        "en": "ami-eca3d89d"
      },
      "ap-northeast-1": {
        "en": "ami-0eeb679d57500a06c"
      },
      "ap-south-1": {
        "en": "ami-009110a2bf8d7dd0a"
      },
      "ap-southeast-1": {
        "en": "ami-03b6f27628a4569c8"
      },
      "ca-central-1": {
        "en": "ami-0d0eaed20348a3389"
      },
      "eu-central-1": {
        "en": "ami-0ac05733838eabc06"
      },
      "eu-north-1": {
        "en": "ami-ada823d3"
      },
      "eu-west-1": {
        "en": "ami-06358f49b5839867c"
      },
      "me-south-1": {
        "en": "ami-0f9f3c6ba0d6bb9e5"
      },
      "sa-east-1": {
        "en": "ami-02a3447be1ec3a38f"
      },
      "us-east-1": {
        "en": "ami-07d0cf3af28718ef8"
      },
      "us-west-1": {
        "en": "ami-08fd8ae3806f09a08"
      },
      "cn-north-1": {
        "en": "ami-01993b4213b4bffb5"
      },
      "cn-northwest-1": {
        "en": "ami-01d4e30d4d4952d0f"
      },
      "us-gov-west-1": {
        "en": "ami-b987c3d8"
      },
      "us-gov-east-1": {
        "en": "ami-cd35d4bc"
      },
      "ap-northeast-2": {
        "en": "ami-0fd02cb7da42ee5e0"
      },
      "ap-southeast-2": {
        "en": "ami-0edcec072887c2caa"
      },
      "eu-west-2": {
        "en": "ami-077a5b1762a2dde35"
      },
      "us-east-2": {
        "en": "ami-05c1fa8df71875112"
      },
      "us-west-2": {
        "en": "ami-06f2f779464715dc5"
      },
      "ap-northeast-3": {
        "en": "ami-07327bfff5b81d70a"
      },
      "eu-west-3": {
        "en": "ami-0ad37dbbe571ce2a1"
      }
    }
  },
  "Conditions": {
    "CustomAMI": {
      "Fn::Not": [
        {
          "Fn::Equals": [
            {
              "Ref": "BaseAMI"
            },
            ""
          ]
        }
      ]
    },
    "AttachEIP": {
      "Fn::Not": [
        {
          "Fn::Equals": [
            {
              "Ref": "SiteEIPAllocationID"
            },
            ""
          ]
        }
      ]
    }
  },
  "Resources": {
    "SiteAdminPasswordSecret": {
      "Type": "AWS::SecretsManager::Secret",
      "Properties": {
        "Description": "Password of ArcGIS Notebook Server primary site administrator",
        "SecretString": {
          "Ref": "SiteAdminPassword"
        }
      }
    },
    "SSLCertPasswordSecret": {
      "Type": "AWS::SecretsManager::Secret",
      "Properties": {
        "Description": "SSL certificate file password",
        "SecretString": {
          "Ref": "SSLCertPassword"
        }
      }
    },
    "StopStackFunction": {
      "Type": "AWS::Lambda::Function",
      "DependsOn": "IAMRole",
      "Properties": {
        "Code": {
          "S3Bucket": {"Fn::Join" : ["", ["arcgisstore108", "-", {"Ref": "AWS::Region"}]]},
          "S3Key": "12790/lambda/arcgis-cfn-lambda-python3.zip"
        },
        "Environment" : {
          "Variables" : {
            "StackName" : {"Ref" : "AWS::StackName"}
          }
        }, 
        "Handler": "stop_start.stop_notebook_server_stack",
        "Runtime": "python3.8",
        "Timeout": "300",
        "Role": {"Fn::GetAtt" : ["LambdaExecutionRole", "Arn"]},
        "Description" : "Stops all EC2 instances of the CloudFormation stack"
      }
    },
    "StartStackFunction": {
      "Type": "AWS::Lambda::Function",
      "DependsOn": "IAMRole",
      "Properties": {
        "Code": {
          "S3Bucket": {"Fn::Join" : ["", ["arcgisstore108", "-", {"Ref": "AWS::Region"}]]},
          "S3Key": "12790/lambda/arcgis-cfn-lambda-python3.zip"
        },
        "Environment" : {
          "Variables" : {
            "StackName" : {"Ref" : "AWS::StackName"}
          }
        }, 
        "Handler": "stop_start.start_notebook_server_stack",
        "Runtime": "python3.8",
        "Timeout": "300",
        "Role": {"Fn::GetAtt" : ["LambdaExecutionRole", "Arn"]},
        "Description" : "Starts all EC2 instances of the CloudFormation stack"
      }
    },
    "LambdaExecutionRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [{
            "Effect": "Allow",
            "Principal": {"Service": ["lambda.amazonaws.com"]},
            "Action": ["sts:AssumeRole"]
          }]
        },
        "Path": "/",
        "Policies": [{
          "PolicyName": "root",
          "PolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [{
              "Effect": "Allow",
              "Action": ["logs:CreateLogGroup","logs:CreateLogStream","logs:PutLogEvents"],
              "Resource": "*"
            },
            {
              "Effect": "Allow",
              "Action": ["s3:*"],
              "Resource": "*"
            },
            {
              "Effect": "Allow",
              "Action": ["ec2:*"],
              "Resource": "*"
            },
            {
              "Effect": "Allow",
              "Action": ["cloudformation:*"],
              "Resource": "*"
            },
            {
              "Effect": "Allow",
              "Action": ["autoscaling:*"],
              "Resource": "*"
            }]
          }
        }]
      }
    },
    "IAMRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "Service": [
                  "ec2.amazonaws.com"
                ]
              },
              "Action": [
                "sts:AssumeRole"
              ]
            }
          ]
        },
        "Path": "/"
      }
    },
    "IAMPolicy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "ArcGISNotebookServer",
        "PolicyDocument": {
          "Statement": [
            {
              "Action": [
                "logs:*"
              ],
              "Effect": "Allow",
              "Resource": "*"
            },
            {
              "Action": [
                "ssm:DescribeAssociation",
                "ssm:GetDeployablePatchSnapshotForInstance",
                "ssm:GetDocument",
                "ssm:DescribeDocument",
                "ssm:GetManifest",
                "ssm:GetParameter",
                "ssm:GetParameters",
                "ssm:ListAssociations",
                "ssm:ListInstanceAssociations",
                "ssm:PutInventory",
                "ssm:PutComplianceItems",
                "ssm:PutConfigurePackageResult",
                "ssm:UpdateAssociationStatus",
                "ssm:UpdateInstanceAssociationStatus",
                "ssm:UpdateInstanceInformation"
              ],
              "Effect": "Allow",
              "Resource": "*"
            },
            {
              "Action": [
                "ec2messages:AcknowledgeMessage",
                "ec2messages:DeleteMessage",
                "ec2messages:FailMessage",
                "ec2messages:GetEndpoint",
                "ec2messages:GetMessages",
                "ec2messages:SendReply"
              ],
              "Effect": "Allow",
              "Resource": "*"
            },
            {
              "Action": [
                "ssmmessages:CreateControlChannel",
                "ssmmessages:CreateDataChannel",
                "ssmmessages:OpenControlChannel",
                "ssmmessages:OpenDataChannel"
              ],
              "Effect": "Allow",
              "Resource": "*"
            },
            {
              "Action": [
                "s3:*"
              ],
              "Effect": "Allow",
              "Resource": [
                {
                  "Fn::Sub": "arn:${AWS::Partition}:s3:::${DeploymentBucket}/*"
                },
                {
                  "Fn::Sub": "arn:${AWS::Partition}:s3:::${DeploymentBucket}"
                }
              ]
            },
            {
              "Action": [
                "s3:*"
              ],
              "Effect": "Allow",
              "Resource": [
                {
                  "Fn::Sub": [
                    "arn:${AWS::Partition}:s3:::${bucket}/*",
                    {
                      "bucket": {
                        "Fn::FindInMap": [
                          "Repositories",
                          {
                            "Ref": "AWS::Region"
                          },
                          "bucket"
                        ]
                      }
                    }
                  ]
                },
                {
                  "Fn::Sub": [
                    "arn:${AWS::Partition}:s3:::${bucket}",
                    {
                      "bucket": {
                        "Fn::FindInMap": [
                          "Repositories",
                          {
                            "Ref": "AWS::Region"
                          },
                          "bucket"
                        ]
                      }
                    }
                  ]
                }
              ]
            },
            {
              "Action": [
                "secretsmanager:GetSecretValue"
              ],
              "Effect": "Allow",
              "Resource": [
                {
                  "Ref": "SiteAdminPasswordSecret"
                },
                {
                  "Ref": "SSLCertPasswordSecret"
                }
              ]
            }
          ]
        },
        "Roles": [
          {
            "Ref": "IAMRole"
          }
        ]
      }
    },
    "IAMInstanceProfile": {
      "Type": "AWS::IAM::InstanceProfile",
      "Properties": {
        "Path": "/",
        "Roles": [
          {
            "Ref": "IAMRole"
          }
        ]
      }
    },
    "SecurityGroup": {
      "Type": "AWS::EC2::SecurityGroup",
      "Properties": {
        "GroupDescription": "Allow HTTP and HTTPS for all client IPs",
        "VpcId": {
          "Ref": "VPCId"
        },
        "SecurityGroupIngress": [
          {
            "IpProtocol": "tcp",
            "FromPort": 80,
            "ToPort": 80,
            "CidrIp": "0.0.0.0/0"
          },
          {
            "IpProtocol": "tcp",
            "FromPort": 443,
            "ToPort": 443,
            "CidrIp": "0.0.0.0/0"
          }
        ]
      }
    },
    "SecurityGroupIngress": {
      "Type": "AWS::EC2::SecurityGroupIngress",
      "Properties": {
        "GroupId": {
          "Ref": "SecurityGroup"
        },
        "IpProtocol": "tcp",
        "FromPort": 0,
        "ToPort": 65535,
        "SourceSecurityGroupId": {
          "Ref": "SecurityGroup"
        }
      }
    },
    "PrimaryEC2Instance": {
      "Type": "AWS::EC2::Instance",
      "Properties": {
        "IamInstanceProfile": {
          "Ref": "IAMInstanceProfile"
        },
        "ImageId": {
          "Fn::If": [
            "CustomAMI",
            {
              "Ref": "BaseAMI"
            },
            {
              "Fn::FindInMap": [
                "RegionMap",
                {
                  "Ref": "AWS::Region"
                },
                "en"
              ]
            }
          ]
        },
        "InstanceType": {
          "Ref": "InstanceType"
        },
        "BlockDeviceMappings": [
          {
            "DeviceName": "/dev/sda1",
            "Ebs": {
              "VolumeSize": {
                "Ref": "DriveSizeRoot"
              }
            }
          }
        ],
        "KeyName": {
          "Ref": "KeyName"
        },
        "Monitoring": true,
        "NetworkInterfaces": [
          {
            "AssociatePublicIpAddress": true,
            "DeleteOnTermination": true,
            "DeviceIndex": "0",
            "GroupSet": [
              {
                "Ref": "SecurityGroup"
              }
            ],
            "SubnetId": {
              "Ref": "Subnet"
            }
          }
        ],
        "Tags": [
          {
            "Key": "Name",
            "Value": {
              "Fn::Sub": "${AWS::StackName}-primary"
            }
          },
          {
            "Key": "arcgis:role",
            "Value": "notebook-server-primary"
          }
        ]
      }
    },
    "EIPAssociation": {
      "Type": "AWS::EC2::EIPAssociation",
      "Condition": "AttachEIP",
      "Properties": {
        "AllocationId": {
          "Ref": "SiteEIPAllocationID"
        },
        "InstanceId": {
          "Ref": "PrimaryEC2Instance"
        }
      }
    },
    "PrimaryInstanceRecoveryAlarm": {
      "Type": "AWS::CloudWatch::Alarm",
      "Properties": {
        "AlarmDescription": "Trigger a recovery when instance status check fails for 5 consecutive minutes.",
        "MetricName": "StatusCheckFailed_System",
        "Namespace": "AWS/EC2",
        "Statistic": "Minimum",
        "Period": 60,
        "EvaluationPeriods": 5,
        "Threshold": 0,
        "ComparisonOperator": "GreaterThanThreshold",
        "AlarmActions": [
          {
            "Fn::Sub": "arn:${AWS::Partition}:automate:${AWS::Region}:ec2:recover"
          }
        ],
        "Dimensions": [
          {
            "Name": "InstanceId",
            "Value": {
              "Ref": "PrimaryEC2Instance"
            }
          }
        ]
      }
    },
    "LaunchConfig": {
      "Type": "AWS::AutoScaling::LaunchConfiguration",
      "Properties": {
        "IamInstanceProfile": {
          "Ref": "IAMInstanceProfile"
        },
        "ImageId": {
          "Fn::If": [
            "CustomAMI",
            {
              "Ref": "BaseAMI"
            },
            {
              "Fn::FindInMap": [
                "RegionMap",
                {
                  "Ref": "AWS::Region"
                },
                "en"
              ]
            }
          ]
        },
        "InstanceType": {
          "Ref": "InstanceType"
        },
        "BlockDeviceMappings": [
          {
            "DeviceName": "/dev/sda1",
            "Ebs": {
              "VolumeSize": {
                "Ref": "DriveSizeRoot"
              }
            }
          }
        ],
        "KeyName": {
          "Ref": "KeyName"
        },
        "AssociatePublicIpAddress": true,
        "SecurityGroups": [
          {
            "Ref": "SecurityGroup"
          }
        ]
      }
    },
    "AutoScalingGroup": {
      "Type": "AWS::AutoScaling::AutoScalingGroup",
      "Properties": {
        "VPCZoneIdentifier": [
          {
            "Ref": "Subnet"
          }
        ],
        "Cooldown": "300",
        "MaxSize": {
          "Ref": "AdditionalInstances"
        },
        "MinSize": {
          "Ref": "AdditionalInstances"
        },
        "LaunchConfigurationName": {
          "Ref": "LaunchConfig"
        },
        "HealthCheckType": "EC2",
        "HealthCheckGracePeriod": 3600,
        "Tags": [
          {
            "Key": "Name",
            "Value": {
              "Fn::Sub": "${AWS::StackName}-node"
            },
            "PropagateAtLaunch": true
          },
          {
            "Key": "arcgis:role",
            "Value": "notebook-server-node",
            "PropagateAtLaunch": true
          }
        ]
      },
      "UpdatePolicy": {
        "AutoScalingReplacingUpdate": {
          "WillReplace": true
        }
      }
    },
    "InstallNotebookServerCommand": {
      "Type": "AWS::SSM::Document",
      "Properties": {
        "DocumentType": "Command",
        "Content": {
          "schemaVersion": "2.2",
          "description": "Installs and configures ArcGIS Notebook Server",
          "parameters": {
            "chefClientUrl": {
              "type": "String",
              "default": {
                "Fn::Sub": [
                  "s3://${bucket}/chefclient/chef_14.12.9-1_amd64.deb",
                  {
                    "bucket": {
                      "Fn::FindInMap": [
                        "Repositories",
                        {
                          "Ref": "AWS::Region"
                        },
                        "bucket"
                      ]
                    }
                  }
                ]
              },
              "description": "S3 URL of Chef Client package"
            },
            "cookbooksUrl": {
              "type": "String",
              "default": "https://arcgisstore108.s3.amazonaws.com/12790/cookbooks/arcgis-3.5.0-cookbooks.tar.gz",
              "description": "ArcGIS Chef cookbooks URL"
            },
            "region": {
              "type": "String",
              "default": {
                "Ref": "AWS::Region"
              },
              "description": "AWS region"
            },
            "arcgisVersion": {
              "type": "String",
              "default": "10.8",
              "description": "ArcGIS Notebook Server version"
            },
            "machineRole": {
              "type": "String",
              "default": "notebook-server-primary",
              "description": "ArcGIS Notebook machine role",
              "allowedValues": [
                "notebook-server-primary",
                "notebook-server-node"
              ]
            },
            "waitConditionHandle": {
              "type": "String",
              "default": "",
              "description": "CloudFormation wait condition handle"
            },
            "repositoryBucket": {
              "type": "String",
              "default": {
                "Fn::FindInMap": [
                  "Repositories",
                  {
                    "Ref": "AWS::Region"
                  },
                  "bucket"
                ]
              },
              "description": "S3 bucket of ArcGIS software repository"
            },
            "deploymentBucket": {
              "type": "String",
              "default": {
                "Ref": "DeploymentBucket"
              },
              "description": "S3 bucket with authorization files and SSL certificates"
            },
            "licenseFile": {
              "type": "String",
              "default": {
                "Ref": "ServerLicenseFile"
              },
              "description": "S3 key of ArcGIS Notebook Server software authorization file"
            },
            "licenseLevel": {
              "type": "String",
              "default": "advanced",
              "description": "ArcGIS Notebook Server license level",
              "allowedValues": [
                "standard",
                "advanced"
              ]
            },
            "certificateFile": {
              "type": "String",
              "default": {
                "Ref": "SSLCertificateFile"
              },
              "description": "S3 key of SSL certificate file in PKSC12 format"
            },
            "certificatePasswordSecretId": {
              "type": "String",
              "description": "Secret ID of SSL certificate file password"
            },
            "siteAdmin": {
              "type": "String",
              "default": "admin",
              "description": "ArcGIS Notebook Server primary site administrator username"
            },
            "siteAdminPasswordSecretId": {
              "type": "String",
              "description": "Secret ID of ArcGIS Notebook Server primary site administrator password"
            },
            "primaryServerIp": {
              "type": "String",
              "default": "",
              "description": "IP address of primary ArcGIS Notebook Server machine (required for notebook-server-node machine role)"
            }
          },
          "mainSteps": [
            {
              "action": "aws:runShellScript",
              "name": "installNotebookServer",
              "inputs": {
                "runCommand": [
                  "#!/bin/bash",
                  "signal_if_failed()",
                  "{",
                  "if [ $1 -ne 0 ]; then",
                  "echo $2",
                  "if [ ! -z \"{{waitConditionHandle}}\" ]; then",
                  "/opt/aws/bin/cfn-signal -e $1 -r \"$2\" \"{{waitConditionHandle}}\"",
                  "fi",
                  "exit $1",
                  "fi",
                  "}",
                  "echo 'Install python, AWS CLI, jq, autofs, and CloudFormation Helper Scripts'",
                  "apt-get update -y",
                  "apt-get install -y python3-pip python-setuptools jq autofs",
                  "pip3 install awscli --upgrade --user",
                  "echo 'export HOME=/root' >> ~/.profile",
                  "echo 'export PATH=~/.local/bin:$PATH' >> ~/.profile",
                  ". ~/.profile",
                  "aws --version",
                  "sed -i 's/#\\/net/\\/net/g' /etc/auto.master",
                  "service autofs reload",
                  "mkdir -p /opt/aws/bin",
                  "wget https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-latest.tar.gz",
                  "python /usr/lib/python2.7/dist-packages/easy_install.py --script-dir /opt/aws/bin aws-cfn-bootstrap-latest.tar.gz",
                  "echo 'Install Chef Client'",
                  "aws s3 cp {{chefClientUrl}} /tmp/chef-client.deb",
                  "sudo dpkg -i /tmp/chef-client.deb",
                  "signal_if_failed $? \"Chef installation failed.\"",
                  "rm /tmp/chef-client.deb",
                  "echo 'Install ArcGIS Chef cookbooks'",
                  "wget -c {{cookbooksUrl}} -O - | sudo tar -xz -C /opt/chef",
                  "signal_if_failed $? \"ArcGIS Chef cookbooks installation failed.\"",
                  "echo 'Download ArcGIS Notebook Server license and SSL certificate from S3.'",
                  ". ~/.profile",
                  "licenseFile='/opt/software/esri/{{licenseFile}}'",
                  "aws s3 cp s3://{{deploymentBucket}}/{{licenseFile}} $licenseFile",
                  "signal_if_failed $? \"Failed to get object '{{licenseFile}}' from S3 bucket '{{deploymentBucket}}'.\"",
                  "certificateFile='/tomcat_arcgis/conf/{{certificateFile}}'",
                  "aws s3 cp s3://{{deploymentBucket}}/{{certificateFile}} $certificateFile",
                  "signal_if_failed $? \"Failed to get object '{{certificateFile}}' from S3 bucket '{{deploymentBucket}}'.\"",
                  "echo 'Get secrets from Secrets Manager.'",
                  "siteAdminPassword=$(aws secretsmanager get-secret-value --secret-id {{siteAdminPasswordSecretId}} --region {{region}} | jq --raw-output '.SecretString')",
                  "signal_if_failed $(expr \"$siteAdminPassword\" == \"\") \"Failed to get password of ArcGIS Notebook Server primary site administrator from AWS Secrets Manager.\"",
                  "certificatePassword=$(aws secretsmanager get-secret-value --secret-id {{certificatePasswordSecretId}} --region {{region}} | jq --raw-output '.SecretString')",
                  "signal_if_failed $(expr \"$certificatePassword\" == \"\") \"Failed to get SSL certificate file password from AWS Secrets Manager.\"",
                  "templatejson=\"/opt/chef/templates/arcgis-notebook-server/{{arcgisVersion}}/ubuntu/{{machineRole}}.json\"",
                  "test -e $templatejson",
                  "signal_if_failed $? \"Chef deployment template '$templatejson' not found.\"",
                  "nodejson='/opt/chef/notebook-server-primary.json'",
                  "echo 'Replace attribute values in the Chef deployment template'",
                  "cat $templatejson | \\",
                  "jq \".tomcat.keystore_file=\\\"${certificateFile}\\\"\" | \\",
                  "jq \".tomcat.keystore_password=\\\"${certificatePassword}\\\"\" | \\",
                  "jq \".arcgis.repository.server.s3bucket=\\\"{{repositoryBucket}}\\\"\" | \\",
                  "jq \".arcgis.hosts.primary=\\\"{{primaryServerIp}}\\\"\" | \\",
                  "jq \".arcgis.notebook_server.authorization_file=\\\"${licenseFile}\\\"\" | \\",
                  "jq \".arcgis.notebook_server.license_level=\\\"{{licenseLevel}}\\\"\" | \\",
                  "jq \".arcgis.notebook_server.admin_username=\\\"{{siteAdmin}}\\\"\" | \\",
                  "jq \".arcgis.notebook_server.admin_password=\\\"${siteAdminPassword}\\\"\" > $nodejson",
                  "echo 'Run Chef.'",
                  "/opt/chef/bin/chef-client -z -F min --force-formatter --no-color -j $nodejson --config-option \"cookbook_path=/opt/chef/cookbooks\"",
                  "exitcode=$?",
                  "echo 'Reject Docker containers access to EC2 instance metadata IP address.'",
                  "sudo iptables --insert DOCKER-ISOLATION-STAGE-1 --destination 169.254.169.254 --jump REJECT --verbose",
                  "echo 'Allow SSH for arcgis user.'",
                  "mkdir /home/arcgis/.ssh/",
                  "chown arcgis:root /home/arcgis/.ssh/",
                  "cp /home/ubuntu/.ssh/authorized_keys /home/arcgis/.ssh/",
                  "chown arcgis:root /home/arcgis/.ssh/authorized_keys",
                  "chmod 440 /home/arcgis/.ssh/authorized_keys",
                  "echo 'Shred sensitive data used by the Chef run.'",
                  "shred -vzu $nodejson",
                  "find /opt/chef/nodes -type f -exec shred -vzu {} \\;",
                  "signal_if_failed $exitcode 'ArcGIS Notebook Server installation failed.'",
                  "if [ ! -z \"{{waitConditionHandle}}\" ]; then",
                  "/opt/aws/bin/cfn-signal -e 0 -r 'ArcGIS Notebook Server installation complete.' '{{waitConditionHandle}}'",
                  "fi"
                ]
              }
            }
          ]
        }
      }
    },
    "InstallNotebookServerPrimary": {
      "Type": "AWS::SSM::Association",
      "Properties": {
        "Name": {
          "Ref": "InstallNotebookServerCommand"
        },
        "Parameters": {
          "machineRole": [
            "notebook-server-primary"
          ],
          "waitConditionHandle": [
            {
              "Ref": "InstallNotebookServerPrimaryWaitHandle"
            }
          ],
          "deploymentBucket": [
            {
              "Ref": "DeploymentBucket"
            }
          ],
          "licenseFile": [
            {
              "Ref": "ServerLicenseFile"
            }
          ],
          "certificateFile": [
            {
              "Ref": "SSLCertificateFile"
            }
          ],
          "certificatePasswordSecretId": [
            {
              "Ref": "SSLCertPasswordSecret"
            }
          ],
          "siteAdmin": [
            {
              "Ref": "SiteAdmin"
            }
          ],
          "siteAdminPasswordSecretId": [
            {
              "Ref": "SiteAdminPasswordSecret"
            }
          ]
        },
        "Targets": [
          {
            "Key": "tag:aws:cloudformation:stack-name",
            "Values": [
              {
                "Ref": "AWS::StackName"
              }
            ]
          },
          {
            "Key": "tag:arcgis:role",
            "Values": [
              "notebook-server-primary"
            ]
          }
        ],
        "OutputLocation": {
          "S3Location": {
            "OutputS3BucketName": {
              "Ref": "DeploymentBucket"
            },
            "OutputS3KeyPrefix": "run-command/outputs/"
          }
        }
      }
    },
    "InstallNotebookServerPrimaryWaitHandle": {
      "Type": "AWS::CloudFormation::WaitConditionHandle"
    },
    "InstallNotebookServerPrimaryWaitCondition": {
      "Type": "AWS::CloudFormation::WaitCondition",
      "Properties": {
        "Count": 1,
        "Handle": {
          "Ref": "InstallNotebookServerPrimaryWaitHandle"
        },
        "Timeout": "3600"
      }
    },
    "InstallNotebookServerNode": {
      "Type": "AWS::SSM::Association",
      "DependsOn": [
        "InstallNotebookServerPrimaryWaitCondition"
      ],
      "Properties": {
        "Name": {
          "Ref": "InstallNotebookServerCommand"
        },
        "Parameters": {
          "machineRole": [
            "notebook-server-node"
          ],
          "waitConditionHandle": [
            {
              "Ref": "InstallNotebookServerNodeWaitHandle"
            }
          ],
          "deploymentBucket": [
            {
              "Ref": "DeploymentBucket"
            }
          ],
          "licenseFile": [
            {
              "Ref": "ServerLicenseFile"
            }
          ],
          "certificateFile": [
            {
              "Ref": "SSLCertificateFile"
            }
          ],
          "certificatePasswordSecretId": [
            {
              "Ref": "SSLCertPasswordSecret"
            }
          ],
          "siteAdmin": [
            {
              "Ref": "SiteAdmin"
            }
          ],
          "siteAdminPasswordSecretId": [
            {
              "Ref": "SiteAdminPasswordSecret"
            }
          ],
          "primaryServerIp": [
            {
              "Fn::GetAtt": [
                "PrimaryEC2Instance",
                "PrivateIp"
              ]
            }
          ]
        },
        "Targets": [
          {
            "Key": "tag:aws:cloudformation:stack-name",
            "Values": [
              {
                "Ref": "AWS::StackName"
              }
            ]
          },
          {
            "Key": "tag:arcgis:role",
            "Values": [
              "notebook-server-node"
            ]
          }
        ],
        "OutputLocation": {
          "S3Location": {
            "OutputS3BucketName": {
              "Ref": "DeploymentBucket"
            },
            "OutputS3KeyPrefix": "run-command/outputs/"
          }
        }
      }
    },
    "InstallNotebookServerNodeWaitHandle": {
      "Type": "AWS::CloudFormation::WaitConditionHandle"
    },
    "InstallNotebookServerNodeWaitCondition": {
      "Type": "AWS::CloudFormation::WaitCondition",
      "Properties": {
        "Count": {
          "Ref": "AdditionalInstances"
        },
        "Handle": {
          "Ref": "InstallNotebookServerNodeWaitHandle"
        },
        "Timeout": "7200"
      }
    }
  },
  "Outputs": {
    "ManagerURL": {
      "Description": "ArcGIS Notebook Server Manager URL",
      "Value": {
        "Fn::Sub": "https://${SiteDomain}/notebooks/admin/"
      }
    },
    "RunCommandHistory": {
      "Description": "AWS Systems Manager Run Command history",
      "Value": {
        "Fn::Sub": "https://console.aws.amazon.com/systems-manager/run-command/complete-commands?region=${AWS::Region}"
      }
    },
    "StopStackFunction": {
      "Value" : {"Fn::Join": ["", [ "https://console.aws.amazon.com/lambda/home?region=", {"Ref": "AWS::Region"}, "#/functions/", {"Ref": "StopStackFunction"} ] ]},
      "Description" : "Lambda function used to stop all EC2 instances in the stack."
    },
    "StartStackFunction": {
      "Value" : {"Fn::Join": ["", [ "https://console.aws.amazon.com/lambda/home?region=", {"Ref": "AWS::Region"}, "#/functions/", {"Ref": "StartStackFunction"} ] ]},
      "Description" : "Lambda function used to start all EC2 instances in the stack."
    }
  }
}