diff --git a/examples/deadline/All-In-AWS-Infrastructure-Basic/python/package/lib/storage_tier.py b/examples/deadline/All-In-AWS-Infrastructure-Basic/python/package/lib/storage_tier.py index e241e26fe..f83025410 100644 --- a/examples/deadline/All-In-AWS-Infrastructure-Basic/python/package/lib/storage_tier.py +++ b/examples/deadline/All-In-AWS-Infrastructure-Basic/python/package/lib/storage_tier.py @@ -126,7 +126,9 @@ def __init__(self, scope: Construct, stack_id: str, *, props: StorageTierDocDBPr self, 'DocDBCluster', instance_props=instance_props, - instances=len(self.availability_zones), + # TODO - For cost considerations this example only uses 1 Database instance. + # It is recommended that when creating your render farm you use at least 2 instances for redundancy. + instances=1, master_user=Login(username='adminuser'), backup=BackupProps( # We recommend setting the retention of your backups to 15 days diff --git a/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/lib/storage-tier.ts b/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/lib/storage-tier.ts index d3c837253..46efb8fea 100644 --- a/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/lib/storage-tier.ts +++ b/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/lib/storage-tier.ts @@ -110,7 +110,9 @@ export class StorageTierDocDB extends StorageTier { vpcSubnets: { subnetType: SubnetType.PRIVATE }, instanceType: props.databaseInstanceType, }, - instances: this.availabilityZones.length, + // TODO - For cost considerations this example only uses 1 Database instance. + // It is recommended that when creating your render farm you use at least 2 instances for redundancy. + instances: 1, masterUser: { username: 'adminuser', }, diff --git a/examples/deadline/All-In-AWS-Infrastructure-SEP/README.md b/examples/deadline/All-In-AWS-Infrastructure-SEP/README.md new file mode 100644 index 000000000..8b1e4e376 --- /dev/null +++ b/examples/deadline/All-In-AWS-Infrastructure-SEP/README.md @@ -0,0 +1,106 @@ +# RFDK Sample Application - Deadline Spot Event Plugin + +This is a sample RFDK application that deploys the basic infrastructure for a Deadline render farm. This application is structured in tiers, each representing a CloudFormation stack. The tiers are: + +1. **Network Tier** - Foundational networking required by all components. +1. **Security Tier** - Contains resources that keep the render farm secure (e.g. certificates). +1. **Storage Tier** - Persistent storage (e.g. database, file system). +1. **Service Tier** - Business logic (e.g. central server, licensing). + +Each deployment tier is deployed as a separate CloudFormation Stack, and is dependent upon the ones before it. +The main benefit of this deployment structure is that the later tiers can be brought down (i.e. stacks destroyed) +to save costs while keeping the earlier tiers. For instance, we could destroy the Service & Compute tiers to +reduce the cost to maintain the farm when we know it will be idle while retaining all of our data; +we could re-deploy the Service tierat a later date to restore service to exactly the same state we left it in. + +--- + +_**Note:** This application is an illustrative example to showcase some of the capabilities of the RFDK. **It is not intended to be used for production render farms**, which should be built with more consideration of the security and operational needs of the system._ + +--- + +## Architecture + +This sample application deploys a basic Deadline Render farm that is configured to use Deadlines [Spot Event Plugin](https://docs.thinkboxsoftware.com/products/deadline/10.1/1_User%20Manual/manual/event-spot.html). Below is a diagram of the architecture. + +``` ++-----------------------------------------------------------------------------+ +| | +| Private Hosted Zone | +| | +| +-------------------------------------------------------------------------+ | +| | | | +| | VPC | | +| | | | +| | +------------------------------+ +-----------------------+ | | +| | | | | | | | +| | | Repository | | Render Queue | | | +| | | +--------------> | | | +| | | +----------+ +-------------+ | Backend | +-------------------+ | | | +| | | | | | | | API | | | | | | +| | | | Database | | File System | <--------------+ | RCS Fleet | | | | +| | | | | | | | | | | | | | +| | | +----------+ +-----+-------+ | | | +-------+ | | | | +| | | | | | | | ALB | | | | | +| | +------------------------------+ | | +---+---+ | | | | +| | | | | | | | | | +| | | | | +-----------+ | | | | +| | |Mounts | | | | | | | | | +| | |onto | | v v v | | | | +| | | | | +-++ ++-+ | | | | +| | +--------------+ | | | | ... | | | | | | +| | | - | | +--+ +--+ | | | | +| | | Bastion Host +---------------------> | | | | | +| | | | Can connect to | +-------------------+ | | | +| | +--------------+ +-----------------------+ | | +| +-------------------------------------------------------------------------+ | ++-----------------------------------------------------------------------------+ + + +``` + +### Components + +All components in the render farm live within a [VPC](https://aws.amazon.com/vpc/), which is within a [Private Hosted Zone](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/hosted-zones-private.html). + +#### Repository + +The Repository component contains the database and file system that store persistent data used by Deadline. These resources are initialized by the Deadline Repository installer. The database can either be [MongoDB](https://www.mongodb.com/) or [Amazon DocumentDB](https://aws.amazon.com/documentdb/). + +#### Render Queue + +The Render Queue component contains the fleet of [Deadline Remote Connection Server](https://docs.thinkboxsoftware.com/products/deadline/10.1/1_User%20Manual/manual/remote-connection-server.html) instances behind an [Application Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html). This acts as the central service for Deadline applications and is the only component that interacts with the Repository. When comparing this component to the "All in AWS Infrastructure - Basic" example it has been granted additional permissions in order to use the Spot Event Plugin. + +#### Bastion Host + +The Bastion Host is a `BastionHostLinux` construct that allows you to connect to the Render Queue if you would like to take a look at its state. It is not an essential component to the render farm, so it can be omitted without side effects, if desired. To connect to it, please refer to [Bastion Hosts CDK documentation](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-ec2-readme.html#bastion-hosts). + +#### Spot Event Plugin Configurations + +The Spot Event plugin requires additional Roles for both Deadline's Resource Tracker and the Spot Workers that are created and a Security Group to allow your Spot workers the ability to access the Render Queue. + +## Best Practices + +### VPC Flow Logs +We recommend enabling VPC Flow Logs for networks containing sensitive information. For example, in this application, we have enabled flow logs on the VPC created in the Network Tier. These logs capture information about the IP traffic going in and out of your VPC, which can be useful to detect malicious activity. For more information, see [VPC Flow Logs documentation](https://docs.aws.amazon.com/vpc/latest/userguide/flow-logs.html). + +### VPC Network ACLs + +Network ACLs act as a firewall for controlling traffic in or out of your VPC subnets. We recommend creating custom network ACLs on your VPC to restrict traffic so that only necessary traffic is allowed. The default network ACLs that are created with a new VPC allow all inbound and outbound traffic, whereas custom network ACLs deny all inbound and outbound traffic by default, unless rules are added that explicitly allow traffic. This is a security best-practice to help defend against malicious actions against your render farm. For more information, see [Network ACLs documentation](https://docs.aws.amazon.com/vpc/latest/userguide/vpc-network-acls.html). + +## Prerequisites + +- The Spot Fleet Configuration requires an [Amazon Machine Image (AMI)](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AMIs.html) with the Deadline Worker application installed. This AMI must have Deadline Installed and should be configured to connect to your repository. For additional information on setting up your AMI please see the [Spot Event Plugin Documentation](https://docs.thinkboxsoftware.com/products/deadline/10.1/1_User%20Manual/manual/event-spot.html). +- You have setup and configured the AWS CLI +- Your AWS account already has CDK bootstrapped in the desired region by running `cdk bootstrap` +- You must have NodeJS installed on your system +- You must have Docker installed on your system +- You must have Python 3.7+ installed on your system (Python app only) + +## Typescript + +[Continue to Typescript specific documentation.](ts/README.md) + +## Python + +[Continue to Python specific documentation.](python/README.md) \ No newline at end of file diff --git a/examples/deadline/All-In-AWS-Infrastructure-SEP/python/.gitignore b/examples/deadline/All-In-AWS-Infrastructure-SEP/python/.gitignore new file mode 100644 index 000000000..952b96f95 --- /dev/null +++ b/examples/deadline/All-In-AWS-Infrastructure-SEP/python/.gitignore @@ -0,0 +1,12 @@ +*.swp +package-lock.json +__pycache__ +.pytest_cache +.env +*.egg-info + +# CDK asset staging directory +.cdk.staging +cdk.out +cdk.context.json +stage diff --git a/examples/deadline/All-In-AWS-Infrastructure-SEP/python/README.md b/examples/deadline/All-In-AWS-Infrastructure-SEP/python/README.md new file mode 100644 index 000000000..c66594980 --- /dev/null +++ b/examples/deadline/All-In-AWS-Infrastructure-SEP/python/README.md @@ -0,0 +1,79 @@ +# RFDK Sample Application - Deadline Spot Event Plugin - Python + +## Overview +[Back to overview](../README.md) + +## Instructions + +--- +**NOTE** + +These instructions assume that your working directory is `examples/deadline/All-In-AWS-Infrastructure-SEP/python/` relative to the root of the AWS-RFDK package. + +--- + +1. Install the dependencies of the sample app: + + ```bash + pip install -r requirements.txt + ``` +2. Create an EC2 key pair to give you SSH access to the render farm: + + ```bash + aws ec2 create-key-pair --key-name + ``` +3. Change the value of the `key_pair_name` variable in `package/config.py` to your value for `` in the previous step: + + **Note:** Save the value of the `"KeyMaterial"` field as a file in a secure location. This is your private key that you can use to SSH into the render farm. + + ```python + self.key_pair_name: Optional[str] = '' + ``` +4. Choose the type of database you would like to deploy (AWS DocumentDB or MongoDB). + If you would like to use MongoDB, you will need to accept the Mongo SSPL (see next step). + Once you've decided on a database type, change the value of the `deploy_mongo_db` variable in `package/config.py` accordingly: + + ```python + # True = MongoDB, False = Amazon DocumentDB + self.deploy_mongo_db: bool = False + ``` +5. If you set `deploy_mongo_db` to `True`, then you must accept the [SSPL license](https://www.mongodb.com/licensing/server-side-public-license) to successfully deploy MongoDB. To do so, change the value of `accept_sspl_license` in `package/config.py`: + + ```python + # To accept the MongoDB SSPL, change from USER_REJECTS_SSPL to USER_ACCEPTS_SSPL + self.accept_sspl_license: MongoDbSsplLicenseAcceptance = MongoDbSsplLicenseAcceptance.USER_REJECTS_SSPL + ``` +6. Stage the Docker recipes for `RenderQueue`: + + ```bash + # Set this value to the version of RFDK your application targets + RFDK_VERSION= + + # Set this value to the version of AWS Thinkbox Deadline you'd like to deploy to your farm. Deadline 10.1.9 and up are supported. + RFDK_DEADLINE_VERSION= + + npx --package=aws-rfdk@${RFDK_VERSION} stage-deadline \ + --deadlineInstallerURI s3://thinkbox-installers/Deadline/${RFDK_DEADLINE_VERSION}/Linux/DeadlineClient-${RFDK_DEADLINE_VERSION}-linux-x64-installer.run \ + --dockerRecipesURI s3://thinkbox-installers/DeadlineDocker/${RFDK_DEADLINE_VERSION}/DeadlineDocker-${RFDK_DEADLINE_VERSION}.tar.gz \ + --output stage + ``` +7. Deploy all the stacks in the sample app: + + ```bash + cdk deploy "*" + ``` + +8. Connect to your Render Farm and open up the Deadline Monitor. + +9. Configure the Spot event plugin by following the directions in the [Spot Event Plugin documentation](https://docs.thinkboxsoftware.com/products/deadline/10.1/1_User%20Manual/manual/event-spot.html) with the following considerations: + + Use the default security credentials by using turning "Use Local Credentials" to False and leaving both "Access Key ID" and "Secret Access Key" blank. + Ensure that the Region your Spot workers will be launched in is the same region as your CDK application. + When Creating your Spot Fleet Requests, set the IAM instance profile to "DeadlineSpotWorkerRole" and set the security group to "DeadlineSpotSecurityGroup". + Configure your instances to connect to the Render Queue by either creating your AMI after launching your app and preconfiguring the AMI or by setting up a userdata in the Spot Fleet Request. (see the Spot Event Plugin documentation for additional information on configuring this connection.) + +10. Once you are finished with the sample app, you can tear it down by running: + + ```bash + cdk destroy "*" + ``` diff --git a/examples/deadline/All-In-AWS-Infrastructure-SEP/python/cdk.json b/examples/deadline/All-In-AWS-Infrastructure-SEP/python/cdk.json new file mode 100644 index 000000000..ee4a9b01f --- /dev/null +++ b/examples/deadline/All-In-AWS-Infrastructure-SEP/python/cdk.json @@ -0,0 +1,3 @@ +{ + "app": "python -m package.app" +} diff --git a/examples/deadline/All-In-AWS-Infrastructure-SEP/python/package/__init__.py b/examples/deadline/All-In-AWS-Infrastructure-SEP/python/package/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/examples/deadline/All-In-AWS-Infrastructure-SEP/python/package/app.py b/examples/deadline/All-In-AWS-Infrastructure-SEP/python/package/app.py new file mode 100644 index 000000000..01674bded --- /dev/null +++ b/examples/deadline/All-In-AWS-Infrastructure-SEP/python/package/app.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 + +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +import os + +from aws_cdk.core import ( + App, + Environment +) +from aws_cdk.aws_ec2 import ( + InstanceClass, + InstanceSize, + InstanceType, + MachineImage +) + +from .lib import ( + network_tier, + security_tier, + storage_tier, + service_tier +) + +from .config import config + + +def main(): + # ------------------------------ + # Validate Config Values + # ------------------------------ + + if not config.key_pair_name: + print('EC2 key pair name not specified. You will not have SSH access to the render farm.') + + # ------------------------------ + # Application + # ------------------------------ + app = App() + + if 'CDK_DEPLOY_ACCOUNT' not in os.environ and 'CDK_DEFAULT_ACCOUNT' not in os.environ: + raise ValueError('You must define either CDK_DEPLOY_ACCOUNT or CDK_DEFAULT_ACCOUNT in the environment.') + if 'CDK_DEPLOY_REGION' not in os.environ and 'CDK_DEFAULT_REGION' not in os.environ: + raise ValueError('You must define either CDK_DEPLOY_REGION or CDK_DEFAULT_REGION in the environment.') + env = Environment( + account=os.environ.get('CDK_DEPLOY_ACCOUNT', os.environ.get('CDK_DEFAULT_ACCOUNT')), + region=os.environ.get('CDK_DEPLOY_REGION', os.environ.get('CDK_DEFAULT_REGION')) + ) + + # ------------------------------ + # Network Tier + # ------------------------------ + network = network_tier.NetworkTier( + app, + 'NetworkTier', + env=env + ) + + # ------------------------------ + # Security Tier + # ------------------------------ + security = security_tier.SecurityTier( + app, + 'SecurityTier', + env=env + ) + + # ------------------------------ + # Storage Tier + # ------------------------------ + if config.deploy_mongo_db: + storage_props = storage_tier.StorageTierMongoDBProps( + vpc=network.vpc, + database_instance_type=InstanceType.of(InstanceClass.MEMORY5, InstanceSize.LARGE), + root_ca=security.root_ca, + dns_zone=network.dns_zone, + accept_sspl_license=config.accept_sspl_license, + key_pair_name=config.key_pair_name + ) + storage = storage_tier.StorageTierMongoDB(app, 'StorageTier', props=storage_props, env=env) + else: + storage_props = storage_tier.StorageTierDocDBProps( + vpc=network.vpc, + database_instance_type=InstanceType.of(InstanceClass.MEMORY5, InstanceSize.LARGE), + ) + storage = storage_tier.StorageTierDocDB(app, 'StorageTier', props=storage_props, env=env) + + # ------------------------------ + # Service Tier + # ------------------------------ + service_props = service_tier.ServiceTierProps( + database=storage.database, + file_system=storage.file_system, + vpc=network.vpc, + docker_recipes_stage_path=os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir, 'stage'), + dns_zone=network.dns_zone + ) + service = service_tier.ServiceTier(app, 'ServiceTier', props=service_props, env=env) + + app.synth() + + +if __name__ == '__main__': + main() diff --git a/examples/deadline/All-In-AWS-Infrastructure-SEP/python/package/config.py b/examples/deadline/All-In-AWS-Infrastructure-SEP/python/package/config.py new file mode 100644 index 000000000..9107f68ae --- /dev/null +++ b/examples/deadline/All-In-AWS-Infrastructure-SEP/python/package/config.py @@ -0,0 +1,32 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +from typing import ( + Optional +) + +from aws_rfdk import MongoDbSsplLicenseAcceptance + +class AppConfig: + """ + Configuration values for the sample app. + + TODO: Fill these in with your own values. + """ + def __init__(self): + + # (Optional) The name of the EC2 keypair to associate with the instances. + self.key_pair_name: Optional[str] = None + + # Whether to use MongoDB to back the render farm. + # If false, then we use Amazon DocumentDB to back the render farm. + self.deploy_mongo_db: bool = False + + # This is only relevant if deploy_mongo_db is True. + # + # Change this value to MongoDbSsplLicenseAcceptance.USER_ACCEPTS_SSPL + # if you wish to accept the SSPL and proceed with MongoDB deployment. + self.accept_sspl_license: MongoDbSsplLicenseAcceptance = MongoDbSsplLicenseAcceptance.USER_REJECTS_SSPL + + +config: AppConfig = AppConfig() diff --git a/examples/deadline/All-In-AWS-Infrastructure-SEP/python/package/lib/__init__.py b/examples/deadline/All-In-AWS-Infrastructure-SEP/python/package/lib/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/examples/deadline/All-In-AWS-Infrastructure-SEP/python/package/lib/network_tier.py b/examples/deadline/All-In-AWS-Infrastructure-SEP/python/package/lib/network_tier.py new file mode 100644 index 000000000..e722717b6 --- /dev/null +++ b/examples/deadline/All-In-AWS-Infrastructure-SEP/python/package/lib/network_tier.py @@ -0,0 +1,139 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +from aws_cdk.core import ( + Stack, + Construct +) + +from aws_cdk.aws_ec2 import ( + FlowLogDestination, + FlowLogTrafficType, + GatewayVpcEndpointAwsService, + InterfaceVpcEndpointAwsService, + Vpc, + SubnetConfiguration, + SubnetSelection, + SubnetType +) + +from aws_cdk.aws_route53 import ( + PrivateHostedZone +) + +_INTERFACE_ENDPOINT_SERVICES = [ + {'name': 'CLOUDWATCH', 'service': InterfaceVpcEndpointAwsService.CLOUDWATCH}, + {'name': 'CLOUDWATCH_EVENTS', 'service': InterfaceVpcEndpointAwsService.CLOUDWATCH_EVENTS}, + {'name': 'CLOUDWATCH_LOGS', 'service': InterfaceVpcEndpointAwsService.CLOUDWATCH_LOGS}, + {'name': 'EC2', 'service': InterfaceVpcEndpointAwsService.EC2}, + {'name': 'ECR', 'service': InterfaceVpcEndpointAwsService.ECR}, + {'name': 'ECS', 'service': InterfaceVpcEndpointAwsService.ECS}, + {'name': 'KMS', 'service': InterfaceVpcEndpointAwsService.KMS}, + {'name': 'SECRETS_MANAGER', 'service': InterfaceVpcEndpointAwsService.SECRETS_MANAGER}, + {'name': 'SNS', 'service': InterfaceVpcEndpointAwsService.SNS}, + {'name': 'STS', 'service': InterfaceVpcEndpointAwsService.STS} +] + +_GATEWAY_ENDPOINT_SERVICES = [ + {'name': 'S3', 'service': GatewayVpcEndpointAwsService.S3}, + {'name': 'DYNAMODB', 'service': GatewayVpcEndpointAwsService.DYNAMODB} +] + + +class NetworkTier(Stack): + """ + The network tier consists of all constructs that are required for the foundational + networking between the various components of the Deadline render farm. + """ + + def __init__(self, scope: Construct, stack_id: str, **kwargs) -> None: + """ + Initializes a new instance of NetworkTier + :param scope: The scope of this construct. + :param stack_id: The ID of this construct. + :param kwargs: The stack properties. + """ + super().__init__(scope, stack_id, **kwargs) + + # The VPC that all components of the render farm will be created in. + self.vpc = Vpc( + self, + 'Vpc', + max_azs=2, + subnet_configuration=[ + SubnetConfiguration( + name='Public', + subnet_type=SubnetType.PUBLIC, + cidr_mask=28 + ), + SubnetConfiguration( + name='Private', + subnet_type=SubnetType.PRIVATE, + cidr_mask=18 # 16,382 IP addresses + ) + ] + ) + # VPC flow logs are a security best-practice as they allow us + # to capture information about the traffic going in and out of + # the VPC. For more information, see the README for this app. + self.vpc.add_flow_log( + 'NetworkTierFlowLogs', + destination=FlowLogDestination.to_cloud_watch_logs(), + traffic_type=FlowLogTrafficType.ALL + ) + + # TODO - Create a NetworkAcl for your VPC that only allows + # network traffic required for your render farm. This is a + # security best-practice to ensure the safety of your farm. + # The default network ACLs allow all traffic by default, + # whereas custom network ACLs deny all traffic by default. + # For more information, see the README for this app. + # + # Example code to create a custom network ACL: + # acl = NetworkAcl( + # self, + # 'ACL', + # vpc=self.vpc, + # subnet_selection=SubnetSelection( + # subnets=self.vpc.public_subnets + # ) + # ) + # + # You can optionally add rules to allow traffic (e.g. SSH): + # acl.add_entry( + # 'SSH', + # cidr=AclCidr.ipv4( + # # some-ipv4-address-cidr + # ), + # traffic=AclTraffic.tcp_port(22), + # rule_number=1 + # ) + endpoint_subnets = SubnetSelection(subnet_type=SubnetType.PRIVATE) + + # Add interface endpoints + for idx, service_info in enumerate(_INTERFACE_ENDPOINT_SERVICES): + service_name = service_info['name'] + service = service_info['service'] + self.vpc.add_interface_endpoint( + f'{service_name}{idx}', + service=service, + subnets=endpoint_subnets + ) + + # Add gateway endpoints + for idx, service_info in enumerate(_GATEWAY_ENDPOINT_SERVICES): + service_name = service_info['name'] + service = service_info['service'] + self.vpc.add_gateway_endpoint( + service_name, + service=service, + subnets=[endpoint_subnets] + ) + + # Internal DNS zone for the VPC. + self.dns_zone = PrivateHostedZone( + self, + 'DnsZone', + vpc=self.vpc, + zone_name='deadline-test.internal' + ) diff --git a/examples/deadline/All-In-AWS-Infrastructure-SEP/python/package/lib/security_tier.py b/examples/deadline/All-In-AWS-Infrastructure-SEP/python/package/lib/security_tier.py new file mode 100644 index 000000000..c14169f0f --- /dev/null +++ b/examples/deadline/All-In-AWS-Infrastructure-SEP/python/package/lib/security_tier.py @@ -0,0 +1,36 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +from aws_cdk.core import ( + Construct, + Stack, +) +from aws_rfdk import ( + DistinguishedName, + X509CertificatePem +) + + +class SecurityTier(Stack): + """ + The security tier of the render farm. + This stack contains resources used to ensure the render farm is secure. + """ + def __init__(self, scope: Construct, stack_id: str, **kwargs): + """ + Initialize a new instance of ServiceTier + :param scope: The scope of this construct. + :param stack_id: The ID of this construct. + :param props: The properties for this construct. + :param kwargs: Any kwargs that need to be passed on to the parent class. + """ + super().__init__(scope, stack_id, **kwargs) + + # Our self-signed root CA certificate for the internal endpoints in the farm. + self.root_ca = X509CertificatePem( + self, + 'RootCA', + subject=DistinguishedName( + cn='SampleRootCA' + ) + ) diff --git a/examples/deadline/All-In-AWS-Infrastructure-SEP/python/package/lib/service_tier.py b/examples/deadline/All-In-AWS-Infrastructure-SEP/python/package/lib/service_tier.py new file mode 100644 index 000000000..556892005 --- /dev/null +++ b/examples/deadline/All-In-AWS-Infrastructure-SEP/python/package/lib/service_tier.py @@ -0,0 +1,182 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +import typing +from dataclasses import dataclass + +from aws_cdk.core import ( + Construct, + Duration, + Stack, + StackProps +) +from aws_cdk.aws_ec2 import ( + BastionHostLinux, + BlockDevice, + BlockDeviceVolume, + IVpc, + SecurityGroup, + SubnetSelection, + SubnetType +) +from aws_cdk.aws_elasticloadbalancingv2 import ( + ApplicationProtocol +) +from aws_cdk.aws_iam import ( + ManagedPolicy, + Role, + ServicePrincipal +) +from aws_cdk.aws_secretsmanager import ( + Secret +) +from aws_cdk.aws_route53 import ( + IPrivateHostedZone +) + +from aws_rfdk import ( + DistinguishedName, + IMountableLinuxFilesystem, + X509CertificatePem +) +from aws_rfdk.deadline import ( + DatabaseConnection, + RenderQueue, + RenderQueueHostNameProps, + RenderQueueTrafficEncryptionProps, + RenderQueueExternalTLSProps, + Repository, + Stage, + ThinkboxDockerRecipes, +) + + +@dataclass +class ServiceTierProps(StackProps): + """ + Properties for ServiceTier + """ + # The VPC to deploy service tier resources into. + vpc: IVpc + # The database to connect to. + database: DatabaseConnection + # The file system to install Deadline Repository to. + file_system: IMountableLinuxFilesystem + # The path to the directory where the staged Deadline Docker recipes are. + docker_recipes_stage_path: str + # Internal DNS zone for the VPC + dns_zone: IPrivateHostedZone + + +class ServiceTier(Stack): + """ + The service tier contains all "business-logic" constructs + (e.g. Render Queue, UBL Licensing/License Forwarder, etc.). + """ + + def __init__(self, scope: Construct, stack_id: str, *, props: ServiceTierProps, **kwargs): + """ + Initialize a new instance of ServiceTier + :param scope: The scope of this construct. + :param stack_id: The ID of this construct. + :param props: The properties for this construct. + :param kwargs: Any kwargs that need to be passed on to the parent class. + """ + super().__init__(scope, stack_id, **kwargs) + + # A bastion host to connect to the render farm with. + # The bastion host is for convenience (e.g. SSH into RenderQueue and WorkerFleet instances). + # This is not a critical component of the farm, so can safely be removed. + self.bastion = BastionHostLinux( + self, + 'Bastion', + vpc=props.vpc, + subnet_selection=SubnetSelection( + subnet_type=SubnetType.PUBLIC + ), + block_devices=[ + BlockDevice( + device_name='/dev/xvda', + volume=BlockDeviceVolume.ebs(50, encrypted=True) + ) + ] + ) + + # Granting the bastion access to the file system mount for convenience. + # This can also safely be removed. + props.file_system.mount_to_linux_instance( + self.bastion.instance, + location='/mnt/efs' + ) + + recipes = ThinkboxDockerRecipes( + self, + 'Image', + stage=Stage.from_directory(props.docker_recipes_stage_path) + ) + + repository = Repository( + self, + 'Repository', + vpc=props.vpc, + version=recipes.version, + database=props.database, + file_system=props.file_system, + repository_installation_timeout=Duration.minutes(20) + ) + + self.render_queue = RenderQueue( + self, + 'RenderQueue', + vpc=props.vpc, + version=recipes.version, + images=recipes.render_queue_images, + repository=repository, + hostname=RenderQueueHostNameProps( + hostname='renderqueue', + zone=props.dns_zone + ), + # TODO - Evaluate deletion protection for your own needs. This is set to false to + # cleanly remove everything when this stack is destroyed. If you would like to ensure + # that this resource is not accidentally deleted, you should set this to true. + deletion_protection=False + ) + self.render_queue.connections.allow_default_port_from(self.bastion) + + # Adds the following IAM managed Policies to the Render Queue so it has the necessary permissions + # to run the Spot Event Plugin and launch a Resource Tracker: + # * AWSThinkboxDeadlineSpotEventPluginAdminPolicy + # * AWSThinkboxDeadlineResourceTrackerAdminPolicy + self.render_queue.add_sep_policies() + + # Create the security group that you will assign to your workers + self.worker_security_group = SecurityGroup( + self, + 'SpotSecurityGroup', + vpc=props.vpc, + allow_all_outbound=True, + security_group_name='DeadlineSpotSecurityGroup', + ) + self.worker_security_group.connections.allow_to_default_port(self.render_queue.endpoint); + + # Create the IAM Role for the Spot Event Plugins workers. + # Note: This Role MUST have a roleName that begins with "DeadlineSpot" + # Note: If you already have a worker IAM role in your account you can remove this code. + self.worker_iam_role = Role( + self, + 'SpotWorkerRole', + assumed_by=ServicePrincipal('ec2.amazonaws.com'), + managed_policies= [ManagedPolicy.from_aws_managed_policy_name('AWSThinkboxDeadlineSpotEventPluginWorkerPolicy')], + role_name= 'DeadlineSpotWorkerRole', + ); + + # Creates the Resource Tracker Access role. This role is required to exist in your account so the resource tracker will work properly + # Note: If you already have a Resource Tracker IAM role in your account you can remove this code. + Role( + self, + 'ResourceTrackerRole', + assumed_by=ServicePrincipal('lambda.amazonaws.com'), + managed_policies= [ManagedPolicy.from_aws_managed_policy_name('AWSThinkboxDeadlineResourceTrackerAccessPolicy')], + role_name= 'DeadlineResourceTrackerAccessRole', + ) + diff --git a/examples/deadline/All-In-AWS-Infrastructure-SEP/python/package/lib/storage_tier.py b/examples/deadline/All-In-AWS-Infrastructure-SEP/python/package/lib/storage_tier.py new file mode 100644 index 000000000..e84285563 --- /dev/null +++ b/examples/deadline/All-In-AWS-Infrastructure-SEP/python/package/lib/storage_tier.py @@ -0,0 +1,261 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +import json +from dataclasses import dataclass +from typing import Optional + +from aws_cdk.core import ( + Construct, + Duration, + RemovalPolicy, + Stack, + StackProps +) +from aws_cdk.aws_docdb import ( + BackupProps, + DatabaseCluster, + InstanceProps, + Login +) +from aws_cdk.aws_ec2 import ( + InstanceType, + IVpc, + SubnetSelection, + SubnetType +) +from aws_cdk.aws_efs import ( + FileSystem, +) +from aws_cdk.aws_route53 import ( + IPrivateHostedZone +) + +from aws_rfdk import ( + MongoDbUsers, + MongoDbX509User, + DistinguishedName, + MongoDbInstance, + MongoDbApplicationProps, + MongoDbPostInstallSetup, + MongoDbSsplLicenseAcceptance, + MongoDbVersion, + MountableEfs, + X509CertificatePem, + X509CertificatePkcs12 +) + +from aws_rfdk.deadline import ( + DatabaseConnection +) + + +@dataclass +class StorageTierProps(StackProps): + """ + Properties for StorageTier + """ + # The VPC to deploy resources into. + vpc: IVpc + + +class StorageTier(Stack): + """ + The storage tier of the render farm. + This stack contains all constructs that persist data which would be useful to keep between deployments. + There should little to no "business-logic" constructs in this stack. + """ + + def __init__(self, scope: Construct, stack_id: str, *, props: StorageTierProps, **kwargs): + """ + Initializes a new instance of StorageTier + :param scope: The scope of this construct. + :param stack_id: The ID of this construct. + :param props: The properties for the storage tier. + :param kwargs: Any kwargs that need to be passed on to the parent class. + """ + super().__init__(scope, stack_id, **kwargs) + # The file system to use (e.g. to install Deadline Repository onto). + self.file_system = MountableEfs( + self, + filesystem=FileSystem( + self, + 'EfsFileSystem', + vpc=props.vpc, + # TODO - Evaluate this removal policy for your own needs. This is set to DESTROY to + # cleanly remove everything when this stack is destroyed. If you would like to ensure + # that your data is not accidentally deleted, you should modify this value. + removal_policy=RemovalPolicy.DESTROY + ) + ) + + # The database to connect Deadline to. + self.database: Optional[DatabaseConnection] = None + + +@dataclass +class StorageTierDocDBProps(StorageTierProps): + """ + Properties for StorageTierDocDB. + """ + # The InstanceType for DocDB. + database_instance_type: InstanceType + + +class StorageTierDocDB(StorageTier): + """ + An implementation of StorageTier that is backed by DocumentDB. + """ + + def __init__(self, scope: Construct, stack_id: str, *, props: StorageTierDocDBProps, **kwargs): + """ + Initializes a new instance of StorageTier + :param scope: The scope of this construct. + :param stack_id: the ID of this construct. + :param props: The properties for the storage tier. + :param kwargs: Any kwargs that need to be passed on to the parent class. + """ + super().__init__(scope, stack_id, props=props, **kwargs) + instance_props = InstanceProps( + vpc=props.vpc, + vpc_subnets=SubnetSelection(subnet_type=SubnetType.PRIVATE), + instance_type=props.database_instance_type + ) + + doc_db = DatabaseCluster( + self, + 'DocDBCluster', + instance_props=instance_props, + # TODO - For cost considerations this example only uses 1 Database instance. + # It is recommended that when creating your render farm you use at least 2 instances for redundancy. + instances=1 + master_user=Login(username='adminuser'), + backup=BackupProps( + # We recommend setting the retention of your backups to 15 days + # for security reasons. The default retention is just one day. + # Please note that changing this value will affect cost. + retention=Duration.days(15) + ), + # TODO - Evaluate this removal policy for your own needs. This is set to DESTROY to + # cleanly remove everything when this stack is destroyed. If you would like to ensure + # that your data is not accidentally deleted, you should modify this value. + removal_policy=RemovalPolicy.DESTROY + ) + + self.database = DatabaseConnection.for_doc_db( + database=doc_db, + login=doc_db.secret + ) + + +@dataclass +class StorageTierMongoDBProps(StorageTierProps): + """ + Properties for StorageTierMongoDB + """ + # The InstanceType for MongoDB. + database_instance_type: InstanceType + # Self-signed root CA to sign server certificate with. + root_ca: X509CertificatePem + # Internal DNS zone for the VPC. + dns_zone: IPrivateHostedZone + # Whether the SSPL license is accepted or not. + accept_sspl_license: MongoDbSsplLicenseAcceptance + # The name of the EC2 keypair to associate with the MongoDB instance. + key_pair_name: Optional[str] + + +class StorageTierMongoDB(StorageTier): + """ + An implementation of StorageTier that is backed by MongoDB. + """ + + def __init__(self, scope: Construct, stack_id: str, *, props: StorageTierMongoDBProps, **kwargs): + """ + Initialize a new instance of StorageTierMongoDB + :param scope: The scope of this construct. + :param stack_id: The ID of this construct. + :param props: The properties for this construct. + :param kwargs: Any kwargs that need to be passed on to the parent class. + """ + super().__init__(scope, stack_id, props=props, **kwargs) + + server_cert = X509CertificatePem( + self, + 'MongoCert', + subject=DistinguishedName( + cn=f'mongo.{props.dns_zone.zone_name}', + o='RFDK-Sample', + ou='MongoServer' + ), + signing_certificate=props.root_ca + ) + + client_cert = X509CertificatePem( + self, + 'DeadlineMongoCert', + subject=DistinguishedName( + cn='SampleUser', + o='RFDK-Sample', + ou='MongoClient' + ), + signing_certificate=props.root_ca + ) + client_pkcs12 = X509CertificatePkcs12( + self, + 'DeadlineMongoPkcs12', + source_certificate=client_cert + ) + + availability_zone = props.vpc.availability_zones[0] + + mongo_vpc_subnet = SubnetSelection( + subnet_type=SubnetType.PRIVATE, + availability_zones=[availability_zone] + ), + + mongo_db = MongoDbInstance( + self, + 'MongoDb', + vpc=props.vpc, + vpc_subnets=mongo_vpc_subnet, + key_name=props.key_pair_name, + instance_type=props.database_instance_type, + mongo_db=MongoDbApplicationProps( + user_sspl_acceptance=props.accept_sspl_license, + version=MongoDbVersion.COMMUNITY_3_6, + hostname='mongo', + dns_zone=props.dns_zone, + server_certificate=server_cert + ) + ) + + _mongo_db_post_install_setup = MongoDbPostInstallSetup( + self, + 'MongoDbPostInstall', + vpc=props.vpc, + vpc_subnets=mongo_vpc_subnet, + mongo_db=mongo_db, + users=MongoDbUsers( + x509_auth_users=[ + MongoDbX509User( + certificate=client_cert, + roles=json.dumps([ + { + 'role': 'readWriteAnyDatabase', + 'db': 'admin' + }, + { + 'role': 'clusterMonitor', + 'db': 'admin' + } + ]) + ) + ] + ) + ) + + self.database = DatabaseConnection.for_mongo_db_instance( + database=mongo_db, + client_certificate=client_pkcs12 + ) diff --git a/examples/deadline/All-In-AWS-Infrastructure-SEP/python/requirements.txt b/examples/deadline/All-In-AWS-Infrastructure-SEP/python/requirements.txt new file mode 100644 index 000000000..d6e1198b1 --- /dev/null +++ b/examples/deadline/All-In-AWS-Infrastructure-SEP/python/requirements.txt @@ -0,0 +1 @@ +-e . diff --git a/examples/deadline/All-In-AWS-Infrastructure-SEP/python/setup.py b/examples/deadline/All-In-AWS-Infrastructure-SEP/python/setup.py new file mode 100644 index 000000000..686f16bc9 --- /dev/null +++ b/examples/deadline/All-In-AWS-Infrastructure-SEP/python/setup.py @@ -0,0 +1,25 @@ +import setuptools + + +with open("README.md") as fp: + long_description = fp.read() + + +setuptools.setup( + name="all_in_aws_infrastructure_sep", + version="0.0.1", + + description="RFDK All In AWS Infrastructure SEP", + long_description=long_description, + long_description_content_type="text/markdown", + + package_dir={"": "package"}, + packages=setuptools.find_packages(where="package"), + + install_requires=[ + "aws-cdk.core==1.66.0", + "aws-rfdk==0.18.0" + ], + + python_requires=">=3.7", +) diff --git a/examples/deadline/All-In-AWS-Infrastructure-SEP/python/source.bat b/examples/deadline/All-In-AWS-Infrastructure-SEP/python/source.bat new file mode 100644 index 000000000..8f5744291 --- /dev/null +++ b/examples/deadline/All-In-AWS-Infrastructure-SEP/python/source.bat @@ -0,0 +1,13 @@ +@echo off + +rem The sole purpose of this script is to make the command +rem +rem source .env/bin/activate +rem +rem (which activates a Python virtualenv on Linux or Mac OS X) work on Windows. +rem On Windows, this command just runs this batch file (the argument is ignored). +rem +rem Now we don't need to document a Windows command for activating a virtualenv. + +echo Executing .env\Scripts\activate.bat for you +.env\Scripts\activate.bat diff --git a/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/.gitignore b/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/.gitignore new file mode 100644 index 000000000..e52f47dfa --- /dev/null +++ b/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/.gitignore @@ -0,0 +1,20 @@ +*.d.ts +*.js +cdk.out +cdk.context.json + +# Used by nyc +.nyc_output +coverage +.nycrc + +# Exclude package artifacts +dist +*.tgz +.LAST_PACKAGE + +#Defines license that must be present +!license-header.js + +# The staged files for Deadline +stage \ No newline at end of file diff --git a/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/.npmignore b/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/.npmignore new file mode 100644 index 000000000..a0a349e9e --- /dev/null +++ b/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/.npmignore @@ -0,0 +1,18 @@ +################################################ +# if .npmignore exists, NPM ignores .gitignore # +################################################ + +# Exclude typescript source and config files +*.ts +tsconfig.json +*.tsbuildinfo + +# Include typescript declarations +!*.d.ts + +# Exclude jest config +jest.config.js + +# Exclude packaging artifacts +dist +.LAST_PACKAGE diff --git a/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/README.md b/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/README.md new file mode 100644 index 000000000..de7556022 --- /dev/null +++ b/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/README.md @@ -0,0 +1,75 @@ +# RFDK Sample Application - Deadline - Typescript + +## Overview +[Back to overview](../README.md) + +## Instructions + +--- +**NOTE** + +These instructions assume that your working directory is `examples/deadline/All-In-AWS-Infrastructure-SEP/ts/` relative to the root of the RFDK package. + +--- + +1. Install the dependencies of the sample app: + + ``` + yarn install + ``` +2. Create an EC2 key pair to give you SSH access to the render farm: + + ``` + aws ec2 create-key-pair --key-name + ``` +3. Change the value of the `keyPairName` variable in `bin/config.ts` to your value for `` in the previous step: + + **Note:** Save the value of the `"KeyMaterial"` field as a file in a secure location. This is your private key that you can use to SSH into the render farm. + + ```ts + public readonly keyPairName: string = ''; + ``` +4. Choose the type of database you would like to deploy (AWS DocumentDB or MongoDB). + If you would like to use MongoDB, you will need to accept the Mongo SSPL (see next step). + Once you've decided on a database type, change the value of the `deployMongoDB` variable in `bin/config.ts` accordingly: + + ```ts + // true = MongoDB, false = Amazon DocumentDB + public readonly deployMongoDB: boolean = false; + ``` +5. If you set `deployMongoDB` to `true`, then you must accept the [SSPL license](https://www.mongodb.com/licensing/server-side-public-license) to successfully deploy MongoDB. To do so, change the value of `acceptSsplLicense` in `bin/config.ts`: + + ```ts + // To accept the MongoDB SSPL, change from USER_REJECTS_SSPL to USER_ACCEPTS_SSPL + public readonly acceptSsplLicense: MongoDbSsplLicenseAcceptance = MongoDbSsplLicenseAcceptance.USER_REJECTS_SSPL; + ``` +6. Modify the `deadline_ver` field in the `config` block of `package.json` as desired (Deadline 10.1.9 and up are supported), then stage the Docker recipes for `RenderQueue`: + + ``` + yarn stage + ``` +10. Build the sample app: + + ``` + yarn build + ``` +11. Deploy all the stacks in the sample app: + + ``` + cdk deploy "*" + ``` + +12. Connect to your Render Farm and open up the Deadline Monitor. + +13. Configure the Spot event plugin by following the directions in the [Spot Event Plugin documentation](https://docs.thinkboxsoftware.com/products/deadline/10.1/1_User%20Manual/manual/event-spot.html) with the following considerations: + + Use the default security credentials by using turning "Use Local Credentials" to False and leaving both "Access Key ID" and "Secret Access Key" blank. + Ensure that the Region your Spot workers will be launched in is the same region as your CDK application. + When Creating your Spot Fleet Requests, set the IAM instance profile to "DeadlineSpotWorkerRole" and set the security group to "DeadlineSpotSecurityGroup". + Configure your instances to connect to the Render Queue by either creating your AMI after launching your app and preconfiguring the AMI or by setting up a userdata in the Spot Fleet Request. (see the Spot Event Plugin documentation for additional information on configuring this connection.) + +14. Once you are finished with the sample app, you can tear it down by running: + + ``` + cdk destroy "*" + ``` diff --git a/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/bin/app.ts b/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/bin/app.ts new file mode 100644 index 000000000..f1b78873d --- /dev/null +++ b/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/bin/app.ts @@ -0,0 +1,92 @@ +#!/usr/bin/env node +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import 'source-map-support/register'; +import * as path from 'path'; +import * as pkg from '../package.json'; +import { config } from './config'; +import * as cdk from '@aws-cdk/core'; +import { NetworkTier } from '../lib/network-tier'; +import { ServiceTier } from '../lib/service-tier'; +import { + StorageTier, + StorageTierDocDB, + StorageTierMongoDB, +} from '../lib/storage-tier'; +import { SecurityTier } from '../lib/security-tier'; +import { + InstanceClass, + InstanceSize, + InstanceType, +} from '@aws-cdk/aws-ec2'; + + // ------------------------------ // + // --- Validate Config Values --- // + // ------------------------------ // + + if (!config.keyPairName) { + console.log('EC2 key pair name not specified. You will not have SSH access to the render farm.'); + } + +// ------------------- // +// --- Application --- // +// ------------------- // + +const env = { + account: process.env.CDK_DEPLOY_ACCOUNT ?? process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEPLOY_REGION ?? process.env.CDK_DEFAULT_REGION, +}; + +const app = new cdk.App(); + +// -------------------- // +// --- Network Tier --- // +// -------------------- // + +const network = new NetworkTier(app, 'NetworkTier', { env }); + +// --------------------- // +// --- Security Tier --- // +// --------------------- // + +const security = new SecurityTier(app, 'SecurityTier', { env }); + +// -------------------- // +// --- Storage Tier --- // +// -------------------- // + +let storage: StorageTier; +if (config.deployMongoDB) { + storage = new StorageTierMongoDB(app, 'StorageTier', { + env, + vpc: network.vpc, + databaseInstanceType: InstanceType.of(InstanceClass.R5, InstanceSize.LARGE), + rootCa: security.rootCa, + dnsZone: network.dnsZone, + acceptSsplLicense: config.acceptSsplLicense, + keyPairName: config.keyPairName ? config.keyPairName : undefined, + }); +} else { + storage = new StorageTierDocDB(app, 'StorageTier', { + env, + vpc: network.vpc, + databaseInstanceType: InstanceType.of(InstanceClass.R5, InstanceSize.LARGE), + }); +} + +// -------------------- // +// --- Service Tier --- // +// -------------------- // + +new ServiceTier(app, 'ServiceTier', { + env, + database: storage.database, + fileSystem: storage.fileSystem, + vpc: network.vpc, + dockerRecipesStagePath: path.join(__dirname, '..', pkg.config.stage_path), // Stage directory in config is relative, make it absolute + rootCa: security.rootCa, + dnsZone: network.dnsZone, +}); \ No newline at end of file diff --git a/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/bin/config.ts b/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/bin/config.ts new file mode 100644 index 000000000..7daa2d898 --- /dev/null +++ b/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/bin/config.ts @@ -0,0 +1,37 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import 'source-map-support/register'; +import { MongoDbSsplLicenseAcceptance } from 'aws-rfdk'; + +/** + * Configuration values for the sample app. + * + * TODO: Fill these in with your own values. + */ +class AppConfig { + + /** + * (Optional) The name of the EC2 keypair to associate with instances. + */ + public readonly keyPairName: string = ''; + + /** + * Whether to use MongoDB to back the render farm. + * If false, then we use Amazon DocumentDB to back the render farm. + */ + public readonly deployMongoDB: boolean = false; + + /** + * This is only relevant if deployMongoDB = true. + * + * Change this value to MongoDbSsplLicenseAcceptance.USER_ACCEPTS_SSPL + * if you wish to accept the SSPL and proceed with MongoDB deployment. + */ + public readonly acceptSsplLicense: MongoDbSsplLicenseAcceptance = MongoDbSsplLicenseAcceptance.USER_REJECTS_SSPL; + +} + +export const config = new AppConfig(); diff --git a/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/cdk.json b/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/cdk.json new file mode 100644 index 000000000..97bafc2cb --- /dev/null +++ b/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/cdk.json @@ -0,0 +1,7 @@ +{ + "app": "npx ts-node bin/app.ts", + "context": { + "@aws-cdk/core:enableStackNameDuplicates": "true", + "aws-cdk:enableDiffNoFail": "true" + } +} diff --git a/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/clean.sh b/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/clean.sh new file mode 100755 index 000000000..0a907b641 --- /dev/null +++ b/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/clean.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +set -euo pipefail + +rm -rf node_modules/ cdk.out/ cdk.context.json stage/ diff --git a/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/lib/network-tier.ts b/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/lib/network-tier.ts new file mode 100644 index 000000000..07b37b9b5 --- /dev/null +++ b/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/lib/network-tier.ts @@ -0,0 +1,142 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + FlowLogDestination, + FlowLogTrafficType, + GatewayVpcEndpointAwsService, + IInterfaceVpcEndpointService, + IGatewayVpcEndpointService, + InterfaceVpcEndpointAwsService, + IVpc, + SubnetSelection, + SubnetType, + Vpc, +} from '@aws-cdk/aws-ec2'; +import { + PrivateHostedZone, +} from '@aws-cdk/aws-route53'; +import * as cdk from '@aws-cdk/core'; + +/** + * The network tier consists of all constructs that are required for the foundational + * networking between the various components of the Deadline render farm. + */ +export class NetworkTier extends cdk.Stack { + /** + * The VPC that all components of the render farm will be created in. + */ + public readonly vpc: IVpc; + + /** + * Internal DNS zone for the VPC. + */ + public readonly dnsZone: PrivateHostedZone; + + /** + * The interface endpoints for the AWS services used in this app. + */ + private static readonly INTERFACE_ENDPOINT_SERVICES: { name: string, service: IInterfaceVpcEndpointService }[] = [ + { name: 'CLOUDWATCH', service: InterfaceVpcEndpointAwsService.CLOUDWATCH }, + { name: 'CLOUDWATCH_EVENTS', service: InterfaceVpcEndpointAwsService.CLOUDWATCH_EVENTS }, + { name: 'CLOUDWATCH_LOGS', service: InterfaceVpcEndpointAwsService.CLOUDWATCH_LOGS }, + { name: 'EC2', service: InterfaceVpcEndpointAwsService.EC2 }, + { name: 'ECR', service: InterfaceVpcEndpointAwsService.ECR }, + { name: 'ECS', service: InterfaceVpcEndpointAwsService.ECS }, + { name: 'KMS', service: InterfaceVpcEndpointAwsService.KMS }, + { name: 'SECRETS_MANAGER', service: InterfaceVpcEndpointAwsService.SECRETS_MANAGER }, + { name: 'SNS', service: InterfaceVpcEndpointAwsService.SNS }, + { name: 'STS', service: InterfaceVpcEndpointAwsService.STS }, + ]; + + /** + * The gateway endpoints for the AWS services used in this app. + */ + private static readonly GATEWAY_ENDPOINT_SERVICES: { name: string, service: IGatewayVpcEndpointService }[] = [ + { name: 'S3', service: GatewayVpcEndpointAwsService.S3 }, + { name: 'DYNAMODB', service: GatewayVpcEndpointAwsService.DYNAMODB }, + ]; + + /** + * Initializes a new instance of {@link NetworkTier}. + * @param scope The scope of this construct. + * @param id The ID of this construct. + * @param props The stack properties. + */ + constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + this.vpc = new Vpc(this, 'Vpc', { + maxAzs: 2, + subnetConfiguration: [ + { + name: 'Public', + subnetType: SubnetType.PUBLIC, + cidrMask: 28, + }, + { + name: 'Private', + subnetType: SubnetType.PRIVATE, + cidrMask: 18, // 16,382 IP addresses + }, + ], + // VPC flow logs are a security best-practice as they allow us + // to capture information about the traffic going in and out of + // the VPC. For more information, see the README for this app. + flowLogs: { + 'NetworkTierFlowLogs': { + trafficType: FlowLogTrafficType.ALL, + destination: FlowLogDestination.toCloudWatchLogs(), + }, + }, + }); + + // TODO - Create a NetworkAcl for your VPC that only allows + // network traffic required for your render farm. This is a + // security best-practice to ensure the safety of your farm. + // The default network ACLs allow all traffic by default, + // whereas custom network ACLs deny all traffic by default. + // For more information, see the README for this app. + // + // Example code to create a custom network ACL: + // const acl = new NetworkAcl(this, 'ACL' { + // vpc: this.vpc, + // subnetSelection: { subnets: this.vpc.publicSubnets } + // }); + // + // You can optionally add rules to allow traffic (e.g. SSH): + // acl.addEntry('SSH', { + // cidr: AclCidr.ipv4(/* some-ipv4-address-cidr */), + // traffic: AclTraffic.tcpPort(22), + // ruleNumber: 1 + // }); + + // Add the required VPC Endpoints + // ------------- + // Subnets to add the VPC endpoints to + const endpointSubnets: SubnetSelection = { subnetType: SubnetType.PRIVATE }; + + // Add interface endpoints + NetworkTier.INTERFACE_ENDPOINT_SERVICES.forEach((serviceInfo, idx) => { + this.vpc.addInterfaceEndpoint(`${serviceInfo.name}${idx}`, { + service: serviceInfo.service, + subnets: endpointSubnets, + }); + }); + + // Add gateway endpoints + NetworkTier.GATEWAY_ENDPOINT_SERVICES.forEach(serviceInfo => { + this.vpc.addGatewayEndpoint(serviceInfo.name, { + service: serviceInfo.service, + subnets: [ endpointSubnets ], + }); + }); + + this.dnsZone = new PrivateHostedZone(this, 'DnsZone', { + vpc: this.vpc, + zoneName: 'deadline-test.internal', + }); + } +} diff --git a/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/lib/security-tier.ts b/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/lib/security-tier.ts new file mode 100644 index 000000000..f4e4376af --- /dev/null +++ b/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/lib/security-tier.ts @@ -0,0 +1,36 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as cdk from '@aws-cdk/core'; +import { + X509CertificatePem, +} from 'aws-rfdk'; + +/** + * The security tier of the render farm. This stack contains resources used to + * ensure the render farm is secure. + */ +export class SecurityTier extends cdk.Stack { + /** + * Our self-signed root CA certificate for the internal endpoints in the farm. + */ + readonly rootCa: X509CertificatePem; + + /** + * Initializes a new instance of {@link SecurityTier}. + * @param scope The scope of this construct. + * @param id The ID of this construct. + * @param props The properties for the security tier. + */ + constructor(scope: cdk.Construct, id: string, props: cdk.StackProps) { + super(scope, id, props); + + this.rootCa = new X509CertificatePem(this, 'RootCA', { + subject: { + cn: 'SampleRootCA', + }, + }); + } +}; diff --git a/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/lib/service-tier.ts b/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/lib/service-tier.ts new file mode 100644 index 000000000..4577ffd8a --- /dev/null +++ b/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/lib/service-tier.ts @@ -0,0 +1,182 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + BastionHostLinux, + BlockDeviceVolume, + IVpc, + SecurityGroup, + SubnetType, +} from '@aws-cdk/aws-ec2'; +import { + IPrivateHostedZone, +} from '@aws-cdk/aws-route53'; +import * as cdk from '@aws-cdk/core'; +import { + IMountableLinuxFilesystem, + X509CertificatePem, +} from 'aws-rfdk'; +import { + DatabaseConnection, + RenderQueue, + Repository, + Stage, + ThinkboxDockerRecipes, +} from 'aws-rfdk/deadline'; +import { Duration } from '@aws-cdk/core'; +import { ManagedPolicy, Role, ServicePrincipal } from '@aws-cdk/aws-iam'; + +/** + * Properties for {@link ServiceTier}. + */ +export interface ServiceTierProps extends cdk.StackProps { + /** + * The VPC to deploy service tier resources into. + */ + readonly vpc: IVpc; + + /** + * The database to connect to. + */ + readonly database: DatabaseConnection; + + /** + * The file system to install Deadline Repository to. + */ + readonly fileSystem: IMountableLinuxFilesystem; + + /** + * The path to the directory where the staged Deadline Docker recipes are. + */ + readonly dockerRecipesStagePath: string; + + /** + * Our self-signed root CA certificate for the internal endpoints in the farm. + */ + readonly rootCa: X509CertificatePem; + + /** + * Internal DNS zone for the VPC. + */ + readonly dnsZone: IPrivateHostedZone; +} + +/** + * The service tier contains all "business-logic" constructs (e.g. Render Queue, UBL Licensing / License Forwarder, etc.). + */ +export class ServiceTier extends cdk.Stack { + /** + * The render queue. + */ + public readonly renderQueue: RenderQueue; + + /** + * A bastion host to connect to the render farm with. + */ + public readonly bastion: BastionHostLinux; + + /** + * A security Group that can be assigned to your spot workers so they can access the RenderQueue + */ + public readonly workerSecurityGroup: SecurityGroup; + + /** + * + */ + public readonly workerIamRole: Role; + + /** + * Initializes a new instance of {@link ServiceTier}. + * @param scope The scope of this construct. + * @param id The ID of this construct. + * @param props The properties for this construct. + */ + constructor(scope: cdk.Construct, id: string, props: ServiceTierProps) { + super(scope, id, props); + + // Bastion instance for convenience (e.g. SSH into RenderQueue and WorkerFleet instances) + // Not a critical component of the farm, so this can be safely removed + this.bastion = new BastionHostLinux(this, 'Bastion', { + vpc: props.vpc, + subnetSelection: { + subnetType: SubnetType.PUBLIC, + }, + blockDevices: [{ + deviceName: '/dev/xvda', + volume: BlockDeviceVolume.ebs(50, { + encrypted: true, + })}, + ] + }); + // Granting the bastion access to the file system mount for convenience + // This can also be safely removed + props.fileSystem.mountToLinuxInstance(this.bastion.instance, { + location: '/mnt/efs', + }); + + const recipes = new ThinkboxDockerRecipes(this, 'Image', { + stage: Stage.fromDirectory(props.dockerRecipesStagePath), + }); + + const repository = new Repository(this, 'Repository', { + vpc: props.vpc, + version: recipes.version, + database: props.database, + fileSystem: props.fileSystem, + repositoryInstallationTimeout: Duration.minutes(20), + }); + + this.renderQueue = new RenderQueue(this, 'RenderQueue', { + vpc: props.vpc, + version: recipes.version, + images: recipes.renderQueueImages, + repository: repository, + hostname: { + hostname: 'renderqueue', + zone: props.dnsZone, + }, + // TODO - Evaluate deletion protection for your own needs. This is set to false to + // cleanly remove everything when this stack is destroyed. If you would like to ensure + // that this resource is not accidentally deleted, you should set this to true. + deletionProtection: false, + }); + this.renderQueue.connections.allowDefaultPortFrom(this.bastion); + + // Adds the following IAM managed Policies to the Render Queue so it has the necessary permissions + // to run the Spot Event Plugin and launch a Resource Tracker: + // * AWSThinkboxDeadlineSpotEventPluginAdminPolicy + // * AWSThinkboxDeadlineResourceTrackerAdminPolicy + this.renderQueue.addSEPPolicies(); + + // Create the security group that you will assign to your workers + this.workerSecurityGroup = new SecurityGroup(this, 'SpotSecurityGroup', { + vpc: props.vpc, + allowAllOutbound: true, + securityGroupName: 'DeadlineSpotSecurityGroup', + }); + this.workerSecurityGroup.connections.allowToDefaultPort(this.renderQueue.endpoint); + + // Create the IAM Role for the Spot Event Plugins workers. + // Note: This Role MUST have a roleName that begins with "DeadlineSpot" + // Note if you already have a worker IAM role in your account you can remove this code. + this.workerIamRole = new Role( this, 'SpotWorkerRole', { + assumedBy: new ServicePrincipal('ec2.amazonaws.com'), + managedPolicies: [ + ManagedPolicy.fromAwsManagedPolicyName('AWSThinkboxDeadlineSpotEventPluginWorkerPolicy'), + ], + roleName: 'DeadlineSpotWorkerRole', + }); + + // Creates the Resource Tracker Access role. This role is required to exist in your account so the resource tracker will work properly + // Note: If you already have a Resource Tracker IAM role in your account you can remove this code. + new Role( this, 'ResourceTrackerRole', { + assumedBy: new ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + ManagedPolicy.fromAwsManagedPolicyName('AWSThinkboxDeadlineResourceTrackerAccessPolicy'), + ], + roleName: 'DeadlineResourceTrackerAccessRole', + }); + } +} diff --git a/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/lib/storage-tier.ts b/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/lib/storage-tier.ts new file mode 100644 index 000000000..f47f3966c --- /dev/null +++ b/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/lib/storage-tier.ts @@ -0,0 +1,243 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + InstanceType, + IVpc, + SubnetType, +} from '@aws-cdk/aws-ec2'; +import * as cdk from '@aws-cdk/core'; +import { DatabaseCluster } from '@aws-cdk/aws-docdb'; +import { FileSystem } from '@aws-cdk/aws-efs'; +import { IPrivateHostedZone } from '@aws-cdk/aws-route53'; +import { RemovalPolicy, Duration } from '@aws-cdk/core'; +import { + IMountableLinuxFilesystem, + MongoDbInstance, + MongoDbPostInstallSetup, + MongoDbSsplLicenseAcceptance, + MongoDbVersion, + MountableEfs, + X509CertificatePem, + X509CertificatePkcs12, +} from 'aws-rfdk'; +import { + DatabaseConnection, +} from 'aws-rfdk/deadline'; + + +/** + * Properties for {@link StorageTier}. + */ +export interface StorageTierProps extends cdk.StackProps { + /** + * The VPC to deploy resources into. + */ + readonly vpc: IVpc; +} + +/** + * The storage tier of the render farm. This stack contains all constructs that persist + * data which would be useful to keep between deployments. There should little to no "business-logic" + * constructs in this stack. + */ +export abstract class StorageTier extends cdk.Stack { + /** + * The file system to use (e.g. to install Deadline Repository onto). + */ + public readonly fileSystem: IMountableLinuxFilesystem; + + /** + * The database to connect Deadline to. + */ + public abstract readonly database: DatabaseConnection; + + /** + * Initializes a new instance of {@link StorageTier}. + * @param scope The scope of this construct. + * @param id The ID of this construct. + * @param props The properties for the storage tier. + */ + constructor(scope: cdk.Construct, id: string, props: StorageTierProps) { + super(scope, id, props); + + this.fileSystem = new MountableEfs(this, { + filesystem: new FileSystem(this, 'EfsFileSystem', { + vpc: props.vpc, + encrypted: true, + // TODO - Evaluate this removal policy for your own needs. This is set to DESTROY to + // cleanly remove everything when this stack is destroyed. If you would like to ensure + // that your data is not accidentally deleted, you should modify this value. + removalPolicy: RemovalPolicy.DESTROY, + }), + }); + } +} + +/** + * Properties for {@link StorageTierDocDB}. + */ +export interface StorageTierDocDBProps extends StorageTierProps { + /** + * The {@link InstanceType} for DocDB. + */ + readonly databaseInstanceType: InstanceType; +} + +/** + * An implementation of {@link StorageTier} that is backed by DocumentDB. + */ +export class StorageTierDocDB extends StorageTier { + /** + * The DocDB connection. + */ + public readonly database: DatabaseConnection; + + /** + * Initiailizes a new instance of {@link StorageTierDocDB}. + * @param scope The scope of this construct. + * @param id The ID of this construct. + * @param props The properties for this construct. + */ + constructor(scope: cdk.Construct, id: string, props: StorageTierDocDBProps) { + super(scope, id, props); + + const docDb = new DatabaseCluster(this, 'DocDBCluster', { + instanceProps: { + vpc: props.vpc, + vpcSubnets: { subnetType: SubnetType.PRIVATE }, + instanceType: props.databaseInstanceType, + }, + // TODO - For cost considerations this example only uses 1 Database instance. + // It is recommended that when creating your render farm you use at least 2 instances for redundancy. + instances: 1, + masterUser: { + username: 'adminuser', + }, + backup: { + // We recommend setting the retention of your backups to 15 days + // for security reasons. The default retention is just one day. + // Please note that changing this value will affect cost. + retention: Duration.days(15), + }, + // TODO - Evaluate this removal policy for your own needs. This is set to DESTROY to + // cleanly remove everything when this stack is destroyed. If you would like to ensure + // that your data is not accidentally deleted, you should modify this value. + removalPolicy: RemovalPolicy.DESTROY, + }); + + this.database = DatabaseConnection.forDocDB({ + database: docDb, + login: docDb.secret!, + }); + } +} + +/** + * Properties for {@link StorageTierMongoDB}. + */ +export interface StorageTierMongoDBProps extends StorageTierProps { + /** + * The {@link InstanceType} for MongoDB. + */ + readonly databaseInstanceType: InstanceType; + + /** + * Self-signed root CA to sign server certificates with. + */ + readonly rootCa: X509CertificatePem; + + /** + * Internal DNS zone for the VPC. + */ + readonly dnsZone: IPrivateHostedZone; + + /** + * Whether the SSPL license is accepted or not. + */ + readonly acceptSsplLicense: MongoDbSsplLicenseAcceptance; + + /** + * The name of the EC2 keypair to associate with the MongoDB instance. + */ + readonly keyPairName?: string; +} + +/** + * An implementation of {@link StorageTier} that is backed by MongoDB. + */ +export class StorageTierMongoDB extends StorageTier { + /** + * The MongoDB connection. + */ + public readonly database: DatabaseConnection; + + + /** + * Initiailizes a new instance of {@link StorageTierMongoDB}. + * @param scope The scope of this construct. + * @param id The ID of this construct. + * @param props The properties for this construct. + */ + constructor(scope: cdk.Construct, id: string, props: StorageTierMongoDBProps) { + super(scope, id, props); + + const serverCert = new X509CertificatePem(this, 'MongoCert', { + subject: { + cn: `mongo.${props.dnsZone.zoneName}`, + o: 'RFDK-Sample', + ou: 'MongoServer', + }, + signingCertificate: props.rootCa, + }); + const clientCert = new X509CertificatePem(this, 'DeadlineMongoCert', { + subject: { + cn: 'SampleUser', + o: 'RFDK-Sample', + ou: 'MongoClient', + }, + signingCertificate: props.rootCa, + }); + const clientPkcs12 = new X509CertificatePkcs12(this, 'DeadlineMongoPkcs12', { + sourceCertificate: clientCert, + }); + + const availabilityZone = props.vpc.availabilityZones[0]; + + const mongoDb = new MongoDbInstance(this, 'MongoDb', { + vpc: props.vpc, + vpcSubnets: { subnetType: SubnetType.PRIVATE, availabilityZones: [ availabilityZone ] }, + keyName: props.keyPairName, + instanceType: props.databaseInstanceType, + mongoDb: { + userSsplAcceptance: props.acceptSsplLicense, + version: MongoDbVersion.COMMUNITY_3_6, + hostname: 'mongo', + dnsZone: props.dnsZone, + serverCertificate: serverCert, + }, + }); + + new MongoDbPostInstallSetup(this, 'MongoDbPostInstall', { + vpc: props.vpc, + vpcSubnets: { subnetType: SubnetType.PRIVATE, availabilityZones: [ availabilityZone ] }, + mongoDb, + users: { + x509AuthUsers: [ + { + certificate: clientCert.cert, + // Default roles set by Deadline when it creates an X.509 user in MongoDB. + roles: JSON.stringify([ { role: 'readWriteAnyDatabase', db: 'admin' }, { role: 'clusterMonitor', db: 'admin' } ]), + }, + ], + }, + }); + + this.database = DatabaseConnection.forMongoDbInstance({ + database: mongoDb, + clientCertificate: clientPkcs12, + }); + } +} diff --git a/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/package.json b/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/package.json new file mode 100644 index 000000000..b4e7f6152 --- /dev/null +++ b/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/package.json @@ -0,0 +1,31 @@ +{ + "name": "all-in-farm-sep", + "version": "0.18.0", + "bin": { + "app": "bin/app.js" + }, + "config": { + "deadline_ver": "10.1.9.2", + "stage_path": "stage" + }, + "scripts": { + "build": "tsc", + "build+test": "yarn build && yarn test", + "cdk": "cdk", + "clean": "tsc --build --clean && bash ./clean.sh", + "stage": "stage-deadline --deadlineInstallerURI s3://thinkbox-installers/Deadline/${npm_package_config_deadline_ver}/Linux/DeadlineClient-${npm_package_config_deadline_ver}-linux-x64-installer.run --dockerRecipesURI s3://thinkbox-installers/DeadlineDocker/${npm_package_config_deadline_ver}/DeadlineDocker-${npm_package_config_deadline_ver}.tar.gz --output ${npm_package_config_stage_path}", + "test": "echo 'no tests to run'", + "watch": "tsc -w" + }, + "devDependencies": { + "@types/node": "^14.6.1", + "aws-cdk": "1.66.0", + "ts-node": "^9.0.0", + "typescript": "~3.9.7" + }, + "dependencies": { + "@aws-cdk/core": "1.66.0", + "aws-rfdk": "0.18.0", + "source-map-support": "^0.5.19" + } +} diff --git a/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/tsconfig.json b/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/tsconfig.json new file mode 100644 index 000000000..f247c93ca --- /dev/null +++ b/examples/deadline/All-In-AWS-Infrastructure-SEP/ts/tsconfig.json @@ -0,0 +1,35 @@ +{ + "compilerOptions": { + "target": "ES2018", + "module": "commonjs", + "lib": [ + "es2018" + ], + "strict": true, + "alwaysStrict": true, + "declaration": true, + "inlineSourceMap": true, + "inlineSources": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "resolveJsonModule": true, + "composite": true, + "incremental": true + }, + "exclude": [ + "cdk.out" + ], + "include": [ + "**/*.ts" + ], + "files": [ + "package.json" + ], + "references": [ + { + "path": "../../../../packages/aws-rfdk" + } + ] +}