From 49ba6a3bd93566ea9c653c8e77144e3dd6e52bae Mon Sep 17 00:00:00 2001 From: akshbhu <39866697+akshbhu@users.noreply.github.com> Date: Fri, 24 Sep 2021 18:22:58 -0700 Subject: [PATCH 1/6] feat: FF for override stacks (#8228) --- .eslintrc.js | 1 + .gitignore | 2 + .../amplify-base-cdk-types.ts | 32 +++++ .../category-base-schema-generator.ts | 123 ++++++++++++++++++ .../category-input-state.ts | 12 ++ .../category-override-base.ts | 24 ++++ .../src/category-interfaces/index.ts | 4 + packages/amplify-cli-core/src/cliConstants.ts | 34 +++++ .../src/feature-flags/featureFlags.ts | 16 +++ packages/amplify-cli-core/src/index.ts | 3 + .../src/overrides-manager/index.ts | 1 + .../override-skeleton-generator.ts | 81 ++++++++++++ .../src/state-manager/pathManager.ts | 43 ++++++ .../amplify-cli-overrides-helper/.npmignore | 5 + .../amplify-cli-overrides-helper/CHANGELOG.md | 0 .../amplify-cli-overrides-helper/README.md | 1 + .../amplify-cli-overrides-helper/package.json | 48 +++++++ .../amplify-cli-overrides-helper/src/index.ts | 13 ++ .../tsconfig.json | 8 ++ .../__tests__/commands/build-override.test.ts | 65 +++++++++ .../src/commands/build-override.ts | 59 +++++++++ packages/amplify-cli/src/execution-manager.ts | 28 +++- .../get-category-pluginInfo.ts | 8 ++ .../amplify-helpers/push-resources.ts | 7 +- .../amplify-plugin.json | 28 ++-- .../package.json | 1 + .../resources/overrides-resource/override.ts | 6 + .../resources/overrides-resource/package.json | 18 +++ .../overrides-resource/tsconfig.json | 12 ++ .../overrides-resource/tsconfig.resource.json | 13 ++ .../commands/awscloudformation/override.ts | 19 +++ .../src/index.ts | 3 + .../src/override-manager/index.ts | 1 + .../override-manager/transform-resource.ts | 31 +++++ .../src/utility-functions.js | 13 ++ .../tsconfig.json | 1 + yarn.lock | 2 +- 37 files changed, 746 insertions(+), 20 deletions(-) create mode 100644 packages/amplify-cli-core/src/category-interfaces/amplify-base-cdk-types.ts create mode 100644 packages/amplify-cli-core/src/category-interfaces/category-base-schema-generator.ts create mode 100644 packages/amplify-cli-core/src/category-interfaces/category-input-state.ts create mode 100644 packages/amplify-cli-core/src/category-interfaces/category-override-base.ts create mode 100644 packages/amplify-cli-core/src/category-interfaces/index.ts create mode 100644 packages/amplify-cli-core/src/overrides-manager/index.ts create mode 100644 packages/amplify-cli-core/src/overrides-manager/override-skeleton-generator.ts create mode 100644 packages/amplify-cli-overrides-helper/.npmignore create mode 100644 packages/amplify-cli-overrides-helper/CHANGELOG.md create mode 100644 packages/amplify-cli-overrides-helper/README.md create mode 100644 packages/amplify-cli-overrides-helper/package.json create mode 100644 packages/amplify-cli-overrides-helper/src/index.ts create mode 100644 packages/amplify-cli-overrides-helper/tsconfig.json create mode 100644 packages/amplify-cli/src/__tests__/commands/build-override.test.ts create mode 100644 packages/amplify-cli/src/commands/build-override.ts create mode 100644 packages/amplify-provider-awscloudformation/resources/overrides-resource/override.ts create mode 100644 packages/amplify-provider-awscloudformation/resources/overrides-resource/package.json create mode 100644 packages/amplify-provider-awscloudformation/resources/overrides-resource/tsconfig.json create mode 100644 packages/amplify-provider-awscloudformation/resources/overrides-resource/tsconfig.resource.json create mode 100644 packages/amplify-provider-awscloudformation/src/commands/awscloudformation/override.ts create mode 100644 packages/amplify-provider-awscloudformation/src/override-manager/index.ts create mode 100644 packages/amplify-provider-awscloudformation/src/override-manager/transform-resource.ts diff --git a/.eslintrc.js b/.eslintrc.js index 092c091ea4a..89a006999ab 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -323,6 +323,7 @@ module.exports = { '/packages/amplify-graphql-*transformer*/lib', '/packages/amplify-provider-awscloudformation/lib', '/packages/amplify-console-integration-tests/lib', + '/packages/amplify-cli-overrides-helper/lib', // Ignore CHANGELOG.md files '/packages/*/CHANGELOG.md', diff --git a/.gitignore b/.gitignore index b2b840c431f..6b24ec226e4 100644 --- a/.gitignore +++ b/.gitignore @@ -67,3 +67,5 @@ packages/**/reports/junit/* test.out.log *.tsbuildinfo packages/amplify-graphiql-explorer/.eslintcache +packages/amplify-cli-overrides-helper/lib + diff --git a/packages/amplify-cli-core/src/category-interfaces/amplify-base-cdk-types.ts b/packages/amplify-cli-core/src/category-interfaces/amplify-base-cdk-types.ts new file mode 100644 index 00000000000..8b85ac9ca2b --- /dev/null +++ b/packages/amplify-cli-core/src/category-interfaces/amplify-base-cdk-types.ts @@ -0,0 +1,32 @@ +//Define all common classes and interfaces required to generate cloudformation using CDK. +import * as cdk from '@aws-cdk/core'; + +//Base template +//Customer can use these params to mutate the Cloudformation for the resource +export interface AmplifyStackTemplate { + addCfnParameter(props: cdk.CfnParameterProps, logicalId: string): void; + addCfnOutput(props: cdk.CfnOutputProps, logicalId: string): void; + addCfnMapping(props: cdk.CfnMappingProps, logicalId: string): void; + addCfnCondition(props: cdk.CfnConditionProps, logicalId: string): void; + + getCfnParameter(logicalId: string): cdk.CfnParameter; + getCfnOutput(logicalId: string): cdk.CfnOutput; + getCfnMapping(logicalId: string): cdk.CfnMapping; + getCfnCondition(logicalId: string): cdk.CfnCondition; +} + +export interface Template { + AWSTemplateFormatVersion?: string; + Description?: string; + Metadata?: Record; + Parameters?: Record; + Mappings?: { + [key: string]: { + [key: string]: Record; + }; + }; + Conditions?: Record; + Transform?: any; + Resources?: Record; + Outputs?: Record; +} diff --git a/packages/amplify-cli-core/src/category-interfaces/category-base-schema-generator.ts b/packages/amplify-cli-core/src/category-interfaces/category-base-schema-generator.ts new file mode 100644 index 00000000000..1b1501860f8 --- /dev/null +++ b/packages/amplify-cli-core/src/category-interfaces/category-base-schema-generator.ts @@ -0,0 +1,123 @@ +/** + * Utility base classes for all categories : CLIInputSchemaGenerator + * Generates JSON-schema from Typescript structures.The generated schemas + * can be used for run-time validation of Walkthrough/Headless structures. + */ +import { getProgramFromFiles, buildGenerator, PartialArgs } from 'typescript-json-schema'; +import fs from 'fs-extra'; +import path from 'path'; +import Ajv from 'ajv'; +import { printer } from 'amplify-prompts'; + +// Interface types are expected to be exported as "typeName" in the file +export type TypeDef = { + typeName: string; + service: string; +}; + +export class CLIInputSchemaGenerator { + // Paths are relative to the package root + TYPES_SRC_ROOT = './src/provider-utils/awscloudformation/service-walkthrough-types/'; + SCHEMA_FILES_ROOT = './resources/schemas'; + OVERWRITE_SCHEMA_FLAG = '--overwrite'; + + private serviceTypeDefs: TypeDef[]; + + private getSchemaFileNameForType(typeName: string): string { + return `${typeName}.schema.json`; + } + + private getTypesSrcRootForSvc(svcName: string): string { + return `${this.TYPES_SRC_ROOT}/${svcName}-user-input-types.ts`; + } + + private getSvcFileAbsolutePath(svcName: string): string { + return path.resolve(this.getTypesSrcRootForSvc(svcName)); + } + + private printWarningSchemaFileExists() { + printer.info('The interface version must be bumped after any changes.'); + printer.info(`Use the ${this.OVERWRITE_SCHEMA_FLAG} flag to overwrite existing versions`); + printer.info('Skipping this schema'); + } + + private printSuccessSchemaFileWritten(typeName: string) { + printer.info(`Schema written for type ${typeName}.`); + } + + constructor(typeDefs: TypeDef[]) { + this.serviceTypeDefs = typeDefs; + } + + public generateJSONSchemas(): string[] { + const force = process.argv.includes(this.OVERWRITE_SCHEMA_FLAG); + const generatedFilePaths: string[] = []; + + // schema generation settings. see https://www.npmjs.com/package/typescript-json-schema#command-line + const settings: PartialArgs = { + required: true, + }; + + for (const typeDef of this.serviceTypeDefs) { + //get absolute file path to the user-input types for the given service + const svcAbsoluteFilePath = this.getSvcFileAbsolutePath(typeDef.service); + printer.info(svcAbsoluteFilePath); + //generate json-schema from the input-types + const typeSchema = buildGenerator(getProgramFromFiles([svcAbsoluteFilePath]), settings)?.getSchemaForSymbol(typeDef.typeName); + //save json-schema file for the input-types. (used to validate cli-inputs.json) + const outputSchemaFilePath = path.resolve( + path.join(this.SCHEMA_FILES_ROOT, typeDef.service, this.getSchemaFileNameForType(typeDef.typeName)), + ); + if (!force && fs.existsSync(outputSchemaFilePath)) { + this.printWarningSchemaFileExists(); + return generatedFilePaths; + } + fs.ensureFileSync(outputSchemaFilePath); + fs.writeFileSync(outputSchemaFilePath, JSON.stringify(typeSchema, undefined, 4)); + //print success status to the terminal + this.printSuccessSchemaFileWritten(typeDef.typeName); + generatedFilePaths.push(outputSchemaFilePath); + } + return generatedFilePaths; + } +} + +//Read Schema, Validate and return Typescript object. +export class CLIInputSchemaValidator { + _category: string; + _service: string; + _schemaFileName: string; + _ajv: Ajv.Ajv; + + constructor(service: string, category: string, schemaFileName: string) { + this._category = category; + this._service = service; + this._schemaFileName = schemaFileName; + this._ajv = new Ajv(); + } + + async getUserInputSchema() { + try { + return await import(generateSchemaPath(this._category, this._service, this._schemaFileName)); + } catch (ex) { + throw new Error(`Schema defination doesnt exist : ${generateSchemaPath(this._category, this._service, this._schemaFileName)}`); + } + } + + async validateInput(userInput: string): Promise { + const userInputSchema = await this.getUserInputSchema(); + if (userInputSchema.dependencySchemas) { + userInputSchema.dependencySchemas.reduce((acc: { addSchema: (arg0: any) => any }, it: any) => acc.addSchema(it), this._ajv); + } + const validate = this._ajv.compile(userInputSchema); + const input = JSON.parse(userInput); + if (!validate(input) as boolean) { + throw new Error(`Data did not validate against the supplied schema. Underlying errors were ${JSON.stringify(validate.errors)}`); + } + return true; + } +} + +const generateSchemaPath = (category: string, service: string, schemaFileName: string): string => { + return path.join(`@aws-amplify/amplify-category-${category}`, 'resources', 'schemas', `${service}`, `${schemaFileName}.schema.json`); +}; diff --git a/packages/amplify-cli-core/src/category-interfaces/category-input-state.ts b/packages/amplify-cli-core/src/category-interfaces/category-input-state.ts new file mode 100644 index 00000000000..34dcc9fb89c --- /dev/null +++ b/packages/amplify-cli-core/src/category-interfaces/category-input-state.ts @@ -0,0 +1,12 @@ +import { $TSAny } from '..'; + +export abstract class CategoryInputState { + _resourceName: string; + constructor(resourceName: string) { + this._resourceName = resourceName; + } + + abstract getCLIInputPayload(): $TSAny; + abstract saveCLIInputPayload(props: $TSAny): void; + abstract isCLIInputsValid(props: $TSAny): void; +} diff --git a/packages/amplify-cli-core/src/category-interfaces/category-override-base.ts b/packages/amplify-cli-core/src/category-interfaces/category-override-base.ts new file mode 100644 index 00000000000..58f40effbdf --- /dev/null +++ b/packages/amplify-cli-core/src/category-interfaces/category-override-base.ts @@ -0,0 +1,24 @@ +import { $TSContext } from '..'; +import { Template } from './amplify-base-cdk-types'; + +export abstract class AmplifyCategoryTransform { + _resourceName: string; + constructor(resourceName: string) { + this._resourceName = resourceName; + } + /** + * Entry point for CFN transformation process for a category + * @param context + */ + abstract transform(context: $TSContext): Promise