Skip to content

Commit

Permalink
feat: Override functionality enabled for Root stack (#7702)
Browse files Browse the repository at this point in the history
* feat: initial commit

* feat: root stack creation on init with cdk

* feat: enable for push

* feat: added build command for root stack override

* fix: authrole and Unauthrole Names

* feat: added synthesizer and address comments

* feat: added hash for root stack

* feat: deploy root stack to disk

* feat: updated amplifyMeta after root override

* feat: update curren-cloud-backend

* feat: refractor Root transform and added unit tests

* fix: removes Template from Cloudform

* feat: added build step in overrides
  • Loading branch information
akshbhu authored Jul 29, 2021
1 parent 8b240c5 commit a509b8e
Show file tree
Hide file tree
Showing 21 changed files with 1,546 additions and 35 deletions.
43 changes: 43 additions & 0 deletions packages/amplify-cli-core/src/state-manager/pathManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ export const PathConstants = {
BackendDirName: 'backend',
CurrentCloudBackendDirName: '#current-cloud-backend',

// 2nd Level
OverrideDirName: 'overrides',
ProviderName: 'awscloudformation',
CfnStacksBuildDirName: 'build',

// FileNames
AmplifyAdminConfigFileName: 'config.json',

Expand All @@ -35,6 +40,7 @@ export const PathConstants = {
LocalAWSInfoFileName: 'local-aws-info.json',
TeamProviderInfoFileName: 'team-provider-info.json',
BackendConfigFileName: 'backend-config.json',
OverrideFileName: 'override.ts',

CLIJSONFileName: 'cli.json',
CLIJSONFileNameGlob: 'cli*.json',
Expand Down Expand Up @@ -77,6 +83,43 @@ export class PathManager {
getBackendDirPath = (projectPath?: string): string =>
this.constructPath(projectPath, [PathConstants.AmplifyDirName, PathConstants.BackendDirName]);

getOverrideDirPath = (projectPath: string, category: string, resourceName: string): string => {
return this.constructPath(projectPath, [
PathConstants.AmplifyDirName,
PathConstants.BackendDirName,
category!,
resourceName!,
PathConstants.OverrideDirName,
]);
};

getRootOverrideDirPath = (projectPath: string): string => {
return this.constructPath(projectPath, [
PathConstants.AmplifyDirName,
PathConstants.BackendDirName,
PathConstants.ProviderName,
PathConstants.OverrideDirName,
]);
};

getRootStackDirPath = (projectPath: string): string => {
return this.constructPath(projectPath, [
PathConstants.AmplifyDirName,
PathConstants.BackendDirName,
PathConstants.ProviderName,
PathConstants.CfnStacksBuildDirName,
]);
};

getCurrentCloudRootStackDirPath = (projectPath: string): string => {
return this.constructPath(projectPath, [
PathConstants.AmplifyDirName,
PathConstants.CurrentCloudBackendDirName,
PathConstants.ProviderName,
PathConstants.CfnStacksBuildDirName,
]);
};

getCurrentCloudBackendDirPath = (projectPath?: string): string =>
this.constructPath(projectPath, [PathConstants.AmplifyDirName, PathConstants.CurrentCloudBackendDirName]);

Expand Down
1 change: 1 addition & 0 deletions packages/amplify-cli/amplify-plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "core",
"type": "core",
"commands": [
"build-overrides",
"categories",
"configure",
"console",
Expand Down
27 changes: 27 additions & 0 deletions packages/amplify-cli/src/commands/build-overrides.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Command to transform CFN with overrides
*/
const subcommand = 'build-overrides';

module.exports = {
name: subcommand,
run: async context => {
try {
const {
parameters: { options },
} = context;
await context.amplify.executeProviderUtils(context, 'awscloudformation', 'buildOverrides', {
forceCompile: true,
});
} catch (error) {
context.print.error(error.message);

if (error.stack) {
context.print.info(error.stack);
}

context.usageData.emitError(error);
process.exitCode = 1;
}
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export async function pushResources(
}
}

// building all CFN stacks here to get the resource Changes
context.amplify.executeProviderUtils(context, 'awscloudformation', 'buildOverrides', { forceCompile: true });
const hasChanges = await showResourceTable(category, resourceName, filteredResources);

// no changes detected
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { CLOUD_INITIALIZED, CLOUD_NOT_INITIALIZED, getCloudInitStatus } from './
import { ServiceName as FunctionServiceName, hashLayerResource } from 'amplify-category-function';
import { removeGetUserEndpoints } from '../amplify-helpers/remove-pinpoint-policy';
import { pathManager, stateManager, $TSMeta, $TSAny, NotInitializedError } from 'amplify-cli-core';
import { rootStackFileName } from 'amplify-provider-awscloudformation';

async function isBackendDirModifiedSinceLastPush(resourceName, category, lastPushTimeStamp, hashFunction) {
// Pushing the resource for the first time hence no lastPushTimeStamp
Expand All @@ -29,6 +30,17 @@ async function isBackendDirModifiedSinceLastPush(resourceName, category, lastPus
return localDirHash !== cloudDirHash;
}

export function getHashForRootStack(dirPath, files?: string[]) {
const options: HashElementOptions = {
folders: { exclude: ['.*', 'node_modules', 'test_coverage', 'dist', 'build'] },
files: {
include: files,
},
};

return hashElement(dirPath, options).then(result => result.hash);
}

export function getHashForResourceDir(dirPath, files?: string[]) {
const options: HashElementOptions = {
folders: { exclude: ['.*', 'node_modules', 'test_coverage', 'dist', 'build'] },
Expand Down Expand Up @@ -388,13 +400,18 @@ export async function getResourceStatus(category?, resourceName?, providerName?,
// if not equal there is a tag update
const tagsUpdated = !_.isEqual(stateManager.getProjectTags(), stateManager.getCurrentProjectTags());

// check if there is an update in root stack

const rootStackUpdated: boolean = await isRootStackModifiedSinceLastPush(getHashForRootStack);

return {
resourcesToBeCreated,
resourcesToBeUpdated,
resourcesToBeSynced,
resourcesToBeDeleted,
tagsUpdated,
allResources,
rootStackUpdated,
};
}

Expand All @@ -416,6 +433,7 @@ export async function showResourceTable(category, resourceName, filteredResource
resourcesToBeSynced,
allResources,
tagsUpdated,
rootStackUpdated,
} = await getResourceStatus(category, resourceName, undefined, filteredResources);

let noChangeResources = _.differenceWith(
Expand Down Expand Up @@ -501,8 +519,29 @@ export async function showResourceTable(category, resourceName, filteredResource
print.info('\nTag Changes Detected');
}

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;
}

async function isRootStackModifiedSinceLastPush(hashFunction): Promise<boolean> {
try {
const projectPath = pathManager.findProjectRoot();
const localBackendDir = pathManager.getRootStackDirPath(projectPath!);
const cloudBackendDir = pathManager.getCurrentCloudRootStackDirPath(projectPath!);

const localDirHash = await hashFunction(localBackendDir, [rootStackFileName]);
const cloudDirHash = await hashFunction(cloudBackendDir, [rootStackFileName]);

return localDirHash !== cloudDirHash;
} catch (error) {
throw new Error('Amplify Project not initialized.');
}
}
Original file line number Diff line number Diff line change
@@ -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",
},
},
}
`;
Loading

0 comments on commit a509b8e

Please sign in to comment.