diff --git a/examples/deadline/All-In-AWS-Infrastructure-Basic/python/README.md b/examples/deadline/All-In-AWS-Infrastructure-Basic/python/README.md index 0d3e4f8a6..6229a3129 100644 --- a/examples/deadline/All-In-AWS-Infrastructure-Basic/python/README.md +++ b/examples/deadline/All-In-AWS-Infrastructure-Basic/python/README.md @@ -12,12 +12,27 @@ These instructions assume that your working directory is `examples/deadline/All- --- -1. Install the dependencies of the sample app: +1. This sample app on the `mainline` branch may contain features that have not yet been officially released, and may not be available in the `aws-rfdk` package installed through pip from PyPI. To work from an example of the latest release, please switch to the `release` branch. If you would like to try out unreleased features, you can stay on `mainline` and follow the instructions for building, packing, and installing the `aws-rfdk` from your local repository. +2. Install the dependencies of the sample app: ```bash pip install -r requirements.txt ``` -2. Change the value in the `deadline_client_linux_ami_map` variable in `package/config.py` to include the region + AMI ID mapping of your EC2 AMI(s) with Deadline Worker. +3. If working on the `release` branch, this step can be skipped. If working on `mainline`, navigate to the base directory where the build and packaging scripts are, then run them and install the result over top of the `aws-rfdk` version that was installed in the previous step: + ```bash + # Navigate to the root directory of the RFDK repository + pushd ../../../.. + # Enter the Docker container to run the build and pack scripts + ./scripts/rfdk_build_environment.sh + ./build.sh + ./pack.sh + # Exit the Docker container + exit + # Navigate back to the example directory + popd + pip install ../../../../dist/python/aws-rfdk-.tar.gz + ``` +4. Change the value in the `deadline_client_linux_ami_map` variable in `package/config.py` to include the region + AMI ID mapping of your EC2 AMI(s) with Deadline Worker. ```python # For example, in the us-west-2 region @@ -25,17 +40,17 @@ These instructions assume that your working directory is `examples/deadline/All- 'us-west-2': '' } ``` -3. Create a binary secret in [SecretsManager](https://aws.amazon.com/secrets-manager/) that contains your [Usage-Based Licensing](https://docs.thinkboxsoftware.com/products/deadline/10.1/1_User%20Manual/manual/aws-portal/licensing-setup.html?highlight=usage%20based%20licensing) certificates in a `.zip` file: +5. Create a binary secret in [SecretsManager](https://aws.amazon.com/secrets-manager/) that contains your [Usage-Based Licensing](https://docs.thinkboxsoftware.com/products/deadline/10.1/1_User%20Manual/manual/aws-portal/licensing-setup.html?highlight=usage%20based%20licensing) certificates in a `.zip` file: ```bash aws secretsmanager create-secret --name --secret-binary fileb:// ``` -4. The output from the previous step will contain the secret's ARN. Change the value of the `ubl_certificate_secret_arn` variable in `package/config.py` to the secret's ARN: +6. The output from the previous step will contain the secret's ARN. Change the value of the `ubl_certificate_secret_arn` variable in `package/config.py` to the secret's ARN: ```python self.ubl_certificate_secret_arn: str = '' ``` -5. Choose your UBL limits and change the value of the `ubl_licenses` variable in `package/config.py` accordingly. For example: +7. Choose your UBL limits and change the value of the `ubl_licenses` variable in `package/config.py` accordingly. For example: ```python self.ubl_licenses: List[UsageBasedLicense] = [ @@ -54,19 +69,19 @@ These instructions assume that your working directory is `examples/deadline/All- **Note:** The next two steps are optional. You may skip these if you do not need SSH access into your render farm. --- -6. Create an EC2 key pair to give you SSH access to the render farm: +8. Create an EC2 key pair to give you SSH access to the render farm: ```bash aws ec2 create-key-pair --key-name ``` -7. Change the value of the `key_pair_name` variable in `package/config.py` to your value for `` in the previous step: +9. 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] = '' ``` -8. Choose the type of database you would like to deploy (AWS DocumentDB or MongoDB). +10. 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: @@ -74,13 +89,13 @@ These instructions assume that your working directory is `examples/deadline/All- # True = MongoDB, False = Amazon DocumentDB self.deploy_mongo_db: bool = False ``` -9. 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`: +11. 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 ``` -10. Stage the Docker recipes for `RenderQueue` and `UBLLicensing`: +12. Stage the Docker recipes for `RenderQueue` and `UBLLicensing`: ```bash # Set this value to the version of RFDK your application targets @@ -94,7 +109,7 @@ These instructions assume that your working directory is `examples/deadline/All- --dockerRecipesURI s3://thinkbox-installers/DeadlineDocker/${RFDK_DEADLINE_VERSION}/DeadlineDocker-${RFDK_DEADLINE_VERSION}.tar.gz \ --output stage ``` -11. Deploy all the stacks in the sample app: +12. Deploy all the stacks in the sample app: ```bash cdk deploy "*" diff --git a/examples/deadline/All-In-AWS-Infrastructure-Basic/python/package/lib/service_tier.py b/examples/deadline/All-In-AWS-Infrastructure-Basic/python/package/lib/service_tier.py index fa4886638..b2358eec2 100644 --- a/examples/deadline/All-In-AWS-Infrastructure-Basic/python/package/lib/service_tier.py +++ b/examples/deadline/All-In-AWS-Infrastructure-Basic/python/package/lib/service_tier.py @@ -121,10 +121,10 @@ def __init__(self, scope: Construct, stack_id: str, *, props: ServiceTierProps, self, 'Repository', vpc=props.vpc, - version=recipes.version, database=props.database, file_system=props.file_system, - repository_installation_timeout=Duration.minutes(20) + repository_installation_timeout=Duration.minutes(20), + version=recipes.version, ) server_cert = X509CertificatePem( @@ -142,7 +142,6 @@ def __init__(self, scope: Construct, stack_id: str, *, props: ServiceTierProps, self, 'RenderQueue', vpc=props.vpc, - version=recipes.version, images=recipes.render_queue_images, repository=repository, hostname=RenderQueueHostNameProps( @@ -155,6 +154,7 @@ def __init__(self, scope: Construct, stack_id: str, *, props: ServiceTierProps, ), internal_protocol=ApplicationProtocol.HTTPS ), + version=recipes.version, # 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. diff --git a/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/README.md b/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/README.md index 6f86fe685..c99009546 100644 --- a/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/README.md +++ b/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/README.md @@ -12,12 +12,13 @@ These instructions assume that your working directory is `examples/deadline/All- --- -1. Install the dependencies of the sample app: +1. This sample app on the `mainline` branch may contain features that have not yet been officially released, and may not be available in the `aws-rfdk` package installed through npm from npmjs. To work from an example of the latest release, please switch to the `release` branch. If you would like to try out unreleased features, you can stay on `mainline` and follow the instructions for building and using the `aws-rfdk` from your local repository. +2. Install the dependencies of the sample app: ``` yarn install ``` -2. Change the value in the `deadlineClientLinuxAmiMap` variable in `bin/config.ts` to include the region + AMI ID mapping of your EC2 AMI(s) with Deadline Worker. +3. Change the value in the `deadlineClientLinuxAmiMap` variable in `bin/config.ts` to include the region + AMI ID mapping of your EC2 AMI(s) with Deadline Worker. ```ts // For example, in the us-west-2 region @@ -26,17 +27,17 @@ These instructions assume that your working directory is `examples/deadline/All- // ... }; ``` -3. Create a binary secret in [SecretsManager](https://aws.amazon.com/secrets-manager/) that contains your [Usage-Based Licensing](https://docs.thinkboxsoftware.com/products/deadline/10.1/1_User%20Manual/manual/aws-portal/licensing-setup.html?highlight=usage%20based%20licensing) certificates in a `.zip` file: +4. Create a binary secret in [SecretsManager](https://aws.amazon.com/secrets-manager/) that contains your [Usage-Based Licensing](https://docs.thinkboxsoftware.com/products/deadline/10.1/1_User%20Manual/manual/aws-portal/licensing-setup.html?highlight=usage%20based%20licensing) certificates in a `.zip` file: ``` aws secretsmanager create-secret --name --secret-binary fileb:// ``` -4. The output from the previous step will contain the secret's ARN. Change the value of the `ublCertificatesSecretArn` variable in `bin/config.ts` to the secret's ARN: +5. The output from the previous step will contain the secret's ARN. Change the value of the `ublCertificatesSecretArn` variable in `bin/config.ts` to the secret's ARN: ```ts public readonly ublCertificatesSecretArn: string = ''; ``` -5. Choose your UBL limits and change the value of the `ublLicenses` variable in `bin/config.ts` accordingly. For example: +6. Choose your UBL limits and change the value of the `ublLicenses` variable in `bin/config.ts` accordingly. For example: ```ts public readonly ublLicenses: UsageBasedLicense[] = [ @@ -55,19 +56,19 @@ These instructions assume that your working directory is `examples/deadline/All- **Note:** The next two steps are optional. You may skip these if you do not need SSH access into your render farm. --- -6. Create an EC2 key pair to give you SSH access to the render farm: +7. Create an EC2 key pair to give you SSH access to the render farm: ``` aws ec2 create-key-pair --key-name ``` -7. Change the value of the `keyPairName` variable in `bin/config.ts` to your value for `` in the previous step: +8. 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 = ''; ``` -8. Choose the type of database you would like to deploy (AWS DocumentDB or MongoDB). +9. 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: @@ -75,28 +76,36 @@ These instructions assume that your working directory is `examples/deadline/All- // true = MongoDB, false = Amazon DocumentDB public readonly deployMongoDB: boolean = false; ``` -9. 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`: +10. 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; ``` -10. 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` and `UBLLicensing`: +11. 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` and `UBLLicensing`: ``` yarn stage ``` -11. Build the sample app: - - ``` +12. Build the `aws-rfdk` package, and then build the sample app. There is some magic in the way yarn workspaces and lerna packages work that will link the built `aws-rfdk` from the base directory as the dependency to be used in the example's directory: + ```bash + # Navigate to the root directory of the RFDK repository (assumes you started in the example's directory) + pushd ../../../.. + # Enter the Docker container, run the build, and then exit + ./scripts/rfdk_build_environment.sh + ./build.sh + exit + # Navigate back to the example directory + popd + # Run the example's build yarn build ``` -12. Deploy all the stacks in the sample app: +13. Deploy all the stacks in the sample app: ``` cdk deploy "*" ``` -13. Once you are finished with the sample app, you can tear it down by running: +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-Basic/ts/lib/service-tier.ts b/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/lib/service-tier.ts index e2c0cb605..d8694de7a 100644 --- a/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/lib/service-tier.ts +++ b/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/lib/service-tier.ts @@ -16,7 +16,7 @@ import { IPrivateHostedZone, } from '@aws-cdk/aws-route53'; import * as cdk from '@aws-cdk/core'; -import { +import { IMountableLinuxFilesystem, X509CertificatePem, } from 'aws-rfdk'; @@ -30,8 +30,8 @@ import { UsageBasedLicense, UsageBasedLicensing, } from 'aws-rfdk/deadline'; -import { - Secret +import { + Secret, } from '@aws-cdk/aws-secretsmanager'; import { Duration } from '@aws-cdk/core'; @@ -150,7 +150,6 @@ export class ServiceTier extends cdk.Stack { }); this.renderQueue = new RenderQueue(this, 'RenderQueue', { vpc: props.vpc, - version: recipes.version, images: recipes.renderQueueImages, repository: repository, hostname: { @@ -163,6 +162,7 @@ export class ServiceTier extends cdk.Stack { }, internalProtocol: ApplicationProtocol.HTTPS, }, + version: recipes.version, // 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. @@ -170,7 +170,7 @@ export class ServiceTier extends cdk.Stack { }); this.renderQueue.connections.allowDefaultPortFrom(this.bastion); - const ublCertSecret = Secret.fromSecretArn(this, 'UBLCertsSecret', props.ublCertsSecretArn); + const ublCertSecret = Secret.fromSecretArn(this, 'UBLCertsSecret', props.ublCertsSecretArn); this.ublLicensing = new UsageBasedLicensing(this, 'UBLLicensing', { vpc: props.vpc, images: recipes.ublImages, diff --git a/integ/components/deadline/deadline_01_repository/bin/deadline_01_repository.ts b/integ/components/deadline/deadline_01_repository/bin/deadline_01_repository.ts index 02880ca37..a0143d86d 100644 --- a/integ/components/deadline/deadline_01_repository/bin/deadline_01_repository.ts +++ b/integ/components/deadline/deadline_01_repository/bin/deadline_01_repository.ts @@ -4,6 +4,11 @@ */ import { App, Stack } from '@aws-cdk/core'; +import { + Stage, + ThinkboxDockerRecipes, +} from 'aws-rfdk/deadline'; + import { DatabaseType, StorageStruct } from '../../../../lib/storage-struct'; import { RepositoryTestingTier } from '../lib/repository-testing-tier'; @@ -17,17 +22,26 @@ const integStackTag = process.env.INTEG_STACK_TAG!.toString(); const componentTier = new Stack(app, 'RFDKInteg-DL-ComponentTier' + integStackTag, {env}); +const stagePath = process.env.DEADLINE_STAGING_PATH!.toString(); +// Stage docker recipes, which include the repo installer in (`recipes.version`) +const recipes = new ThinkboxDockerRecipes(componentTier, 'DockerRecipes', { + stage: Stage.fromDirectory(stagePath), +}); + const structs: Array = [ new StorageStruct(componentTier, 'StorageStruct1', { integStackTag, + version: recipes.version, }), new StorageStruct(componentTier, 'StorageStruct2', { integStackTag, databaseType: DatabaseType.DocDB, + version: recipes.version, }), new StorageStruct(componentTier, 'StorageStruct3', { integStackTag, databaseType: DatabaseType.MongoDB, + version: recipes.version, }), ]; diff --git a/integ/components/deadline/deadline_02_renderQueue/bin/deadline_02_renderQueue.ts b/integ/components/deadline/deadline_02_renderQueue/bin/deadline_02_renderQueue.ts index 423fdd948..d4aff6826 100644 --- a/integ/components/deadline/deadline_02_renderQueue/bin/deadline_02_renderQueue.ts +++ b/integ/components/deadline/deadline_02_renderQueue/bin/deadline_02_renderQueue.ts @@ -4,6 +4,11 @@ */ import { App, Stack } from '@aws-cdk/core'; +import { + Stage, + ThinkboxDockerRecipes, +} from 'aws-rfdk/deadline'; + import { RenderStruct } from '../../../../lib/render-struct'; import { DatabaseType, StorageStruct } from '../../../../lib/storage-struct'; import { RenderQueueTestingTier } from '../lib/renderQueue-testing-tier'; @@ -20,10 +25,18 @@ const integStackTag = process.env.INTEG_STACK_TAG!.toString(); // Create component stack const componentTier = new Stack(app, 'RFDKInteg-RQ-ComponentTier' + integStackTag, {env}); +const stagePath = process.env.DEADLINE_STAGING_PATH!.toString(); +// Stage docker recipes, which include the image used for the render queue instance and the repo +// installer (in `recipes.version`) +const recipes = new ThinkboxDockerRecipes(componentTier, 'DockerRecipes', { + stage: Stage.fromDirectory(stagePath), +}); + // Add struct containing Deadline repository (the same repo is used for all test configurations) const storage = new StorageStruct(componentTier, 'StorageStruct', { integStackTag, databaseType: DatabaseType.DocDB, + version: recipes.version, }); const structs: Array = [ @@ -32,12 +45,14 @@ const structs: Array = [ integStackTag, repository: storage.repo, protocol: 'http', + recipes, }), //Create test struct for Render Queue in https mode new RenderStruct(componentTier, 'RenderStructRQ2', { integStackTag, repository: storage.repo, protocol: 'https', + recipes, }), ]; diff --git a/integ/components/deadline/deadline_03_workerFleet/bin/deadline_03_workerFleet.ts b/integ/components/deadline/deadline_03_workerFleet/bin/deadline_03_workerFleet.ts index a597ea7ea..1e4bd2e18 100644 --- a/integ/components/deadline/deadline_03_workerFleet/bin/deadline_03_workerFleet.ts +++ b/integ/components/deadline/deadline_03_workerFleet/bin/deadline_03_workerFleet.ts @@ -4,6 +4,11 @@ */ import { App, Stack } from '@aws-cdk/core'; +import { + Stage, + ThinkboxDockerRecipes, +} from 'aws-rfdk/deadline'; + import { RenderStruct } from '../../../../lib/render-struct'; import { DatabaseType, StorageStruct } from '../../../../lib/storage-struct'; import { WorkerStruct } from '../../../../lib/worker-struct'; @@ -29,16 +34,26 @@ oss.forEach( os => { const testId = 'WF' + i.toString(); // Create component stack for structs const componentTier = new Stack(app, 'RFDKInteg-' + testId + '-ComponentTier' + integStackTag, {env}); + + const stagePath = process.env.DEADLINE_STAGING_PATH!.toString(); + // Stage docker recipes, which include image used for the render queue instance and the repo + // installer (in `recipes.version`) + const recipes = new ThinkboxDockerRecipes(componentTier, 'DockerRecipes', { + stage: Stage.fromDirectory(stagePath), + }); + // Create StorageStruct with repository const storage = new StorageStruct(componentTier, 'StorageStruct' + testId, { integStackTag, databaseType: DatabaseType.DocDB, + version: recipes.version, }); // Create render queue with either HTTP or HTTPS protocol const render = new RenderStruct(componentTier, 'RenderStruct' + testId, { integStackTag, repository: storage.repo, protocol, + recipes, }); // Create worker struct containing three nodes using either Linux or Windows structs.push(new WorkerStruct(componentTier, 'WorkerStruct' + testId, { diff --git a/integ/lib/render-struct.ts b/integ/lib/render-struct.ts index 6d306f008..19b683015 100644 --- a/integ/lib/render-struct.ts +++ b/integ/lib/render-struct.ts @@ -8,12 +8,17 @@ import { ApplicationProtocol } from '@aws-cdk/aws-elasticloadbalancingv2'; import { PrivateHostedZone } from '@aws-cdk/aws-route53'; import { Construct, Stack } from '@aws-cdk/core'; import { X509CertificatePem } from 'aws-rfdk'; -import { IRepository, RenderQueue, Stage, ThinkboxDockerRecipes } from 'aws-rfdk/deadline'; +import { + IRepository, + RenderQueue, + ThinkboxDockerRecipes, +} from 'aws-rfdk/deadline'; export interface RenderStructProps { readonly integStackTag: string; readonly repository: IRepository; readonly protocol: string; + readonly recipes: ThinkboxDockerRecipes; } export class RenderStruct extends Construct { @@ -25,16 +30,10 @@ export class RenderStruct extends Construct { // Collect environment variables const infrastructureStackName = 'RFDKIntegInfrastructure' + props.integStackTag; - const stagePath = process.env.DEADLINE_STAGING_PATH!.toString(); // Retrieve VPC created for _infrastructure stack const vpc = Vpc.fromLookup(this, 'Vpc', { tags: { StackName: infrastructureStackName }}) as Vpc; - // Stage docker recipes, which include image used for the render queue instance - const recipes = new ThinkboxDockerRecipes(this, 'DockerRecipes', { - stage: Stage.fromDirectory(stagePath), - }); - const host = 'renderqueue'; const zoneName = Stack.of(this).stackName + '.local'; @@ -78,12 +77,12 @@ export class RenderStruct extends Construct { var renderQueueProps = { vpc, repository: props.repository, - images: recipes.renderQueueImages, - version: recipes.version, + images: props.recipes.renderQueueImages, logGroupProps: { logGroupPrefix: Stack.of(this).stackName + '-' + id, }, hostname, + version: props.recipes.version, trafficEncryption, deletionProtection: false, }; diff --git a/integ/lib/storage-struct.ts b/integ/lib/storage-struct.ts index 710c875c0..ca7b79dd0 100644 --- a/integ/lib/storage-struct.ts +++ b/integ/lib/storage-struct.ts @@ -4,13 +4,36 @@ */ import { DatabaseCluster } from '@aws-cdk/aws-docdb'; -import { InstanceClass, InstanceSize, InstanceType, Vpc, SubnetType } from '@aws-cdk/aws-ec2'; +import { + InstanceClass, + InstanceSize, + InstanceType, + Vpc, + SubnetType, +} from '@aws-cdk/aws-ec2'; import { FileSystem } from '@aws-cdk/aws-efs'; import { PrivateHostedZone } from '@aws-cdk/aws-route53'; import { ISecret } from '@aws-cdk/aws-secretsmanager'; -import { Construct, Duration, RemovalPolicy, Stack } from '@aws-cdk/core'; -import { MongoDbInstance, MongoDbPostInstallSetup, MongoDbSsplLicenseAcceptance, MongoDbVersion, MountableEfs, X509CertificatePem, X509CertificatePkcs12 } from 'aws-rfdk'; -import { DatabaseConnection, Repository, Stage, ThinkboxDockerRecipes } from 'aws-rfdk/deadline'; +import { + Construct, + Duration, + RemovalPolicy, + Stack, +} from '@aws-cdk/core'; +import { + MongoDbInstance, + MongoDbPostInstallSetup, + MongoDbSsplLicenseAcceptance, + MongoDbVersion, + MountableEfs, + X509CertificatePem, + X509CertificatePkcs12, +} from 'aws-rfdk'; +import { + DatabaseConnection, + IVersion, + Repository, +} from 'aws-rfdk/deadline'; // Interface for supplying database connection and accompanying secret for credentials @@ -27,6 +50,7 @@ export enum DatabaseType { export interface StorageStructProps { readonly integStackTag: string; + readonly version: IVersion; readonly databaseType?: DatabaseType; } @@ -44,17 +68,10 @@ export class StorageStruct extends Construct { userAcceptsSSPL === 'true' ? MongoDbSsplLicenseAcceptance.USER_ACCEPTS_SSPL : MongoDbSsplLicenseAcceptance.USER_REJECTS_SSPL; const infrastructureStackName = 'RFDKIntegInfrastructure' + props.integStackTag; - const stagePath = process.env.DEADLINE_STAGING_PATH!.toString(); // Get farm VPC from lookup const vpc = Vpc.fromLookup(this, 'Vpc', { tags: { StackName: infrastructureStackName }}) as Vpc; - // Create recipes object used to prepare the Deadline installers - const recipes = new ThinkboxDockerRecipes(this, 'DockerRecipes', { - stage: Stage.fromDirectory(stagePath), - }); - const version = recipes.version; - let cacert; let database; let databaseConnection; @@ -188,7 +205,7 @@ export class StorageStruct extends Construct { vpc, database: databaseConnection, fileSystem: deadlineMountableEfs, - version: version, + version: props.version, repositoryInstallationTimeout: Duration.minutes(20), logGroupProps: { logGroupPrefix: Stack.of(this).stackName + '-' + id, diff --git a/packages/aws-rfdk/lib/core/lambdas/nodejs/lib/custom-resource/simple-resource.ts b/packages/aws-rfdk/lib/core/lambdas/nodejs/lib/custom-resource/simple-resource.ts index d5c8b8463..30d18ebf9 100644 --- a/packages/aws-rfdk/lib/core/lambdas/nodejs/lib/custom-resource/simple-resource.ts +++ b/packages/aws-rfdk/lib/core/lambdas/nodejs/lib/custom-resource/simple-resource.ts @@ -85,7 +85,8 @@ export abstract class SimpleCustomResource { try { if (requestType === 'Create') { if (!this.validateInput(resourceProperties)) { - throw Error('Input did not pass validation check'); + throw Error(`Input did not pass validation check. Check log group "${context.logGroupName}" ` + + `for log stream ${context.logStreamName} for additional information.`); } cfnData = await this.doCreate(physicalId, resourceProperties); console.debug(`Create data: ${JSON.stringify(cfnData)}`); diff --git a/packages/aws-rfdk/lib/core/lambdas/nodejs/lib/version-provider/index.ts b/packages/aws-rfdk/lib/core/lambdas/nodejs/lib/version-provider/index.ts index 180ad2437..330c651ae 100644 --- a/packages/aws-rfdk/lib/core/lambdas/nodejs/lib/version-provider/index.ts +++ b/packages/aws-rfdk/lib/core/lambdas/nodejs/lib/version-provider/index.ts @@ -3,4 +3,5 @@ * SPDX-License-Identifier: Apache-2.0 */ +export * from './version'; export * from './version-provider'; diff --git a/packages/aws-rfdk/bin/index-test.json b/packages/aws-rfdk/lib/core/lambdas/nodejs/lib/version-provider/test/index-test.json similarity index 100% rename from packages/aws-rfdk/bin/index-test.json rename to packages/aws-rfdk/lib/core/lambdas/nodejs/lib/version-provider/test/index-test.json diff --git a/packages/aws-rfdk/lib/core/lambdas/nodejs/lib/version-provider/test/version-provider.test.ts b/packages/aws-rfdk/lib/core/lambdas/nodejs/lib/version-provider/test/version-provider.test.ts index 6ecebe3b6..026463d96 100644 --- a/packages/aws-rfdk/lib/core/lambdas/nodejs/lib/version-provider/test/version-provider.test.ts +++ b/packages/aws-rfdk/lib/core/lambdas/nodejs/lib/version-provider/test/version-provider.test.ts @@ -3,47 +3,22 @@ * SPDX-License-Identifier: Apache-2.0 */ -/* eslint-disable no-console */ /* eslint-disable dot-notation */ -import { Platform, Product, VersionProvider } from '../version-provider'; +import * as path from 'path'; -const versionProvider = new VersionProvider('bin/index-test.json'); +import { Version } from '../version'; +import { + Platform, + Product, + VersionProvider, +} from '../version-provider'; + +const versionProvider = new VersionProvider(path.join(__dirname, 'index-test.json')); const indexTest = versionProvider['readInstallersIndex'](); const productSection = indexTest[Product.deadline]; -test('version parsing', () => { - const result = versionProvider['parseVersionString']('10.1.10.6'); - - expect(result).not.toBeNull(); - - if (result === null) { return; } - expect(result[0]).toEqual('10.1.10.6'); - expect(result[1]).toEqual('10'); - expect(result[2]).toEqual('1'); - expect(result[3]).toEqual('10'); - expect(result[4]).toEqual('6'); -}); - -test('partial version parsing', () => { - const result = versionProvider['parseVersionString']('10.1'); - - expect(result).not.toBeNull(); - - if (result === null) { return; } - expect(result[0]).toEqual('10.1'); - expect(result[1]).toEqual('10'); - expect(result[2]).toEqual('1'); - expect(result[3]).toBeUndefined(); - expect(result[4]).toBeUndefined(); -}); - -test.each(['10.1.9.2.1', '10.', '10.1.', '10.-1', 'a.b.c'])('incorrect version %s parsing', (versionString: string) => { - const result = versionProvider['parseVersionString'](versionString); - expect(result).toBeNull(); -}); - test.each([[Platform.linux, '10.1.9.2'], [Platform.mac, '10.1.9.2'], [Platform.windows, '10.1.8.5'], @@ -67,21 +42,26 @@ test.each([ [Platform.mac, { bundle: 's3://thinkbox-installers/Deadline/10.1.9.2/Mac/Deadline-10.1.9.2-osx-installers.dmg', } ], -])('get Uri for platform', (platform: Platform, versionedUris: any) => { - versionProvider['getUrisForPlatform']( +])('get Uri for platform', async (platform: Platform, versionedUris: any) => { + const result = versionProvider['getUrisForPlatform']( Product.deadline, productSection, platform, '10.1.9.2', - ).then(result => { - expect(result).not.toBeNull(); - - expect(result?.Uris).toEqual(versionedUris); - }, - ).catch(error => { - process.stderr.write(`${error.toString()}\n`); - process.exit(1); - }); + ); + + expect(result).not.toBeNull(); + expect(result?.Uris).toEqual(versionedUris); +}); + +test('get Uri for platform - bad version', async () => { + const badVersion = 'badVersionString'; + expect(() => versionProvider['getUrisForPlatform']( + Product.deadline, + productSection, + Platform.linux, + badVersion, + )).toThrowError(`Couldn't parse version from ${badVersion}`); }); test('get deadline version', async () => { @@ -160,38 +140,6 @@ test('get deadline version for all platforms', async () => { expect(windowsInstallerVersion?.PatchVersion).toEqual('5'); }); -test('validate correct input', async () => { - expect(versionProvider.implementsIVersionProviderProperties({ - product: Product.deadline, - versionString: '10.1.9.2', - platform: 'linux', - })).toBeTruthy(); -}); - -test('validate non-object input', async () => { - expect(versionProvider['implementsIVersionProviderProperties']('test')).toEqual(false); -}); - -test('validate input without product', async () => { - expect(versionProvider.implementsIVersionProviderProperties({ - versionString: 'version', - })).toEqual(false); -}); - -test('validate input with invalid versionString', async () => { - expect(versionProvider.implementsIVersionProviderProperties({ - product: Product.deadline, - versionString: 'version', - })).toEqual(false); -}); - -test('validate input with invalid platform', async () => { - expect(versionProvider['implementsIVersionProviderProperties']({ - product: Product.deadline, - platform: 'test', - })).toEqual(false); -}); - test('not defined file path', () => { expect(() => (new VersionProvider())['readInstallersIndex']()).toThrowError(/File path should be defined./); }); @@ -205,39 +153,63 @@ test('get latest version without latest section', () => { }); test('get latest version without informtion for platform', () => { - expect(() => versionProvider['getLatestVersion']('linux',{latest: {}})).toThrowError(/Information about latest version for platform linux can not be found/); + expect(() => versionProvider['getLatestVersion']('linux',{ latest: {} })).toThrowError(/Information about latest version for platform linux can not be found/); }); test('get requested Uri version for existing product.', () => { - const requestedVersion = versionProvider['parseVersionString']('10.1.9.2'); - expect(versionProvider['getRequestedUriVersion'](requestedVersion, { - 10: { - 1: { - 9: { - 2: { - linux: 's3://thinkbox-installers/DeadlineDocker/10.1.9.2/DeadlineDocker-10.1.9.2.tar.gz', + const requestedVersion = Version.parseFromVersionString('10.1.9.2'); + + expect(requestedVersion).not.toBeNull(); + if (requestedVersion === null) { + return; + } + + expect(versionProvider['getRequestedUriVersion']( + requestedVersion, + { + 10: { + 1: { + 9: { + 2: { + linux: 's3://thinkbox-installers/DeadlineDocker/10.1.9.2/DeadlineDocker-10.1.9.2.tar.gz', + }, }, }, }, - }}, Platform.linux, Product.deadlineDocker )).toEqual({ + }, + Platform.linux, + Product.deadlineDocker, + )).toEqual({ MajorVersion: '10', MinorVersion: '1', ReleaseVersion: '9', PatchVersion: '2', - Uris: {bundle: 's3://thinkbox-installers/DeadlineDocker/10.1.9.2/DeadlineDocker-10.1.9.2.tar.gz'}, + Uris: { bundle: 's3://thinkbox-installers/DeadlineDocker/10.1.9.2/DeadlineDocker-10.1.9.2.tar.gz' }, }); }); test('get requested Uri version for not existing product.', () => { - const requestedVersion = versionProvider['parseVersionString']('10.1.9.2'); - expect(versionProvider['getRequestedUriVersion'](requestedVersion, { - 10: { - 1: { - 9: { - 2: { - linux: 's3://thinkbox-installers/DeadlineDocker/10.1.9.2/DeadlineDocker-10.1.9.2.tar.gz', + const requestedVersion = Version.parseFromVersionString('10.1.9.2'); + + expect(requestedVersion).not.toBeNull(); + if (requestedVersion === null) { + return; + } + + expect(versionProvider['getRequestedUriVersion']( + requestedVersion, + { + 10: { + 1: { + 9: { + 2: { + linux: 's3://thinkbox-installers/DeadlineDocker/10.1.9.2/DeadlineDocker-10.1.9.2.tar.gz', + }, }, }, }, - }}, Platform.windows, Product.deadlineDocker )).toEqual(undefined); + }, + Platform.windows, + Product.deadlineDocker, + )).toEqual(undefined); }); diff --git a/packages/aws-rfdk/lib/core/lambdas/nodejs/lib/version-provider/version-provider.ts b/packages/aws-rfdk/lib/core/lambdas/nodejs/lib/version-provider/version-provider.ts index 7f9b76a8a..1e6717f13 100644 --- a/packages/aws-rfdk/lib/core/lambdas/nodejs/lib/version-provider/version-provider.ts +++ b/packages/aws-rfdk/lib/core/lambdas/nodejs/lib/version-provider/version-provider.ts @@ -3,13 +3,13 @@ * SPDX-License-Identifier: Apache-2.0 */ -/* eslint-disable no-console */ - import * as fs from 'fs'; -import * as http from 'http'; +import { IncomingMessage } from 'http'; import * as https from 'https'; import * as url from 'url'; +import { Version } from './version'; + export enum Platform { linux = 'linux', @@ -70,25 +70,24 @@ export interface IVersionedUris { } /** - * The version provider parse index JSON which can be downloaded or loaded from local file - * and returns URIs for specific product. + * The version provider parses a JSON file containing version information for the Deadline and DockerDeadline products. + * It can be downloaded or loaded from local file and returns URIs for the specific products. * By default returns the last version of URIs or specified full or partial version. * If platform is not defined returns URIs for each platform. */ export class VersionProvider { + private static readonly VERSION_INDEX_URL = 'https://downloads.thinkboxsoftware.com/version_info.json'; + private readonly indexFilePath: string|undefined; - private readonly VALID_VERSION_REGEX = /^(0|[1-9]\d*)(?:\.(0|[1-9]\d*))?(?:\.(0|[1-9]\d*))?(?:\.(0|[1-9]\d*))?$/; + constructor(indexFilePath?: string) { this.indexFilePath = indexFilePath; } /** * Returns URIs for specified product - * - * @param resourceProperties */ public async getVersionUris(resourceProperties: IVersionProviderProperties): Promise> { - /* istanbul ignore next */ const indexJson = this.indexFilePath ? this.readInstallersIndex() : await this.downloadInstallerIndex(); const productSection = indexJson[resourceProperties.product]; @@ -99,7 +98,7 @@ export class VersionProvider { let installers = new Map(); if (resourceProperties.platform) { - const versionedUris = await this.getUrisForPlatform( + const versionedUris = this.getUrisForPlatform( resourceProperties.product, productSection, resourceProperties.platform, @@ -108,9 +107,10 @@ export class VersionProvider { if (versionedUris) { installers.set(resourceProperties.platform, versionedUris); } + } else { Object.values(Platform).forEach(async p => { - const versionedUris = await this.getUrisForPlatform( + const versionedUris = this.getUrisForPlatform( resourceProperties.product, productSection, p, @@ -125,29 +125,8 @@ export class VersionProvider { return installers; } - public implementsIVersionProviderProperties(value: any): boolean { - if (!value || typeof(value) !== 'object') { return false; } - - if (!value.product || !Object.values(Product).includes(value.product)) { - return false; - } - - if (value.versionString) { - if (null === this.parseVersionString(value.versionString)) { return false; } - } - - if (value.platform) { - if (!Object.values(Platform).includes(value.platform.toLowerCase())) { return false; } - } - - return true; - } - - /* istanbul ignore next */ // @ts-ignore private async downloadInstallerIndex() { - const productionInfoURL = 'https://downloads.thinkboxsoftware.com/version_info.json'; - - const parsedUrl = url.parse(productionInfoURL); + const parsedUrl = url.parse(VersionProvider.VERSION_INDEX_URL); const options = { host: parsedUrl.hostname, @@ -155,7 +134,7 @@ export class VersionProvider { }; return new Promise((resolve, reject) => { - https.get(options, (res: http.IncomingMessage) => { + https.get(options, (res: IncomingMessage) => { let json = ''; res.on('data', (chunk: any) => { @@ -200,29 +179,22 @@ export class VersionProvider { return json; } - private parseVersionString(versionString: string): RegExpExecArray | null { - return this.VALID_VERSION_REGEX.exec(versionString); - } - /** - * This method returns IVersionedUris for specific platform - * - * @param product - * @param productSection - * @param platform - * @param version + * This method returns IVersionedUris (the patch version plus installer URI's) for a specific platform. */ - private async getUrisForPlatform( + private getUrisForPlatform( product: Product, productSection: any, platform: Platform, version?: string, - ): Promise { + ): IVersionedUris | undefined { const versionString: string = version ? version : this.getLatestVersion(platform, productSection); + const requestedVersion = Version.parseFromVersionString(versionString); - const requestedVersion = this.parseVersionString( versionString ); + if (!requestedVersion) { + throw new Error(`Couldn't parse version from ${versionString}`); + } - // Based on the requested version, fetches the latest patch and its installer file paths. return this.getRequestedUriVersion( requestedVersion, productSection.versions, @@ -233,9 +205,6 @@ export class VersionProvider { /** * This method returns the latest version for specified platform. - * - * @param platform - * @param indexedVersionInfo */ private getLatestVersion(platform: string, indexedVersionInfo: any): string { const latestSection = indexedVersionInfo.latest; @@ -258,17 +227,13 @@ export class VersionProvider { * with the indexed info. * If any of the requested version number is missing, it fetches the latest * (highest) available version for it. - * - * @param requestedVersion - * @param indexedVersionInfo */ private getRequestedUriVersion( - requestedVersion: RegExpExecArray | null, + requestedVersion: string[], indexedVersionInfo: any, platform: Platform, product: Product, ): IVersionedUris | undefined { - let versionMap = indexedVersionInfo; const versionArray: string[] = []; @@ -276,7 +241,7 @@ export class VersionProvider { // and get the matching version from the indexed version map. for (let versionIndex = 0; versionIndex < 4; versionIndex++) { let version: string; - if (requestedVersion?.[versionIndex + 1] == null) { + if (requestedVersion[versionIndex + 1] == null) { // version is not provided, get the max version. const numberValues: number[] = (Object.keys(versionMap)).map((val: string) => { @@ -293,18 +258,18 @@ export class VersionProvider { let uriIndex: IUris | undefined; if ((platform in versionMap)) { + const platformVersionMap = versionMap[platform]; if (product == Product.deadline) { - const platformVersion = versionMap[platform]; uriIndex = { - bundle: platformVersion.bundle, - clientInstaller: versionMap[platform].clientInstaller, - repositoryInstaller: versionMap[platform].repositoryInstaller, - certificateInstaller: versionMap[platform].certificateInstaller, + bundle: platformVersionMap.bundle, + clientInstaller: platformVersionMap.clientInstaller, + repositoryInstaller: platformVersionMap.repositoryInstaller, + certificateInstaller: platformVersionMap.certificateInstaller, }; } else { // Product.deadlineDocker uriIndex = { - bundle: versionMap[platform], + bundle: platformVersionMap, }; } } diff --git a/packages/aws-rfdk/lib/core/lambdas/nodejs/lib/version-provider/version.ts b/packages/aws-rfdk/lib/core/lambdas/nodejs/lib/version-provider/version.ts new file mode 100644 index 000000000..833740c0d --- /dev/null +++ b/packages/aws-rfdk/lib/core/lambdas/nodejs/lib/version-provider/version.ts @@ -0,0 +1,35 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Utility class for validating and converting the version number to or from a version string + */ +export class Version { + public static validateVersionString(versionString: string): boolean { + if (null === this.parseFromVersionString(versionString)) { return false; } + return true; + } + + public static parseFromVersionString(versionString: string): RegExpExecArray | null { + return Version.VALID_VERSION_REGEX.exec(versionString); + } + + public static convertToFullVersionString(major: string, minor: string, release: string, patch: string): string { + const majorNumber = Number(major); + const minorNumber = Number(minor); + const releaseNumber = Number(release); + const patchNumber = Number(patch); + + if (isNaN(majorNumber) || majorNumber < 0 + || isNaN(minorNumber) || minorNumber < 0 + || isNaN(releaseNumber) || releaseNumber < 0 + || isNaN(patchNumber) || patchNumber < 0) { + throw new Error(`A component of the version was not in the correct format: ${major}.${minor}.${release}.${patch}`); + } + return `${major}.${minor}.${release}.${patch}`; + } + + private static readonly VALID_VERSION_REGEX = /^(0|[1-9]\d*)(?:\.(0|[1-9]\d*))?(?:\.(0|[1-9]\d*))?(?:\.(0|[1-9]\d*))?$/; +} diff --git a/packages/aws-rfdk/lib/core/lambdas/nodejs/version-provider/handler.ts b/packages/aws-rfdk/lib/core/lambdas/nodejs/version-provider/handler.ts index dccb8482f..b9e0b9383 100644 --- a/packages/aws-rfdk/lib/core/lambdas/nodejs/version-provider/handler.ts +++ b/packages/aws-rfdk/lib/core/lambdas/nodejs/version-provider/handler.ts @@ -8,51 +8,175 @@ import { LambdaContext } from '../lib/aws-lambda'; import { CfnRequestEvent, SimpleCustomResource } from '../lib/custom-resource'; import { - VersionProvider, - IVersionProviderProperties, - IVersionedUris, Platform, + Product, + Version, + VersionProvider, } from '../lib/version-provider'; +/** + * The input to this Custom Resource + */ +export interface IVersionProviderResourceProperties { + /** + * The version of Deadline to look up. + */ + readonly versionString?: string; + + /** + * A random string that can be used to force the Lambda to run again which is intended to allow for Deadline updates + * to be discovered. + */ + readonly forceRun?: string; +} + +/** + * Output of this Custom Resource, it is all made up of a flat structure of strings to avoid issues with how the + * results are tokenized by CDK to be used in constructs. + */ +export interface FlatVersionedUriOutput { + /** + * The S3 bucket holding the installers. This makes the assumption they are all in the same bucket. + */ + readonly S3Bucket: string; + + /** + * The major version of the Linux installer. For example, "a" in "a.b.c.d". + */ + readonly MajorVersion: string; + + /** + * The minor version of the Linux installer. For example, "b" in "a.b.c.d". + */ + readonly MinorVersion: string; + + /** + * The release version of the Linux installer. For example, "c" in "a.b.c.d". + */ + readonly ReleaseVersion: string; + + /** + * The patch version of the Linux installer. For example, "d" in "a.b.c.d". + */ + readonly LinuxPatchVersion: string; + + /** + * The object key of the Deadline repository installer for Linux. + */ + readonly LinuxRepositoryInstaller: string; +} +/** + * This custom resource will parse and return the S3 URI's of the Deadline installers and Docker recipes for use in + * any constructs that need to install Deadline. + */ export class VersionProviderResource extends SimpleCustomResource { readonly versionProvider: VersionProvider; - constructor(indexFilePath?: string) { + constructor() { super(); - this.versionProvider = new VersionProvider(indexFilePath); + this.versionProvider = new VersionProvider(); } /** * @inheritdoc */ - /* istanbul ignore next */ // @ts-ignore public validateInput(data: object): boolean { - return this.versionProvider.implementsIVersionProviderProperties(data); + return this.implementsIVersionProviderResourceProperties(data); } /** * @inheritdoc */ // @ts-ignore -- we do not use the physicalId - public async doCreate(physicalId: string, resourceProperties: IVersionProviderProperties): Promise> { - return await this.versionProvider.getVersionUris(resourceProperties); + public async doCreate(physicalId: string, resourceProperties: IVersionProviderResourceProperties): Promise { + const deadlinePlatFormVersionedUris = await this.versionProvider.getVersionUris({ + versionString: resourceProperties.versionString, + platform: Platform.linux, + product: Product.deadline, + }); + + const deadlineLinux = deadlinePlatFormVersionedUris.get(Platform.linux)!; + const deadlineLinuxUris = deadlineLinux.Uris; + + const s3Bucket = this.parseS3BucketName(deadlineLinuxUris.bundle); + const linuxRepoObjectKey = this.parseS3ObjectKey(deadlineLinuxUris.repositoryInstaller!); + + return { + S3Bucket: s3Bucket, + MajorVersion: deadlineLinux.MajorVersion, + MinorVersion: deadlineLinux.MinorVersion, + ReleaseVersion: deadlineLinux.ReleaseVersion, + LinuxPatchVersion: deadlineLinux.PatchVersion, + LinuxRepositoryInstaller: linuxRepoObjectKey, + }; } /** * @inheritdoc */ /* istanbul ignore next */ // @ts-ignore - public async doDelete(physicalId: string, resourceProperties: IVersionProviderProperties): Promise { + public async doDelete(physicalId: string, resourceProperties: IVersionProviderResourceProperties): Promise { // Nothing to do -- we don't modify anything. return; } + + private implementsIVersionProviderResourceProperties(value: any): boolean { + if (!value || typeof(value) !== 'object') { return false; } + + if (value.versionString) { + if (!Version.validateVersionString(value.versionString)) { + console.log(`Failed to validate the version string: ${value.versionString}`); + return false; + } + } + + if (value.forceRun && typeof(value.forceRun) !== 'string') { return false; } + + return true; + } + + /** + * Parses the S3 bucket name from an S3 URI. + */ + private parseS3BucketName(uri: string): string { + let bucketName; + try { + bucketName = this.findRegex(uri, /^s3:\/\/([A-Za-z0-9\-]+)\//)[1]; + } catch (e) { + throw new Error(`Could not parse S3 bucket name from ${uri}`); + } + return bucketName; + } + + /** + * Parses the S3 object key from an S3 URI. + */ + private parseS3ObjectKey(uri: string): string { + let objectKey; + try { + objectKey = this.findRegex(uri, /^s3:\/\/[A-Za-z0-9\-]+\/([A-Za-z0-9\-\/\.]+)$/)[1]; + } catch (e) { + throw new Error(`Could not parse S3 object key from ${uri}`); + } + return objectKey; + } + + // Assumes a single capture is in the regex + private findRegex(str: string, re: RegExp): RegExpMatchArray { + const found = str.match(re); + + if (found === null) { + throw new Error(`Couldn't find regular expression ${re} in ${str}`); + } + + return found; + } } /** * The handler used to provide the installer links for the requested version */ -/* istanbul ignore next */ export async function handler(event: CfnRequestEvent, context: LambdaContext): Promise { const versionProvider = new VersionProviderResource(); return await versionProvider.handler(event, context); diff --git a/packages/aws-rfdk/lib/core/lambdas/nodejs/version-provider/test/handler.test.ts b/packages/aws-rfdk/lib/core/lambdas/nodejs/version-provider/test/handler.test.ts new file mode 100644 index 000000000..7eefd3db4 --- /dev/null +++ b/packages/aws-rfdk/lib/core/lambdas/nodejs/version-provider/test/handler.test.ts @@ -0,0 +1,86 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +/* eslint-disable dot-notation */ + +import { VersionProviderResource } from '../handler'; + +describe('VersionProviderResource', () => { + let versionProviderResource: VersionProviderResource; + beforeEach(() => { + versionProviderResource = new VersionProviderResource(); + }); + + describe('implementsIVersionProviderResourceProperties', () => { + test('correct input', async () => { + expect(versionProviderResource['implementsIVersionProviderResourceProperties']({ + versionString: '10.1.9.2', + forceRun: 'testValue', + })).toBeTruthy(); + }); + + test('correct input no forceRun', async () => { + expect(versionProviderResource['implementsIVersionProviderResourceProperties']({ + versionString: '10.1.9.2', + })).toBeTruthy(); + }); + + test('correct input with no versionString', async () => { + expect(versionProviderResource['implementsIVersionProviderResourceProperties']({})).toBeTruthy(); + }); + + test('non-object input', async () => { + expect(versionProviderResource['implementsIVersionProviderResourceProperties']('test')).toBeFalsy(); + }); + + test('input with invalid versionString', async () => { + expect(versionProviderResource['implementsIVersionProviderResourceProperties']({ + versionString: 'version', + })).toBeFalsy(); + }); + + test('input with invalid forceRun', async () => { + expect(versionProviderResource['implementsIVersionProviderResourceProperties']({ + versionString: '10.1.9.2', + forceRun: {}, + })).toBeFalsy(); + }); + }); + + describe('parseS3BucketName', () => { + test('correct input', () => { + expect(versionProviderResource['parseS3BucketName']('s3://bucketName/objectKey')).toEqual('bucketName'); + }); + + test.each([ + ':/bucketName/objectKey', + 's3:/bucketName/objectKey', + 's3://bucketName', + 'bucketName', + ])('malformed input: %p', (s3Uri: string) => { + expect(() => versionProviderResource['parseS3BucketName'](s3Uri)).toThrowError(/Could not parse S3 bucket name/); + }); + }); + + describe('parseS3ObjectKey', () => { + test.each([ + ['s3://bucketName/objectKey', 'objectKey'], + ['s3://bucketName/objectDirectory/objectName', 'objectDirectory/objectName'], + ['s3://bucketName/objectDirectory/objectName.run', 'objectDirectory/objectName.run'], + ['s3://bucketName/objectDirectory/10.1.9.2/objectName.run', 'objectDirectory/10.1.9.2/objectName.run'], + ])('correct input: %p', (s3Uri: string, objectKey: string) => { + expect(versionProviderResource['parseS3ObjectKey'](s3Uri)).toEqual(objectKey); + }); + + test.each([ + ':/bucketName/objectKey', + 's3:/bucketName/objectKey', + 's3://bucketName', + 'bucketName', + ])('malformed input: %p', (s3Uri: string) => { + expect(() => versionProviderResource['parseS3ObjectKey'](s3Uri)).toThrowError(/Could not parse S3 object key/); + }); + }); +}); diff --git a/packages/aws-rfdk/lib/core/lambdas/nodejs/version-provider/test/version.test.ts b/packages/aws-rfdk/lib/core/lambdas/nodejs/version-provider/test/version.test.ts new file mode 100644 index 000000000..ea8cfc185 --- /dev/null +++ b/packages/aws-rfdk/lib/core/lambdas/nodejs/version-provider/test/version.test.ts @@ -0,0 +1,91 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Version } from '../../lib/version-provider/version'; + +describe('validateVersionString', () => { + test('correct input', () => { + expect(Version.validateVersionString('10.1.9.2')).toBeTruthy(); + }); + test('malformed input', () => { + expect(Version.validateVersionString('10.1.9.2.1')).toBeFalsy(); + }); +}); + +describe('parseVersionString', () => { + test.each([ + [ + '10.1.10.6', + [ '10', '1', '10', '6' ], + ], + [ + '10.1.9.2', + [ '10', '1', '9', '2' ], + ], + [ + '10.1.9', + [ '10', '1', '9' ], + ], + [ + '10.1', + [ '10', '1' ], + ], + [ + '10', + [ '10' ], + ], + ])('correct input: %p', (value: string, resultArray: string[]) => { + const regexResult = Version.parseFromVersionString(value); + + expect(regexResult).not.toBeNull(); + if (regexResult === null) { return; } + + expect(regexResult[0]).toEqual(value); + + for (let x = 0; x < resultArray.length; x++) { + expect(regexResult[x+1]).toEqual(resultArray[x]); + } + }); + + test.each([ + '10.1.9.2.1', + '10.', + '10.1.', + '10.-1', + 'a.b.c', + ])('incorrect version %s parsing', (versionString: string) => { + const result = Version.parseFromVersionString(versionString); + expect(result).toBeNull(); + }); +}); + +describe('convertToFullVersionString', () => { + test('correct input', () => { + expect(Version.convertToFullVersionString( + '10', + '1', + '9', + '2', + )).toEqual('10.1.9.2'); + }); + + test('negative value', () => { + expect(() => Version.convertToFullVersionString( + '10', + '-1', + '9', + '2', + )).toThrowError(/A component of the version was not in the correct format/); + }); + + test('non-numeric value', () => { + expect(() => Version.convertToFullVersionString( + '10', + 'test', + '9', + '2', + )).toThrowError(/A component of the version was not in the correct format/); + }); +}); diff --git a/packages/aws-rfdk/lib/deadline/lib/index.ts b/packages/aws-rfdk/lib/deadline/lib/index.ts index d4e42225f..7be2ac796 100644 --- a/packages/aws-rfdk/lib/deadline/lib/index.ts +++ b/packages/aws-rfdk/lib/deadline/lib/index.ts @@ -13,4 +13,5 @@ export * from './render-queue-ref'; export * from './stage'; export * from './thinkbox-docker-recipes'; export * from './version'; +export * from './version-query'; export * from './version-ref'; diff --git a/packages/aws-rfdk/lib/deadline/lib/repository.ts b/packages/aws-rfdk/lib/deadline/lib/repository.ts index 7f646cc93..354ae1b15 100644 --- a/packages/aws-rfdk/lib/deadline/lib/repository.ts +++ b/packages/aws-rfdk/lib/deadline/lib/repository.ts @@ -467,8 +467,9 @@ export class Repository extends Construct implements IRepository { if (props.database) { this.databaseConnection = props.database; if (props.databaseAuditLogging !== undefined){ - this.node.addWarning(`The parameter databaseAuditLogging only has an effect when the Repository is creating its own database. - Please ensure that the Database provided is configured correctly.`); + const warningMsg = 'The parameter databaseAuditLogging only has an effect when the Repository is creating its own database.\n' + + 'Please ensure that the Database provided is configured correctly.'; + this.node.addWarning(warningMsg); } } else { const databaseAuditLogging = props.databaseAuditLogging ?? true; @@ -791,13 +792,6 @@ export class Repository extends Construct implements IRepository { this.databaseConnection.addInstallerDBArgs(installerGroup); - if (!version.linuxInstallers?.repository) { - throw new Error('Version given to Repository must provide a Linux Repository installer.'); - } - const linuxVersionString = version.linuxFullVersionString(); - if (!linuxVersionString) { - throw new Error('Version given to Repository must provide a full Linux version string.'); - } version.linuxInstallers.repository.s3Bucket.grantRead(installerGroup, version.linuxInstallers.repository.objectKey); installerScriptAsset.executeOn({ @@ -805,7 +799,7 @@ export class Repository extends Construct implements IRepository { args: [ `"s3://${version.linuxInstallers.repository.s3Bucket.bucketName}/${version.linuxInstallers.repository.objectKey}"`, `"${installPath}"`, - linuxVersionString, + version.linuxFullVersionString(), ], }); } diff --git a/packages/aws-rfdk/lib/deadline/lib/stage.ts b/packages/aws-rfdk/lib/deadline/lib/stage.ts index cc8e6529a..3cd327c67 100644 --- a/packages/aws-rfdk/lib/deadline/lib/stage.ts +++ b/packages/aws-rfdk/lib/deadline/lib/stage.ts @@ -10,9 +10,10 @@ import { DockerImageAsset } from '@aws-cdk/aws-ecr-assets'; import { Construct } from '@aws-cdk/core'; import { + IVersion, Version, VersionQuery, -} from './version'; +} from './'; /** * Build arguments to supply to a Docker image build @@ -197,8 +198,9 @@ export class Stage { * @param scope The parent scope * @param id The construct ID */ - public getVersion(scope: Construct, id: string) { - return VersionQuery.exactString(scope, id, this.manifest.version); + public getVersion(scope: Construct, id: string): IVersion { + const releaseVersion = this.getReleaseVersion(this.manifest.version); + return new VersionQuery(scope, id, { version: releaseVersion }); } /** @@ -217,4 +219,13 @@ export class Stage { ...recipe, }); } + + /** + * This removes the patch version from a full version string. No validation is done as that is handled + * in the constructor with the version check. + */ + private getReleaseVersion(fullVersion: string): string { + const versionComponents = fullVersion.split('.'); + return `${versionComponents[0]}.${versionComponents[1]}.${versionComponents[2]}`; + } } diff --git a/packages/aws-rfdk/lib/deadline/lib/version-query.ts b/packages/aws-rfdk/lib/deadline/lib/version-query.ts new file mode 100644 index 000000000..ae624b6e3 --- /dev/null +++ b/packages/aws-rfdk/lib/deadline/lib/version-query.ts @@ -0,0 +1,186 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { randomBytes } from 'crypto'; +import { join } from 'path'; + +import { + Code, + SingletonFunction, + Runtime, +} from '@aws-cdk/aws-lambda'; +import { RetentionDays } from '@aws-cdk/aws-logs'; +import { + Bucket, +} from '@aws-cdk/aws-s3'; +import { + Construct, + CustomResource, + Duration, + Token, +} from '@aws-cdk/core'; + +import { + IVersionProviderResourceProperties, +} from '../../core/lambdas/nodejs/version-provider'; + +import { + IVersion, + PlatformInstallers, +} from './version-ref'; + +/** + * Properties for the Deadline Version + */ +export interface VersionQueryProps { + /** + * String containing the complete or partial deadline version. + * + * @default - the latest available version of deadline installer. + */ + readonly version?: string; +} + +/** + * The abstract class for new or imported(custom) Deadline Version. + */ +abstract class VersionQueryBase extends Construct implements IVersion { + /** + * @inheritdoc + */ + readonly abstract majorVersion: number; + + /** + * @inheritdoc + */ + readonly abstract minorVersion: number; + + /** + * @inheritdoc + */ + readonly abstract releaseVersion: number; + + /** + * @inheritdoc + */ + readonly abstract linuxInstallers: PlatformInstallers; + + /** + * @inheritdoc + */ + abstract linuxFullVersionString(): string; +} + +/** + * This class encapsulates information about a particular version of Thinkbox's Deadline software. + * Information such as the version number, and where to get installers for that version from Amazon S3. + * + * The version of an official release of Deadline is always four numeric version components separated by dots. + * ex: 10.1.8.5. We refer to the components in this version, in order from left-to-right, as the + * major, minor, release, and patch versions. For example, Deadline version 10.1.8.5 is majorVersion 10, minorVersion 1, + * releaseVersion 8, and patchVersion 5. + * + * All of the installers provided by an instance of this class must be for the same Deadline release (ex: 10.1.8), + * but the patch versions may differ between operating systems depending on the particulars of that release of Deadline. + * This class provides a simple way to query a version of Deadline prior to or during deployment of a + * CDK app. + * + * You pass an instance of this class to various Deadline constructs in this library to tell those + * constructs which version of Deadline you want them to use, and be configured for. + */ +export class VersionQuery extends VersionQueryBase { + /** + * @inheritdoc + */ + readonly majorVersion: number; + + /** + * @inheritdoc + */ + readonly minorVersion: number; + + /** + * @inheritdoc + */ + readonly releaseVersion: number; + + /** + * @inheritdoc + */ + readonly linuxInstallers: PlatformInstallers; + + constructor(scope: Construct, id: string, props?: VersionQueryProps) { + super(scope, id); + + const lambdaCode = Code.fromAsset(join(__dirname, '../..', 'core', 'lambdas', 'nodejs')); + + const lambdaFunc = new SingletonFunction(this, 'VersionProviderFunction', { + uuid: '2e19e243-16ee-4d1a-a3c9-18d35eddd446', + description: 'Used by the Version construct to get installer locations for a specific Deadline version.', + code: lambdaCode, + runtime: Runtime.NODEJS_12_X, + handler: 'version-provider.handler', + timeout: Duration.seconds(30), + logRetention: RetentionDays.ONE_WEEK, + }); + + const deadlineProperties: IVersionProviderResourceProperties = { + versionString: props?.version, + // If we don't have a full static version string, create a random string that will force the Lambda to always + // run on redeploys, effectively checking for version updates. + forceRun: this.forceRun(props?.version), + }; + + const deadlineResource = new CustomResource(this, 'DeadlineResource', { + serviceToken: lambdaFunc.functionArn, + properties: deadlineProperties, + resourceType: 'Custom::RFDK_DEADLINE_INSTALLERS', + }); + + this.majorVersion = Token.asNumber(deadlineResource.getAtt('MajorVersion')); + this.minorVersion = Token.asNumber(deadlineResource.getAtt('MinorVersion')); + this.releaseVersion = Token.asNumber(deadlineResource.getAtt('ReleaseVersion')); + + this.linuxInstallers = { + patchVersion: Token.asNumber(deadlineResource.getAtt('LinuxPatchVersion')), + repository: { + objectKey: Token.asString(deadlineResource.getAtt('LinuxRepositoryInstaller')), + s3Bucket: Bucket.fromBucketName(scope, 'InstallerBucket', Token.asString(deadlineResource.getAtt('S3Bucket'))), + }, + }; + } + + public linuxFullVersionString(): string { + const major = Token.isUnresolved(this.majorVersion) ? Token.asString(this.majorVersion) : this.majorVersion.toString(); + const minor = Token.isUnresolved(this.minorVersion) ? Token.asString(this.minorVersion) : this.minorVersion.toString(); + const release = Token.isUnresolved(this.releaseVersion) ? Token.asString(this.releaseVersion) : this.releaseVersion.toString(); + const patch = Token.isUnresolved(this.linuxInstallers.patchVersion) + ? Token.asString(this.linuxInstallers.patchVersion) + : this.linuxInstallers.patchVersion.toString(); + + return `${major}.${minor}.${release}.${patch}`; + } + + /** + * Check if we have a full version in the supplied version string. If we don't, we want to make sure the Lambda + * that fetches the full version number and the installers for it is always run. This allows for Deadline updates + * to be discovered. + */ + private forceRun(version?: string): string | undefined { + return !this.isFullVersion(version) ? randomBytes(32).toString('base64').slice(0, 32) : undefined; + } + + /** + * Checks if the supplied version contains the major, minor, release, and patch version numbers, + * and returns true only if all 4 are supplied. + */ + private isFullVersion(version?: string): boolean { + const components = version?.split('.').map(x => parseInt(x)); + if (!components || components?.length != 4) { + return false; + } + return true; + } +} diff --git a/packages/aws-rfdk/lib/deadline/lib/version-ref.ts b/packages/aws-rfdk/lib/deadline/lib/version-ref.ts index 3cedbfabe..730df8ba1 100644 --- a/packages/aws-rfdk/lib/deadline/lib/version-ref.ts +++ b/packages/aws-rfdk/lib/deadline/lib/version-ref.ts @@ -37,10 +37,8 @@ export interface PlatformInstallers { * * - DeadlineRepository-10.1.8.5-linux-x64-installer.run * - DeadlineRepository-10.1.8.5-windows-installer.exe - * - * @default No repository installer is provided. */ - readonly repository?: Installer; + readonly repository: Installer; } /** @@ -92,14 +90,12 @@ export interface IVersion extends IReleaseVersion { * * @default No installers for Linux are provided. */ - readonly linuxInstallers?: PlatformInstallers; + readonly linuxInstallers: PlatformInstallers; /** * Construct the full version string for the linux patch release referenced in * this version object. This is constructed by joining the major, minor, * release, and patch versions by dots. - * - * Will return undefined if the linuxInstallers property is undefined. */ - linuxFullVersionString(): string | undefined; + linuxFullVersionString(): string; } diff --git a/packages/aws-rfdk/lib/deadline/lib/version.ts b/packages/aws-rfdk/lib/deadline/lib/version.ts index cf97ad064..dfab0d18e 100644 --- a/packages/aws-rfdk/lib/deadline/lib/version.ts +++ b/packages/aws-rfdk/lib/deadline/lib/version.ts @@ -3,75 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { Bucket } from '@aws-cdk/aws-s3'; -import { Construct } from '@aws-cdk/core'; - import { IPatchVersion, - IVersion, - PlatformInstallers, } from './version-ref'; -/** - * Properties for the Deadline Version - */ -export interface VersionQueryProps { - /** - * String containing the complete or partial deadline version. - * - * @default - the latest available version of deadline installer. - */ - readonly version?: string; -} - -/** - * The abstract class for new or imported(custom) Deadline Version. - */ -abstract class VersionQueryBase extends Construct implements IVersion { - protected static readonly INSTALLER_BUCKET = 'thinkbox-installers'; - - /** - * @inheritdoc - */ - public abstract readonly majorVersion: number; - - /** - * @inheritdoc - */ - public abstract readonly minorVersion: number; - - /** - * @inheritdoc - */ - public abstract readonly releaseVersion: number; - - /** - * @inheritdoc - */ - public abstract readonly linuxInstallers?: PlatformInstallers; - - /** - * @inheritdoc - */ - public linuxFullVersionString(): string | undefined { - if (!this.linuxInstallers) { - return undefined; - } - return this.fullVersionString(this.linuxInstallers.patchVersion); - } - - protected fullVersionString(patchVersion: number): string { - return `${this.releaseVersionString}.${patchVersion}`; - } - - /** - * Helper to concatenate the major, minor, and release version values into a release version string. - */ - protected get releaseVersionString(): string { - return `${this.majorVersion}.${this.minorVersion}.${this.releaseVersion}`; - } -} - /** * This class is reposonsible to do basic operations on version format. */ @@ -152,7 +87,6 @@ export class Version implements IPatchVersion { } constructor(components: number[]) { - // validations if(components.length != 4) { throw new Error('Invalid version format. Version should contain exactly 4 components.'); } @@ -160,6 +94,9 @@ export class Version implements IPatchVersion { if (component < 0) { throw new RangeError('Invalid version format. None of the version components can be negative.'); } + if (!Number.isInteger(component)) { + throw new RangeError('Invalid version format. None of the version components can contain decimal values.'); + } }); this.components = components; @@ -231,125 +168,3 @@ export class Version implements IPatchVersion { return 0; } } - -/** - * This class encapsulates information about a particular version of Thinkbox's Deadline software. - * Information such as the version number, and where to get installers for that version from Amazon S3. - * - * The version of an official release of Deadline is always four numeric version components separated by dots. - * ex: 10.1.8.5. We refer to the components in this version, in order from left-to-right, as the - * major, minor, release, and patch versions. For example, Deadline version 10.1.8.5 is majorVersion 10, minorVersion 1, - * releaseVersion 8, and patchVersion 5. - * - * All of the installers provided by an instance of this class must be for the same Deadline release (ex: 10.1.8), - * but the patch versions may differ between operating systems depending on the particulars of that release of Deadline. - * This class provides a simple way to query a version of Deadline prior to or during deployment of a - * CDK app. - * - * You pass an instance of this class to various Deadline constructs in this library to tell those - * constructs which version of Deadline you want them to use, and be configured for. - */ -export class VersionQuery extends VersionQueryBase { - /** - * Specify a Deadline version from a fully-qualified Deadline patch version. - * - * This only provides the Linux repository installer based on its conventional - * S3 object path. - * - * @remark Thinkbox reserves the right to revoke patch versions of Deadline and suceeed them with a new patch version. - * For this reason, using this method may fail if Thinkbox revokes the specific patch version of the Deadline - * installer in the event of a critical issue such as a security vulnerability. - * - * Use at your own risk. - * - * @param scope The parent scope - * @param id The construct ID - * @param versionComponents The individual components of the Deadline release version - */ - public static exact(scope: Construct, id: string, versionComponents: IPatchVersion): IVersion { - class ExactVersion extends VersionQueryBase { - /** - * @inheritdoc - */ - public readonly majorVersion: number; - - /** - * @inheritdoc - */ - public readonly minorVersion: number; - - /** - * @inheritdoc - */ - public readonly releaseVersion: number; - - /** - * @inheritdoc - */ - public readonly linuxInstallers?: PlatformInstallers; - - constructor() { - super(scope, id); - const installerBucket = Bucket.fromBucketName(this, 'ThinkboxInstallers', ExactVersion.INSTALLER_BUCKET); - - const { majorVersion, minorVersion, releaseVersion, patchVersion } = versionComponents; - - this.majorVersion = majorVersion; - this.minorVersion = minorVersion; - this.releaseVersion = releaseVersion; - - const fullVersionString = this.fullVersionString(patchVersion); - const objectKey = `Deadline/${fullVersionString}/Linux/DeadlineRepository-${fullVersionString}-linux-x64-installer.run`; - - this.linuxInstallers = { - patchVersion, - repository: { - s3Bucket: installerBucket, - objectKey, - }, - }; - } - } - - return new ExactVersion(); - } - - /** - * Specify Version from a fully-qualified Deadline release version string. - * - * This only provides the Linux repository installer based on its conventional - * S3 object path. - * - * @param scope The parent scope - * @param id The construct ID - * @param versionString A fully qualified version string (e.g. 10.1.9.2) - */ - public static exactString(scope: Construct, id: string, versionString: string) { - return VersionQuery.exact(scope, id, Version.parse(versionString)); - } - - /** - * @inheritdoc - */ - public readonly majorVersion: number; - - /** - * @inheritdoc - */ - public readonly minorVersion: number; - - /** - * @inheritdoc - */ - public readonly releaseVersion: number; - - /** - * @inheritdoc - */ - public readonly linuxInstallers?: PlatformInstallers; - - constructor(scope: Construct, id: string, props?: VersionQueryProps) { - super(scope, id); - throw new Error(`MethodNotSupportedException: This method is currently not implemented. Input: ${JSON.stringify(props)}`); - } -} diff --git a/packages/aws-rfdk/lib/deadline/test/asset-constants.ts b/packages/aws-rfdk/lib/deadline/test/asset-constants.ts index 17e865690..d75751b6b 100644 --- a/packages/aws-rfdk/lib/deadline/test/asset-constants.ts +++ b/packages/aws-rfdk/lib/deadline/test/asset-constants.ts @@ -41,3 +41,8 @@ export const RQ_CONNECTION_ASSET = { Bucket: 'AssetParameters89a29e05a2a88ec4d4a02e847e3c3c9461d0154b326492f4cad655d4ca0bda98S3BucketC22E185C', Key: 'AssetParameters89a29e05a2a88ec4d4a02e847e3c3c9461d0154b326492f4cad655d4ca0bda98S3VersionKey0833D670', }; + +export const VERSION_QUERY_ASSET = { + Bucket: stringLike('AssetParameters*S3Bucket8394B4B1'), + Key: stringLike('AssetParameters*S3VersionKey246878CE'), +}; diff --git a/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts b/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts index cf249172d..4e2d25be4 100644 --- a/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts +++ b/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts @@ -89,12 +89,7 @@ describe('RenderQueue', () => { app = new App(); dependencyStack = new Stack(app, 'DepStack'); vpc = new Vpc(dependencyStack, 'Vpc'); - version = VersionQuery.exact(dependencyStack, 'Version', { - majorVersion: 10, - minorVersion: 1, - releaseVersion: 9, - patchVersion: 1, - }); + version = new VersionQuery(dependencyStack, 'Version'); repository = new Repository(dependencyStack, 'Repo', { version, vpc, @@ -1910,12 +1905,7 @@ describe('RenderQueue', () => { }, }); isolatedVpc = new Vpc(isolatedStack, 'Vpc'); - isolatedVersion = VersionQuery.exact(isolatedStack, 'Version', { - majorVersion: 10, - minorVersion: 1, - releaseVersion: 9, - patchVersion: 1, - }); + isolatedVersion = new VersionQuery(isolatedStack, 'Version'); isolatedRepository = new Repository(isolatedStack, 'Repo', { version: isolatedVersion, diff --git a/packages/aws-rfdk/lib/deadline/test/repository.test.ts b/packages/aws-rfdk/lib/deadline/test/repository.test.ts index b6c151d5a..ef342768b 100644 --- a/packages/aws-rfdk/lib/deadline/test/repository.test.ts +++ b/packages/aws-rfdk/lib/deadline/test/repository.test.ts @@ -10,6 +10,7 @@ import { haveResource, haveResourceLike, ResourcePart, + stringLike, } from '@aws-cdk/assert'; import {AutoScalingGroup} from '@aws-cdk/aws-autoscaling'; import {DatabaseCluster} from '@aws-cdk/aws-docdb'; @@ -29,9 +30,7 @@ import { import { FileSystem as EfsFileSystem, } from '@aws-cdk/aws-efs'; -import { - Bucket, -} from '@aws-cdk/aws-s3'; +import { Bucket } from '@aws-cdk/aws-s3'; import { App, CfnElement, @@ -50,7 +49,6 @@ import { DatabaseConnection, IVersion, Repository, - VersionQuery, } from '../lib'; import { REPO_DC_ASSET, @@ -59,7 +57,7 @@ import { let app: App; let stack: Stack; let vpc: IVpc; -let deadlineVersion: IVersion; +let version: IVersion; function escapeTokenRegex(s: string): string { // A CDK Token looks like: ${Token[TOKEN.12]} @@ -72,26 +70,33 @@ beforeEach(() => { app = new App(); stack = new Stack(app, 'Stack'); vpc = new Vpc(stack, 'VPC'); - deadlineVersion = VersionQuery.exact(stack, 'Version', { + version = { majorVersion: 10, minorVersion: 1, releaseVersion: 9, - patchVersion: 2, - }); + linuxInstallers: { + patchVersion: 2, + repository: { + objectKey: 'testInstaller', + s3Bucket: new Bucket(stack, 'InstallerBucket'), + }, + }, + linuxFullVersionString: () => '10.1.9.2', + }; }); test('can create two repositories', () => { // GIVEN new Repository(stack, 'Repo1', { vpc, - version: deadlineVersion, + version, }); // THEN expect(() => { new Repository(stack, 'Repo2', { vpc, - version: deadlineVersion, + version, }); }).not.toThrow(); }); @@ -100,7 +105,7 @@ test('repository installer instance is created correctly', () => { // WHEN new Repository(stack, 'repositoryInstaller', { vpc, - version: deadlineVersion, + version, }); // THEN @@ -173,7 +178,7 @@ test('repository installer honors vpcSubnet', () => { }); new Repository(stack, 'repositoryInstaller', { vpc: attrVpc, - version: deadlineVersion, + version, vpcSubnets: { subnetType: SubnetType.PUBLIC }, }); @@ -187,7 +192,7 @@ test('repository installer security groups created correctly', () => { // WHEN new Repository(stack, 'repositoryInstaller', { vpc, - version: deadlineVersion, + version, }); // THEN @@ -270,7 +275,7 @@ test('repository installer iam permissions: db secret access', () => { // WHEN new Repository(stack, 'repositoryInstaller', { vpc, - version: deadlineVersion, + version, }); // THEN @@ -305,7 +310,7 @@ test('repository installer iam permissions: installer get', () => { // WHEN new Repository(stack, 'repositoryInstaller', { vpc, - version: deadlineVersion, + version, }); // THEN @@ -328,7 +333,10 @@ test('repository installer iam permissions: installer get', () => { { Ref: 'AWS::Partition', }, - ':s3:::thinkbox-installers', + ':s3:::', + { + Ref: stringLike('AssetParameters*S3Bucket352E624B'), + }, ], ], }, @@ -340,7 +348,11 @@ test('repository installer iam permissions: installer get', () => { { Ref: 'AWS::Partition', }, - ':s3:::thinkbox-installers/Deadline/10.1.9.2/Linux/DeadlineRepository-10.1.9.2-linux-x64-installer.run', + ':s3:::', + { + Ref: stringLike('AssetParameters*S3Bucket352E624B'), + }, + '/*', ], ], }, @@ -355,7 +367,7 @@ test('default repository installer log group created correctly', () => { // WHEN new Repository(stack, 'repositoryInstaller', { vpc, - version: deadlineVersion, + version, }); // THEN @@ -369,7 +381,7 @@ test('repository installer logs all required files', () => { // WHEN new Repository(stack, 'repositoryInstaller', { vpc, - version: deadlineVersion, + version, }); // THEN @@ -397,7 +409,7 @@ test('repository mounts repository filesystem', () => { // GIVEN const repo = new Repository(stack, 'repositoryInstaller', { vpc, - version: deadlineVersion, + version, }); // WHEN @@ -415,7 +427,7 @@ test.each([ // WHEN new Repository(stack, 'repositoryInstaller', { vpc, - version: deadlineVersion, + version, removalPolicy: { database: policy, }, @@ -435,7 +447,7 @@ test.each([ // WHEN new Repository(stack, 'repositoryInstaller', { vpc, - version: deadlineVersion, + version, removalPolicy: { filesystem: policy, }, @@ -460,7 +472,7 @@ test('repository warns if removal policy for filesystem when filesystem provided const repo = new Repository(stack, 'repositoryInstaller', { vpc, fileSystem: testFS, - version: deadlineVersion, + version, removalPolicy: { filesystem: RemovalPolicy.DESTROY, }, @@ -500,7 +512,7 @@ test('repository warns if removal policy for database when database provided', ( const repo = new Repository(stack, 'repositoryInstaller', { vpc, database: DatabaseConnection.forDocDB({ database: fsDatabase, login: fsDatabase.secret! }), - version: deadlineVersion, + version, removalPolicy: { database: RemovalPolicy.DESTROY, }, @@ -529,7 +541,7 @@ test('repository creates deadlineDatabase if none provided', () => { new Repository(stack, 'repositoryInstaller', { vpc, fileSystem: testFS, - version: deadlineVersion, + version, }); // THEN @@ -560,7 +572,7 @@ test('disabling Audit logging does not enable Cloudwatch audit logs', () => { new Repository(stack, 'repositoryInstaller', { vpc, fileSystem: testFS, - version: deadlineVersion, + version, databaseAuditLogging: false, }); @@ -598,7 +610,7 @@ test('repository warns if databaseAuditLogging defined and database is specified // WHEN const repo = new Repository(stack, 'repositoryInstaller', { vpc, - version: deadlineVersion, + version, removalPolicy: { filesystem: RemovalPolicy.DESTROY, }, @@ -606,13 +618,15 @@ test('repository warns if databaseAuditLogging defined and database is specified databaseAuditLogging: true, }); + const warningMsg = 'The parameter databaseAuditLogging only has an effect when the Repository is creating its own database.\n' + + 'Please ensure that the Database provided is configured correctly.'; + // THEN expect(repo.node.metadata).toEqual( expect.arrayContaining([ expect.objectContaining({ type: 'aws:cdk:warning', - data: `The parameter databaseAuditLogging only has an effect when the Repository is creating its own database. - Please ensure that the Database provided is configured correctly.`, + data: warningMsg, }), ]), ); @@ -638,7 +652,7 @@ test('honors subnet specification', () => { // WHEN new Repository(isolatedStack, 'repositoryInstaller', { vpc: dependencyVpc, - version: deadlineVersion, + version, vpcSubnets: { subnets, }, @@ -662,7 +676,7 @@ test('repository honors database instance count', () => { // WHEN new Repository(stack, 'repositoryInstaller', { vpc, - version: deadlineVersion, + version, documentDbInstanceCount: instanceCount, }); @@ -679,7 +693,7 @@ test('repository honors database retention period', () => { // WHEN new Repository(stack, 'repositoryInstaller', { vpc, - version: deadlineVersion, + version, backupOptions: { databaseRetention: Duration.days(period), }, @@ -714,7 +728,7 @@ test('warns if both retention period and database provided', () => { const repo = new Repository(stack, 'repositoryInstaller', { vpc, database: DatabaseConnection.forDocDB({ database: fsDatabase, login: fsDatabase.secret! }), - version: deadlineVersion, + version, backupOptions: { databaseRetention: Duration.days(20), }, @@ -761,7 +775,7 @@ test('repository creates filesystem if none provided', () => { new Repository(stack, 'repositoryInstaller', { vpc, database: DatabaseConnection.forDocDB({ database: fsDatabase, login: fsDatabase.secret }), - version: deadlineVersion, + version, }); // THEN @@ -772,7 +786,7 @@ test('repository creates filesystem if none provided', () => { test('default repository instance is created using user defined installation path prefix', () => { const repo = new Repository(stack, 'repositoryInstaller', { vpc, - version: deadlineVersion, + version, repositoryInstallationPrefix: 'xyz', }); @@ -784,7 +798,7 @@ test('default repository instance is created using user defined installation pat test('default repository instance is created using user defined installation path prefix with extra slashes in path', () => { const repo = new Repository(stack, 'repositoryInstaller', { vpc, - version: deadlineVersion, + version, repositoryInstallationPrefix: '/xyz//', }); @@ -797,7 +811,7 @@ test('repository instance is created with user defined timeout', () => { new Repository(stack, 'repositoryInstaller', { vpc, repositoryInstallationTimeout: Duration.minutes(30), - version: deadlineVersion, + version, }); // THEN @@ -817,7 +831,7 @@ test('repository instance is created with correct installer path version', () => const repo = new Repository(stack, 'repositoryInstaller', { vpc, repositoryInstallationTimeout: Duration.minutes(30), - version: deadlineVersion, + version, }); // THEN @@ -835,7 +849,7 @@ test.each([ // WHEN new Repository(stack, id, { vpc, - version: deadlineVersion, + version, logGroupProps: { logGroupPrefix: testPrefix, }, @@ -852,7 +866,7 @@ test('validate instance self-termination', () => { const repo = new Repository(stack, 'repositoryInstaller', { vpc, repositoryInstallationTimeout: Duration.minutes(30), - version: deadlineVersion, + version, }); const asgLogicalId = stack.getLogicalId(repo.node.defaultChild!.node.defaultChild as CfnElement); @@ -887,7 +901,7 @@ test('repository configure client instance', () => { const repo = new Repository(stack, 'repositoryInstaller', { vpc, repositoryInstallationTimeout: Duration.minutes(30), - version: deadlineVersion, + version, }); const instance = new Instance(stack, 'Instance', { vpc, @@ -921,7 +935,7 @@ test('configureClientInstance uses singleton for repo config script', () => { const repo = new Repository(stack, 'repositoryInstaller', { vpc, repositoryInstallationTimeout: Duration.minutes(30), - version: deadlineVersion, + version, }); const instance1 = new Instance(stack, 'Instance1', { vpc, @@ -997,56 +1011,12 @@ test('configureClientInstance uses singleton for repo config script', () => { })); }); -test('must provide linux repository installer', () => { - // GIVEN - const version: IVersion = { - majorVersion: 10, - minorVersion: 1, - releaseVersion: 0, - linuxFullVersionString: () => '10.1.0.3', - }; - - // THEN - expect(() => { - new Repository(stack, 'repositoryInstaller', { - vpc, - version, - }); - }).toThrowError('Version given to Repository must provide a Linux Repository installer.'); -}); - -test('must provide linux repository full version string', () => { - // GIVEN - const s3Bucket = Bucket.fromBucketName(stack, 'Bucket', 'someBucket'); - const version: IVersion = { - majorVersion: 10, - minorVersion: 1, - releaseVersion: 0, - linuxFullVersionString: () => undefined, - linuxInstallers: { - patchVersion: 1, - repository: { - s3Bucket, - objectKey: 'somekey', - }, - }, - }; - - // THEN - expect(() => { - new Repository(stack, 'repositoryInstaller', { - vpc, - version, - }); - }).toThrowError('Version given to Repository must provide a full Linux version string.'); -}); - test('windows client cannot direct connect to repository', () => { // GIVEN const repo = new Repository(stack, 'repositoryInstaller', { vpc, repositoryInstallationTimeout: Duration.minutes(30), - version: deadlineVersion, + version, }); const instance = new Instance(stack, 'Instance', { vpc, @@ -1071,7 +1041,7 @@ describe('tagging', () => { const isolatedStack = new Stack(app, 'IsolatedStack'); new Repository(isolatedStack, 'Repository', { vpc, - version: deadlineVersion, + version, }); return isolatedStack; }, diff --git a/packages/aws-rfdk/lib/deadline/test/stage.test.ts b/packages/aws-rfdk/lib/deadline/test/stage.test.ts index ff7818cfc..69d29fa0c 100644 --- a/packages/aws-rfdk/lib/deadline/test/stage.test.ts +++ b/packages/aws-rfdk/lib/deadline/test/stage.test.ts @@ -6,6 +6,11 @@ import * as fs from 'fs'; import * as path from 'path'; +import { + expect as expectCDK, + haveResourceLike, + stringLike, +} from '@aws-cdk/assert'; import { App, Stack, @@ -395,19 +400,11 @@ describe('Stage', () => { }); // WHEN - const version = stage.getVersion(stack, 'Version'); - const linuxFullVersionString = version.linuxFullVersionString(); - - // THEN - expect(version.majorVersion).toEqual(10); - expect(version.minorVersion).toEqual(1); - expect(version.releaseVersion).toEqual(9); - - expect(version.linuxInstallers).toBeDefined(); - expect(version.linuxInstallers?.patchVersion).toEqual(2); - - expect(linuxFullVersionString).toBeDefined(); - expect(linuxFullVersionString).toEqual('10.1.9.2'); + stage.getVersion(stack, 'Version'); + expectCDK(stack).to(haveResourceLike('Custom::RFDK_DEADLINE_INSTALLERS', { + forceRun: stringLike('*'), + versionString: '10.1.9', + })); }); }); }); diff --git a/packages/aws-rfdk/lib/deadline/test/thinkbox-docker-recipes.test.ts b/packages/aws-rfdk/lib/deadline/test/thinkbox-docker-recipes.test.ts index 590053bbd..fde6f4f76 100644 --- a/packages/aws-rfdk/lib/deadline/test/thinkbox-docker-recipes.test.ts +++ b/packages/aws-rfdk/lib/deadline/test/thinkbox-docker-recipes.test.ts @@ -5,6 +5,11 @@ import * as path from 'path'; +import { + expect as expectCDK, + haveResourceLike, + stringLike, +} from '@aws-cdk/assert'; import { DockerImageAsset } from '@aws-cdk/aws-ecr-assets'; import { App, @@ -13,7 +18,6 @@ import { import { DeadlineDockerRecipes, - IVersion, Manifest, Recipe, Stage, @@ -54,7 +58,8 @@ describe('ThinkboxDockerRecipes', () => { const MINOR_VERSION = 1; const RELEASE_VERSION = 9; const PATCH_VERSION = 2; - const FULL_VERSION_STRING = `${MAJOR_VERSION}.${MINOR_VERSION}.${RELEASE_VERSION}.${PATCH_VERSION}`; + const RELEASE_VERSION_STRING = `${MAJOR_VERSION}.${MINOR_VERSION}.${RELEASE_VERSION}`; + const FULL_VERSION_STRING = `${RELEASE_VERSION_STRING}.${PATCH_VERSION}`; beforeEach(() => { app = new App(); @@ -129,40 +134,16 @@ describe('ThinkboxDockerRecipes', () => { expect(actualImage.sourceHash).toEqual(expectedImage.sourceHash); }); - describe('provides the Deadline version with', () => { - let version: IVersion; - beforeEach(() => { - // WHEN - const recipes = new ThinkboxDockerRecipes(stack, 'Recipes', { - stage, - }); - version = recipes.version; + test('provides the Deadline version', () => { + // WHEN + new ThinkboxDockerRecipes(stack, 'Recipes', { + stage, }); - test('majorVersion attribute', () => { - // THEN - expect(version.majorVersion).toEqual(MAJOR_VERSION); - }); - test('minorVersion attribute', () => { - // THEN - expect(version.minorVersion).toEqual(MINOR_VERSION); - }); - test('releaseVersion component', () => { - // THEN - expect(version.releaseVersion).toEqual(RELEASE_VERSION); - }); - test('linuxFullVersionString() result', () => { - // THEN - expect(version.linuxFullVersionString()).toEqual(FULL_VERSION_STRING); - }); - test('linuxInstallers', () => { - // THEN - expect(version.linuxInstallers).toBeDefined(); - expect(version.linuxInstallers?.patchVersion).toEqual(PATCH_VERSION); - expect(version.linuxInstallers?.repository).toBeDefined(); - expect(version.linuxInstallers?.repository?.s3Bucket.bucketName).toEqual('thinkbox-installers'); - expect(version.linuxInstallers?.repository?.objectKey).toEqual(`Deadline/${FULL_VERSION_STRING}/Linux/DeadlineRepository-${FULL_VERSION_STRING}-linux-x64-installer.run`); - }); + expectCDK(stack).to(haveResourceLike('Custom::RFDK_DEADLINE_INSTALLERS', { + forceRun: stringLike('*'), + versionString: RELEASE_VERSION_STRING, + })); }); test.each([ diff --git a/packages/aws-rfdk/lib/deadline/test/usage-based-licensing.test.ts b/packages/aws-rfdk/lib/deadline/test/usage-based-licensing.test.ts index 228090417..e8ffaab33 100644 --- a/packages/aws-rfdk/lib/deadline/test/usage-based-licensing.test.ts +++ b/packages/aws-rfdk/lib/deadline/test/usage-based-licensing.test.ts @@ -57,7 +57,7 @@ const env = { }; let app: App; let certificateSecret: ISecret; -let deadlineVersion: IVersion; +let versionedInstallers: IVersion; let dependencyStack: Stack; let dockerContainer: DockerImageAsset; let images: UsageBasedLicensingImages; @@ -77,27 +77,20 @@ describe('UsageBasedLicensing', () => { dependencyStack = new Stack(app, 'DependencyStack', { env }); - deadlineVersion = VersionQuery.exact(dependencyStack, 'Version', { - majorVersion: 10, - minorVersion: 1, - releaseVersion: 9, - patchVersion: 1, - }); - - expect(deadlineVersion.linuxFullVersionString).toBeDefined(); + versionedInstallers = new VersionQuery(dependencyStack, 'VersionQuery'); vpc = new Vpc(dependencyStack, 'VPC'); rcsImage = ContainerImage.fromDockerImageAsset(new DockerImageAsset(dependencyStack, 'Image', { directory: __dirname, })); renderQueue = new RenderQueue(dependencyStack, 'RQ-NonDefaultPort', { - version: deadlineVersion, vpc, images: { remoteConnectionServer: rcsImage }, repository: new Repository(dependencyStack, 'RepositoryNonDefault', { vpc, - version: deadlineVersion, + version: versionedInstallers, }), + version: versionedInstallers, }); lfCluster = new Cluster(dependencyStack, 'licenseForwarderCluster', { diff --git a/packages/aws-rfdk/lib/deadline/test/version-query.test.ts b/packages/aws-rfdk/lib/deadline/test/version-query.test.ts new file mode 100644 index 000000000..845a1b71f --- /dev/null +++ b/packages/aws-rfdk/lib/deadline/test/version-query.test.ts @@ -0,0 +1,133 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + ABSENT, + expect as expectCDK, + haveResourceLike, + stringLike, +} from '@aws-cdk/assert'; +import { + App, + Stack, +} from '@aws-cdk/core'; + +import { + VersionQuery, +} from '../lib'; + +import { VERSION_QUERY_ASSET } from './asset-constants'; + +test('VersionQuery constructor full version', () => { + const app = new App(); + const stack = new Stack(app, 'Stack'); + new VersionQuery(stack, 'VersionQuery', { version: '10.1.9.2'}); + + expectCDK(stack).to(haveResourceLike('Custom::RFDK_DEADLINE_INSTALLERS', { + forceRun: ABSENT, + versionString: '10.1.9.2', + })); + expectCDK(stack).to(haveResourceLike('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'lambda.amazonaws.com', + }, + }, + ], + }, + ManagedPolicyArns: [ + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':iam::aws:policy/service-role/AWSLambdaBasicExecutionRole', + ], + ], + }, + ], + })); + expectCDK(stack).to(haveResourceLike('AWS::Lambda::Function', { + Code: { + S3Bucket: { + Ref: VERSION_QUERY_ASSET.Bucket, + }, + S3Key: { + 'Fn::Join': [ + '', + [ + { + 'Fn::Select': [ + 0, + { + 'Fn::Split': [ + '||', + { + Ref: VERSION_QUERY_ASSET.Key, + }, + ], + }, + ], + }, + { + 'Fn::Select': [ + 1, + { + 'Fn::Split': [ + '||', + { + Ref: VERSION_QUERY_ASSET.Key, + }, + ], + }, + ], + }, + ], + ], + }, + }, + Handler: 'version-provider.handler', + Role: { + 'Fn::GetAtt': [ + stringLike('SingletonLambda*ServiceRole*'), + 'Arn', + ], + }, + Runtime: 'nodejs12.x', + })); +}); + +test('VersionQuery constructor no versionString', () => { + const app = new App(); + const stack = new Stack(app, 'Stack'); + new VersionQuery(stack, 'VersionQuery'); + + expectCDK(stack).to(haveResourceLike('Custom::RFDK_DEADLINE_INSTALLERS', { + forceRun: stringLike('*'), + versionString: ABSENT, + })); +}); + +test.each([ + ['10.1.9'], + ['10.1'], + ['10'], +])('VersionQuery constructor partial version: %s', (version: string) => { + const app = new App(); + const stack = new Stack(app, 'Stack'); + new VersionQuery(stack, 'VersionQuery', { version }); + + expectCDK(stack).to(haveResourceLike('Custom::RFDK_DEADLINE_INSTALLERS', { + versionString: version, + forceRun: stringLike('*'), + })); +}); diff --git a/packages/aws-rfdk/lib/deadline/test/version.test.ts b/packages/aws-rfdk/lib/deadline/test/version.test.ts index 3c8c0eeec..7d3b819a9 100644 --- a/packages/aws-rfdk/lib/deadline/test/version.test.ts +++ b/packages/aws-rfdk/lib/deadline/test/version.test.ts @@ -4,21 +4,9 @@ */ import { - expect as expectCDK, - haveResource, - haveResourceLike, -} from '@aws-cdk/assert'; -import { - Stack, -} from '@aws-cdk/core'; -import { - IVersion, Version, - VersionQuery, } from '../lib'; -let stack: Stack; - describe('Version', () => { describe('.isGreaterThan', () => { test.each<[string, { firstVersion: string, secondVersion: string, expectedValue: boolean }]>([ @@ -164,19 +152,19 @@ describe('Version', () => { 'incorrect component count', { version: [10, 1, 9], - expectedException: /Invalid version format/, + expectedException: /Invalid version format. Version should contain exactly 4 components./, }, ], [ 'negative value', { - version: [10, -1, 9], - expectedException: /Invalid version format/, + version: [10, -1, 9, 2], + expectedException: /Invalid version format. None of the version components can be negative./, }, ], [ 'decimal value', { - version: [10, 1, 9.2], - expectedException: /Invalid version format/, + version: [10, 1, 9.2, 2], + expectedException: /Invalid version format. None of the version components can contain decimal values./, }, ], [ 'correct value', @@ -191,7 +179,11 @@ describe('Version', () => { if (expectedException) { expect(() => new Version(version)).toThrow(expectedException); } else { - expect(() => new Version(version)).not.toThrow(); + const versionObj = new Version(version); + expect(versionObj.majorVersion).toEqual(version[0]); + expect(versionObj.minorVersion).toEqual(version[1]); + expect(versionObj.releaseVersion).toEqual(version[2]); + expect(versionObj.patchVersion).toEqual(version[3]); } }); }); @@ -234,141 +226,3 @@ describe('Version', () => { }); }); }); - -describe('VersionQuery', () => { - beforeEach(() => { - stack = new Stack(undefined, undefined); - }); - - describe('constructor', () => { - test('throws not implemented error for empty version string', () => { - // WHEN - expect(() => { - new VersionQuery(stack, 'version', { - version: '', - }); - }).toThrowError(/MethodNotSupportedException: This method is currently not implemented./); - - // THEN - expectCDK(stack).notTo(haveResource('AWS::Lambda::Function')); - expectCDK(stack).notTo(haveResourceLike('AWS::CloudFormation::CustomResource', { - DeadlineVersion: '', - })); - }); - - test('throws not implemented error for valid version string', () => { - // WHEN - expect(() => { - new VersionQuery(stack, 'version', { - version: '1.2', - }); - }).toThrowError(/MethodNotSupportedException: This method is currently not implemented./); - - // THEN - expectCDK(stack).notTo(haveResource('AWS::Lambda::Function')); - expectCDK(stack).notTo(haveResourceLike('AWS::CloudFormation::CustomResource', { - DeadlineVersion: '1.2', - })); - }); - - test('throws not implemented error without props', () => { - // WHEN - expect(() => { - new VersionQuery(stack, 'version'); - }).toThrowError(/MethodNotSupportedException: This method is currently not implemented./); - - // THEN - expectCDK(stack).notTo(haveResource('AWS::Lambda::Function')); - expectCDK(stack).notTo(haveResourceLike('AWS::CloudFormation::CustomResource')); - expectCDK(stack).notTo(haveResourceLike('AWS::CloudFormation::CustomResource', { - DeadlineVersion: '', - })); - }); - }); - - // GIVEN - const majorVersion = 1; - const minorVersion = 2; - const releaseVersion = 3; - const patchVersion = 4; - const expectedVersionString = `${majorVersion}.${minorVersion}.${releaseVersion}.${patchVersion}`; - - let version: IVersion; - - function exactVersionTests() { - test('does not create a custom resource', () => { - // THEN - expectCDK(stack).notTo(haveResourceLike('AWS::CloudFormation::CustomResource')); - }); - - test('does not create a lambda', () => { - // THEN - expectCDK(stack).notTo(haveResource('AWS::Lambda::Function')); - }); - - test('populates version components', () => { - // THEN - expect(version.majorVersion).toEqual(1); - expect(version.minorVersion).toEqual(2); - expect(version.releaseVersion).toEqual(3); - }); - - test('provides linux installers', () => { - // GIVEN - const linuxFullVersionString = version.linuxFullVersionString(); - - // THEN - expect(version.linuxInstallers).toBeDefined(); - expect(linuxFullVersionString).toBeDefined(); - expect(linuxFullVersionString).toMatch(expectedVersionString); - - expect(version.linuxInstallers!.repository).toBeDefined(); - expect(version.linuxInstallers!.repository!.s3Bucket.bucketName).toMatch('thinkbox-installers'); - expect(version.linuxInstallers!.repository!.objectKey).toMatch(`DeadlineRepository-${expectedVersionString}-linux-x64-installer.run`); - }); - } - - describe('.exact()', () => { - beforeEach(() => { - version = VersionQuery.exact(stack, 'version', { - majorVersion, - minorVersion, - releaseVersion, - patchVersion, - }); - }); - - exactVersionTests(); - }); - - describe('.exactString()', () => { - beforeEach(() => { - version = VersionQuery.exactString(stack, 'version', expectedVersionString); - }); - - exactVersionTests(); - - test.each([ - [''], - ['abc'], - ['1'], - ['1.2'], - ['1.2.3'], - ['1.2.3.4a'], - ['a1.2.3.4a'], - ['a1.2.3.4'], - [' 1.2.3.4 '], - ['a.b.c.d'], - ['-1.0.2.3'], - ['.1.0.2.3'], - ])('throws an error on invalid versions %p', (versionStr: string) => { - // WHEN - function when() { - VersionQuery.exactString(stack, 'version', versionStr); - } - - // THEN - expect(when).toThrowError(new RegExp(`^Invalid version format. Expected format 'a.b.c.d', found '${versionStr}'$`)); - }); - }); -}); diff --git a/packages/aws-rfdk/lib/deadline/test/worker-fleet.test.ts b/packages/aws-rfdk/lib/deadline/test/worker-fleet.test.ts index a538ec83d..f74e65d93 100644 --- a/packages/aws-rfdk/lib/deadline/test/worker-fleet.test.ts +++ b/packages/aws-rfdk/lib/deadline/test/worker-fleet.test.ts @@ -27,7 +27,7 @@ import { AssetImage, ContainerImage, } from '@aws-cdk/aws-ecs'; -import {ArtifactMetadataEntryType} from '@aws-cdk/cloud-assembly-schema'; +import { ArtifactMetadataEntryType } from '@aws-cdk/cloud-assembly-schema'; import { App, CfnElement, @@ -70,20 +70,15 @@ beforeEach(() => { }); vpc = new Vpc(stack, 'VPC'); rcsImage = ContainerImage.fromAsset(__dirname); - const version = VersionQuery.exact(stack, 'Version', { - majorVersion: 10, - minorVersion: 0, - releaseVersion: 0, - patchVersion: 0, - }); + const version = new VersionQuery(stack, 'VersionQuery'); renderQueue = new RenderQueue(stack, 'RQ', { - version, vpc, images: { remoteConnectionServer: rcsImage }, repository: new Repository(stack, 'Repository', { vpc, version, }), + version, }); wfstack = new Stack(app, 'workerFleetStack', { env: {