packages/aws-cdk-lib/aws-appsync/lib/data-source-common.ts (223 lines of code) (raw):
import { Construct } from 'constructs';
import { IApi } from './api-base';
import { CfnDataSource } from './appsync.generated';
import { ITable } from '../../aws-dynamodb';
import { IEventBus } from '../../aws-events';
import {
IRole,
Role,
ServicePrincipal,
IPrincipal,
IGrantable,
Grant,
} from '../../aws-iam';
import { IFunction } from '../../aws-lambda';
import { IDomain } from '../../aws-opensearchservice';
import { IDatabaseCluster, IServerlessCluster } from '../../aws-rds';
import { ISecret } from '../../aws-secretsmanager';
import { IResolvable, Token, Lazy, Stack } from '../../core';
/**
* Valid data source types for AppSync
*/
export enum AppSyncDataSourceType {
/**
* Lambda data source type
*/
LAMBDA = 'AWS_LAMBDA',
/**
* DynamoDB data source type
*/
DYNAMODB = 'AMAZON_DYNAMODB',
/**
* EventBridge data source type
*/
EVENTBRIDGE = 'AMAZON_EVENTBRIDGE',
/**
* OpenSearch service data source type
*/
OPENSEARCH_SERVICE = 'AMAZON_OPENSEARCH_SERVICE',
/**
* HTTP data source type
*/
HTTP = 'HTTP',
/**
* Relational DB data source type
*/
RELATIONAL_DATABASE = 'RELATIONAL_DATABASE',
/**
* Bedrock runtime data source type
*/
BEDROCK = 'AMAZON_BEDROCK_RUNTIME',
}
/**
* Invoke types for direct Lambda data sources
*/
export enum LambdaInvokeType {
/**
* Invoke function asynchronously
*/
EVENT = 'EVENT',
/**
* Invoke function synchronously
*/
REQUEST_RESPONSE = 'REQUEST_RESPONSE',
}
/**
* Base properties for an AppSync datasource
*/
export interface AppSyncBaseDataSourceProps {
/**
* The API to attach this data source to
*/
readonly api: IApi;
/**
* The name of the data source. The only allowed pattern is: {[_A-Za-z][_0-9A-Za-z]*}.
* Any invalid characters will be automatically removed.
*
* @default - id of data source
*/
readonly name?: string;
/**
* The description of the data source
*
* @default - None
*/
readonly description?: string;
}
/**
* Properties for an AppSync datasource backed by a resource
*/
export interface AppSyncBackedDataSourceProps extends AppSyncBaseDataSourceProps {
/**
* The IAM service role to be assumed by AppSync to interact with the data source
*
* @default - Create a new role
*/
readonly serviceRole?: IRole;
}
/**
* Props used by implementations of BaseDataSource to provide configuration. Should not be used directly.
*/
export interface AppSyncExtendedDataSourceProps {
/**
* The type of the AppSync datasource
*/
readonly type: AppSyncDataSourceType;
/**
* Configuration for DynamoDB Datasource
*
* @default - No config
*/
readonly dynamoDbConfig?: CfnDataSource.DynamoDBConfigProperty | IResolvable;
/**
* Configuration for OpenSearch data source
*
* @default - No config
*/
readonly openSearchServiceConfig?: CfnDataSource.OpenSearchServiceConfigProperty | IResolvable;
/**
* Configuration for HTTP Datasource
*
* @default - No config
*/
readonly httpConfig?: CfnDataSource.HttpConfigProperty | IResolvable;
/**
* Configuration for EventBridge Datasource
*
* @default - No config
*/
readonly eventBridgeConfig?: CfnDataSource.EventBridgeConfigProperty | IResolvable;
/**
* Configuration for Lambda Datasource
*
* @default - No config
*/
readonly lambdaConfig?: CfnDataSource.LambdaConfigProperty | IResolvable;
/**
* Configuration for RDS Datasource
*
* @default - No config
*/
readonly relationalDatabaseConfig?: CfnDataSource.RelationalDatabaseConfigProperty | IResolvable;
}
/**
* Abstract AppSync datasource implementation. Do not use directly but use subclasses for concrete datasources
*/
export abstract class AppSyncBaseDataSource extends Construct {
/**
* The name of the data source
*/
public readonly name: string;
/**
* The underlying CFN data source resource
*/
public readonly resource: CfnDataSource;
protected api: IApi;
protected serviceRole?: IRole;
constructor(scope: Construct, id: string, props: AppSyncBackedDataSourceProps, extended: AppSyncExtendedDataSourceProps) {
super(scope, id);
this.serviceRole = props.serviceRole || new Role(this, 'ServiceRole', { assumedBy: new ServicePrincipal('appsync.amazonaws.com') });
// Replace unsupported characters from DataSource name. The only allowed pattern is: {[_A-Za-z][_0-9A-Za-z]*}
const name = (props.name ?? id);
const supportedName = Token.isUnresolved(name) ? name : name.replace(/[\W]+/g, '');
this.resource = new CfnDataSource(this, 'Resource', {
apiId: props.api.apiId,
name: supportedName,
description: props.description,
serviceRoleArn: this.serviceRole?.roleArn,
...extended,
});
this.name = supportedName;
this.api = props.api;
}
}
/**
* Abstract AppSync datasource implementation. Do not use directly but use subclasses for resource backed datasources
*/
export abstract class AppSyncBackedDataSource extends AppSyncBaseDataSource implements IGrantable {
/**
* The principal of the data source to be IGrantable
*/
public readonly grantPrincipal: IPrincipal;
constructor(scope: Construct, id: string, props: AppSyncBackedDataSourceProps, extended: AppSyncExtendedDataSourceProps) {
super(scope, id, props, extended);
this.grantPrincipal = this.serviceRole!;
}
}
/**
* Properties for an AppSync DynamoDB datasource
*/
export interface AppSyncDynamoDbDataSourceProps extends AppSyncBackedDataSourceProps {
/**
* The DynamoDB table backing this data source
*/
readonly table: ITable;
/**
* Specify whether this Data Source is read only or has read and write permissions to the DynamoDB table
*
* @default false
*/
readonly readOnlyAccess?: boolean;
/**
* Use credentials of caller to access DynamoDB
*
* @default false
*/
readonly useCallerCredentials?: boolean;
}
/**
* An AppSync datasource backed by a DynamoDB table
*/
export class AppSyncDynamoDbDataSource extends AppSyncBackedDataSource {
constructor(scope: Construct, id: string, props: AppSyncDynamoDbDataSourceProps) {
super(scope, id, props, {
type: AppSyncDataSourceType.DYNAMODB,
dynamoDbConfig: {
tableName: props.table.tableName,
awsRegion: props.table.env.region,
useCallerCredentials: props.useCallerCredentials,
},
});
if (props.readOnlyAccess) {
props.table.grantReadData(this);
} else {
props.table.grantReadWriteData(this);
}
}
}
/**
* The authorization config in case the HTTP endpoint requires authorization
*/
export interface AppSyncAwsIamConfig {
/**
* The signing region for AWS IAM authorization
*/
readonly signingRegion: string;
/**
* The signing service name for AWS IAM authorization
*/
readonly signingServiceName: string;
}
/**
* Optional configuration for data sources
*/
export interface AppSyncDataSourceOptions {
/**
* The name of the data source, overrides the id given by CDK
*
* @default - generated by CDK given the id
*/
readonly name?: string;
/**
* The description of the data source
*
* @default - No description
*/
readonly description?: string;
}
/**
* Optional configuration for Http data sources
*/
export interface AppSyncHttpDataSourceOptions extends AppSyncDataSourceOptions {
/**
* The authorization config in case the HTTP endpoint requires authorization
*
* @default - none
*/
readonly authorizationConfig?: AppSyncAwsIamConfig;
}
/**
* Properties for an AppSync http datasource
*/
export interface AppSyncHttpDataSourceProps extends AppSyncBackedDataSourceProps {
/**
* The http endpoint
*/
readonly endpoint: string;
/**
* The authorization config in case the HTTP endpoint requires authorization
*
* @default - none
*/
readonly authorizationConfig?: AppSyncAwsIamConfig;
}
/**
* An AppSync datasource backed by a http endpoint
*/
export class AppSyncHttpDataSource extends AppSyncBackedDataSource {
constructor(scope: Construct, id: string, props: AppSyncHttpDataSourceProps) {
const authorizationConfig = props.authorizationConfig ? {
authorizationType: 'AWS_IAM',
awsIamConfig: props.authorizationConfig,
} : undefined;
super(scope, id, props, {
type: AppSyncDataSourceType.HTTP,
httpConfig: {
endpoint: props.endpoint,
authorizationConfig,
},
});
}
}
/**
* Properties for an AppSync EventBridge datasource
*/
export interface AppSyncEventBridgeDataSourceProps extends AppSyncBackedDataSourceProps {
/**
* The EventBridge EventBus
*/
readonly eventBus: IEventBus;
}
/**
* An AppSync datasource backed by EventBridge
*/
export class AppSyncEventBridgeDataSource extends AppSyncBackedDataSource {
constructor(scope: Construct, id: string, props: AppSyncEventBridgeDataSourceProps) {
super(scope, id, props, {
type: AppSyncDataSourceType.EVENTBRIDGE,
eventBridgeConfig: {
eventBusArn: props.eventBus.eventBusArn,
},
});
props.eventBus.grantPutEventsTo(this);
}
}
/**
* Properties for an AppSync Lambda datasource
*/
export interface AppSyncLambdaDataSourceProps extends AppSyncBackedDataSourceProps {
/**
* The Lambda function to call to interact with this data source
*/
readonly lambdaFunction: IFunction;
}
/**
* An AppSync datasource backed by a Lambda function
*/
export class AppSyncLambdaDataSource extends AppSyncBackedDataSource {
constructor(scope: Construct, id: string, props: AppSyncLambdaDataSourceProps) {
super(scope, id, props, {
type: AppSyncDataSourceType.LAMBDA,
lambdaConfig: {
lambdaFunctionArn: props.lambdaFunction.functionArn,
},
});
props.lambdaFunction.grantInvoke(this);
}
}
/**
* Properties for an AppSync RDS datasource Aurora Serverless V1
*/
export interface AppSyncRdsDataSourceProps extends AppSyncBackedDataSourceProps {
/**
* The serverless cluster to call to interact with this data source
*/
readonly serverlessCluster: IServerlessCluster;
/**
* The secret containing the credentials for the database
*/
readonly secretStore: ISecret;
/**
* The name of the database to use within the cluster
*
* @default - None
*/
readonly databaseName?: string;
}
/**
* Properties for an AppSync RDS datasource Aurora Serverless V2
*/
export interface AppSyncRdsDataSourcePropsV2 extends AppSyncBackedDataSourceProps {
/**
* The serverless cluster to call to interact with this data source
*/
readonly serverlessCluster: IDatabaseCluster;
/**
* The secret containing the credentials for the database
*/
readonly secretStore: ISecret;
/**
* The name of the database to use within the cluster
*
* @default - None
*/
readonly databaseName?: string;
}
/**
* An AppSync datasource backed by RDS
*/
export class AppSyncRdsDataSource extends AppSyncBackedDataSource {
constructor(scope: Construct, id: string, props: AppSyncRdsDataSourceProps)
constructor(scope: Construct, id: string, props: AppSyncRdsDataSourcePropsV2) {
super(scope, id, props, {
type: AppSyncDataSourceType.RELATIONAL_DATABASE,
relationalDatabaseConfig: {
rdsHttpEndpointConfig: {
awsRegion: props.serverlessCluster.env.region,
dbClusterIdentifier: Lazy.string({
produce: () => {
return Stack.of(this).formatArn({
service: 'rds',
resource: `cluster:${props.serverlessCluster.clusterIdentifier}`,
});
},
}),
awsSecretStoreArn: props.secretStore.secretArn,
databaseName: props.databaseName,
},
relationalDatabaseSourceType: 'RDS_HTTP_ENDPOINT',
},
});
const clusterArn = Stack.of(this).formatArn({
service: 'rds',
resource: `cluster:${props.serverlessCluster.clusterIdentifier}`,
});
props.secretStore.grantRead(this);
// Change to grant with RDS grant becomes implemented
props.serverlessCluster.grantDataApiAccess(this);
Grant.addToPrincipal({
grantee: this,
actions: [
'rds-data:DeleteItems',
'rds-data:ExecuteSql',
'rds-data:GetItems',
'rds-data:InsertItems',
'rds-data:UpdateItems',
],
resourceArns: [clusterArn, `${clusterArn}:*`],
scope: this,
});
}
}
/**
* Properties for the OpenSearch Data Source
*/
export interface AppSyncOpenSearchDataSourceProps extends AppSyncBackedDataSourceProps {
/**
* The OpenSearch domain containing the endpoint for the data source
*/
readonly domain: IDomain;
}
/**
* An Appsync datasource backed by OpenSearch
*/
export class AppSyncOpenSearchDataSource extends AppSyncBackedDataSource {
constructor(scope: Construct, id: string, props: AppSyncOpenSearchDataSourceProps) {
super(scope, id, props, {
type: AppSyncDataSourceType.OPENSEARCH_SERVICE,
openSearchServiceConfig: {
awsRegion: props.domain.env.region,
endpoint: `https://${props.domain.domainEndpoint}`,
},
});
props.domain.grantReadWrite(this);
}
}