From db4e718872569f8aa467cebd487484637001052d Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Thu, 25 Oct 2018 12:41:43 +0200 Subject: [PATCH] feat(aws-cdk): add CDK app version negotiation (#988) Tag the CDK app output with a version, so that the Toolkit can compare the sent and expected versions and complain if there's a mismatch. Fixes #891. --- packages/@aws-cdk/cdk/lib/app.ts | 1 + packages/@aws-cdk/cdk/test/test.app.ts | 4 +++- packages/@aws-cdk/cx-api/lib/cxapi.ts | 22 +++++++++++++++++ packages/aws-cdk/bin/cdk.ts | 33 +++++++++++++++++++++++++- packages/aws-cdk/package.json | 2 ++ 5 files changed, 60 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/cdk/lib/app.ts b/packages/@aws-cdk/cdk/lib/app.ts index ee861781761f0..f3e7c6a90ff8a 100644 --- a/packages/@aws-cdk/cdk/lib/app.ts +++ b/packages/@aws-cdk/cdk/lib/app.ts @@ -43,6 +43,7 @@ export class App extends Root { } const result: cxapi.SynthesizeResponse = { + version: cxapi.PROTO_RESPONSE_VERSION, stacks: this.synthesizeStacks(Object.keys(this.stacks)), runtime: this.collectRuntimeInformation() }; diff --git a/packages/@aws-cdk/cdk/test/test.app.ts b/packages/@aws-cdk/cdk/test/test.app.ts index 9b8d7325e1203..177ac8682219d 100644 --- a/packages/@aws-cdk/cdk/test/test.app.ts +++ b/packages/@aws-cdk/cdk/test/test.app.ts @@ -69,7 +69,9 @@ export = { response.stacks.forEach(s => delete s.metadata); delete response.runtime; - test.deepEqual(response, { stacks: + test.deepEqual(response, { + version: '0.14.0', + stacks: [ { name: 'stack1', environment: { name: '12345/us-east-1', diff --git a/packages/@aws-cdk/cx-api/lib/cxapi.ts b/packages/@aws-cdk/cx-api/lib/cxapi.ts index 9835ffd765500..f0ef41a59256b 100644 --- a/packages/@aws-cdk/cx-api/lib/cxapi.ts +++ b/packages/@aws-cdk/cx-api/lib/cxapi.ts @@ -4,6 +4,24 @@ import { Environment } from './environment'; +/** + * Bump this to the library version if and only if the CX protocol changes. + * + * We could also have used 1, 2, 3, ... here to indicate protocol versions, but + * those then still need to be mapped to software versions to be useful. So we + * 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. + */ +export const PROTO_RESPONSE_VERSION = '0.14.0'; + export const OUTFILE_NAME = 'cdk.out'; export const OUTDIR_ENV = 'CDK_OUTDIR'; export const CONTEXT_ENV = 'CDK_CONTEXT_JSON'; @@ -21,6 +39,10 @@ export interface MissingContext { } export interface SynthesizeResponse { + /** + * Protocol version + */ + version: string; stacks: SynthesizedStack[]; runtime?: AppRuntime; } diff --git a/packages/aws-cdk/bin/cdk.ts b/packages/aws-cdk/bin/cdk.ts index 27a9e21451ab7..256a5d3bc6b61 100644 --- a/packages/aws-cdk/bin/cdk.ts +++ b/packages/aws-cdk/bin/cdk.ts @@ -9,6 +9,7 @@ import YAML = require('js-yaml'); import minimatch = require('minimatch'); import os = require('os'); import path = require('path'); +import semver = require('semver'); import util = require('util'); import yargs = require('yargs'); import cdkUtil = require('../lib/util'); @@ -469,7 +470,7 @@ async function initCommandLine() { const response = await fs.readJson(outfile); debug(response); - return response; + return versionCheckResponse(response); } finally { debug('Removing outdir', outdir); await fs.remove(outdir); @@ -508,7 +509,37 @@ async function initCommandLine() { }); } } + } + + /** + * Look at the type of response we get and upgrade it to the latest expected version + */ + function versionCheckResponse(response: cxapi.SynthesizeResponse): cxapi.SynthesizeResponse { + if (!response.version) { + // tslint:disable-next-line:max-line-length + throw new Error(`CDK Framework >= ${cxapi.PROTO_RESPONSE_VERSION} is required in order to interact with this version of the Toolkit.`); + } + + const frameworkVersion = semver.coerce(response.version); + const toolkitVersion = semver.coerce(cxapi.PROTO_RESPONSE_VERSION); + + // Should not happen, but I don't trust this library 100% either, so let's check for it to be safe + if (!frameworkVersion || !toolkitVersion) { throw new Error('SemVer library could not parse versions'); } + + if (semver.gt(frameworkVersion, toolkitVersion)) { + throw new Error(`CDK Toolkit >= ${response.version} is required in order to interact with this program.`); + } + + if (semver.lt(frameworkVersion, toolkitVersion)) { + // Toolkit protocol is newer than the framework version, and we KNOW the + // version. This is a scenario in which we could potentially do some + // upgrading of the response in the future. + // + // For now though, we simply reject old responses. + throw new Error(`CDK Framework >= ${cxapi.PROTO_RESPONSE_VERSION} is required in order to interact with this version of the Toolkit.`); + } + return response; } /** diff --git a/packages/aws-cdk/package.json b/packages/aws-cdk/package.json index 31ad390cfb164..c768a563aa33e 100644 --- a/packages/aws-cdk/package.json +++ b/packages/aws-cdk/package.json @@ -39,6 +39,7 @@ "@types/request": "^2.47.1", "@types/uuid": "^3.4.3", "@types/yargs": "^8.0.3", + "@types/semver": "^5.5.0", "cdk-build-tools": "^0.13.0", "mockery": "^2.1.0", "pkglint": "^0.13.0" @@ -58,6 +59,7 @@ "promptly": "^0.2.0", "proxy-agent": "^3.0.1", "request": "^2.83.0", + "semver": "^5.5.0", "source-map-support": "^0.5.6", "yargs": "^9.0.1" },