Skip to content

Commit

Permalink
feat: defer root stack creation to first amplify push (aws-amplify#…
Browse files Browse the repository at this point in the history
  • Loading branch information
edwardfoyle authored May 10, 2021
1 parent 5c918be commit d28dd1c
Show file tree
Hide file tree
Showing 18 changed files with 348 additions and 124 deletions.
58 changes: 44 additions & 14 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1127,22 +1127,30 @@ jobs:
environment:
TEST_SUITE: src/__tests__/function_5.test.ts
CLI_REGION: eu-central-1
defer-init-push-amplify_e2e_tests:
working_directory: ~/repo
docker: *ref_1
resource_class: large
steps: *ref_4
environment:
TEST_SUITE: src/__tests__/defer-init-push.test.ts
CLI_REGION: ap-northeast-1
configure-project-amplify_e2e_tests:
working_directory: ~/repo
docker: *ref_1
resource_class: large
steps: *ref_4
environment:
TEST_SUITE: src/__tests__/configure-project.test.ts
CLI_REGION: ap-northeast-1
CLI_REGION: ap-southeast-1
api_4-amplify_e2e_tests:
working_directory: ~/repo
docker: *ref_1
resource_class: large
steps: *ref_4
environment:
TEST_SUITE: src/__tests__/api_4.test.ts
CLI_REGION: ap-southeast-1
CLI_REGION: ap-southeast-2
schema-iterative-update-4-amplify_e2e_tests_pkg_linux:
working_directory: ~/repo
docker: *ref_1
Expand Down Expand Up @@ -1813,6 +1821,16 @@ jobs:
TEST_SUITE: src/__tests__/function_5.test.ts
CLI_REGION: eu-central-1
steps: *ref_5
defer-init-push-amplify_e2e_tests_pkg_linux:
working_directory: ~/repo
docker: *ref_1
resource_class: large
environment:
AMPLIFY_DIR: /home/circleci/repo/out
AMPLIFY_PATH: /home/circleci/repo/out/amplify-pkg-linux
TEST_SUITE: src/__tests__/defer-init-push.test.ts
CLI_REGION: ap-northeast-1
steps: *ref_5
configure-project-amplify_e2e_tests_pkg_linux:
working_directory: ~/repo
docker: *ref_1
Expand All @@ -1821,7 +1839,7 @@ jobs:
AMPLIFY_DIR: /home/circleci/repo/out
AMPLIFY_PATH: /home/circleci/repo/out/amplify-pkg-linux
TEST_SUITE: src/__tests__/configure-project.test.ts
CLI_REGION: ap-northeast-1
CLI_REGION: ap-southeast-1
steps: *ref_5
api_4-amplify_e2e_tests_pkg_linux:
working_directory: ~/repo
Expand All @@ -1831,7 +1849,7 @@ jobs:
AMPLIFY_DIR: /home/circleci/repo/out
AMPLIFY_PATH: /home/circleci/repo/out/amplify-pkg-linux
TEST_SUITE: src/__tests__/api_4.test.ts
CLI_REGION: ap-southeast-1
CLI_REGION: ap-southeast-2
steps: *ref_5
workflows:
version: 2
Expand Down Expand Up @@ -1950,15 +1968,15 @@ workflows:
- containers-api-amplify_e2e_tests
- interactions-amplify_e2e_tests
- datastore-modelgen-amplify_e2e_tests
- configure-project-amplify_e2e_tests
- defer-init-push-amplify_e2e_tests
- schema-iterative-update-2-amplify_e2e_tests
- schema-data-access-patterns-amplify_e2e_tests
- init-special-case-amplify_e2e_tests
- api_4-amplify_e2e_tests
- auth_1-amplify_e2e_tests
- configure-project-amplify_e2e_tests
- feature-flags-amplify_e2e_tests
- schema-versioned-amplify_e2e_tests
- plugin-amplify_e2e_tests
- api_4-amplify_e2e_tests
- done_with_pkg_linux_e2e_tests:
requires:
- schema-key-amplify_e2e_tests_pkg_linux
Expand All @@ -1980,15 +1998,15 @@ workflows:
- containers-api-amplify_e2e_tests_pkg_linux
- interactions-amplify_e2e_tests_pkg_linux
- datastore-modelgen-amplify_e2e_tests_pkg_linux
- configure-project-amplify_e2e_tests_pkg_linux
- defer-init-push-amplify_e2e_tests_pkg_linux
- schema-iterative-update-2-amplify_e2e_tests_pkg_linux
- schema-data-access-patterns-amplify_e2e_tests_pkg_linux
- init-special-case-amplify_e2e_tests_pkg_linux
- api_4-amplify_e2e_tests_pkg_linux
- auth_1-amplify_e2e_tests_pkg_linux
- configure-project-amplify_e2e_tests_pkg_linux
- feature-flags-amplify_e2e_tests_pkg_linux
- schema-versioned-amplify_e2e_tests_pkg_linux
- plugin-amplify_e2e_tests_pkg_linux
- api_4-amplify_e2e_tests_pkg_linux
- amplify_migration_tests_latest:
context:
- amplify-ecr-image-pull
Expand Down Expand Up @@ -2403,7 +2421,7 @@ workflows:
filters: *ref_9
requires:
- migration-api-key-migration1-amplify_e2e_tests
- configure-project-amplify_e2e_tests:
- defer-init-push-amplify_e2e_tests:
context: *ref_7
post-steps: *ref_8
filters: *ref_9
Expand Down Expand Up @@ -2463,7 +2481,7 @@ workflows:
filters: *ref_9
requires:
- layer-amplify_e2e_tests
- api_4-amplify_e2e_tests:
- configure-project-amplify_e2e_tests:
context: *ref_7
post-steps: *ref_8
filters: *ref_9
Expand Down Expand Up @@ -2523,6 +2541,12 @@ workflows:
filters: *ref_9
requires:
- auth_3-amplify_e2e_tests
- api_4-amplify_e2e_tests:
context: *ref_7
post-steps: *ref_8
filters: *ref_9
requires:
- auth_1-amplify_e2e_tests
- schema-iterative-update-4-amplify_e2e_tests_pkg_linux:
context: &ref_10
- amplify-ecr-image-pull
Expand Down Expand Up @@ -2845,7 +2869,7 @@ workflows:
filters: *ref_12
requires:
- migration-api-key-migration1-amplify_e2e_tests_pkg_linux
- configure-project-amplify_e2e_tests_pkg_linux:
- defer-init-push-amplify_e2e_tests_pkg_linux:
context: *ref_10
post-steps: *ref_11
filters: *ref_12
Expand Down Expand Up @@ -2909,7 +2933,7 @@ workflows:
filters: *ref_12
requires:
- layer-amplify_e2e_tests_pkg_linux
- api_4-amplify_e2e_tests_pkg_linux:
- configure-project-amplify_e2e_tests_pkg_linux:
context: *ref_10
post-steps: *ref_11
filters: *ref_12
Expand Down Expand Up @@ -2973,3 +2997,9 @@ workflows:
filters: *ref_12
requires:
- auth_3-amplify_e2e_tests_pkg_linux
- api_4-amplify_e2e_tests_pkg_linux:
context: *ref_10
post-steps: *ref_11
filters: *ref_12
requires:
- auth_1-amplify_e2e_tests_pkg_linux
3 changes: 2 additions & 1 deletion packages/amplify-cli-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ interface AmplifyToolkit {
getProjectMeta: () => $TSMeta;
getResourceStatus: (category?: $TSAny, resourceName?: $TSAny, providerName?: $TSAny, filteredResources?: $TSAny) => $TSAny;
getResourceOutputs: () => $TSAny;
getTags: (context: $TSContext) => $TSAny;
getWhen: () => $TSAny;
inputValidation: (input: $TSAny) => $TSAny;
listCategories: () => $TSAny;
Expand All @@ -195,7 +196,7 @@ interface AmplifyToolkit {
filteredResources?: { category: string; resourceName: string }[],
) => $TSAny;
storeCurrentCloudBackend: () => $TSAny;
readJsonFile: () => $TSAny;
readJsonFile: (file: string) => $TSAny;
removeEnvFromCloud: () => $TSAny;
removeDeploymentSecrets: (context: $TSContext, category: string, resource: string) => void;
removeResource: () => $TSAny;
Expand Down
2 changes: 2 additions & 0 deletions packages/amplify-cli/src/commands/push.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import sequential from 'promise-sequential';
import ora from 'ora';
import { $TSContext, $TSObject, stateManager, exitOnNextTick } from 'amplify-cli-core';
import { getProviderPlugins } from '../extensions/amplify-helpers/get-provider-plugins';
import { onSuccess } from '../init-steps/s9-onSuccess';

const spinner = ora('');

Expand Down Expand Up @@ -44,6 +45,7 @@ export const run = async (context: $TSContext) => {
if (context.parameters.options.force) {
context.exeInfo.forcePush = true;
}
context.exeInfo.deferredInitCallback = onSuccess;
await syncCurrentCloudBackend(context);
return await context.amplify.pushResources(context);
} catch (e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,12 @@ export function saveEnvResourceParameters(context: $TSContext, category: string,
stateManager.setTeamProviderInfo(undefined, teamProviderInfo);
// write hostedUIProviderCreds to deploymentSecrets
const deploymentSecrets = stateManager.getDeploymentSecrets();
const rootStackId = getRootStackId();
let rootStackId;
try {
rootStackId = getRootStackId();
} catch (err) {
return;
}
if (hostedUIProviderCreds) {
stateManager.setDeploymentSecrets(
mergeDeploymentSecrets({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ export function getRootStackId(): string {
const teamProviderInfo = stateManager.getTeamProviderInfo();
const { envName } = stateManager.getLocalEnvInfo();
const envTeamProviderInfo = teamProviderInfo[envName];
if (envTeamProviderInfo && envTeamProviderInfo.awscloudformation) {
const stackId = envTeamProviderInfo.awscloudformation.StackId;
const stackId = envTeamProviderInfo?.awscloudformation?.StackId;
if (typeof stackId === 'string') {
return stackId.split('/')[2];
}
throw new Error('Root stack Id not found');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -486,12 +486,16 @@ export async function showResourceTable(category, resourceName, filteredResource

table(tableOptions, { format: 'markdown' });

if (tagsUpdated) {
// in the case of a deferred init, need to also check there are resources in the project
// checking for length > 1 because for some reason the aws cfn provider is in the resources list but it's not a resource
const updateTags = tagsUpdated && allResources.length > 1;

if (updateTags) {
print.info('\nTag Changes Detected');
}

const resourceChanged =
resourcesToBeCreated.length + resourcesToBeUpdated.length + resourcesToBeSynced.length + resourcesToBeDeleted.length > 0 || tagsUpdated;
resourcesToBeCreated.length + resourcesToBeUpdated.length + resourcesToBeSynced.length + resourcesToBeDeleted.length > 0 || updateTags;

return resourceChanged;
}
18 changes: 13 additions & 5 deletions packages/amplify-cli/src/init-steps/s9-onSuccess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { getProviderPlugins } from '../extensions/amplify-helpers/get-provider-p
import { insertAmplifyIgnore } from '../extensions/amplify-helpers/git-manager';
import { writeReadMeFile } from '../extensions/amplify-helpers/docs-manager';
import { initializeEnv } from '../initialize-env';
import _ from 'lodash';

export async function onHeadlessSuccess(context: $TSContext) {
const frontendPlugins = getFrontendPlugins(context);
Expand Down Expand Up @@ -108,12 +109,19 @@ function generateLocalTagsFile(context: $TSContext) {
}

export function generateAmplifyMetaFile(context: $TSContext) {
if (context.exeInfo.isNewEnv) {
const { projectPath } = context.exeInfo.localEnvInfo;
const { projectPath } = context.exeInfo.localEnvInfo;

stateManager.setCurrentMeta(projectPath, context.exeInfo.amplifyMeta);
stateManager.setMeta(projectPath, context.exeInfo.amplifyMeta);
}
const { isNewEnv } = context.exeInfo;

// store amplifyMeta
const meta = isNewEnv ? {} : stateManager.getMeta(projectPath, { throwIfNotExist: false }) || {};
_.merge(meta, context.exeInfo.amplifyMeta);
stateManager.setMeta(projectPath, meta);

// store current cloud meta
const currMeta = isNewEnv ? {} : stateManager.getCurrentMeta(projectPath, { throwIfNotExist: false }) || {};
_.merge(currMeta, context.exeInfo.amplifyMeta);
stateManager.setCurrentMeta(projectPath, currMeta);
}

function generateNonRuntimeFiles(context: $TSContext) {
Expand Down
6 changes: 6 additions & 0 deletions packages/amplify-cli/src/initialize-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ export async function initializeEnv(context: $TSContext, currentAmplifyMeta?: $T

await sequential(categoryInitializationTasks);

// this function can now be called on the push codepath in the case of a deffered root stack push
// in that case, we don't need to push here as that will happen automatically down the road
if (context?.input?.command === 'push') {
return;
}

if (context.exeInfo.forcePush === undefined) {
context.exeInfo.forcePush = await context.amplify.confirmPrompt(
'Do you want to push your resources to the cloud for your environment?',
Expand Down
3 changes: 3 additions & 0 deletions packages/amplify-e2e-core/src/init/deleteProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { nspawn as spawn, retry, getCLIPath, describeCloudFormationStack, getPro

export const deleteProject = async (cwd: string, profileConfig?: any): Promise<void> => {
const { StackName: stackName, Region: region } = getProjectMeta(cwd).providers.awscloudformation;
if (!stackName || !region) {
return;
}
await retry(
() => describeCloudFormationStack(stackName, region, profileConfig),
stack => stack.StackStatus.endsWith('_COMPLETE'),
Expand Down
12 changes: 12 additions & 0 deletions packages/amplify-e2e-core/src/init/initProjectHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,18 @@ export function amplifyInitSandbox(cwd: string, settings: {}): Promise<void> {
});
}

export function amplifyInitYes(cwd: string): Promise<void> {
return new Promise((resolve, reject) => {
spawn(getCLIPath(), ['init', '--yes'], {
cwd,
stripColors: true,
env: {
CLI_DEV_INTERNAL_DISABLE_AMPLIFY_APP_CREATION: '1',
},
}).run((err: Error) => (err ? reject(err) : resolve()));
});
}

export function amplifyVersion(cwd: string, expectedVersion: string, testingWithLatestCodebase = false): Promise<void> {
return new Promise((resolve, reject) => {
spawn(getCLIPath(testingWithLatestCodebase), ['--version'], { cwd, stripColors: true })
Expand Down
81 changes: 81 additions & 0 deletions packages/amplify-e2e-tests/src/__tests__/defer-init-push.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import {
addFunction,
amplifyInitYes,
amplifyPushAuth,
createNewProjectDir,
deleteProject,
deleteProjectDir,
getLocalEnvInfo,
getTeamProviderInfo,
initJSProjectWithProfile,
} from 'amplify-e2e-core';
import { addEnvironment, addEnvironmentYes, checkoutEnvironment, removeEnvironment } from '../environment/env';

describe('defer root stack push', () => {
let projRoot: string;
beforeEach(async () => {
projRoot = await createNewProjectDir('defer-init-push');
});

afterEach(async () => {
await deleteProject(projRoot);
deleteProjectDir(projRoot);
});

it('does not create a root stack on interactive init', async () => {
await initJSProjectWithProfile(projRoot, { name: 'deferInitPush', envName: 'dev' });
const tpi_before = getTeamProviderInfo(projRoot);
expect(tpi_before?.dev?.awscloudformation?.DeploymentBucketName).toBeUndefined();
await addFunction(projRoot, { functionTemplate: 'Hello World' }, 'nodejs');
await amplifyPushAuth(projRoot);
const tpi_after = getTeamProviderInfo(projRoot);
expect(tpi_after?.dev?.awscloudformation?.DeploymentBucketName).toBeDefined();
});

it('does not create a root stack on interactive env add', async () => {
await initJSProjectWithProfile(projRoot, { name: 'deferInitPush', envName: 'dev' });
const tpi_dev = getTeamProviderInfo(projRoot);
expect(tpi_dev?.dev?.awscloudformation?.DeploymentBucketName).toBeUndefined();
await addEnvironment(projRoot, { envName: 'test' });
const tpi_test = getTeamProviderInfo(projRoot);
expect(tpi_test?.test?.awscloudformation?.DeploymentBucketName).toBeUndefined();
});

it('creates a root stack on headless init', async () => {
await amplifyInitYes(projRoot);
const tpi_after = getTeamProviderInfo(projRoot);
expect(tpi_after?.dev?.awscloudformation?.DeploymentBucketName).toBeDefined();
});

it('creates a root stack on headless env add', async () => {
await initJSProjectWithProfile(projRoot, { name: 'deferInitPush', envName: 'dev' });
const tpi_dev = getTeamProviderInfo(projRoot);
expect(tpi_dev?.dev?.awscloudformation?.DeploymentBucketName).toBeUndefined();
await addEnvironmentYes(projRoot, { envName: 'test' });
const tpi_test = getTeamProviderInfo(projRoot);
expect(tpi_test?.test?.awscloudformation?.DeploymentBucketName).toBeDefined();
});

it('can checkout unpushed environment', async () => {
await initJSProjectWithProfile(projRoot, { name: 'deferInitPush', envName: 'dev' });
const tpi_dev = getTeamProviderInfo(projRoot);
expect(tpi_dev?.dev?.awscloudformation?.DeploymentBucketName).toBeUndefined();
await addEnvironment(projRoot, { envName: 'test' });
const tpi_test = getTeamProviderInfo(projRoot);
expect(tpi_test?.test?.awscloudformation?.DeploymentBucketName).toBeUndefined();
await checkoutEnvironment(projRoot, { envName: 'dev' });
const localEnvInfo = getLocalEnvInfo(projRoot);
expect(localEnvInfo?.envName).toEqual('dev');
});

it('can remove unpushed environment', async () => {
await initJSProjectWithProfile(projRoot, { name: 'deferInitPush', envName: 'dev' });
await addEnvironment(projRoot, { envName: 'test' });
await checkoutEnvironment(projRoot, { envName: 'dev' });
const tpi_before = getTeamProviderInfo(projRoot);
expect(Object.keys(tpi_before).sort()).toEqual(['dev', 'test']);
await removeEnvironment(projRoot, { envName: 'test' });
const tpi_after = getTeamProviderInfo(projRoot);
expect(Object.keys(tpi_after)).toEqual(['dev']);
});
});
Loading

0 comments on commit d28dd1c

Please sign in to comment.