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 all 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
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
14 changes: 14 additions & 0 deletions packages/@aws-cdk/cx-api/lib/cxapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,20 @@ export const DEFAULT_ACCOUNT_ENV = 'CDK_DEFAULT_ACCOUNT';
*/
export const DEFAULT_REGION_ENV = 'CDK_DEFAULT_REGION';

/**
* Version of Cloud Assembly expected by CDK CLI.
*
* CLI started emitting this at 1.10.1
*/
export const CLI_ASM_VERSION_ENV = 'CDK_CLI_ASM_VERSION';

/**
* Version of the CLI currently running.
*
* CLI started emitting this at 1.10.1
*/
export const CLI_VERSION_ENV = 'CDK_CLI_VERSION';

/**
* Enables the embedding of the "aws:cdk:path" in CloudFormation template metadata.
*/
Expand Down
60 changes: 50 additions & 10 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,29 @@ 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}`);
throw new Error(`The CDK CLI you are using requires your app to use CDK modules with version >= ${CLOUD_ASSEMBLY_VERSION}`);
}
}

/**
* 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 +75,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 });
}
12 changes: 10 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,7 @@ 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`);
// tslint:disable-next-line:max-line-length
expect(() => verifyManifestVersion('0.31.0')).toThrow(`The CDK CLI you are using requires your app to use CDK modules with version >= ${CLOUD_ASSEMBLY_VERSION}`);
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"
}
}
}
5 changes: 5 additions & 0 deletions packages/aws-cdk/lib/api/cxapp/exec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import fs = require('fs-extra');
import path = require('path');
import { debug } from '../../logging';
import { Configuration, PROJECT_CONFIG, USER_DEFAULTS } from '../../settings';
import { versionNumber } from '../../version';
import { ISDK } from '../util/sdk';

/** Invokes the cloud executable and returns JSON output */
Expand Down Expand Up @@ -73,6 +74,10 @@ export async function execProgram(aws: ISDK, config: Configuration): Promise<cxa
debug('outdir:', outdir);
env[cxapi.OUTDIR_ENV] = outdir;

// Send version information
env[cxapi.CLI_ASM_VERSION_ENV] = cxapi.CLOUD_ASSEMBLY_VERSION;
Copy link
Contributor

Choose a reason for hiding this comment

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

Curious why through environment variables and not through context... not that I have a strong opinion...

env[cxapi.CLI_VERSION_ENV] = versionNumber();

debug('env:', env);

await exec();
Expand Down
2 changes: 1 addition & 1 deletion packages/aws-cdk/lib/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const exec = promisify(_exec);

export const DISPLAY_VERSION = `${versionNumber()} (build ${commit()})`;

function versionNumber(): string {
export function versionNumber(): string {
return require('../package.json').version.replace(/\+[0-9a-f]+$/, '');
}

Expand Down