Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
236 changes: 158 additions & 78 deletions packages/amplify-graphql-api-construct/.jsii

Large diffs are not rendered by default.

11 changes: 10 additions & 1 deletion packages/amplify-graphql-api-construct/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ export interface AmplifyGraphqlApiProps {
readonly definition: IAmplifyGraphqlDefinition;
readonly functionNameMap?: Record<string, IFunction>;
readonly functionSlots?: FunctionSlot[];
readonly importedAmplifyDynamoDBTableMap?: Record<string, string>;
readonly outputStorageStrategy?: IBackendOutputStorageStrategy;
readonly predictionsBucket?: IBucket;
readonly stackMappings?: Record<string, string>;
Expand Down Expand Up @@ -257,14 +258,22 @@ export interface IBackendOutputStorageStrategy {
addBackendOutputEntry(keyName: string, backendOutputEntry: IBackendOutputEntry): void;
}

// @public
export interface ImportedAmplifyDynamoDbModelDataSourceStrategy {
// (undocumented)
readonly dbType: 'DYNAMODB';
// (undocumented)
readonly provisionStrategy: 'IMPORTED_AMPLIFY_TABLE';
}

// @public
export interface LambdaAuthorizationConfig {
readonly function: IFunction;
readonly ttl: Duration;
}

// @public
export type ModelDataSourceStrategy = DefaultDynamoDbModelDataSourceStrategy | AmplifyDynamoDbModelDataSourceStrategy | SQLLambdaModelDataSourceStrategy;
export type ModelDataSourceStrategy = DefaultDynamoDbModelDataSourceStrategy | AmplifyDynamoDbModelDataSourceStrategy | ImportedAmplifyDynamoDbModelDataSourceStrategy | SQLLambdaModelDataSourceStrategy;

// @public
export type ModelDataSourceStrategyDbType = 'DYNAMODB' | ModelDataSourceStrategySqlDbType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import {
} from './internal';
import { getStackForScope, walkAndProcessNodes } from './internal/construct-tree';
import { getDataSourceStrategiesProvider } from './internal/data-source-config';
import { validateImportedTableMap } from './internal/imported-tables';

/**
* L3 Construct which invokes the Amplify Transformer Pattern over an input Graphql Schema.
Expand Down Expand Up @@ -152,8 +153,11 @@ export class AmplifyGraphqlApi extends Construct {
translationBehavior,
functionNameMap,
outputStorageStrategy,
importedAmplifyDynamoDBTableMap,
} = props;

validateImportedTableMap(definition, importedAmplifyDynamoDBTableMap);

const dataSources = getMetadataDataSources(definition);

new AttributionMetadataStorage().storeAttributionMetadata(Stack.of(scope), this.stackType, path.join(__dirname, '..', 'package.json'), {
Expand Down Expand Up @@ -209,7 +213,7 @@ export class AmplifyGraphqlApi extends Construct {
// CDK construct uses a custom resource. We'll define this explicitly here to remind ourselves that this value is unused in the CDK
// construct flow
rdsLayerMapping: undefined,
...getDataSourceStrategiesProvider(definition),
...getDataSourceStrategiesProvider(definition, importedAmplifyDynamoDBTableMap),
};

executeTransform(executeTransformConfig);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,16 @@ export const constructCustomSqlDataSourceStrategies = (
* Extracts the data source provider from the definition. This jumps through some hoops to avoid changing the public interface. If we decide
* to change the public interface to simplify the structure, then this process gets a lot simpler.
*/
export const getDataSourceStrategiesProvider = (definition: IAmplifyGraphqlDefinition): DataSourceStrategiesProvider => {
export const getDataSourceStrategiesProvider = (
definition: IAmplifyGraphqlDefinition,
importedAmplifyDynamoDBTableMap: Record<string, string> = {},
): DataSourceStrategiesProvider => {
const provider: DataSourceStrategiesProvider = {
// We can directly use the interface strategies, even though the SQL strategies have the customSqlStatements field that is unused by the
// transformer flavor of this type
dataSourceStrategies: definition.dataSourceStrategies,
sqlDirectiveDataSourceStrategies: [],
importedAmplifyDynamoDBTableMap,
};

// We'll collect all the custom SQL statements from the definition into a single map, and use that to make our
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { isDynamoDbType } from '@aws-amplify/graphql-transformer-core';
import { ImportedAmplifyDynamoDbModelDataSourceStrategy, ModelDataSourceStrategy } from '../model-datasource-strategy-types';
import { IAmplifyGraphqlDefinition } from '../types';

export const validateImportedTableMap = (definition: IAmplifyGraphqlDefinition, importedTableMap?: Record<string, string>): void => {
const { dataSourceStrategies } = definition;
const importedModels = Object.keys(dataSourceStrategies).filter((modelTypeName) =>
isImportedAmplifyDynamoDbModelDataSourceStrategy(dataSourceStrategies[modelTypeName]),
);
if (importedModels.length > 0) {
if (!importedTableMap) {
throw new Error('Table mapping is missing for imported Amplify DynamoDB table strategy');
}
importedModels.forEach((modelName) => {
if (!importedTableMap[modelName]) {
throw new Error(`Cannot find imported Amplify DynamoDB table mapping for model ${modelName}`);
}
});
}
};

/**
* Type predicate that returns true if `obj` is a AmplifyDynamoDbModelDataSourceStrategy
*/
export const isImportedAmplifyDynamoDbModelDataSourceStrategy = (
strategy: ModelDataSourceStrategy,
): strategy is ImportedAmplifyDynamoDbModelDataSourceStrategy => {
return (
isDynamoDbType(strategy.dbType) &&
typeof (strategy as any)['provisionStrategy'] === 'string' &&
(strategy as any)['provisionStrategy'] === 'IMPORTED_AMPLIFY_TABLE'
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
export type ModelDataSourceStrategy =
| DefaultDynamoDbModelDataSourceStrategy
| AmplifyDynamoDbModelDataSourceStrategy
| ImportedAmplifyDynamoDbModelDataSourceStrategy
| SQLLambdaModelDataSourceStrategy;

/**
Expand Down Expand Up @@ -40,6 +41,14 @@ export interface AmplifyDynamoDbModelDataSourceStrategy {
readonly provisionStrategy: 'AMPLIFY_TABLE';
}

/**
* Use custom resource type 'Custom::ImportedAmplifyDynamoDBTable' to provision table.
*/
export interface ImportedAmplifyDynamoDbModelDataSourceStrategy {
readonly dbType: 'DYNAMODB';
readonly provisionStrategy: 'IMPORTED_AMPLIFY_TABLE';
}

/**
* A strategy that creates a Lambda to connect to a pre-existing SQL table to resolve model data.
*/
Expand Down
5 changes: 5 additions & 0 deletions packages/amplify-graphql-api-construct/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,11 @@ export interface AmplifyGraphqlApiProps {
* Strategy to store construct outputs. If no outputStorageStrategey is provided a default strategy will be used.
*/
readonly outputStorageStrategy?: IBackendOutputStorageStrategy;

/**
* The table map for the imported tables. The key is the model type name defined in schema; the value is the table name of the existing table
*/
readonly importedAmplifyDynamoDBTableMap?: Record<string, string>;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,29 @@ Object {
"dynamodb:UpdateTimeToLive",
],
"Effect": "Allow",
"Resource": Object {
"Fn::Sub": Array [
"arn:aws:dynamodb:\${AWS::Region}:\${AWS::AccountId}:table/*-\${apiId}-\${envName}",
Object {
"apiId": Object {
"Ref": "referencetotransformerrootstackGraphQLAPI20497F53ApiId",
"Resource": Array [
Object {
"Fn::Sub": Array [
"arn:aws:dynamodb:\${AWS::Region}:\${AWS::AccountId}:table/*-\${apiId}-\${envName}",
Object {
"apiId": Object {
"Ref": "referencetotransformerrootstackGraphQLAPI20497F53ApiId",
},
"envName": Object {
"Ref": "referencetotransformerrootstackenv10C5A902Ref",
},
},
"envName": Object {
"Ref": "referencetotransformerrootstackenv10C5A902Ref",
],
},
Object {
"Fn::Sub": Array [
"arn:aws:dynamodb:\${AWS::Region}:\${AWS::AccountId}:table/\${tableName}",
Object {
"tableName": "Author-myApiId-myEnv",
},
},
],
},
],
},
],
},
],
"Version": "2012-10-17",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import * as cdk from 'aws-cdk-lib';
import { AmplifyDynamoDBTable, CUSTOM_DDB_CFN_TYPE } from '../resources/amplify-dynamodb-table/amplify-dynamodb-table-construct';
import {
AmplifyDynamoDBTable,
CUSTOM_DDB_CFN_TYPE,
CUSTOM_IMPORTED_DDB_CFN_TYPE,
} from '../resources/amplify-dynamodb-table/amplify-dynamodb-table-construct';
import { AttributeType, StreamViewType, TableEncryption } from 'aws-cdk-lib/aws-dynamodb';
import { Template } from 'aws-cdk-lib/assertions';

Expand Down Expand Up @@ -147,4 +151,23 @@ describe('Amplify DynamoDB Table Construct Tests', () => {
replaceTableUponGsiUpdate: false,
});
});
it('render the imported amplify dynamodb table in correct form', () => {
const stack = new cdk.Stack();
new AmplifyDynamoDBTable(stack, 'MockTable', {
customResourceServiceToken: 'mockResourceServiceToken',
tableName: 'mockTableName',
partitionKey: {
name: 'id',
type: AttributeType.STRING,
},
isImported: true,
});
const template = Template.fromStack(stack);
// The correct template should be generated with default input
template.hasResourceProperties(CUSTOM_IMPORTED_DDB_CFN_TYPE, {
ServiceToken: 'mockResourceServiceToken',
tableName: 'mockTableName',
isImported: true,
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import {
DDB_AMPLIFY_MANAGED_DATASOURCE_STRATEGY,
DDB_DEFAULT_DATASOURCE_STRATEGY,
validateModelSchema,
IMPORTED_DDB_AMPLIFY_MANAGED_DATASOURCE_STRATEGY,
} from '@aws-amplify/graphql-transformer-core';
import { parse } from 'graphql';
import { ModelTransformer } from '../graphql-model-transformer';
import { CUSTOM_DDB_CFN_TYPE } from '../resources/amplify-dynamodb-table/amplify-dynamodb-table-construct';
import { CUSTOM_DDB_CFN_TYPE, CUSTOM_IMPORTED_DDB_CFN_TYPE } from '../resources/amplify-dynamodb-table/amplify-dynamodb-table-construct';
import { ITERATIVE_TABLE_STACK_NAME } from '../resources/amplify-dynamodb-table/amplify-dynamo-model-resource-generator';

describe('ModelTransformer:', () => {
Expand All @@ -20,13 +21,21 @@ describe('ModelTransformer:', () => {
id: ID!
content: String
}
type Author @model {
id: ID!
name: String
}
`;
const out = testTransform({
schema: validSchema,
transformers: [new ModelTransformer()],
dataSourceStrategies: {
Comment: DDB_DEFAULT_DATASOURCE_STRATEGY,
Post: DDB_AMPLIFY_MANAGED_DATASOURCE_STRATEGY,
Author: IMPORTED_DDB_AMPLIFY_MANAGED_DATASOURCE_STRATEGY,
},
importedAmplifyDynamoDBTableMap: {
Author: 'Author-myApiId-myEnv',
},
});
expect(out).toBeDefined();
Expand All @@ -49,6 +58,33 @@ describe('ModelTransformer:', () => {
const commentTable = commentStack.Resources?.CommentTable;
expect(commentTable).toBeDefined();
expect(commentTable.Type).toBe('AWS::DynamoDB::Table');
// Author table resource should be generated within the imported amplify DynamoDB table
const authorStack = out.stacks['Author'];
expect(authorStack).toBeDefined();
const authorTable = authorStack.Resources?.AuthorTable;
expect(authorTable).toBeDefined();
expect(authorTable.Type).toBe(CUSTOM_IMPORTED_DDB_CFN_TYPE);
expect(authorTable.UpdateReplacePolicy).toBe('Retain');
expect(authorTable.DeletionPolicy).toBe('Retain');
expect(authorTable.Properties.isImported).toBe(true);
expect(authorTable.Properties.tableName).toBe('Author-myApiId-myEnv');
// Validate schema
validateModelSchema(parse(out.schema));
});
it('should throw error when the mapping is not provided for model of imported table strategy', async () => {
const validSchema = `
type Post @model {
id: ID!
title: String!
}
`;
const transformOption = {
schema: validSchema,
transformers: [new ModelTransformer()],
dataSourceStrategies: {
Post: IMPORTED_DDB_AMPLIFY_MANAGED_DATASOURCE_STRATEGY,
},
};
expect(() => testTransform(transformOption)).toThrowErrorMatchingInlineSnapshot(`"Cannot find imported table mapping for model Post"`);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ import { DynamoModelResourceGenerator } from './resources/dynamo-model-resource-
import { RdsModelResourceGenerator } from './resources/rds-model-resource-generator';
import { ModelTransformerOptions } from './types';
import { AmplifyDynamoModelResourceGenerator } from './resources/amplify-dynamodb-table/amplify-dynamo-model-resource-generator';
import { isImportedAmplifyDynamoDbModelDataSourceStrategy } from '@aws-amplify/graphql-transformer-core';

/**
* Nullable
Expand Down Expand Up @@ -121,6 +122,7 @@ export const directiveDefinition = /* GraphQl */ `
// Keys for the resource generator map to reference the generator for various ModelDataSourceStrategies
const ITERATIVE_TABLE_GENERATOR = 'AmplifyDDB';
const SQL_LAMBDA_GENERATOR = 'SQL';
const IMPORTED_AMPLIFY_TABLE_GENERATOR = 'ImportedAmplifyDDB';

/**
* ModelTransformer
Expand All @@ -134,7 +136,7 @@ export class ModelTransformer extends TransformerModelBase implements Transforme

private resourceGeneratorMap: Map<string, ModelResourceGenerator> = new Map<string, ModelResourceGenerator>();

private dataSourceStrategiesProvider: DataSourceStrategiesProvider = { dataSourceStrategies: {} };
private dataSourceStrategiesProvider: DataSourceStrategiesProvider = { dataSourceStrategies: {}, importedAmplifyDynamoDBTableMap: {} };

/**
* A Map to hold the directive configuration
Expand All @@ -146,14 +148,15 @@ export class ModelTransformer extends TransformerModelBase implements Transforme
this.options = this.getOptions(options);
this.resourceGeneratorMap.set(DDB_DB_TYPE, new DynamoModelResourceGenerator());
this.resourceGeneratorMap.set(SQL_LAMBDA_GENERATOR, new RdsModelResourceGenerator());
this.resourceGeneratorMap.set(ITERATIVE_TABLE_GENERATOR, new AmplifyDynamoModelResourceGenerator());
const amplifyTableDynamoModelResourceGenerator = new AmplifyDynamoModelResourceGenerator();
this.resourceGeneratorMap.set(ITERATIVE_TABLE_GENERATOR, amplifyTableDynamoModelResourceGenerator);
}

before = (ctx: TransformerBeforeStepContextProvider): void => {
// We only store this the model transformer because some of the required override methods need to pass through to the Resource
// generators, but do not have access to the context
const { dataSourceStrategies, sqlDirectiveDataSourceStrategies } = ctx;
this.dataSourceStrategiesProvider = { dataSourceStrategies, sqlDirectiveDataSourceStrategies };
const { dataSourceStrategies, sqlDirectiveDataSourceStrategies, importedAmplifyDynamoDBTableMap } = ctx;
this.dataSourceStrategiesProvider = { dataSourceStrategies, sqlDirectiveDataSourceStrategies, importedAmplifyDynamoDBTableMap };

const strategies = Object.values(dataSourceStrategies);
const customSqlDataSources = sqlDirectiveDataSourceStrategies?.map((dss) => dss.strategy) ?? [];
Expand All @@ -165,7 +168,7 @@ export class ModelTransformer extends TransformerModelBase implements Transforme
this.resourceGeneratorMap.get(SQL_LAMBDA_GENERATOR)?.enableGenerator();
this.resourceGeneratorMap.get(SQL_LAMBDA_GENERATOR)?.enableUnprovisioned();
}
if (strategies.some(isAmplifyDynamoDbModelDataSourceStrategy)) {
if (strategies.some(isAmplifyDynamoDbModelDataSourceStrategy) || strategies.some(isImportedAmplifyDynamoDbModelDataSourceStrategy)) {
this.resourceGeneratorMap.get(ITERATIVE_TABLE_GENERATOR)?.enableGenerator();
this.resourceGeneratorMap.get(ITERATIVE_TABLE_GENERATOR)?.enableProvisioned();
}
Expand Down Expand Up @@ -888,6 +891,12 @@ export class ModelTransformer extends TransformerModelBase implements Transforme
generator = this.resourceGeneratorMap.get(ITERATIVE_TABLE_GENERATOR);
} else if (isSqlStrategy(strategy)) {
generator = this.resourceGeneratorMap.get(SQL_LAMBDA_GENERATOR);
} else if (isImportedAmplifyDynamoDbModelDataSourceStrategy(strategy)) {
if (ctx.importedAmplifyDynamoDBTableMap && ctx.importedAmplifyDynamoDBTableMap[typeName]) {
generator = this.resourceGeneratorMap.get(ITERATIVE_TABLE_GENERATOR);
} else {
throw new Error(`Cannot find imported table mapping for model ${typeName}`);
}
}

if (!generator) {
Expand Down
Loading