diff --git a/.changeset/proud-dolls-hang.md b/.changeset/proud-dolls-hang.md new file mode 100644 index 0000000000..34cdba3c3d --- /dev/null +++ b/.changeset/proud-dolls-hang.md @@ -0,0 +1,7 @@ +--- +'@aws-amplify/integration-tests': patch +'create-amplify': patch +'@aws-amplify/backend': patch +--- + +chore: add new defineBackend to better align with other backend factories diff --git a/.changeset/thick-goats-mix.md b/.changeset/thick-goats-mix.md new file mode 100644 index 0000000000..43308ff875 --- /dev/null +++ b/.changeset/thick-goats-mix.md @@ -0,0 +1,5 @@ +--- +'@aws-amplify/backend-deployer': patch +--- + +do not require confirmation at pipeline deploy diff --git a/.github/workflows/health_checks.yml b/.github/workflows/health_checks.yml index 1b7108f10b..5579ac83bb 100644 --- a/.github/workflows/health_checks.yml +++ b/.github/workflows/health_checks.yml @@ -74,6 +74,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} + timeout-minutes: 60 needs: - do_include_e2e - build diff --git a/packages/backend-deployer/src/cdk_deployer.test.ts b/packages/backend-deployer/src/cdk_deployer.test.ts index ca1102eb8e..9126cf2302 100644 --- a/packages/backend-deployer/src/cdk_deployer.test.ts +++ b/packages/backend-deployer/src/cdk_deployer.test.ts @@ -52,7 +52,7 @@ void describe('invokeCDKCommand', () => { void it('handles options for branch deployments', async () => { await invoker.deploy(uniqueBackendIdentifier); assert.strictEqual(execaMock.mock.callCount(), 1); - assert.equal(execaMock.mock.calls[0].arguments[1]?.length, 12); + assert.equal(execaMock.mock.calls[0].arguments[1]?.length, 14); assert.deepStrictEqual(execaMock.mock.calls[0].arguments[1], [ 'cdk', 'deploy', @@ -66,6 +66,8 @@ void describe('invokeCDKCommand', () => { 'backend-id=123', '--context', 'branch-name=testBranch', + '--require-approval', + 'never', ]); }); @@ -161,7 +163,7 @@ void describe('invokeCDKCommand', () => { 'es2022', 'amplify/backend.ts', ]); - assert.equal(execaMock.mock.calls[1].arguments[1]?.length, 14); + assert.equal(execaMock.mock.calls[1].arguments[1]?.length, 16); assert.deepStrictEqual(execaMock.mock.calls[1].arguments[1], [ 'cdk', 'deploy', @@ -175,6 +177,8 @@ void describe('invokeCDKCommand', () => { 'backend-id=123', '--context', 'branch-name=testBranch', + '--require-approval', + 'never', '--context', `${CDKContextKey.DEPLOYMENT_TYPE}=BRANCH`, ]); @@ -226,7 +230,7 @@ void describe('invokeCDKCommand', () => { validateAppSources: true, }); assert.strictEqual(execaMock.mock.callCount(), 1); - assert.equal(execaMock.mock.calls[0].arguments[1]?.length, 14); + assert.equal(execaMock.mock.calls[0].arguments[1]?.length, 16); assert.deepStrictEqual(execaMock.mock.calls[0].arguments[1], [ 'cdk', 'deploy', @@ -240,6 +244,8 @@ void describe('invokeCDKCommand', () => { 'backend-id=123', '--context', 'branch-name=testBranch', + '--require-approval', + 'never', '--context', 'deployment-type=BRANCH', ]); diff --git a/packages/backend-deployer/src/cdk_deployer.ts b/packages/backend-deployer/src/cdk_deployer.ts index 51339327d3..01c4c6d4bc 100644 --- a/packages/backend-deployer/src/cdk_deployer.ts +++ b/packages/backend-deployer/src/cdk_deployer.ts @@ -131,7 +131,9 @@ export class CDKDeployer implements BackendDeployer { if (deploymentType !== BackendDeploymentType.SANDBOX) { cdkCommandArgs.push( '--context', - `branch-name=${uniqueBackendIdentifier.disambiguator}` + `branch-name=${uniqueBackendIdentifier.disambiguator}`, + '--require-approval', + 'never' ); } } diff --git a/packages/backend/API.md b/packages/backend/API.md index 8b7b834ec4..2f2e2ddef6 100644 --- a/packages/backend/API.md +++ b/packages/backend/API.md @@ -18,18 +18,20 @@ import { Stack } from 'aws-cdk-lib'; export { a } // @public -export class Backend>> { - constructor(constructFactories: T, stack?: Stack); +export type Backend>> = { getOrCreateStack: (name: string) => Stack; readonly resources: { [K in keyof T]: ReturnType; }; -} +}; export { ClientSchema } export { defineAuth } +// @public +export const defineBackend: >>(constructFactories: T) => Backend; + export { defineData } export { defineStorage } diff --git a/packages/backend/src/backend.ts b/packages/backend/src/backend.ts index 32cd62597a..81f105f6cd 100644 --- a/packages/backend/src/backend.ts +++ b/packages/backend/src/backend.ts @@ -1,103 +1,14 @@ -import { Construct } from 'constructs'; import { ConstructFactory } from '@aws-amplify/plugin-types'; +import { Construct } from 'constructs'; import { Stack } from 'aws-cdk-lib'; -import { - NestedStackResolver, - StackResolver, -} from './engine/nested_stack_resolver.js'; -import { SingletonConstructContainer } from './engine/singleton_construct_container.js'; -import { ToggleableImportPathVerifier } from './engine/toggleable_import_path_verifier.js'; -import { - AttributionMetadataStorage, - StackMetadataBackendOutputStorageStrategy, -} from '@aws-amplify/backend-output-storage'; -import { createDefaultStack } from './default_stack_factory.js'; -import { getUniqueBackendIdentifier } from './backend_identifier.js'; -import { - BackendDeploymentType, - SandboxBackendIdentifier, -} from '@aws-amplify/platform-core'; -import { stackOutputKey } from '@aws-amplify/backend-output-schemas'; -import { fileURLToPath } from 'url'; - -// 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'; /** - * Class that collects and instantiates all the Amplify backend constructs + * Top level type for instance returned from `defineBackend`. Contains property `resources` for overriding + * Amplify generated resources and `getOrCreateStack()` for adding custom resources. */ -export class Backend>> { - private readonly stackResolver: StackResolver; - /** - * These are the resolved CDK constructs that are created by the inputs to the constructor - * Used for overriding properties of underlying CDK constructs or to reference in custom CDK code - */ +export type Backend>> = { + getOrCreateStack: (name: string) => Stack; readonly resources: { [K in keyof T]: ReturnType; }; - /** - * 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()) { - new AttributionMetadataStorage().storeAttributionMetadata( - stack, - rootStackTypeIdentifier, - fileURLToPath(new URL('../package.json', import.meta.url)) - ); - this.stackResolver = new NestedStackResolver(stack); - - const constructContainer = new SingletonConstructContainer( - this.stackResolver - ); - - const outputStorageStrategy = new StackMetadataBackendOutputStorageStrategy( - stack - ); - - const uniqueBackendIdentifier = getUniqueBackendIdentifier(stack); - outputStorageStrategy.addBackendOutputEntry(stackOutputKey, { - version: '1', - payload: { - deploymentType: - uniqueBackendIdentifier instanceof SandboxBackendIdentifier - ? BackendDeploymentType.SANDBOX - : BackendDeploymentType.BRANCH, - }, - }); - - const importPathVerifier = new ToggleableImportPathVerifier(); - - // register providers but don't actually execute anything yet - Object.values(constructFactories).forEach((factory) => { - if (typeof factory.provides === 'string') { - constructContainer.registerConstructFactory(factory.provides, factory); - } - }); - - // now invoke all the factories and collect the constructs into this.resources - // eslint-disable-next-line @typescript-eslint/no-explicit-any - this.resources = {} as any; - Object.entries(constructFactories).forEach( - ([resourceName, constructFactory]) => { - // The type inference on this.resources is not happy about this assignment because it doesn't know the exact type of .getInstance() - // However, the assignment is okay because we are iterating over the entries of constructFactories and assigning the resource name to the corresponding instance - this.resources[resourceName as keyof T] = constructFactory.getInstance( - { - constructContainer, - outputStorageStrategy, - importPathVerifier, - } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ) as any; - } - ); - } - - /** - * Returns a CDK stack within the Amplify project that can be used for creating custom resources - */ - getOrCreateStack = (name: string): Stack => { - return this.stackResolver.getStackFor(name); - }; -} +}; diff --git a/packages/backend/src/backend.test.ts b/packages/backend/src/backend_factory.test.ts similarity index 94% rename from packages/backend/src/backend.test.ts rename to packages/backend/src/backend_factory.test.ts index 2679a5d8b8..23bd6584a3 100644 --- a/packages/backend/src/backend.test.ts +++ b/packages/backend/src/backend_factory.test.ts @@ -2,7 +2,7 @@ import { beforeEach, describe, it } from 'node:test'; import { ConstructFactory } from '@aws-amplify/plugin-types'; import { Bucket } from 'aws-cdk-lib/aws-s3'; import { Construct } from 'constructs'; -import { Backend } from './backend.js'; +import { BackendFactory } from './backend_factory.js'; import { App, Stack } from 'aws-cdk-lib'; import { Template } from 'aws-cdk-lib/assertions'; import assert from 'node:assert'; @@ -41,7 +41,7 @@ void describe('Backend', () => { }, }; - new Backend( + new BackendFactory( { testConstructFactory, }, @@ -76,7 +76,7 @@ void describe('Backend', () => { }, }; - new Backend( + new BackendFactory( { testConstructFactory, }, @@ -116,7 +116,7 @@ void describe('Backend', () => { }, }; - const backend = new Backend( + const backend = new BackendFactory( { testConstructFactory, }, @@ -126,7 +126,7 @@ void describe('Backend', () => { }); void it('stores attribution metadata in root stack', () => { - new Backend({}, rootStack); + new BackendFactory({}, rootStack); const rootStackTemplate = Template.fromStack(rootStack); assert.equal( JSON.parse(rootStackTemplate.toJSON().Description).stackType, @@ -136,7 +136,7 @@ void describe('Backend', () => { void describe('getOrCreateStack', () => { void it('returns nested stack', () => { - const backend = new Backend({}, rootStack); + const backend = new BackendFactory({}, rootStack); const testStack = backend.getOrCreateStack('testStack'); assert.strictEqual(rootStack.node.findChild('testStack'), testStack); }); diff --git a/packages/backend/src/backend_factory.ts b/packages/backend/src/backend_factory.ts new file mode 100644 index 0000000000..aaeec7ae7f --- /dev/null +++ b/packages/backend/src/backend_factory.ts @@ -0,0 +1,117 @@ +import { Construct } from 'constructs'; +import { ConstructFactory } from '@aws-amplify/plugin-types'; +import { Stack } from 'aws-cdk-lib'; +import { + NestedStackResolver, + StackResolver, +} from './engine/nested_stack_resolver.js'; +import { SingletonConstructContainer } from './engine/singleton_construct_container.js'; +import { ToggleableImportPathVerifier } from './engine/toggleable_import_path_verifier.js'; +import { + AttributionMetadataStorage, + StackMetadataBackendOutputStorageStrategy, +} from '@aws-amplify/backend-output-storage'; +import { createDefaultStack } from './default_stack_factory.js'; +import { getUniqueBackendIdentifier } from './backend_identifier.js'; +import { + BackendDeploymentType, + SandboxBackendIdentifier, +} from '@aws-amplify/platform-core'; +import { stackOutputKey } from '@aws-amplify/backend-output-schemas'; +import { fileURLToPath } from 'url'; +import { Backend } from './backend.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'; + +/** + * Factory that collects and instantiates all the Amplify backend constructs + */ +export class BackendFactory< + T extends Record> +> implements Backend +{ + private readonly stackResolver: StackResolver; + /** + * These are the resolved CDK constructs that are created by the inputs to the constructor + * Used for overriding properties of underlying CDK constructs or to reference in custom CDK code + */ + readonly resources: { + [K in keyof T]: ReturnType; + }; + /** + * 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()) { + new AttributionMetadataStorage().storeAttributionMetadata( + stack, + rootStackTypeIdentifier, + fileURLToPath(new URL('../package.json', import.meta.url)) + ); + this.stackResolver = new NestedStackResolver(stack); + + const constructContainer = new SingletonConstructContainer( + this.stackResolver + ); + + const outputStorageStrategy = new StackMetadataBackendOutputStorageStrategy( + stack + ); + + const uniqueBackendIdentifier = getUniqueBackendIdentifier(stack); + outputStorageStrategy.addBackendOutputEntry(stackOutputKey, { + version: '1', + payload: { + deploymentType: + uniqueBackendIdentifier instanceof SandboxBackendIdentifier + ? BackendDeploymentType.SANDBOX + : BackendDeploymentType.BRANCH, + }, + }); + + const importPathVerifier = new ToggleableImportPathVerifier(); + + // register providers but don't actually execute anything yet + Object.values(constructFactories).forEach((factory) => { + if (typeof factory.provides === 'string') { + constructContainer.registerConstructFactory(factory.provides, factory); + } + }); + + // now invoke all the factories and collect the constructs into this.resources + // eslint-disable-next-line @typescript-eslint/no-explicit-any + this.resources = {} as any; + Object.entries(constructFactories).forEach( + ([resourceName, constructFactory]) => { + // The type inference on this.resources is not happy about this assignment because it doesn't know the exact type of .getInstance() + // However, the assignment is okay because we are iterating over the entries of constructFactories and assigning the resource name to the corresponding instance + this.resources[resourceName as keyof T] = constructFactory.getInstance( + { + constructContainer, + outputStorageStrategy, + importPathVerifier, + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ) as any; + } + ); + } + + /** + * Returns a CDK stack within the Amplify project that can be used for creating custom resources + */ + getOrCreateStack = (name: string): Stack => { + return this.stackResolver.getStackFor(name); + }; +} + +/** + * 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 = < + T extends Record> +>( + constructFactories: T +): Backend => new BackendFactory(constructFactories); diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 14d2ba18c1..931df8a103 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -1,3 +1,4 @@ +export { defineBackend } from './backend_factory.js'; export * from './backend.js'; export * from './secret.js'; diff --git a/packages/create-amplify/templates/basic-auth-data/amplify/backend.ts b/packages/create-amplify/templates/basic-auth-data/amplify/backend.ts index 7e5c96bfa5..56375d22c4 100644 --- a/packages/create-amplify/templates/basic-auth-data/amplify/backend.ts +++ b/packages/create-amplify/templates/basic-auth-data/amplify/backend.ts @@ -1,8 +1,8 @@ -import { Backend } from '@aws-amplify/backend'; +import { defineBackend } from '@aws-amplify/backend'; import { auth } from './auth/resource.js'; import { data } from './data/resource.js'; -new Backend({ +defineBackend({ auth, data, }); diff --git a/packages/integration-tests/src/cdk_snapshot_test_suite_generator.ts b/packages/integration-tests/src/cdk_snapshot_test_suite_generator.ts index f7876a55be..2eb32ee390 100644 --- a/packages/integration-tests/src/cdk_snapshot_test_suite_generator.ts +++ b/packages/integration-tests/src/cdk_snapshot_test_suite_generator.ts @@ -9,7 +9,7 @@ import { fileURLToPath } from 'url'; * If dirPath is an absolute path, it points to the directory * If dirPath is relative, the directory is located at path.resolve(\, relativeDirPath) * Each subdirectory is the name of the test - * Within each test directory is a backend.ts file that contains the `new Backend({...})` entry point + * Within each test directory is a backend.ts file that contains the `defineBackend({...})` entry point * Within each test directory is an `expected-cdk-out` directory that contains a snapshot of the expected synthesis result of the backend * @example * diff --git a/packages/integration-tests/test-projects/data-storage-auth-with-triggers/amplify/backend.ts b/packages/integration-tests/test-projects/data-storage-auth-with-triggers/amplify/backend.ts index 34c8cc14d7..3f02f903a7 100644 --- a/packages/integration-tests/test-projects/data-storage-auth-with-triggers/amplify/backend.ts +++ b/packages/integration-tests/test-projects/data-storage-auth-with-triggers/amplify/backend.ts @@ -1,10 +1,10 @@ -import { Backend } from '@aws-amplify/backend'; +import { defineBackend } from '@aws-amplify/backend'; import { auth } from './auth/resource.js'; import { storage } from './storage/resource.js'; import { myFunc } from './function.js'; import { data } from './data/resource.js'; -new Backend({ +defineBackend({ auth, storage, myFunc, diff --git a/packages/integration-tests/test-projects/minimalist-project-with-typescript-idioms/amplify/backend.ts b/packages/integration-tests/test-projects/minimalist-project-with-typescript-idioms/amplify/backend.ts index 4d99db91c3..a4a86080b7 100644 --- a/packages/integration-tests/test-projects/minimalist-project-with-typescript-idioms/amplify/backend.ts +++ b/packages/integration-tests/test-projects/minimalist-project-with-typescript-idioms/amplify/backend.ts @@ -1,7 +1,7 @@ -import { Backend } from '@aws-amplify/backend'; +import { defineBackend } from '@aws-amplify/backend'; import { storage } from './storage/resource.js'; -new Backend({ +defineBackend({ storage, });