AWSTemplateFormatVersion: 2010-09-09
Parameters:
  DatabaseName:
    Description: The name of the first database to be created when the cluster is created
    Type: String
    Default: dev
    AllowedPattern: '([a-z]|[0-9])+'
  NumberOfNodes:
    Description: >-
      The number of compute nodes in the cluster. For multi-node clusters, the
      NumberOfNodes parameter must be greater than 1
    Type: Number
    MinValue: 2
    Default: 2
  NodeType:
    Description: The type of node to be provisioned
    Type: String
    Default: ra3.4xlarge
    AllowedValues:
      - ra3.xlplus
      - ra3.4xlarge
      - ra3.16xlarge
  MasterUsername:
    Description: >-
      The user name that is associated with the master user account for the
      cluster that is being created
    Type: String
    Default: awsuser
    AllowedPattern: '([a-z])([a-z]|[0-9])*'
  MasterUserPassword:
    Description: >-
      The password that is associated with the master user account for the
      cluster that is being created. Default is Awsuser123
    Type: String
    Default: Awsuser123
    NoEcho: 'true'
  InboundTraffic:
    Description: >-
      The IP address CIDR range (x.x.x.x/x) to connect from your local machine. 
      FYI, get your address using http://www.whatismyip.com.
    Type: String
    MinLength: '9'
    MaxLength: '18'
    Default: 0.0.0.0/0
    AllowedPattern: '(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})'
    ConstraintDescription: Must be a valid CIDR range of the form x.x.x.x/x.
  PortNumber:
    Description: The port number on which the cluster accepts incoming connections.
    Type: Number
    Default: '5439'
Metadata:
  'AWS::CloudFormation::Interface':
    ParameterGroups:
      - Label:
          default: Connection Details
        Parameters:
          - NodeType
          - NumberOfNodes
          - MasterUsername
          - MasterUserPassword
          - DatabaseName
          - PortNumber
          - InboundTraffic
  'AWS::CloudFormation::Designer':
    1ffc9837-8bd5-4834-acb8-1341d385203b:
      size:
        width: 60
        height: 60
      position:
        x: 630
        'y': 300
      z: 1
      embeds: []
    176f3d5f-1a0e-4842-92d8-f6c49f105bf4:
      size:
        width: 510
        height: 420
      position:
        x: 60
        'y': 90
      z: 1
      embeds:
        - 780feaa3-f96b-45ce-9867-b907eb9b1055
        - 8e9e8d14-f12e-41ee-9efb-5883f12147c1
        - e38a58e4-b19b-40a0-94c8-0c6c87742d76
    e0e2cb93-3b17-4d61-be3a-97b04ba51f0b:
      size:
        width: 240
        height: 240
      position:
        x: 60
        'y': 570
      z: 1
      embeds:
        - 59f56921-c3e4-48ed-b192-18ae05febf23
    995b723f-e6f9-4f7d-82bc-237f7d42a3ba:
      source:
        id: 176f3d5f-1a0e-4842-92d8-f6c49f105bf4
      target:
        id: 1ffc9837-8bd5-4834-acb8-1341d385203b
    59f56921-c3e4-48ed-b192-18ae05febf23:
      size:
        width: 60
        height: 60
      position:
        x: 90
        'y': 630
      z: 2
      parent: e0e2cb93-3b17-4d61-be3a-97b04ba51f0b
      embeds: []
      isassociatedwith:
        - 1ffc9837-8bd5-4834-acb8-1341d385203b
      iscontainedinside:
        - e0e2cb93-3b17-4d61-be3a-97b04ba51f0b
      dependson:
        - 995b723f-e6f9-4f7d-82bc-237f7d42a3ba
    780feaa3-f96b-45ce-9867-b907eb9b1055:
      size:
        width: 60
        height: 60
      position:
        x: 90
        'y': 360
      z: 2
      parent: 176f3d5f-1a0e-4842-92d8-f6c49f105bf4
      embeds: []
      iscontainedinside:
        - 176f3d5f-1a0e-4842-92d8-f6c49f105bf4
    1f1ba6e8-26e0-4add-9d90-ed5d28ec5314:
      source:
        id: 780feaa3-f96b-45ce-9867-b907eb9b1055
      target:
        id: 780feaa3-f96b-45ce-9867-b907eb9b1055
    8e9e8d14-f12e-41ee-9efb-5883f12147c1:
      size:
        width: 150
        height: 150
      position:
        x: 300
        'y': 150
      z: 2
      parent: 176f3d5f-1a0e-4842-92d8-f6c49f105bf4
      embeds: []
      iscontainedinside:
        - 176f3d5f-1a0e-4842-92d8-f6c49f105bf4
    e38a58e4-b19b-40a0-94c8-0c6c87742d76:
      size:
        width: 150
        height: 150
      position:
        x: 90
        'y': 150
      z: 2
      parent: 176f3d5f-1a0e-4842-92d8-f6c49f105bf4
      embeds: []
      iscontainedinside:
        - 176f3d5f-1a0e-4842-92d8-f6c49f105bf4
    c270ad51-45f7-45bb-9874-e9d1baa2084b:
      size:
        width: 150
        height: 150
      position:
        x: 630
        'y': 90
      z: 1
      embeds: []
      iscontainedinside:
        - e38a58e4-b19b-40a0-94c8-0c6c87742d76
        - 8e9e8d14-f12e-41ee-9efb-5883f12147c1
    7bc88741-9bb3-4abf-8213-a4308d24038b:
      size:
        width: 60
        height: 60
      position:
        x: 750
        'y': 300
      z: 1
      embeds: []
    3b7285de-2727-481b-969c-2831b95bb6c1:
      size:
        width: 60
        height: 60
      position:
        x: 630
        'y': 420
      z: 1
      embeds: []
      dependson:
        - 995b723f-e6f9-4f7d-82bc-237f7d42a3ba
    7f0bf6e7-3f08-4e5f-9e82-96faa1915a68:
      size:
        width: 60
        height: 60
      position:
        x: 750
        'y': 420
      z: 1
      embeds: []
      dependson:
        - 3b7285de-2727-481b-969c-2831b95bb6c1
    881469c2-8267-4470-b3f0-f8db570c9745:
      size:
        width: 60
        height: 60
      position:
        x: 630
        'y': 540
      z: 1
      embeds: []
      dependson:
        - 3b7285de-2727-481b-969c-2831b95bb6c1
    48f7ae4a-021c-44c7-b8a4-53ec5f3e75ff:
      size:
        width: 60
        height: 60
      position:
        x: 750
        'y': 540
      z: 1
      embeds: []
    597cd4ca-e3fc-4f7e-91dd-7e68f7ba6566:
      size:
        width: 60
        height: 60
      position:
        x: 360
        'y': 570
      z: 1
      embeds: []
    46a23ed6-0577-4099-b30d-8a2406918da0:
      size:
        width: 60
        height: 60
      position:
        x: 480
        'y': 570
      z: 1
      embeds: []
    33296051-085a-4c3c-b4cf-6422f04dc2e5:
      size:
        width: 60
        height: 60
      position:
        x: 600
        'y': 660
      z: 1
      embeds: []
      dependson:
        - 46a23ed6-0577-4099-b30d-8a2406918da0
Resources:
  LambdaRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      Path: /
      Policies:
        - PolicyName: LambdaCloudFormationPolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - 's3:*'
                Resource:
                  - !Sub >-
                    arn:aws:s3:::cloudformation-custom-resource-response-${AWS::Region}
                  - !Sub 'arn:aws:s3:::cloudformation-waitcondition-${AWS::Region}'
                  - !Sub >-
                    arn:aws:s3:::cloudformation-custom-resource-response-${AWS::Region}/*
                  - !Sub 'arn:aws:s3:::cloudformation-waitcondition-${AWS::Region}/*'
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/AWSLambda_FullAccess'
        - 'arn:aws:iam::aws:policy/CloudWatchLogsFullAccess'
        - 'arn:aws:iam::aws:policy/service-role/AWSLambdaRole'
        - 'arn:aws:iam::aws:policy/IAMFullAccess'
        - 'arn:aws:iam::aws:policy/AmazonRedshiftFullAccess'
    Metadata:
      'AWS::CloudFormation::Designer':
        id: 48f7ae4a-021c-44c7-b8a4-53ec5f3e75ff
  LambdaFunctionDefaultRole:
    Type: 'AWS::Lambda::Function'
    Properties:
      Timeout: 300
      Code:
        ZipFile: |
          import sys
          import os
          import json
          import cfnresponse
          import logging
          from pip._internal import main

          main(['install', 'boto3', '--target', '/tmp/'])
          sys.path.insert(0,'/tmp/')

          import boto3
          from botocore.exceptions import ClientError

          def lambda_handler(event, context):

              print(boto3.__version__)

              if event['RequestType'] == 'Delete':
                  cfnresponse.send(event, context, cfnresponse.SUCCESS, {'Data': 'Delete complete'})
              else:
                  try:
                      client = boto3.client('redshift')
                      response = client.modify_cluster_iam_roles(
                          ClusterIdentifier=os.environ['RedshiftClusterIdentifier'],
                          DefaultIamRoleArn=os.environ['RedshiftClusterRole']
                      )
                      print(response)
                  except Exception as e:
                      logger.error(e)
                      cfnresponse.send(event, context, cfnresponse.FAILED, {'Data': 'Create failed'})

              cfnresponse.send(event, context, cfnresponse.SUCCESS, {'Data': 'Create complete'})

              return {
                  'statusCode': 200,
                  'body': json.dumps('Deployed Default Role')
              }
      Environment:
        Variables:
          RedshiftClusterIdentifier:
            Ref: RedshiftCluster
          RedshiftClusterRole:
            'Fn::GetAtt':
              - RedshiftRole
              - Arn
      Handler: index.lambda_handler
      Role:
        'Fn::GetAtt':
          - LambdaRole
          - Arn
      Runtime: python3.9
    DependsOn:
      - LambdaRole
      - RedshiftCluster
      - RedshiftRole
    Metadata:
      'AWS::CloudFormation::Designer':
        id: 597cd4ca-e3fc-4f7e-91dd-7e68f7ba6566

  MyAwsCLILayer:
    Type: AWS::Lambda::LayerVersion
    Properties:
      CompatibleRuntimes:
        - python3.9
      Content:
        S3Bucket: aws-bigdata-blog
        S3Key: artifacts/BDB-994-awscli-lambda-layer/awscli-lambda-layer.zip
      Description: My layer
      LayerName: aws-cli-layer
      LicenseInfo: MIT

  LambdaFunctionModifyCluster:
    Type: 'AWS::Lambda::Function'
    Properties:
      Timeout: 300
      Code:
        ZipFile: |
          import sys
          import os
          import json
          import cfnresponse
          import logging
          from pip._internal import main

          import subprocess
          import logging

          logger = logging.getLogger()
          logger.setLevel(logging.INFO)

          def run_command(command):
            command_list = command.split(' ')

            try:
                logger.info("Running shell command: \"{}\"".format(command))
                result = subprocess.run(command_list, stdout=subprocess.PIPE);
                logger.info("Command output:\n---\n{}\n---".format(result.stdout.decode('UTF-8')))
            except Exception as e:
                  logger.error("Exception: {}".format(e))
                  return False

            return True

          def lambda_handler(event, context):
            run_command('/opt/aws redshift modify-cluster --cluster-identifier redshift-cluster-az-relocation --availability-zone us-east-1b')

      Handler: index.lambda_handler
      Layers:
        - !Ref MyAwsCLILayer
      Role:
        'Fn::GetAtt':
          - LambdaRole
          - Arn
      Runtime: python3.9
    DependsOn:
      - LambdaRole
      - RedshiftCluster
      - RedshiftRole
    
  MySNSTopic:
    Type: AWS::SNS::Topic
    Properties:
      TopicName: "Redshift-cluster_alarms"


  PrimerInvokeDefaultRole:
    Type: 'AWS::CloudFormation::CustomResource'
    DependsOn:
      - LambdaFunctionDefaultRole
    Version: '1.0'
    Properties:
      ServiceToken: !GetAtt LambdaFunctionDefaultRole.Arn
    Metadata:
      'AWS::CloudFormation::Designer':
        id: 46a23ed6-0577-4099-b30d-8a2406918da0
  RedshiftRole:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: RedshiftImmersionRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - redshift.amazonaws.com
            Action:
              - 'sts:AssumeRole'
          - Effect: Allow
            Principal:
              Service:
                - events.amazonaws.com
            Action:
              - 'sts:AssumeRole'
          - Effect: Allow
            Principal:
              Service:
                - scheduler.redshift.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      Path: /
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/AmazonS3FullAccess'
        - 'arn:aws:iam::aws:policy/AWSGlueConsoleFullAccess'
        - 'arn:aws:iam::aws:policy/AmazonRedshiftFullAccess'
    Metadata:
      'AWS::CloudFormation::Designer':
        id: 7bc88741-9bb3-4abf-8213-a4308d24038b
  RedshiftCluster:
    Type: 'AWS::Redshift::Cluster'
    DependsOn:
      - AttachGateway
      - RedshiftRole
    Properties:
      ClusterType: multi-node
      NumberOfNodes:
        Ref: NumberOfNodes
      NodeType:
        Ref: NodeType
      DBName:
        Ref: DatabaseName
      MasterUsername:
        Ref: MasterUsername
      MasterUserPassword:
        Ref: MasterUserPassword
      ClusterParameterGroupName: default.redshift-1.0
      VpcSecurityGroupIds:
        - Ref: SecurityGroup
      ClusterSubnetGroupName:
        Ref: RedshiftClusterSubnetGroup
      PubliclyAccessible: 'false'
      AvailabilityZoneRelocation: 'true'
      Port:
        Ref: PortNumber
      IamRoles:
        - 'Fn::GetAtt':
            - RedshiftRole
            - Arn
    Metadata:
      'AWS::CloudFormation::Designer':
        id: 3b7285de-2727-481b-969c-2831b95bb6c1
  RedshiftClusterSubnetGroup:
    Type: 'AWS::Redshift::ClusterSubnetGroup'
    Properties:
      Description: Cluster subnet group
      SubnetIds:
        - Ref: Subnet
        - Ref: Subnet2
    Metadata:
      'AWS::CloudFormation::Designer':
        id: c270ad51-45f7-45bb-9874-e9d1baa2084b
  VPC:
    Type: 'AWS::EC2::VPC'
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsHostnames: true
      EnableDnsSupport: true
    Metadata:
      'AWS::CloudFormation::Designer':
        id: 176f3d5f-1a0e-4842-92d8-f6c49f105bf4
  Subnet:
    Type: 'AWS::EC2::Subnet'
    Properties:
      MapPublicIpOnLaunch: true
      CidrBlock: 10.0.0.0/24
      AvailabilityZone: !Select 
        - 0
        - !GetAZs ''
      VpcId:
        Ref: VPC
    Metadata:
      'AWS::CloudFormation::Designer':
        id: e38a58e4-b19b-40a0-94c8-0c6c87742d76
  Subnet2:
    Type: 'AWS::EC2::Subnet'
    Properties:
      MapPublicIpOnLaunch: true
      CidrBlock: 10.0.1.0/24
      AvailabilityZone: !Select 
        - 1
        - !GetAZs ''
      VpcId:
        Ref: VPC
    Metadata:
      'AWS::CloudFormation::Designer':
        id: 8e9e8d14-f12e-41ee-9efb-5883f12147c1
  SecurityGroup:
    Type: 'AWS::EC2::SecurityGroup'
    Properties:
      GroupDescription: Security group
      SecurityGroupIngress:
        - CidrIp:
            Ref: InboundTraffic
          FromPort: !Ref PortNumber
          ToPort: !Ref PortNumber
          IpProtocol: tcp
      VpcId:
        Ref: VPC
    Metadata:
      'AWS::CloudFormation::Designer':
        id: 780feaa3-f96b-45ce-9867-b907eb9b1055
  SecurityGroupSelfReference:
    Type: 'AWS::EC2::SecurityGroupIngress'
    Properties:
      Description: Self Referencing Rule
      FromPort: -1
      IpProtocol: -1
      GroupId: !GetAtt 
        - SecurityGroup
        - GroupId
      SourceSecurityGroupId: !GetAtt 
        - SecurityGroup
        - GroupId
      ToPort: -1
    Metadata:
      'AWS::CloudFormation::Designer':
        id: 1f1ba6e8-26e0-4add-9d90-ed5d28ec5314
  InternetGateway:
    Type: 'AWS::EC2::InternetGateway'
    Metadata:
      'AWS::CloudFormation::Designer':
        id: 1ffc9837-8bd5-4834-acb8-1341d385203b
  AttachGateway:
    Type: 'AWS::EC2::VPCGatewayAttachment'
    Properties:
      VpcId:
        Ref: VPC
      InternetGatewayId:
        Ref: InternetGateway
    Metadata:
      'AWS::CloudFormation::Designer':
        id: 995b723f-e6f9-4f7d-82bc-237f7d42a3ba
  RouteTable:
    Type: 'AWS::EC2::RouteTable'
    Properties:
      VpcId:
        Ref: VPC
    Metadata:
      'AWS::CloudFormation::Designer':
        id: e0e2cb93-3b17-4d61-be3a-97b04ba51f0b
  Route:
    Type: 'AWS::EC2::Route'
    DependsOn: AttachGateway
    Properties:
      RouteTableId:
        Ref: RouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId:
        Ref: InternetGateway
    Metadata:
      'AWS::CloudFormation::Designer':
        id: 59f56921-c3e4-48ed-b192-18ae05febf23
  SubnetRouteTableAssociation:
    Type: 'AWS::EC2::SubnetRouteTableAssociation'
    Properties:
      SubnetId:
        Ref: Subnet
      RouteTableId:
        Ref: RouteTable
  SubnetRouteTableAssociation2:
    Type: 'AWS::EC2::SubnetRouteTableAssociation'
    Properties:
      SubnetId:
        Ref: Subnet2
      RouteTableId:
        Ref: RouteTable
Outputs:
  ClusterEndpoint:
    Description: Cluster endpoint
    Value: !Sub '${RedshiftCluster.Endpoint.Address}:${RedshiftCluster.Endpoint.Port}'
  NumberOfNodes:
    Description: Number of nodes in your Redshift cluster
    Value:
      Ref: NumberOfNodes
  NodeType:
    Description: Redshift cluster node type config
    Value:
      Ref: NodeType
  MasterUsername:
    Description: Username to login to your Redshift cluster
    Value:
      Ref: MasterUsername
  MasterUserPassword:
    Description: Username to login to your Redshift cluster
    Value:
      Ref: MasterUserPassword
  VPC:
    Description: VPC ID for this configuration
    Value:
      Ref: VPC
  InboundTraffic:
    Description: CIDR range
    Value:
      Ref: InboundTraffic
  PortNumber:
    Description: Redshift port number for connections and security
    Value:
      Ref: PortNumber
  RedshiftRoleName:
    Description: Name of Redshift Role
    Value:
      Ref: RedshiftRole
  LambdaFunctionName:
    Description: Name of Lambda Function
    Value:
      Ref: LambdaFunctionModifyCluster

  LambdaRoleName:
    Description: Name of Lambda Role
    Value:
      Ref: LambdaRole
  RedshiftRoleNameArn:
    Description: Name of Redshift Role with ARN FN tag
    Value:
      'Fn::GetAtt':
        - RedshiftRole
        - Arn
  LambdaRoleNameArn:
    Description: Name of Lambda Role with ARN FN tag
    Value:
      'Fn::GetAtt':
        - LambdaRole
        - Arn
  AmazonSNSName:
    Description: Name of Amazon SNS Topic
    Value:
      Ref: MySNSTopic  
  ClusterName:
    Description: Name of cluster
    Value:
      Ref: RedshiftCluster
  RedshiftClusterSubnetGroupName:
    Description: Name of cluster subnet group
    Value:
      Ref: RedshiftClusterSubnetGroup
  SubnetA:
    Description: first subnet
    Value:
      Ref: Subnet
  SubnetB:
    Description: second subnet
    Value:
      Ref: Subnet2
  RedshiftClusterSecurityGroupName:
    Description: Name of cluster security group
    Value:
      Ref: SecurityGroup
