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(cli): make new CLI recognize old assembly versions #4307

Merged
merged 3 commits into from
Oct 1, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 4 additions & 2 deletions packages/@aws-cdk/cx-api/lib/cloud-assembly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ArtifactManifest, ArtifactType, CloudArtifact } from './cloud-artifact'
import { CloudFormationStackArtifact } from './cloudformation-artifact';
import { topologicalSort } from './toposort';
import { TreeCloudArtifact } from './tree-cloud-artifact';
import { CLOUD_ASSEMBLY_VERSION, verifyManifestVersion } from './versioning';
import { CLOUD_ASSEMBLY_VERSION, upgradeAssemblyManifest, verifyManifestVersion } from './versioning';

/**
* A manifest which describes the cloud assembly.
Expand Down Expand Up @@ -73,7 +73,9 @@ export class CloudAssembly {
*/
constructor(directory: string) {
this.directory = directory;
this.manifest = JSON.parse(fs.readFileSync(path.join(directory, MANIFEST_FILE), 'UTF-8'));

const manifest = JSON.parse(fs.readFileSync(path.join(directory, MANIFEST_FILE), 'UTF-8'));
this.manifest = upgradeAssemblyManifest(manifest);

this.version = this.manifest.version;
verifyManifestVersion(this.version);
Expand Down
59 changes: 50 additions & 9 deletions packages/@aws-cdk/cx-api/lib/versioning.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,24 @@
import semver = require('semver');
import { AssemblyManifest } from './cloud-assembly';

// ----------------------------------------------------------------------
//
// READ THIS FIRST WHEN CHANGING THIS FILE
//
// ----------------------------------------------------------------------
//
// You need (and only need) to bump the CLOUD_ASSEMBLY_VERSION if the cloud
// assembly needs new features from the CDK CLI. Examples: new fields, new
// behavior, new artifact types.
//
// If that happens, you set the CLOUD_ASSEMBLY_VERSION to the *next* (not the
// current!) CDK version that will be released. This is done to produce
// useful error messages.
//
// When you do this, you will force users of a new library to upgrade the CLI
// (good), but UNLESS YOU ALSO IMPLEMENT 'upgradeAssemblyManifest' you will also
// force people who have installed a newer CLI to upgrade their libraries (bad!).
// Do that too, unless you have a very good reason not to.

/**
* Bump this to the library version if and only if the CX protocol changes.
Expand All @@ -8,13 +28,8 @@ import semver = require('semver');
* might as well use the software version as protocol version and immediately
* generate a useful error message from this.
*
* Note the following:
*
* - The versions are not compared in a semver way, they are used as
* opaque ordered tokens.
* - The version needs to be set to the NEXT releasable version when it's
* updated (as the current verison in package.json has already been released!)
* - The request does not have versioning yet, only the response.
* Note that the versions are not compared in a semver way, they are used as
* opaque ordered tokens.
*/
export const CLOUD_ASSEMBLY_VERSION = '1.10.0';

Expand All @@ -27,14 +42,30 @@ export function verifyManifestVersion(manifetVersion: string) {

// if framework > cli, we require a newer cli version
if (semver.gt(frameworkVersion, toolkitVersion)) {
throw new Error(`CDK CLI >= ${frameworkVersion} is required to interact with this app`);
throw new Error(`A newer version of the CDK CLI (>= ${frameworkVersion}) is necessary to interact with this app`);
}

// if framework < cli, we require a newer framework version
if (semver.lt(frameworkVersion, toolkitVersion)) {
throw new Error(
`CDK CLI can only be used with apps created by CDK >= ${CLOUD_ASSEMBLY_VERSION}`);
`A newer version of the CDK framework (>= ${CLOUD_ASSEMBLY_VERSION}) is necessary to interact with this version of the CLI`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe something like “the CDK CLI you are using requires your CDK app to use CDK modules with version >= xxx”

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😭

}
}

/**
* Upgrade old manifest versions to later manifest version here (if possible).
*
* Use this to make the toolkit recognize old assembly versions. This function should
* add newly required fields with appropriate default values, etc.
*/
export function upgradeAssemblyManifest(manifest: AssemblyManifest): AssemblyManifest {

if (manifest.version === '0.36.0') {
// Adding a new artifact type, old version will not have it so painless upgrade.
manifest = justUpgradeVersion(manifest, '1.10.0');
}

return manifest;
}

function parseSemver(version: string) {
Expand All @@ -45,3 +76,13 @@ function parseSemver(version: string) {

return ver;
}

/**
* Return a copy of the manifest with just the version field updated
*
* Useful if there are protocol changes that are automatically backwards
* compatible.
*/
function justUpgradeVersion(manifest: AssemblyManifest, version: string): AssemblyManifest {
return Object.assign({}, manifest, { version });
}
11 changes: 9 additions & 2 deletions packages/@aws-cdk/cx-api/test/cloud-assembly.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,13 @@ test('assets', () => {
expect(assembly.stacks[0].assets).toMatchSnapshot();
});

test('can-read-0.36.0', () => {
// WHEN
new CloudAssembly(path.join(FIXTURES, 'single-stack-0.36'));
// THEN: no eexception
expect(true).toBeTruthy();
});

test('dependencies', () => {
const assembly = new CloudAssembly(path.join(FIXTURES, 'depends'));
expect(assembly.stacks).toHaveLength(4);
Expand All @@ -106,6 +113,6 @@ test('fails for invalid dependencies', () => {

test('verifyManifestVersion', () => {
verifyManifestVersion(CLOUD_ASSEMBLY_VERSION);
expect(() => verifyManifestVersion('0.31.0')).toThrow(`CDK CLI can only be used with apps created by CDK >= ${CLOUD_ASSEMBLY_VERSION}`);
expect(() => verifyManifestVersion('99.99.99')).toThrow(`CDK CLI >= 99.99.99 is required to interact with this app`);
expect(() => verifyManifestVersion('0.31.0')).toThrow(`A newer version of the CDK framework (>= ${CLOUD_ASSEMBLY_VERSION}) is necessary to interact with this version of the CLI`);
expect(() => verifyManifestVersion('99.99.99')).toThrow(`A newer version of the CDK CLI (>= 99.99.99) is necessary to interact with this app`);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"version": "1.10.0",
"artifacts": {
"Tree": {
"type": "cdk:tree",
"properties": {
"file": "foo.tree.json"
}
},
"MyStackName": {
"type": "aws:cloudformation:stack",
"environment": "aws://37736633/us-region-1",
"properties": {
"templateFile": "template.json"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"Resources": {
"MyBucket": {
"Type": "AWS::S3::Bucket"
}
}
}