def make_ssm_parameter(self, base_param_name: str, param_value: str, description: str): ''' Creates and tags an SSM Parameter Parameters ---------- base_param_name : str The base parameter name, wihtout the application namespace prefix param_value : str parameter value description : str parameter description ''' param = ssm.StringParameter( self, base_param_name, parameter_name="%s_%s" % (self.APPLICATION_PREFIX.upper(), base_param_name), string_value=param_value, description=description #,type=ssm.ParameterType.SECURE_STRING ) util.tag_resource(param, base_param_name, description) return param
def make_ecr_repo(self, repo_suffix : str, repo_description : str): ''' Creates an ECR repo Parameters ---------- repo_suffix : str The suffix of the repo. The full repo name is APPLICATION_PREFIX _ repo_suffix ''' repo_name = "%s-%s" % (self.APPLICATION_PREFIX, repo_suffix) repo_description = "%s %s" % (self.APPLICATION_PREFIX, repo_description) ecr_repo = ecr.Repository(self, repo_name, repository_name=repo_name, removal_policy=core.RemovalPolicy.DESTROY) util.tag_resource(ecr_repo, repo_name, repo_description) return ecr_repo
def make_codebuild_project( self, project_suffix : str, description : str, buildspec_path : str, env_variables : dict): ''' Creates a codebuild project Parameters ---------- project_suffix : str The suffix of the project. The full project name is APPLICATION_PREFIX _ project_suffix description : str Description used by tags buildspec_path : str the path the buildspec used to build this project env_variables : str The environment variables supplued to the project, e.g. the ECR epo URI ''' project_name = "%s-%s" % (self.APPLICATION_PREFIX, project_suffix) build_project = codebuild.Project( self, project_name, source=codebuild.Source.git_hub(owner=self.GITHUB_REPO_OWNER, repo=self.GITHUB_REPO_NAME), build_spec=codebuild.BuildSpec.from_source_filename(buildspec_path), description=description, environment_variables=env_variables, environment=codebuild.BuildEnvironment( privileged=True, ), project_name=project_name, role=self.codebuild_role_name, timeout=core.Duration.hours(1)) util.tag_resource(build_project, project_name, description)
def make_fargate_scheduled_task( self, scheduled_task_name: str, task_definition_description: str, task_ecr_repo: object, cloudwatch_loggroup_name: str, container_commands: list, container_secrets: dict, scheduled_task_description: str, scheduled_task_cron_expression: str): ''' Creates a Fargate Task definition, then scheduled task and associates it with the ECS cluster, applied tags, etc. Parameters ---------- scheduled_task_name : str The name of the contsruct being created. Used to form the names of the various resources task_definition_description : str Description used for tags cloudwatch_loggroup_name : str Name of log group used by the container container_commands : str List of commands supplied to the container container_secrets : str Secrets supplied to the container scheduled_task_description : str Description used for tags scheduled_task_cron_expression : str Task schedule's chron expresion ''' task_definition_name = "%s-%s-task-definition" % ( self.APPLICATION_PREFIX, scheduled_task_name) fargate_task = ecs.FargateTaskDefinition( self, task_definition_name, cpu=512, memory_limit_mib=1024, execution_role=self.ecs_task_exec_role, family=None, task_role=self.props['ecs_task_role'], ) fargate_task.add_container( "%s-%s-container" % (self.APPLICATION_PREFIX, scheduled_task_name), image=ecs.ContainerImage.from_ecr_repository( task_ecr_repo, "latest"), logging=ecs.LogDriver.aws_logs( stream_prefix=self.APPLICATION_PREFIX, log_group=logs.LogGroup( self, "%s-%s-cloudwatch-loggroup" % (self.APPLICATION_PREFIX, scheduled_task_name), log_group_name="%s%s" % (self.APPLICATION_PREFIX, cloudwatch_loggroup_name), retention=logs.RetentionDays.ONE_MONTH, removal_policy=core.RemovalPolicy.DESTROY)), command=container_commands, secrets=container_secrets) util.tag_resource(fargate_task, task_definition_name, task_definition_description) scheduled_task_name = "%s-%s-scheduled-task" % ( self.APPLICATION_PREFIX, scheduled_task_name) ecs_sched_task = ecs_patterns.ScheduledFargateTask( self, scheduled_task_name, scheduled_fargate_task_definition_options=ecs_patterns. ScheduledFargateTaskDefinitionOptions( task_definition=fargate_task), schedule=asg.Schedule.expression(scheduled_task_cron_expression), cluster=self.props['ecs_fargate_task_cluster'], vpc=self.props['ecs_fargate_task_cluster'], subnet_selection=ec2.SubnetSelection( subnet_type=ec2.SubnetType(ec2.SubnetType.PUBLIC))) util.tag_resource(ecs_sched_task, scheduled_task_name, scheduled_task_description)
def __init__(self, scope: core.Construct, id: str, props: dict, **kwargs): super().__init__(scope, id, **kwargs) self.output_props = props.copy() self.props = props r_a_prefix = util.get_region_acct_prefix(kwargs['env']) self.APPLICATION_PREFIX = self.props['APPLICATION_PREFIX'] ''' ECR Repo ''' self.repo_recommendation_service = self.make_ecr_repo( "recommendation-service", "Recommendation Service") self.rep_portfolio_manager = self.make_ecr_repo( "portfolio-manager-service", "Portfolio Manager Service") ''' IAM Role and Policy used by Fargate to execute task ''' policy_name = "policy-%s-ecs-task-execution" % self.APPLICATION_PREFIX docker_exec_policy = iam.ManagedPolicy(self, policy_name) docker_exec_policy.add_statements( iam.PolicyStatement(actions=[ "ec2:AttachNetworkInterface", "ec2:CreateNetworkInterface", "ec2:CreateNetworkInterfacePermission", "ec2:DeleteNetworkInterface", "ec2:DeleteNetworkInterfacePermission", "ec2:Describe*", "ec2:DetachNetworkInterface", "elasticloadbalancing:DeregisterInstancesFromLoadBalancer", "elasticloadbalancing:DeregisterTargets", "elasticloadbalancing:Describe*", "elasticloadbalancing:RegisterInstancesWithLoadBalancer", "elasticloadbalancing:RegisterTargets", "route53:ChangeResourceRecordSets", "route53:CreateHealthCheck", "route53:DeleteHealthCheck", "route53:Get*", "route53:List*", "route53:UpdateHealthCheck", "servicediscovery:DeregisterInstance", "servicediscovery:Get*", "servicediscovery:List*", "servicediscovery:RegisterInstance", "servicediscovery:UpdateInstanceCustomHealthStatus" ], conditions=None, effect=iam.Effect.ALLOW, resources=["*"])) docker_exec_policy.add_statements( iam.PolicyStatement( actions=[ "cloudwatch:DeleteAlarms", "cloudwatch:DescribeAlarms", "cloudwatch:PutMetricAlarm" ], conditions=None, effect=iam.Effect.ALLOW, resources=["arn:aws:cloudwatch:%s:alarm:*" % r_a_prefix])) docker_exec_policy.add_statements( iam.PolicyStatement( actions=["ec2:CreateTags"], conditions=None, effect=iam.Effect.ALLOW, resources=["arn:aws:ec2:%s:network-interface/*" % r_a_prefix])) docker_exec_policy.add_statements( iam.PolicyStatement(actions=[ "logs:CreateLogGroup", "logs:DescribeLogGroups", "logs:PutRetentionPolicy" ], conditions=None, effect=iam.Effect.ALLOW, resources=[ "arn:aws:logs:%s:log-group:/aws/ecs/*" % r_a_prefix ])) docker_exec_policy.add_statements( iam.PolicyStatement( actions=[ "logs:CreateLogStream", "logs:DescribeLogStreams", "logs:PutLogEvents" ], conditions=None, effect=iam.Effect.ALLOW, resources=[ "arn:aws:logs:%s:log-group:/aws/ecs/*:log-stream:*" % r_a_prefix ])) exec_role_name = "role-%s-ecs-task-execution" % self.APPLICATION_PREFIX self.ecs_task_exec_role = iam.Role( self, exec_role_name, assumed_by=iam.ServicePrincipal("ecs-tasks.amazonaws.com"), description="%s Execution role assumed by ECS" % self.APPLICATION_PREFIX, managed_policies=[docker_exec_policy], role_name=exec_role_name) util.tag_resource( self.ecs_task_exec_role, exec_role_name, "IAM Role and Policy used by Fargate to execute task") ''' Parameter Store variables: Intrinio API Key TDAMeritrade Client ID and Refresh Token ''' intrinio_api_key_name = 'INTRINIO_API_KEY' self.intrinio_api_key_param = self.make_ssm_parameter( intrinio_api_key_name, 'put_api_key_here', 'API Key used to access Intrinio financial data') td_ameritrade_account_id_name = 'TDAMERITRADE_ACCOUNT_ID' self.tdameritrade_account_id = self.make_ssm_parameter( td_ameritrade_account_id_name, 'put_account_id_here', 'The TDAmeritrade Account ID') td_ameritrade_client_id_name = 'TDAMERITRADE_CLIENT_ID' self.tdameritrade_client_id = self.make_ssm_parameter( td_ameritrade_client_id_name, 'put_client_id_here', 'The Client Key used to authenticate the application') td_ameritrade_refresh_token_name = 'TDAMERITRADE_REFRESH_TOKEN' self.tdameritrade_refresh_token = self.make_ssm_parameter( td_ameritrade_refresh_token_name, 'put_refresh_token_here', 'OAuth refresh token used to generate temporary Access Keys') ''' Fargate Tasks: 1) Recommendation Service 2) Portfolio Manager ''' self.make_fargate_scheduled_task( "recommendation-service", "Recommendation service task definition", self.repo_recommendation_service, "/ecs/recommendation-service", [ '-ticker_file', 'djia30.txt', '-output_size', '3', 'production', '-app_namespace', self.APPLICATION_PREFIX ], { intrinio_api_key_name: ecs.Secret.from_ssm_parameter(self.intrinio_api_key_param) }, "Recommendation service monthly scheduled task", "cron(0 10 ? * MON-FRI *)") self.make_fargate_scheduled_task( "portfolio-manager-service", "Portfolio Manager service task definition", self.rep_portfolio_manager, "/ecs/portfolio-manager", [ '-app_namespace', self.APPLICATION_PREFIX, "-portfolio_size", "3" ], { intrinio_api_key_name: ecs.Secret.from_ssm_parameter(self.intrinio_api_key_param), td_ameritrade_account_id_name: ecs.Secret.from_ssm_parameter(self.tdameritrade_account_id), td_ameritrade_client_id_name: ecs.Secret.from_ssm_parameter(self.tdameritrade_client_id), td_ameritrade_refresh_token_name: ecs.Secret.from_ssm_parameter(self.tdameritrade_refresh_token) }, "Portfolio Manager daily task", "cron(0 15 ? * MON-FRI *)") ''' Outputs ''' self.output_props[ 'repo_recommendation_service'] = self.repo_recommendation_service self.output_props[ 'repo_portfolio_manager'] = self.rep_portfolio_manager
def __init__(self, scope: core.Construct, id: str, props: dict, **kwargs): super().__init__(scope, id, **kwargs) APPLICATION_PREFIX = props['APPLICATION_PREFIX'] ''' S3 Data Bucket ''' bucket_name = "%s-data-bucket" % APPLICATION_PREFIX bucket_description = "%s application data" % APPLICATION_PREFIX self.bucket = s3.Bucket(self, bucket_name, removal_policy=core.RemovalPolicy.DESTROY) util.tag_resource(self.bucket, bucket_name, bucket_description) ''' SNS Topic used for application notifications and events ''' sns_topic_name = "%s-app-notifications-topic" % APPLICATION_PREFIX self.notification_topic = sns.Topic(self, sns_topic_name, display_name=sns_topic_name, topic_name=sns_topic_name) util.tag_resource( self.notification_topic, sns_topic_name, "SNS Topic used for application notifications and events") ''' IAM Role and Policy used to define permissions for ECS tasks ''' policy_name = "policy-%s-ecs-tasks" % APPLICATION_PREFIX ecs_tasks_policy = iam.ManagedPolicy(self, policy_name) ecs_tasks_policy.add_statements( iam.PolicyStatement( actions=["cloudformation:Describe*", "cloudformation:List*"], conditions=None, effect=iam.Effect.ALLOW, resources=["*"])) ecs_tasks_policy.add_statements( iam.PolicyStatement(actions=[ "s3:*", ], conditions=None, effect=iam.Effect.ALLOW, resources=[self.bucket.bucket_arn + "/*"])), ecs_tasks_policy.add_statements( iam.PolicyStatement(actions=[ "s3:*", ], conditions=None, effect=iam.Effect.ALLOW, resources=[self.bucket.bucket_arn])), ecs_tasks_policy.add_statements( iam.PolicyStatement(actions=[ "sns:*", ], conditions=None, effect=iam.Effect.ALLOW, resources=[self.notification_topic.topic_arn])) task_role_name = "role-%s-ecs-tasks" % APPLICATION_PREFIX self.ecs_task_role = iam.Role( self, task_role_name, assumed_by=iam.ServicePrincipal("ecs-tasks.amazonaws.com"), description="AWS permissions shared by %s docker tasks" % APPLICATION_PREFIX, role_name=task_role_name, managed_policies=[ecs_tasks_policy]) util.tag_resource( self.ecs_task_role, task_role_name, "IAM Role and Policy used to define permissions for ECS tasks") ''' VPC using by ECS Cluster ''' self.vpc = ec2.Vpc(self, "%s-vpc" % APPLICATION_PREFIX, max_azs=3, cidr="192.168.0.0/17", subnet_configuration=[ ec2.SubnetConfiguration( subnet_type=ec2.SubnetType.PUBLIC, cidr_mask=20, name="public-subnet") ]) util.tag_resource( self.vpc, "%s-vpc" % APPLICATION_PREFIX, "%s vpc for all container tasks" % APPLICATION_PREFIX) util.tag_resource(self.vpc.public_subnets[0], "%s-pub-subnet-1" % APPLICATION_PREFIX, "%s public subnet 1" % APPLICATION_PREFIX) util.tag_resource(self.vpc.public_subnets[1], "%s-pub-subnet-1" % APPLICATION_PREFIX, "%s public subnet 2" % APPLICATION_PREFIX) sg_name = "%s-sg" % APPLICATION_PREFIX sg_description = "%s security Group for ECS tasks" % APPLICATION_PREFIX self.sg = ec2.SecurityGroup(self, sg_name, vpc=self.vpc, allow_all_outbound=True, description=sg_description, security_group_name=sg_name) util.tag_resource(self.sg, sg_name, sg_description) cluster_name = "%s-applicaton-cluster" % APPLICATION_PREFIX cluster_description = "%s ECS cluster for all applicaton tasks" % APPLICATION_PREFIX self.fargate_cluster = ecs.Cluster(self, cluster_name, vpc=self.vpc) util.tag_resource(self.fargate_cluster, cluster_name, cluster_description) ''' Exports ''' core.CfnOutput(self, "%s-databucketname" % APPLICATION_PREFIX, description="Data Bucket Name", value=self.bucket.bucket_name, export_name=bucket_name + "-name") core.CfnOutput( self, "%s-appnotificationstopic" % APPLICATION_PREFIX, description="%s SNS Topic for Application Notifications" % APPLICATION_PREFIX.upper(), value=self.notification_topic.topic_arn, export_name=sns_topic_name + "-name") ''' Outputs ''' self.output_props = props.copy() self.output_props['vpc'] = self.vpc self.output_props['ecs_fargate_task_cluster'] = self.fargate_cluster self.output_props['ecs_task_role'] = self.ecs_task_role