From c48453bc261d3f424e15179d40d6a21f5b15002a Mon Sep 17 00:00:00 2001 From: Colin Ihrig Date: Thu, 21 Oct 2021 23:49:26 -0400 Subject: [PATCH] Revert "feat: version blocking for CLI (#8512)" (#8522) This reverts commit 52edf2b58508c96e78184aba1f77c06c021cc9b1. Co-authored-by: Colin Ihrig --- packages/amplify-cli-core/src/errors/index.ts | 9 +- packages/amplify-cli-core/src/index.ts | 6 - packages/amplify-cli/package.json | 5 - .../src/__tests__/context-manager.test.ts | 11 +- .../get-all-category-pluginInfos.test.ts | 2 - .../get-category-pluginInfo.test.ts | 2 - .../amplify-helpers/get-project-meta.test.ts | 10 +- .../src/__tests__/version-gating.test.ts | 266 ------------------ packages/amplify-cli/src/commands/version.ts | 5 +- packages/amplify-cli/src/context-manager.ts | 10 +- packages/amplify-cli/src/domain/context.ts | 9 - packages/amplify-cli/src/index.ts | 11 +- .../amplify-cli/src/version-gating/index.ts | 202 ------------- packages/amplify-cli/src/version-notifier.ts | 8 +- packages/amplify-e2e-core/src/index.ts | 2 +- packages/amplify-e2e-tests/package.json | 1 - .../src/__tests__/version-gating.test.ts | 238 ---------------- .../src/__tests__/initializer.test.ts | 6 +- .../src/aws-utils/aws-cfn.js | 7 +- .../src/initializer.ts | 12 - .../src/push-resources.ts | 14 +- 21 files changed, 27 insertions(+), 809 deletions(-) delete mode 100644 packages/amplify-cli/src/__tests__/version-gating.test.ts delete mode 100644 packages/amplify-cli/src/version-gating/index.ts delete mode 100644 packages/amplify-e2e-tests/src/__tests__/version-gating.test.ts diff --git a/packages/amplify-cli-core/src/errors/index.ts b/packages/amplify-cli-core/src/errors/index.ts index a83df8d6139..aa418fdcd48 100644 --- a/packages/amplify-cli-core/src/errors/index.ts +++ b/packages/amplify-cli-core/src/errors/index.ts @@ -24,10 +24,11 @@ export class NotInitializedError extends Error { public constructor() { super(); this.name = 'NotInitializedError'; - this.message = `No Amplify backend project files detected within this folder. Either initialize a new Amplify project or pull an existing project. -- "amplify init" to initialize a new Amplify project -- "amplify pull " to pull your existing Amplify project. Find the in the AWS Console or Amplify Admin UI.`; - + this.message = ` + No Amplify backend project files detected within this folder. Either initialize a new Amplify project or pull an existing project. + - "amplify init" to initialize a new Amplify project + - "amplify pull " to pull your existing Amplify project. Find the in the AWS Console or Amplify Admin UI. + `; this.stack = undefined; } } diff --git a/packages/amplify-cli-core/src/index.ts b/packages/amplify-cli-core/src/index.ts index 80254de11aa..b1d07fdd24f 100644 --- a/packages/amplify-cli-core/src/index.ts +++ b/packages/amplify-cli-core/src/index.ts @@ -51,7 +51,6 @@ export type $TSContext = { newUserInfo?: $TSAny; filesystem: IContextFilesystem; template: IContextTemplate; - versionInfo: CLIVersionInfo; }; export type CategoryName = string; @@ -146,11 +145,6 @@ export type DeploymentSecrets = { }>; }; -export type CLIVersionInfo = { - currentCLIVersion: string; - minimumCompatibleCLIVersion: string; -}; - /** * Plugins or other packages bundled with the CLI that pass a file to a system command or execute a binary file must export a function named * "getPackageAssetPaths" of this type. diff --git a/packages/amplify-cli/package.json b/packages/amplify-cli/package.json index 28dfec91a26..6f021a367ed 100644 --- a/packages/amplify-cli/package.json +++ b/packages/amplify-cli/package.json @@ -32,11 +32,6 @@ "engines": { "node": ">=12.0.0" }, - "amplify-cli": { - "configuration": { - "minimumCompatibleCLIVersion": "6.4.0" - } - }, "dependencies": { "@aws-cdk/cloudformation-diff": "~1.124.0", "amplify-app": "3.0.16", diff --git a/packages/amplify-cli/src/__tests__/context-manager.test.ts b/packages/amplify-cli/src/__tests__/context-manager.test.ts index 5f7a555aa16..2191b0b07d1 100644 --- a/packages/amplify-cli/src/__tests__/context-manager.test.ts +++ b/packages/amplify-cli/src/__tests__/context-manager.test.ts @@ -23,13 +23,8 @@ jest.mock('../domain/amplify-usageData/', () => { }); jest.mock('../app-config'); -jest.mock('../version-gating', () => ({ - getCurrentCLIVersion: jest.fn().mockReturnValue(() => '5.2.0'), - getMinimumCompatibleCLIVersion: jest.fn().mockReturnValue(() => '5.0.0'), -})); - describe('test attachUsageData', () => { - const version = '5.2.0'; + const version = 'latestversion'; const mockContext = jest.genMockFromModule('../domain/context'); mockContext.input = new Input([ @@ -37,10 +32,6 @@ describe('test attachUsageData', () => { '/Users/userName/.nvm/versions/node/v8.11.4/bin/amplify', 'status', ]); - mockContext.versionInfo = { - currentCLIVersion: '5.2.0', - minimumCompatibleCLIVersion: '5.0.0', - }; mockContext.pluginPlatform = new PluginPlatform(); mockContext.pluginPlatform.plugins['core'] = [new PluginInfo('', version, '', new PluginManifest('', ''))]; diff --git a/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/get-all-category-pluginInfos.test.ts b/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/get-all-category-pluginInfos.test.ts index 0b0672080cd..a11095ed69d 100644 --- a/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/get-all-category-pluginInfos.test.ts +++ b/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/get-all-category-pluginInfos.test.ts @@ -6,8 +6,6 @@ import { constructMockPluginPlatform } from './mock-plugin-platform'; import { getAllCategoryPluginInfo } from '../../../extensions/amplify-helpers/get-all-category-pluginInfos'; -jest.mock('../../../version-gating'); - test('getAllCategoryPluginInfo', () => { const mockPluginPlatform = constructMockPluginPlatform(); const mockProcessArgv = [ diff --git a/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/get-category-pluginInfo.test.ts b/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/get-category-pluginInfo.test.ts index 51dc87b7585..50760e7780f 100644 --- a/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/get-category-pluginInfo.test.ts +++ b/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/get-category-pluginInfo.test.ts @@ -3,8 +3,6 @@ import { constructMockPluginPlatform } from './mock-plugin-platform'; import { constructContext } from '../../../context-manager'; import { getCategoryPluginInfo } from '../../../extensions/amplify-helpers/get-category-pluginInfo'; -jest.mock('../../../version-gating'); - test('getCategoryPluginInfo returns the first pluginInfo to match category', () => { const mockPluginPlatform = constructMockPluginPlatform(); const mockProcessArgv = [ diff --git a/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/get-project-meta.test.ts b/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/get-project-meta.test.ts index e572921d01b..0025b102646 100644 --- a/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/get-project-meta.test.ts +++ b/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/get-project-meta.test.ts @@ -30,10 +30,10 @@ describe('getProjectMeta', () => { }); it('should throw NotInitializedError when metaFile does not exists', () => { stateManager_mock.metaFileExists.mockImplementation(() => false); - expect(() => getProjectMeta()).toThrow( - `No Amplify backend project files detected within this folder. Either initialize a new Amplify project or pull an existing project. -- "amplify init" to initialize a new Amplify project -- "amplify pull " to pull your existing Amplify project. Find the in the AWS Console or Amplify Admin UI.`, - ); + expect(() => getProjectMeta()).toThrow(` + No Amplify backend project files detected within this folder. Either initialize a new Amplify project or pull an existing project. + - "amplify init" to initialize a new Amplify project + - "amplify pull " to pull your existing Amplify project. Find the in the AWS Console or Amplify Admin UI. + `); }); }); diff --git a/packages/amplify-cli/src/__tests__/version-gating.test.ts b/packages/amplify-cli/src/__tests__/version-gating.test.ts deleted file mode 100644 index db4d302901e..00000000000 --- a/packages/amplify-cli/src/__tests__/version-gating.test.ts +++ /dev/null @@ -1,266 +0,0 @@ -import { $TSContext } from 'amplify-cli-core'; - -describe('command blocking', () => { - test('validate which commands will be blocked or not', async () => { - const { isCommandInMatches, versionGatingBlockedCommands } = await import('../version-gating'); - - expect(isCommandInMatches({ plugin: 'api', command: 'add' }, versionGatingBlockedCommands)).toBe(true); - expect(isCommandInMatches({ plugin: 'function', command: 'add' }, versionGatingBlockedCommands)).toBe(true); - - expect(isCommandInMatches({ plugin: 'api', command: 'update' }, versionGatingBlockedCommands)).toBe(true); - expect(isCommandInMatches({ plugin: 'function', command: 'update' }, versionGatingBlockedCommands)).toBe(true); - - expect(isCommandInMatches({ plugin: 'api', command: 'remove' }, versionGatingBlockedCommands)).toBe(true); - expect(isCommandInMatches({ plugin: 'function', command: 'remove' }, versionGatingBlockedCommands)).toBe(true); - - expect(isCommandInMatches({ plugin: 'core', command: 'push' }, versionGatingBlockedCommands)).toBe(true); - expect(isCommandInMatches({ plugin: 'api', command: 'push' }, versionGatingBlockedCommands)).toBe(true); - expect(isCommandInMatches({ plugin: 'function', command: 'push' }, versionGatingBlockedCommands)).toBe(true); - - expect(isCommandInMatches({ plugin: 'hosting', command: 'publish' }, versionGatingBlockedCommands)).toBe(true); - - expect(isCommandInMatches({ plugin: 'api', command: 'gql-compile' }, versionGatingBlockedCommands)).toBe(true); - - expect(isCommandInMatches({ plugin: undefined, command: 'help' }, versionGatingBlockedCommands)).toBe(false); - expect(isCommandInMatches({ plugin: undefined, command: 'version' }, versionGatingBlockedCommands)).toBe(false); - expect(isCommandInMatches({ plugin: undefined, command: 'configure' }, versionGatingBlockedCommands)).toBe(false); - expect(isCommandInMatches({ plugin: undefined, command: 'console' }, versionGatingBlockedCommands)).toBe(false); - expect(isCommandInMatches({ plugin: undefined, command: 'init' }, versionGatingBlockedCommands)).toBe(false); - expect(isCommandInMatches({ plugin: undefined, command: 'logout' }, versionGatingBlockedCommands)).toBe(false); - expect(isCommandInMatches({ plugin: undefined, command: 'status' }, versionGatingBlockedCommands)).toBe(false); - expect(isCommandInMatches({ plugin: undefined, command: 'pull' }, versionGatingBlockedCommands)).toBe(false); - - expect(isCommandInMatches({ plugin: 'env', command: 'list' }, versionGatingBlockedCommands)).toBe(false); - }); -}); - -describe('version gating', () => { - const originalProcessEnv = process.env; - - let stackMetadata: any = undefined; - - class CfnClientMock { - public getTemplateSummary = () => { - return { - promise: () => - new Promise((resolve, _) => { - resolve({ Metadata: stackMetadata }); - }), - }; - }; - } - - const cfnClientMockInstance = new CfnClientMock(); - - class CloudFormation { - cfn: CfnClientMock; - - constructor() { - this.cfn = cfnClientMockInstance; - } - } - - const cloudFormationClient_stub = new CloudFormation(); - - const meta_stub = { - providers: { - awscloudformation: { - StackName: 'mockstack', - }, - }, - }; - - const stackMetadata_stub_520_500 = { - AmplifyCLI: { - DeployedByCLIVersion: '5.2.0', - MinimumCompatibleCLIVersion: '5.0.0', - }, - }; - - const stackMetadata_stub_520_530 = { - AmplifyCLI: { - DeployedByCLIVersion: '5.2.0', - MinimumCompatibleCLIVersion: '5.3.0', - }, - }; - - const stackMetadata_stub_530_531 = { - AmplifyCLI: { - DeployedByCLIVersion: '5.3.0', - MinimumCompatibleCLIVersion: '5.3.1', - }, - }; - - const versionInfo_520_500 = { - currentCLIVersion: '5.2.0', - minimumCompatibleCLIVersion: '5.0.0', - }; - - const versionInfo_520_510 = { - currentCLIVersion: '5.2.0', - minimumCompatibleCLIVersion: '5.1.0', - }; - - const versionInfo_520_540 = { - currentCLIVersion: '5.2.0', - minimumCompatibleCLIVersion: '5.4.0', - }; - - const versionInfo_532_530 = { - currentCLIVersion: '5.3.2', - minimumCompatibleCLIVersion: '5.3.0', - }; - - const context_stub = { - print: { - info: jest.fn(), - warning: jest.fn(), - success: jest.fn(), - }, - input: { - plugin: 'api', - command: 'add', - }, - versionInfo: versionInfo_520_500, - amplify: { - invokePluginMethod: jest.fn().mockReturnValue(cloudFormationClient_stub), - }, - } as unknown as jest.Mocked<$TSContext>; - - beforeEach(() => { - jest.clearAllMocks(); - jest.resetModules(); - - // reset mutated state - context_stub.input.plugin = 'api'; - context_stub.input.command = 'add'; - context_stub.versionInfo = versionInfo_520_500; - - stackMetadata = undefined; - - process.env = { ...originalProcessEnv }; - }); - - afterEach(() => { - jest.clearAllMocks(); - jest.resetModules(); - - // reset mutated state - context_stub.input.plugin = 'api'; - context_stub.input.command = 'add'; - - stackMetadata = undefined; - - process.env = { ...originalProcessEnv }; - }); - - test('version gating should pass when env override set', async () => { - process.env.AMPLIFY_CLI_DISABLE_VERSION_CHECK = '1'; - - const versionGating = await import('../version-gating'); - - const isCommandInMatchesMock = jest.spyOn(versionGating, 'isCommandInMatches'); - - await expect(versionGating.isMinimumVersionSatisfied(context_stub)).resolves.toBe(true); - - expect(isCommandInMatchesMock).toHaveBeenCalledTimes(0); - }); - - test('version gating should pass when command is non-blocking', async () => { - context_stub.input.plugin = 'core'; - context_stub.input.command = 'version'; - - const versionGating = await import('../version-gating'); - const { stateManager } = await import('amplify-cli-core'); - - const isCommandInMatchesMock = jest.spyOn(versionGating, 'isCommandInMatches'); - const stateManagerMock = jest.spyOn(stateManager, 'getMeta').mockImplementation(() => undefined); - - await expect(versionGating.isMinimumVersionSatisfied(context_stub)).resolves.toBe(true); - - expect(isCommandInMatchesMock).toHaveBeenCalledTimes(1); - expect(stateManagerMock).toHaveBeenCalledTimes(0); - }); - - test('version gating should pass when stack is not deployed', async () => { - const versionGating = await import('../version-gating'); - const { stateManager } = await import('amplify-cli-core'); - - const stateManagerMock = jest.spyOn(stateManager, 'getMeta').mockImplementation(() => undefined); - - await expect(versionGating.isMinimumVersionSatisfied(context_stub)).resolves.toBe(true); - - expect(stateManagerMock).toHaveBeenCalledTimes(1); - expect(context_stub.amplify.invokePluginMethod).toHaveBeenCalledTimes(0); - }); - - test('version gating should pass when stack has no metadata', async () => { - const versionGating = await import('../version-gating'); - const { stateManager } = await import('amplify-cli-core'); - - const stateManagerMock = jest.spyOn(stateManager, 'getMeta').mockImplementation(() => meta_stub); - - await expect(versionGating.isMinimumVersionSatisfied(context_stub)).resolves.toBe(true); - - expect(stateManagerMock).toHaveBeenCalledTimes(1); - expect(context_stub.amplify.invokePluginMethod).toHaveBeenCalledTimes(1); - }); - - test('version gating should pass, meta: 5.2.0, metamin: 5.0.0, current: 5.2.0, min: 5.0.0', async () => { - const versionGating = await import('../version-gating'); - const { stateManager } = await import('amplify-cli-core'); - - stackMetadata = stackMetadata_stub_520_500; - - const stateManagerMock = jest.spyOn(stateManager, 'getMeta').mockImplementation(() => meta_stub); - - await expect(versionGating.isMinimumVersionSatisfied(context_stub)).resolves.toBe(true); - }); - - test('version gating should pass, meta: 5.2.0, metamin: 5.0.0, current: 5.2.0, min: 5.1.0', async () => { - const versionGating = await import('../version-gating'); - const { stateManager } = await import('amplify-cli-core'); - - stackMetadata = stackMetadata_stub_520_500; - context_stub.versionInfo = versionInfo_520_510; - - const stateManagerMock = jest.spyOn(stateManager, 'getMeta').mockImplementation(() => meta_stub); - - await expect(versionGating.isMinimumVersionSatisfied(context_stub)).resolves.toBe(true); - }); - - test('version gating should pass, meta: 5.3.0, metamin: 5.3.1, current: 5.3.2, min: 5.3.0', async () => { - const versionGating = await import('../version-gating'); - const { stateManager } = await import('amplify-cli-core'); - - stackMetadata = stackMetadata_stub_530_531; - context_stub.versionInfo = versionInfo_532_530; - - const stateManagerMock = jest.spyOn(stateManager, 'getMeta').mockImplementation(() => meta_stub); - - await expect(versionGating.isMinimumVersionSatisfied(context_stub)).resolves.toBe(true); - }); - - test('version gating should fail, meta: 5.2.0, metamin: 5.3.0, current: 5.2.0, min: 5.0.0', async () => { - const versionGating = await import('../version-gating'); - const { stateManager } = await import('amplify-cli-core'); - - stackMetadata = stackMetadata_stub_520_530; - - const stateManagerMock = jest.spyOn(stateManager, 'getMeta').mockImplementation(() => meta_stub); - - expect(versionGating.isMinimumVersionSatisfied(context_stub)).resolves.toEqual(false); - }); - - test('version gating should fail, meta: 5.2.0, metamin: 5.3.0, current: 5.2.0, min: 5.4.0', async () => { - const versionGating = await import('../version-gating'); - const { stateManager } = await import('amplify-cli-core'); - - stackMetadata = stackMetadata_stub_520_530; - context_stub.versionInfo = versionInfo_520_540; - - const stateManagerMock = jest.spyOn(stateManager, 'getMeta').mockImplementation(() => meta_stub); - - expect(versionGating.isMinimumVersionSatisfied(context_stub)).resolves.toEqual(false); - }); -}); diff --git a/packages/amplify-cli/src/commands/version.ts b/packages/amplify-cli/src/commands/version.ts index d4e7f1e0864..3611d9ea176 100644 --- a/packages/amplify-cli/src/commands/version.ts +++ b/packages/amplify-cli/src/commands/version.ts @@ -1,6 +1,7 @@ -import { printer } from 'amplify-prompts'; import { Context } from '../domain/context'; +import { printer } from 'amplify-prompts'; +import { getAmplifyVersion } from '../extensions/amplify-helpers/get-amplify-version'; export const run = (context: Context) => { - printer.info(context.versionInfo.currentCLIVersion); + printer.info(getAmplifyVersion()); }; diff --git a/packages/amplify-cli/src/context-manager.ts b/packages/amplify-cli/src/context-manager.ts index 69f4a55ca43..cd81af5b829 100644 --- a/packages/amplify-cli/src/context-manager.ts +++ b/packages/amplify-cli/src/context-manager.ts @@ -24,15 +24,11 @@ export async function attachUsageData(context: Context) { } else { context.usageData = NoUsageData.Instance; } - context.usageData.init( - config.usageDataConfig.installationUuid, - context.versionInfo.currentCLIVersion, - context.input, - '', - getProjectSettings(), - ); + context.usageData.init(config.usageDataConfig.installationUuid, getVersion(context), context.input, '', getProjectSettings()); } +const getVersion = (context: Context) => context.pluginPlatform.plugins.core[0].packageVersion; + const getProjectSettings = (): ProjectSettings => { const projectSettings: ProjectSettings = {}; if (stateManager.projectConfigExists()) { diff --git a/packages/amplify-cli/src/domain/context.ts b/packages/amplify-cli/src/domain/context.ts index da6fc24564b..7d834643531 100644 --- a/packages/amplify-cli/src/domain/context.ts +++ b/packages/amplify-cli/src/domain/context.ts @@ -2,21 +2,12 @@ import { Input } from './input'; import { AmplifyToolkit } from './amplify-toolkit'; import { PluginPlatform } from './plugin-platform'; import { IUsageData } from './amplify-usageData'; -import { CLIVersionInfo } from 'amplify-cli-core'; -import { getCurrentCLIVersion, getMinimumCompatibleCLIVersion } from '../version-gating'; export class Context { amplify: AmplifyToolkit; usageData!: IUsageData; - versionInfo: CLIVersionInfo; - constructor(public pluginPlatform: PluginPlatform, public input: Input) { this.amplify = new AmplifyToolkit(); - - this.versionInfo = { - currentCLIVersion: getCurrentCLIVersion(), - minimumCompatibleCLIVersion: getMinimumCompatibleCLIVersion(), - }; } // ToDo: this is to attach gluegun extensions and other attached properties diff --git a/packages/amplify-cli/src/index.ts b/packages/amplify-cli/src/index.ts index a3ed03aafd8..f60113a1bdc 100644 --- a/packages/amplify-cli/src/index.ts +++ b/packages/amplify-cli/src/index.ts @@ -5,6 +5,7 @@ import { CLIContextEnvironmentProvider, exitOnNextTick, FeatureFlags, + JSONUtilities, JSONValidationError, pathManager, stateManager, @@ -31,7 +32,6 @@ import { rewireDeprecatedCommands } from './rewireDeprecatedCommands'; import { ensureMobileHubCommandCompatibility } from './utils/mobilehub-support'; import { migrateTeamProviderInfo } from './utils/team-provider-migrate'; import { deleteOldVersion } from './utils/win-utils'; -import { getCurrentCLIVersion, isMinimumVersionSatisfied } from './version-gating'; import { notify } from './version-notifier'; import { getAmplifyVersion } from './extensions/amplify-helpers/get-amplify-version'; @@ -131,7 +131,8 @@ export async function run() { } // Initialize Banner messages. These messages are set on the server side - BannerMessage.initialize(getCurrentCLIVersion()); + const pkg = JSONUtilities.readJson<$TSAny>(path.join(__dirname, '..', 'package.json')); + BannerMessage.initialize(pkg.version); ensureFilePermissions(pathManager.getAWSCredentialsFilePath()); ensureFilePermissions(pathManager.getAWSConfigFilePath()); @@ -184,12 +185,6 @@ export async function run() { process.on('SIGINT', sigIntHandler.bind(context)); - if ((await isMinimumVersionSatisfied(context as unknown as $TSContext)) === false) { - context.usageData.emitError(new Error('Version gating requirements were not passed.')); - - return 1; - } - // Skip NodeJS version check and migrations if Amplify CLI is executed in CI/CD or // the command is not push if (!isCI && context.input.command === 'push') { diff --git a/packages/amplify-cli/src/version-gating/index.ts b/packages/amplify-cli/src/version-gating/index.ts deleted file mode 100644 index f1068aa1b57..00000000000 --- a/packages/amplify-cli/src/version-gating/index.ts +++ /dev/null @@ -1,202 +0,0 @@ -import * as path from 'path'; -import _ from 'lodash'; -import semver from 'semver'; -import { $TSContext, JSONUtilities, stateManager } from 'amplify-cli-core'; -import * as CloudFormation from 'aws-sdk/clients/cloudformation'; - -const packageJsonFileName = 'package.json'; -const disableVersionGatingEnvVarName = 'AMPLIFY_CLI_DISABLE_VERSION_CHECK'; - -// Allowed Commands: 'configure', 'console', 'env list', 'help', 'init', 'logout', 'version', 'status', 'pull' - -// Type used internally for object matching -type CommandMatch = { - plugins: string[]; - command: string; -}; - -type Command = { - plugin?: string; - command: string; -}; - -type VersionGatingMetadata = { - DeployedByCLIVersion?: string; - MinimumCompatibleCLIVersion?: string; -}; - -export const versionGatingBlockedCommands: CommandMatch[] = [ - { - plugins: ['*'], - command: 'add', - }, - { - plugins: ['*'], - command: 'update', - }, - { - plugins: ['*'], - command: 'remove', - }, - { - plugins: ['*'], - command: 'push', - }, - { - plugins: ['*'], - command: 'publish', - }, - { - plugins: ['api'], - command: 'gql-compile', - }, -]; - -export const readCLIPackageJson = (): T => { - const packageJsonPath = path.join(__dirname, '..', '..', packageJsonFileName); - - const packageJsonContent = JSONUtilities.readJson(packageJsonPath); - - return packageJsonContent! as T; -}; - -export const getCurrentCLIVersion = (): string => { - const { version } = readCLIPackageJson<{ version: string }>(); - - // This data always exists in package.json - return version; -}; - -export const getMinimumCompatibleCLIVersion = (): string => { - const { 'amplify-cli': amplifyCLI } = readCLIPackageJson<{ 'amplify-cli': { configuration: { minimumCompatibleCLIVersion: string } } }>(); - - // This data always exists in package.json - return amplifyCLI.configuration.minimumCompatibleCLIVersion; -}; - -/* - -Summary for the version gating logic: - -Inputs: -- M: Metadata in root stack -- CV: Currently running CLI verison -- CMin: Minimum version value defined in currently running CLI's package.json -- DV: Version of the CLI the root stack was deployed with last time -- DMin: Minimum required CLI version for deployment - -Checks: -- M == null or no version info present => pass (Current CLI is newer always) -- CV >= DMin => pass (CV >= CMin always so no need to check) -- In all other cases => fail (for example: DMin > CV) - -Notes: -- DV: It is possible that this information will change from 5.1.0 to 5.0.2 and 5.1.3 with pushes as long as DMin is 5.0.0. -- DV: is not used for any checks, just persisted for diagnostic purposes. - -Cases we cannot handle: -- If a CLI version is used which does not have version gating, will always can run against a newer stack, which will nuke out - the metadata we added. - -*/ - -export const isMinimumVersionSatisfied = async (context: $TSContext): Promise => { - // Check if version gating is disabled via the environment variable - if (!!process.env[disableVersionGatingEnvVarName]) { - return true; - } - - const currentCommand: Command = { - plugin: context.input.plugin, - command: context.input.command, - }; - - const isBlockedCommand = isCommandInMatches(currentCommand, versionGatingBlockedCommands); - - if (isBlockedCommand === false) { - return true; - } - - // At this point the command is blocking so version check must be performed against current CLI version - // and the minimum CLI version vs. the deployed version from the root stack metadata (if any) - - const meta = stateManager.getMeta(undefined, { - throwIfNotExist: false, - }); - - const rootStackName = _.get(meta, ['providers', 'awscloudformation', 'StackName']); - - if (rootStackName === undefined) { - // No meta or root stack name is not present, which means that version gating cannot be enforced on an uninitialized project - // so assume version check passed as the rest of the CLI will validate the command in question for execution. - return true; - } - - const cloudFormation: { cfn: CloudFormation } = await context.amplify.invokePluginMethod( - context, - 'awscloudformation', - undefined, - 'getCloudFormationSdk', - [context], - ); - - const cfnClient: CloudFormation = cloudFormation.cfn as CloudFormation; - - const templateSummary = await cfnClient - .getTemplateSummary({ - StackName: rootStackName, - }) - .promise(); - - const metadataValue = _.get(templateSummary, ['Metadata']) || '{}'; - const metadata = JSONUtilities.parse(metadataValue); - const versionGatingMetadata: VersionGatingMetadata = _.get(metadata, ['AmplifyCLI']); - - // If no metadata or version info in metadata was found in the root stack template, then - // assume version check passed as current CLI is newer than the stack was deployed with. - if ( - versionGatingMetadata === undefined || - versionGatingMetadata.MinimumCompatibleCLIVersion === undefined || - versionGatingMetadata.DeployedByCLIVersion === undefined - ) { - return true; - } - - // These are always valid version numbers as CLI creating them. - const stackMinimumCompatibleCLIVersion = semver.coerce(versionGatingMetadata.MinimumCompatibleCLIVersion)!; - - // Pick the greater minimum version - const minimumCompatibleCLIVersion = semver.gt(stackMinimumCompatibleCLIVersion, context.versionInfo.minimumCompatibleCLIVersion) - ? stackMinimumCompatibleCLIVersion - : semver.coerce(context.versionInfo.minimumCompatibleCLIVersion)!; - - // If current version is greater than - if (semver.gte(context.versionInfo.currentCLIVersion, minimumCompatibleCLIVersion)) { - return true; - } - - const deployedCLIVersion = semver.coerce(versionGatingMetadata.DeployedByCLIVersion)!; - - context.print.warning( - `This project was previously deployed with Amplify CLI version ${deployedCLIVersion}. The currently running Amplify CLI version is ${context.versionInfo.currentCLIVersion}.`, - ); - - context.print.info(''); - context.print.info(`Some features in this project require Amplify CLI version >=${minimumCompatibleCLIVersion} to function correctly.`); - context.print.info('Upgrade to the latest version of Amplify CLI, run: "amplify upgrade" or "npm install -g @aws-amplify/cli"'); - - return false; -}; - -export const isCommandInMatches = (command: Command, commandsToMatch: CommandMatch[]): boolean => { - for (const commandToMatch of commandsToMatch) { - if ( - ((commandToMatch.plugins.length === 1 && commandToMatch.plugins[0] === '*') || _.includes(commandToMatch.plugins, command.plugin)) && - commandToMatch.command === command.command - ) { - return true; - } - } - - return false; -}; diff --git a/packages/amplify-cli/src/version-notifier.ts b/packages/amplify-cli/src/version-notifier.ts index f5c6ff214f0..ef736c92d5f 100644 --- a/packages/amplify-cli/src/version-notifier.ts +++ b/packages/amplify-cli/src/version-notifier.ts @@ -1,17 +1,15 @@ -import { $TSAny, isPackaged } from 'amplify-cli-core'; +import { JSONUtilities, $TSAny, isPackaged } from 'amplify-cli-core'; import { default as updateNotifier } from 'update-notifier'; +import path from 'path'; import chalk from 'chalk'; -import { readCLIPackageJson } from './version-gating'; -const pkg = readCLIPackageJson<$TSAny>(); +const pkg = JSONUtilities.readJson<$TSAny>(path.join(__dirname, '..', 'package.json')); const notifier = updateNotifier({ pkg }); // defaults to 1 day interval const defaultOpts: updateNotifier.NotifyOptions = { message: isPackaged ? `Update available:\nRun ${chalk.blueBright('amplify upgrade')} for the latest features and fixes!` : undefined, }; - export function notify(notifyOpts?: updateNotifier.NotifyOptions): void { notifyOpts = { ...defaultOpts, ...notifyOpts }; - notifier.notify(notifyOpts); } diff --git a/packages/amplify-e2e-core/src/index.ts b/packages/amplify-e2e-core/src/index.ts index fedfbba1c72..048d2576415 100644 --- a/packages/amplify-e2e-core/src/index.ts +++ b/packages/amplify-e2e-core/src/index.ts @@ -97,7 +97,7 @@ export async function createNewProjectDir( } while (fs.existsSync(projectDir)); fs.ensureDirSync(projectDir); - + console.log(projectDir); return projectDir; } diff --git a/packages/amplify-e2e-tests/package.json b/packages/amplify-e2e-tests/package.json index 378ef0e7d51..98e6a2e73e8 100644 --- a/packages/amplify-e2e-tests/package.json +++ b/packages/amplify-e2e-tests/package.json @@ -41,7 +41,6 @@ "promise-sequential": "^1.1.1", "rimraf": "^3.0.0", "uuid": "^3.4.0", - "which": "^2.0.2", "yargs": "^15.1.0" }, "devDependencies": { diff --git a/packages/amplify-e2e-tests/src/__tests__/version-gating.test.ts b/packages/amplify-e2e-tests/src/__tests__/version-gating.test.ts deleted file mode 100644 index a3391a3a713..00000000000 --- a/packages/amplify-e2e-tests/src/__tests__/version-gating.test.ts +++ /dev/null @@ -1,238 +0,0 @@ -import * as aws from 'aws-sdk'; -import * as fs from 'fs-extra'; -import * as path from 'path'; -import * as which from 'which'; -import CloudFormation from 'aws-sdk/clients/cloudformation'; -import _ from 'lodash'; -import { JSONUtilities, stateManager } from 'amplify-cli-core'; -import { - createNewProjectDir, - deleteProject, - deleteProjectDir, - getCLIPath, - initJSProjectWithProfile, - nspawn as spawn, - isCI, -} from 'amplify-e2e-core'; - -type VersionGatingMetadata = { - DeployedByCLIVersion?: string; - MinimumCompatibleCLIVersion?: string; -}; - -describe('version gating', () => { - let projRoot: string; - let projectInitialized = false; - const projName = 'versiongating'; - - // Extreme version numbers for testing purposes - const baseCLIVersion = '100.0.0'; - const baseMinimumCLIVersion = '100.0.0'; - - let cliPath: string; - let packageJsonPath: string; - let originalPackageJsonContent: string; - let packageJson: { version: string; 'amplify-cli': { configuration: { minimumCompatibleCLIVersion: string } } }; - - const profileName = isCI() ? 'amplify-integ-test-user' : 'default'; - - const creds = new aws.SharedIniFileCredentials({ profile: profileName }); - aws.config.credentials = creds; - - let region: string; - let rootStackName: string; - let cfnClient: CloudFormation; - - beforeEach(async () => { - projRoot = await createNewProjectDir(projName); - - if (!isPackagedAmplifyInPath()) { - cliPath = resolveRealCLIPath(); - packageJsonPath = path.resolve(cliPath, '..', '..', '..', 'amplify-cli', 'package.json'); - originalPackageJsonContent = fs.readFileSync(packageJsonPath, 'utf8').toString(); - packageJson = - JSONUtilities.parse<{ version: string; 'amplify-cli': { configuration: { minimumCompatibleCLIVersion: string } } }>( - originalPackageJsonContent, - ); - } - }); - - afterEach(async () => { - if (projectInitialized) { - await deleteProject(projRoot); - } - - deleteProjectDir(projRoot); - }); - - it('test version gating on projects', async () => { - // We cannot execute this test for packaged CLI as we are modifying the package.json file to - // simulate multiple versions - if (isPackagedAmplifyInPath()) { - return; - } - - try { - // Reset version information to make sure tests will pass with future CLI versionsas well - updateVersionInPackageJson(baseCLIVersion, baseMinimumCLIVersion); - - await initJSProjectWithProfile(projRoot, { name: projName }); - projectInitialized = true; - - const meta = stateManager.getMeta(projRoot); - - region = _.get(meta, ['providers', 'awscloudformation', 'Region']); - rootStackName = _.get(meta, ['providers', 'awscloudformation', 'StackName']); - - cfnClient = new CloudFormation({ region }); - - await expectStackMetadata(baseCLIVersion, baseMinimumCLIVersion); - - // Set CLI version to 100.1.0, verify push succeeds with newer version of CLI - updateVersionInPackageJson('100.1.0', undefined); - - await push(); - - await expectStackMetadata('100.1.0', baseMinimumCLIVersion); - - // Set Minimum CLI version to 100.2.0, verify push succeeds with newer version of CLI - updateVersionInPackageJson(undefined, '100.2.0'); - - await pushFail(); - - // Set CLI version to 100.2.0, Minimum CLI version to 100.2.0, verify push succeeds - updateVersionInPackageJson('100.2.0', '100.2.0'); - - await push(); - - await expectStackMetadata('100.2.0', '100.2.0'); - - // Set CLI version to 100.3.0, Minimum CLI version to 100.2.0, verify push succeeds - updateVersionInPackageJson('100.3.0', '100.2.0'); - - await push(); - - await expectStackMetadata('100.3.0', '100.2.0'); - - // Set CLI version back to 100.2.0, Minimum CLI version to 100.2.0, verify push succeeds as - // minimum deploy version requirement has met - updateVersionInPackageJson('100.2.0', '100.2.0'); - - await push(); - - await expectStackMetadata('100.2.0', '100.2.0'); - } finally { - // restore original package.json - fs.writeFileSync(packageJsonPath, originalPackageJsonContent); - } - }); - - // Test helper functions - const push = async () => { - await amplifyPushForceWithYes(projRoot); - }; - - const pushFail = async () => { - await amplifyPushForceWithVersionGatingOutput(projRoot); - }; - - const updateVersionInPackageJson = (cliVersion: string | undefined, minimumCompatibleCLIVersion: string | undefined) => { - if (cliVersion) { - packageJson.version = cliVersion; - } else { - packageJson.version = baseCLIVersion; - } - - if (minimumCompatibleCLIVersion) { - packageJson['amplify-cli'].configuration.minimumCompatibleCLIVersion = minimumCompatibleCLIVersion; - } else { - packageJson['amplify-cli'].configuration.minimumCompatibleCLIVersion = baseMinimumCLIVersion; - } - - fs.writeFileSync(packageJsonPath, JSONUtilities.stringify(packageJson)); - }; - - const expectStackMetadata = async (deployedByCLIVersion: string, minimumCompatibleCLIVersion: string) => { - const templateSummary = await cfnClient - .getTemplateSummary({ - StackName: rootStackName, - }) - .promise(); - - const metadataValue = _.get(templateSummary, ['Metadata']) || '{}'; - const metadata = JSONUtilities.parse(metadataValue); - const versionGatingMetadata: VersionGatingMetadata = _.get(metadata, ['AmplifyCLI']); - - expect(versionGatingMetadata.DeployedByCLIVersion).toBe(deployedByCLIVersion); - expect(versionGatingMetadata.MinimumCompatibleCLIVersion).toBe(minimumCompatibleCLIVersion); - }; -}); - -const resolveRealCLIPath = (): string => { - const cliPath = getCLIPath(false); - const cliResolvedPath = which.sync(cliPath); - - return fs.realpathSync(cliResolvedPath); -}; - -const isPackagedAmplifyInPath = (): boolean => { - const shebang = '#!/usr/bin/env node'; - const isWin = process.platform.startsWith('win'); - - const cliRealPath = resolveRealCLIPath(); - - if (isWin) { - if (cliRealPath.endsWith('.cmd')) { - return false; - } else if (cliRealPath.endsWith('.exe')) { - return true; - } - } - - // As *nix have no file extensions, read into the file and look for the shebang - let fileDescriptor: number | undefined; - - try { - fileDescriptor = fs.openSync(cliRealPath, 'r'); - - const buffer = Buffer.alloc(19); - - fs.readSync(fileDescriptor, buffer, 0, 19, 0); - - const preamble = String(buffer); - - return preamble !== shebang; - } finally { - if (fileDescriptor) { - fs.closeSync(fileDescriptor); - } - } -}; - -const amplifyPushForceWithVersionGatingOutput = (cwd: string, testingWithLatestCodebase: boolean = false): Promise => { - return new Promise((resolve, reject) => { - spawn(getCLIPath(testingWithLatestCodebase), ['push', '--forcePush', '--force', '--yes'], { cwd, stripColors: true }) - .wait('Upgrade to the latest version of Amplify CLI, run: "amplify upgrade" or "npm install -g @aws-amplify/cli') - .run((err: Error) => { - if (!err) { - resolve(); - } else { - reject(err); - } - }); - }); -}; - -const amplifyPushForceWithYes = (cwd: string, testingWithLatestCodebase: boolean = false): Promise => { - return new Promise((resolve, reject) => { - spawn(getCLIPath(testingWithLatestCodebase), ['push', '--forcePush', '--force', '--yes'], { cwd, stripColors: true }) - .wait('All resources are updated in the cloud') - .run((err: Error) => { - if (!err) { - resolve(); - } else { - reject(err); - } - }); - }); -}; diff --git a/packages/amplify-provider-awscloudformation/src/__tests__/initializer.test.ts b/packages/amplify-provider-awscloudformation/src/__tests__/initializer.test.ts index feec4802cd7..563b23501b8 100644 --- a/packages/amplify-provider-awscloudformation/src/__tests__/initializer.test.ts +++ b/packages/amplify-provider-awscloudformation/src/__tests__/initializer.test.ts @@ -31,12 +31,8 @@ describe('run', () => { }, teamProviderInfo: {}, }, - versionInfo: { - currentCLIVersion: '5.2.0', - minimumCompatibleCLIVersion: '5.0.0', - }, amplify: { - getTags: jest.fn().mockImplementation(() => []), + getTags: jest.fn(), }, }; CloudFormation_mock.mockImplementation( diff --git a/packages/amplify-provider-awscloudformation/src/aws-utils/aws-cfn.js b/packages/amplify-provider-awscloudformation/src/aws-utils/aws-cfn.js index b1c1e90ef59..571683df4b3 100644 --- a/packages/amplify-provider-awscloudformation/src/aws-utils/aws-cfn.js +++ b/packages/amplify-provider-awscloudformation/src/aws-utils/aws-cfn.js @@ -219,7 +219,7 @@ class CloudFormation { }); } - updateResourceStack(context, filePath) { + updateResourceStack(filePath) { const cfnFile = path.parse(filePath).base; const projectDetails = this.context.amplify.getProjectDetails(); const stackName = projectDetails.amplifyMeta.providers ? projectDetails.amplifyMeta.providers[providerName].StackName : ''; @@ -231,11 +231,6 @@ class CloudFormation { const Tags = this.context.amplify.getTags(this.context); - Tags.push({ - Key: 'amplify:DeployedByCLIVersion', - Value: context.versionInfo.currentCLIVersion, - }); - if (!stackName) { throw new Error('Project stack has not been created yet. Use amplify init to initialize the project.'); } diff --git a/packages/amplify-provider-awscloudformation/src/initializer.ts b/packages/amplify-provider-awscloudformation/src/initializer.ts index 8600a7436b1..d6c735b02fe 100644 --- a/packages/amplify-provider-awscloudformation/src/initializer.ts +++ b/packages/amplify-provider-awscloudformation/src/initializer.ts @@ -56,18 +56,6 @@ export async function run(context) { rootStack.Description = 'Root Stack for AWS Amplify Console'; } - rootStack.Metadata = { - AmplifyCLI: { - DeployedByCLIVersion: context.versionInfo.currentCLIVersion, - MinimumCompatibleCLIVersion: context.versionInfo.minimumCompatibleCLIVersion, - }, - }; - - Tags.push({ - Key: 'amplify:DeployedByCLIVersion', - Value: context.versionInfo.currentCLIVersion, - }); - const params = { StackName: stackName, Capabilities: ['CAPABILITY_NAMED_IAM', 'CAPABILITY_AUTO_EXPAND'], diff --git a/packages/amplify-provider-awscloudformation/src/push-resources.ts b/packages/amplify-provider-awscloudformation/src/push-resources.ts index dc8bbc3b0cc..165776d8be3 100644 --- a/packages/amplify-provider-awscloudformation/src/push-resources.ts +++ b/packages/amplify-provider-awscloudformation/src/push-resources.ts @@ -700,7 +700,7 @@ async function updateCloudFormationNestedStack( const log = logger('updateCloudFormationNestedStack', [providerDirectory, transformedStackPath]); try { log(); - await cfnItem.updateResourceStack(context, transformedStackPath); + await cfnItem.updateResourceStack(transformedStackPath); } catch (error) { log(error); throw error; @@ -860,18 +860,6 @@ async function formNestedStack( const { amplifyMeta } = projectDetails; let authResourceName: string; - // Add CLI versioning information to the root stack's metadata - const metadata = nestedStack.Metadata || {}; - - Object.assign(metadata, { - AmplifyCLI: { - DeployedByCLIVersion: context.versionInfo.currentCLIVersion, - MinimumCompatibleCLIVersion: context.versionInfo.minimumCompatibleCLIVersion, - }, - }); - - nestedStack.Metadata = metadata; - const { APIGatewayAuthURL, NetworkStackS3Url, AuthTriggerTemplateURL } = amplifyMeta.providers[constants.ProviderName]; if (APIGatewayAuthURL) {