Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(deadline): adding version check for staging deadline #109

Merged
merged 17 commits into from
Sep 17, 2020
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion packages/aws-rfdk/lib/deadline/lib/stage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import * as path from 'path';
import { DockerImageAsset } from '@aws-cdk/aws-ecr-assets';
import { Construct } from '@aws-cdk/core';

import { VersionQuery } from './version';
import {
Version,
VersionQuery,
} from './version';

/**
* Build arguments to supply to a Docker image build
Expand Down Expand Up @@ -99,6 +102,8 @@ export interface StageProps {
* requires to deploy Deadline. It should contain a manifest file, the Deadline
* installers, and any supporting files required for building the Deadline
* container.
*
* Note: Current version of RFDK supports Deadline v10.1.9.2 and later.
*/
export class Stage {
/**
Expand Down Expand Up @@ -154,6 +159,12 @@ export class Stage {
throw new TypeError(`Expected a string "version" but got: ${typeof version}`);
}

// Do minimum supported deadline version check
const stagedVersion = Version.parse(version);
if (stagedVersion.isLessThan(Version.MINIMUM_SUPPORTED_DEADLINE_VERSION)) {
throw new TypeError(`Staged Deadline Version (${version}) is less than the minimum supported version (${Version.MINIMUM_SUPPORTED_DEADLINE_VERSION.toString()})`);
}

return true;
}

Expand Down
182 changes: 153 additions & 29 deletions packages/aws-rfdk/lib/deadline/lib/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,158 @@ abstract class VersionQueryBase extends Construct implements IVersion {
}
}

/**
* This class is reposonsible to do basic operations on version format.
*/
export class Version implements IPatchVersion {

/**
* This variable holds the value for minimum supported deadline version.
*/
public static readonly MINIMUM_SUPPORTED_DEADLINE_VERSION = new Version([10, 1, 9, 2]);

/**
* This method parses the input string and returns the version object.
*
* @param version version string to parse
*/
public static parse(version: string): Version {
if (!Version.validateVersionFormat(version)) {
throw new TypeError(`Invalid version format. Expected format 'a.b.c.d', found '${version}'`);
}

return new Version(version.split('.').map(x => parseInt(x)));
}

/**
* This method validates the given string for a sequence '.' separated numbers.
*
* @param version the string to be validated.
*
* @returns true if the format is correct, else false.
*/
private static validateVersionFormat(version: string): boolean {
/**
* Regex: ^\d+(?:\.\d+){3}$
* Matches a sequence of '.' separated numbers with exactly 4 digits.
* - ^ asserts position at start of a line.
* - \d+ Matches one or more digits.
* - (?:\.\d+) Matches a dot and the following one or more digits.
* - {3} Matches previous pattern exactly 3 times.
* - $ asserts position at the end of a line
*/
if (version.match(/^\d+(?:\.\d+){3}$/)) {
return true;
}
return false;
}

/**
* Numeric components of version.
*/
private readonly components: number[];

/**
* @inheritdoc
*/
public get majorVersion(): number {
return this.components[0];
}

/**
* @inheritdoc
*/
public get minorVersion(): number {
return this.components[1];
}

/**
* @inheritdoc
*/
public get releaseVersion(): number {
return this.components[2];
}

/**
* @inheritdoc
*/
public get patchVersion(): number {
return this.components[3];
}

constructor(components: number[]) {
// validations
if(components.length != 4) {
throw new Error('Invalid version format. Version should contain exactly 4 components.');
}
components.forEach((component) => {
if (component < 0) {
throw new RangeError('Invalid version format. None of the version components can be negative.');
}
});

this.components = components;
jusiskin marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* This method compares two version strings
*
* @param version
*
* @returns true if this version is greater than the provided version;
* false if this version is less than or equal to the provided verison.
*/
public isGreaterThan(version: Version): boolean {
if (this.components.length != version.components.length) {
ryyakobe marked this conversation as resolved.
Show resolved Hide resolved
throw new TypeError('Component count in both the versions should be same.');
}

return this.compare(version) > 0;
}

/**
* This method compares two version strings
*
* @param version
*
* @returns true if this version is less than the provided version;
* false if this version is greater than or equal to the provided verison.
*/
public isLessThan(version: Version): boolean {
ryyakobe marked this conversation as resolved.
Show resolved Hide resolved
if (this.components.length != version.components.length) {
throw new TypeError('Component count in both the versions should be same.');
}

return this.compare(version) < 0;
}

/**
* The method returns the version components in dot separated string format.
*/
public toString(): string {
return this.components.join('.');
}

/**
* This method compares 2 versions.
*
* @param version version to compare
*
* @returns negative value if this version is less than the provided version;
* 0 if both the versions are equal;
* positive value if this version is greater than the provided verison.
*/
private compare(version: Version): number {
ryyakobe marked this conversation as resolved.
Show resolved Hide resolved
for (let i = 0; i < version.components.length; i++) {
const diff = this.components[i] - version.components[i];
if (diff != 0) {
return diff;
}
}
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.
Expand All @@ -90,29 +242,6 @@ abstract class VersionQueryBase extends Construct implements IVersion {
* constructs which version of Deadline you want them to use, and be configured for.
*/
export class VersionQuery extends VersionQueryBase {
/**
* Parses a version string of the format:
*
* <major>.<minor>.<release>.<patch>
*
* and extracts the components.
*
* @param versionstr The input version string
*/
public static parseVersionString(versionstr: string): IPatchVersion {
const match = VersionQuery.RE_FULL_VERSION.exec(versionstr);
if (!match) {
throw new Error(`"${versionstr}" is not a valid version`);
}

return {
majorVersion: parseInt(match.groups!.major, 10),
minorVersion: parseInt(match.groups!.minor, 10),
releaseVersion: parseInt(match.groups!.release, 10),
patchVersion: parseInt(match.groups!.patch, 10),
};
}

/**
* Specify a Deadline version from a fully-qualified Deadline patch version.
*
Expand Down Expand Up @@ -188,14 +317,9 @@ export class VersionQuery extends VersionQueryBase {
* @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, VersionQuery.parseVersionString(versionString));
return VersionQuery.exact(scope, id, Version.parse(versionString));
}

/**
* Regular expression for matching a Deadline release version number
*/
private static readonly RE_FULL_VERSION = /^(?<major>\d+)\.(?<minor>\d+)\.(?<release>\d+)\.(?<patch>\d+)$/;

/**
* @inheritdoc
*/
Expand Down
70 changes: 57 additions & 13 deletions packages/aws-rfdk/lib/deadline/test/stage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ describe('Stage', () => {
// GIVEN
const manifest: Manifest = {
schema: 1,
version: '1.2.3.4',
version: '10.1.9.2',
recipes: {},
};

Expand Down Expand Up @@ -77,10 +77,34 @@ describe('Stage', () => {
});
});

describe('.fromDirectory', () => {
test('not supported version failure', () => {
// GIVEN
const manifest: Manifest = {
schema: 1,
version: '10.1.8.0',
recipes: {},
};

// WHEN
const readFileSync = jest.spyOn(fs, 'readFileSync');
readFileSync.mockReturnValue(JSON.stringify(manifest));

// THEN
expect(() => {
require('../lib').Stage.fromDirectory(STAGE_PATH) // eslint-disable-line
}).toThrow('Staged Deadline Version (10.1.8.0) is less than the minimum supported version (10.1.9.2)');

readFileSync.mockRestore();
jest.resetModules();
});

});

test('has manifest', () => {
const manifest: Manifest = {
schema: 1,
version: '1.2.3.4',
version: '10.1.9.2',
recipes: {
a: {
title: 'a-title',
Expand Down Expand Up @@ -131,7 +155,7 @@ describe('Stage', () => {
'missing schema',
{
manifest: {
version: '1.2.3.4',
version: '10.1.9.2',
recipes: {},
},
expectedError: /Manifest contains no "schema" key/,
Expand All @@ -141,7 +165,7 @@ describe('Stage', () => {
'wrong schema type', {
manifest: {
schema: 'a',
version: '1.2.3.4',
version: '10.1.9.2',
recipes: {},
},
expectedError: /Expected a numeric "schema" but got:/,
Expand All @@ -156,6 +180,26 @@ describe('Stage', () => {
expectedError: /Manifest contains no "version" key/,
},
],
[
'incorrect version format', {
manifest: {
schema: 1,
version: '10.1.',
recipes: {},
},
expectedError: /Invalid version format/,
},
],
[
'version not supported', {
manifest: {
schema: 1,
version: '10.1.0.0',
recipes: {},
},
expectedError: 'Staged Deadline Version (10.1.0.0) is less than the minimum supported version (10.1.9.2)',
},
],
])('%s', (_name, testcase) => {
const { manifest, expectedError } = testcase;
// WHEN
Expand Down Expand Up @@ -225,7 +269,7 @@ describe('Stage', () => {
recipeName: recipe,
},
schema: 1,
version: '1.2.3.4',
version: '10.1.9.2',
};

// WHEN
Expand Down Expand Up @@ -287,7 +331,7 @@ describe('Stage', () => {
const stage = new ReloadedStageWithPublicConstructor({
path: STAGE_PATH,
manifest: {
version: '1.2.3.4',
version: '10.1.9.2',
schema: 1,
recipes: {
[recipeName]: recipe,
Expand Down Expand Up @@ -316,7 +360,7 @@ describe('Stage', () => {
// GIVEN
const manifest: Manifest = {
schema: 1,
version: '1.2.3.4',
version: '10.1.9.2',
recipes: {},
};
const invalidRecipeName = 'this-recipe-does-not-exist';
Expand All @@ -342,7 +386,7 @@ describe('Stage', () => {
const stack = new Stack(app, 'Stack');
const manifest: Manifest = {
schema: 1,
version: '1.2.3.4',
version: '10.1.9.2',
recipes: {},
};
const stage = new StageWithPulicConstructor({
Expand All @@ -355,15 +399,15 @@ describe('Stage', () => {
const linuxFullVersionString = version.linuxFullVersionString();

// THEN
expect(version.majorVersion).toEqual(1);
expect(version.minorVersion).toEqual(2);
expect(version.releaseVersion).toEqual(3);
expect(version.majorVersion).toEqual(10);
expect(version.minorVersion).toEqual(1);
expect(version.releaseVersion).toEqual(9);

expect(version.linuxInstallers).toBeDefined();
expect(version.linuxInstallers?.patchVersion).toEqual(4);
expect(version.linuxInstallers?.patchVersion).toEqual(2);

expect(linuxFullVersionString).toBeDefined();
expect(linuxFullVersionString).toEqual('1.2.3.4');
expect(linuxFullVersionString).toEqual('10.1.9.2');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ describe('ThinkboxDockerRecipes', () => {

const MAJOR_VERSION = 10;
const MINOR_VERSION = 1;
const RELEASE_VERSION = 8;
const PATCH_VERSION = 5;
const RELEASE_VERSION = 9;
const PATCH_VERSION = 2;
const FULL_VERSION_STRING = `${MAJOR_VERSION}.${MINOR_VERSION}.${RELEASE_VERSION}.${PATCH_VERSION}`;

beforeEach(() => {
Expand Down
Loading