Skip to content

Commit

Permalink
Unified Client config schema (#1143)
Browse files Browse the repository at this point in the history
* add changeset

* Remove helper methods

* PR updates

* PR updates

* make version a tighter type

* add API.md

* Make ClientConfig (Gen2) as the only type of client config

* Add version mismatch validations

* Make return of getClientConfig narrower

* Add some tests for backend and platform-core

* More tests

* one more test and fixing custom in legacy converter

* change codegen from quiktype to json2ts

* fix small change

* add different filename for Gen2 client config

* cleanup

* cleanup

* Add other categories in legacy config conversion from Gen2

* Remove sample V2 config version

* Add tests

* Update changeset

* Add data changes

* PR feedback updates

* PR feedback updates

* PR feedback updates

* Update versions description in generate command

* PR feedback updates

* PR feedback updates

* PR feedback updates

* update API after merge from main
  • Loading branch information
Amplifiyer committed Mar 18, 2024
1 parent c393c97 commit 5e12247
Show file tree
Hide file tree
Showing 69 changed files with 3,012 additions and 870 deletions.
10 changes: 10 additions & 0 deletions .changeset/tame-donkeys-compete.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@aws-amplify/client-config': minor
'@aws-amplify/backend-cli': minor
'@aws-amplify/backend': patch
'@aws-amplify/platform-core': patch
'@aws-amplify/integration-tests': patch
'@aws-amplify/plugin-types': patch
---

feat(client-config): Generate client configuration based on a unified JSON schema
3 changes: 3 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,8 @@ examples
verdaccio-cache
expected-cdk-out

#auto generated files
packages/client-config/src/client-config-schema

# Directory below is git-ignored. We create and delete test projects in e2e tests there.
packages/integration-tests/src/e2e-tests
3 changes: 2 additions & 1 deletion packages/backend/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { ConstructContainer } from '@aws-amplify/plugin-types';
import { ConstructContainerEntryGenerator } from '@aws-amplify/plugin-types';
import { ConstructFactory } from '@aws-amplify/plugin-types';
import { ConstructFactoryGetInstanceProps } from '@aws-amplify/plugin-types';
import { DeepPartial } from '@aws-amplify/plugin-types';
import { defineAuth } from '@aws-amplify/backend-auth';
import { defineData } from '@aws-amplify/backend-data';
import { defineFunction } from '@aws-amplify/backend-function';
Expand Down Expand Up @@ -46,7 +47,7 @@ export type Backend<T extends DefineBackendProps> = BackendBase & {
// @public (undocumented)
export type BackendBase = {
createStack: (name: string) => Stack;
addOutput: (clientConfigPart: Partial<ClientConfig>) => void;
addOutput: (clientConfigPart: DeepPartial<ClientConfig>) => void;
};

export { BackendOutputEntry }
Expand Down
8 changes: 6 additions & 2 deletions packages/backend/src/backend.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { ConstructFactory, ResourceProvider } from '@aws-amplify/plugin-types';
import {
ConstructFactory,
DeepPartial,
ResourceProvider,
} from '@aws-amplify/plugin-types';
import { Stack } from 'aws-cdk-lib';
import { ClientConfig } from '@aws-amplify/client-config';

export type BackendBase = {
createStack: (name: string) => Stack;
addOutput: (clientConfigPart: Partial<ClientConfig>) => void;
addOutput: (clientConfigPart: DeepPartial<ClientConfig>) => void;
};

// Type that allows construct factories to be defined using any keys except those used in BackendHelpers
Expand Down
5 changes: 4 additions & 1 deletion packages/backend/src/backend_factory.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { beforeEach, describe, it } from 'node:test';
import {
ConstructFactory,
DeepPartial,
DeploymentType,
ResourceProvider,
} from '@aws-amplify/plugin-types';
Expand All @@ -9,6 +10,7 @@ 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';
import { ClientConfig } from '@aws-amplify/client-config';

const createStackAndSetContext = (deploymentType: DeploymentType): Stack => {
const app = new App();
Expand Down Expand Up @@ -187,7 +189,8 @@ void describe('Backend', () => {
void it('can add custom output', () => {
const rootStack = createStackAndSetContext('sandbox');
const backend = new BackendFactory({}, rootStack);
const clientConfigPartial = {
const clientConfigPartial: DeepPartial<ClientConfig> = {
version: '1',
custom: {
someCustomOutput: 'someCustomOutputValue',
},
Expand Down
23 changes: 20 additions & 3 deletions packages/backend/src/backend_factory.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { ConstructFactory, ResourceProvider } from '@aws-amplify/plugin-types';
import {
ConstructFactory,
DeepPartial,
ResourceProvider,
} from '@aws-amplify/plugin-types';
import { Stack } from 'aws-cdk-lib';
import {
NestedStackResolver,
Expand All @@ -16,13 +20,20 @@ import { platformOutputKey } from '@aws-amplify/backend-output-schemas';
import { fileURLToPath } from 'url';
import { Backend, DefineBackendProps } from './backend.js';
import { AmplifyBranchLinkerConstruct } from './engine/branch-linker/branch_linker_construct.js';
import { ClientConfig } from '@aws-amplify/client-config';
import {
ClientConfig,
ClientConfigVersionOption,
} from '@aws-amplify/client-config';
import { CustomOutputsAccumulator } from './engine/custom_outputs_accumulator.js';
import { ObjectAccumulator } from '@aws-amplify/platform-core';

// 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';

// Client config version that is used by `backend.addOutput()`
const DEFAULT_CLIENT_CONFIG_VERSION_FOR_BACKEND_ADD_OUTPUT =
ClientConfigVersionOption.V1;

/**
* Factory that collects and instantiates all the Amplify backend constructs
*/
Expand Down Expand Up @@ -118,8 +129,14 @@ export class BackendFactory<
return this.stackResolver.createCustomStack(name);
};

addOutput = (clientConfigPart: Partial<ClientConfig>) =>
addOutput = (clientConfigPart: DeepPartial<ClientConfig>) => {
const { version } = clientConfigPart;
if (!version) {
clientConfigPart.version =
DEFAULT_CLIENT_CONFIG_VERSION_FOR_BACKEND_ADD_OUTPUT;
}
this.customOutputsAccumulator.addOutput(clientConfigPart);
};
}

/**
Expand Down
60 changes: 57 additions & 3 deletions packages/backend/src/engine/custom_outputs_accumulator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import assert from 'node:assert';
import {
BackendOutputEntry,
BackendOutputStorageStrategy,
DeepPartial,
} from '@aws-amplify/plugin-types';
import { CustomOutputsAccumulator } from './custom_outputs_accumulator.js';
import {
AmplifyUserError,
ObjectAccumulator,
ObjectAccumulatorPropertyAlreadyExistsError,
ObjectAccumulatorVersionMismatchError,
} from '@aws-amplify/platform-core';
import { ClientConfig } from '@aws-amplify/client-config';
import { StackMetadataBackendOutputStorageStrategy } from '@aws-amplify/backend-output-storage';
Expand Down Expand Up @@ -50,6 +52,30 @@ void describe('Custom outputs accumulator', () => {
assert.strictEqual(storeOutputMock.mock.calls.length, 1);
});

void it('accumulates client config entries for same version and creates single merged output', () => {
const accumulator = new CustomOutputsAccumulator(
stubBackendOutputStorageStrategy,
objectAccumulator
);

const configPart1: DeepPartial<ClientConfig> = {
version: '1',
custom: { output1: 'val1' },
};
const configPart2: DeepPartial<ClientConfig> = {
version: '1',
custom: { output2: 'val2' },
};
accumulator.addOutput(configPart1);
accumulator.addOutput(configPart2);

assert.strictEqual(accumulateMock.mock.calls.length, 2);
assert.strictEqual(accumulateMock.mock.calls[0].arguments[0], configPart1);
assert.strictEqual(accumulateMock.mock.calls[1].arguments[0], configPart2);

assert.strictEqual(storeOutputMock.mock.calls.length, 1);
});

void it('wraps property already exist error', () => {
const accumulator = new CustomOutputsAccumulator(
stubBackendOutputStorageStrategy,
Expand Down Expand Up @@ -77,6 +103,30 @@ void describe('Custom outputs accumulator', () => {
);
});

void it('wraps version mismatch error', () => {
const accumulator = new CustomOutputsAccumulator(
stubBackendOutputStorageStrategy,
objectAccumulator
);

accumulateMock.mock.mockImplementationOnce(() => {
throw new ObjectAccumulatorVersionMismatchError('val0', 'val1');
});

assert.throws(
() =>
accumulator.addOutput({ version: '1', custom: { output1: 'val1' } }),
(error: AmplifyUserError) => {
assert.strictEqual(
error.message,
'Conflicting versions of client configuration found.'
);
assert.ok(error.resolution);
return true;
}
);
});

void it('does not wrap unexpected errors', () => {
const accumulator = new CustomOutputsAccumulator(
stubBackendOutputStorageStrategy,
Expand Down Expand Up @@ -105,7 +155,9 @@ void describe('Custom outputs accumulator', () => {
);

accumulator.addOutput({
aws_user_pools_id: 'some_user_pool_id',
auth: {
user_pool_id: 'some_user_pool_id',
},
custom: {
output1: 'value1',
},
Expand All @@ -116,8 +168,10 @@ void describe('Custom outputs accumulator', () => {
},
});

const expectedAccumulatedOutput: Partial<ClientConfig> = {
aws_user_pools_id: 'some_user_pool_id',
const expectedAccumulatedOutput: DeepPartial<ClientConfig> = {
auth: {
user_pool_id: 'some_user_pool_id',
},
custom: {
output1: 'value1',
output2: 'value2',
Expand Down
20 changes: 18 additions & 2 deletions packages/backend/src/engine/custom_outputs_accumulator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { BackendOutputStorageStrategy } from '@aws-amplify/plugin-types';
import {
BackendOutputStorageStrategy,
DeepPartial,
} from '@aws-amplify/plugin-types';
import { ClientConfig } from '@aws-amplify/client-config';
import {
CustomOutput,
Expand All @@ -9,6 +12,7 @@ import {
AmplifyUserError,
ObjectAccumulator,
ObjectAccumulatorPropertyAlreadyExistsError,
ObjectAccumulatorVersionMismatchError,
} from '@aws-amplify/platform-core';

/**
Expand All @@ -25,7 +29,7 @@ export class CustomOutputsAccumulator {
private readonly clientConfigAccumulator: ObjectAccumulator<ClientConfig>
) {}

addOutput = (clientConfigPart: Partial<ClientConfig>) => {
addOutput = (clientConfigPart: DeepPartial<ClientConfig>) => {
try {
this.clientConfigAccumulator.accumulate(clientConfigPart);
} catch (error) {
Expand All @@ -40,6 +44,18 @@ export class CustomOutputsAccumulator {
error
);
}
if (error instanceof ObjectAccumulatorVersionMismatchError) {
throw new AmplifyUserError(
'VersionMismatchError',
{
message: `Conflicting versions of client configuration found.`,
resolution:
"Ensure that the version specified in 'backend.addOutput' is consistent" +
' and is same as the one used for generating the client config',
},
error
);
}
throw error;
}
this.ensureBackendOutputEntry();
Expand Down
12 changes: 10 additions & 2 deletions packages/cli/src/client-config/client_config_generator_adapter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
ClientConfig,
ClientConfigFormat,
ClientConfigVersion,
generateClientConfig,
generateClientConfigToFile,
} from '@aws-amplify/client-config';
Expand All @@ -22,9 +23,14 @@ export class ClientConfigGeneratorAdapter {
* Generates the client configuration for a given backend
*/
generateClientConfig = async (
backendIdentifier: DeployedBackendIdentifier
backendIdentifier: DeployedBackendIdentifier,
version: ClientConfigVersion
): Promise<ClientConfig> => {
return generateClientConfig(this.awsCredentialProvider, backendIdentifier);
return generateClientConfig(
this.awsCredentialProvider,
backendIdentifier,
version
);
};

/**
Expand All @@ -33,12 +39,14 @@ export class ClientConfigGeneratorAdapter {
*/
generateClientConfigToFile = async (
backendIdentifier: DeployedBackendIdentifier,
version: ClientConfigVersion,
outDir?: string,
format?: ClientConfigFormat
): Promise<void> => {
await generateClientConfigToFile(
this.awsCredentialProvider,
backendIdentifier,
version,
outDir,
format,
(message) => printer.log(message)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import {
ClientConfigFormat,
ClientConfigVersion,
getClientConfigFileName,
getClientConfigPath,
} from '@aws-amplify/client-config';
import { ClientConfigGeneratorAdapter } from './client_config_generator_adapter.js';
Expand All @@ -15,6 +17,7 @@ export class ClientConfigLifecycleHandler {
*/
constructor(
private clientConfigGeneratorAdapter: ClientConfigGeneratorAdapter,
private readonly version: ClientConfigVersion,
private readonly outDir?: string,
private readonly format?: ClientConfigFormat
) {}
Expand All @@ -24,13 +27,15 @@ export class ClientConfigLifecycleHandler {
) => {
await this.clientConfigGeneratorAdapter.generateClientConfigToFile(
backendIdentifier,
this.version,
this.outDir,
this.format
);
};

deleteClientConfigFile = async () => {
const path = await getClientConfigPath(this.outDir, this.format);
const fileName = getClientConfigFileName(this.version);
const path = await getClientConfigPath(fileName, this.outDir, this.format);
await fsp.rm(path);
};
}
Loading

0 comments on commit 5e12247

Please sign in to comment.