From e3dfa31cf22031a48324ab65d00561c8236a223b Mon Sep 17 00:00:00 2001 From: renevatium Date: Thu, 20 Jun 2024 23:25:57 +0100 Subject: [PATCH 1/9] pass valid props to root stack --- .changeset/chatty-gifts-bathe.md | 5 +++ packages/backend/API.md | 6 ++- packages/backend/src/backend_factory.ts | 20 ++++++++-- packages/backend/src/default_stack_factory.ts | 11 +++++- packages/backend/src/engine/amplify_stack.ts | 37 +++++++++++++++++-- packages/backend/src/index.ts | 1 + .../project_environment_main_stack_creator.ts | 7 ++-- 7 files changed, 75 insertions(+), 12 deletions(-) create mode 100644 .changeset/chatty-gifts-bathe.md diff --git a/.changeset/chatty-gifts-bathe.md b/.changeset/chatty-gifts-bathe.md new file mode 100644 index 0000000000..cb7d99eba9 --- /dev/null +++ b/.changeset/chatty-gifts-bathe.md @@ -0,0 +1,5 @@ +--- +'@aws-amplify/backend': minor +--- + +Allow StackProps to be passed from defineBackend down to AmplifyStack for root Stack configuration diff --git a/packages/backend/API.md b/packages/backend/API.md index 1cb83ee09f..c61c7d8940 100644 --- a/packages/backend/API.md +++ b/packages/backend/API.md @@ -31,6 +31,7 @@ import { ResourceProvider } from '@aws-amplify/plugin-types'; import { SsmEnvironmentEntriesGenerator } from '@aws-amplify/plugin-types'; import { SsmEnvironmentEntry } from '@aws-amplify/plugin-types'; import { Stack } from 'aws-cdk-lib'; +import { StackProps } from 'aws-cdk-lib'; export { a } @@ -70,7 +71,7 @@ export { ConstructFactoryGetInstanceProps } export { defineAuth } // @public -export const defineBackend: (constructFactories: T) => Backend; +export const defineBackend: (constructFactories: T, mainStackProps?: MainStackProps) => Backend; // @public (undocumented) export type DefineBackendProps = Record>>> & { @@ -89,6 +90,9 @@ export { GenerateContainerEntryProps } export { ImportPathVerifier } +// @public +export type MainStackProps = Pick; + export { ResourceProvider } // @public diff --git a/packages/backend/src/backend_factory.ts b/packages/backend/src/backend_factory.ts index 208a82ea17..8a7923e547 100644 --- a/packages/backend/src/backend_factory.ts +++ b/packages/backend/src/backend_factory.ts @@ -27,6 +27,7 @@ import { import { CustomOutputsAccumulator } from './engine/custom_outputs_accumulator.js'; import { ObjectAccumulator } from '@aws-amplify/platform-core'; import { DefaultResourceNameValidator } from './engine/validations/default_resource_name_validator.js'; +import { MainStackProps } from './engine/amplify_stack.js'; // Be very careful editing this value. It is the value used in the BI metrics to attribute stacks as Amplify root stacks const rootStackTypeIdentifier = 'root'; @@ -55,7 +56,15 @@ export class BackendFactory< * Initialize an Amplify backend with the given construct factories and in the given CDK App. * If no CDK App is specified a new one is created */ - constructor(constructFactories: T, stack: Stack = createDefaultStack()) { + constructor( + constructFactories: T, + stack?: Stack, + mainStackProps?: MainStackProps + ) { + if (stack === undefined) { + stack = createDefaultStack(undefined, mainStackProps); + } + new AttributionMetadataStorage().storeAttributionMetadata( stack, rootStackTypeIdentifier, @@ -150,9 +159,14 @@ export class BackendFactory< * @param constructFactories - list of backend factories such as those created by `defineAuth` or `defineData` */ export const defineBackend = ( - constructFactories: T + constructFactories: T, + mainStackProps?: MainStackProps ): Backend => { - const backend = new BackendFactory(constructFactories); + const backend = new BackendFactory( + constructFactories, + undefined, + mainStackProps + ); return { ...backend.resources, createStack: backend.createStack, diff --git a/packages/backend/src/default_stack_factory.ts b/packages/backend/src/default_stack_factory.ts index b084c6ce6d..26535c1e14 100644 --- a/packages/backend/src/default_stack_factory.ts +++ b/packages/backend/src/default_stack_factory.ts @@ -1,14 +1,21 @@ import { App, Stack } from 'aws-cdk-lib'; import { ProjectEnvironmentMainStackCreator } from './project_environment_main_stack_creator.js'; import { getBackendIdentifier } from './backend_identifier.js'; +import { MainStackProps } from './engine/amplify_stack.js'; /** * Creates a default CDK scope for the Amplify backend to use if no scope is provided to the constructor */ -export const createDefaultStack = (app = new App()): Stack => { +export const createDefaultStack = ( + app?: App, + props?: MainStackProps +): Stack => { + if (app === undefined) { + app = new App(); + } const mainStackCreator = new ProjectEnvironmentMainStackCreator( app, getBackendIdentifier(app) ); - return mainStackCreator.getOrCreateMainStack(); + return mainStackCreator.getOrCreateMainStack(props); }; diff --git a/packages/backend/src/engine/amplify_stack.ts b/packages/backend/src/engine/amplify_stack.ts index 4c7475658b..ec8c87add1 100644 --- a/packages/backend/src/engine/amplify_stack.ts +++ b/packages/backend/src/engine/amplify_stack.ts @@ -1,8 +1,39 @@ import { AmplifyFault } from '@aws-amplify/platform-core'; -import { Aspects, CfnElement, IAspect, Stack } from 'aws-cdk-lib'; +import { Aspects, CfnElement, IAspect, Stack, StackProps } from 'aws-cdk-lib'; import { Role } from 'aws-cdk-lib/aws-iam'; import { Construct, IConstruct } from 'constructs'; +/** + * Props for root CDK Stack + * @see {@link https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.StackProps.html | AWS documentation for aws-cdk-lib.StackProps} + * + * Props are picked to ensure explicit addition of new StackProps is required. + * Props incompatible with Amplify's intended Stack hierarchy, build or deployment processes are ommited: + * - stackName: Conflicts with dynamic resource naming. + * - synthesizer: Conflicts with managed deployments and resource references. + * - terminationProtection: Conflicts with sandbox/app delete. + * - permissionsBoundary: Conflicts with single root Stack ethos (i.e. Unable to create Role prior to `defineBackend`). + * + * Props are passed down from `defineBackend`: + * @example Set explicit region (e.g. for `new cloudfront.experimental.EdgeFunction`) + * ``` + * defineBackend({}, { + * env: + * region: 'us-east-1' // Any valid AWS region + * } + * }) + * ``` + */ +export type MainStackProps = Pick< + StackProps, + | 'description' + | 'env' + | 'tags' + | 'analyticsReporting' + | 'crossRegionReferences' + | 'suppressTemplateIndentation' +>; + /** * Amplify-specific Stack implementation to handle cross-cutting concerns for all Amplify stacks */ @@ -10,8 +41,8 @@ export class AmplifyStack extends Stack { /** * Default constructor */ - constructor(scope: Construct, id: string) { - super(scope, id); + constructor(scope: Construct, id: string, props?: MainStackProps) { + super(scope, id, props); Aspects.of(this).add(new CognitoRoleTrustPolicyValidator()); } /** diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 039b5de54c..0ece4012a9 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -1,3 +1,4 @@ +export { type MainStackProps } from './engine/amplify_stack.js'; export { defineBackend } from './backend_factory.js'; export * from './backend.js'; export * from './secret.js'; diff --git a/packages/backend/src/project_environment_main_stack_creator.ts b/packages/backend/src/project_environment_main_stack_creator.ts index fbadb59b57..08f3353067 100644 --- a/packages/backend/src/project_environment_main_stack_creator.ts +++ b/packages/backend/src/project_environment_main_stack_creator.ts @@ -1,7 +1,7 @@ import { BackendIdentifier, MainStackCreator } from '@aws-amplify/plugin-types'; import { Construct } from 'constructs'; import { Stack, Tags } from 'aws-cdk-lib'; -import { AmplifyStack } from './engine/amplify_stack.js'; +import { AmplifyStack, MainStackProps } from './engine/amplify_stack.js'; import { BackendIdentifierConversions } from '@aws-amplify/platform-core'; /** @@ -20,11 +20,12 @@ export class ProjectEnvironmentMainStackCreator implements MainStackCreator { /** * Get a stack for this environment in the provided CDK scope */ - getOrCreateMainStack = (): Stack => { + getOrCreateMainStack = (props?: MainStackProps): Stack => { if (this.mainStack === undefined) { this.mainStack = new AmplifyStack( this.scope, - BackendIdentifierConversions.toStackName(this.backendId) + BackendIdentifierConversions.toStackName(this.backendId), + props ); } From 93b77dcadb1e2d8531e8492fd91ccf2fa86f621e Mon Sep 17 00:00:00 2001 From: renevatium Date: Mon, 24 Jun 2024 23:42:03 +0100 Subject: [PATCH 2/9] mainStackProps as key in DefineBackendProps --- packages/backend/API.md | 11 ++++++++--- packages/backend/src/backend.ts | 9 +++++++-- packages/backend/src/backend_factory.ts | 22 +++++++++++----------- 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/packages/backend/API.md b/packages/backend/API.md index c61c7d8940..cbe0d4e600 100644 --- a/packages/backend/API.md +++ b/packages/backend/API.md @@ -42,7 +42,7 @@ export { AuthResources } export { AuthRoleName } // @public -export type Backend = BackendBase & { +export type Backend = BackendBase & { [K in keyof T]: Omit, keyof ResourceAccessAcceptorFactory>; }; @@ -71,13 +71,18 @@ export { ConstructFactoryGetInstanceProps } export { defineAuth } // @public -export const defineBackend: (constructFactories: T, mainStackProps?: MainStackProps) => Backend; +export const defineBackend: (constructFactories: T, props?: DefineBackendProps) => Backend; // @public (undocumented) -export type DefineBackendProps = Record>>> & { +export type DefineBackendConstructFactories = Record>>> & { [K in keyof BackendBase]?: never; }; +// @public (undocumented) +export type DefineBackendProps = { + mainStackProps: MainStackProps; +}; + export { defineData } export { defineFunction } diff --git a/packages/backend/src/backend.ts b/packages/backend/src/backend.ts index a4e767ada2..3dac6c6cac 100644 --- a/packages/backend/src/backend.ts +++ b/packages/backend/src/backend.ts @@ -6,6 +6,7 @@ import { } from '@aws-amplify/plugin-types'; import { Stack } from 'aws-cdk-lib'; import { ClientConfig } from '@aws-amplify/client-config'; +import { MainStackProps } from './index.internal.js'; export type BackendBase = { createStack: (name: string) => Stack; @@ -15,19 +16,23 @@ export type BackendBase = { }; // Type that allows construct factories to be defined using any keys except those used in BackendHelpers -export type DefineBackendProps = Record< +export type DefineBackendConstructFactories = Record< string, ConstructFactory< ResourceProvider & Partial> > > & { [K in keyof BackendBase]?: never }; +export type DefineBackendProps = { + mainStackProps: MainStackProps; +}; + /** * Use `defineBackend` to create an instance of this type. * This object has the Amplify BackendBase methods on it for interacting with the backend. * It also has dynamic properties based on the resources passed into `defineBackend` */ -export type Backend = BackendBase & { +export type Backend = BackendBase & { [K in keyof T]: Omit< ReturnType, keyof ResourceAccessAcceptorFactory diff --git a/packages/backend/src/backend_factory.ts b/packages/backend/src/backend_factory.ts index 8a7923e547..ae429cf91e 100644 --- a/packages/backend/src/backend_factory.ts +++ b/packages/backend/src/backend_factory.ts @@ -18,7 +18,11 @@ import { createDefaultStack } from './default_stack_factory.js'; import { getBackendIdentifier } from './backend_identifier.js'; import { platformOutputKey } from '@aws-amplify/backend-output-schemas'; import { fileURLToPath } from 'node:url'; -import { Backend, DefineBackendProps } from './backend.js'; +import { + Backend, + DefineBackendConstructFactories, + DefineBackendProps, +} from './backend.js'; import { AmplifyBranchLinkerConstruct } from './engine/branch-linker/branch_linker_construct.js'; import { ClientConfig, @@ -27,7 +31,6 @@ import { import { CustomOutputsAccumulator } from './engine/custom_outputs_accumulator.js'; import { ObjectAccumulator } from '@aws-amplify/platform-core'; import { DefaultResourceNameValidator } from './engine/validations/default_resource_name_validator.js'; -import { MainStackProps } from './engine/amplify_stack.js'; // Be very careful editing this value. It is the value used in the BI metrics to attribute stacks as Amplify root stacks const rootStackTypeIdentifier = 'root'; @@ -52,6 +55,7 @@ export class BackendFactory< private readonly stackResolver: StackResolver; private readonly customOutputsAccumulator: CustomOutputsAccumulator; + /** * Initialize an Amplify backend with the given construct factories and in the given CDK App. * If no CDK App is specified a new one is created @@ -59,10 +63,10 @@ export class BackendFactory< constructor( constructFactories: T, stack?: Stack, - mainStackProps?: MainStackProps + props?: DefineBackendProps ) { if (stack === undefined) { - stack = createDefaultStack(undefined, mainStackProps); + stack = createDefaultStack(undefined, props?.mainStackProps); } new AttributionMetadataStorage().storeAttributionMetadata( @@ -158,15 +162,11 @@ export class BackendFactory< * Creates a new Amplify backend instance and returns it * @param constructFactories - list of backend factories such as those created by `defineAuth` or `defineData` */ -export const defineBackend = ( +export const defineBackend = ( constructFactories: T, - mainStackProps?: MainStackProps + props?: DefineBackendProps ): Backend => { - const backend = new BackendFactory( - constructFactories, - undefined, - mainStackProps - ); + const backend = new BackendFactory(constructFactories, undefined, props); return { ...backend.resources, createStack: backend.createStack, From e931b42cfb8eebc6a4023feff4fb41abd0e50560 Mon Sep 17 00:00:00 2001 From: renevatium Date: Tue, 25 Jun 2024 00:01:33 +0100 Subject: [PATCH 3/9] pick only MainStackProps relevant to region handling --- packages/backend/src/engine/amplify_stack.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/packages/backend/src/engine/amplify_stack.ts b/packages/backend/src/engine/amplify_stack.ts index ec8c87add1..e775f69c10 100644 --- a/packages/backend/src/engine/amplify_stack.ts +++ b/packages/backend/src/engine/amplify_stack.ts @@ -8,7 +8,7 @@ import { Construct, IConstruct } from 'constructs'; * @see {@link https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.StackProps.html | AWS documentation for aws-cdk-lib.StackProps} * * Props are picked to ensure explicit addition of new StackProps is required. - * Props incompatible with Amplify's intended Stack hierarchy, build or deployment processes are ommited: + * Props incompatible with Amplify's intended Stack hierarchy, build or deployment processes should always be ommited: * - stackName: Conflicts with dynamic resource naming. * - synthesizer: Conflicts with managed deployments and resource references. * - terminationProtection: Conflicts with sandbox/app delete. @@ -24,15 +24,7 @@ import { Construct, IConstruct } from 'constructs'; * }) * ``` */ -export type MainStackProps = Pick< - StackProps, - | 'description' - | 'env' - | 'tags' - | 'analyticsReporting' - | 'crossRegionReferences' - | 'suppressTemplateIndentation' ->; +export type MainStackProps = Pick; /** * Amplify-specific Stack implementation to handle cross-cutting concerns for all Amplify stacks From 1f79750ddad54ce488e4e430ddf810b22e5cce12 Mon Sep 17 00:00:00 2001 From: renevatium Date: Tue, 25 Jun 2024 00:16:54 +0100 Subject: [PATCH 4/9] fix missing update:api for #e931b42 --- packages/backend/API.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/API.md b/packages/backend/API.md index cbe0d4e600..76c52df0ca 100644 --- a/packages/backend/API.md +++ b/packages/backend/API.md @@ -96,7 +96,7 @@ export { GenerateContainerEntryProps } export { ImportPathVerifier } // @public -export type MainStackProps = Pick; +export type MainStackProps = Pick; export { ResourceProvider } From 28a8743b72d82271ff850ac4e27ea09ae0ff28cf Mon Sep 17 00:00:00 2001 From: renevatium Date: Tue, 25 Jun 2024 00:30:24 +0100 Subject: [PATCH 5/9] mainStackProps in DefineBackendProps should be optional --- packages/backend/src/backend.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/backend.ts b/packages/backend/src/backend.ts index 3dac6c6cac..6698d34ba6 100644 --- a/packages/backend/src/backend.ts +++ b/packages/backend/src/backend.ts @@ -24,7 +24,7 @@ export type DefineBackendConstructFactories = Record< > & { [K in keyof BackendBase]?: never }; export type DefineBackendProps = { - mainStackProps: MainStackProps; + mainStackProps?: MainStackProps; }; /** From 93e0222a06aafaf8a71dff085f062a77d59ef460 Mon Sep 17 00:00:00 2001 From: renevatium Date: Tue, 25 Jun 2024 00:45:00 +0100 Subject: [PATCH 6/9] better import path for MainStackProps --- packages/backend/src/backend.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/backend.ts b/packages/backend/src/backend.ts index 6698d34ba6..7e49411762 100644 --- a/packages/backend/src/backend.ts +++ b/packages/backend/src/backend.ts @@ -6,7 +6,7 @@ import { } from '@aws-amplify/plugin-types'; import { Stack } from 'aws-cdk-lib'; import { ClientConfig } from '@aws-amplify/client-config'; -import { MainStackProps } from './index.internal.js'; +import { MainStackProps } from './engine/amplify_stack.js'; export type BackendBase = { createStack: (name: string) => Stack; From 3b24347294b12115cdb8ca80f822d89fd8706169 Mon Sep 17 00:00:00 2001 From: renevatium Date: Tue, 25 Jun 2024 00:51:43 +0100 Subject: [PATCH 7/9] BackendFactory constructor overloading for conditional prop exclusion --- packages/backend/src/backend_factory.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/backend/src/backend_factory.ts b/packages/backend/src/backend_factory.ts index ae429cf91e..1f86d244e5 100644 --- a/packages/backend/src/backend_factory.ts +++ b/packages/backend/src/backend_factory.ts @@ -39,6 +39,9 @@ const rootStackTypeIdentifier = 'root'; const DEFAULT_CLIENT_CONFIG_VERSION_FOR_BACKEND_ADD_OUTPUT = ClientConfigVersionOption.V1; +// Stricter Omit for constructor overloading +type ExplicitOmit = Pick>; + /** * Factory that collects and instantiates all the Amplify backend constructs */ @@ -56,6 +59,19 @@ export class BackendFactory< private readonly stackResolver: StackResolver; private readonly customOutputsAccumulator: CustomOutputsAccumulator; + /** + * + */ + constructor( + constructFactories: T, + stack: Stack, + props?: ExplicitOmit + ); + /** + * + */ + constructor(constructFactories: T, stack?: never, props?: DefineBackendProps); + /** * Initialize an Amplify backend with the given construct factories and in the given CDK App. * If no CDK App is specified a new one is created From 279f39369c736ba396c888464f3b45527d8e5748 Mon Sep 17 00:00:00 2001 From: renevatium Date: Tue, 25 Jun 2024 06:26:32 +0100 Subject: [PATCH 8/9] revert DefineBackendConstructFactories back to DefineBackendProps and rename new prop --- packages/backend/src/backend.ts | 6 +++--- packages/backend/src/backend_factory.ts | 18 +++++++++++------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/packages/backend/src/backend.ts b/packages/backend/src/backend.ts index 7e49411762..055cde97d0 100644 --- a/packages/backend/src/backend.ts +++ b/packages/backend/src/backend.ts @@ -16,14 +16,14 @@ export type BackendBase = { }; // Type that allows construct factories to be defined using any keys except those used in BackendHelpers -export type DefineBackendConstructFactories = Record< +export type DefineBackendProps = Record< string, ConstructFactory< ResourceProvider & Partial> > > & { [K in keyof BackendBase]?: never }; -export type DefineBackendProps = { +export type DefineBackendOptions = { mainStackProps?: MainStackProps; }; @@ -32,7 +32,7 @@ export type DefineBackendProps = { * This object has the Amplify BackendBase methods on it for interacting with the backend. * It also has dynamic properties based on the resources passed into `defineBackend` */ -export type Backend = BackendBase & { +export type Backend = BackendBase & { [K in keyof T]: Omit< ReturnType, keyof ResourceAccessAcceptorFactory diff --git a/packages/backend/src/backend_factory.ts b/packages/backend/src/backend_factory.ts index 1f86d244e5..ea66f1f82a 100644 --- a/packages/backend/src/backend_factory.ts +++ b/packages/backend/src/backend_factory.ts @@ -20,7 +20,7 @@ import { platformOutputKey } from '@aws-amplify/backend-output-schemas'; import { fileURLToPath } from 'node:url'; import { Backend, - DefineBackendConstructFactories, + DefineBackendOptions, DefineBackendProps, } from './backend.js'; import { AmplifyBranchLinkerConstruct } from './engine/branch-linker/branch_linker_construct.js'; @@ -60,17 +60,21 @@ export class BackendFactory< private readonly customOutputsAccumulator: CustomOutputsAccumulator; /** - * + * stack and props.mainStackProps are mutually exclusive */ constructor( constructFactories: T, stack: Stack, - props?: ExplicitOmit + props?: ExplicitOmit ); /** * */ - constructor(constructFactories: T, stack?: never, props?: DefineBackendProps); + constructor( + constructFactories: T, + stack?: never, + props?: DefineBackendOptions + ); /** * Initialize an Amplify backend with the given construct factories and in the given CDK App. @@ -79,7 +83,7 @@ export class BackendFactory< constructor( constructFactories: T, stack?: Stack, - props?: DefineBackendProps + props?: DefineBackendOptions ) { if (stack === undefined) { stack = createDefaultStack(undefined, props?.mainStackProps); @@ -178,9 +182,9 @@ export class BackendFactory< * Creates a new Amplify backend instance and returns it * @param constructFactories - list of backend factories such as those created by `defineAuth` or `defineData` */ -export const defineBackend = ( +export const defineBackend = ( constructFactories: T, - props?: DefineBackendProps + props?: DefineBackendOptions ): Backend => { const backend = new BackendFactory(constructFactories, undefined, props); return { From 5fb4e0328052cd45aaabe378ae0190eaed5643ed Mon Sep 17 00:00:00 2001 From: renevatium Date: Tue, 25 Jun 2024 06:30:35 +0100 Subject: [PATCH 9/9] revert and update backend API doc --- packages/backend/API.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/backend/API.md b/packages/backend/API.md index 76c52df0ca..7efc2c19a5 100644 --- a/packages/backend/API.md +++ b/packages/backend/API.md @@ -42,7 +42,7 @@ export { AuthResources } export { AuthRoleName } // @public -export type Backend = BackendBase & { +export type Backend = BackendBase & { [K in keyof T]: Omit, keyof ResourceAccessAcceptorFactory>; }; @@ -71,16 +71,16 @@ export { ConstructFactoryGetInstanceProps } export { defineAuth } // @public -export const defineBackend: (constructFactories: T, props?: DefineBackendProps) => Backend; +export const defineBackend: (constructFactories: T, props?: DefineBackendOptions) => Backend; // @public (undocumented) -export type DefineBackendConstructFactories = Record>>> & { - [K in keyof BackendBase]?: never; +export type DefineBackendOptions = { + mainStackProps?: MainStackProps; }; // @public (undocumented) -export type DefineBackendProps = { - mainStackProps: MainStackProps; +export type DefineBackendProps = Record>>> & { + [K in keyof BackendBase]?: never; }; export { defineData }