From fad2e80f6bfa1d74c13c8f8a707e2a61dbb21efe Mon Sep 17 00:00:00 2001 From: David Horsman Date: Thu, 8 Oct 2020 08:28:35 +0000 Subject: [PATCH] feat(deadline): versionquery construct Fixes #176 BREAKING CHANGE: The version parameter in the RenderQueue constructor properties was removed and the one taken by the Repository constructor was modified to use the new VersionQuery construct. --- .../python/package/lib/service_tier.py | 6 +- .../ts/lib/service-tier.ts | 15 +- integ/lib/render-struct.ts | 1 - integ/lib/storage-struct.ts | 41 +++- .../nodejs/version-provider/handler.ts | 223 +++++++++++++++++- .../lambdas/nodejs/version-provider/index.ts | 1 + .../version-provider/test/handler.test.ts | 52 ++++ .../version-provider/test}/index-test.json | 0 .../test/version-provider.test.ts | 162 ++++++------- .../version-provider/test/version.test.ts | 91 +++++++ .../version-provider/version-provider.ts | 92 +++----- .../nodejs/version-provider/version.ts | 35 +++ packages/aws-rfdk/lib/deadline/lib/index.ts | 2 + .../lib/deadline/lib/render-queue-ref.ts | 7 - .../aws-rfdk/lib/deadline/lib/repository.ts | 34 ++- packages/aws-rfdk/lib/deadline/lib/stage.ts | 24 +- .../deadline/lib/thinkbox-docker-recipes.ts | 6 +- .../lib/deadline/lib/version-query-ref.ts | 152 ++++++++++++ .../lib/deadline/lib/version-query.ts | 179 ++++++++++++++ .../aws-rfdk/lib/deadline/lib/version-ref.ts | 66 ------ packages/aws-rfdk/lib/deadline/lib/version.ts | 191 +-------------- .../lib/deadline/test/asset-constants.ts | 5 + .../lib/deadline/test/render-queue.test.ts | 50 +--- .../lib/deadline/test/repository.test.ts | 155 ++++++------ .../aws-rfdk/lib/deadline/test/stage.test.ts | 54 ++--- .../test/thinkbox-docker-recipes.test.ts | 46 +--- .../test/usage-based-licensing.test.ts | 16 +- .../lib/deadline/test/version-query.test.ts | 116 +++++++++ .../lib/deadline/test/version.test.ts | 176 ++------------ .../lib/deadline/test/worker-fleet.test.ts | 13 +- 30 files changed, 1150 insertions(+), 861 deletions(-) create mode 100644 packages/aws-rfdk/lib/core/lambdas/nodejs/version-provider/test/handler.test.ts rename packages/aws-rfdk/{bin => lib/core/lambdas/nodejs/version-provider/test}/index-test.json (100%) create mode 100644 packages/aws-rfdk/lib/core/lambdas/nodejs/version-provider/test/version.test.ts create mode 100644 packages/aws-rfdk/lib/core/lambdas/nodejs/version-provider/version.ts create mode 100644 packages/aws-rfdk/lib/deadline/lib/version-query-ref.ts create mode 100644 packages/aws-rfdk/lib/deadline/lib/version-query.ts create mode 100644 packages/aws-rfdk/lib/deadline/test/version-query.test.ts 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..b377cd940 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 @@ -44,6 +44,7 @@ ThinkboxDockerRecipes, UsageBasedLicense, UsageBasedLicensing, + VersionQuery, ) @@ -117,11 +118,13 @@ def __init__(self, scope: Construct, stack_id: str, *, props: ServiceTierProps, stage=Stage.from_directory(props.docker_recipes_stage_path) ) + version_query = VersionQuery(self, 'VersionQuery') + repository = Repository( self, 'Repository', vpc=props.vpc, - version=recipes.version, + version=version_query, database=props.database, file_system=props.file_system, repository_installation_timeout=Duration.minutes(20) @@ -142,7 +145,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( 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..76bbcaa7d 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'; @@ -29,9 +29,10 @@ import { ThinkboxDockerRecipes, UsageBasedLicense, UsageBasedLicensing, + VersionQuery, } from 'aws-rfdk/deadline'; -import { - Secret +import { + Secret, } from '@aws-cdk/aws-secretsmanager'; import { Duration } from '@aws-cdk/core'; @@ -132,9 +133,12 @@ export class ServiceTier extends cdk.Stack { stage: Stage.fromDirectory(props.dockerRecipesStagePath), }); + // By not specifying the version property, this will always pull the latest version + const versionQuery = new VersionQuery(this, 'VersionQuery'); + const repository = new Repository(this, 'Repository', { vpc: props.vpc, - version: recipes.version, + version: versionQuery, database: props.database, fileSystem: props.fileSystem, repositoryInstallationTimeout: Duration.minutes(20), @@ -150,7 +154,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: { @@ -170,7 +173,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/lib/render-struct.ts b/integ/lib/render-struct.ts index 6d306f008..8d22e5bc3 100644 --- a/integ/lib/render-struct.ts +++ b/integ/lib/render-struct.ts @@ -79,7 +79,6 @@ export class RenderStruct extends Construct { vpc, repository: props.repository, images: recipes.renderQueueImages, - version: recipes.version, logGroupProps: { logGroupPrefix: Stack.of(this).stackName + '-' + id, }, diff --git a/integ/lib/storage-struct.ts b/integ/lib/storage-struct.ts index 710c875c0..32aedd071 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, + Repository, + VersionQuery, +} from 'aws-rfdk/deadline'; // Interface for supplying database connection and accompanying secret for credentials @@ -44,16 +67,12 @@ 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; + // This will get the installers for the latest version of Deadline if the version property isn't specified + const versionQuery = new VersionQuery(this, 'VersionQuery'); let cacert; let database; @@ -188,7 +207,7 @@ export class StorageStruct extends Construct { vpc, database: databaseConnection, fileSystem: deadlineMountableEfs, - version: version, + versionedInstallers: versionQuery, repositoryInstallationTimeout: Duration.minutes(20), logGroupProps: { logGroupPrefix: Stack.of(this).stackName + '-' + id, 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 b8cca78d9..cacdac699 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 @@ -7,52 +7,253 @@ import { LambdaContext } from '../lib/aws-lambda'; import { CfnRequestEvent, SimpleCustomResource } from '../lib/custom-resource'; +import { Version } from './version'; import { - VersionProvider, - IVersionProviderProperties, - IVersionedUris, Platform, + Product, + VersionProvider, } from './version-provider'; +/** + * The input to this Custom Resource + */ +export interface IVersionProviderResourceProperties { + /** + * The version of Deadline to look up. + */ + readonly versionString?: 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 for all the URI's in this structure. + */ + readonly MajorVersion: string; + + /** + * The minor version for all the URI's in this structure. + */ + readonly MinorVersion: string; + + /** + * The release version for all the URI's in this structure. + */ + readonly ReleaseVersion: string; + + /** + * The full version of the Linux installers, saved as "major.minor.release.patch". For example: "10.1.10.6". + */ + readonly LinuxDeadlineVersion: string; + + /** + * The full version of the Mac installers, saved as "major.minor.release.patch". For example: "10.1.10.6". + */ + readonly MacDeadlineVersion: string; + + /** + * The full version of the Windows installers, saved as "major.minor.release.patch". For example: "10.1.10.6". + */ + readonly WindowsDeadlineVersion: string; + + /** + * The URI of the bundled Deadline installers for Linux. + */ + readonly LinuxBundle: string; + + /** + * The URI of the Deadline client installer for Linux. + */ + readonly LinuxClientInstaller: string; + + /** + * The URI of the Deadline repository installer for Linux. + */ + readonly LinuxRepositoryInstaller: string; + + /** + * The URI of the Deadline certificate installer for Linux. + * @default - No installer provided + */ + readonly LinuxCertificateInstaller?: string; + + /** + * The URI of the bundled Deadline installers for Mac. + */ + readonly MacBundle: string; + + /** + * The URI of the bundled Deadline installers for Windows. + */ + readonly WindowsBundle: string; + + /** + * The URI of the Deadline client installer for Windows. + */ + readonly WindowsClientInstaller: string; + + /** + * The URI of the Deadline repository installer for Windows. + */ + readonly WindowsRepositoryInstaller: string; + + /** + * The URI of the Deadline certificate installer for Windows. + * @default - No installer provided + */ + readonly WindowsCertificateInstaller?: string; + + /** + * The full version of the Deadline Docker recipes, saved as "major.minor.release.patch". For example: "10.1.10.6". + */ + readonly LinuxDeadlineDockerVersion: string; + + /** + * The URI of the Deadline Docker recipes for Linux. + */ + readonly LinuxDockerRecipes: 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, + product: Product.deadline, + }); + + const deadlineLinux = deadlinePlatFormVersionedUris.get(Platform.linux)!; + const deadlineLinuxUris = deadlineLinux.Uris; + const deadlineMac = deadlinePlatFormVersionedUris.get(Platform.mac)!; + const deadlineMacUris = deadlineMac.Uris; + const deadlineWindows = deadlinePlatFormVersionedUris.get(Platform.windows)!; + const deadlineWindowsUris = deadlineWindows.Uris; + + const deadlineDockerPlatFormVersionedUris = await this.versionProvider.getVersionUris({ + versionString: resourceProperties.versionString, + product: Product.deadlineDocker, + platform: Platform.linux, + }); + + const deadlineDockerLinux = deadlineDockerPlatFormVersionedUris.get(Platform.linux)!; + const deadlineDockerLinuxUris = deadlineDockerLinux.Uris; + + const s3Bucket = this.parseS3BucketName(deadlineLinuxUris.bundle); + + return { + S3Bucket: s3Bucket, + MajorVersion: deadlineLinux.MajorVersion, + MinorVersion: deadlineLinux.MinorVersion, + ReleaseVersion: deadlineLinux.ReleaseVersion, + LinuxDeadlineVersion: Version.convertToFullVersionString( + deadlineLinux.MajorVersion, + deadlineLinux.MinorVersion, + deadlineLinux.ReleaseVersion, + deadlineLinux.PatchVersion), + LinuxBundle: deadlineLinuxUris.bundle, + LinuxClientInstaller: deadlineLinuxUris.clientInstaller!, + LinuxRepositoryInstaller: deadlineLinuxUris.repositoryInstaller!, + LinuxCertificateInstaller: deadlineLinuxUris.certificateInstaller, + MacDeadlineVersion: Version.convertToFullVersionString( + deadlineMac.MajorVersion, + deadlineMac.MinorVersion, + deadlineMac.ReleaseVersion, + deadlineMac.PatchVersion), + MacBundle: deadlineMacUris.bundle, + WindowsDeadlineVersion: Version.convertToFullVersionString( + deadlineWindows.MajorVersion, + deadlineWindows.MinorVersion, + deadlineWindows.ReleaseVersion, + deadlineWindows.PatchVersion), + WindowsBundle: deadlineWindowsUris.bundle, + WindowsClientInstaller: deadlineWindowsUris.clientInstaller!, + WindowsRepositoryInstaller: deadlineWindowsUris.repositoryInstaller!, + WindowsCertificateInstaller: deadlineWindowsUris.certificateInstaller, + LinuxDeadlineDockerVersion: Version.convertToFullVersionString( + deadlineDockerLinux.MajorVersion, + deadlineDockerLinux.MinorVersion, + deadlineDockerLinux.ReleaseVersion, + deadlineDockerLinux.PatchVersion), + LinuxDockerRecipes: deadlineDockerLinuxUris.bundle, + }; } /** * @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)) { 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 bucket name from ${uri}`); + } + return bucketName; + } + + // 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/index.ts b/packages/aws-rfdk/lib/core/lambdas/nodejs/version-provider/index.ts index 9b8a8b5eb..e348747ab 100644 --- a/packages/aws-rfdk/lib/core/lambdas/nodejs/version-provider/index.ts +++ b/packages/aws-rfdk/lib/core/lambdas/nodejs/version-provider/index.ts @@ -5,3 +5,4 @@ export * from './handler'; export * from './version-provider'; +export * from './version'; 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..c58c9afe4 --- /dev/null +++ b/packages/aws-rfdk/lib/core/lambdas/nodejs/version-provider/test/handler.test.ts @@ -0,0 +1,52 @@ +/** + * 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', + })).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(); + }); + }); + + 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 bucket name/); + }); + }); +}); diff --git a/packages/aws-rfdk/bin/index-test.json b/packages/aws-rfdk/lib/core/lambdas/nodejs/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/version-provider/test/index-test.json diff --git a/packages/aws-rfdk/lib/core/lambdas/nodejs/version-provider/test/version-provider.test.ts b/packages/aws-rfdk/lib/core/lambdas/nodejs/version-provider/test/version-provider.test.ts index 6ecebe3b6..026463d96 100644 --- a/packages/aws-rfdk/lib/core/lambdas/nodejs/version-provider/test/version-provider.test.ts +++ b/packages/aws-rfdk/lib/core/lambdas/nodejs/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/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..bd3e278e9 --- /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 '../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/core/lambdas/nodejs/version-provider/version-provider.ts b/packages/aws-rfdk/lib/core/lambdas/nodejs/version-provider/version-provider.ts index 6a27d2dd0..1e6717f13 100644 --- a/packages/aws-rfdk/lib/core/lambdas/nodejs/version-provider/version-provider.ts +++ b/packages/aws-rfdk/lib/core/lambdas/nodejs/version-provider/version-provider.ts @@ -3,12 +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', @@ -69,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]; @@ -98,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, @@ -107,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, @@ -124,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, @@ -154,7 +134,7 @@ export class VersionProvider { }; return new Promise((resolve, reject) => { - http.get(options, (res: http.IncomingMessage) => { + https.get(options, (res: IncomingMessage) => { let json = ''; res.on('data', (chunk: any) => { @@ -199,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, @@ -232,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; @@ -257,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[] = []; @@ -275,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) => { @@ -292,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/version-provider/version.ts b/packages/aws-rfdk/lib/core/lambdas/nodejs/version-provider/version.ts new file mode 100644 index 000000000..833740c0d --- /dev/null +++ b/packages/aws-rfdk/lib/core/lambdas/nodejs/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/deadline/lib/index.ts b/packages/aws-rfdk/lib/deadline/lib/index.ts index d4e42225f..5a633880a 100644 --- a/packages/aws-rfdk/lib/deadline/lib/index.ts +++ b/packages/aws-rfdk/lib/deadline/lib/index.ts @@ -13,4 +13,6 @@ export * from './render-queue-ref'; export * from './stage'; export * from './thinkbox-docker-recipes'; export * from './version'; +export * from './version-query'; +export * from './version-query-ref'; export * from './version-ref'; diff --git a/packages/aws-rfdk/lib/deadline/lib/render-queue-ref.ts b/packages/aws-rfdk/lib/deadline/lib/render-queue-ref.ts index eba234ae7..21b1fbe9a 100644 --- a/packages/aws-rfdk/lib/deadline/lib/render-queue-ref.ts +++ b/packages/aws-rfdk/lib/deadline/lib/render-queue-ref.ts @@ -36,7 +36,6 @@ import { } from '../../core'; import { IHost } from './host-ref'; import { IRepository } from './repository'; -import { IVersion } from './version-ref'; /** * Parameters for the generation of a VPC-internal hostname for the RenderQueue. @@ -215,12 +214,6 @@ export interface RenderQueueAccessLogProps { * Properties for the Render Queue */ export interface RenderQueueProps { - - /** - * The Deadline Client version that will be running within this RenderQueue. - */ - readonly version: IVersion; - /** * The Deadline Repository which the RCS instances will create a direct connection to. */ diff --git a/packages/aws-rfdk/lib/deadline/lib/repository.ts b/packages/aws-rfdk/lib/deadline/lib/repository.ts index 7f646cc93..dadfd4af3 100644 --- a/packages/aws-rfdk/lib/deadline/lib/repository.ts +++ b/packages/aws-rfdk/lib/deadline/lib/repository.ts @@ -63,7 +63,7 @@ import { import { DatabaseConnection } from './database-connection'; import { IHost } from './host-ref'; -import { IVersion } from './version-ref'; +import { IVersionedDeadlineInstallers } from './version-query-ref'; /** * Configuration interface for specifying ECS container instances to permit connecting hosted ECS tasks to the repository @@ -173,7 +173,7 @@ export interface IRepository extends IConstruct { /** * The version of Deadline for Linux that is installed on this Repository. */ - readonly version: IVersion; + readonly versionedInstallers: IVersionedDeadlineInstallers; /** * Configures an ECS Container Instance and Task Definition for deploying a Deadline Client that directly connects to @@ -255,7 +255,7 @@ export interface RepositoryProps { * the latest version of deadline. The current implementation of Version construct * only supports importing it with static values, hence keeping it mandatory for now. */ - readonly version: IVersion; + readonly versionedInstallers: IVersionedDeadlineInstallers; /** * Properties for setting up the Deadline Repository's LogGroup in CloudWatch @@ -421,7 +421,7 @@ export class Repository extends Construct implements IRepository { /** * @inheritdoc */ - public readonly version: IVersion; + public readonly versionedInstallers: IVersionedDeadlineInstallers; /** * Connection object for the database for this repository. @@ -451,7 +451,7 @@ export class Repository extends Construct implements IRepository { this.node.addWarning('RemovalPolicy for database will not be applied since a database is not being created by this construct'); } - this.version = props.version; + this.versionedInstallers = props.versionedInstallers; // Set up the Filesystem and Database components of the repository this.fileSystem = props.fileSystem ?? new MountableEfs(this, { @@ -467,7 +467,7 @@ 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. + 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.`); } } else { @@ -569,7 +569,7 @@ export class Repository extends Construct implements IRepository { this.configureRepositoryInstallerScript( this.installerGroup, repositoryInstallationPath, - props.version, + props.versionedInstallers, ); this.configureSelfTermination(); @@ -586,7 +586,7 @@ export class Repository extends Construct implements IRepository { */ public configureClientECS(props: ECSDirectConnectProps): IContainerDirectRepositoryConnection { const hostMountPoint = props.containerInstances.filesystemMountPoint ?? '/mnt/repo'; - const containerMountPoint = props.containers.filesystemMountPoint ?? `/opt/Thinkbox/DeadlineRepository${this.version.majorVersion}`; + const containerMountPoint = props.containers.filesystemMountPoint ?? `/opt/Thinkbox/DeadlineRepository${this.versionedInstallers.majorVersion}`; // Set up a direct connection on the host machine. This: // - grants IAM permissions to the role associated with the instance profile access to @@ -778,7 +778,7 @@ export class Repository extends Construct implements IRepository { private configureRepositoryInstallerScript( installerGroup: AutoScalingGroup, installPath: string, - version: IVersion) { + versionedInstallers: IVersionedDeadlineInstallers) { const installerScriptAsset = ScriptAsset.fromPathConvention(this, 'DeadlineRepositoryInstallerScript', { osType: installerGroup.osType, baseName: 'installDeadlineRepository', @@ -791,21 +791,15 @@ 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); - + // We are giving permission to read the whole bucket here because we don't have a way to parse the specific object + // key from the repo installer URI (it's a CDK Token that can't be parsed at this point). + versionedInstallers.installers.bucket.grantRead(installerGroup); installerScriptAsset.executeOn({ host: installerGroup, args: [ - `"s3://${version.linuxInstallers.repository.s3Bucket.bucketName}/${version.linuxInstallers.repository.objectKey}"`, + versionedInstallers.installers.linux.repositoryInstaller, `"${installPath}"`, - linuxVersionString, + versionedInstallers.installers.linux.version, ], }); } diff --git a/packages/aws-rfdk/lib/deadline/lib/stage.ts b/packages/aws-rfdk/lib/deadline/lib/stage.ts index cc8e6529a..55f64bcb2 100644 --- a/packages/aws-rfdk/lib/deadline/lib/stage.ts +++ b/packages/aws-rfdk/lib/deadline/lib/stage.ts @@ -9,10 +9,8 @@ import * as path from 'path'; import { DockerImageAsset } from '@aws-cdk/aws-ecr-assets'; import { Construct } from '@aws-cdk/core'; -import { - Version, - VersionQuery, -} from './version'; +import { Version } from './version'; +// import { VersionQuery } from './version-query'; /** * Build arguments to supply to a Docker image build @@ -191,15 +189,15 @@ export class Stage { this.manifest = props.manifest; } - /** - * Creates a {@link Version} based on the manifest version - * - * @param scope The parent scope - * @param id The construct ID - */ - public getVersion(scope: Construct, id: string) { - return VersionQuery.exactString(scope, id, this.manifest.version); - } + // /** + // * Creates a {@link Version} based on the manifest version + // * + // * @param scope The parent scope + // * @param id The construct ID + // */ + // public getVersion(scope: Construct, id: string) { + // return new VersionQuery(scope, id, { version: this.manifest.version }); + // } /** * Construct a {@link DockerImageAsset} instance from a recipe in the Stage diff --git a/packages/aws-rfdk/lib/deadline/lib/thinkbox-docker-recipes.ts b/packages/aws-rfdk/lib/deadline/lib/thinkbox-docker-recipes.ts index d16b57460..e9e6a66b1 100644 --- a/packages/aws-rfdk/lib/deadline/lib/thinkbox-docker-recipes.ts +++ b/packages/aws-rfdk/lib/deadline/lib/thinkbox-docker-recipes.ts @@ -8,7 +8,7 @@ import { ContainerImage } from '@aws-cdk/aws-ecs'; import { Construct } from '@aws-cdk/core'; import { - IVersion, + // VersionQuery, RenderQueueImages, Stage, UsageBasedLicensingImages, @@ -99,12 +99,12 @@ export class ThinkboxDockerRecipes extends Construct { /** * The version of Deadline in the stage directory. */ - public readonly version: IVersion; + // public readonly versionQuery: VersionQuery; constructor(scope: Construct, id: string, props: ThinkboxDockerRecipesProps) { super(scope, id); - this.version = props.stage.getVersion(this, 'Version'); + // this.versionQuery = props.stage.getVersion(this, 'Version'); for (const recipe of [ThinkboxManagedDeadlineDockerRecipes.RemoteConnectionServer, ThinkboxManagedDeadlineDockerRecipes.LicenseForwarder]) { if (!props.stage.manifest.recipes[recipe]) { diff --git a/packages/aws-rfdk/lib/deadline/lib/version-query-ref.ts b/packages/aws-rfdk/lib/deadline/lib/version-query-ref.ts new file mode 100644 index 000000000..8517175be --- /dev/null +++ b/packages/aws-rfdk/lib/deadline/lib/version-query-ref.ts @@ -0,0 +1,152 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { IBucket } from '@aws-cdk/aws-s3'; + +import { IReleaseVersion } from './version-ref'; + +/** + * This base interface represents the Deadline installers files for a specific Deadline version stored in S3 that are + * available on all platforms. + */ +export interface IBaseDeadlinePlatformUris { + /** + * The bundle containing all Deadline installers (repository, client, and certificate). + * + * For example: + * + * - s3://thinkbox-installers/Deadline/10.1.10.6/Linux/Deadline-10.1.10.6-linux-installers.tar + * - s3://thinkbox-installers/Deadline/10.1.10.6/Mac/Deadline-10.1.10.6-osx-installers.dmg + * - s3://thinkbox-installers/Deadline/10.1.10.6/Windows/Deadline-10.1.10.6-windows-installers.zip + */ + readonly bundle: string; + + /** + * The full version string for this release the URI's link to. This includes the major, minor, release, and + * patch versions. + */ + readonly version: string; +} + +/** + * This interface represents a collection of Deadline installer files for a specific Deadline version stored in S3. + */ +export interface IDeadlinePlatformUris extends IBaseDeadlinePlatformUris { + /** + * The Deadline Repository installer for this platform, as extracted from the bundle on the Thinkbox download site. + * + * For example: + * + * - s3://thinkbox-installers/Deadline/10.1.10.6/Linux/DeadlineRepository-10.1.10.6-linux-x64-installer.run + * - s3://thinkbox-installers/Deadline/10.1.10.6/Windows/DeadlineRepository-10.1.10.6-windows-installer.exe + * + */ + readonly repositoryInstaller: string; + + /** + * The Deadline Client installer for this platform, as extracted from the bundle on the Thinkbox download site. + * + * For example: + * + * - s3://thinkbox-installers/Deadline/10.1.10.6/Linux/DeadlineClient-10.1.10.6-linux-x64-installer.run + * - s3://thinkbox-installers/Deadline/10.1.10.6/Windows/DeadlineClient-10.1.10.6-windows-installer.exe + * + */ + readonly clientInstaller: string; + + /** + * The Deadline Cerificate installer for this platform, as extracted from the bundle on the Thinkbox download site. + * + * For example: + * + * - s3://thinkbox-installers/Deadline/10.1.10.6/Linux/CertificateInstaller-10.1.10.6-linux-x64-installer.run + * - s3://thinkbox-installers/Deadline/10.1.10.6/Windows/CertificateInstaller-10.1.10.6-windows-installer.exe + * + * @default No certificate installer is provided. + */ + readonly certificateInstaller?: string; +} + +/** + * This interface defines each supported platform and the installers that are available for it, as well as the S3 + * Bucket that all the installers are contained in. + */ +export interface IDeadlinePlatformInstallers { + /** + * The URI's for Linux. + */ + readonly linux: IDeadlinePlatformUris; + + /** + * The URI's for Mac. + */ + readonly mac: IBaseDeadlinePlatformUris; + + /** + * The URI's for Windows. + */ + readonly windows: IDeadlinePlatformUris; + + /** + * The S3 Bucket that all the installers are in. + */ + readonly bucket: IBucket; +} + +/** + * This interface represents the installers for different Deadline components across OS's. It contains the named + * components of the version string (major, minor, and release but not patch because that is platform specific). + * It also contains the S3 URI's and version strings for each platform, as well as the S3 Bucket the installers are + * contained in. + */ +export interface IVersionedDeadlineInstallers extends IReleaseVersion { + /** + * The locations of the Deadline installers. + */ + readonly installers: IDeadlinePlatformInstallers; +} + +/** + * This interface represents the Deadline Docker recipes files for a specific Deadline version. + */ +export interface IDeadlineDockerPlatformUris { + /** + * The URI of the Deadline recipes for creating Docker images for Linux. + */ + readonly recipes: string; + + /** + * The full version string for the release referenced in this version object. This includes the major, + * minor, release, and patch versions. + */ + readonly version: string; +} + +/** + * This interface splits the S3 URI's for the recipes up by OS. Currently only Linux is supported. + */ +export interface IDeadlineDockerPlatformRecipes { + /** + * The S3 Bucket the recipes are in. + */ + readonly bucket: IBucket; + + /** + * The Uris of the Deadline recipes for creating Docker images for Linux. + */ + readonly linux: IDeadlineDockerPlatformUris; +} + +/** + * This interface represents the recipes to create docker images containing the specified Deadline version. It contains + * the version string essential to identify a version as well as the named components of the version string (major, + * minor, release, and patch). It also includes the S3 URI's of the recipes. + */ +export interface IVersionedDeadlineDockerRecipes extends IReleaseVersion { + /** + * The recipes for this version. + */ + readonly recipes: IDeadlineDockerPlatformRecipes; +} 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..aabd3cf9a --- /dev/null +++ b/packages/aws-rfdk/lib/deadline/lib/version-query.ts @@ -0,0 +1,179 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +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 { + IDeadlineDockerPlatformRecipes, + IDeadlinePlatformInstallers, + IVersionedDeadlineDockerRecipes, + IVersionedDeadlineInstallers, +} from './version-query-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 IVersionedDeadlineInstallers, IVersionedDeadlineDockerRecipes { + /** + * @inheritdoc + */ + public abstract readonly majorVersion: number; + + /** + * @inheritdoc + */ + public abstract readonly minorVersion: number; + + /** + * @inheritdoc + */ + public abstract readonly releaseVersion: number; + + /** + * @inheritdoc + */ + public abstract readonly installers: IDeadlinePlatformInstallers; + + /** + * @inheritdoc + */ + public abstract readonly recipes: IDeadlineDockerPlatformRecipes; +} + +/** + * 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 + */ + public readonly majorVersion: number; + + /** + * @inheritdoc + */ + public readonly minorVersion: number; + + /** + * @inheritdoc + */ + public readonly releaseVersion: number; + + /** + * @inheritdoc + */ + public readonly installers: IDeadlinePlatformInstallers; + + /** + * @inheritdoc + */ + public readonly recipes: IDeadlineDockerPlatformRecipes; + + 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, + }; + + 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.installers = { + bucket: Bucket.fromBucketName(scope, 'InstallersBucket', Token.asString(deadlineResource.getAtt('S3Bucket'))), + linux: { + version: Token.asString(deadlineResource.getAtt('LinuxDeadlineVersion')), + bundle: Token.asString(deadlineResource.getAtt('LinuxBundle')), + clientInstaller: Token.asString(deadlineResource.getAtt('LinuxClientInstaller')), + repositoryInstaller: Token.asString(deadlineResource.getAtt('LinuxRepositoryInstaller')), + certificateInstaller: Token.asString(deadlineResource.getAtt('LinuxCertificateInstaller')), + }, + mac: { + version: Token.asString(deadlineResource.getAtt('MacDeadlineVersion')), + bundle: Token.asString(deadlineResource.getAtt('MacBundle')), + }, + windows: { + version: Token.asString(deadlineResource.getAtt('WindowsDeadlineVersion')), + bundle: Token.asString(deadlineResource.getAtt('WindowsBundle')), + clientInstaller: Token.asString(deadlineResource.getAtt('WindowsClientInstaller')), + repositoryInstaller: Token.asString(deadlineResource.getAtt('WindowsRepositoryInstaller')), + certificateInstaller: Token.asString(deadlineResource.getAtt('WindowsCertificateInstaller')), + }, + }; + + this.recipes = { + bucket: Bucket.fromBucketName(scope, 'RecipesBucket', Token.asString(deadlineResource.getAtt('S3Bucket'))), + linux: { + recipes: Token.asString(deadlineResource.getAtt('LinuxDockerRecipes')), + version: Token.asString(deadlineResource.getAtt('LinuxDeadlineDockerVersion')), + }, + }; + } +} diff --git a/packages/aws-rfdk/lib/deadline/lib/version-ref.ts b/packages/aws-rfdk/lib/deadline/lib/version-ref.ts index 3cedbfabe..82fcdaf72 100644 --- a/packages/aws-rfdk/lib/deadline/lib/version-ref.ts +++ b/packages/aws-rfdk/lib/deadline/lib/version-ref.ts @@ -3,46 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { IBucket } from '@aws-cdk/aws-s3'; - -/** - * This interface represents a deadline installer object stored on - * an S3 bucket. - */ -export interface Installer { - /** - * The S3 Bucket interface where the installer is located. - */ - readonly s3Bucket: IBucket; - - /** - * The object key where the installer file is located. - */ - readonly objectKey: string; -} - -/** - * This interface represents a collection of Deadline installer files for a specific Deadline version stored in S3. - */ -export interface PlatformInstallers { - /** - * The patch version for these Deadline installers. - * ex: If the installer is for version 10.1.8.5, then this will be 5. - */ - readonly patchVersion: number; - - /** - * The Deadline Repository installer for this platform, as extracted from the bundle on the Thinkbox download site. - * For example: - * - * - 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; -} - /** * Represents a release of Deadline up to and including the third (release) * component of the version. @@ -77,29 +37,3 @@ export interface IPatchVersion extends IReleaseVersion { */ readonly patchVersion: number; } - -/** - * This interface represents a deadline version. It contains the - * major, minor, and release numbers essential to identify - * a version. It also includes the S3 path of the installers. - * - * The Deadline version tag consists of four numbers: - * Major.Minor.Release.Patch - */ -export interface IVersion extends IReleaseVersion { - /** - * The Linux installers for this version. - * - * @default No installers for Linux are provided. - */ - 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; -} 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..3c7e76bcb 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*S3Bucket0442683B'), + Key: stringLike('AssetParameters*S3VersionKey0DF6BBA9'), +}; 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..e430d1ecd 100644 --- a/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts +++ b/packages/aws-rfdk/lib/deadline/test/render-queue.test.ts @@ -60,7 +60,7 @@ import { testConstructTags, } from '../../core/test/tag-helpers'; import { - IVersion, + IVersionedDeadlineInstallers, RenderQueue, RenderQueueImages, RenderQueueProps, @@ -80,7 +80,7 @@ describe('RenderQueue', () => { let images: RenderQueueImages; let repository: Repository; - let version: IVersion; + let versionedInstallers: IVersionedDeadlineInstallers; let renderQueueCommon: RenderQueue; @@ -89,15 +89,10 @@ 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, - }); + versionedInstallers = new VersionQuery(dependencyStack, 'VersionQuery'); repository = new Repository(dependencyStack, 'Repo', { - version, vpc, + versionedInstallers, }); stack = new Stack(app, 'Stack'); rcsImage = ContainerImage.fromAsset(__dirname); @@ -107,7 +102,6 @@ describe('RenderQueue', () => { renderQueueCommon = new RenderQueue(stack, 'RenderQueueCommon', { images, repository, - version, vpc, }); }); @@ -228,7 +222,6 @@ describe('RenderQueue', () => { new RenderQueue(isolatedStack, 'RenderQueue', { images, repository, - version, vpc, renderQueueSize: {}, }); @@ -248,7 +241,6 @@ describe('RenderQueue', () => { const props: RenderQueueProps = { images, repository, - version, vpc, renderQueueSize: { min, @@ -270,7 +262,6 @@ describe('RenderQueue', () => { const props: RenderQueueProps = { images, repository, - version, vpc, renderQueueSize: { min, @@ -302,7 +293,6 @@ describe('RenderQueue', () => { const props: RenderQueueProps = { images, repository, - version, vpc, renderQueueSize: { desired: 2, @@ -326,7 +316,6 @@ describe('RenderQueue', () => { const props: RenderQueueProps = { images, repository, - version, vpc, renderQueueSize: { desired, @@ -362,7 +351,6 @@ describe('RenderQueue', () => { const props: RenderQueueProps = { images, repository, - version, vpc, trafficEncryption: {}, }; @@ -400,7 +388,6 @@ describe('RenderQueue', () => { const props: RenderQueueProps = { images, repository, - version, vpc, trafficEncryption: { internalProtocol: ApplicationProtocol.HTTPS, @@ -527,7 +514,6 @@ describe('RenderQueue', () => { const props: RenderQueueProps = { images, repository, - version, vpc, trafficEncryption: { internalProtocol: ApplicationProtocol.HTTP, @@ -568,7 +554,6 @@ describe('RenderQueue', () => { const props: RenderQueueProps = { images, repository, - version, vpc, trafficEncryption: { externalTLS: { @@ -613,7 +598,6 @@ describe('RenderQueue', () => { const props: RenderQueueProps = { images, repository, - version, vpc, trafficEncryption: { externalTLS: { @@ -660,7 +644,6 @@ describe('RenderQueue', () => { const props: RenderQueueProps = { images, repository, - version, vpc, trafficEncryption: { externalTLS: { @@ -748,7 +731,6 @@ describe('RenderQueue', () => { const props: RenderQueueProps = { images, repository, - version, vpc, trafficEncryption: { externalTLS: { @@ -783,7 +765,6 @@ describe('RenderQueue', () => { const props: RenderQueueProps = { images, repository, - version, vpc, trafficEncryption: { externalTLS: { @@ -816,7 +797,6 @@ describe('RenderQueue', () => { const props: RenderQueueProps = { images, repository, - version, vpc, trafficEncryption: { externalTLS: { @@ -854,7 +834,6 @@ describe('RenderQueue', () => { const props: RenderQueueProps = { images, repository, - version, vpc, hostname: { zone, @@ -1283,7 +1262,6 @@ describe('RenderQueue', () => { const props: RenderQueueProps = { images, repository, - version, vpc, hostname: { zone, @@ -1696,7 +1674,6 @@ describe('RenderQueue', () => { const props: RenderQueueProps = { images, repository, - version, vpc, vpcSubnets: { subnets, @@ -1721,7 +1698,6 @@ describe('RenderQueue', () => { images, instanceType: InstanceType.of(InstanceClass.C5, InstanceSize.LARGE), repository, - version, vpc, }; const isolatedStack = new Stack(app, 'IsolatedStack'); @@ -1740,7 +1716,6 @@ describe('RenderQueue', () => { const props: RenderQueueProps = { images, repository, - version, vpc, deletionProtection: false, }; @@ -1767,7 +1742,6 @@ describe('RenderQueue', () => { const props: RenderQueueProps = { images, repository, - version, vpc, }; const isolatedStack = new Stack(app, 'IsolatedStack'); @@ -1799,7 +1773,6 @@ describe('RenderQueue', () => { const props: RenderQueueProps = { images, repository, - version, vpc, }; @@ -1828,7 +1801,6 @@ describe('RenderQueue', () => { const props: RenderQueueProps = { images, repository, - version, vpc, hostname: { zone, @@ -1875,7 +1847,6 @@ describe('RenderQueue', () => { const props: RenderQueueProps = { images, repository, - version, vpc, hostname: { hostname, @@ -1897,7 +1868,7 @@ describe('RenderQueue', () => { let isolatedStack: Stack; let isolatedVpc: Vpc; let isolatedRepository: Repository; - let isolatedVersion: IVersion; + let isolatedVersion: IVersionedDeadlineInstallers; let isolatedimages: RenderQueueImages; let accessBucket: Bucket; @@ -1910,15 +1881,10 @@ describe('RenderQueue', () => { }, }); isolatedVpc = new Vpc(isolatedStack, 'Vpc'); - isolatedVersion = VersionQuery.exact(isolatedStack, 'Version', { - majorVersion: 10, - minorVersion: 1, - releaseVersion: 9, - patchVersion: 1, - }); + isolatedVersion = new VersionQuery(isolatedStack, 'VersionQuery'); isolatedRepository = new Repository(isolatedStack, 'Repo', { - version: isolatedVersion, + versionedInstallers: isolatedVersion, vpc: isolatedVpc, }); @@ -1935,7 +1901,6 @@ describe('RenderQueue', () => { const props: RenderQueueProps = { images: isolatedimages, repository: isolatedRepository, - version: isolatedVersion, vpc: isolatedVpc, accessLogs: { destinationBucket: accessBucket, @@ -2055,7 +2020,6 @@ describe('RenderQueue', () => { const props: RenderQueueProps = { images: isolatedimages, repository: isolatedRepository, - version: isolatedVersion, vpc: isolatedVpc, accessLogs: { destinationBucket: accessBucket, diff --git a/packages/aws-rfdk/lib/deadline/test/repository.test.ts b/packages/aws-rfdk/lib/deadline/test/repository.test.ts index b6c151d5a..25cce800f 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, @@ -48,9 +47,8 @@ import { } from '../../core/test/tag-helpers'; import { DatabaseConnection, - IVersion, + IVersionedDeadlineInstallers, 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 versionedInstallers: IVersionedDeadlineInstallers; function escapeTokenRegex(s: string): string { // A CDK Token looks like: ${Token[TOKEN.12]} @@ -72,26 +70,44 @@ beforeEach(() => { app = new App(); stack = new Stack(app, 'Stack'); vpc = new Vpc(stack, 'VPC'); - deadlineVersion = VersionQuery.exact(stack, 'Version', { + versionedInstallers = { majorVersion: 10, minorVersion: 1, releaseVersion: 9, - patchVersion: 2, - }); + installers: { + bucket: Bucket.fromBucketName(stack, 'InstallersBucket', 'TestBucket'), + linux: { + version: '10.1.9.2', + bundle: 's3://bundle/test', + clientInstaller: 's3://client/test', + repositoryInstaller: 's3://repo/test', + }, + mac: { + version: '10.1.9.2', + bundle: 's3://bundle/test', + }, + windows: { + version: '10.1.9.2', + bundle: 's3://bundle/test', + clientInstaller: 's3://client/test', + repositoryInstaller: 's3://repo/test', + }, + }, + }; }); test('can create two repositories', () => { // GIVEN new Repository(stack, 'Repo1', { vpc, - version: deadlineVersion, + versionedInstallers, }); // THEN expect(() => { new Repository(stack, 'Repo2', { vpc, - version: deadlineVersion, + versionedInstallers, }); }).not.toThrow(); }); @@ -100,7 +116,7 @@ test('repository installer instance is created correctly', () => { // WHEN new Repository(stack, 'repositoryInstaller', { vpc, - version: deadlineVersion, + versionedInstallers, }); // THEN @@ -173,7 +189,7 @@ test('repository installer honors vpcSubnet', () => { }); new Repository(stack, 'repositoryInstaller', { vpc: attrVpc, - version: deadlineVersion, + versionedInstallers, vpcSubnets: { subnetType: SubnetType.PUBLIC }, }); @@ -187,7 +203,7 @@ test('repository installer security groups created correctly', () => { // WHEN new Repository(stack, 'repositoryInstaller', { vpc, - version: deadlineVersion, + versionedInstallers, }); // THEN @@ -270,7 +286,7 @@ test('repository installer iam permissions: db secret access', () => { // WHEN new Repository(stack, 'repositoryInstaller', { vpc, - version: deadlineVersion, + versionedInstallers, }); // THEN @@ -305,7 +321,7 @@ test('repository installer iam permissions: installer get', () => { // WHEN new Repository(stack, 'repositoryInstaller', { vpc, - version: deadlineVersion, + versionedInstallers, }); // THEN @@ -328,7 +344,10 @@ test('repository installer iam permissions: installer get', () => { { Ref: 'AWS::Partition', }, - ':s3:::thinkbox-installers', + ':s3:::', + { + Ref: stringLike('AssetParameters*S3Bucket352E624B'), + }, ], ], }, @@ -340,7 +359,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 +378,7 @@ test('default repository installer log group created correctly', () => { // WHEN new Repository(stack, 'repositoryInstaller', { vpc, - version: deadlineVersion, + versionedInstallers, }); // THEN @@ -369,7 +392,7 @@ test('repository installer logs all required files', () => { // WHEN new Repository(stack, 'repositoryInstaller', { vpc, - version: deadlineVersion, + versionedInstallers, }); // THEN @@ -397,7 +420,7 @@ test('repository mounts repository filesystem', () => { // GIVEN const repo = new Repository(stack, 'repositoryInstaller', { vpc, - version: deadlineVersion, + versionedInstallers, }); // WHEN @@ -415,7 +438,7 @@ test.each([ // WHEN new Repository(stack, 'repositoryInstaller', { vpc, - version: deadlineVersion, + versionedInstallers, removalPolicy: { database: policy, }, @@ -435,7 +458,7 @@ test.each([ // WHEN new Repository(stack, 'repositoryInstaller', { vpc, - version: deadlineVersion, + versionedInstallers, removalPolicy: { filesystem: policy, }, @@ -460,7 +483,7 @@ test('repository warns if removal policy for filesystem when filesystem provided const repo = new Repository(stack, 'repositoryInstaller', { vpc, fileSystem: testFS, - version: deadlineVersion, + versionedInstallers, removalPolicy: { filesystem: RemovalPolicy.DESTROY, }, @@ -500,7 +523,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, + versionedInstallers, removalPolicy: { database: RemovalPolicy.DESTROY, }, @@ -529,7 +552,7 @@ test('repository creates deadlineDatabase if none provided', () => { new Repository(stack, 'repositoryInstaller', { vpc, fileSystem: testFS, - version: deadlineVersion, + versionedInstallers, }); // THEN @@ -560,7 +583,7 @@ test('disabling Audit logging does not enable Cloudwatch audit logs', () => { new Repository(stack, 'repositoryInstaller', { vpc, fileSystem: testFS, - version: deadlineVersion, + versionedInstallers, databaseAuditLogging: false, }); @@ -598,7 +621,7 @@ test('repository warns if databaseAuditLogging defined and database is specified // WHEN const repo = new Repository(stack, 'repositoryInstaller', { vpc, - version: deadlineVersion, + versionedInstallers, removalPolicy: { filesystem: RemovalPolicy.DESTROY, }, @@ -611,7 +634,7 @@ test('repository warns if databaseAuditLogging defined and database is specified expect.arrayContaining([ expect.objectContaining({ type: 'aws:cdk:warning', - data: `The parameter databaseAuditLogging only has an effect when the Repository is creating its own database. + 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.`, }), ]), @@ -638,7 +661,7 @@ test('honors subnet specification', () => { // WHEN new Repository(isolatedStack, 'repositoryInstaller', { vpc: dependencyVpc, - version: deadlineVersion, + versionedInstallers, vpcSubnets: { subnets, }, @@ -662,7 +685,7 @@ test('repository honors database instance count', () => { // WHEN new Repository(stack, 'repositoryInstaller', { vpc, - version: deadlineVersion, + versionedInstallers, documentDbInstanceCount: instanceCount, }); @@ -679,7 +702,7 @@ test('repository honors database retention period', () => { // WHEN new Repository(stack, 'repositoryInstaller', { vpc, - version: deadlineVersion, + versionedInstallers, backupOptions: { databaseRetention: Duration.days(period), }, @@ -714,7 +737,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, + versionedInstallers, backupOptions: { databaseRetention: Duration.days(20), }, @@ -761,7 +784,7 @@ test('repository creates filesystem if none provided', () => { new Repository(stack, 'repositoryInstaller', { vpc, database: DatabaseConnection.forDocDB({ database: fsDatabase, login: fsDatabase.secret }), - version: deadlineVersion, + versionedInstallers, }); // THEN @@ -772,7 +795,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, + versionedInstallers, repositoryInstallationPrefix: 'xyz', }); @@ -784,7 +807,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, + versionedInstallers, repositoryInstallationPrefix: '/xyz//', }); @@ -797,7 +820,7 @@ test('repository instance is created with user defined timeout', () => { new Repository(stack, 'repositoryInstaller', { vpc, repositoryInstallationTimeout: Duration.minutes(30), - version: deadlineVersion, + versionedInstallers, }); // THEN @@ -817,7 +840,7 @@ test('repository instance is created with correct installer path version', () => const repo = new Repository(stack, 'repositoryInstaller', { vpc, repositoryInstallationTimeout: Duration.minutes(30), - version: deadlineVersion, + versionedInstallers, }); // THEN @@ -835,7 +858,7 @@ test.each([ // WHEN new Repository(stack, id, { vpc, - version: deadlineVersion, + versionedInstallers, logGroupProps: { logGroupPrefix: testPrefix, }, @@ -852,7 +875,7 @@ test('validate instance self-termination', () => { const repo = new Repository(stack, 'repositoryInstaller', { vpc, repositoryInstallationTimeout: Duration.minutes(30), - version: deadlineVersion, + versionedInstallers, }); const asgLogicalId = stack.getLogicalId(repo.node.defaultChild!.node.defaultChild as CfnElement); @@ -887,7 +910,7 @@ test('repository configure client instance', () => { const repo = new Repository(stack, 'repositoryInstaller', { vpc, repositoryInstallationTimeout: Duration.minutes(30), - version: deadlineVersion, + versionedInstallers, }); const instance = new Instance(stack, 'Instance', { vpc, @@ -921,7 +944,7 @@ test('configureClientInstance uses singleton for repo config script', () => { const repo = new Repository(stack, 'repositoryInstaller', { vpc, repositoryInstallationTimeout: Duration.minutes(30), - version: deadlineVersion, + versionedInstallers, }); const instance1 = new Instance(stack, 'Instance1', { vpc, @@ -997,56 +1020,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, + versionedInstallers, }); const instance = new Instance(stack, 'Instance', { vpc, @@ -1071,7 +1050,7 @@ describe('tagging', () => { const isolatedStack = new Stack(app, 'IsolatedStack'); new Repository(isolatedStack, 'Repository', { vpc, - version: deadlineVersion, + versionedInstallers, }); 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..20465c44b 100644 --- a/packages/aws-rfdk/lib/deadline/test/stage.test.ts +++ b/packages/aws-rfdk/lib/deadline/test/stage.test.ts @@ -379,35 +379,27 @@ describe('Stage', () => { }); }); - describe('.getVersion()', () => { - test('returns a version in the success case', () => { - // GIVEN - const app = new App(); - const stack = new Stack(app, 'Stack'); - const manifest: Manifest = { - schema: 1, - version: '10.1.9.2', - recipes: {}, - }; - const stage = new StageWithPulicConstructor({ - manifest, - path: STAGE_PATH, - }); - - // 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'); - }); - }); + // describe('.getVersion()', () => { + // test('returns a version in the success case', () => { + // // GIVEN + // const app = new App(); + // const stack = new Stack(app, 'Stack'); + // const manifest: Manifest = { + // schema: 1, + // version: '10.1.9.2', + // recipes: {}, + // }; + // const stage = new StageWithPulicConstructor({ + // manifest, + // path: STAGE_PATH, + // }); + + // // WHEN + // const version = stage.getVersion(stack, 'Version'); + // const linuxFullVersionString = version.linuxFullVersionString(); + + // expect(linuxFullVersionString).toBeDefined(); + // expect(linuxFullVersionString).toEqual('10.1.9.2'); + // }); + // }); }); 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..720e762a1 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 @@ -13,7 +13,6 @@ import { import { DeadlineDockerRecipes, - IVersion, Manifest, Recipe, Stage, @@ -129,41 +128,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('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`); - }); - }); + // describe('provides the Deadline version with', () => { + // test('version gets created attribute', () => { + // const recipes = new ThinkboxDockerRecipes(stack, 'Recipes', { + // stage, + // }); + // const version = recipes.versionQuery; + // // THEN + // expect(version.version).not.toBeNull(); + // }); + // }); test.each([ ['rcs', { 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 3710a1e6c..3efffeea3 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 @@ -41,7 +41,7 @@ import { testConstructTags, } from '../../core/test/tag-helpers'; import { - IVersion, + IVersionedDeadlineInstallers, IWorkerFleet, RenderQueue, Repository, @@ -57,7 +57,7 @@ const env = { }; let app: App; let certificateSecret: ISecret; -let deadlineVersion: IVersion; +let versionedInstallers: IVersionedDeadlineInstallers; let dependencyStack: Stack; let dockerContainer: DockerImageAsset; let images: UsageBasedLicensingImages; @@ -77,26 +77,18 @@ 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, + versionedInstallers, }), }); 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..015f300ef --- /dev/null +++ b/packages/aws-rfdk/lib/deadline/test/version-query.test.ts @@ -0,0 +1,116 @@ +/** + * 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', () => { + const app = new App(); + const stack = new Stack(app, 'Stack'); + new VersionQuery(stack, 'VersionQuery', { version: '10.1.9'}); + + expectCDK(stack).to(haveResourceLike('Custom::RFDK_DEADLINE_INSTALLERS', { + versionString: '10.1.9', + })); + 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', { + versionString: ABSENT, + })); +}); diff --git a/packages/aws-rfdk/lib/deadline/test/version.test.ts b/packages/aws-rfdk/lib/deadline/test/version.test.ts index 3c8c0eeec..6fda7c171 100644 --- a/packages/aws-rfdk/lib/deadline/test/version.test.ts +++ b/packages/aws-rfdk/lib/deadline/test/version.test.ts @@ -3,22 +3,20 @@ * SPDX-License-Identifier: Apache-2.0 */ +/* eslint-disable dot-notation */ + +// import { +// expect as expectCDK, +// haveResource, +// haveResourceLike, +// } from '@aws-cdk/assert'; +// import { +// Stack, +// } from '@aws-cdk/core'; 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 +162,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 +189,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 +236,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..51618d5d9 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, @@ -42,6 +42,7 @@ import { } from '../../core/test/tag-helpers'; import { IRenderQueue, + IVersionedDeadlineInstallers, RenderQueue, Repository, Version, @@ -70,19 +71,13 @@ 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: IVersionedDeadlineInstallers = new VersionQuery(stack, 'VersionQuery'); renderQueue = new RenderQueue(stack, 'RQ', { - version, vpc, images: { remoteConnectionServer: rcsImage }, repository: new Repository(stack, 'Repository', { vpc, - version, + versionedInstallers: version, }), }); wfstack = new Stack(app, 'workerFleetStack', {