constructor()

in resources/flink-on-kda/cdk/lib/workshop-infrastructure.ts [23:313]


  constructor(scope: cdk.Construct, id: string, props: WorkshopInfrastructureProps) {
    super(scope, id, props);


    const bucket = new s3.Bucket(this, 'Bucket', {
      versioned: true,
      removalPolicy: RemovalPolicy.DESTROY
    });

    new EmptyBucketOnDelete(this, 'EmptyBucket', {
      bucket: bucket
    });


    new GithubBuildPipeline(this, 'KinesisReplayBuildPipeline', {
      url: `https://github.com/aws-samples/amazon-kinesis-replay/archive/${props.kinesisReplayVersion}.zip`,
      bucket: bucket,
      extract: true
    });

    new GithubBuildPipeline(this, 'ConsumerApplicationBuildPipeline', {
      url: `https://github.com/aws-samples/amazon-kinesis-analytics-taxi-consumer/archive/${props.consumerApplicationVersion}.zip`,
      bucket: bucket,
      extract: true
    });


    const localAdminPassword = new secretsmanager.Secret(this, 'TemplatedSecret', {
      generateSecretString: {
        secretStringTemplate: JSON.stringify({ username: 'Administrator' }),
        generateStringKey: 'password',
        passwordLength: 16,
        excludePunctuation: true,
        includeSpace: false
      }
    });

    
    const eip = new ec2.CfnEIP(this, 'InstanceEip');

    const es = new elasticsearch.CfnDomain(this, 'ElasticsearchDomain', {
      elasticsearchClusterConfig: {
        instanceCount: 1,
        instanceType: 't2.medium.elasticsearch'
      },
      ebsOptions: {
        ebsEnabled: true,
        volumeSize: 10,
        volumeType: 'gp2'
      },
      elasticsearchVersion: "7.1",
      accessPolicies: {
        "Version": "2012-10-17",
        "Statement": [
          {
            "Effect": "Allow",
            "Principal": {
              "AWS": "*"
            },
            "Action": [
              "es:ESHttp*"
            ],
            "Resource": "*",
            "Condition": {
              "IpAddress": {
                "aws:SourceIp": [
                  eip.ref
                ]
              }
            }
          }
        ]
      }
    });


    const policy = new iam.PolicyDocument();

    policy.addStatements(new iam.PolicyStatement({
      actions: [
        'secretsmanager:GetSecretValue',
      ],
      resources: [ localAdminPassword.secretArn ]
    }));

    policy.addStatements(new iam.PolicyStatement({
      actions: [
        'ec2:AssociateAddress',
        'cloudwatch:PutMetricData',
        'logs:Describe*', 'logs:PutLogEvents',
        'kinesis:DescribeStream', 'kinesis:ListShards', 'kinesis:GetShardIterator', 'kinesis:GetRecords', 'kinesis:PutRecord', 'kinesis:PutRecords',
        'kinesisanalytics:StartApplication'
      ],
      resources: [ '*' ]
    }));

    policy.addStatements(new iam.PolicyStatement({
      actions: [
        'cloudformation:DescribeStacks'
      ],
      resources: [ cdk.Aws.STACK_ID ]
    }));

    policy.addStatements(new iam.PolicyStatement({
      actions: [
        's3:GetObject*', 's3:GetBucket*', 's3:List*'
      ],
      resources: [
        bucket.bucketArn,
        `${bucket.bucketArn}/*`,
        `arn:${cdk.Aws.PARTITION}:s3:::aws-bigdata-blog`,
        `arn:${cdk.Aws.PARTITION}:s3:::aws-bigdata-blog/*`,
      ]
    }));


    const vpc = new ec2.Vpc(this, 'Vpc', {
      subnetConfiguration: [{  
        name: 'public',
        subnetType: ec2.SubnetType.PUBLIC
      }]
    });

    const sg = new ec2.SecurityGroup(this, 'SecurityGroup', {
      vpc: vpc
    });

    sg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(3389));

    const ami = new ec2.WindowsImage(ec2.WindowsVersion.WINDOWS_SERVER_2019_ENGLISH_FULL_BASE);

    const instanceRole = new iam.Role(this, 'InstanceRole', {
      assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
      managedPolicies: [
        iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore')
      ],
      inlinePolicies: {
        WorkshopPermissions: policy
      }
    });

    const instanceProfile = new iam.CfnInstanceProfile(this, 'InstanceProfile', {
      roles: [
        instanceRole.roleName
      ]
    });

    const waitHandle = new cfn.CfnWaitConditionHandle(this, 'InstanceWaitHandle');

    const waitCondition = new cfn.CfnWaitCondition(this, 'InstanceBootstrapWaitCondition', {
      count: 1,
      handle: waitHandle.ref,
      timeout: Duration.minutes(20).toSeconds().toString()
    });

    const launchTemplate = new ec2.CfnLaunchTemplate(this, 'LaunchTemplate', {
      launchTemplateData: {
        imageId: ami.getImage(this).imageId,
        iamInstanceProfile: {
          arn: instanceProfile.attrArn
        },
        networkInterfaces: [{
          associatePublicIpAddress: true,
          deleteOnTermination: true,
          deviceIndex: 0,
          groups: [sg.securityGroupId]
        }],
        userData: cdk.Fn.base64(
          `<powershell>            
            Import-Module AWSPowerShell

            # Install choco
            iex ((New-Object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))

            # Add gitter and retry to install commands
            $iter = 0
            $sleep = 5

            Do {
              Start-Sleep -Seconds (Get-Random -Maximum ($sleep*[Math]::Pow(2,$iter++)))
              choco install git --no-progress -y
            } Until ($LASTEXITCODE -eq 0)

            Do {
              Start-Sleep -Seconds (Get-Random -Maximum ($sleep*[Math]::Pow(2,$iter++)))
              choco install firefox --no-progress -y
            } Until ($LASTEXITCODE -eq 0)

            Do {
              Start-Sleep -Seconds (Get-Random -Maximum ($sleep*[Math]::Pow(2,$iter++)))
              choco install intellijidea-community --no-progress --version 2019.2.4 -y
            } Until ($LASTEXITCODE -eq 0)

            # Add IntelliJ Java 11 to the path
            $PATH = [Environment]::GetEnvironmentVariable("PATH", "Machine")
            $intellij_path = "C:\\Program Files\\JetBrains\\IntelliJ IDEA Community Edition 2019.2.4\\jbr\\bin"
            [Environment]::SetEnvironmentVariable("PATH", "$PATH;$intellij_path", "Machine")

            $desktop = "C:\\Users\\Administrator\\Desktop"

            # Create desktop shortcuts
            Remove-Item -path "$desktop\\*.website"
            Copy-Item "C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\JetBrains\\*.lnk" "$desktop"

            # Change password
            $password = ((Get-SECSecretValue -SecretId '${localAdminPassword.secretArn}').SecretString | ConvertFrom-Json).Password
            net.exe user Administrator "$password"

            # Associate EIP
            $instanceId = Invoke-RestMethod -uri http://169.254.169.254/latest/meta-data/instance-id
            Register-EC2Address -InstanceId "$instanceId" -AllocationId "${eip.attrAllocationId}"

            # Signal success to CFN
            cfn-signal.exe --success true --region "${cdk.Aws.REGION}" "${waitHandle.ref}"


            # Download artifacts
            New-Item -Path "$desktop" -Name "workshop-resources" -ItemType "directory"

            $url = "https://raw.githubusercontent.com/aws-samples/amazon-kinesis-analytics-taxi-consumer/${props.consumerApplicationVersion}/misc/streaming-analytics-workshop-dashboard.json"
            $file = "$desktop\\workshop-resources\\streaming-analytics-workshop-dashboard.json"
            (New-Object System.Net.WebClient).DownloadFile($url, $file)

            # Wait until build pipelines have successfully build all artifacts
            Wait-CFNStack -StackName "${cdk.Aws.STACK_NAME}" -Timeout 900

            Copy-S3Object -BucketName "${bucket.bucketName}" -KeyPrefix target -LocalFolder "$desktop\\workshop-resources"
          </powershell>`.split('\n').map(line => line.trimLeft()).join('\n')
        )
      }
    });

    waitCondition.addDependsOn(launchTemplate);


    new autoscaling.CfnAutoScalingGroup(this, 'AutoScalingGroup', {
      mixedInstancesPolicy: {
        launchTemplate: {
          launchTemplateSpecification: {
            launchTemplateId: launchTemplate.ref,
            version: launchTemplate.attrDefaultVersionNumber
          },
          overrides: [
            {instanceType: 'm5.2xlarge'},
            {instanceType: 'c5.2xlarge'},
            {instanceType: 'm3.2xlarge'},
            {instanceType: 'm5.xlarge'},
            {instanceType: 'c5.xlarge'},
            {instanceType: 'm4.xlarge'},
            {instanceType: 'c4.xlarge'}
           ]
        },
        instancesDistribution: {
          onDemandBaseCapacity: 1
        }
      },
      maxSize: '1',
      minSize: '1',
      desiredCapacity: '1',
      vpcZoneIdentifier: vpc.publicSubnets.map(subnet => subnet.subnetId)
    });


    const kdaRole = new iam.Role(this, 'KdaRole', {
      assumedBy: new iam.ServicePrincipal('kinesisanalytics.amazonaws.com'),

    });

    kdaRole.addToPolicy(new iam.PolicyStatement({
      actions: [
        'logs:Describe*', 'logs:PutLogEvents',
        'kinesis:List*', 'kinesis:Describe*', 'kinesis:Get*', 'kinesis:SubscribeToShard',
      ],
      resources: [ '*' ]
    }));

    kdaRole.addToPolicy(new iam.PolicyStatement({
      actions: [ 'es:ESHttp*' ],
      resources: [ `${es.attrArn}/*` ]
    }));

    bucket.grantRead(kdaRole);


    new cdk.CfnOutput(this, 'InstanceIp', { value: eip.ref });
    new cdk.CfnOutput(this, 'InstanceLoginCredentials', { value: `https://console.aws.amazon.com/secretsmanager/#/secret?name=${localAdminPassword.secretArn}` });    
    new cdk.CfnOutput(this, 'ElasticsearchDomainName', { value: es.attrDomainEndpoint });
    new cdk.CfnOutput(this, 'KinesisAnalyticsServiceRole', { value: kdaRole.roleName });
    new cdk.CfnOutput(this, 'FlinkApplicationJarBucket', { value: bucket.bucketName });
    new cdk.CfnOutput(this, 'FlinkApplicationJarObject', { value: `target/${props.consumerApplicationJarObject}` });
  }