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..7efc2c19a5 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,12 @@ export { ConstructFactoryGetInstanceProps } export { defineAuth } // @public -export const defineBackend: (constructFactories: T) => Backend; +export const defineBackend: (constructFactories: T, props?: DefineBackendOptions) => Backend; + +// @public (undocumented) +export type DefineBackendOptions = { + mainStackProps?: MainStackProps; +}; // @public (undocumented) export type DefineBackendProps = Record>>> & { @@ -89,6 +95,9 @@ export { GenerateContainerEntryProps } export { ImportPathVerifier } +// @public +export type MainStackProps = Pick; + export { ResourceProvider } // @public diff --git a/packages/backend/src/backend.ts b/packages/backend/src/backend.ts index a4e767ada2..055cde97d0 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 './engine/amplify_stack.js'; export type BackendBase = { createStack: (name: string) => Stack; @@ -22,6 +23,10 @@ export type DefineBackendProps = Record< > > & { [K in keyof BackendBase]?: never }; +export type DefineBackendOptions = { + 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. diff --git a/packages/backend/src/backend_factory.ts b/packages/backend/src/backend_factory.ts index 208a82ea17..ea66f1f82a 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, + DefineBackendOptions, + DefineBackendProps, +} from './backend.js'; import { AmplifyBranchLinkerConstruct } from './engine/branch-linker/branch_linker_construct.js'; import { ClientConfig, @@ -35,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 */ @@ -51,11 +58,37 @@ export class BackendFactory< private readonly stackResolver: StackResolver; private readonly customOutputsAccumulator: CustomOutputsAccumulator; + + /** + * stack and props.mainStackProps are mutually exclusive + */ + constructor( + constructFactories: T, + stack: Stack, + props?: ExplicitOmit + ); + /** + * + */ + constructor( + constructFactories: T, + stack?: never, + props?: DefineBackendOptions + ); + /** * 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, + props?: DefineBackendOptions + ) { + if (stack === undefined) { + stack = createDefaultStack(undefined, props?.mainStackProps); + } + new AttributionMetadataStorage().storeAttributionMetadata( stack, rootStackTypeIdentifier, @@ -150,9 +183,10 @@ export class BackendFactory< * @param constructFactories - list of backend factories such as those created by `defineAuth` or `defineData` */ export const defineBackend = ( - constructFactories: T + constructFactories: T, + props?: DefineBackendOptions ): Backend => { - const backend = new BackendFactory(constructFactories); + const backend = new BackendFactory(constructFactories, undefined, props); 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..e775f69c10 100644 --- a/packages/backend/src/engine/amplify_stack.ts +++ b/packages/backend/src/engine/amplify_stack.ts @@ -1,8 +1,31 @@ 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 should always be 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; + /** * Amplify-specific Stack implementation to handle cross-cutting concerns for all Amplify stacks */ @@ -10,8 +33,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 ); }