def __init__( self, scope: Construct, id: str, function_id: str, bucket: S3Bucket, config: BaseClusterConfig, execution_role: iam.CfnRole, handler_func: str, timeout: int = 900, ): super().__init__(scope, id) function_name = f"pcluster-{function_id}-{self._stack_unique_id()}" self.log_group = logs.CfnLogGroup( scope, f"{function_id}FunctionLogGroup", log_group_name=f"/aws/lambda/{function_name}", retention_in_days=get_cloud_watch_logs_retention_days(config), ) self.log_group.cfn_options.deletion_policy = get_log_group_deletion_policy( config) self.lambda_func = awslambda.CfnFunction( scope, f"{function_id}Function", function_name=function_name, code=awslambda.CfnFunction.CodeProperty( s3_bucket=bucket.name, s3_key= f"{bucket.artifact_directory}/custom_resources/artifacts.zip", ), handler=f"{handler_func}.handler", memory_size=128, role=execution_role, runtime="python3.8", timeout=timeout, )
def _add_lambda_cleanup(self, policy_statements, build_tags): lambda_cleanup_execution_role = None if self.custom_cleanup_lambda_role: execution_role = self.custom_cleanup_lambda_role else: # LambdaCleanupPolicies self._add_resource_delete_policy( policy_statements, ["cloudformation:DeleteStack"], [ self.format_arn( service="cloudformation", resource="stack", resource_name="{0}/{1}".format( self.image_id, self._stack_unique_id()), ) ], ) self._add_resource_delete_policy( policy_statements, ["ec2:CreateTags"], [ self.format_arn( service="ec2", account="", resource="image", region=region, resource_name="*", ) for region in self._get_distribution_regions() ], ) self._add_resource_delete_policy( policy_statements, ["tag:TagResources"], ["*"], ) self._add_resource_delete_policy( policy_statements, [ "iam:DetachRolePolicy", "iam:DeleteRole", "iam:DeleteRolePolicy" ], [ self.format_arn( service="iam", resource="role", region="", resource_name="{0}/{1}".format( IAM_ROLE_PATH.strip("/"), self._build_resource_name( IMAGEBUILDER_RESOURCE_NAME_PREFIX + "Cleanup"), ), ) ], ) self._add_resource_delete_policy( policy_statements, ["lambda:DeleteFunction", "lambda:RemovePermission"], [ self.format_arn( service="lambda", resource="function", sep=":", resource_name=self._build_resource_name( IMAGEBUILDER_RESOURCE_NAME_PREFIX), ) ], ) self._add_resource_delete_policy( policy_statements, ["logs:DeleteLogGroup"], [ self.format_arn( service="logs", resource="log-group", sep=":", resource_name="/aws/lambda/{0}:*".format( self._build_resource_name( IMAGEBUILDER_RESOURCE_NAME_PREFIX)), ) ], ) self._add_resource_delete_policy( policy_statements, ["iam:RemoveRoleFromInstanceProfile"], [ self.format_arn( service="iam", resource="instance-profile", region="", resource_name="{0}/{1}".format( IAM_ROLE_PATH.strip("/"), self._build_resource_name( IMAGEBUILDER_RESOURCE_NAME_PREFIX), ), ) ], ) self._add_resource_delete_policy( policy_statements, ["iam:DetachRolePolicy", "iam:DeleteRolePolicy"], [ self.format_arn( service="iam", resource="role", region="", resource_name="{0}/{1}".format( IAM_ROLE_PATH.strip("/"), self._build_resource_name( IMAGEBUILDER_RESOURCE_NAME_PREFIX), ), ) ], ) self._add_resource_delete_policy( policy_statements, [ "SNS:GetTopicAttributes", "SNS:DeleteTopic", "SNS:Unsubscribe" ], [ self.format_arn( service="sns", resource="{0}".format( self._build_resource_name( IMAGEBUILDER_RESOURCE_NAME_PREFIX)), ) ], ) policy_document = iam.PolicyDocument(statements=policy_statements) managed_lambda_policy = [ Fn.sub( "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" ), ] # LambdaCleanupExecutionRole lambda_cleanup_execution_role = iam.CfnRole( self, "DeleteStackFunctionExecutionRole", managed_policy_arns=managed_lambda_policy, assume_role_policy_document=get_assume_role_policy_document( "lambda.amazonaws.com"), path=IAM_ROLE_PATH, policies=[ iam.CfnRole.PolicyProperty( policy_document=policy_document, policy_name="LambdaCleanupPolicy", ), ], tags=build_tags, role_name=self._build_resource_name( IMAGEBUILDER_RESOURCE_NAME_PREFIX + "Cleanup"), ) execution_role = lambda_cleanup_execution_role.attr_arn # LambdaCleanupEnv lambda_env = awslambda.CfnFunction.EnvironmentProperty( variables={"IMAGE_STACK_ARN": self.stack_id}) # LambdaCWLogGroup lambda_log = logs.CfnLogGroup( self, "DeleteStackFunctionLog", log_group_name="/aws/lambda/{0}".format( self._build_resource_name(IMAGEBUILDER_RESOURCE_NAME_PREFIX)), ) # LambdaCleanupFunction lambda_cleanup = awslambda.CfnFunction( self, "DeleteStackFunction", function_name=self._build_resource_name( IMAGEBUILDER_RESOURCE_NAME_PREFIX), code=awslambda.CfnFunction.CodeProperty( s3_bucket=self.config.custom_s3_bucket or S3Bucket.get_bucket_name( AWSApi.instance().sts.get_account_id(), get_region()), s3_key=self.bucket.get_object_key(S3FileType.CUSTOM_RESOURCES, "artifacts.zip"), ), handler="delete_image_stack.handler", memory_size=128, role=execution_role, runtime="python3.8", timeout=900, environment=lambda_env, tags=build_tags, ) permission = awslambda.CfnPermission( self, "DeleteStackFunctionPermission", action="lambda:InvokeFunction", principal="sns.amazonaws.com", function_name=lambda_cleanup.attr_arn, source_arn=Fn.ref("BuildNotificationTopic"), ) lambda_cleanup.add_depends_on(lambda_log) return lambda_cleanup, permission, lambda_cleanup_execution_role, lambda_log
def __init__(self, scope: core.Construct, id: str, **kwargs) -> None: super().__init__(scope, id, **kwargs) current_directory = os.path.realpath( os.path.join(os.getcwd(), os.path.dirname(__file__))) allowed_values = yaml.load(open( os.path.join(current_directory, "..", "..", "allowed_values.yaml")), Loader=yaml.SafeLoader) ami_mapping = {"AMI": {"OEJITSI": AMI_NAME}} for region in generated_ami_ids.keys(): ami_mapping[region] = {"OEJITSI": generated_ami_ids[region]} aws_ami_region_map = core.CfnMapping(self, "AWSAMIRegionMap", mapping=ami_mapping) # utility function to parse the unique id from the stack id for # shorter resource names using cloudformation functions def append_stack_uuid(name): return core.Fn.join("-", [ name, core.Fn.select( 0, core.Fn.split( "-", core.Fn.select(2, core.Fn.split( "/", core.Aws.STACK_ID)))) ]) # # PARAMETERS # cidr_block_param = core.CfnParameter( self, "IngressCidrBlock", allowed_pattern="((\d{1,3})\.){3}\d{1,3}/\d{1,2}", default="0.0.0.0/0", description= "Required: A CIDR block to restrict access to the Jitsi application. Leave as 0.0.0.0/0 to allow public access from internet." ) ec2_instance_type_param = core.CfnParameter( self, "InstanceType", allowed_values=allowed_values["allowed_instance_types"], default="t3.xlarge", description= "Required: The EC2 instance type for the application Auto Scaling Group." ) jitsi_hostname_param = core.CfnParameter( self, "JitsiHostname", description= "Required: The hostname to access Jitsi. E.G. 'jitsi.internal.mycompany.com'" ) jitsi_interface_app_name_param = core.CfnParameter( self, "JitsiInterfaceAppName", default="Jitsi Meet", description= "Optional: Customize the app name on the Jitsi interface.") jitsi_interface_default_remote_display_name_param = core.CfnParameter( self, "JitsiInterfaceDefaultRemoteDisplayName", default="Fellow Jitster", description= "Optional: Customize the default display name for Jitsi users.") jitsi_interface_native_app_name_param = core.CfnParameter( self, "JitsiInterfaceNativeAppName", default="Jitsi Meet", description= "Optional: Customize the native app name on the Jitsi interface.") jitsi_interface_show_brand_watermark_param = core.CfnParameter( self, "JitsiInterfaceShowBrandWatermark", allowed_values=["true", "false"], default="true", description= "Optional: Display the watermark logo image in the upper left corner." ) jitsi_interface_show_watermark_for_guests_param = core.CfnParameter( self, "JitsiInterfaceShowWatermarkForGuests", allowed_values=["true", "false"], default="true", description= "Optional: Display the watermark logo image in the upper left corner for guest users. This can be set to override the general setting behavior for guest users." ) jitsi_interface_brand_watermark_param = core.CfnParameter( self, "JitsiInterfaceBrandWatermark", default="", description= "Optional: Provide a URL to a PNG image to be used as the brand watermark logo image in the upper right corner. File should be publically available for download." ) jitsi_interface_brand_watermark_link_param = core.CfnParameter( self, "JitsiInterfaceBrandWatermarkLink", default="http://jitsi.org", description= "Optional: Provide a link destination for the brand watermark logo image in the upper right corner." ) jitsi_interface_watermark_param = core.CfnParameter( self, "JitsiInterfaceWatermark", default="", description= "Optional: Provide a URL to a PNG image to be used as the watermark logo image in the upper left corner. File should be publically available for download." ) jitsi_interface_watermark_link_param = core.CfnParameter( self, "JitsiInterfaceWatermarkLink", default="http://jitsi.org", description= "Optional: Provide a link destination for the Jitsi watermark logo image in the upper left corner." ) route_53_hosted_zone_name_param = core.CfnParameter( self, "Route53HostedZoneName", description= "Required: Route 53 Hosted Zone name in which a DNS record will be created by this template. Must already exist and be the domain part of the Jitsi Hostname parameter, without trailing dot. E.G. 'internal.mycompany.com'" ) notification_email_param = core.CfnParameter( self, "NotificationEmail", default="", description= "Optional: Specify an email address to get emails about deploys, Let's Encrypt, and other system events." ) # # CONDITIONS # notification_email_exists_condition = core.CfnCondition( self, "NotificationEmailExistsCondition", expression=core.Fn.condition_not( core.Fn.condition_equals(notification_email_param.value, ""))) # # RESOURCES # # vpc vpc = Vpc(self, "Vpc") # sns sns_notification_topic = aws_sns.CfnTopic( self, "NotificationTopic", topic_name="{}-notifications".format(core.Aws.STACK_NAME)) sns_notification_subscription = aws_sns.CfnSubscription( self, "NotificationSubscription", protocol="email", topic_arn=sns_notification_topic.ref, endpoint=notification_email_param.value_as_string) sns_notification_subscription.cfn_options.condition = notification_email_exists_condition iam_notification_publish_policy = aws_iam.PolicyDocument(statements=[ aws_iam.PolicyStatement(effect=aws_iam.Effect.ALLOW, actions=["sns:Publish"], resources=[sns_notification_topic.ref]) ]) # cloudwatch app_log_group = aws_logs.CfnLogGroup( self, "JitsiAppLogGroup", retention_in_days=TWO_YEARS_IN_DAYS) app_log_group.cfn_options.update_replace_policy = core.CfnDeletionPolicy.RETAIN app_log_group.cfn_options.deletion_policy = core.CfnDeletionPolicy.RETAIN system_log_group = aws_logs.CfnLogGroup( self, "JitsiSystemLogGroup", retention_in_days=TWO_YEARS_IN_DAYS) system_log_group.cfn_options.update_replace_policy = core.CfnDeletionPolicy.RETAIN system_log_group.cfn_options.deletion_policy = core.CfnDeletionPolicy.RETAIN # iam iam_jitsi_instance_role = aws_iam.CfnRole( self, "JitsiInstanceRole", assume_role_policy_document=aws_iam.PolicyDocument(statements=[ aws_iam.PolicyStatement( effect=aws_iam.Effect.ALLOW, actions=["sts:AssumeRole"], principals=[aws_iam.ServicePrincipal("ec2.amazonaws.com")]) ]), policies=[ aws_iam.CfnRole.PolicyProperty( policy_document=aws_iam.PolicyDocument(statements=[ aws_iam.PolicyStatement( effect=aws_iam.Effect.ALLOW, actions=[ "logs:CreateLogStream", "logs:DescribeLogStreams", "logs:PutLogEvents" ], resources=[ app_log_group.attr_arn, system_log_group.attr_arn ]) ]), policy_name="AllowStreamLogsToCloudWatch"), aws_iam.CfnRole.PolicyProperty( policy_document=aws_iam.PolicyDocument(statements=[ aws_iam.PolicyStatement( effect=aws_iam.Effect.ALLOW, actions=[ "ec2:AssociateAddress", "ec2:DescribeVolumes", "ec2:DescribeTags", "cloudwatch:GetMetricStatistics", "cloudwatch:ListMetrics", "cloudwatch:PutMetricData" ], resources=["*"]) ]), policy_name="AllowStreamMetricsToCloudWatch"), aws_iam.CfnRole.PolicyProperty( policy_document=aws_iam.PolicyDocument(statements=[ aws_iam.PolicyStatement( effect=aws_iam.Effect.ALLOW, actions=["autoscaling:Describe*"], resources=["*"]) ]), policy_name="AllowDescribeAutoScaling"), ], managed_policy_arns=[ "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" ]) # ec2 jitsi_sg = aws_ec2.CfnSecurityGroup( self, "JitsiSg", group_description="Jitsi security group", vpc_id=vpc.id()) eip = aws_ec2.CfnEIP(self, "Eip", domain="vpc") core.Tags.of(eip).add("Name", "{}/Eip".format(core.Aws.STACK_NAME)) ec2_instance_profile = aws_iam.CfnInstanceProfile( self, "JitsiInstanceProfile", roles=[iam_jitsi_instance_role.ref]) with open("jitsi/jitsi_launch_config_user_data.sh") as f: jitsi_launch_config_user_data = f.read() ec2_launch_config = aws_autoscaling.CfnLaunchConfiguration( self, "JitsiLaunchConfig", image_id=core.Fn.find_in_map("AWSAMIRegionMap", core.Aws.REGION, "OEJITSI"), instance_type=ec2_instance_type_param.value_as_string, iam_instance_profile=ec2_instance_profile.ref, security_groups=[jitsi_sg.ref], user_data=(core.Fn.base64( core.Fn.sub( jitsi_launch_config_user_data, { "JitsiHostname": jitsi_hostname_param.value_as_string, "JitsiPublicIP": eip.ref, "LetsEncryptCertificateEmail": notification_email_param.value_as_string })))) # autoscaling asg = aws_autoscaling.CfnAutoScalingGroup( self, "JitsiAsg", launch_configuration_name=ec2_launch_config.ref, desired_capacity="1", max_size="1", min_size="1", vpc_zone_identifier=vpc.public_subnet_ids()) asg.cfn_options.creation_policy = core.CfnCreationPolicy( resource_signal=core.CfnResourceSignal(count=1, timeout="PT15M")) asg.cfn_options.update_policy = core.CfnUpdatePolicy( auto_scaling_rolling_update=core.CfnAutoScalingRollingUpdate( max_batch_size=1, min_instances_in_service=0, pause_time="PT15M", wait_on_resource_signals=True)) core.Tags.of(asg).add("Name", "{}/JitsiAsg".format(core.Aws.STACK_NAME)) jitsi_http_ingress = aws_ec2.CfnSecurityGroupIngress( self, "JitsiHttpSgIngress", cidr_ip=cidr_block_param.value_as_string, from_port=80, group_id=jitsi_sg.ref, ip_protocol="tcp", to_port=80) jitsi_https_ingress = aws_ec2.CfnSecurityGroupIngress( self, "JitsiHttpsSgIngress", cidr_ip=cidr_block_param.value_as_string, from_port=443, group_id=jitsi_sg.ref, ip_protocol="tcp", to_port=443) jitsi_fallback_network_audio_video_ingress = aws_ec2.CfnSecurityGroupIngress( self, "JitsiFallbackNetworkAudioVideoSgIngress", cidr_ip=cidr_block_param.value_as_string, from_port=4443, group_id=jitsi_sg.ref, ip_protocol="tcp", to_port=4443) jitsi_general_network_audio_video_ingress = aws_ec2.CfnSecurityGroupIngress( self, "JitsiGeneralNetworkAudioVideoSgIngress", cidr_ip=cidr_block_param.value_as_string, from_port=10000, group_id=jitsi_sg.ref, ip_protocol="udp", to_port=10000) # route 53 record_set = aws_route53.CfnRecordSet( self, "RecordSet", hosted_zone_name= f"{route_53_hosted_zone_name_param.value_as_string}.", name=jitsi_hostname_param.value_as_string, resource_records=[eip.ref], type="A") # https://github.com/aws/aws-cdk/issues/8431 record_set.add_property_override("TTL", 60) # AWS::CloudFormation::Interface self.template_options.metadata = { "OE::Patterns::TemplateVersion": template_version, "AWS::CloudFormation::Interface": { "ParameterGroups": [{ "Label": { "default": "Infrastructure Config" }, "Parameters": [ jitsi_hostname_param.logical_id, route_53_hosted_zone_name_param.logical_id, cidr_block_param.logical_id, ec2_instance_type_param.logical_id, notification_email_param.logical_id ] }, { "Label": { "default": "Jitsi Config" }, "Parameters": [ jitsi_interface_app_name_param.logical_id, jitsi_interface_default_remote_display_name_param. logical_id, jitsi_interface_native_app_name_param.logical_id, jitsi_interface_show_brand_watermark_param.logical_id, jitsi_interface_show_watermark_for_guests_param. logical_id, jitsi_interface_brand_watermark_param.logical_id, jitsi_interface_brand_watermark_link_param.logical_id, jitsi_interface_watermark_param.logical_id, jitsi_interface_watermark_link_param.logical_id, ] }, *vpc.metadata_parameter_group()], "ParameterLabels": { cidr_block_param.logical_id: { "default": "Ingress CIDR Block" }, ec2_instance_type_param.logical_id: { "default": "EC2 instance type" }, jitsi_hostname_param.logical_id: { "default": "Jitsi Hostname" }, jitsi_interface_app_name_param.logical_id: { "default": "Jitsi Interface App Name" }, jitsi_interface_default_remote_display_name_param.logical_id: { "default": "Jitsi Interface Default Remote Display Name" }, jitsi_interface_native_app_name_param.logical_id: { "default": "Jitsi Interface Native App Name" }, jitsi_interface_show_brand_watermark_param.logical_id: { "default": "Jitsi Interface Show Watermark" }, jitsi_interface_show_watermark_for_guests_param.logical_id: { "default": "Jitsi Interface Show Watermark For Guests" }, jitsi_interface_brand_watermark_param.logical_id: { "default": "Jitsi Interface Watermark" }, jitsi_interface_brand_watermark_link_param.logical_id: { "default": "Jitsi Interface Watermark Link" }, jitsi_interface_watermark_param.logical_id: { "default": "Jitsi Interface Watermark" }, jitsi_interface_watermark_link_param.logical_id: { "default": "Jitsi Interface Watermark Link" }, notification_email_param.logical_id: { "default": "Notification Email" }, route_53_hosted_zone_name_param.logical_id: { "default": "AWS Route 53 Hosted Zone Name" }, **vpc.metadata_parameter_labels() } } } # # OUTPUTS # eip_output = core.CfnOutput( self, "EipOutput", description= "The Elastic IP address dynamically mapped to the autoscaling group instance.", value=eip.ref) endpoint_output = core.CfnOutput( self, "JitsiUrl", description="The URL for the Jitsi instance.", value=core.Fn.join( "", ["https://", jitsi_hostname_param.value_as_string]))
def _add_code_build_docker_image_builder_project(self): timestamp = f"{datetime.utcnow().strftime('%Y%m%d%H%M')}" log_group_name = ( f"{CW_LOG_GROUP_NAME_PREFIX}codebuild/{self.stack_name}-CodeBuildDockerImageBuilderProject-{timestamp}" ) log_group = logs.CfnLogGroup( self.stack_scope, "CodeBuildLogGroup", log_group_name=log_group_name, retention_in_days=get_cloud_watch_logs_retention_days(self.config), ) log_group.cfn_options.deletion_policy = get_log_group_deletion_policy( self.config) return codebuild.CfnProject( self.stack_scope, "CodeBuildDockerImageBuilderProj", artifacts=codebuild.CfnProject.ArtifactsProperty( type="NO_ARTIFACTS"), environment=codebuild.CfnProject.EnvironmentProperty( compute_type="BUILD_GENERAL1_LARGE" if self._condition_use_arm_code_build_image() else "BUILD_GENERAL1_SMALL", environment_variables=[ codebuild.CfnProject.EnvironmentVariableProperty( name="AWS_REGION", value=self._stack_region, ), codebuild.CfnProject.EnvironmentVariableProperty( name="AWS_ACCOUNT_ID", value=self._stack_account, ), codebuild.CfnProject.EnvironmentVariableProperty( name="IMAGE_REPO_NAME", value=self._docker_images_repo.ref, ), codebuild.CfnProject.EnvironmentVariableProperty( name="IMAGE", value=self.config.image.os, ), codebuild.CfnProject.EnvironmentVariableProperty( name="NOTIFICATION_URL", value=self._docker_build_wait_condition_handle.ref, ), ], image="aws/codebuild/amazonlinux2-aarch64-standard:1.0" if self._condition_use_arm_code_build_image() else "aws/codebuild/amazonlinux2-x86_64-standard:3.0", type="ARM_CONTAINER" if self._condition_use_arm_code_build_image() else "LINUX_CONTAINER", privileged_mode=True, ), name=f"pcluster-{self.stack_name}-build-docker-images-project", service_role=self._code_build_role.attr_arn, source=codebuild.CfnProject.SourceProperty( location=f"{self.bucket.name}/{self.bucket.artifact_directory}" "/custom_resources/scheduler_resources.zip", type="S3", ), logs_config=codebuild.CfnProject.LogsConfigProperty( cloud_watch_logs=codebuild.CfnProject. CloudWatchLogsConfigProperty(group_name=log_group_name, status="ENABLED")), )
def __init__(self, scope: core.Construct, id: str, config: Config, **kwargs): super().__init__(scope, id, **kwargs) logs.CfnLogGroup( self, id=config.log_group_id(), retention_in_days=config.log_retention_days() ) for role, policies in config.roles().items(): ServiceRole( self, id=role, policies=policies, service='ecs-tasks.amazonaws.com', whitelisted_actions=config.whitelisted_actions() ) TaskDefinition( self, id='TaskDefinition', containers=config.containers(), cpu=config.task_cpu(), memory=config.task_memory(), log_group_id=config.log_group_id(), execution_role_id=config.execution_role_id(), task_role_id=config.task_role_id(), overrides=config.task_overrides() ) TargetGroup( self, id='TargetGroup', health_check_path=config.service_health_check_path(), port=config.service_port(), protocol=str.upper(config.service_health_check_protocol()), vpc_id=config.vpc_id(), overrides=config.target_group_overrides() ) Service( self, id='Service', cluster_name=config.cluster_stack_name(), desired_count=config.service_desired_count(), service_name=config.service_name(), service_port=config.service_port(), security_group_import_name=config.shared_service_security_group_export_name(), subnet_ids=config.private_subnet_ids(), target_group_id='TargetGroup', task_def_id='TaskDefinition', overrides=config.service_overrides() ) ListenerRule( self, id='ListenerRule', listener_import_name=config.shared_lb_listener_export_name(), paths=config.service_paths(), priority=config.service_priority(), target_group_id='TargetGroup' ) RecordSet( self, id='RecordSet', hosted_zone_name=config.hosted_zone_name(), dns_name_import_name=config.shared_lb_dns_name_export_name(), hosted_zone_id_import_name=config.shared_lb_hosted_zone_id_export_name() ) for key, value in config.tags().items(): core.Tag.add(self, key, value) self.node.apply_aspect(UseOriginalConstructID())