Exemple #1
0
    def __init__(self, scope: core.Construct, id: str, vpc, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        self.sng = _ec.CfnSubnetGroup(
            self,
            "ecSubnetGroup",
            description="redisSubnetGroup",
            subnet_ids=vpc.select_subnets(
                subnet_type=_ec2.SubnetType.ISOLATED).subnet_ids,
            cache_subnet_group_name=f"{core.Aws.STACK_NAME}-ecSng")

        self.ec = _ec.CfnReplicationGroup(
            self,
            "redisCluster",
            replication_group_description="redisReplicationGroup",
            engine="redis",
            engine_version="5.0.6",
            cache_node_type="cache.t3.micro",
            automatic_failover_enabled=True,
            auto_minor_version_upgrade=True,
            cache_subnet_group_name=self.sng.cache_subnet_group_name,
            num_node_groups=3,
            security_group_ids=[vpc.sg.security_group_id])
Exemple #2
0
    def create_redis(self, vpc: ec2.IVpc):
        selection = vpc.select_subnets(subnet_type=ec2.SubnetType.PUBLIC)

        redis_security_group = ec2.SecurityGroup(self,
                                                 id='redis-security-group',
                                                 vpc=vpc)
        redis_security_group.add_ingress_rule(ec2.Peer.any_ipv4(),
                                              ec2.Port.tcp(6379),
                                              "Incoming to Redis")

        redis_subnet_group = elasticache.CfnSubnetGroup(
            self,
            "RedisClusterPrivateSubnetGroup",
            cache_subnet_group_name="redis-subnet-group",
            description="Tubby Redis Subnet",
            subnet_ids=selection.subnet_ids)

        redis_parameter_group = elasticache.CfnParameterGroup(
            self,
            "RedisParameterGroup",
            description="Redis Params",
            cache_parameter_group_family="redis6.x",
            properties={},
        )

        redis = elasticache.CfnCacheCluster(
            self,
            "RedisCacheCluster",
            engine="redis",
            cache_node_type="cache.t2.micro",
            num_cache_nodes=1,
            cluster_name="startuptoolbag-redis",
            vpc_security_group_ids=[redis_security_group.security_group_id],
            cache_subnet_group_name=redis_subnet_group.cache_subnet_group_name,
            cache_parameter_group_name=redis_parameter_group.ref,
        )
        return redis
    def __init__(self, app: core.App, id: str, **kwargs) -> None:
        super().__init__(app, id, **kwargs)

        # -- VPC
        vpc = ec2.Vpc(self, "vpc_airflow")
        # ecr
        ecr_repo = ecr.Repository.from_repository_name(self,
                                                       "ecr_repo_airflow",
                                                       "airflow")
        # rds
        sg_airflow_backend_db = ec2.SecurityGroup(
            self,
            "sg_airflow_backend_database",
            vpc=vpc,
            description="Airflow backend database",
            security_group_name="sg_airflow_backend_database",
        )
        db = rds.DatabaseInstance(
            self,
            "rds_airfow_backend",
            master_username="******",
            master_user_password=core.SecretValue.plain_text("postgres"),
            database_name="airflow",
            engine=rds.DatabaseInstanceEngine.postgres(
                version=rds.PostgresEngineVersion.VER_11_8),
            vpc=vpc,
            instance_type=ec2.InstanceType.of(
                ec2.InstanceClass.BURSTABLE3,
                ec2.InstanceSize.MICRO,
            ),
            instance_identifier="airflow-backend",
            removal_policy=core.RemovalPolicy.DESTROY,
            deletion_protection=False,
            security_groups=[sg_airflow_backend_db],
            vpc_placement=ec2.SubnetSelection(
                subnet_type=ec2.SubnetType.PUBLIC),
        )
        # -- ElasticCache Redis
        sg_redis = ec2.SecurityGroup(
            self,
            "sg_redis",
            vpc=vpc,
            description="Airflow redis",
            security_group_name="sg_redis",
        )
        redis_subnet_group = ec.CfnSubnetGroup(
            self,
            "airflow-redis-subnet-group",
            description="For Airflow Task Queue",
            subnet_ids=vpc.select_subnets(
                subnet_type=ec2.SubnetType.PRIVATE).subnet_ids,
            cache_subnet_group_name="airflow-redis-task-queue",
        )
        redis = ec.CfnCacheCluster(
            self,
            "redis",
            cluster_name="airflow-redis",
            cache_node_type="cache.t2.micro",
            engine="redis",
            num_cache_nodes=1,
            auto_minor_version_upgrade=True,
            engine_version="5.0.6",
            port=REDIS_PORT,
            cache_subnet_group_name=redis_subnet_group.ref,
            vpc_security_group_ids=[sg_redis.security_group_id],
        )
        # ECS cluster
        cluster = ecs.Cluster(
            self,
            "ecs_airflow",
            cluster_name="airflow",
            vpc=vpc,
            container_insights=True,
        )
        # scheduler
        scheduler_task_role = iam.Role(
            self,
            "iam_role_scheduler",
            assumed_by=iam.ServicePrincipal("ecs-tasks.amazonaws.com"),
            description="IAM role for ECS Scheduler service",
            managed_policies=[
                iam.ManagedPolicy.from_aws_managed_policy_name(
                    "AmazonEC2ContainerRegistryReadOnly"),
                iam.ManagedPolicy.from_aws_managed_policy_name(
                    "CloudWatchLogsFullAccess"),
            ],
            role_name="airflow-ecs-scheduler-task",
        )
        scheduler_task = ecs.FargateTaskDefinition(
            self,
            "ecs_task_scheduler",
            cpu=512,
            memory_limit_mib=2048,
            task_role=scheduler_task_role,
        )
        scheduler_task.add_container(
            "scheduler",
            command=["scheduler"],
            # credentials should be provided from Secrets Manager
            environment={
                "LOAD_EX": "n",
                "FERNET_KEY": "46BKJoQYlPPOexq0OhDZnIlNepKFf87WFwLbfzqDDho=",
                "EXECUTOR": "Celery",
                "POSTGRES_HOST": db.db_instance_endpoint_address,
                "POSTGRES_USER": "******",
                "POSTGRES_PASSWORD": "******",
                "POSTGRES_DB": "airflow",
                "REDIS_HOST": redis.attr_redis_endpoint_address,
            },
            image=ecs.ContainerImage.from_ecr_repository(
                ecr_repo,
                "1.10.9",
            ),
            logging=ecs.LogDriver.aws_logs(
                stream_prefix="scheduler",
                log_group=logs.LogGroup(
                    self,
                    "log-airflow-scheduler",
                    log_group_name="ecs/airflow/scheduler",
                    retention=logs.RetentionDays.ONE_WEEK,
                ),
            ),
        )
        sg_airflow_scheduler = ec2.SecurityGroup(
            self,
            "sg_airflow_scheduler",
            vpc=vpc,
            description="Airflow Scheduler service",
            security_group_name="sg_airflow_scheduler",
        )
        sg_redis.add_ingress_rule(
            peer=sg_airflow_scheduler,
            connection=ec2.Port(
                protocol=ec2.Protocol.TCP,
                string_representation="from scheduler",
                from_port=REDIS_PORT,
                to_port=REDIS_PORT,
            ),
            description="from scheduler service",
        )
        sg_airflow_backend_db.add_ingress_rule(
            peer=sg_airflow_scheduler,
            connection=ec2.Port(
                protocol=ec2.Protocol.TCP,
                string_representation="from home",
                from_port=POSTGRES_PORT,
                to_port=POSTGRES_PORT,
            ),
            description="home",
        )
        scheduler_service = ecs.FargateService(
            self,
            "ecs_service_scheduler",
            cluster=cluster,
            task_definition=scheduler_task,
            desired_count=1,
            security_groups=[sg_airflow_scheduler],
            service_name="scheduler",
        )
        # flower
        flower_task_role = iam.Role(
            self,
            "iam_role_flower",
            assumed_by=iam.ServicePrincipal("ecs-tasks.amazonaws.com"),
            description="IAM role for ECS Flower service",
            managed_policies=[
                iam.ManagedPolicy.from_aws_managed_policy_name(
                    "AmazonEC2ContainerRegistryReadOnly"),
                iam.ManagedPolicy.from_aws_managed_policy_name(
                    "CloudWatchLogsFullAccess"),
            ],
            role_name="airflow-ecs-flower-task",
        )
        flower_task = ecs.FargateTaskDefinition(
            self,
            "ecs_task_flower",
            cpu=512,
            memory_limit_mib=1024,
            task_role=scheduler_task_role,
        )
        flower_task.add_container(
            "flower",
            command=["flower"],
            # credentials should be provided from Secrets Manager
            environment={
                "LOAD_EX": "n",
                "FERNET_KEY": "46BKJoQYlPPOexq0OhDZnIlNepKFf87WFwLbfzqDDho=",
                "EXECUTOR": "Celery",
                "REDIS_HOST": redis.attr_redis_endpoint_address,
            },
            image=ecs.ContainerImage.from_ecr_repository(
                ecr_repo,
                "1.10.9",
            ),
            logging=ecs.LogDriver.aws_logs(
                stream_prefix="flower",
                log_group=logs.LogGroup(
                    self,
                    "log-airflow-flower",
                    log_group_name="ecs/airflow/flower",
                    retention=logs.RetentionDays.ONE_WEEK,
                ),
            ),
        ).add_port_mappings(
            ecs.PortMapping(
                container_port=FLOWER_PORT,
                host_port=FLOWER_PORT,
                protocol=ecs.Protocol.TCP,
            ))
        sg_airflow_flower = ec2.SecurityGroup(
            self,
            "sg_airflow_flower",
            vpc=vpc,
            description="Airflow Flower service",
            security_group_name="sg_airflow_flower",
        )
        sg_airflow_flower.add_ingress_rule(
            peer=ec2.Peer.ipv4("115.66.217.45/32"),
            connection=ec2.Port(
                protocol=ec2.Protocol.TCP,
                string_representation="from homr",
                from_port=FLOWER_PORT,
                to_port=FLOWER_PORT,
            ),
            description="from home",
        )
        sg_redis.add_ingress_rule(
            peer=sg_airflow_flower,
            connection=ec2.Port(
                protocol=ec2.Protocol.TCP,
                string_representation="from flower",
                from_port=REDIS_PORT,
                to_port=REDIS_PORT,
            ),
            description="from flower",
        )
        flower_service = ecs.FargateService(
            self,
            "ecs_service_flower",
            cluster=cluster,
            task_definition=flower_task,
            desired_count=1,
            security_groups=[sg_airflow_flower],
            service_name="flower",
        )
        # worker
        worker_task_role = iam.Role(
            self,
            "iam_role_worker",
            assumed_by=iam.ServicePrincipal("ecs-tasks.amazonaws.com"),
            description="IAM role for ECS worker service",
            managed_policies=[
                iam.ManagedPolicy.from_aws_managed_policy_name(
                    "AmazonEC2ContainerRegistryReadOnly"),
                iam.ManagedPolicy.from_aws_managed_policy_name(
                    "CloudWatchLogsFullAccess"),
            ],
            role_name="airflow-ecs-worker-task",
        )
        worker_task = ecs.FargateTaskDefinition(
            self,
            "ecs_task_worker",
            cpu=1024,
            memory_limit_mib=3072,
            task_role=worker_task_role,
        )
        worker_task.add_container(
            "worker",
            command=["worker"],
            # credentials should be provided from Secrets Manager
            environment={
                "LOAD_EX": "n",
                "FERNET_KEY": "46BKJoQYlPPOexq0OhDZnIlNepKFf87WFwLbfzqDDho=",
                "EXECUTOR": "Celery",
                "POSTGRES_HOST": db.db_instance_endpoint_address,
                "POSTGRES_USER": "******",
                "POSTGRES_PASSWORD": "******",
                "POSTGRES_DB": "airflow",
                "REDIS_HOST": redis.attr_redis_endpoint_address,
            },
            image=ecs.ContainerImage.from_ecr_repository(
                ecr_repo,
                "1.10.9",
            ),
            logging=ecs.LogDriver.aws_logs(
                stream_prefix="worker",
                log_group=logs.LogGroup(
                    self,
                    "log-airflow-worker",
                    log_group_name="ecs/airflow/worker",
                    retention=logs.RetentionDays.ONE_WEEK,
                ),
            ),
        )
        sg_airflow_worker = ec2.SecurityGroup(
            self,
            "sg_airflow_worker",
            vpc=vpc,
            description="Airflow worker service",
            security_group_name="sg_airflow_worker",
        )
        sg_redis.add_ingress_rule(
            peer=sg_airflow_worker,
            connection=ec2.Port(
                protocol=ec2.Protocol.TCP,
                string_representation="from worker",
                from_port=REDIS_PORT,
                to_port=REDIS_PORT,
            ),
            description="from worker service",
        )
        sg_airflow_backend_db.add_ingress_rule(
            peer=sg_airflow_worker,
            connection=ec2.Port(
                protocol=ec2.Protocol.TCP,
                string_representation="from worker",
                from_port=POSTGRES_PORT,
                to_port=POSTGRES_PORT,
            ),
            description="From worker",
        )
        worker_service = ecs.FargateService(
            self,
            "ecs_service_worker",
            cluster=cluster,
            task_definition=worker_task,
            desired_count=1,
            security_groups=[sg_airflow_worker],
            service_name="worker",
        )
        # web server
        web_server_task_role = iam.Role(
            self,
            "iam_role_web_server",
            assumed_by=iam.ServicePrincipal("ecs-tasks.amazonaws.com"),
            description="IAM role for ECS web server service",
            managed_policies=[
                iam.ManagedPolicy.from_aws_managed_policy_name(
                    "AmazonEC2ContainerRegistryReadOnly"),
                iam.ManagedPolicy.from_aws_managed_policy_name(
                    "CloudWatchLogsFullAccess"),
            ],
            role_name="airflow-ecs-web-server-task",
        )
        web_server_task = ecs.FargateTaskDefinition(
            self,
            "ecs_task_web_server",
            cpu=512,
            memory_limit_mib=1024,
            task_role=web_server_task_role,
        )
        web_server_task.add_container(
            "web_server",
            command=["webserver"],
            # credentials should be provided from Secrets Manager
            environment={
                "LOAD_EX": "n",
                "FERNET_KEY": "46BKJoQYlPPOexq0OhDZnIlNepKFf87WFwLbfzqDDho=",
                "EXECUTOR": "Celery",
                "POSTGRES_HOST": db.db_instance_endpoint_address,
                "POSTGRES_USER": "******",
                "POSTGRES_PASSWORD": "******",
                "POSTGRES_DB": "airflow",
                "REDIS_HOST": redis.attr_redis_endpoint_address,
            },
            image=ecs.ContainerImage.from_ecr_repository(
                ecr_repo,
                "1.10.9",
            ),
            logging=ecs.LogDriver.aws_logs(
                stream_prefix="web_server",
                log_group=logs.LogGroup(
                    self,
                    "log-airflow-web-server",
                    log_group_name="ecs/airflow/web-server",
                    retention=logs.RetentionDays.ONE_WEEK,
                ),
            ),
        ).add_port_mappings(
            ecs.PortMapping(
                container_port=WEB_SERVER_PORT,
                host_port=WEB_SERVER_PORT,
                protocol=ecs.Protocol.TCP,
            ))
        sg_airflow_web_server = ec2.SecurityGroup(
            self,
            "sg_airflow_web_server",
            vpc=vpc,
            description="Airflow web server service",
            security_group_name="sg_airflow_web_server",
        )
        sg_airflow_backend_db.add_ingress_rule(
            peer=sg_airflow_web_server,
            connection=ec2.Port(
                protocol=ec2.Protocol.TCP,
                string_representation="From web server",
                from_port=POSTGRES_PORT,
                to_port=POSTGRES_PORT,
            ),
            description="From web server",
        )
        sg_airflow_backend_db.add_ingress_rule(
            peer=sg_airflow_web_server,
            connection=ec2.Port(
                protocol=ec2.Protocol.TCP,
                string_representation="From web server",
                from_port=POSTGRES_PORT,
                to_port=POSTGRES_PORT,
            ),
            description="From web server",
        )
        sg_redis.add_ingress_rule(
            peer=sg_airflow_web_server,
            connection=ec2.Port(
                protocol=ec2.Protocol.TCP,
                string_representation="from web server",
                from_port=REDIS_PORT,
                to_port=REDIS_PORT,
            ),
            description="from web server",
        )
        web_server_service = ecs.FargateService(
            self,
            "ecs_service_web_server",
            cluster=cluster,
            task_definition=web_server_task,
            desired_count=1,
            security_groups=[sg_airflow_web_server],
            service_name="web_server",
        )
        # Load balancer
        sg_airflow_alb = ec2.SecurityGroup(
            self,
            "sg_airflow_alb",
            vpc=vpc,
            description="Airflow ALB",
            security_group_name="sg_airflow_alb",
        )
        # ALB -> web server
        sg_airflow_web_server.add_ingress_rule(
            peer=sg_airflow_alb,
            connection=ec2.Port(
                protocol=ec2.Protocol.TCP,
                string_representation="From ALB",
                from_port=WEB_SERVER_PORT,
                to_port=WEB_SERVER_PORT,
            ),
            description="From ALB",
        )
        # ALB -> flower
        sg_airflow_flower.add_ingress_rule(
            peer=sg_airflow_alb,
            connection=ec2.Port(
                protocol=ec2.Protocol.TCP,
                string_representation="From ALB",
                from_port=FLOWER_PORT,
                to_port=FLOWER_PORT,
            ),
            description="From ALB",
        )
        # Home -> ALB
        sg_airflow_alb.add_ingress_rule(
            peer=ec2.Peer.ipv4(MY_IP_CIDR),
            connection=ec2.Port(
                protocol=ec2.Protocol.TCP,
                string_representation="From Home",
                from_port=ALB_PORT,
                to_port=ALB_PORT,
            ),
            description="From Home",
        )
        # Home -> ALB
        sg_airflow_alb.add_ingress_rule(
            peer=ec2.Peer.ipv4(MY_IP_CIDR),
            connection=ec2.Port(
                protocol=ec2.Protocol.TCP,
                string_representation="From Home",
                from_port=FLOWER_PORT,
                to_port=FLOWER_PORT,
            ),
            description="From Home",
        )
        alb = elb.ApplicationLoadBalancer(
            self,
            "alb_airflow",
            internet_facing=True,
            security_group=sg_airflow_alb,
            vpc=vpc,
            load_balancer_name="alb-airflow",
        )
        listener1 = alb.add_listener(
            "alb_airflow_listener1",
            open=False,
            port=ALB_PORT,
            protocol=elb.ApplicationProtocol.HTTP,
            default_target_groups=[
                elb.ApplicationTargetGroup(
                    self,
                    "alb_airflow_target_group_web_server",
                    port=WEB_SERVER_PORT,
                    protocol=elb.ApplicationProtocol.HTTP,
                    target_group_name="alb-tg-airflow-web-server",
                    targets=[web_server_service],
                    vpc=vpc,
                )
            ],
        )
        alb.add_listener(
            "alb_airflow_listener2",
            open=False,
            port=FLOWER_PORT,
            protocol=elb.ApplicationProtocol.HTTP,
            default_target_groups=[
                elb.ApplicationTargetGroup(
                    self,
                    "alb_airflow_target_group_flower",
                    port=FLOWER_PORT,
                    protocol=elb.ApplicationProtocol.HTTP,
                    target_group_name="alb-tg-aiflow-flower",
                    targets=[flower_service],
                    vpc=vpc,
                )
            ],
        )
Exemple #4
0
    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        vpc = _ec2.Vpc.from_lookup(
            self, id="vpc", vpc_id=environment.AWS_VPC_ID)
        subnet_group = _elasticache.CfnSubnetGroup(self,
                                                   id="subnet-group",
                                                   description="The redis subnet group",
                                                   subnet_ids=list(map(lambda s: s.subnet_id, vpc.private_subnets)))

        security_group = _ec2.SecurityGroup.from_security_group_id(
            self, id="Security Group", security_group_id=environment.AWS_SECURITY_GROUP_ID)

        # define s3 bucket for redis data backup
        # do not use RemovalPolicy.DESTORY on production, use RemovalPolicy.RETAIN instead
        # bucket: s3.Bucket = _s3.Bucket(self, "RankingDataBackup", removal_policy=core.RemovalPolicy.DESTROY)

        # define elasticache for ranking
        elasticache = _elasticache.CfnCacheCluster(
            self,
            id="LeaderBoardElasticache",
            cache_node_type="cache.t2.micro",
            num_cache_nodes=1,
            engine="redis",
            engine_version="5.0.6",
            cache_parameter_group_name="default.redis5.0",
            cache_subnet_group_name=subnet_group.cache_subnet_group_name,
            vpc_security_group_ids=[security_group.security_group_id])

        elasticache.apply_removal_policy(core.RemovalPolicy.DESTROY)
        elasticache.add_depends_on(subnet_group)

        elasticache_host = elasticache.attr_redis_endpoint_address
        elasticache_port = elasticache.attr_redis_endpoint_port

        lambda_function = _lambda.Function(self, "LeaderBoardFunction",
                                           handler='lambda_handler.handler',
                                           runtime=_lambda.Runtime.PYTHON_3_8,
                                           code=_lambda.Code.from_asset('lambda'),
                                           memory_size=128,
                                           vpc=vpc,
                                           security_group=security_group,
                                           timeout=core.Duration.seconds(10),
                                           log_retention=_logs.RetentionDays.ONE_WEEK,
                                           layers=[self.create_dependencies_layer("leaderboard", "lambda")])

        lambda_function.add_environment("REDIS_HOST", elasticache_host)
        lambda_function.add_environment("REDIS_PORT", elasticache_port)
        lambda_function.add_environment("ADMIN_SECRET_TOKEN", environment.ADMIN_SECRET_TOKEN)
        lambda_function.add_environment("DEFAULT_FETCH_COUNT", str(environment.DEFAULT_FETCH_COUNT))
        lambda_function.add_environment("MAX_FETCH_COUNT", str(environment.MAX_FETCH_COUNT))

        base_api = _apigw.RestApi(self, 'LeaderBoardApi', rest_api_name='LeaderBoardApi')

        root_api = base_api.root
        entity_lambda_integration = _apigw.LambdaIntegration(lambda_function, proxy=True, integration_responses=[
            {
                'statusCode': '200',
                "responseParameters": {
                    'method.response.header.Access-Control-Allow-Origin': "'*'",
                }
            }
        ])

        root_api.add_method('GET', entity_lambda_integration,
                            method_responses=[{
                                'statusCode': '200',
                                'responseParameters': {
                                    'method.response.header.Access-Control-Allow-Origin': True,
                                }
                            }])

        entity = root_api.add_resource("{proxy+}")
        entity.add_method("ANY", _apigw.LambdaIntegration(lambda_function))

        self.add_cors_options(root_api)
Exemple #5
0
    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        # The code that defines your stack goes here
        vpc = aws_ec2.Vpc(self, 'RedisVPC', max_azs=2)

        sg_use_elasticache = aws_ec2.SecurityGroup(
            self,
            'RedisClientSG',
            vpc=vpc,
            allow_all_outbound=True,
            description='security group for redis client',
            security_group_name='use-default-redis')
        core.Tags.of(sg_use_elasticache).add('Name', 'use-default-redis')

        sg_elasticache = aws_ec2.SecurityGroup(
            self,
            'RedisServerSG',
            vpc=vpc,
            allow_all_outbound=True,
            description='security group for redis',
            security_group_name='default-redis-server')
        core.Tags.of(sg_elasticache).add('Name', 'redis-server')

        sg_elasticache.add_ingress_rule(peer=sg_use_elasticache,
                                        connection=aws_ec2.Port.tcp(6379),
                                        description='use-default-redis')

        elasticache_subnet_group = aws_elasticache.CfnSubnetGroup(
            self,
            'RedisSubnetGroup',
            description='subnet group for redis',
            subnet_ids=vpc.select_subnets(
                subnet_type=aws_ec2.SubnetType.PRIVATE).subnet_ids,
            cache_subnet_group_name='default-redis')

        redis_param_group = aws_elasticache.CfnParameterGroup(
            self,
            'RedisParamGroup',
            cache_parameter_group_family='redis5.0',
            description='parameter group for redis5.0',
            properties={
                'databases': '256',  # database: 16 (default)
                'tcp-keepalive': '0',  #tcp-keepalive: 300 (default)
                'maxmemory-policy':
                'volatile-ttl'  #maxmemory-policy: volatile-lru (default)
            })

        redis_primary_only = aws_elasticache.CfnCacheCluster(
            self,
            'RedisCache',
            cache_node_type='cache.t3.small',
            #XXX: NumCacheNodes should be 1 if engine is redis
            num_cache_nodes=1,
            engine='redis',
            engine_version='5.0.5',
            auto_minor_version_upgrade=False,
            cluster_name='elasticache-redis',
            snapshot_retention_limit=3,
            snapshot_window='17:00-19:00',
            preferred_maintenance_window='mon:19:00-mon:20:30',
            #XXX: Elasticache.CfnParameterGroup cannot be initialized with a parameter_group_name
            # https://github.com/aws-cloudformation/aws-cloudformation-coverage-roadmap/issues/484
            # https://github.com/aws/aws-cdk/issues/8180
            cache_parameter_group_name=redis_param_group.ref,
            cache_subnet_group_name=elasticache_subnet_group.
            cache_subnet_group_name,
            vpc_security_group_ids=[sg_elasticache.security_group_id],
            tags=[
                core.CfnTag(key='Name', value='redis-primary-only'),
                core.CfnTag(key='desc', value='primary only redis')
            ])
        #XXX: Subnet group must exist before ElastiCache is created
        redis_primary_only.add_depends_on(elasticache_subnet_group)

        redis_with_replicas = aws_elasticache.CfnReplicationGroup(
            self,
            'RedisCacheWithReplicas',
            cache_node_type='cache.t3.small',
            engine='redis',
            engine_version='5.0.5',
            snapshot_retention_limit=3,
            snapshot_window='19:00-21:00',
            preferred_maintenance_window='mon:21:00-mon:22:30',
            automatic_failover_enabled=True,
            auto_minor_version_upgrade=False,
            multi_az_enabled=True,
            replication_group_description='redis with replicas',
            replicas_per_node_group=1,
            cache_parameter_group_name=redis_param_group.ref,
            cache_subnet_group_name=elasticache_subnet_group.
            cache_subnet_group_name,
            security_group_ids=[sg_elasticache.security_group_id],
            tags=[
                core.CfnTag(key='Name', value='redis-with-replicas'),
                core.CfnTag(key='desc', value='primary-replica redis')
            ])
        redis_with_replicas.add_depends_on(elasticache_subnet_group)

        redis_cluster_param_group = aws_elasticache.CfnParameterGroup(
            self,
            'RedisClusterParamGroup',
            cache_parameter_group_family='redis5.0',
            description='parameter group for redis5.0 cluster',
            properties={
                'cluster-enabled': 'yes',  # Enable cluster mode
                'tcp-keepalive': '0',  #tcp-keepalive: 300 (default)
                'maxmemory-policy':
                'volatile-ttl'  #maxmemory-policy: volatile-lru (default)
            })

        redis_cluster = aws_elasticache.CfnReplicationGroup(
            self,
            'RedisCluster',
            cache_node_type='cache.t3.small',
            engine='redis',
            engine_version='5.0.5',
            snapshot_retention_limit=3,
            snapshot_window='19:00-21:00',
            preferred_maintenance_window='mon:21:00-mon:22:30',
            automatic_failover_enabled=True,
            auto_minor_version_upgrade=False,
            #XXX: Each Node Group needs to have at least one replica for Multi-AZ enabled Replication Group
            multi_az_enabled=False,
            replication_group_description='redis5.0 cluster on',
            num_node_groups=3,
            cache_parameter_group_name=redis_cluster_param_group.ref,
            cache_subnet_group_name=elasticache_subnet_group.
            cache_subnet_group_name,
            security_group_ids=[sg_elasticache.security_group_id],
            tags=[
                core.CfnTag(key='Name', value='redis-cluster'),
                core.CfnTag(key='desc', value='primary-replica redis')
            ])
        redis_cluster.add_depends_on(elasticache_subnet_group)
  def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
    super().__init__(scope, construct_id, **kwargs)

    vpc_name = self.node.try_get_context("vpc_name")
    vpc = aws_ec2.Vpc.from_lookup(self, "VPC",
      # is_default=True, #XXX: Whether to match the default VPC
      vpc_name=vpc_name)

    # s3_bucket_name = self.node.try_get_context('s3_bucket_name')
    # s3_bucket = s3.Bucket.from_bucket_name(self, id, s3_bucket_name)
    s3_bucket_name_suffix = self.node.try_get_context('s3_bucket_name_suffix')
    s3_bucket = s3.Bucket(self, 'TransRecentAnncmtBucket',
      # removal_policy=cdk.RemovalPolicy.DESTROY,
      bucket_name='aws-rss-feed-{region}-{suffix}'.format(region=cdk.Aws.REGION,
        suffix=s3_bucket_name_suffix))

    s3_bucket.add_lifecycle_rule(prefix='whats-new-html/', id='whats-new-html',
      abort_incomplete_multipart_upload_after=cdk.Duration.days(3),
      expiration=cdk.Duration.days(7))

    sg_use_elasticache = aws_ec2.SecurityGroup(self, 'RssFeedTransBotCacheClientSG',
      vpc=vpc,
      allow_all_outbound=True,
      description='security group for redis client used rss feed trans bot',
      security_group_name='use-rss-feed-trans-bot-redis'
    )
    cdk.Tags.of(sg_use_elasticache).add('Name', 'use-rss-feed-trans-bot-redis')

    sg_elasticache = aws_ec2.SecurityGroup(self, 'RssFeedTransBotCacheSG',
      vpc=vpc,
      allow_all_outbound=True,
      description='security group for redis used rss feed trans bot',
      security_group_name='rss-feed-trans-bot-redis'
    )
    cdk.Tags.of(sg_elasticache).add('Name', 'rss-feed-trans-bot-redis')

    sg_elasticache.add_ingress_rule(peer=sg_use_elasticache, connection=aws_ec2.Port.tcp(6379), description='use-rss-feed-trans-bot-redis')

    elasticache_subnet_group = aws_elasticache.CfnSubnetGroup(self, 'RssFeedTransBotCacheSubnetGroup',
      description='subnet group for rss-feed-trans-bot-redis',
      subnet_ids=vpc.select_subnets(subnet_type=aws_ec2.SubnetType.PRIVATE_WITH_NAT).subnet_ids,
      cache_subnet_group_name='rss-feed-trans-bot-redis'
    )

    translated_feed_cache = aws_elasticache.CfnCacheCluster(self, 'RssFeedTransBotCache',
      cache_node_type='cache.t3.small',
      num_cache_nodes=1,
      engine='redis',
      engine_version='5.0.5',
      auto_minor_version_upgrade=False,
      cluster_name='rss-feed-trans-bot-redis',
      snapshot_retention_limit=3,
      snapshot_window='17:00-19:00',
      preferred_maintenance_window='mon:19:00-mon:20:30',
      #XXX: Do not use referece for 'cache_subnet_group_name' - https://github.com/aws/aws-cdk/issues/3098
      cache_subnet_group_name=elasticache_subnet_group.cache_subnet_group_name, # Redis cluster goes to wrong VPC
      vpc_security_group_ids=[sg_elasticache.security_group_id]
    )

    #XXX: If you're going to launch your cluster in an Amazon VPC, you need to create a subnet group before you start creating a cluster.
    # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticache-cache-cluster.html#cfn-elasticache-cachecluster-cachesubnetgroupname
    translated_feed_cache.add_depends_on(elasticache_subnet_group)

    cluster = aws_ecs.Cluster(self, "ECSCluster",
      cluster_name="rssfeed-trans-bot",
      vpc=vpc
    )

    task_role_policy_doc = aws_iam.PolicyDocument()
    task_role_policy_doc.add_statements(aws_iam.PolicyStatement(**{
      "effect": aws_iam.Effect.ALLOW,
      "resources": [s3_bucket.bucket_arn, "{}/*".format(s3_bucket.bucket_arn)],
      "actions": ["s3:AbortMultipartUpload",
        "s3:GetBucketLocation",
        "s3:GetObject",
        "s3:ListBucket",
        "s3:ListBucketMultipartUploads",
        "s3:PutObject"]
    }))

    task_execution_role = aws_iam.Role(self, 'ecsScheduledTaskRole',
      role_name='ecsRssFeedTransTaskExecutionRole',
      assumed_by=aws_iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
      inline_policies = {
        "s3access": task_role_policy_doc
      },
      managed_policies=[
        aws_iam.ManagedPolicy.from_aws_managed_policy_name("service-role/AmazonECSTaskExecutionRolePolicy"),
        aws_iam.ManagedPolicy.from_aws_managed_policy_name("AmazonSESFullAccess")
      ]
    )

    #XXX: ECS Fargate Task Scheduling using existing Security Group #5213
    # https://github.com/aws/aws-cdk/issues/5213
    # https://stackoverflow.com/questions/59067514/aws-cdk-ecs-task-scheduling-specify-existing-securitygroup
    task = aws_ecs.FargateTaskDefinition(self, 'ECSTaskDef',
      cpu=512,
      memory_limit_mib=1024,
      task_role=task_execution_role
    )

    repository_name = self.node.try_get_context('container_repository_name')
    repository_arn = aws_ecr.Repository.arn_for_local_repository(repository_name,
      self, cdk.Aws.ACCOUNT_ID)

    # repository = aws_ecr.Repository.from_repository_arn(self, "Repository",
    #   repository_arn=repository_arn)
    #
    # jsii.errors.JSIIError: "repositoryArn" is a late-bound value,
    # and therefore "repositoryName" is required. Use `fromRepositoryAttributes` instead
    repository = aws_ecr.Repository.from_repository_attributes(self, "ContainerRepository",
      repository_arn=repository_arn,
      repository_name=repository_name)

    container_image_tag = self.node.try_get_context('container_image_tag')
    container_image_tag = 'latest' if not container_image_tag else container_image_tag

    DRY_RUN = self.node.try_get_context('dry_run')
    DRY_RUN = 'false' if not DRY_RUN else DRY_RUN

    TRANSLATE_ALL_FEEDS = self.node.try_get_context('translate_all_feeds')
    TRANSLATE_ALL_FEEDS = 'false' if not TRANSLATE_ALL_FEEDS else TRANSLATE_ALL_FEEDS

    TRANS_DEST_LANG = self.node.try_get_context('trans_dest_lang')
    TRANS_DEST_LANG = 'false' if not TRANS_DEST_LANG else TRANS_DEST_LANG

    EMAIL_FROM_ADDRESS = self.node.try_get_context('email_from_address')
    EMAIL_TO_ADDRESSES = self.node.try_get_context('email_to_addresses')
    task.add_container('transbot',
      image=aws_ecs.ContainerImage.from_ecr_repository(repository, tag=container_image_tag),
      environment={
        "ELASTICACHE_HOST": translated_feed_cache.attr_redis_endpoint_address,
        "DRY_RUN": DRY_RUN,
        "TRANS_DEST_LANG": TRANS_DEST_LANG,
        "TRANSLATE_ALL_FEEDS": TRANSLATE_ALL_FEEDS,
        "EMAIL_FROM_ADDRESS": EMAIL_FROM_ADDRESS,
        "EMAIL_TO_ADDRESSES": EMAIL_TO_ADDRESSES,
        "REGION_NAME": cdk.Aws.REGION
      },
      logging=aws_ecs.LogDriver.aws_logs(stream_prefix="ecs",
        log_group=aws_logs.LogGroup(self, 
          "ECSContainerLogGroup",
          log_group_name="/ecs/rss-feed-trans-bot",
          retention=aws_logs.RetentionDays.ONE_DAY,
          removal_policy=cdk.RemovalPolicy.DESTROY)
      )
    )

    # See https://docs.aws.amazon.com/lambda/latest/dg/tutorial-scheduled-events-schedule-expressions.html
    event_schedule = dict(zip(['minute', 'hour', 'month', 'week_day', 'year'],
      self.node.try_get_context('event_schedule').split(' ')))

    scheduled_event_rule = aws_events.Rule(self, 'RssFeedScheduledRule',
      enabled=True,
      schedule=aws_events.Schedule.cron(**event_schedule),
      description="Translate AWS What's New")

    ecs_events_role = aws_iam.Role(self, 'ecsEventsRole',
      role_name='ecsRssFeedTransEventsRole',
      assumed_by=aws_iam.ServicePrincipal('events.amazonaws.com'),
      managed_policies=[
        aws_iam.ManagedPolicy.from_aws_managed_policy_name("service-role/AmazonEC2ContainerServiceEventsRole")
      ]
    )

    scheduled_event_rule.add_target(aws_events_targets.EcsTask(cluster=cluster,
      task_definition=task,
      role=ecs_events_role,
      security_groups=[sg_use_elasticache],
      subnet_selection=aws_ec2.SubnetSelection(subnet_type=aws_ec2.SubnetType.PRIVATE_WITH_NAT)))
Exemple #7
0
    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        vpc = aws_ec2.Vpc(
            self,
            "OctemberVPC",
            max_azs=2,
            #      subnet_configuration=[{
            #          "cidrMask": 24,
            #          "name": "Public",
            #          "subnetType": aws_ec2.SubnetType.PUBLIC,
            #        },
            #        {
            #          "cidrMask": 24,
            #          "name": "Private",
            #          "subnetType": aws_ec2.SubnetType.PRIVATE
            #        },
            #        {
            #          "cidrMask": 28,
            #          "name": "Isolated",
            #          "subnetType": aws_ec2.SubnetType.ISOLATED,
            #          "reserved": True
            #        }
            #      ],
            gateway_endpoints={
                "S3":
                aws_ec2.GatewayVpcEndpointOptions(
                    service=aws_ec2.GatewayVpcEndpointAwsService.S3)
            })

        dynamo_db_endpoint = vpc.add_gateway_endpoint(
            "DynamoDbEndpoint",
            service=aws_ec2.GatewayVpcEndpointAwsService.DYNAMODB)

        s3_bucket = s3.Bucket(
            self,
            "s3bucket",
            bucket_name="octember-bizcard-{region}-{account}".format(
                region=core.Aws.REGION, account=core.Aws.ACCOUNT_ID))

        api = apigw.RestApi(
            self,
            "BizcardImageUploader",
            rest_api_name="BizcardImageUploader",
            description="This service serves uploading bizcard images into s3.",
            endpoint_types=[apigw.EndpointType.REGIONAL],
            binary_media_types=["image/png", "image/jpg"],
            deploy=True,
            deploy_options=apigw.StageOptions(stage_name="v1"))

        rest_api_role = aws_iam.Role(
            self,
            "ApiGatewayRoleForS3",
            role_name="ApiGatewayRoleForS3FullAccess",
            assumed_by=aws_iam.ServicePrincipal("apigateway.amazonaws.com"),
            managed_policies=[
                aws_iam.ManagedPolicy.from_aws_managed_policy_name(
                    "AmazonS3FullAccess")
            ])

        list_objects_responses = [
            apigw.IntegrationResponse(
                status_code="200",
                #XXX: https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_apigateway/IntegrationResponse.html#aws_cdk.aws_apigateway.IntegrationResponse.response_parameters
                # The response parameters from the backend response that API Gateway sends to the method response.
                # Use the destination as the key and the source as the value:
                #  - The destination must be an existing response parameter in the MethodResponse property.
                #  - The source must be an existing method request parameter or a static value.
                response_parameters={
                    'method.response.header.Timestamp':
                    'integration.response.header.Date',
                    'method.response.header.Content-Length':
                    'integration.response.header.Content-Length',
                    'method.response.header.Content-Type':
                    'integration.response.header.Content-Type'
                }),
            apigw.IntegrationResponse(status_code="400",
                                      selection_pattern="4\d{2}"),
            apigw.IntegrationResponse(status_code="500",
                                      selection_pattern="5\d{2}")
        ]

        list_objects_integration_options = apigw.IntegrationOptions(
            credentials_role=rest_api_role,
            integration_responses=list_objects_responses)

        get_s3_integration = apigw.AwsIntegration(
            service="s3",
            integration_http_method="GET",
            path='/',
            options=list_objects_integration_options)

        api.root.add_method(
            "GET",
            get_s3_integration,
            authorization_type=apigw.AuthorizationType.IAM,
            api_key_required=False,
            method_responses=[
                apigw.MethodResponse(
                    status_code="200",
                    response_parameters={
                        'method.response.header.Timestamp': False,
                        'method.response.header.Content-Length': False,
                        'method.response.header.Content-Type': False
                    },
                    response_models={'application/json': apigw.EmptyModel()}),
                apigw.MethodResponse(status_code="400"),
                apigw.MethodResponse(status_code="500")
            ],
            request_parameters={'method.request.header.Content-Type': False})

        get_s3_folder_integration_options = apigw.IntegrationOptions(
            credentials_role=rest_api_role,
            integration_responses=list_objects_responses,
            #XXX: https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_apigateway/IntegrationOptions.html#aws_cdk.aws_apigateway.IntegrationOptions.request_parameters
            # Specify request parameters as key-value pairs (string-to-string mappings), with a destination as the key and a source as the value.
            # The source must be an existing method request parameter or a static value.
            request_parameters={
                "integration.request.path.bucket": "method.request.path.folder"
            })

        get_s3_folder_integration = apigw.AwsIntegration(
            service="s3",
            integration_http_method="GET",
            path="{bucket}",
            options=get_s3_folder_integration_options)

        s3_folder = api.root.add_resource('{folder}')
        s3_folder.add_method(
            "GET",
            get_s3_folder_integration,
            authorization_type=apigw.AuthorizationType.IAM,
            api_key_required=False,
            method_responses=[
                apigw.MethodResponse(
                    status_code="200",
                    response_parameters={
                        'method.response.header.Timestamp': False,
                        'method.response.header.Content-Length': False,
                        'method.response.header.Content-Type': False
                    },
                    response_models={'application/json': apigw.EmptyModel()}),
                apigw.MethodResponse(status_code="400"),
                apigw.MethodResponse(status_code="500")
            ],
            request_parameters={
                'method.request.header.Content-Type': False,
                'method.request.path.folder': True
            })

        get_s3_item_integration_options = apigw.IntegrationOptions(
            credentials_role=rest_api_role,
            integration_responses=list_objects_responses,
            request_parameters={
                "integration.request.path.bucket":
                "method.request.path.folder",
                "integration.request.path.object": "method.request.path.item"
            })

        get_s3_item_integration = apigw.AwsIntegration(
            service="s3",
            integration_http_method="GET",
            path="{bucket}/{object}",
            options=get_s3_item_integration_options)

        s3_item = s3_folder.add_resource('{item}')
        s3_item.add_method(
            "GET",
            get_s3_item_integration,
            authorization_type=apigw.AuthorizationType.IAM,
            api_key_required=False,
            method_responses=[
                apigw.MethodResponse(
                    status_code="200",
                    response_parameters={
                        'method.response.header.Timestamp': False,
                        'method.response.header.Content-Length': False,
                        'method.response.header.Content-Type': False
                    },
                    response_models={'application/json': apigw.EmptyModel()}),
                apigw.MethodResponse(status_code="400"),
                apigw.MethodResponse(status_code="500")
            ],
            request_parameters={
                'method.request.header.Content-Type': False,
                'method.request.path.folder': True,
                'method.request.path.item': True
            })

        put_s3_item_integration_options = apigw.IntegrationOptions(
            credentials_role=rest_api_role,
            integration_responses=[
                apigw.IntegrationResponse(status_code="200"),
                apigw.IntegrationResponse(status_code="400",
                                          selection_pattern="4\d{2}"),
                apigw.IntegrationResponse(status_code="500",
                                          selection_pattern="5\d{2}")
            ],
            request_parameters={
                "integration.request.header.Content-Type":
                "method.request.header.Content-Type",
                "integration.request.path.bucket":
                "method.request.path.folder",
                "integration.request.path.object": "method.request.path.item"
            })

        put_s3_item_integration = apigw.AwsIntegration(
            service="s3",
            integration_http_method="PUT",
            path="{bucket}/{object}",
            options=put_s3_item_integration_options)

        s3_item.add_method(
            "PUT",
            put_s3_item_integration,
            authorization_type=apigw.AuthorizationType.IAM,
            api_key_required=False,
            method_responses=[
                apigw.MethodResponse(
                    status_code="200",
                    response_parameters={
                        'method.response.header.Content-Type': False
                    },
                    response_models={'application/json': apigw.EmptyModel()}),
                apigw.MethodResponse(status_code="400"),
                apigw.MethodResponse(status_code="500")
            ],
            request_parameters={
                'method.request.header.Content-Type': False,
                'method.request.path.folder': True,
                'method.request.path.item': True
            })

        ddb_table = dynamodb.Table(
            self,
            "BizcardImageMetaInfoDdbTable",
            table_name="OctemberBizcardImgMeta",
            partition_key=dynamodb.Attribute(
                name="image_id", type=dynamodb.AttributeType.STRING),
            billing_mode=dynamodb.BillingMode.PROVISIONED,
            read_capacity=15,
            write_capacity=5)

        img_kinesis_stream = kinesis.Stream(
            self, "BizcardImagePath", stream_name="octember-bizcard-image")

        # create lambda function
        trigger_textract_lambda_fn = _lambda.Function(
            self,
            "TriggerTextExtractorFromImage",
            runtime=_lambda.Runtime.PYTHON_3_7,
            function_name="TriggerTextExtractorFromImage",
            handler="trigger_text_extract_from_s3_image.lambda_handler",
            description="Trigger to extract text from an image in S3",
            code=_lambda.Code.asset(
                "./src/main/python/TriggerTextExtractFromS3Image"),
            environment={
                'REGION_NAME': core.Aws.REGION,
                'DDB_TABLE_NAME': ddb_table.table_name,
                'KINESIS_STREAM_NAME': img_kinesis_stream.stream_name
            },
            timeout=core.Duration.minutes(5))

        ddb_table_rw_policy_statement = aws_iam.PolicyStatement(
            effect=aws_iam.Effect.ALLOW,
            resources=[ddb_table.table_arn],
            actions=[
                "dynamodb:BatchGetItem", "dynamodb:Describe*",
                "dynamodb:List*", "dynamodb:GetItem", "dynamodb:Query",
                "dynamodb:Scan", "dynamodb:BatchWriteItem",
                "dynamodb:DeleteItem", "dynamodb:PutItem",
                "dynamodb:UpdateItem", "dax:Describe*", "dax:List*",
                "dax:GetItem", "dax:BatchGetItem", "dax:Query", "dax:Scan",
                "dax:BatchWriteItem", "dax:DeleteItem", "dax:PutItem",
                "dax:UpdateItem"
            ])

        trigger_textract_lambda_fn.add_to_role_policy(
            ddb_table_rw_policy_statement)
        trigger_textract_lambda_fn.add_to_role_policy(
            aws_iam.PolicyStatement(effect=aws_iam.Effect.ALLOW,
                                    resources=[img_kinesis_stream.stream_arn],
                                    actions=[
                                        "kinesis:Get*", "kinesis:List*",
                                        "kinesis:Describe*",
                                        "kinesis:PutRecord",
                                        "kinesis:PutRecords"
                                    ]))

        # assign notification for the s3 event type (ex: OBJECT_CREATED)
        s3_event_filter = s3.NotificationKeyFilter(prefix="bizcard-raw-img/",
                                                   suffix=".jpg")
        s3_event_source = S3EventSource(s3_bucket,
                                        events=[s3.EventType.OBJECT_CREATED],
                                        filters=[s3_event_filter])
        trigger_textract_lambda_fn.add_event_source(s3_event_source)

        #XXX: https://github.com/aws/aws-cdk/issues/2240
        # To avoid to create extra Lambda Functions with names like LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a
        # if log_retention=aws_logs.RetentionDays.THREE_DAYS is added to the constructor props
        log_group = aws_logs.LogGroup(
            self,
            "TriggerTextractLogGroup",
            log_group_name="/aws/lambda/TriggerTextExtractorFromImage",
            retention=aws_logs.RetentionDays.THREE_DAYS)
        log_group.grant_write(trigger_textract_lambda_fn)

        text_kinesis_stream = kinesis.Stream(
            self, "BizcardTextData", stream_name="octember-bizcard-txt")

        textract_lambda_fn = _lambda.Function(
            self,
            "GetTextFromImage",
            runtime=_lambda.Runtime.PYTHON_3_7,
            function_name="GetTextFromImage",
            handler="get_text_from_s3_image.lambda_handler",
            description="extract text from an image in S3",
            code=_lambda.Code.asset("./src/main/python/GetTextFromS3Image"),
            environment={
                'REGION_NAME': core.Aws.REGION,
                'DDB_TABLE_NAME': ddb_table.table_name,
                'KINESIS_STREAM_NAME': text_kinesis_stream.stream_name
            },
            timeout=core.Duration.minutes(5))

        textract_lambda_fn.add_to_role_policy(ddb_table_rw_policy_statement)
        textract_lambda_fn.add_to_role_policy(
            aws_iam.PolicyStatement(effect=aws_iam.Effect.ALLOW,
                                    resources=[text_kinesis_stream.stream_arn],
                                    actions=[
                                        "kinesis:Get*", "kinesis:List*",
                                        "kinesis:Describe*",
                                        "kinesis:PutRecord",
                                        "kinesis:PutRecords"
                                    ]))

        textract_lambda_fn.add_to_role_policy(
            aws_iam.PolicyStatement(
                **{
                    "effect":
                    aws_iam.Effect.ALLOW,
                    "resources": [
                        s3_bucket.bucket_arn, "{}/*".format(
                            s3_bucket.bucket_arn)
                    ],
                    "actions": [
                        "s3:AbortMultipartUpload", "s3:GetBucketLocation",
                        "s3:GetObject", "s3:ListBucket",
                        "s3:ListBucketMultipartUploads", "s3:PutObject"
                    ]
                }))

        textract_lambda_fn.add_to_role_policy(
            aws_iam.PolicyStatement(effect=aws_iam.Effect.ALLOW,
                                    resources=["*"],
                                    actions=["textract:*"]))

        img_kinesis_event_source = KinesisEventSource(
            img_kinesis_stream,
            batch_size=100,
            starting_position=_lambda.StartingPosition.LATEST)
        textract_lambda_fn.add_event_source(img_kinesis_event_source)

        log_group = aws_logs.LogGroup(
            self,
            "GetTextFromImageLogGroup",
            log_group_name="/aws/lambda/GetTextFromImage",
            retention=aws_logs.RetentionDays.THREE_DAYS)
        log_group.grant_write(textract_lambda_fn)

        sg_use_bizcard_es = aws_ec2.SecurityGroup(
            self,
            "BizcardSearchClientSG",
            vpc=vpc,
            allow_all_outbound=True,
            description=
            'security group for octember bizcard elasticsearch client',
            security_group_name='use-octember-bizcard-es')
        core.Tags.of(sg_use_bizcard_es).add('Name', 'use-octember-bizcard-es')

        sg_bizcard_es = aws_ec2.SecurityGroup(
            self,
            "BizcardSearchSG",
            vpc=vpc,
            allow_all_outbound=True,
            description='security group for octember bizcard elasticsearch',
            security_group_name='octember-bizcard-es')
        core.Tags.of(sg_bizcard_es).add('Name', 'octember-bizcard-es')

        sg_bizcard_es.add_ingress_rule(peer=sg_bizcard_es,
                                       connection=aws_ec2.Port.all_tcp(),
                                       description='octember-bizcard-es')
        sg_bizcard_es.add_ingress_rule(peer=sg_use_bizcard_es,
                                       connection=aws_ec2.Port.all_tcp(),
                                       description='use-octember-bizcard-es')

        sg_ssh_access = aws_ec2.SecurityGroup(
            self,
            "BastionHostSG",
            vpc=vpc,
            allow_all_outbound=True,
            description='security group for bastion host',
            security_group_name='octember-bastion-host-sg')
        core.Tags.of(sg_ssh_access).add('Name', 'octember-bastion-host')
        sg_ssh_access.add_ingress_rule(peer=aws_ec2.Peer.any_ipv4(),
                                       connection=aws_ec2.Port.tcp(22),
                                       description='ssh access')

        bastion_host = aws_ec2.BastionHostLinux(
            self,
            "BastionHost",
            vpc=vpc,
            instance_type=aws_ec2.InstanceType('t3.nano'),
            security_group=sg_ssh_access,
            subnet_selection=aws_ec2.SubnetSelection(
                subnet_type=aws_ec2.SubnetType.PUBLIC))
        bastion_host.instance.add_security_group(sg_use_bizcard_es)

        #XXX: aws cdk elastsearch example - https://github.com/aws/aws-cdk/issues/2873
        es_cfn_domain = aws_elasticsearch.CfnDomain(
            self,
            'BizcardSearch',
            elasticsearch_cluster_config={
                "dedicatedMasterCount": 3,
                "dedicatedMasterEnabled": True,
                "dedicatedMasterType": "t2.medium.elasticsearch",
                "instanceCount": 2,
                "instanceType": "t2.medium.elasticsearch",
                "zoneAwarenessEnabled": True
            },
            ebs_options={
                "ebsEnabled": True,
                "volumeSize": 10,
                "volumeType": "gp2"
            },
            domain_name="octember-bizcard",
            elasticsearch_version="7.9",
            encryption_at_rest_options={"enabled": False},
            access_policies={
                "Version":
                "2012-10-17",
                "Statement": [{
                    "Effect":
                    "Allow",
                    "Principal": {
                        "AWS": "*"
                    },
                    "Action":
                    ["es:Describe*", "es:List*", "es:Get*", "es:ESHttp*"],
                    "Resource":
                    self.format_arn(service="es",
                                    resource="domain",
                                    resource_name="octember-bizcard/*")
                }]
            },
            snapshot_options={"automatedSnapshotStartHour": 17},
            vpc_options={
                "securityGroupIds": [sg_bizcard_es.security_group_id],
                "subnetIds":
                vpc.select_subnets(
                    subnet_type=aws_ec2.SubnetType.PRIVATE).subnet_ids
            })
        core.Tags.of(es_cfn_domain).add('Name', 'octember-bizcard-es')

        s3_lib_bucket_name = self.node.try_get_context("lib_bucket_name")

        #XXX: https://github.com/aws/aws-cdk/issues/1342
        s3_lib_bucket = s3.Bucket.from_bucket_name(self, id,
                                                   s3_lib_bucket_name)
        es_lib_layer = _lambda.LayerVersion(
            self,
            "ESLib",
            layer_version_name="es-lib",
            compatible_runtimes=[_lambda.Runtime.PYTHON_3_7],
            code=_lambda.Code.from_bucket(s3_lib_bucket,
                                          "var/octember-es-lib.zip"))

        redis_lib_layer = _lambda.LayerVersion(
            self,
            "RedisLib",
            layer_version_name="redis-lib",
            compatible_runtimes=[_lambda.Runtime.PYTHON_3_7],
            code=_lambda.Code.from_bucket(s3_lib_bucket,
                                          "var/octember-redis-lib.zip"))

        #XXX: Deploy lambda in VPC - https://github.com/aws/aws-cdk/issues/1342
        upsert_to_es_lambda_fn = _lambda.Function(
            self,
            "UpsertBizcardToES",
            runtime=_lambda.Runtime.PYTHON_3_7,
            function_name="UpsertBizcardToElasticSearch",
            handler="upsert_bizcard_to_es.lambda_handler",
            description="Upsert bizcard text into elasticsearch",
            code=_lambda.Code.asset("./src/main/python/UpsertBizcardToES"),
            environment={
                'ES_HOST': es_cfn_domain.attr_domain_endpoint,
                'ES_INDEX': 'octember_bizcard',
                'ES_TYPE': 'bizcard'
            },
            timeout=core.Duration.minutes(5),
            layers=[es_lib_layer],
            security_groups=[sg_use_bizcard_es],
            vpc=vpc)

        text_kinesis_event_source = KinesisEventSource(
            text_kinesis_stream,
            batch_size=99,
            starting_position=_lambda.StartingPosition.LATEST)
        upsert_to_es_lambda_fn.add_event_source(text_kinesis_event_source)

        log_group = aws_logs.LogGroup(
            self,
            "UpsertBizcardToESLogGroup",
            log_group_name="/aws/lambda/UpsertBizcardToElasticSearch",
            retention=aws_logs.RetentionDays.THREE_DAYS)
        log_group.grant_write(upsert_to_es_lambda_fn)

        firehose_role_policy_doc = aws_iam.PolicyDocument()
        firehose_role_policy_doc.add_statements(
            aws_iam.PolicyStatement(
                **{
                    "effect":
                    aws_iam.Effect.ALLOW,
                    "resources": [
                        s3_bucket.bucket_arn, "{}/*".format(
                            s3_bucket.bucket_arn)
                    ],
                    "actions": [
                        "s3:AbortMultipartUpload", "s3:GetBucketLocation",
                        "s3:GetObject", "s3:ListBucket",
                        "s3:ListBucketMultipartUploads", "s3:PutObject"
                    ]
                }))

        firehose_role_policy_doc.add_statements(
            aws_iam.PolicyStatement(effect=aws_iam.Effect.ALLOW,
                                    resources=["*"],
                                    actions=[
                                        "glue:GetTable",
                                        "glue:GetTableVersion",
                                        "glue:GetTableVersions"
                                    ]))

        firehose_role_policy_doc.add_statements(
            aws_iam.PolicyStatement(effect=aws_iam.Effect.ALLOW,
                                    resources=[text_kinesis_stream.stream_arn],
                                    actions=[
                                        "kinesis:DescribeStream",
                                        "kinesis:GetShardIterator",
                                        "kinesis:GetRecords"
                                    ]))

        firehose_log_group_name = "/aws/kinesisfirehose/octember-bizcard-txt-to-s3"
        firehose_role_policy_doc.add_statements(
            aws_iam.PolicyStatement(
                effect=aws_iam.Effect.ALLOW,
                #XXX: The ARN will be formatted as follows:
                # arn:{partition}:{service}:{region}:{account}:{resource}{sep}}{resource-name}
                resources=[
                    self.format_arn(service="logs",
                                    resource="log-group",
                                    resource_name="{}:log-stream:*".format(
                                        firehose_log_group_name),
                                    sep=":")
                ],
                actions=["logs:PutLogEvents"]))

        firehose_role = aws_iam.Role(
            self,
            "FirehoseDeliveryRole",
            role_name="FirehoseDeliveryRole",
            assumed_by=aws_iam.ServicePrincipal("firehose.amazonaws.com"),
            #XXX: use inline_policies to work around https://github.com/aws/aws-cdk/issues/5221
            inline_policies={"firehose_role_policy": firehose_role_policy_doc})

        bizcard_text_to_s3_delivery_stream = aws_kinesisfirehose.CfnDeliveryStream(
            self,
            "BizcardTextToS3",
            delivery_stream_name="octember-bizcard-txt-to-s3",
            delivery_stream_type="KinesisStreamAsSource",
            kinesis_stream_source_configuration={
                "kinesisStreamArn": text_kinesis_stream.stream_arn,
                "roleArn": firehose_role.role_arn
            },
            extended_s3_destination_configuration={
                "bucketArn": s3_bucket.bucket_arn,
                "bufferingHints": {
                    "intervalInSeconds": 60,
                    "sizeInMBs": 1
                },
                "cloudWatchLoggingOptions": {
                    "enabled": True,
                    "logGroupName": firehose_log_group_name,
                    "logStreamName": "S3Delivery"
                },
                "compressionFormat": "GZIP",
                "prefix": "bizcard-text/",
                "roleArn": firehose_role.role_arn
            })

        sg_use_bizcard_es_cache = aws_ec2.SecurityGroup(
            self,
            "BizcardSearchCacheClientSG",
            vpc=vpc,
            allow_all_outbound=True,
            description=
            'security group for octember bizcard search query cache client',
            security_group_name='use-octember-bizcard-es-cache')
        core.Tags.of(sg_use_bizcard_es_cache).add(
            'Name', 'use-octember-bizcard-es-cache')

        sg_bizcard_es_cache = aws_ec2.SecurityGroup(
            self,
            "BizcardSearchCacheSG",
            vpc=vpc,
            allow_all_outbound=True,
            description=
            'security group for octember bizcard search query cache',
            security_group_name='octember-bizcard-es-cache')
        core.Tags.of(sg_bizcard_es_cache).add('Name',
                                              'octember-bizcard-es-cache')

        sg_bizcard_es_cache.add_ingress_rule(
            peer=sg_use_bizcard_es_cache,
            connection=aws_ec2.Port.tcp(6379),
            description='use-octember-bizcard-es-cache')

        es_query_cache_subnet_group = aws_elasticache.CfnSubnetGroup(
            self,
            "QueryCacheSubnetGroup",
            description="subnet group for octember-bizcard-es-cache",
            subnet_ids=vpc.select_subnets(
                subnet_type=aws_ec2.SubnetType.PRIVATE).subnet_ids,
            cache_subnet_group_name='octember-bizcard-es-cache')

        es_query_cache = aws_elasticache.CfnCacheCluster(
            self,
            "BizcardSearchQueryCache",
            cache_node_type="cache.t3.small",
            num_cache_nodes=1,
            engine="redis",
            engine_version="5.0.5",
            auto_minor_version_upgrade=False,
            cluster_name="octember-bizcard-es-cache",
            snapshot_retention_limit=3,
            snapshot_window="17:00-19:00",
            preferred_maintenance_window="mon:19:00-mon:20:30",
            #XXX: Do not use referece for "cache_subnet_group_name" - https://github.com/aws/aws-cdk/issues/3098
            #cache_subnet_group_name=es_query_cache_subnet_group.cache_subnet_group_name, # Redis cluster goes to wrong VPC
            cache_subnet_group_name='octember-bizcard-es-cache',
            vpc_security_group_ids=[sg_bizcard_es_cache.security_group_id])

        #XXX: If you're going to launch your cluster in an Amazon VPC, you need to create a subnet group before you start creating a cluster.
        # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticache-cache-cluster.html#cfn-elasticache-cachecluster-cachesubnetgroupname
        es_query_cache.add_depends_on(es_query_cache_subnet_group)

        #XXX: add more than 2 security groups
        # https://github.com/aws/aws-cdk/blob/ea10f0d141a48819ec0000cd7905feda993870a9/packages/%40aws-cdk/aws-lambda/lib/function.ts#L387
        # https://github.com/aws/aws-cdk/issues/1555
        # https://github.com/aws/aws-cdk/pull/5049
        bizcard_search_lambda_fn = _lambda.Function(
            self,
            "BizcardSearchServer",
            runtime=_lambda.Runtime.PYTHON_3_7,
            function_name="BizcardSearchProxy",
            handler="es_search_bizcard.lambda_handler",
            description="Proxy server to search bizcard text",
            code=_lambda.Code.asset("./src/main/python/SearchBizcard"),
            environment={
                'ES_HOST': es_cfn_domain.attr_domain_endpoint,
                'ES_INDEX': 'octember_bizcard',
                'ES_TYPE': 'bizcard',
                'ELASTICACHE_HOST': es_query_cache.attr_redis_endpoint_address
            },
            timeout=core.Duration.minutes(1),
            layers=[es_lib_layer, redis_lib_layer],
            security_groups=[sg_use_bizcard_es, sg_use_bizcard_es_cache],
            vpc=vpc)

        #XXX: create API Gateway + LambdaProxy
        search_api = apigw.LambdaRestApi(
            self,
            "BizcardSearchAPI",
            handler=bizcard_search_lambda_fn,
            proxy=False,
            rest_api_name="BizcardSearch",
            description="This service serves searching bizcard text.",
            endpoint_types=[apigw.EndpointType.REGIONAL],
            deploy=True,
            deploy_options=apigw.StageOptions(stage_name="v1"))

        bizcard_search = search_api.root.add_resource('search')
        bizcard_search.add_method(
            "GET",
            method_responses=[
                apigw.MethodResponse(
                    status_code="200",
                    response_models={'application/json': apigw.EmptyModel()}),
                apigw.MethodResponse(status_code="400"),
                apigw.MethodResponse(status_code="500")
            ])

        sg_use_bizcard_graph_db = aws_ec2.SecurityGroup(
            self,
            "BizcardGraphDbClientSG",
            vpc=vpc,
            allow_all_outbound=True,
            description='security group for octember bizcard graph db client',
            security_group_name='use-octember-bizcard-neptune')
        core.Tags.of(sg_use_bizcard_graph_db).add(
            'Name', 'use-octember-bizcard-neptune')

        sg_bizcard_graph_db = aws_ec2.SecurityGroup(
            self,
            "BizcardGraphDbSG",
            vpc=vpc,
            allow_all_outbound=True,
            description='security group for octember bizcard graph db',
            security_group_name='octember-bizcard-neptune')
        core.Tags.of(sg_bizcard_graph_db).add('Name',
                                              'octember-bizcard-neptune')

        sg_bizcard_graph_db.add_ingress_rule(
            peer=sg_bizcard_graph_db,
            connection=aws_ec2.Port.tcp(8182),
            description='octember-bizcard-neptune')
        sg_bizcard_graph_db.add_ingress_rule(
            peer=sg_use_bizcard_graph_db,
            connection=aws_ec2.Port.tcp(8182),
            description='use-octember-bizcard-neptune')

        bizcard_graph_db_subnet_group = aws_neptune.CfnDBSubnetGroup(
            self,
            "NeptuneSubnetGroup",
            db_subnet_group_description=
            "subnet group for octember-bizcard-neptune",
            subnet_ids=vpc.select_subnets(
                subnet_type=aws_ec2.SubnetType.PRIVATE).subnet_ids,
            db_subnet_group_name='octember-bizcard-neptune')

        bizcard_graph_db = aws_neptune.CfnDBCluster(
            self,
            "BizcardGraphDB",
            availability_zones=vpc.availability_zones,
            db_subnet_group_name=bizcard_graph_db_subnet_group.
            db_subnet_group_name,
            db_cluster_identifier="octember-bizcard",
            backup_retention_period=1,
            preferred_backup_window="08:45-09:15",
            preferred_maintenance_window="sun:18:00-sun:18:30",
            vpc_security_group_ids=[sg_bizcard_graph_db.security_group_id])
        bizcard_graph_db.add_depends_on(bizcard_graph_db_subnet_group)

        bizcard_graph_db_instance = aws_neptune.CfnDBInstance(
            self,
            "BizcardGraphDBInstance",
            db_instance_class="db.r5.large",
            allow_major_version_upgrade=False,
            auto_minor_version_upgrade=False,
            availability_zone=vpc.availability_zones[0],
            db_cluster_identifier=bizcard_graph_db.db_cluster_identifier,
            db_instance_identifier="octember-bizcard",
            preferred_maintenance_window="sun:18:00-sun:18:30")
        bizcard_graph_db_instance.add_depends_on(bizcard_graph_db)

        bizcard_graph_db_replica_instance = aws_neptune.CfnDBInstance(
            self,
            "BizcardGraphDBReplicaInstance",
            db_instance_class="db.r5.large",
            allow_major_version_upgrade=False,
            auto_minor_version_upgrade=False,
            availability_zone=vpc.availability_zones[-1],
            db_cluster_identifier=bizcard_graph_db.db_cluster_identifier,
            db_instance_identifier="octember-bizcard-replica",
            preferred_maintenance_window="sun:18:00-sun:18:30")
        bizcard_graph_db_replica_instance.add_depends_on(bizcard_graph_db)
        bizcard_graph_db_replica_instance.add_depends_on(
            bizcard_graph_db_instance)

        gremlinpython_lib_layer = _lambda.LayerVersion(
            self,
            "GremlinPythonLib",
            layer_version_name="gremlinpython-lib",
            compatible_runtimes=[_lambda.Runtime.PYTHON_3_7],
            code=_lambda.Code.from_bucket(
                s3_lib_bucket, "var/octember-gremlinpython-lib.zip"))

        #XXX: https://github.com/aws/aws-cdk/issues/1342
        upsert_to_neptune_lambda_fn = _lambda.Function(
            self,
            "UpsertBizcardToGraphDB",
            runtime=_lambda.Runtime.PYTHON_3_7,
            function_name="UpsertBizcardToNeptune",
            handler="upsert_bizcard_to_graph_db.lambda_handler",
            description="Upsert bizcard into neptune",
            code=_lambda.Code.asset(
                "./src/main/python/UpsertBizcardToGraphDB"),
            environment={
                'REGION_NAME': core.Aws.REGION,
                'NEPTUNE_ENDPOINT': bizcard_graph_db.attr_endpoint,
                'NEPTUNE_PORT': bizcard_graph_db.attr_port
            },
            timeout=core.Duration.minutes(5),
            layers=[gremlinpython_lib_layer],
            security_groups=[sg_use_bizcard_graph_db],
            vpc=vpc)

        upsert_to_neptune_lambda_fn.add_event_source(text_kinesis_event_source)

        log_group = aws_logs.LogGroup(
            self,
            "UpsertBizcardToGraphDBLogGroup",
            log_group_name="/aws/lambda/UpsertBizcardToNeptune",
            retention=aws_logs.RetentionDays.THREE_DAYS)
        log_group.grant_write(upsert_to_neptune_lambda_fn)

        sg_use_bizcard_neptune_cache = aws_ec2.SecurityGroup(
            self,
            "BizcardNeptuneCacheClientSG",
            vpc=vpc,
            allow_all_outbound=True,
            description=
            'security group for octember bizcard recommendation query cache client',
            security_group_name='use-octember-bizcard-neptune-cache')
        core.Tags.of(sg_use_bizcard_neptune_cache).add(
            'Name', 'use-octember-bizcard-es-cache')

        sg_bizcard_neptune_cache = aws_ec2.SecurityGroup(
            self,
            "BizcardNeptuneCacheSG",
            vpc=vpc,
            allow_all_outbound=True,
            description=
            'security group for octember bizcard recommendation query cache',
            security_group_name='octember-bizcard-neptune-cache')
        core.Tags.of(sg_bizcard_neptune_cache).add(
            'Name', 'octember-bizcard-neptune-cache')

        sg_bizcard_neptune_cache.add_ingress_rule(
            peer=sg_use_bizcard_neptune_cache,
            connection=aws_ec2.Port.tcp(6379),
            description='use-octember-bizcard-neptune-cache')

        recomm_query_cache_subnet_group = aws_elasticache.CfnSubnetGroup(
            self,
            "RecommQueryCacheSubnetGroup",
            description="subnet group for octember-bizcard-neptune-cache",
            subnet_ids=vpc.select_subnets(
                subnet_type=aws_ec2.SubnetType.PRIVATE).subnet_ids,
            cache_subnet_group_name='octember-bizcard-neptune-cache')

        recomm_query_cache = aws_elasticache.CfnCacheCluster(
            self,
            "BizcardRecommQueryCache",
            cache_node_type="cache.t3.small",
            num_cache_nodes=1,
            engine="redis",
            engine_version="5.0.5",
            auto_minor_version_upgrade=False,
            cluster_name="octember-bizcard-neptune-cache",
            snapshot_retention_limit=3,
            snapshot_window="17:00-19:00",
            preferred_maintenance_window="mon:19:00-mon:20:30",
            #XXX: Do not use referece for "cache_subnet_group_name" - https://github.com/aws/aws-cdk/issues/3098
            #cache_subnet_group_name=recomm_query_cache_subnet_group.cache_subnet_group_name, # Redis cluster goes to wrong VPC
            cache_subnet_group_name='octember-bizcard-neptune-cache',
            vpc_security_group_ids=[
                sg_bizcard_neptune_cache.security_group_id
            ])

        recomm_query_cache.add_depends_on(recomm_query_cache_subnet_group)

        bizcard_recomm_lambda_fn = _lambda.Function(
            self,
            "BizcardRecommender",
            runtime=_lambda.Runtime.PYTHON_3_7,
            function_name="BizcardRecommender",
            handler="neptune_recommend_bizcard.lambda_handler",
            description="This service serves PYMK(People You May Know).",
            code=_lambda.Code.asset("./src/main/python/RecommendBizcard"),
            environment={
                'REGION_NAME': core.Aws.REGION,
                'NEPTUNE_ENDPOINT': bizcard_graph_db.attr_read_endpoint,
                'NEPTUNE_PORT': bizcard_graph_db.attr_port,
                'ELASTICACHE_HOST':
                recomm_query_cache.attr_redis_endpoint_address
            },
            timeout=core.Duration.minutes(1),
            layers=[gremlinpython_lib_layer, redis_lib_layer],
            security_groups=[
                sg_use_bizcard_graph_db, sg_use_bizcard_neptune_cache
            ],
            vpc=vpc)

        #XXX: create API Gateway + LambdaProxy
        recomm_api = apigw.LambdaRestApi(
            self,
            "BizcardRecommendAPI",
            handler=bizcard_recomm_lambda_fn,
            proxy=False,
            rest_api_name="BizcardRecommend",
            description="This service serves PYMK(People You May Know).",
            endpoint_types=[apigw.EndpointType.REGIONAL],
            deploy=True,
            deploy_options=apigw.StageOptions(stage_name="v1"))

        bizcard_recomm = recomm_api.root.add_resource('pymk')
        bizcard_recomm.add_method(
            "GET",
            method_responses=[
                apigw.MethodResponse(
                    status_code="200",
                    response_models={'application/json': apigw.EmptyModel()}),
                apigw.MethodResponse(status_code="400"),
                apigw.MethodResponse(status_code="500")
            ])

        sagemaker_notebook_role_policy_doc = aws_iam.PolicyDocument()
        sagemaker_notebook_role_policy_doc.add_statements(
            aws_iam.PolicyStatement(
                **{
                    "effect":
                    aws_iam.Effect.ALLOW,
                    "resources": [
                        "arn:aws:s3:::aws-neptune-notebook",
                        "arn:aws:s3:::aws-neptune-notebook/*"
                    ],
                    "actions": ["s3:GetObject", "s3:ListBucket"]
                }))

        sagemaker_notebook_role_policy_doc.add_statements(
            aws_iam.PolicyStatement(
                **{
                    "effect":
                    aws_iam.Effect.ALLOW,
                    "resources": [
                        "arn:aws:neptune-db:{region}:{account}:{cluster_id}/*".
                        format(region=core.Aws.REGION,
                               account=core.Aws.ACCOUNT_ID,
                               cluster_id=bizcard_graph_db.
                               attr_cluster_resource_id)
                    ],
                    "actions": ["neptune-db:connect"]
                }))

        sagemaker_notebook_role = aws_iam.Role(
            self,
            'SageMakerNotebookForNeptuneWorkbenchRole',
            role_name='AWSNeptuneNotebookRole-OctemberBizcard',
            assumed_by=aws_iam.ServicePrincipal('sagemaker.amazonaws.com'),
            #XXX: use inline_policies to work around https://github.com/aws/aws-cdk/issues/5221
            inline_policies={
                'AWSNeptuneNotebook': sagemaker_notebook_role_policy_doc
            })

        neptune_wb_lifecycle_content = '''#!/bin/bash
sudo -u ec2-user -i <<'EOF'
echo "export GRAPH_NOTEBOOK_AUTH_MODE=DEFAULT" >> ~/.bashrc
echo "export GRAPH_NOTEBOOK_HOST={NeptuneClusterEndpoint}" >> ~/.bashrc
echo "export GRAPH_NOTEBOOK_PORT={NeptuneClusterPort}" >> ~/.bashrc
echo "export NEPTUNE_LOAD_FROM_S3_ROLE_ARN=''" >> ~/.bashrc
echo "export AWS_REGION={AWS_Region}" >> ~/.bashrc
aws s3 cp s3://aws-neptune-notebook/graph_notebook.tar.gz /tmp/graph_notebook.tar.gz
rm -rf /tmp/graph_notebook
tar -zxvf /tmp/graph_notebook.tar.gz -C /tmp
/tmp/graph_notebook/install.sh
EOF
'''.format(NeptuneClusterEndpoint=bizcard_graph_db.attr_endpoint,
           NeptuneClusterPort=bizcard_graph_db.attr_port,
           AWS_Region=core.Aws.REGION)

        neptune_wb_lifecycle_config_prop = aws_sagemaker.CfnNotebookInstanceLifecycleConfig.NotebookInstanceLifecycleHookProperty(
            content=core.Fn.base64(neptune_wb_lifecycle_content))

        neptune_wb_lifecycle_config = aws_sagemaker.CfnNotebookInstanceLifecycleConfig(
            self,
            'NpetuneWorkbenchLifeCycleConfig',
            notebook_instance_lifecycle_config_name=
            'AWSNeptuneWorkbenchOctemberBizcardLCConfig',
            on_start=[neptune_wb_lifecycle_config_prop])

        neptune_workbench = aws_sagemaker.CfnNotebookInstance(
            self,
            'NeptuneWorkbench',
            instance_type='ml.t2.medium',
            role_arn=sagemaker_notebook_role.role_arn,
            lifecycle_config_name=neptune_wb_lifecycle_config.
            notebook_instance_lifecycle_config_name,
            notebook_instance_name='OctemberBizcard-NeptuneWorkbench',
            root_access='Disabled',
            security_group_ids=[sg_use_bizcard_graph_db.security_group_name],
            subnet_id=bizcard_graph_db_subnet_group.subnet_ids[0])
Exemple #8
0
    def __init__(
        self,
        scope: core.Construct,
        id: str,
        dataset_metadata_filename: str,
        dataset_metadata_generator_function_name: str,
        memory: int = 1024,
        timeout: int = 30,
        concurrent: int = 100,
        code_dir: str = "./",
        **kwargs: Any,
    ) -> None:
        """Define stack."""
        super().__init__(scope, id, **kwargs)

        # add cache
        if config.VPC_ID:
            vpc = ec2.Vpc.from_lookup(
                self,
                f"{id}-vpc",
                vpc_id=config.VPC_ID,
            )
        else:
            vpc = ec2.Vpc(self, f"{id}-vpc")

        sb_group = escache.CfnSubnetGroup(
            self,
            f"{id}-subnet-group",
            description=f"{id} subnet group",
            subnet_ids=[sb.subnet_id for sb in vpc.private_subnets],
        )

        lambda_function_security_group = ec2.SecurityGroup(self,
                                                           f"{id}-lambda-sg",
                                                           vpc=vpc)
        lambda_function_security_group.add_egress_rule(
            ec2.Peer.any_ipv4(),
            connection=ec2.Port(protocol=ec2.Protocol("ALL"),
                                string_representation=""),
            description="Allow lambda security group all outbound access",
        )

        cache_security_group = ec2.SecurityGroup(self,
                                                 f"{id}-cache-sg",
                                                 vpc=vpc)

        cache_security_group.add_ingress_rule(
            lambda_function_security_group,
            connection=ec2.Port(protocol=ec2.Protocol("ALL"),
                                string_representation=""),
            description=
            "Allow Lambda security group access to Cache security group",
        )

        cache = escache.CfnCacheCluster(
            self,
            f"{id}-cache",
            cache_node_type=config.CACHE_NODE_TYPE,
            engine=config.CACHE_ENGINE,
            num_cache_nodes=config.CACHE_NODE_NUM,
            vpc_security_group_ids=[cache_security_group.security_group_id],
            cache_subnet_group_name=sb_group.ref,
        )

        logs_access = iam.PolicyStatement(
            actions=[
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents",
            ],
            resources=["*"],
        )
        ec2_network_access = iam.PolicyStatement(
            actions=[
                "ec2:CreateNetworkInterface",
                "ec2:DescribeNetworkInterfaces",
                "ec2:DeleteNetworkInterface",
            ],
            resources=["*"],
        )

        lambda_env = DEFAULT_ENV.copy()
        lambda_env.update(
            dict(
                MODULE_NAME="covid_api.main",
                VARIABLE_NAME="app",
                WORKERS_PER_CORE="1",
                LOG_LEVEL="error",
                MEMCACHE_HOST=cache.attr_configuration_endpoint_address,
                MEMCACHE_PORT=cache.attr_configuration_endpoint_port,
                DATASET_METADATA_FILENAME=dataset_metadata_filename,
                DATASET_METADATA_GENERATOR_FUNCTION_NAME=
                dataset_metadata_generator_function_name,
                PLANET_API_KEY=os.environ["PLANET_API_KEY"],
            ))

        lambda_function_props = dict(
            runtime=aws_lambda.Runtime.PYTHON_3_7,
            code=self.create_package(code_dir),
            handler="handler.handler",
            memory_size=memory,
            timeout=core.Duration.seconds(timeout),
            environment=lambda_env,
            security_groups=[lambda_function_security_group],
            vpc=vpc,
        )

        if concurrent:
            lambda_function_props[
                "reserved_concurrent_executions"] = concurrent

        lambda_function = aws_lambda.Function(self, f"{id}-lambda",
                                              **lambda_function_props)

        lambda_function.add_to_role_policy(s3_full_access_to_data_bucket)
        lambda_function.add_to_role_policy(logs_access)
        lambda_function.add_to_role_policy(ec2_network_access)

        # defines an API Gateway Http API resource backed by our "dynamoLambda" function.
        apigw.HttpApi(
            self,
            f"{id}-endpoint",
            default_integration=apigw_integrations.LambdaProxyIntegration(
                handler=lambda_function),
        )
Exemple #9
0
    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        prefix = "test"
        cidr = "192.168.0.0/16"

        # def name(s): return "{0}/{1}".format(prefix, s)
        def name(s):
            return "{0} {1}".format(prefix, s)

        # VPC
        self.vpc = ec2.CfnVPC(self,
                              "vpc",
                              cidr_block=cidr,
                              enable_dns_hostnames=True,
                              enable_dns_support=True,
                              tags=[core.CfnTag(key="Name", value=prefix)])

        # InternetGateway
        igw = ec2.CfnInternetGateway(
            self, "igw", tags=[core.CfnTag(key="Name", value=prefix)])
        igw_attachment = ec2.CfnVPCGatewayAttachment(
            self,
            "igw_attachment",
            vpc_id=self.vpc.ref,
            internet_gateway_id=igw.ref)
        dhcpoptions = ec2.CfnDHCPOptions(
            self,
            "dhcpoptions",
            domain_name="ec2.internal " + prefix,
            domain_name_servers=["AmazonProvidedDNS"],
            tags=[core.CfnTag(key="Name", value=prefix)])
        dhcpoptionsassociation = ec2.CfnVPCDHCPOptionsAssociation(
            self,
            "dhcpoptionsassociation",
            dhcp_options_id=dhcpoptions.ref,
            vpc_id=self.vpc.ref)

        # PrivateSubnetA
        # private_subnet_a = ec2.CfnSubnet(
        #     self, "private_a",
        #     vpc_id=vpc.ref,
        #     cidr_block="192.168.0.0/24",
        #     availability_zone="ap-northeast-1a",
        #     tags=[
        #         core.CfnTag(key="Name", value=name("private_a"))
        #     ]
        # )
        # PrivateSubnetC
        # private_subnet_c = ec2.CfnSubnet(
        #     self, "private_c",
        #     vpc_id=vpc.ref,
        #     cidr_block="192.168.1.0/24",
        #     availability_zone="ap-northeast-1c",
        #     tags=[
        #         core.CfnTag(key="Name", value=name("private_c"))
        #     ]
        # )

        # PublicSubnetA
        self.public_subnet_a = ec2.CfnSubnet(
            self,
            "public_a",
            vpc_id=self.vpc.ref,
            cidr_block="192.168.0.0/20",
            # availability_zone="ap-northeast-1a",
            availability_zone="us-east-1a",
            tags=[core.CfnTag(key="Name", value=prefix + " public_a")])
        # PublicSubnetC
        self.public_subnet_c = ec2.CfnSubnet(
            self,
            "public_c",
            vpc_id=self.vpc.ref,
            cidr_block="192.168.16.0/20",
            availability_zone="us-east-1c",
            tags=[core.CfnTag(key="Name", value=prefix + " public_c")])
        self.public_subnet_d = ec2.CfnSubnet(
            self,
            "public_d",
            vpc_id=self.vpc.ref,
            cidr_block="192.168.32.0/20",
            availability_zone="us-east-1d",
            tags=[core.CfnTag(key="Name", value=prefix + " public_d")])

        # EIP1 (for NATGW)
        # eip1 = ec2.CfnEIP(
        #     self, "eip1",
        #     domain="vpc",
        # )
        # eip1.add_depends_on(igw_attachment)

        # EIP2 (for NATGW)
        # eip2 = ec2.CfnEIP(
        #     self, "eip2",
        #     domain="vpc",
        # )
        # eip2.add_depends_on(igw_attachment)

        # NatGatewayA
        # natgw_a = ec2.CfnNatGateway(
        #     self, "natgw_a",
        #     allocation_id=eip1.attr_allocation_id,
        #     subnet_id=self.public_subnet_a.ref,
        #     tags=[
        #         core.CfnTag(key="Name", value=name("natgw_a"))
        #     ]
        # )
        # NatGatewayC
        # natgw_c = ec2.CfnNatGateway(
        #     self, "natgw_c",
        #     allocation_id=eip2.attr_allocation_id,
        #     subnet_id=public_subnet_c.ref,
        #     tags=[
        #         core.CfnTag(key="Name", value=name("natgw_c"))
        #     ]
        # )

        # RouteTable of PrivateSubnetA
        # rtb_private_a = ec2.CfnRouteTable(
        #     self, "rtb_private_a",
        #     vpc_id=vpc.ref,
        #     tags=[
        #         core.CfnTag(key="Name", value=name("rtb_private_a"))
        #     ]
        # )
        # ec2.CfnSubnetRouteTableAssociation(
        #     self, "rtb_private_a_association",
        #     route_table_id=rtb_private_a.ref,
        #     subnet_id=private_subnet_a.ref
        # )
        # ec2.CfnRoute(
        #     self, "route_private_a",
        #     route_table_id=rtb_private_a.ref,
        #     destination_cidr_block="0.0.0.0/0",
        #     nat_gateway_id=natgw_a.ref
        # )

        # RouteTable of PrivateSubnetC
        # rtb_private_c = ec2.CfnRouteTable(
        #     self, "rtb_private_c",
        #     vpc_id=vpc.ref,
        #     tags=[
        #         core.CfnTag(key="Name", value=name("rtb_private_c"))
        #     ]
        # )
        # ec2.CfnSubnetRouteTableAssociation(
        #     self, "rtb_private_c_association",
        #     route_table_id=rtb_private_c.ref,
        #     subnet_id=private_subnet_c.ref
        # )
        # ec2.CfnRoute(
        #     self, "route_private_c",
        #     route_table_id=rtb_private_c.ref,
        #     destination_cidr_block="0.0.0.0/0",
        #     nat_gateway_id=natgw_c.ref
        # )

        # RouteTable of PublicSubnetA
        self.rtb_public_a = ec2.CfnRouteTable(
            self,
            "rtb_public_a",
            vpc_id=self.vpc.ref,
            tags=[core.CfnTag(key="Name", value=prefix + "rtb_public_a")])
        ec2.CfnSubnetRouteTableAssociation(
            self,
            "rtb_public_a_association",
            route_table_id=self.rtb_public_a.ref,
            subnet_id=self.public_subnet_a.ref)
        ec2.CfnSubnetRouteTableAssociation(
            self,
            "rtb_public_c_association",
            route_table_id=self.rtb_public_a.ref,
            subnet_id=self.public_subnet_c.ref)
        ec2.CfnSubnetRouteTableAssociation(
            self,
            "rtb_public_d_association",
            route_table_id=self.rtb_public_a.ref,
            subnet_id=self.public_subnet_d.ref)
        ec2.CfnRoute(self,
                     "route_public_a",
                     route_table_id=self.rtb_public_a.ref,
                     destination_cidr_block="0.0.0.0/0",
                     gateway_id=igw.ref)

        # RouteTable of PublicSubnetC
        # rtb_public_c = ec2.CfnRouteTable(
        #     self, "rtb_public_c",
        #     vpc_id=vpc.ref,
        #     tags=[
        #         core.CfnTag(key="Name", value=name("rtb_public_c"))
        #     ]
        # )
        # ec2.CfnSubnetRouteTableAssociation(
        #     self, "rtb_public_c_association",
        #     route_table_id=rtb_public_c.ref,
        #     subnet_id=public_subnet_c.ref
        # )
        # ec2.CfnRoute(
        #     self, "route_public_c",
        #     route_table_id=rtb_public_c.ref,
        #     destination_cidr_block="0.0.0.0/0",
        #     gateway_id=igw.ref
        # )

        # ami_id = ec2.AmazonLinuxImage(generation = ec2.AmazonLinuxGeneration.AMAZON_LINUX_2).get_image(self).image_id

        # security_group = ec2.SecurityGroup(
        #     self,
        #     id='test',
        #     vpc=self.vpc,
        #     security_group_name='test-security-group'
        # )

        # security_group.add_ingress_rule(
        #     peer=ec2.Peer.ipv4(cidr),
        #     connection=ec2.Port.tcp(22),
        # )

        # red_web_inst = ec2.CfnInstance(self,
        #     "testInstance01",
        #     image_id = ami_id,
        #     instance_type = "t3a.micro",
        #     monitoring = False,
        #     key_name = "stg-intrinio-www01",
        #     security_group_ids=[security_group.security_group_id],
        #     block_device_mappings = [{
        #     "deviceName": "/dev/xvda",
        #     "ebs": {
        #         "volumeSize": 10,
        #         "volumeType": "io1",
        #         "iops": 150,
        #         "deleteOnTermination": True
        #             }
        #         }
        #     ],
        #     tags = [
        #         { "key": "Name", "value": prefix }
        #     ],
        #     network_interfaces = [{
        #         "deviceIndex": "0",
        #         "associatePublicIpAddress": True,
        #         "subnetId": self.public_subnet_a.ref,
        #         # "groupSet": [web_sg.security_group_id]
        #     }], #https: //github.com/aws/aws-cdk/issues/3419
        # )
        # RedisSecurityGroup
        redis_security_group = ec2.CfnSecurityGroup(
            self,
            "RedisSecurityGroup",
            group_name="stg-test-redis01",
            group_description="HTTP traffic",
            vpc_id=self.vpc.ref,
            security_group_ingress=[{
                "ipProtocol": "tcp",
                "fromPort": 6379,
                "toPort": 6379,
                "cidrIp": "192.168.0.0/16"
            }],
            security_group_egress=[{
                "ipProtocol": "tcp",
                "fromPort": 0,
                "toPort": 65535,
                "cidrIp": "0.0.0.0/0"
            }],
        )
        # MyDBSubnetGroup
        redis_subnet_group = redis.CfnSubnetGroup(
            self,
            "RedisSubnetGroup",
            cache_subnet_group_name="stg-test-redis01",
            description="stg-test-redis01",
            subnet_ids=[
                self.public_subnet_a.ref, self.public_subnet_c.ref,
                self.public_subnet_d.ref
            ])
        redis_params = {
            'auto_minor_version_upgrade': True,
            'engine': 'redis',
            'at_rest_encryption_enabled': True,
            'automatic_failover_enabled': False,
            'engine_version': '4.0.10',
            'cache_node_type': 'cache.t3.micro',
            'num_cache_clusters': 1,
            'replication_group_description': "stg-test-redis01",
            'security_group_ids': [redis_security_group.ref],
            'cache_subnet_group_name': redis_subnet_group.ref
        }

        self.redis = redis.CfnReplicationGroup(self, 'staff-redis',
                                               **redis_params)

        core.CfnOutput(self, "OutputVpc", value=self.vpc.ref)
        core.CfnOutput(self, "OutputRedis", value=self.redis.ref)
Exemple #10
0
    def __init__(self, scope: core.Stack, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)

        self.poc_config = {'api_poc': dict()}
        self.read_config()

        # shared stuff
        self._vpc = ec2.Vpc(
            self,
            'api_poc-vpc',
            cidr='10.0.0.0/23',
            max_azs=1,
            nat_gateways=1,
        )

        self._private_subnet_selection = self._vpc.select_subnets(
            subnet_type=ec2.SubnetType.PRIVATE)
        self._security_group = ec2.SecurityGroup.from_security_group_id(
            self,
            'default_sg',
            security_group_id=self._vpc.vpc_default_security_group)

        self._security_group.add_ingress_rule(description='redis',
                                              peer=self._security_group,
                                              connection=ec2.Port.tcp_range(
                                                  start_port=6379,
                                                  end_port=6379))

        self._python3_lib_layer = _lambda.LayerVersion(
            self,
            'python3-lib-layer',
            description="python3 module dependencies",
            compatible_runtimes=[
                _lambda.Runtime.PYTHON_3_7, _lambda.Runtime.PYTHON_3_6
            ],
            code=_lambda.Code.from_asset('layers/python3-lib-layer.zip'))

        # redis cache cluster
        self._cache_subnet_group = elasticache.CfnSubnetGroup(
            self,
            'cache_subnet_group',
            description='elasticache subnet group',
            subnet_ids=self._private_subnet_selection.subnet_ids,
            cache_subnet_group_name='cache-subnet-group')

        self._redis_cache = elasticache.CfnCacheCluster(
            self,
            'cache',
            cache_node_type='cache.t2.micro',
            num_cache_nodes=1,
            engine='redis',
            cache_subnet_group_name='cache-subnet-group',
            vpc_security_group_ids=[self._security_group.security_group_id],
        )
        self._redis_cache.add_depends_on(self._cache_subnet_group)

        # external API simulator lambda
        api_handler = _lambda.Function(
            self,
            "external-api",
            runtime=_lambda.Runtime.PYTHON_3_7,
            code=_lambda.Code.asset('lambda'),
            handler='external_api.handler',
            layers=[self._python3_lib_layer],
            vpc=self._vpc,
            vpc_subnets=self._private_subnet_selection,
            security_group=self._security_group,
            log_retention=logs.RetentionDays.FIVE_DAYS,
            tracing=_lambda.Tracing.ACTIVE)
        api_handler.add_environment('REDIS_ADDRESS', self.redis_address)
        api_handler.add_environment('REDIS_PORT', self.redis_port)

        # API Gateway frontend to simulator lambda
        self._api_gateway = apigw.LambdaRestApi(
            self,
            'external_api',
            description='external API emulator',
            options=apigw.StageOptions(stage_name='dev'),
            handler=api_handler,
            proxy=True)

        job_dlq = sqs.Queue(self, 'job-dlq')

        job_queue = sqs.Queue(self,
                              'job-queue',
                              dead_letter_queue=sqs.DeadLetterQueue(
                                  queue=job_dlq, max_receive_count=3))

        throttle_event_topic = sns.Topic(self, 'throttle-events-topic')

        self.add_sns_subscriptions(throttle_event_topic)

        worker = _lambda.Function(self,
                                  'worker',
                                  runtime=_lambda.Runtime.PYTHON_3_7,
                                  code=_lambda.Code.asset('lambda'),
                                  handler='worker.handler',
                                  layers=[self._python3_lib_layer],
                                  reserved_concurrent_executions=20,
                                  timeout=core.Duration.minutes(1),
                                  vpc=self._vpc,
                                  vpc_subnets=self._private_subnet_selection,
                                  security_group=self._security_group,
                                  log_retention=logs.RetentionDays.FIVE_DAYS,
                                  tracing=_lambda.Tracing.ACTIVE,
                                  dead_letter_queue_enabled=False)
        worker.add_environment('API_KEY', '212221848ab214821de993a9d')
        worker.add_environment('JOB_QUEUE_URL', job_queue.queue_url)
        worker.add_environment('THROTTLE_EVENTS_TOPIC',
                               throttle_event_topic.topic_arn)
        worker.add_environment('REDIS_ADDRESS', self.redis_address)
        worker.add_environment('REDIS_PORT', self.redis_port)
        job_queue.grant_send_messages(worker)
        throttle_event_topic.grant_publish(worker)

        orchestrator = _lambda.Function(
            self,
            'orchestrator',
            runtime=_lambda.Runtime.PYTHON_3_7,
            code=_lambda.Code.asset('lambda'),
            handler='orchestrator.handler',
            layers=[self._python3_lib_layer],
            reserved_concurrent_executions=1,
            timeout=core.Duration.minutes(2),
            vpc=self._vpc,
            vpc_subnets=self._private_subnet_selection,
            security_group=self._security_group,
            log_retention=logs.RetentionDays.FIVE_DAYS,
            tracing=_lambda.Tracing.ACTIVE,
        )
        orchestrator.add_environment('API_HOST_URL', self._api_gateway.url)
        orchestrator.add_environment('JOB_QUEUE_URL', job_queue.queue_url)
        orchestrator.add_environment('JOB_DLQ_URL', job_dlq.queue_url)
        orchestrator.add_environment('THROTTLE_EVENTS_TOPIC',
                                     throttle_event_topic.topic_arn)
        orchestrator.add_environment('REDIS_ADDRESS', self.redis_address)
        orchestrator.add_environment('REDIS_PORT', self.redis_port)
        orchestrator.add_environment('WORKER_FUNCTION_ARN',
                                     worker.function_arn)
        job_queue.grant_consume_messages(orchestrator)
        job_dlq.grant_send_messages(orchestrator)
        throttle_event_topic.grant_publish(orchestrator)
        worker.grant_invoke(orchestrator)

        task_master = _lambda.Function(
            self,
            'task_master',
            runtime=_lambda.Runtime.PYTHON_3_7,
            code=_lambda.Code.asset('lambda'),
            handler='task_master.handler',
            layers=[self._python3_lib_layer],
            reserved_concurrent_executions=1,
            vpc=self._vpc,
            vpc_subnets=self._private_subnet_selection,
            security_group=self._security_group,
            log_retention=logs.RetentionDays.FIVE_DAYS,
            tracing=_lambda.Tracing.ACTIVE,
        )
        task_master.add_environment('SQS_URL', job_queue.queue_url)
        task_master.add_environment('REDIS_ADDRESS', self.redis_address)
        task_master.add_environment('REDIS_PORT', self.redis_port)
        task_master.add_environment('API_HOST_URL', self._api_gateway.url)
        job_queue.grant_send_messages(task_master)

        slack_notify = _lambda.Function(
            self,
            'slack-notify',
            runtime=_lambda.Runtime.PYTHON_3_7,
            code=_lambda.Code.from_asset('lambda'),
            handler='slack_notify.lambda_handler',
            log_retention=logs.RetentionDays.FIVE_DAYS,
            tracing=_lambda.Tracing.ACTIVE,
        )
        # lambda uses ssm parameter store to retrieve values
        slack_notify.add_environment('encryptedHookUrlKey',
                                     '/api_poc/notify/slack/hook_url')
        slack_notify.add_environment('slackChannelKey',
                                     '/api_poc/notify/slack/channel')
        slack_notify.add_environment('notifySlack', 'false')
        slack_notify.add_event_source(
            event_sources.SnsEventSource(throttle_event_topic))
        slack_notify.add_to_role_policy(
            iam.PolicyStatement(
                effect=iam.Effect.ALLOW,
                # TODO fix least privilege
                # actions=['ssm:GetParameter'],
                # resources=['arn:aws:ssm:::parameter/api_poc/notify/slack/*'],
                actions=['ssm:*'],
                resources=['*'],
            ))

        # kick off lambda(s) once per interval
        rule = events.Rule(self,
                           'orchestrator_rule',
                           schedule=events.Schedule.rate(
                               core.Duration.hours(1)))
        # See https://docs.aws.amazon.com/lambda/latest/dg/tutorial-scheduled-events-schedule-expressions.html
        rule.add_target(targets.LambdaFunction(orchestrator))
        rule.add_target(targets.LambdaFunction(task_master))

        # stack outputs
        core.CfnOutput(self,
                       'Redis_Address',
                       value=self._redis_cache.attr_redis_endpoint_address +
                       ':' + self._redis_cache.attr_redis_endpoint_port)
Exemple #11
0
    def __init__(self, scope: core.Construct, id: str, env: core.Environment,
                 **kwargs) -> None:
        super().__init__(scope, id, env=env, *kwargs)

        # Create a /16 VPC with 1 public and private subnet
        # CIDR range will be divided evenly among subnets
        vpc = ec2.Vpc(self, 'bot-vpc', max_azs=2)

        # Create a Redis instance
        redis_security_group = ec2.SecurityGroup(self,
                                                 'redis-security-group',
                                                 vpc=vpc)
        redis_security_group.add_ingress_rule(
            peer=ec2.Peer.ipv4(vpc.vpc_cidr_block),
            connection=ec2.Port.tcp(6379),
            description='Allow connection from within VPC')

        redis_subnetgroup = ec.CfnSubnetGroup(
            self,
            'redis-subnetgroup',
            description="Group of private subnets from the VPC",
            subnet_ids=[ps.subnet_id for ps in vpc.private_subnets])

        redis_cluster = ec.CfnCacheCluster(
            self,
            'redis-cluster',
            cache_node_type='cache.t2.small',
            engine='redis',
            num_cache_nodes=1,
            port=6379,
            cache_subnet_group_name=redis_subnetgroup.ref,
            vpc_security_group_ids=[redis_security_group.security_group_id])
        redis_cluster.add_depends_on(redis_subnetgroup)

        #  Create a cluster
        cluster = ecs.Cluster(self, 'bot-cluster', vpc=vpc)

        # Create a Worker task definition
        worker_logging = ecs.AwsLogDriver(stream_prefix='worker')

        worker_task = ecs.FargateTaskDefinition(self,
                                                'worker-task',
                                                cpu=256,
                                                memory_limit_mib=512)
        worker_task.add_container(
            id='worker-container',
            image=ecs.ContainerImage.from_registry(
                'leonweecs/cosmos-worker:1.1'),
            environment={
                'REDIS_HOST': redis_cluster.attr_redis_endpoint_address,
                'REDIS_PORT': redis_cluster.attr_redis_endpoint_port
            },
            logging=worker_logging).add_port_mappings(
                ecs.PortMapping(container_port=80, host_port=80))

        # Create Worker Service
        worker_service = ecs_patterns.ApplicationLoadBalancedFargateService(
            self,
            'worker-service',
            cluster=cluster,
            assign_public_ip=False,
            cpu=256,
            memory_limit_mib=512,
            task_definition=worker_task,
            desired_count=1,
            protocol=elb.ApplicationProtocol.
            HTTP  # HTTPS requires valid domain name and SSL certificate
        )

        # Add a rule to allow ELB to talk with containers
        worker_service.service.connections.security_groups[0].add_ingress_rule(
            peer=ec2.Peer.ipv4(vpc.vpc_cidr_block),
            connection=ec2.Port.tcp(80),
            description='Allow http inbound from VPC')

        # Configure ELB health check route
        worker_service.target_group.configure_health_check(
            path='/worker',
            healthy_http_codes='200-299',
        )

        # Setup AutoScaling policy
        scaling = worker_service.service.auto_scale_task_count(max_capacity=3)
        scaling.scale_on_cpu_utilization(
            'CpuScaling',
            target_utilization_percent=65,
            scale_in_cooldown=core.Duration.seconds(60),
            scale_out_cooldown=core.Duration.seconds(60),
        )

        # Create a MX task definition
        mx_logging = ecs.AwsLogDriver(stream_prefix='mx')

        mx_task = ecs.FargateTaskDefinition(self,
                                            'mx-task',
                                            cpu=256,
                                            memory_limit_mib=512)
        mx_task.add_container(
            id='mx-container',
            image=ecs.ContainerImage.from_registry('leonweecs/cosmos-mx:1.1'),
            environment={
                'REDIS_HOST':
                redis_cluster.attr_redis_endpoint_address,
                'REDIS_PORT':
                redis_cluster.attr_redis_endpoint_port,
                'WORKER_HOST':
                worker_service.load_balancer.load_balancer_dns_name,
                'WORKER_PORT':
                '80',
                'API_ID':
                ssm.StringParameter.value_for_string_parameter(self, "API_ID"),
                'API_HASH':
                ssm.StringParameter.value_for_string_parameter(
                    self, "API_HASH"),
                'BOT_TOKEN':
                ssm.StringParameter.value_for_string_parameter(
                    self, "BOT_TOKEN"),
            },
            logging=mx_logging)

        # Create a MX service
        mx_security_group = ec2.SecurityGroup(self,
                                              'mx-security-group',
                                              vpc=vpc)

        mx_service = ecs.FargateService(self,
                                        'mx-service',
                                        task_definition=mx_task,
                                        assign_public_ip=True,
                                        security_group=mx_security_group,
                                        cluster=cluster)

        core.CfnOutput(
            self,
            "LoadBalancerDNS",
            value=worker_service.load_balancer.load_balancer_dns_name)
Exemple #12
0
    def __init__(self, scope: core.Construct, id: str, context: InfraContext,
                 **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        self.security_group = ec2.SecurityGroup(
            self,
            'FriendlyNamedSvc-SG',
            vpc=context.networking.vpc,
            allow_all_outbound=True,
            description='Security group for FriendlyNamed service components')

        self.security_group.add_ingress_rule(
            peer=ec2.Peer.any_ipv4(),
            connection=ec2.Port(protocol=ec2.Protocol.TCP,
                                string_representation='RedisInbound',
                                from_port=6379,
                                to_port=6379))

        self.subnet_group = ec.CfnSubnetGroup(
            self,
            'CacheSubnets',
            cache_subnet_group_name='FriendlyNamed-Subnets',
            description='Subnet groups for FriendlyNamed service',
            subnet_ids=[
                net.subnet_id
                for net in context.networking.vpc._select_subnet_objects(
                    subnet_group_name='FriendlyNamed')
            ])

        self.cluster = ec.CfnCacheCluster(
            self,
            'FriendlyNamedStore',
            cache_node_type="cache.t2.micro",
            engine='redis',
            cluster_name='friendly-named',
            num_cache_nodes=1,
            auto_minor_version_upgrade=True,
            cache_subnet_group_name=self.subnet_group.cache_subnet_group_name,
            vpc_security_group_ids=[self.security_group.security_group_id])

        self.python_lambda = PythonLambda(
            self,
            'Friendly-Named',
            build_prefix='artifacts/FinSurf-Friendly-Named',
            handler='handler.app',
            subnet_group_name='FriendlyNamed',
            context=context,
            securityGroups=[self.security_group])

        self.python_lambda.function.add_environment(
            key='REDIS_HOST', value=self.cluster.attr_redis_endpoint_address)
        self.python_lambda.function.add_environment(
            key='REDIS_PORT', value=self.cluster.attr_redis_endpoint_port)

        self.frontend_proxy = LambdaProxyConstruct(
            self,
            'FriendlyNamedAPI',
            handler=self.python_lambda.function,
            context=context)

        self.url = self.frontend_proxy.rest_api.url
Exemple #13
0
    def create_redis(stack, vpc, is_group=False):
        print(vpc.private_subnets)
        subnetGroup = ec.CfnSubnetGroup(
            stack,
            "RedisClusterPrivateSubnetGroup-test",
            cache_subnet_group_name="recommendations-redis-subnet-group-test",
            description="Redis subnet for recommendations",
            subnet_ids=[subnet.subnet_id for subnet in vpc.private_subnets]
        )

        redis_security_group = ec2.SecurityGroup(
            stack, 
            "redis-security-group-test", 
            vpc=vpc
        )

        redis_connections = ec2.Connections(
            security_groups=[redis_security_group], 
            default_port=ec2.Port.tcp(6379)
        )
        redis_connections.allow_from_any_ipv4(port_range=ec2.Port.tcp(6379))


        if is_group:
            #group
            redis = ec.CfnReplicationGroup(
                stack,
                "RecommendationsRedisCacheCluster",
                engine="redis",
                cache_node_type="cache.t2.small",
                replicas_per_node_group=1,
                num_node_groups=3,
                replication_group_description="redis-gw-test",
                automatic_failover_enabled=True,
                security_group_ids=[redis_security_group.security_group_id],
                cache_subnet_group_name=subnetGroup.cache_subnet_group_name
            )
        else:
            # one node
            redis = ec.CfnCacheCluster(
                stack,
                "RecommendationsRedisCacheCluster",
                engine="redis",
                cache_node_type="cache.t2.small",
                num_cache_nodes=1,
                cluster_name="redis-gw-test",
                vpc_security_group_ids=[redis_security_group.security_group_id],
                cache_subnet_group_name=subnetGroup.cache_subnet_group_name
            )


        # no python sample, this is nodejs sample for group mode
        '''
        const redisReplication = new CfnReplicationGroup(
            this,
            `RedisReplicaGroup`,
            {
                engine: "redis",
                cacheNodeType: "cache.m5.xlarge",
                replicasPerNodeGroup: 1,
                numNodeGroups: 3,
                automaticFailoverEnabled: true,
                autoMinorVersionUpgrade: true,
                replicationGroupDescription: "cluster redis di produzione",
                cacheSubnetGroupName: redisSubnetGroup.cacheSubnetGroupName
            }
            );
        '''
        
        redis.add_depends_on(subnetGroup)

        if is_group:
            return redis.attr_primary_end_point_address,redis.attr_primary_end_point_port
        else:
            return redis.attr_redis_endpoint_address, redis.attr_redis_endpoint_port
    def __init__(self, scope: core.Construct, id: str, vpc, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        # Read BootStrap Script
        try:
            with open("bootstrap_scripts/install_httpd.sh", mode="r") as file:
                user_data = file.read()
        except OSError:
            print('Unable to read UserData script')

        # Get the latest AMI from AWS SSM
        linux_ami = _ec2.AmazonLinuxImage(
            generation=_ec2.AmazonLinuxGeneration.AMAZON_LINUX_2,
            edition=_ec2.AmazonLinuxEdition.STANDARD,
            virtualization=_ec2.AmazonLinuxVirt.HVM,
            storage=_ec2.AmazonLinuxStorage.GENERAL_PURPOSE)

        # Read BootStrap Script
        try:
            with open("bootstrap_scripts/install_httpd.sh", mode="r") as file:
                user_data = file.read()
        except OSError:
            print('Unable to read UserData script')

        # Get the latest ami
        amzn_linux_ami = _ec2.MachineImage.latest_amazon_linux(
            generation=_ec2.AmazonLinuxGeneration.AMAZON_LINUX_2)
        # ec2 Instance Role
        _instance_role = _iam.Role(
            self,
            "webAppClientRole",
            assumed_by=_iam.ServicePrincipal('ec2.amazonaws.com'),
            managed_policies=[
                _iam.ManagedPolicy.from_aws_managed_policy_name(
                    'AmazonSSMManagedInstanceCore'),
                _iam.ManagedPolicy.from_aws_managed_policy_name(
                    'AmazonS3ReadOnlyAccess')
            ])
        # web_app_client Instance
        web_app_client = _ec2.Instance(
            self,
            "webAppClient",
            instance_type=_ec2.InstanceType(
                instance_type_identifier="t2.micro"),
            instance_name="web_app_Client",
            machine_image=amzn_linux_ami,
            vpc=vpc,
            vpc_subnets=_ec2.SubnetSelection(
                subnet_type=_ec2.SubnetType.PUBLIC),
            role=_instance_role,
            user_data=_ec2.UserData.custom(user_data))

        # S3 Bucket
        app_data_bkt = _s3.Bucket(self,
                                  "appDataBkt",
                                  removal_policy=core.RemovalPolicy.DESTROY)

        output_0 = core.CfnOutput(
            self,
            "AutomationFrom",
            value=f"{global_args.SOURCE_INFO}",
            description=
            "To know more about this automation stack, check out our github page."
        )

        output_1 = core.CfnOutput(
            self,
            "ApplicationClient",
            value=web_app_client.instance_id,
            description=
            f"This instance can be used as app client for testing performance")
        output_2 = core.CfnOutput(
            self,
            "MonitoredS3Bucket",
            value=(
                #   f"https://console.aws.amazon.com/s3/buckets/"
                f"{app_data_bkt.bucket_name}"),
            description=f"S3 Bucket to host application data")

        # Security Group for redis
        redis_sg = _ec2.SecurityGroup(
            self,
            'redisSecurityGroup',
            vpc=vpc,
            security_group_name='RedisSG',
            description="Security Group for Redis Cache",
            allow_all_outbound=True)

        # Allows Cache Cluster to receive traffic from the VPC on port 6379
        redis_sg.add_ingress_rule(
            _ec2.Peer.ipv4(vpc.vpc_cidr_block),
            # _ec2.Peer.any_ipv4(),
            _ec2.Port.tcp(6379),
            description="Allow Clients to fetch data from Redis Cache Cluster")

        # Iterate the private subnets
        pvt_subnets = vpc.select_subnets(subnet_type=_ec2.SubnetType.PRIVATE)

        # Create the Redis Subnet Group
        redis_subnet_group = _elasticache.CfnSubnetGroup(
            self,
            'redis-sg',
            subnet_ids=pvt_subnets.subnet_ids,
            description='subnet group for redis')

        # Apparently no CDK Construct(yet Mar2020) for ElastiCache. No love there
        # https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_elasticache/CfnCacheCluster.html
        # Lets use the CFn Construct
        redis_cluster = _elasticache.CfnCacheCluster(
            self,
            'redisCluster',
            cache_node_type='cache.t3.micro',
            engine='redis',
            num_cache_nodes=1,
            port=6379,
            cluster_name='miztiik-cluster',
            cache_subnet_group_name=redis_subnet_group.ref,
            vpc_security_group_ids=[redis_sg.security_group_id],
            auto_minor_version_upgrade=True)
        redis_cluster.add_depends_on(redis_subnet_group)

        output_3 = core.CfnOutput(
            self,
            'redisSg',
            value=redis_sg.security_group_id,
            export_name='redisSg',
            description='The ElastiCache Cluster Security Group Id')
        output_4 = core.CfnOutput(
            self,
            'redisClusterEndpoint',
            value=redis_cluster.attr_redis_endpoint_address,
            description='The endpoint of the ElastiCache Cluster')
        output_5 = core.CfnOutput(
            self,
            'redisClusterPort',
            value=redis_cluster.attr_redis_endpoint_port,
            description='The port of the ElastiCache Cluster')

        # Lets load some dummy data into the ES Cluster & S3 using a custom lambda custom_resource
        ingest_data_redis = redis_data_ingester(
            self,
            "ingestData",
            config_params={
                "REDIS_HOST": redis_cluster.attr_redis_endpoint_address,
                'REDIS_PORT': '6379',
                "BUCKET_NAME": app_data_bkt.bucket_name,
                'RECORD_COUNT': '200',
                'BUCKET': app_data_bkt,
                'VPC': vpc,
                'REDIS_SG': redis_sg
            },
            message=[{
                "REDIS_HOST": redis_cluster.attr_redis_endpoint_address,
                'REDIS_PORT': '6379',
                "BUCKET_NAME": app_data_bkt.bucket_name,
                'RECORD_COUNT': '200'
            }])
        """