Skip to content

Commit

Permalink
feat: export (#8486)
Browse files Browse the repository at this point in the history
* feat(amplify-provider-awscloudformation): refactor of the exporting resources

* feat(amplify-provider-awscloudformation): refactored export to write files to external path

* refactor: clean up

* refactor: remove unused

* fix: mispelled url

* feat: fall back on push globing and template url

* feat: export

* feat: modify generation of lambda layer version content

* fix(amplify-category-function): lambda layers filter stacks

* feat: added command line for export

* test(amplify-provider-awscloudformation): added tests for export resources

* perf: removed unused

* refactor: removed unused

* fix: make the tags file pascal case

* docs(amplify-provider-awscloudformation): added some documentation

* feat(amplify-provider-awscloudformation): added warning and folder perms

* fix: check in constants change

* refactor: addressing PR feedback

* refactor: es6 export

* feat: minor changes for integration testing

* refactor: pr comments

* fix: cleared commented out code removed backup

* ci: revert config file to main
  • Loading branch information
ammarkarachi authored Oct 18, 2021
1 parent 76b0e70 commit 03dde65
Show file tree
Hide file tree
Showing 25 changed files with 1,710 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ export type PackageRequestMeta = ResourceTuple & {
export type Packager = (
context: $TSContext,
resource: PackageRequestMeta,
isExport?: boolean,
) => Promise<{ newPackageCreated: boolean; zipFilename: string; zipFilePath: string }>;
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ export class LayerCloudState {
const layerVersionList = await lambdaClient.listLayerVersions(isMultiEnvLayer(layerName) ? `${layerName}-${envName}` : layerName);
const cfnClient = await providerPlugin.getCloudFormationSdk(context);
const stackList = await cfnClient.listStackResources();
const layerStacks = stackList?.StackResourceSummaries?.filter(stack => stack.LogicalResourceId.includes(layerName));
const layerStacks = stackList?.StackResourceSummaries?.filter(
// do this because cdk does some rearranging of resources
stack => stack.LogicalResourceId.includes(layerName) && stack.ResourceType === 'AWS::CloudFormation::Stack',
);
let detailedLayerStack;

if (layerStacks?.length > 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { ServiceName } from './constants';
import { packageFunction } from './packageFunction';
import { packageLayer } from './packageLayer';

export const packageResource: Packager = async (context, resource) => getPackagerForService(resource.service)(context, resource);
export const packageResource: Packager = async (context, resource, isExport) =>
getPackagerForService(resource.service)(context, resource, isExport);

// there are some other categories (api and maybe others) that depend on the packageFunction function to create a zip file of resource
// which is why it is the default return value here
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ import { zipPackage } from './zipResource';
/**
* Packages lambda layer code and artifacts into a lambda-compatible .zip file
*/
export const packageLayer: Packager = async (context, resource) => {
export const packageLayer: Packager = async (context, resource, isExport) => {
const previousHash = loadPreviousLayerHash(resource.resourceName);
const currentHash = await ensureLayerVersion(context, resource.resourceName, previousHash);

if (previousHash === currentHash) {
if (!isExport && previousHash === currentHash) {
// This happens when a Lambda layer's permissions have been updated, but no new layer version needs to be pushed
return { newPackageCreated: false, zipFilename: undefined, zipFilePath: undefined };
}
Expand Down Expand Up @@ -84,7 +84,10 @@ export const packageLayer: Packager = async (context, resource) => {
}

const zipFilename = createLayerZipFilename(resource.resourceName, layerCloudState.latestVersionLogicalId);
context.amplify.updateAmplifyMetaAfterPackage(resource, zipFilename, { resourceKey: versionHash, hashValue: currentHash });
if (!isExport) {
// don't apply an update to Amplify meta on export
context.amplify.updateAmplifyMetaAfterPackage(resource, zipFilename, { resourceKey: versionHash, hashValue: currentHash });
}
return { newPackageCreated: true, zipFilename, zipFilePath: destination };
};

Expand Down
2 changes: 1 addition & 1 deletion packages/amplify-cli-core/src/errors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export class AppIdMismatchError extends Error {}
export class UnrecognizedFrameworkError extends Error {}
export class ConfigurationError extends Error {}
export class CustomPoliciesFormatError extends Error {}

export class ExportPathValidationError extends Error {}
export class NotInitializedError extends Error {
public constructor() {
super();
Expand Down
11 changes: 4 additions & 7 deletions packages/amplify-cli-core/src/state-manager/stateManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { SecretFileMode } from '../cliConstants';
import { HydrateTags, ReadTags, Tag } from '../tags';
import { CustomIAMPolicies } from '../customPoliciesUtils';


export type GetOptions<T> = {
throwIfNotExist?: boolean;
preserveComments?: boolean;
Expand Down Expand Up @@ -80,9 +79,9 @@ export class StateManager {

getCustomPolicies = (categoryName: string, resourceName: string): CustomIAMPolicies | undefined => {
const filePath = pathManager.getCustomPoliciesPath(categoryName, resourceName);
try{
try {
return JSONUtilities.readJson<CustomIAMPolicies>(filePath);
} catch(err) {
} catch (err) {
return undefined;
}
};
Expand Down Expand Up @@ -201,11 +200,11 @@ export class StateManager {
JSONUtilities.writeJson(filePath, localAWSInfo);
};

getHydratedTags = (projectPath?: string | undefined): Tag[] => {
getHydratedTags = (projectPath?: string | undefined, skipProjEnv: boolean = false): Tag[] => {
const tags = this.getProjectTags(projectPath);
const { projectName } = this.getProjectConfig(projectPath);
const { envName } = this.getLocalEnvInfo(projectPath);
return HydrateTags(tags, { projectName, envName });
return HydrateTags(tags, { projectName, envName }, skipProjEnv);
};

isTagFilePresent = (projectPath?: string | undefined): boolean => {
Expand Down Expand Up @@ -379,5 +378,3 @@ export class StateManager {
}

export const stateManager = new StateManager();


12 changes: 7 additions & 5 deletions packages/amplify-cli-core/src/tags/Tags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export function ReadTags(tagsFilePath: string): Tag[] {
return tags;
}

export function validate(tags: Tag[]): void {
export function validate(tags: Tag[], skipProjectEnv: boolean = false): void {
const allowedKeySet = new Set(['Key', 'Value']);

//check if Tags have the right format
Expand All @@ -34,7 +34,8 @@ export function validate(tags: Tag[]): void {
// check if the tags has valid keys and values
_.each(tags, tag => {
const tagValidationRegExp = /[^a-z0-9_.:/=+@\- ]/gi;
if (tagValidationRegExp.test(tag.Value)) {
const tagValue = skipProjectEnv ? tag.Value.replace('{project-env}', '') : tag.Value;
if (tagValidationRegExp.test(tagValue)) {
throw new Error(
'Invalid character found in Tag Value. Tag values may only contain unicode letters, digits, whitespace, or one of these symbols: _ . : / = + - @',
);
Expand All @@ -56,19 +57,20 @@ export function validate(tags: Tag[]): void {
});
}

export function HydrateTags(tags: Tag[], tagVariables: TagVariables): Tag[] {
export function HydrateTags(tags: Tag[], tagVariables: TagVariables, skipProjectEnv: boolean = false): Tag[] {
const { envName, projectName } = tagVariables;
const replace: any = {
'{project-name}': projectName,
'{project-env}': envName,
};
const regexMatcher = skipProjectEnv ? /{project-name}/g : /{project-name}|{project-env}/g;
const hydrdatedTags = tags.map(tag => {
return {
...tag,
Value: tag.Value.replace(/{project-name}|{project-env}/g, (matched: string) => replace[matched]),
Value: tag.Value.replace(regexMatcher, (matched: string) => replace[matched]),
};
});
validate(hydrdatedTags);
validate(hydrdatedTags, skipProjectEnv);
return hydrdatedTags;
}

Expand Down
1 change: 1 addition & 0 deletions packages/amplify-cli-core/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from './isResourceNameUnique';
export * from './open';
export * from './packageManager';
export * from './recursiveOmit';
export * from './validate-path';
22 changes: 22 additions & 0 deletions packages/amplify-cli-core/src/utils/validate-path.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import * as fs from 'fs-extra';
import { ExportPathValidationError } from '../errors';

/**
* Validates whether the path is a directory
* @throws {ExportPathValidationError} if path not valid
* @param directoryPath to validate
*/
export function validateExportDirectoryPath(directoryPath: any) {
if (typeof directoryPath !== 'string') {
throw new ExportPathValidationError(`${directoryPath} is not a valid path specified by --out`);
}

if (!fs.existsSync(directoryPath)) {
throw new ExportPathValidationError(`${directoryPath} does not exist`);
}

const stat = fs.lstatSync(directoryPath);
if (!stat.isDirectory()) {
throw new ExportPathValidationError(`${directoryPath} is not a valid directory`);
}
}
1 change: 1 addition & 0 deletions packages/amplify-cli/amplify-plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"console",
"delete",
"env",
"export",
"help",
"init",
"logout",
Expand Down
31 changes: 31 additions & 0 deletions packages/amplify-cli/src/commands/export.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { $TSContext, ExportPathValidationError } from 'amplify-cli-core';
import { printer } from 'amplify-prompts';
import * as chalk from 'chalk';
import * as fs from 'fs-extra';
import * as path from 'path';
export const run = async (context: $TSContext) => {
const options = context.input.options;

const showHelp = !options || options.help || !options.out;
if (showHelp) {
printer.blankLine();
printer.info("'amplify export', Allows you to integrate your backend into an external deployment tool");
printer.blankLine();
printer.info(`${chalk.yellow('--cdk')} Export all resources with cdk comatibility`);
printer.info(`${chalk.yellow('--out')} Root directory of cdk project`);
printer.blankLine();
printer.info(`Example: ${chalk.green('amplify export --cdk --out ~/myCDKApp')}`);
printer.blankLine();
return;
}

await context.amplify.showResourceTable();
const resources = await context.amplify.getResourceStatus();
const providerPlugin = context.amplify.getProviderPlugins(context);
const providers = Object.keys(providerPlugin);
const exportPath = path.resolve(context.input.options['out']);
for await (const provider of providers) {
const plugin = await import(providerPlugin[provider]);
await plugin.exportResources(context, resources, exportPath);
}
};
16 changes: 16 additions & 0 deletions packages/amplify-e2e-core/src/export/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { nspawn as spawn, getCLIPath } from '..';

export function exportBackend(cwd: string, settings: { exportPath: string }): Promise<void> {
return new Promise((resolve, reject) => {
spawn(getCLIPath(), ['export', '--out', settings.exportPath], { cwd, stripColors: true })
.wait('For more information: docs.amplify.aws/cli/export')
.sendEof()
.run((err: Error) => {
if (!err) {
resolve();
} else {
reject(err);
}
});
});
}
14 changes: 7 additions & 7 deletions packages/amplify-e2e-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export * from './init/';
export * from './utils/';
export * from './categories';
export * from './utils/sdk-calls';
export * from './export/';
export { addFeatureFlag } from './utils/feature-flags';

declare global {
Expand All @@ -38,14 +39,12 @@ export function getCLIPath(testingWithLatestCodebase = false) {
}

export function isTestingWithLatestCodebase(scriptRunnerPath) {
return scriptRunnerPath === process.execPath
return scriptRunnerPath === process.execPath;
}

export function getScriptRunnerPath(testingWithLatestCodebase = false) {
if (!testingWithLatestCodebase) {
return process.platform === 'win32'
? 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'
: 'exec';
return process.platform === 'win32' ? 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe' : 'exec';
}

// nodejs executable
Expand Down Expand Up @@ -81,9 +80,10 @@ export async function installAmplifyCLI(version: string = 'latest') {
env: process.env,
stdio: 'inherit',
});
process.env.AMPLIFY_PATH = process.platform === 'win32'
? path.join(os.homedir(), '..', '..', 'Program` Files', 'nodejs', 'node_modules', '@aws-amplify', 'cli', 'bin', 'amplify')
: path.join(os.homedir(), '.npm-global', 'bin', 'amplify');
process.env.AMPLIFY_PATH =
process.platform === 'win32'
? path.join(os.homedir(), '..', '..', 'Program` Files', 'nodejs', 'node_modules', '@aws-amplify', 'cli', 'bin', 'amplify')
: path.join(os.homedir(), '.npm-global', 'bin', 'amplify');
}

export async function createNewProjectDir(
Expand Down
Loading

0 comments on commit 03dde65

Please sign in to comment.