diff --git a/.circleci/config.base.yml b/.circleci/config.base.yml index 9d3d92b2d9c..a5590e74fd7 100644 --- a/.circleci/config.base.yml +++ b/.circleci/config.base.yml @@ -366,6 +366,30 @@ jobs: path: ~/repo/packages/amplify-migration-tests/amplify-migration-reports working_directory: ~/repo + amplify_migration_tests_overrides: + <<: *defaults + environment: + AMPLIFY_PATH: /home/circleci/.npm-global/lib/node_modules/@aws-amplify/cli/bin/amplify + steps: + - attach_workspace: + at: ./ + - restore_cache: + key: amplify-cli-yarn-deps-{{ .Branch }}-{{ checksum "yarn.lock" }} + - run: + name: Run tests migrating from CLI v6.0.1 + command: | + source .circleci/local_publish_helpers.sh + changeNpmGlobalPath + cd packages/amplify-migration-tests + yarn run migration_v6.0.1 --maxWorkers=3 $TEST_SUITE + no_output_timeout: 90m + - run: *scan_e2e_test_artifacts + - store_test_results: + path: packages/amplify-migration-tests/ + - store_artifacts: + path: ~/repo/packages/amplify-migration-tests/amplify-migration-reports + working_directory: ~/repo + amplify_migration_tests_v4_30_0: <<: *defaults environment: diff --git a/packages/amplify-cli-core/src/feature-flags/featureFlags.ts b/packages/amplify-cli-core/src/feature-flags/featureFlags.ts index e611a2b4b96..61c4b2717d3 100644 --- a/packages/amplify-cli-core/src/feature-flags/featureFlags.ts +++ b/packages/amplify-cli-core/src/feature-flags/featureFlags.ts @@ -724,13 +724,13 @@ export class FeatureFlags { name: 'auth', type: 'boolean', defaultValueForExistingProjects: false, - defaultValueForNewProjects: false, + defaultValueForNewProjects: true, }, { name: 'project', type: 'boolean', defaultValueForExistingProjects: false, - defaultValueForNewProjects: false, + defaultValueForNewProjects: true, }, ]); }; diff --git a/packages/amplify-cli-core/src/overrides-manager/override-skeleton-generator.ts b/packages/amplify-cli-core/src/overrides-manager/override-skeleton-generator.ts index dce84ee061d..213c85f5e8f 100644 --- a/packages/amplify-cli-core/src/overrides-manager/override-skeleton-generator.ts +++ b/packages/amplify-cli-core/src/overrides-manager/override-skeleton-generator.ts @@ -2,7 +2,7 @@ import fs from 'fs-extra'; import { $TSContext, getPackageManager } from '../index'; import execa from 'execa'; import * as path from 'path'; -import { printer } from 'amplify-prompts'; +import { printer, prompter } from 'amplify-prompts'; import { JSONUtilities } from '../jsonUtilities'; export const generateOverrideSkeleton = async (context: $TSContext, srcResourceDirPath: string, destDirPath: string): Promise => { @@ -25,10 +25,13 @@ export const generateOverrideSkeleton = async (context: $TSContext, srcResourceD await buildOverrideDir(backendDir, destDirPath); printer.success(`Successfully generated "override.ts" folder at ${destDirPath}`); - await context.amplify.openEditor(context, overrideFile); + const isOpen = await prompter.confirmContinue('Do you want to edit override.ts file now?'); + if (isOpen) { + await context.amplify.openEditor(context, overrideFile); + } }; -export async function buildOverrideDir(cwd: string, destDirPath: string) { +export async function buildOverrideDir(cwd: string, destDirPath: string): Promise { const packageManager = getPackageManager(cwd); if (packageManager === null) { @@ -41,6 +44,15 @@ export async function buildOverrideDir(cwd: string, destDirPath: string) { stdio: 'pipe', encoding: 'utf-8', }); + // run tsc build to build override.ts file + const tsConfigDir = path.join(destDirPath, 'build'); + const tsConfigFilePath = path.join(tsConfigDir, 'tsconfig.resource.json'); + execa.sync('tsc', [`--project`, `${tsConfigFilePath}`], { + cwd: tsConfigDir, + stdio: 'pipe', + encoding: 'utf-8', + }); + return true; } catch (error) { if ((error as any).code === 'ENOENT') { throw new Error(`Packaging overrides failed. Could not find ${packageManager} executable in the PATH.`); @@ -48,15 +60,6 @@ export async function buildOverrideDir(cwd: string, destDirPath: string) { throw new Error(`Packaging overrides failed with the error \n${error.message}`); } } - - // run tsc build to build override.ts file - const tsConfigDir = path.join(destDirPath, 'build'); - const tsConfigFilePath = path.join(tsConfigDir, 'tsconfig.resource.json'); - execa.sync('tsc', [`--project`, `${tsConfigFilePath}`], { - cwd: tsConfigDir, - stdio: 'pipe', - encoding: 'utf-8', - }); } export const generateAmplifyOverrideProjectBuildFiles = (backendDir: string, srcResourceDirPath: string) => { @@ -64,12 +67,14 @@ export const generateAmplifyOverrideProjectBuildFiles = (backendDir: string, src const tsConfigFilePath = path.join(backendDir, 'tsconfig.json'); // add package.json to amplofy backend if (!fs.existsSync(packageJSONFilePath)) { - JSONUtilities.writeJson(packageJSONFilePath, JSONUtilities.readJson(path.join(srcResourceDirPath, 'package.json'))); + const packageJson = JSONUtilities.readJson(path.join(srcResourceDirPath, 'package.json')); + JSONUtilities.writeJson(packageJSONFilePath, packageJson); } // add tsConfig.json to amplify backend if (!fs.existsSync(tsConfigFilePath)) { - JSONUtilities.writeJson(tsConfigFilePath, JSONUtilities.readJson(path.join(srcResourceDirPath, 'tsconfig.json'))); + const tsConfigJson = JSONUtilities.readJson(path.join(srcResourceDirPath, 'tsconfig.json')); + JSONUtilities.writeJson(tsConfigFilePath, tsConfigJson); } }; diff --git a/packages/amplify-cli-core/src/state-manager/pathManager.ts b/packages/amplify-cli-core/src/state-manager/pathManager.ts index c5a09400f29..f70d6f7ea36 100644 --- a/packages/amplify-cli-core/src/state-manager/pathManager.ts +++ b/packages/amplify-cli-core/src/state-manager/pathManager.ts @@ -198,7 +198,7 @@ export class PathManager { ]); }; - getRootStackDirPath = (projectPath: string): string => { + getRootStackBuildDirPath = (projectPath: string): string => { return this.constructPath(projectPath, [ PathConstants.AmplifyDirName, PathConstants.BackendDirName, diff --git a/packages/amplify-cli-overrides-helper/package.json b/packages/amplify-cli-overrides-helper/package.json index ce1d0d9a90a..0add01b4871 100644 --- a/packages/amplify-cli-overrides-helper/package.json +++ b/packages/amplify-cli-overrides-helper/package.json @@ -27,7 +27,8 @@ "clean": "rimraf ./lib" }, "dependencies": { - "amplify-prompts": "1.1.2" + "amplify-prompts": "1.1.2", + "amplify-provider-awscloudformation": "4.60.1" }, "devDependencies": {}, "jest": { diff --git a/packages/amplify-cli-overrides-helper/src/index.ts b/packages/amplify-cli-overrides-helper/src/index.ts index 950b9bab82b..d8284ac82d6 100644 --- a/packages/amplify-cli-overrides-helper/src/index.ts +++ b/packages/amplify-cli-overrides-helper/src/index.ts @@ -2,12 +2,10 @@ import { printer } from 'amplify-prompts'; function getProjectInfo(): void { printer.info('Hello from the skeleton of get project info'); - return; } function addDependency(): void { printer.info('Hello from the skeleton of add dependency'); - return; } export { getProjectInfo, addDependency }; diff --git a/packages/amplify-cli-overrides-helper/tsconfig.json b/packages/amplify-cli-overrides-helper/tsconfig.json index 4db720dc27c..ec3ef0706fb 100644 --- a/packages/amplify-cli-overrides-helper/tsconfig.json +++ b/packages/amplify-cli-overrides-helper/tsconfig.json @@ -3,6 +3,10 @@ "compilerOptions": { "outDir": "./lib", "rootDir": "./src", - } - } + "module": "ESNext" + }, + "references": [ + { "path": "../amplify-provider-awscloudformation" }, + ] +} \ No newline at end of file diff --git a/packages/amplify-cli/src/__tests__/commands/build-override.test.ts b/packages/amplify-cli/src/__tests__/commands/build-override.test.ts index 92d8d7d0223..666ad6d2824 100644 --- a/packages/amplify-cli/src/__tests__/commands/build-override.test.ts +++ b/packages/amplify-cli/src/__tests__/commands/build-override.test.ts @@ -5,7 +5,7 @@ jest.mock('amplify-cli-core'); jest.mock('amplify-provider-awscloudformation'); describe('run build-override command', () => { - it('runs command successfully', async () => { + it('runs override command for only a resource', async () => { const context_stub = { amplify: { getResourceStatus: jest.fn().mockResolvedValue({ @@ -38,15 +38,100 @@ describe('run build-override command', () => { confirmPrompt: jest.fn().mockResolvedValue(true), invokePluginMethod: jest.fn(), }, + input: { + subCommands: ['mockcategory1', 'mockResourceName1'], + }, + }; + + const context_stub_typed = context_stub as unknown as $TSContext; + await run(context_stub_typed); + expect(context_stub_typed.amplify.invokePluginMethod).toBeCalledTimes(1); + }); + + it('runs override command for only all resources in a category', async () => { + const context_stub = { + amplify: { + getResourceStatus: jest.fn().mockResolvedValue({ + resourcesToBeCreated: [ + { + category: 'mockcategory1', + service: 'mockservice1', + resourceName: 'mockResourceName1', + }, + { + category: 'mockcategory1', + service: 'mockservice2', + resourceName: 'mockResourceName2', + }, + ], + resourcesToBeUpdated: [ + { + category: 'mockcategory1', + service: 'mockservice3', + resourceName: 'mockResourceName3', + }, + { + category: 'mockcategory4', + service: 'mockservice4', + resourceName: 'mockResourceName4', + }, + ], + }), + + confirmPrompt: jest.fn().mockResolvedValue(true), + invokePluginMethod: jest.fn(), + }, + input: { + subCommands: ['mockcategory1'], + }, }; const context_stub_typed = context_stub as unknown as $TSContext; await run(context_stub_typed); - expect(context_stub_typed.amplify.invokePluginMethod).toBeCalledTimes(4); + expect(context_stub_typed.amplify.invokePluginMethod).toBeCalledTimes(3); + }); + + it('runs override command successfully for all resources in all categories', async () => { + const context_stub = { + amplify: { + getResourceStatus: jest.fn().mockResolvedValue({ + resourcesToBeCreated: [ + { + category: 'mockcategory1', + service: 'mockservice1', + resourceName: 'mockResourceName1', + }, + { + category: 'mockcategory2', + service: 'mockservice2', + resourceName: 'mockResourceName2', + }, + ], + resourcesToBeUpdated: [ + { + category: 'mockcategory3', + service: 'mockservice3', + resourceName: 'mockResourceName3', + }, + { + category: 'mockcategory4', + service: 'mockservice4', + resourceName: 'mockResourceName4', + }, + ], + }), + + confirmPrompt: jest.fn().mockResolvedValue(true), + invokePluginMethod: jest.fn(), + }, + }; + + const context_stub_typed = context_stub as unknown as $TSContext; + await run(context_stub_typed); + expect(context_stub_typed.amplify.invokePluginMethod).toBeCalledTimes(5); }); it('runs command successfully empty Arrays', async () => { - jest.clearAllMocks(); const context_stub = { amplify: { getResourceStatus: jest.fn().mockResolvedValue({ @@ -55,11 +140,12 @@ describe('run build-override command', () => { }), confirmPrompt: jest.fn().mockResolvedValue(true), + invokePluginMethod: jest.fn(), }, }; const context_stub_typed = context_stub as unknown as $TSContext; await run(context_stub_typed); - expect(context_stub_typed.amplify.invokePluginMethod).toBeUndefined(); + expect(context_stub_typed.amplify.invokePluginMethod).toBeCalledTimes(1); }); }); diff --git a/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/resource-status-diff.test.ts b/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/resource-status-diff.test.ts index e20ec013117..93401a20062 100644 --- a/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/resource-status-diff.test.ts +++ b/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/resource-status-diff.test.ts @@ -1,12 +1,10 @@ -import { capitalize, - globCFNFilePath, - ResourceDiff, - stackMutationType} from '../../../extensions/amplify-helpers/resource-status-diff'; +import { capitalize, globCFNFilePath, ResourceDiff, stackMutationType } from '../../../extensions/amplify-helpers/resource-status-diff'; import { CLOUD_INITIALIZED } from '../../../extensions/amplify-helpers/get-cloud-init-status'; import glob from 'glob'; import path from 'path'; import * as fs from 'fs-extra'; -import { stateManager, pathManager} from 'amplify-cli-core'; +import { stateManager, pathManager } from 'amplify-cli-core'; +import { cronJobSetting } from '../../../../../amplify-category-function/lib/provider-utils/awscloudformation/utils/constants'; //Mock Glob to fetch test cloudformation jest.mock('glob'); @@ -14,155 +12,180 @@ const localBackendDirPathStub = 'localBackendDirPath'; const currentBackendDirPathStub = 'currentCloudBackendPath'; const testApiName = 'testApiName'; const globMock = glob as jest.Mocked; -const allFiles: string[] = [ "cloudformation-template.json", "parameters.json" , "resolvers", - "stacks" ,"functions", "pipelinesFunctions", "schema.graphql" ] -const templateMatchRegex = ".*template\.(json|yaml|yml)$" -globMock.sync.mockImplementation(() => allFiles.filter( fname => fname.match(templateMatchRegex) )); - +const allFiles: string[] = [ + 'cloudformation-template.json', + 'parameters.json', + 'resolvers', + 'stacks', + 'functions', + 'pipelinesFunctions', + 'schema.graphql', +]; +const templateMatchRegex = '.*template.(json|yaml|yml)$'; +globMock.sync.mockImplementation(() => allFiles.filter(fname => fname.match(templateMatchRegex))); //Mock fs to pass all file-system checks jest.mock('fs-extra', () => ({ - ...(jest.requireActual('fs-extra') as {}), - existsSync: jest.fn().mockImplementation(()=> true), - statSync: jest.fn().mockReturnValue({ isFile: ()=>true } as fs.Stats ) + ...(jest.requireActual('fs-extra') as {}), + existsSync: jest.fn().mockImplementation(() => true), + statSync: jest.fn().mockReturnValue({ isFile: () => true } as fs.Stats), })); - +jest.mock('amplify-cli-core', () => ({ + ...(jest.requireActual('amplify-cli-core') as {}), + FeatureFlags: { + getBoolean: jest.fn(), + getNumber: jest.fn(), + }, +})); describe('resource-status-diff helpers', () => { - - beforeAll(() => { - jest.unmock('amplify-cli-core'); - }) - - it('capitalize should capitalize strings', async () => { - const mockInput = "abcd"; - const expectedOutput = "Abcd"; - expect(capitalize(mockInput)).toBe( expectedOutput ); - }); - - it('should Glob only cloudformation template files ', async () => { - - const mockCloudformationTemplateName = "cloudformation-template.json" - const stubFileFolder = "stub-file-folder" - const expectedGlobOptions = { - absolute: false, - cwd: stubFileFolder, - follow: false, - nodir: true, - }; - - const cfnFilename = globCFNFilePath( stubFileFolder ); - expect(globMock.sync.mock.calls.length).toBe(1); - expect(globMock.sync).toBeCalledWith('**/*template.{yaml,yml,json}', expectedGlobOptions); - expect( cfnFilename ).toBe(`${stubFileFolder}/${mockCloudformationTemplateName}`); - }); - - it('should search both Build and non Build folders for Cloudformation Templates ', async () => { - const mockGraphQLAPIMeta = { - providers: { - awscloudformation: { - Region: 'myMockRegion', - }, - }, - 'api': { - [testApiName] : { - service : 'AppSync' - } - } - }; - - //Enable cloud initialized to test updates , but retain all other functions - jest.mock('../../../extensions/amplify-helpers/get-cloud-init-status', () => ({ - ...(jest.requireActual('../../../extensions/amplify-helpers/get-cloud-init-status') as {}), - getCloudInitStatus: jest.fn().mockImplementation(()=> CLOUD_INITIALIZED), - })); - - //helper to mock common dependencies - const setMockTestCommonDependencies = ()=>{ - jest.mock('amplify-cli-core'); - const pathManagerMock = pathManager as jest.Mocked; - pathManagerMock.getBackendDirPath = jest.fn().mockImplementation(() => localBackendDirPathStub); - pathManagerMock.getCurrentCloudBackendDirPath = jest.fn().mockImplementation(() => currentBackendDirPathStub); - - const stateManagerMock = stateManager as jest.Mocked; - stateManagerMock.getMeta = jest.fn().mockImplementation(()=> mockGraphQLAPIMeta); - stateManagerMock.getCurrentMeta = jest.fn().mockImplementation(()=> mockGraphQLAPIMeta); - - } - - const getMockInputData = ()=>{ - - return { - mockDefaultRootCfnTmpltName : "cloudformation-template.json", - mockCategory : "api", - mockResourceName : testApiName, - mockProvider : "awscloudformation", - normalizedProvider : "cloudformation", - mockMutationInfo: stackMutationType.UPDATE - } - } - - //Test mocks - setMockTestCommonDependencies(); - const input = getMockInputData(); - - /** 1. Code-Under-Test - constructor **/ - const resourceDiff = new ResourceDiff(input.mockCategory, input.mockResourceName, input.mockProvider , input.mockMutationInfo); - - //Test Definitions - const checkProviderNormalization = ()=> { - expect(resourceDiff.provider ).toBe(input.normalizedProvider); - } - - const checkCfnPaths = ()=> { - const mockLocalPreBuildCfnFile = path.join(localBackendDirPathStub, input.mockCategory, input.mockResourceName, input.mockDefaultRootCfnTmpltName); - const mockCurrentPreBuildCfnFile = path.join(currentBackendDirPathStub, input.mockCategory, input.mockResourceName, input.mockDefaultRootCfnTmpltName); - const mockLocalPostBuildCfnFile = path.join(localBackendDirPathStub,input.mockCategory, input.mockResourceName,'build', input.mockDefaultRootCfnTmpltName); - const mockCurrentPostBuildCfnFile = path.join(currentBackendDirPathStub,input.mockCategory, input.mockResourceName,'build', input.mockDefaultRootCfnTmpltName); - expect(resourceDiff.resourceFiles.localPreBuildCfnFile).toBe(mockLocalPreBuildCfnFile); - expect(resourceDiff.resourceFiles.cloudPreBuildCfnFile).toBe(mockCurrentPreBuildCfnFile); - expect(resourceDiff.resourceFiles.localBuildCfnFile).toBe(mockLocalPostBuildCfnFile); - expect(resourceDiff.resourceFiles.cloudBuildCfnFile).toBe(mockCurrentPostBuildCfnFile); - } - - //Test Execution - checkProviderNormalization(); - checkCfnPaths(); - - - }) - - it ( 'should show the diff between local and remote cloudformation ', async ()=>{ - - const getMockInputData = ()=>{ - - return { - mockDefaultRootCfnTmpltName : "cloudformation-template.json", - mockCategory : "api", - mockResourceName : testApiName, - mockProvider : "awscloudformation", - normalizedProvider : "cloudformation", - mockMutationInfo: stackMutationType.UPDATE - } - } - - const input = getMockInputData(); - - /** 1. Code-Under-Test - constructor **/ - const resourceDiff = new ResourceDiff(input.mockCategory, input.mockResourceName, input.mockProvider , input.mockMutationInfo); - - /** 2. Code-Under-Test calculateCfnDiff **/ - //update sample cloudformation paths in resourceDiff - const localPath = `${__dirname}/testData/mockLocalCloud/cloudformation-template.json`; - const cloudPath = `${__dirname}/testData/mockCurrentCloud/cloudformation-template.json`; - //override paths to point to reference cloudformation templates - resourceDiff.resourceFiles.localBuildCfnFile = localPath; - resourceDiff.resourceFiles.cloudBuildCfnFile = cloudPath; - resourceDiff.resourceFiles.localPreBuildCfnFile = localPath; - resourceDiff.resourceFiles.cloudPreBuildCfnFile = cloudPath; - const diff = await resourceDiff.calculateCfnDiff(); - expect( diff ).toMatchSnapshot(); - }) - -}); \ No newline at end of file + beforeAll(() => { + jest.unmock('amplify-cli-core'); + }); + + it('capitalize should capitalize strings', async () => { + const mockInput = 'abcd'; + const expectedOutput = 'Abcd'; + expect(capitalize(mockInput)).toBe(expectedOutput); + }); + + it('should Glob only cloudformation template files ', async () => { + const mockCloudformationTemplateName = 'cloudformation-template.json'; + const stubFileFolder = 'stub-file-folder'; + const expectedGlobOptions = { + absolute: false, + cwd: stubFileFolder, + follow: false, + nodir: true, + }; + + const cfnFilename = globCFNFilePath(stubFileFolder); + expect(globMock.sync.mock.calls.length).toBe(1); + expect(globMock.sync).toBeCalledWith('**/*template.{yaml,yml,json}', expectedGlobOptions); + expect(cfnFilename).toBe(`${stubFileFolder}/${mockCloudformationTemplateName}`); + }); + + it('should search both Build and non Build folders for Cloudformation Templates ', async () => { + const mockGraphQLAPIMeta = { + providers: { + awscloudformation: { + Region: 'myMockRegion', + }, + }, + api: { + [testApiName]: { + service: 'AppSync', + }, + }, + }; + + //Enable cloud initialized to test updates , but retain all other functions + jest.mock('../../../extensions/amplify-helpers/get-cloud-init-status', () => ({ + ...(jest.requireActual('../../../extensions/amplify-helpers/get-cloud-init-status') as {}), + getCloudInitStatus: jest.fn().mockImplementation(() => CLOUD_INITIALIZED), + })); + + //helper to mock common dependencies + const setMockTestCommonDependencies = () => { + jest.mock('amplify-cli-core'); + const pathManagerMock = pathManager as jest.Mocked; + pathManagerMock.getBackendDirPath = jest.fn().mockImplementation(() => localBackendDirPathStub); + pathManagerMock.getCurrentCloudBackendDirPath = jest.fn().mockImplementation(() => currentBackendDirPathStub); + + const stateManagerMock = stateManager as jest.Mocked; + stateManagerMock.getMeta = jest.fn().mockImplementation(() => mockGraphQLAPIMeta); + stateManagerMock.getCurrentMeta = jest.fn().mockImplementation(() => mockGraphQLAPIMeta); + }; + + const getMockInputData = () => { + return { + mockDefaultRootCfnTmpltName: 'cloudformation-template.json', + mockCategory: 'api', + mockResourceName: testApiName, + mockProvider: 'awscloudformation', + normalizedProvider: 'cloudformation', + mockMutationInfo: stackMutationType.UPDATE, + }; + }; + + //Test mocks + setMockTestCommonDependencies(); + const input = getMockInputData(); + + /** 1. Code-Under-Test - constructor **/ + const resourceDiff = new ResourceDiff(input.mockCategory, input.mockResourceName, input.mockProvider, input.mockMutationInfo); + + //Test Definitions + const checkProviderNormalization = () => { + expect(resourceDiff.provider).toBe(input.normalizedProvider); + }; + + const checkCfnPaths = () => { + const mockLocalPreBuildCfnFile = path.join( + localBackendDirPathStub, + input.mockCategory, + input.mockResourceName, + input.mockDefaultRootCfnTmpltName, + ); + const mockCurrentPreBuildCfnFile = path.join( + currentBackendDirPathStub, + input.mockCategory, + input.mockResourceName, + input.mockDefaultRootCfnTmpltName, + ); + const mockLocalPostBuildCfnFile = path.join( + localBackendDirPathStub, + input.mockCategory, + input.mockResourceName, + 'build', + input.mockDefaultRootCfnTmpltName, + ); + const mockCurrentPostBuildCfnFile = path.join( + currentBackendDirPathStub, + input.mockCategory, + input.mockResourceName, + 'build', + input.mockDefaultRootCfnTmpltName, + ); + expect(resourceDiff.resourceFiles.localPreBuildCfnFile).toBe(mockLocalPreBuildCfnFile); + expect(resourceDiff.resourceFiles.cloudPreBuildCfnFile).toBe(mockCurrentPreBuildCfnFile); + expect(resourceDiff.resourceFiles.localBuildCfnFile).toBe(mockLocalPostBuildCfnFile); + expect(resourceDiff.resourceFiles.cloudBuildCfnFile).toBe(mockCurrentPostBuildCfnFile); + }; + + //Test Execution + checkProviderNormalization(); + checkCfnPaths(); + }); + + it('should show the diff between local and remote cloudformation ', async () => { + const getMockInputData = () => { + return { + mockDefaultRootCfnTmpltName: 'cloudformation-template.json', + mockCategory: 'api', + mockResourceName: testApiName, + mockProvider: 'awscloudformation', + normalizedProvider: 'cloudformation', + mockMutationInfo: stackMutationType.UPDATE, + }; + }; + + const input = getMockInputData(); + + /** 1. Code-Under-Test - constructor **/ + const resourceDiff = new ResourceDiff(input.mockCategory, input.mockResourceName, input.mockProvider, input.mockMutationInfo); + + /** 2. Code-Under-Test calculateCfnDiff **/ + //update sample cloudformation paths in resourceDiff + const localPath = `${__dirname}/testData/mockLocalCloud/cloudformation-template.json`; + const cloudPath = `${__dirname}/testData/mockCurrentCloud/cloudformation-template.json`; + //override paths to point to reference cloudformation templates + resourceDiff.resourceFiles.localBuildCfnFile = localPath; + resourceDiff.resourceFiles.cloudBuildCfnFile = cloudPath; + resourceDiff.resourceFiles.localPreBuildCfnFile = localPath; + resourceDiff.resourceFiles.cloudPreBuildCfnFile = cloudPath; + const diff = await resourceDiff.calculateCfnDiff(); + expect(diff).toMatchSnapshot(); + }); +}); diff --git a/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/resource-status.test.ts b/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/resource-status.test.ts index b313026943e..690a56e6b5d 100644 --- a/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/resource-status.test.ts +++ b/packages/amplify-cli/src/__tests__/extensions/amplify-helpers/resource-status.test.ts @@ -12,6 +12,7 @@ import { } from '../../../extensions/amplify-helpers/get-cloud-init-status'; import { stateManager } from 'amplify-cli-core'; import { hashLayerResource } from 'amplify-category-function'; +import { cronJobSetting } from '../../../../../amplify-category-function/lib/provider-utils/awscloudformation/utils/constants'; const sample_hash1 = 'testhash1'; const sample_hash2 = 'testhash2'; @@ -51,6 +52,10 @@ jest.mock('../../../extensions/amplify-helpers/get-cloud-init-status', () => ({ getCloudInitStatus: jest.fn(), })); +jest.mock('../../../extensions/amplify-helpers/root-stack-status', () => ({ + isRootStackModifiedSinceLastPush: jest.fn().mockResolvedValue(false), +})); + const backendDirPathStub = 'backendDirPath'; const currentBackendDirPathStub = 'currentBackendDirPathStub'; const projectRootPath = 'projectRootPath'; @@ -70,6 +75,9 @@ jest.mock('amplify-cli-core', () => ({ getCurrentCloudBackendDirPath: jest.fn(() => currentBackendDirPathStub), findProjectRoot: jest.fn(() => projectRootPath), }, + FeatureFlags: { + getBoolean: jest.fn().mockReturnValue(true), + }, })); jest.mock('amplify-category-function', () => ({ @@ -145,6 +153,7 @@ describe('resource-status', () => { resourcesToBeDeleted: [], resourcesToBeSynced: [], resourcesToBeUpdated: [], + rootStackUpdated: false, tagsUpdated: false, }); }); @@ -184,6 +193,7 @@ describe('resource-status', () => { resourcesToBeSynced: [], resourcesToBeUpdated: [], tagsUpdated: false, + rootStackUpdated: false, }); }); @@ -246,6 +256,7 @@ describe('resource-status', () => { resourcesToBeSynced: [], resourcesToBeUpdated: [], tagsUpdated: false, + rootStackUpdated: false, }); }); @@ -284,6 +295,7 @@ describe('resource-status', () => { resourcesToBeSynced: [], resourcesToBeUpdated: [], tagsUpdated: false, + rootStackUpdated: false, }); }); @@ -336,6 +348,7 @@ describe('resource-status', () => { ], resourcesToBeUpdated: [], tagsUpdated: false, + rootStackUpdated: false, }); }); @@ -380,6 +393,7 @@ describe('resource-status', () => { ], resourcesToBeUpdated: [], tagsUpdated: false, + rootStackUpdated: false, }); }); @@ -439,6 +453,7 @@ describe('resource-status', () => { ], resourcesToBeUpdated: [], tagsUpdated: false, + rootStackUpdated: false, }); }); @@ -502,6 +517,7 @@ describe('resource-status', () => { }, ], tagsUpdated: false, + rootStackUpdated: false, }); }); @@ -559,6 +575,7 @@ describe('resource-status', () => { }, ], tagsUpdated: false, + rootStackUpdated: false, }); }); @@ -614,6 +631,7 @@ describe('resource-status', () => { }, ], tagsUpdated: false, + rootStackUpdated: false, }); }); @@ -669,6 +687,7 @@ describe('resource-status', () => { }, ], tagsUpdated: false, + rootStackUpdated: false, }); }); @@ -699,6 +718,7 @@ describe('resource-status', () => { resourcesToBeSynced: [], resourcesToBeUpdated: [], tagsUpdated: true, + rootStackUpdated: false, }); }); @@ -713,6 +733,7 @@ describe('resource-status', () => { resourcesToBeSynced: [], resourcesToBeUpdated: [], tagsUpdated: false, + rootStackUpdated: false, }); }); diff --git a/packages/amplify-cli/src/commands/build-override.ts b/packages/amplify-cli/src/commands/build-override.ts index 05b0dc4fa7a..4f85a91b4bf 100644 --- a/packages/amplify-cli/src/commands/build-override.ts +++ b/packages/amplify-cli/src/commands/build-override.ts @@ -1,13 +1,14 @@ import { $TSContext, IAmplifyResource } from 'amplify-cli-core'; import { printer } from 'amplify-prompts'; +import { Console } from 'console'; /** * Command to transform CFN with overrides */ const subcommand = 'build-override'; export const run = async (context: $TSContext) => { - let resourceName = context?.input?.subCommands?.[0]; - const categoryName = context?.input?.subCommands?.[1]; + const categoryName = context?.input?.subCommands?.[0]; + let resourceName = context?.input?.subCommands?.[1]; if (categoryName === undefined) { // if no category is mentioned , then defaults to all resource @@ -16,14 +17,19 @@ export const run = async (context: $TSContext) => { try { const resourcesToBuild: IAmplifyResource[] = await getResources(context); - let filteredResources = {}; + let filteredResources: IAmplifyResource[] = resourcesToBuild; if (categoryName) { - filteredResources = resourcesToBuild.filter(resource => resource.category === categoryName); + filteredResources = filteredResources.filter(resource => resource.category === categoryName); } if (categoryName && resourceName) { - filteredResources = resourcesToBuild.filter(resource => resource.category === categoryName && resource.resourceName === resourceName); + filteredResources = filteredResources.filter( + resource => resource.category === categoryName && resource.resourceName === resourceName, + ); } - for (const resource of resourcesToBuild) { + if (!categoryName && !resourceName) { + await context.amplify.invokePluginMethod(context, 'awscloudformation', undefined, 'transformResourceWithOverrides', [context]); + } + for (const resource of filteredResources) { await context.amplify.invokePluginMethod(context, 'awscloudformation', undefined, 'transformResourceWithOverrides', [ context, resource, diff --git a/packages/amplify-cli/src/extensions/amplify-helpers/resource-status-data.ts b/packages/amplify-cli/src/extensions/amplify-helpers/resource-status-data.ts index 96d2cd2c6a9..14ae622e970 100644 --- a/packages/amplify-cli/src/extensions/amplify-helpers/resource-status-data.ts +++ b/packages/amplify-cli/src/extensions/amplify-helpers/resource-status-data.ts @@ -3,11 +3,12 @@ import * as fs from 'fs-extra'; import * as path from 'path'; import { ServiceName as FunctionServiceName, hashLayerResource } from 'amplify-category-function'; import { removeGetUserEndpoints } from '../amplify-helpers/remove-pinpoint-policy'; -import { pathManager, stateManager, NotInitializedError, ViewResourceTableParams } from 'amplify-cli-core'; +import { pathManager, stateManager, NotInitializedError, ViewResourceTableParams, FeatureFlags } from 'amplify-cli-core'; import { hashElement, HashElementOptions } from 'folder-hash'; import { CLOUD_INITIALIZED, CLOUD_NOT_INITIALIZED, getCloudInitStatus } from './get-cloud-init-status'; import * as resourceStatus from './resource-status-diff'; import { IResourceDiffCollection, capitalize } from './resource-status-diff'; +import { getHashForRootStack, isRootStackModifiedSinceLastPush } from './root-stack-status'; //API: Filter resource status for the given categories export async function getMultiCategoryStatus(inputs: ViewResourceTableParams | undefined) { @@ -131,14 +132,29 @@ export async function getResourceStatus( // if not equal there is a tag update const tagsUpdated = !_.isEqual(stateManager.getProjectTags(), stateManager.getCurrentProjectTags()); - return { - resourcesToBeCreated, - resourcesToBeUpdated, - resourcesToBeSynced, - resourcesToBeDeleted, - tagsUpdated, - allResources, - }; + // if not equal there is a root stack update + if (FeatureFlags.getBoolean('overrides.project')) { + const rootStackUpdated = await isRootStackModifiedSinceLastPush(getHashForRootStack); + + return { + resourcesToBeCreated, + resourcesToBeUpdated, + resourcesToBeSynced, + resourcesToBeDeleted, + rootStackUpdated, + tagsUpdated, + allResources, + }; + } else { + return { + resourcesToBeCreated, + resourcesToBeUpdated, + resourcesToBeSynced, + resourcesToBeDeleted, + tagsUpdated, + allResources, + }; + } } export function getAllResources(amplifyMeta, category, resourceName, filteredResources) { diff --git a/packages/amplify-cli/src/extensions/amplify-helpers/resource-status-diff.ts b/packages/amplify-cli/src/extensions/amplify-helpers/resource-status-diff.ts index 0083a8b10c3..e97071c2053 100644 --- a/packages/amplify-cli/src/extensions/amplify-helpers/resource-status-diff.ts +++ b/packages/amplify-cli/src/extensions/amplify-helpers/resource-status-diff.ts @@ -249,6 +249,7 @@ export interface ICategoryStatusCollection { resourcesToBeUpdated: any[]; resourcesToBeDeleted: any[]; resourcesToBeSynced: any[]; + rootStackUpdated?: boolean; allResources: any[]; tagsUpdated: boolean; } diff --git a/packages/amplify-cli/src/extensions/amplify-helpers/resource-status.ts b/packages/amplify-cli/src/extensions/amplify-helpers/resource-status.ts index 9db66ff7322..1ac416c8e4d 100644 --- a/packages/amplify-cli/src/extensions/amplify-helpers/resource-status.ts +++ b/packages/amplify-cli/src/extensions/amplify-helpers/resource-status.ts @@ -1,55 +1,51 @@ - - +import _ from 'lodash'; import { print } from './print'; -import { CLOUD_INITIALIZED, getCloudInitStatus } from './get-cloud-init-status'; -import { ViewResourceTableParams } from "amplify-cli-core"; +import { getEnvInfo } from './get-env-info'; +import { CLOUD_INITIALIZED, getCloudInitStatus } from './get-cloud-init-status'; +import { ViewResourceTableParams } from 'amplify-cli-core'; import { viewSummaryTable, viewEnvInfo, viewResourceDiffs } from './resource-status-view'; import { getMultiCategoryStatus, getResourceStatus, getHashForResourceDir } from './resource-status-data'; -import { getEnvInfo } from './get-env-info'; import chalk from 'chalk'; -export { getResourceStatus, getHashForResourceDir } +export { getResourceStatus, getHashForResourceDir }; -export async function showStatusTable( tableViewFilter : ViewResourceTableParams ){ - const amplifyProjectInitStatus = getCloudInitStatus(); - const { - resourcesToBeCreated, - resourcesToBeUpdated, - resourcesToBeDeleted, - resourcesToBeSynced, - allResources, - tagsUpdated, - } = await getMultiCategoryStatus(tableViewFilter); +export async function showStatusTable(tableViewFilter: ViewResourceTableParams) { + const amplifyProjectInitStatus = getCloudInitStatus(); + const { + resourcesToBeCreated, + resourcesToBeUpdated, + resourcesToBeDeleted, + resourcesToBeSynced, + rootStackUpdated, + allResources, + tagsUpdated, + } = await getMultiCategoryStatus(tableViewFilter); + + //1. Display Environment Info + if (amplifyProjectInitStatus === CLOUD_INITIALIZED) { + viewEnvInfo(); + } + //2. Display Summary Table + viewSummaryTable({ resourcesToBeUpdated, resourcesToBeCreated, resourcesToBeDeleted, resourcesToBeSynced, allResources }); + //3. Display Tags Status + if (tagsUpdated) { + print.info('\nTag Changes Detected'); + } - //1. Display Environment Info - if (amplifyProjectInitStatus === CLOUD_INITIALIZED) { - viewEnvInfo(); - } - //2. Display Summary Table - viewSummaryTable({ resourcesToBeUpdated, - resourcesToBeCreated, - resourcesToBeDeleted, - resourcesToBeSynced, - allResources - }); - //3. Display Tags Status - if (tagsUpdated) { - print.info('\nTag Changes Detected'); - } + //4. Display Root Stack Status + if (rootStackUpdated) { + print.info('Root Stack Changes Detected'); + } - //4. Display Detailed Diffs (Cfn/NonCfn) - if ( tableViewFilter.verbose ) { - await viewResourceDiffs( { resourcesToBeUpdated, - resourcesToBeDeleted, - resourcesToBeCreated } ); - } + //4. Display Detailed Diffs (Cfn/NonCfn) + if (tableViewFilter.verbose) { + await viewResourceDiffs({ resourcesToBeUpdated, resourcesToBeDeleted, resourcesToBeCreated }); + } - const resourceChanged = resourcesToBeCreated.length + - resourcesToBeUpdated.length + - resourcesToBeSynced.length + - resourcesToBeDeleted.length > 0 || tagsUpdated; + const resourceChanged = + resourcesToBeCreated.length + resourcesToBeUpdated.length + resourcesToBeSynced.length + resourcesToBeDeleted.length > 0 || tagsUpdated; - return resourceChanged; + return resourceChanged; } export async function showResourceTable(category?, resourceName?, filteredResources?) { @@ -63,6 +59,7 @@ export async function showResourceTable(category?, resourceName?, filteredResour resourcesToBeSynced, allResources, tagsUpdated, + rootStackUpdated, } = await getResourceStatus(category, resourceName, undefined, filteredResources); //1. Display Environment Info @@ -70,19 +67,21 @@ export async function showResourceTable(category?, resourceName?, filteredResour viewEnvInfo(); } //2. Display Summary Table - viewSummaryTable({ resourcesToBeUpdated, - resourcesToBeCreated, - resourcesToBeDeleted, - resourcesToBeSynced, - allResources - }); + viewSummaryTable({ resourcesToBeUpdated, resourcesToBeCreated, resourcesToBeDeleted, resourcesToBeSynced, allResources }); //3. Display Tags Status if (tagsUpdated) { print.info('\nTag Changes Detected'); } + //4. Display root stack Status + if (rootStackUpdated) { + print.info('\n RootStack Changes Detected'); + } + const resourceChanged = - resourcesToBeCreated.length + resourcesToBeUpdated.length + resourcesToBeSynced.length + resourcesToBeDeleted.length > 0 || tagsUpdated; + resourcesToBeCreated.length + resourcesToBeUpdated.length + resourcesToBeSynced.length + resourcesToBeDeleted.length > 0 || + tagsUpdated || + rootStackUpdated; return resourceChanged; } diff --git a/packages/amplify-cli/src/extensions/amplify-helpers/root-stack-status.ts b/packages/amplify-cli/src/extensions/amplify-helpers/root-stack-status.ts new file mode 100644 index 00000000000..d2a8f659d89 --- /dev/null +++ b/packages/amplify-cli/src/extensions/amplify-helpers/root-stack-status.ts @@ -0,0 +1,35 @@ +import { pathManager } from 'amplify-cli-core'; +import { rootStackFileName } from 'amplify-provider-awscloudformation'; +import { hashElement, HashElementOptions } from 'folder-hash'; +import * as fs from 'fs-extra'; + +export function getHashForRootStack(dirPath, files?: string[]) { + const options: HashElementOptions = { + files: { + include: files, + }, + }; + + return hashElement(dirPath, options).then(result => result.hash); +} + +export async function isRootStackModifiedSinceLastPush(hashFunction): Promise { + try { + const projectPath = pathManager.findProjectRoot(); + const localBackendDir = pathManager.getRootStackBuildDirPath(projectPath!); + const cloudBackendDir = pathManager.getCurrentCloudRootStackDirPath(projectPath!); + if (fs.existsSync(localBackendDir)) { + const localDirHash = await hashFunction(localBackendDir, ['*.json']); + if (fs.existsSync(cloudBackendDir)) { + const cloudDirHash = await hashFunction(cloudBackendDir, ['*.json']); + return localDirHash !== cloudDirHash; + } else { + return true; + } + } else { + return false; + } + } catch (error) { + throw new Error('Amplify Project not initialized.'); + } +} diff --git a/packages/amplify-e2e-core/src/init/amplifyPush.ts b/packages/amplify-e2e-core/src/init/amplifyPush.ts index 0639e0bb7cf..41d3e38e12e 100644 --- a/packages/amplify-e2e-core/src/init/amplifyPush.ts +++ b/packages/amplify-e2e-core/src/init/amplifyPush.ts @@ -327,3 +327,28 @@ export function amplifyPushDestructiveApiUpdate(cwd: string, includeForce: boole } }); } + +export function amplifyPushOverride(cwd: string, testingWithLatestCodebase: boolean = false): Promise { + return new Promise((resolve, reject) => { + //Test detailed status + spawn(getCLIPath(testingWithLatestCodebase), ['status', '-v'], { cwd, stripColors: true, noOutputTimeout: pushTimeoutMS }) + .wait(/.*/) + .run((err: Error) => { + if (err) { + reject(err); + } + }); + //Test amplify push + spawn(getCLIPath(testingWithLatestCodebase), ['push'], { cwd, stripColors: true, noOutputTimeout: pushTimeoutMS }) + .wait('Are you sure you want to continue?') + .sendConfirmYes() + .wait(/.*/) + .run((err: Error) => { + if (!err) { + resolve(); + } else { + reject(err); + } + }); + }); +} diff --git a/packages/amplify-e2e-core/src/init/index.ts b/packages/amplify-e2e-core/src/init/index.ts index 3f207ceb65b..b27bca0fddc 100644 --- a/packages/amplify-e2e-core/src/init/index.ts +++ b/packages/amplify-e2e-core/src/init/index.ts @@ -4,3 +4,4 @@ export * from './deleteProject'; export * from './initProjectHelper'; export * from './pull-headless'; export * from './adminUI'; +export * from './overrideRootStack'; diff --git a/packages/amplify-e2e-core/src/init/overrideRootStack.ts b/packages/amplify-e2e-core/src/init/overrideRootStack.ts new file mode 100644 index 00000000000..997c33b9e72 --- /dev/null +++ b/packages/amplify-e2e-core/src/init/overrideRootStack.ts @@ -0,0 +1,19 @@ +import { nspawn as spawn, getCLIPath } from '..'; + +export function amplifyOverrideRoot(cwd: string, settings: {}) { + return new Promise((resolve, reject) => { + const args = ['override', 'project']; + + spawn(getCLIPath(), args, { cwd, stripColors: true }) + .wait('Do you want to edit override.ts file now?') + .sendConfirmNo() + .sendEof() + .run((err: Error) => { + if (!err) { + resolve({}); + } else { + reject(err); + } + }); + }); +} diff --git a/packages/amplify-e2e-tests/overrides/override-root.ts b/packages/amplify-e2e-tests/overrides/override-root.ts new file mode 100644 index 00000000000..285ea3455cc --- /dev/null +++ b/packages/amplify-e2e-tests/overrides/override-root.ts @@ -0,0 +1,4 @@ +export function overrideProps(props: any): void { + props.authRole.roleName = 'mockRole'; + return props; +} diff --git a/packages/amplify-e2e-tests/src/__tests__/init.test.ts b/packages/amplify-e2e-tests/src/__tests__/init.test.ts index 0c85adf09d4..288c125fc3c 100644 --- a/packages/amplify-e2e-tests/src/__tests__/init.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/init.test.ts @@ -12,8 +12,13 @@ import { amplifyInitSandbox, getProjectSchema, amplifyPush, + amplifyOverrideRoot, + amplifyPushOverride, + createNewProjectDir, + deleteProjectDir, + getEnvVars, + getProjectMeta, } from 'amplify-e2e-core'; -import { createNewProjectDir, deleteProjectDir, getEnvVars, getProjectMeta } from 'amplify-e2e-core'; import { JSONUtilities } from 'amplify-cli-core'; import { SandboxApp } from '../types/SandboxApp'; @@ -24,8 +29,8 @@ describe('amplify init', () => { }); afterEach(async () => { - await deleteProject(projRoot); - deleteProjectDir(projRoot); + // await deleteProject(projRoot); + // deleteProjectDir(projRoot); }); it('should pull sandbox and download schema', async () => { @@ -151,4 +156,24 @@ describe('amplify init', () => { fs.writeFileSync(localEnvPath, JSON.stringify(localEnvData, null, 2)); }); + + it.only('should init the project and override root and push', async () => { + await initJSProjectWithProfile(projRoot, {}); + const meta = getProjectMeta(projRoot).providers.awscloudformation; + expect(meta.Region).toBeDefined(); + const { AuthRoleName, UnauthRoleName, UnauthRoleArn, AuthRoleArn, DeploymentBucketName } = meta; + + expect(UnauthRoleName).toBeIAMRoleWithArn(UnauthRoleArn); + expect(AuthRoleName).toBeIAMRoleWithArn(AuthRoleArn); + expect(DeploymentBucketName).toBeAS3Bucket(DeploymentBucketName); + + // override new env + await amplifyOverrideRoot(projRoot, {}); + const srcOverrideFilePath = path.join(__dirname, '..', '..', 'overrides', 'override-root.ts'); + const destOverrideFilePath = path.join(projRoot, 'amplify', 'backend', 'awscloudformation', 'override.ts'); + fs.copyFileSync(srcOverrideFilePath, destOverrideFilePath); + await amplifyPushOverride(projRoot); + const newEnvMeta = getProjectMeta(projRoot).providers.awscloudformation; + expect(newEnvMeta.AuthRoleName).toEqual('mockRole'); + }); }); diff --git a/packages/amplify-migration-tests/package.json b/packages/amplify-migration-tests/package.json index 6b4e3fdd918..765002108ad 100644 --- a/packages/amplify-migration-tests/package.json +++ b/packages/amplify-migration-tests/package.json @@ -18,11 +18,13 @@ "private": true, "scripts": { "build-tests": "tsc --build tsconfig.tests.json", - "migration_v4.0.0": "npm run setup-profile 4.0.0 && jest --testPathIgnorePatterns=auth.deployment.secrets\\|layer-migration --verbose --passWithNoTests", - "migration_v4.30.0_auth": "npm run setup-profile 4.30.0 && jest src/__tests__/migration_tests/auth-deployment-migration/auth.deployment.secrets.test.ts --verbose --passWithNoTests", - "migration_v4.28.2_nonmultienv_layers": "npm run setup-profile 4.28.2 && jest src/__tests__/migration_tests/lambda-layer-migration/layer-migration.test.ts --verbose --passWithNoTests", - "migration_v4.52.0_multienv_layers": "npm run setup-profile 4.52.0 && jest src/__tests__/migration_tests/lambda-layer-migration/layer-migration.test.ts --verbose --passWithNoTests", - "migration": "npm run setup-profile latest && jest --testPathIgnorePatterns=auth.deployment.secrets\\|layer-migration --verbose --passWithNoTests", + "migration_v4.0.0": "npm run setup-profile 4.0.0 && jest --testPathIgnorePatterns=auth.deployment.secrets\\|layer-migration| overrides --verbose", + "migration_v4.30.0_auth": "npm run setup-profile 4.30.0 && jest src/__tests__/migration_tests/auth-deployment-migration/auth.deployment.secrets.test.ts --verbose", + "migration_v4.28.2_nonmultienv_layers": "npm run setup-profile 4.28.2 && jest src/__tests__/migration_tests/lambda-layer-migration/layer-migration.test.ts --verbose", + "migration_v4.52.0_multienv_layers": "npm run setup-profile 4.52.0 && jest src/__tests__/migration_tests/lambda-layer-migration/layer-migration.test.ts --verbose", + "migration_v6.0.1": "npm run setup-profile 6.0.1 && jest src/__tests__/migration_tests_overrides/ --verbose", + + "migration": "npm run setup-profile latest && jest --testPathIgnorePatterns=auth.deployment.secrets\\|layer-migration --verbose", "setup-profile": "ts-node ./src/configure_tests.ts" }, "dependencies": { diff --git a/packages/amplify-migration-tests/src/__tests__/migration_tests/overrides/init-migration.test.ts b/packages/amplify-migration-tests/src/__tests__/migration_tests/overrides/init-migration.test.ts new file mode 100644 index 00000000000..de5a8ef236b --- /dev/null +++ b/packages/amplify-migration-tests/src/__tests__/migration_tests/overrides/init-migration.test.ts @@ -0,0 +1,43 @@ +import * as fs from 'fs-extra'; +import * as path from 'path'; +import { + initJSProjectWithProfile, + deleteProject, + amplifyOverrideRoot, + createNewProjectDir, + deleteProjectDir, + getProjectMeta, + amplifyPushOverride, +} from 'amplify-e2e-core'; +import { JSONUtilities } from 'amplify-cli-core'; + +describe('amplify init', () => { + let projRoot: string; + beforeEach(async () => { + projRoot = await createNewProjectDir('init'); + }); + + afterEach(async () => { + await deleteProject(projRoot); + deleteProjectDir(projRoot); + }); + + it('should init the project and override root and push', async () => { + await initJSProjectWithProfile(projRoot, {}); + const meta = getProjectMeta(projRoot).providers.awscloudformation; + expect(meta.Region).toBeDefined(); + // turn ON feature flag + const cliJsonPath = path.join(projRoot, 'amplify', 'cli.json'); + const cliJSON = JSONUtilities.readJson(cliJsonPath); + const modifiedCliJson = Object.assign(cliJSON, { overrides: { project: true } }); + JSONUtilities.writeJson(cliJsonPath, modifiedCliJson); + // override new env + await amplifyOverrideRoot(projRoot, {}); + const srcOverrideFilePath = path.join(__dirname, '..', '..', 'overrides', 'override-root.ts'); + const destOverrideFilePath = path.join(projRoot, 'amplify', 'backend', 'awscloudformation', 'override.ts'); + fs.copyFileSync(srcOverrideFilePath, destOverrideFilePath); + await amplifyPushOverride(projRoot); + const newEnvMeta = getProjectMeta(projRoot).providers.awscloudformation; + expect(newEnvMeta.AuthRoleName).toEqual('mockRole'); + }); +}); diff --git a/packages/amplify-provider-awscloudformation/amplify-plugin.json b/packages/amplify-provider-awscloudformation/amplify-plugin.json index 618886f0046..9a51a23bbd9 100644 --- a/packages/amplify-provider-awscloudformation/amplify-plugin.json +++ b/packages/amplify-provider-awscloudformation/amplify-plugin.json @@ -1,6 +1,6 @@ { "name": "awscloudformation", - "aliases": ["awscfn", "aws", "root"], + "aliases": ["awscfn", "aws", "root", "project"], "type": "provider", "commands": ["configure", "console", "reset-cache", "setupNewUser", "help", "override"], "commandAliases": { diff --git a/packages/amplify-provider-awscloudformation/src/__tests__/commands/override.test.ts b/packages/amplify-provider-awscloudformation/src/__tests__/commands/override.test.ts new file mode 100644 index 00000000000..8a950a967b0 --- /dev/null +++ b/packages/amplify-provider-awscloudformation/src/__tests__/commands/override.test.ts @@ -0,0 +1,57 @@ +import { run } from '../../commands/awscloudformation/override'; +import { $TSContext, FeatureFlags, generateOverrideSkeleton } from 'amplify-cli-core'; +import { printer } from 'amplify-prompts'; +import * as path from 'path'; + +jest.mock('amplify-cli-core', () => ({ + FeatureFlags: { + getBoolean: jest.fn(), + }, + generateOverrideSkeleton: jest.fn(), +})); +jest.mock('amplify-provider-awscloudformation'); +jest.mock('amplify-prompts', () => ({ + printer: { + info: jest.fn(), + }, +})); + +jest.mock('fs-extra', () => ({ + ensureDirSync: jest.fn(), + existsSync: jest.fn(), + emptyDirSync: jest.fn(), +})); + +describe('run override command for root stack', () => { + it('runs override command when FF is OFF', async () => { + const context_stub = { + amplify: { + confirmPrompt: jest.fn().mockResolvedValue(true), + invokePluginMethod: jest.fn(), + }, + }; + + const context_stub_typed = context_stub as unknown as $TSContext; + await run(context_stub_typed); + expect(printer.info).toBeCalledTimes(2); + }); + + it('run override command when FF is ON and awscloudformation exist', async () => { + FeatureFlags.getBoolean = jest.fn().mockReturnValue(true); + const context_stub = { + amplify: { + confirmPrompt: jest.fn().mockResolvedValue(true), + invokePluginMethod: jest.fn(), + pathManager: { + getBackendDirPath: jest.fn().mockReturnValue('mydir'), + }, + }, + }; + + const context_stub_typed = context_stub as unknown as $TSContext; + await run(context_stub_typed); + const mockSrcPath = path.join(__dirname, '..', '..', '..', 'resources', 'overrides-resource'); + const mockDestPath = path.join('mydir', 'awscloudformation'); + expect(generateOverrideSkeleton).toBeCalledWith(context_stub_typed, mockSrcPath, mockDestPath); + }); +}); diff --git a/packages/amplify-provider-awscloudformation/src/__tests__/root-stack-builder/__snapshots__/root-stack-builder.test.ts.snap b/packages/amplify-provider-awscloudformation/src/__tests__/root-stack-builder/__snapshots__/root-stack-builder.test.ts.snap new file mode 100644 index 00000000000..c50f3f58fff --- /dev/null +++ b/packages/amplify-provider-awscloudformation/src/__tests__/root-stack-builder/__snapshots__/root-stack-builder.test.ts.snap @@ -0,0 +1,161 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Check RootStack Template generates root stack Template 1`] = ` +Object { + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "Root Stack for AWS Amplify CLI", + "Outputs": Object { + "AuthRoleArn": Object { + "Value": Object { + "Fn::GetAtt": Array [ + "AuthRole", + "Arn", + ], + }, + }, + "Region": Object { + "Description": "CloudFormation provider root stack Region", + "Export": Object { + "Name": Object { + "Fn::Sub": "\${AWS::StackName}-Region", + }, + }, + "Value": Object { + "Ref": "AWS::Region", + }, + }, + "StackId": Object { + "Description": "CloudFormation provider root stack name", + "Export": Object { + "Name": Object { + "Fn::Sub": "\${AWS::StackName}-StackId", + }, + }, + "Value": Object { + "Ref": "AWS::StackId", + }, + }, + "StackName": Object { + "Description": "CloudFormation provider root stack ID", + "Export": Object { + "Name": Object { + "Fn::Sub": "\${AWS::StackName}-StackName", + }, + }, + "Value": Object { + "Ref": "AWS::StackName", + }, + }, + "UnAuthRoleArn": Object { + "Value": Object { + "Fn::GetAtt": Array [ + "UnauthRole", + "Arn", + ], + }, + }, + }, + "Parameters": Object { + "AuthRoleName": Object { + "Default": "AuthRoleName", + "Description": "Name of the common deployment bucket provided by the parent stack", + "Type": "String", + }, + "DeploymentBucketName": Object { + "Default": "DeploymentBucket", + "Description": "Name of the common deployment bucket provided by the parent stack", + "Type": "String", + }, + "UnauthRoleName": Object { + "Default": "UnAuthRoleName", + "Description": "Name of the common deployment bucket provided by the parent stack", + "Type": "String", + }, + }, + "Resources": Object { + "AuthRole": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRoleWithWebIdentity", + "Effect": "Deny", + "Principal": Object { + "Federated": "cognito-identity.amazonaws.com", + }, + "Sid": "", + }, + ], + "Version": "2012-10-17", + }, + "RoleName": Object { + "Ref": "AuthRoleName", + }, + }, + "Type": "AWS::IAM::Role", + }, + "DeploymentBucket": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "BucketName": Object { + "Ref": "DeploymentBucketName", + }, + }, + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + }, + "UnauthRole": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRoleWithWebIdentity", + "Effect": "Deny", + "Principal": Object { + "Federated": "cognito-identity.amazonaws.com", + }, + "Sid": "", + }, + ], + "Version": "2012-10-17", + }, + "RoleName": Object { + "Ref": "UnauthRoleName", + }, + }, + "Type": "AWS::IAM::Role", + }, + }, +} +`; + +exports[`Check RootStack Template rootstack template generated by constructor 1`] = ` +Object { + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "Root Stack for AWS Amplify CLI", +} +`; + +exports[`Check RootStack Template rootstack template generated by constructor with some parameters 1`] = ` +Object { + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "Root Stack for AWS Amplify CLI", + "Parameters": Object { + "AuthRoleName": Object { + "Default": "AuthRoleName", + "Description": "Name of the common deployment bucket provided by the parent stack", + "Type": "String", + }, + "DeploymentBucketName": Object { + "Default": "DeploymentBucket", + "Description": "Name of the common deployment bucket provided by the parent stack", + "Type": "String", + }, + "UnauthRoleName": Object { + "Default": "UnAuthRoleName", + "Description": "Name of the common deployment bucket provided by the parent stack", + "Type": "String", + }, + }, +} +`; diff --git a/packages/amplify-provider-awscloudformation/src/__tests__/root-stack-builder/__snapshots__/root-stack-transform.test.ts.snap b/packages/amplify-provider-awscloudformation/src/__tests__/root-stack-builder/__snapshots__/root-stack-transform.test.ts.snap new file mode 100644 index 00000000000..163f74e39d1 --- /dev/null +++ b/packages/amplify-provider-awscloudformation/src/__tests__/root-stack-builder/__snapshots__/root-stack-transform.test.ts.snap @@ -0,0 +1,151 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Check RootStack Template Generated rootstack template during init 1`] = ` +Object { + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "Root Stack for AWS Amplify CLI", + "Outputs": Object { + "AuthRoleArn": Object { + "Value": Object { + "Fn::GetAtt": Array [ + "AuthRole", + "Arn", + ], + }, + }, + "AuthRoleName": Object { + "Value": Object { + "Ref": "AuthRoleName", + }, + }, + "DeploymentBucketName": Object { + "Description": "CloudFormation provider root stack deployment bucket name", + "Export": Object { + "Name": Object { + "Fn::Sub": "\${AWS::StackName}-DeploymentBucketName", + }, + }, + "Value": Object { + "Ref": "DeploymentBucketName", + }, + }, + "Region": Object { + "Description": "CloudFormation provider root stack Region", + "Export": Object { + "Name": Object { + "Fn::Sub": "\${AWS::StackName}-Region", + }, + }, + "Value": Object { + "Ref": "AWS::Region", + }, + }, + "StackId": Object { + "Description": "CloudFormation provider root stack name", + "Export": Object { + "Name": Object { + "Fn::Sub": "\${AWS::StackName}-StackId", + }, + }, + "Value": Object { + "Ref": "AWS::StackId", + }, + }, + "StackName": Object { + "Description": "CloudFormation provider root stack ID", + "Export": Object { + "Name": Object { + "Fn::Sub": "\${AWS::StackName}-StackName", + }, + }, + "Value": Object { + "Ref": "AWS::StackName", + }, + }, + "UnAuthRoleArn": Object { + "Value": Object { + "Fn::GetAtt": Array [ + "UnauthRole", + "Arn", + ], + }, + }, + "UnauthRoleName": Object { + "Value": Object { + "Ref": "UnauthRoleName", + }, + }, + }, + "Parameters": Object { + "AuthRoleName": Object { + "Default": "AuthRoleName", + "Description": "Name of the common deployment bucket provided by the parent stack", + "Type": "String", + }, + "DeploymentBucketName": Object { + "Default": "DeploymentBucket", + "Description": "Name of the common deployment bucket provided by the parent stack", + "Type": "String", + }, + "UnauthRoleName": Object { + "Default": "UnAuthRoleName", + "Description": "Name of the common deployment bucket provided by the parent stack", + "Type": "String", + }, + }, + "Resources": Object { + "AuthRole": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRoleWithWebIdentity", + "Effect": "Deny", + "Principal": Object { + "Federated": "cognito-identity.amazonaws.com", + }, + "Sid": "", + }, + ], + "Version": "2012-10-17", + }, + "RoleName": Object { + "Ref": "AuthRoleName", + }, + }, + "Type": "AWS::IAM::Role", + }, + "DeploymentBucket": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "BucketName": Object { + "Ref": "DeploymentBucketName", + }, + }, + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + }, + "UnauthRole": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRoleWithWebIdentity", + "Effect": "Deny", + "Principal": Object { + "Federated": "cognito-identity.amazonaws.com", + }, + "Sid": "", + }, + ], + "Version": "2012-10-17", + }, + "RoleName": Object { + "Ref": "UnauthRoleName", + }, + }, + "Type": "AWS::IAM::Role", + }, + }, +} +`; diff --git a/packages/amplify-provider-awscloudformation/src/__tests__/root-stack-builder/root-stack-builder.test.ts b/packages/amplify-provider-awscloudformation/src/__tests__/root-stack-builder/root-stack-builder.test.ts new file mode 100644 index 00000000000..ce79cb8c369 --- /dev/null +++ b/packages/amplify-provider-awscloudformation/src/__tests__/root-stack-builder/root-stack-builder.test.ts @@ -0,0 +1,200 @@ +import { SynthUtils } from '@aws-cdk/assert'; +import * as cdk from '@aws-cdk/core'; +import { AmplifyRootStack } from '../../root-stack-builder/root-stack-builder'; + +jest.mock('../../root-stack-builder/stack-synthesizer'); + +describe('Check RootStack Template', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + test('rootstack template generated by constructor', () => { + const app = new cdk.App(); + let mocksynth: cdk.IStackSynthesizer; + const stack = new AmplifyRootStack(app, 'rootStack', { synthesizer: mocksynth }); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); + }); + + test('rootstack template generated by constructor with some parameters', () => { + const app = new cdk.App(); + let mocksynth: cdk.IStackSynthesizer; + const stack = new AmplifyRootStack(app, 'rootStack', { synthesizer: mocksynth }); + + stack.addCfnParameter( + { + type: 'String', + description: 'Name of the common deployment bucket provided by the parent stack', + default: 'DeploymentBucket', + }, + 'DeploymentBucketName', + ); + + stack.addCfnParameter( + { + type: 'String', + description: 'Name of the common deployment bucket provided by the parent stack', + default: 'AuthRoleName', + }, + 'AuthRoleName', + ); + + stack.addCfnParameter( + { + type: 'String', + description: 'Name of the common deployment bucket provided by the parent stack', + default: 'UnAuthRoleName', + }, + 'UnauthRoleName', + ); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); + }); + + test('adding same logicalId parameter should throw error', () => { + const app = new cdk.App(); + let mocksynth: cdk.IStackSynthesizer; + const stack = new AmplifyRootStack(app, 'rootStack', { synthesizer: mocksynth }); + + stack.addCfnParameter( + { + type: 'String', + description: 'Name of the common deployment bucket provided by the parent stack', + default: 'AuthRoleName', + }, + 'AuthRoleName', + ); + + expect(() => + stack.addCfnParameter( + { + type: 'String', + description: 'Name of the common deployment bucket provided by the parent stack', + default: 'AuthRoleName', + }, + 'AuthRoleName', + ), + ).toThrow('logical Id already Exists'); + }); + + test(' adding two resources with same logicalId throw error', () => { + const app = new cdk.App(); + let mocksynth: cdk.IStackSynthesizer; + const stack = new AmplifyRootStack(app, 'rootStack', { synthesizer: mocksynth }); + + stack.addCfnParameter( + { + type: 'String', + description: 'Name of the common deployment bucket provided by the parent stack', + default: 'DeploymentBucket', + }, + 'DeploymentBucketName', + ); + + // Add Outputs + stack.addCfnOutput( + { + description: 'CloudFormation provider root stack Region', + value: cdk.Fn.ref('AWS::Region'), + exportName: cdk.Fn.sub('${AWS::StackName}-Region'), + }, + 'Region', + ); + + stack.addCfnOutput( + { + description: 'CloudFormation provider root stack ID', + value: cdk.Fn.ref('AWS::StackName'), + exportName: cdk.Fn.sub('${AWS::StackName}-StackName'), + }, + 'StackName', + ); + + expect(() => + stack.addCfnOutput( + { + description: 'CloudFormation provider root stack deployment bucket name', + value: cdk.Fn.ref('DeploymentBucketName'), + exportName: cdk.Fn.sub('${AWS::StackName}-DeploymentBucketName'), + }, + 'DeploymentBucketName', + ), + ).toThrow(`There is already a Construct with name 'DeploymentBucketName' in AmplifyRootStack`); + }); + + test('generates root stack Template', async () => { + const app = new cdk.App(); + let mocksynth: cdk.IStackSynthesizer; + const stack = new AmplifyRootStack(app, 'rootStack', { synthesizer: mocksynth }); + + stack.addCfnParameter( + { + type: 'String', + description: 'Name of the common deployment bucket provided by the parent stack', + default: 'DeploymentBucket', + }, + 'DeploymentBucketName', + ); + + stack.addCfnParameter( + { + type: 'String', + description: 'Name of the common deployment bucket provided by the parent stack', + default: 'AuthRoleName', + }, + 'AuthRoleName', + ); + + stack.addCfnParameter( + { + type: 'String', + description: 'Name of the common deployment bucket provided by the parent stack', + default: 'UnAuthRoleName', + }, + 'UnauthRoleName', + ); + + // Add Outputs + stack.addCfnOutput( + { + description: 'CloudFormation provider root stack Region', + value: cdk.Fn.ref('AWS::Region'), + exportName: cdk.Fn.sub('${AWS::StackName}-Region'), + }, + 'Region', + ); + + stack.addCfnOutput( + { + description: 'CloudFormation provider root stack ID', + value: cdk.Fn.ref('AWS::StackName'), + exportName: cdk.Fn.sub('${AWS::StackName}-StackName'), + }, + 'StackName', + ); + + stack.addCfnOutput( + { + description: 'CloudFormation provider root stack name', + value: cdk.Fn.ref('AWS::StackId'), + exportName: cdk.Fn.sub('${AWS::StackName}-StackId'), + }, + 'StackId', + ); + + stack.addCfnOutput( + { + value: cdk.Fn.getAtt('AuthRole', 'Arn').toString(), + }, + 'AuthRoleArn', + ); + + stack.addCfnOutput( + { + value: cdk.Fn.getAtt('UnauthRole', 'Arn').toString(), + }, + 'UnAuthRoleArn', + ); + + await stack.generateRootStackResources(); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); + }); +}); diff --git a/packages/amplify-provider-awscloudformation/src/__tests__/root-stack-builder/root-stack-transform.test.ts b/packages/amplify-provider-awscloudformation/src/__tests__/root-stack-builder/root-stack-transform.test.ts new file mode 100644 index 00000000000..370dea2d639 --- /dev/null +++ b/packages/amplify-provider-awscloudformation/src/__tests__/root-stack-builder/root-stack-transform.test.ts @@ -0,0 +1,22 @@ +import { AmplifyRootStackTransform } from '../../root-stack-builder/root-stack-transform'; +import { $TSContext } from 'amplify-cli-core'; + +jest.mock('amplify-cli-core'); + +describe('Check RootStack Template', () => { + it('Generated rootstack template during init', async () => { + const context_stub = { + input: { + command: 'init', + }, + }; + + const context_stub_typed = context_stub as unknown as $TSContext; + // CFN transform for Root stack + const resourceName = 'awscloudformation'; + + const rootTransform = new AmplifyRootStackTransform(resourceName); + const mock_template = await rootTransform.transform(context_stub_typed); + expect(mock_template).toMatchSnapshot(); + }); +}); diff --git a/packages/amplify-provider-awscloudformation/src/attach-backend.js b/packages/amplify-provider-awscloudformation/src/attach-backend.js index 1c0f661718c..2c0a482ffe9 100644 --- a/packages/amplify-provider-awscloudformation/src/attach-backend.js +++ b/packages/amplify-provider-awscloudformation/src/attach-backend.js @@ -15,7 +15,7 @@ const { resolveAppId } = require('./utils/resolve-appId'); const { adminLoginFlow } = require('./admin-login'); const { fileLogger } = require('./utils/aws-logger'); const logger = fileLogger('attach-backend'); -import { downloadHooks } from './utils/hooks-manager'; +const { downloadHooks } = require('./utils/hooks-manager'); async function run(context) { let appId; diff --git a/packages/amplify-provider-awscloudformation/src/commands/awscloudformation/override.ts b/packages/amplify-provider-awscloudformation/src/commands/awscloudformation/override.ts index d504f9f0390..e5986921376 100644 --- a/packages/amplify-provider-awscloudformation/src/commands/awscloudformation/override.ts +++ b/packages/amplify-provider-awscloudformation/src/commands/awscloudformation/override.ts @@ -3,17 +3,34 @@ */ import path from 'path'; -import { generateOverrideSkeleton, $TSContext } from 'amplify-cli-core'; +import { generateOverrideSkeleton, $TSContext, FeatureFlags } from 'amplify-cli-core'; +import { printer } from 'amplify-prompts'; +import * as fs from 'fs-extra'; const subcommand = 'override'; export const name = 'overrides'; export const run = async (context: $TSContext) => { - const backendDir = context.amplify.pathManager.getBackendDirPath(); + if (FeatureFlags.getBoolean('overrides.project')) { + const backendDir = context.amplify.pathManager.getBackendDirPath(); - const destPath = path.normalize(path.join(backendDir, 'awscloudformation')); - const srcPath = path.normalize(path.join(__dirname, '..', '..', '..', 'resources', 'overrides-resource')); - - await generateOverrideSkeleton(context, srcPath, destPath); + const destPath = path.join(backendDir, 'awscloudformation'); + fs.ensureDirSync(destPath); + const srcPath = path.join(__dirname, '..', '..', '..', 'resources', 'overrides-resource'); + // removing runtime old root cfn stack + // no need for rollback since these files will be autogenerated after push + const oldRootStackFile = path.join(destPath, 'nested-cloudformation-stack.yml'); + if (fs.existsSync(oldRootStackFile)) { + fs.unlinkSync(oldRootStackFile); + } + await generateOverrideSkeleton(context, srcPath, destPath); + } else { + printer.info('Project level overrides is currently not turned on. In cli.json file please include the following:'); + printer.info(`{ + override: { + project: true + } + }`); + } }; diff --git a/packages/amplify-provider-awscloudformation/src/index.ts b/packages/amplify-provider-awscloudformation/src/index.ts index 60f15796561..d90f3de0ece 100644 --- a/packages/amplify-provider-awscloudformation/src/index.ts +++ b/packages/amplify-provider-awscloudformation/src/index.ts @@ -39,7 +39,13 @@ import { updateEnv } from './update-env'; import { uploadHooksDirectory } from './utils/hooks-manager'; import { getTransformerVersion } from './transform-graphql-schema'; +export const cfnRootStackFileName = 'root-cloudformation-stack.json'; +export { storeRootStackTemplate } from './initializer'; import { transformResourceWithOverrides } from './override-manager'; +export { transformResourceWithOverrides } from './override-manager'; +import { rootStackFileName } from './push-resources'; +export { rootStackFileName } from './push-resources'; +export { AmplifyRootStackTemplate } from './root-stack-builder'; function init(context) { return initializer.run(context); @@ -168,4 +174,5 @@ module.exports = { getLocationRegionMapping, getTransformerVersion, transformResourceWithOverrides, + rootStackFileName, }; diff --git a/packages/amplify-provider-awscloudformation/src/initializer.ts b/packages/amplify-provider-awscloudformation/src/initializer.ts index d6c735b02fe..5c55d14104b 100644 --- a/packages/amplify-provider-awscloudformation/src/initializer.ts +++ b/packages/amplify-provider-awscloudformation/src/initializer.ts @@ -1,9 +1,10 @@ -import { $TSContext } from 'amplify-cli-core'; +import { $TSContext, FeatureFlags, Template, pathManager, PathConstants, stateManager, JSONUtilities } from 'amplify-cli-core'; import _ from 'lodash'; +import { transformRootStack } from './override-manager'; +import { rootStackFileName } from './push-resources'; const moment = require('moment'); const path = require('path'); -const { pathManager, PathConstants, stateManager, JSONUtilities } = require('amplify-cli-core'); const glob = require('glob'); const archiver = require('./utils/archiver'); const fs = require('fs-extra'); @@ -20,7 +21,6 @@ const { prePushCfnTemplateModifier } = require('./pre-push-cfn-processor/pre-pus const logger = fileLogger('attach-backend'); const { configurePermissionsBoundaryForInit } = require('./permissions-boundary/permissions-boundary'); const { uploadHooksDirectory } = require('./utils/hooks-manager'); - export async function run(context) { await configurationManager.init(context); if (!context.exeInfo || context.exeInfo.isNewEnv) { @@ -43,19 +43,25 @@ export async function run(context) { }; const { amplifyAppId, verifiedStackName, deploymentBucketName } = await amplifyServiceManager.init(amplifyServiceParams); + // start root stack builder and deploy + + // moved cfn build to next its builder stackName = verifiedStackName; const Tags = context.amplify.getTags(context); const authRoleName = `${stackName}-authRole`; const unauthRoleName = `${stackName}-unauthRole`; - const rootStack = JSONUtilities.readJson(initTemplateFilePath); + const rootStack = JSONUtilities.readJson