diff --git a/doc/dataSources.md b/doc/dataSources.md index 7efff2e4..b02e119d 100644 --- a/doc/dataSources.md +++ b/doc/dataSources.md @@ -139,6 +139,34 @@ appSync: - `eventBusArn`: The ARN of the event bus +## Bedrock + +```yaml +appSync: + dataSources: + myBedrock: + type: 'AMAZON_BEDROCK_RUNTIME' + config: + models: + - amazon.titan-text-lite-v1 + - eu.amazon.nova-2-lite-v1:0 + - arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.anthropic.claude-3-5-haiku-20241022-v1:0 +``` + +### config + +All fields are optional. When `config` is omitted entirely, the plugin still creates the data source and generates a default service role. + +- `models`: Optional list of model identifiers used to scope the generated IAM policy. When omitted, the default role allows `bedrock:InvokeModel` and `bedrock:Converse` on `*`. Each entry is expanded as follows: + - A **bare foundation model ID** (e.g. `amazon.titan-text-lite-v1`) becomes `arn:${AWS::Partition}:bedrock:${region}::foundation-model/`. + - A **cross-region inference profile ID** (prefixed with a geographic code such as `us.`, `eu.`, `apac.`, or `us-gov.` — e.g. `eu.amazon.nova-2-lite-v1:0`) is expanded into **two** ARNs: the inference profile in this account/region (`arn:${AWS::Partition}:bedrock:${region}:${AWS::AccountId}:inference-profile/`) and the underlying foundation model across all regions the profile can route to (`arn:${AWS::Partition}:bedrock:*::foundation-model/`). Models like Amazon Nova are only invokable through an inference profile, so use the profile ID here (and as the `modelId` in your resolver). + - A **full ARN** (or CloudFormation intrinsic function) is used as-is. +- `region`: AWS region used when expanding bare model IDs. Defaults to the stack region. +- `serviceRoleArn`: The service role ARN for this DataSource. If not provided, a new one will be created. +- `iamRoleStatements`: Statements to use for the generated IAM Role. If not provided, default statements will be used. + +Resolvers invoke Bedrock through the `APPSYNC_JS` runtime using `InvokeModel` or `Converse` request objects. AppSync only supports synchronous invocations that complete within 10 seconds; streaming APIs are not supported. See the [AWS AppSync Bedrock resolver reference](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-reference-bedrock-js.html) for request/response shapes and helper utilities. + ## NONE ```yaml diff --git a/e2e/datasource-bedrock.e2e.test.ts b/e2e/datasource-bedrock.e2e.test.ts new file mode 100644 index 00000000..b39b3868 --- /dev/null +++ b/e2e/datasource-bedrock.e2e.test.ts @@ -0,0 +1,41 @@ +import { synthesize } from './helpers/synthesize'; +import { + expectDataSourceOfType, + findResourcesByType, +} from './helpers/assertions'; + +describe('examples/datasource-bedrock', () => { + let result: ReturnType; + + beforeAll(() => { + result = synthesize('examples/datasource-bedrock'); + }); + + afterAll(() => { + result.cleanup(); + }); + + it('creates an AMAZON_BEDROCK_RUNTIME data source', () => { + const ds = expectDataSourceOfType( + result.template, + 'AMAZON_BEDROCK_RUNTIME', + ); + expect(ds.resource.Properties?.Name).toBe('bedrock'); + }); + + it('generates a service role with bedrock:InvokeModel permissions', () => { + const roles = findResourcesByType(result.template, 'AWS::IAM::Role'); + const bedrockRole = roles.find(({ resource }) => { + const policies = resource.Properties?.Policies as Array<{ + PolicyDocument?: { Statement?: Array<{ Action?: string[] }> }; + }>; + return policies?.some((policy) => + policy.PolicyDocument?.Statement?.some((statement) => + statement.Action?.includes('bedrock:InvokeModel'), + ), + ); + }); + + expect(bedrockRole).toBeDefined(); + }); +}); diff --git a/e2e/helpers/assertions.ts b/e2e/helpers/assertions.ts index 8647abdb..330a6df2 100644 --- a/e2e/helpers/assertions.ts +++ b/e2e/helpers/assertions.ts @@ -126,6 +126,7 @@ export function expectDataSourceOfType( | 'HTTP' | 'RELATIONAL_DATABASE' | 'AMAZON_EVENTBRIDGE' + | 'AMAZON_BEDROCK_RUNTIME' | 'NONE', ): { logicalId: string; resource: CfnResource } { return expectResourceWithProperties(template, 'AWS::AppSync::DataSource', { diff --git a/examples/README.md b/examples/README.md index 70dbb25d..93ee9694 100644 --- a/examples/README.md +++ b/examples/README.md @@ -28,6 +28,7 @@ stay current with the plugin's actual behavior — if they break, CI fails. | [datasource-http](./datasource-http/) | HTTP data source with optional IAM signing | | [datasource-none](./datasource-none/) | NONE data source (local resolvers) | | [datasource-eventbridge](./datasource-eventbridge/) | EventBridge data source | +| [datasource-bedrock](./datasource-bedrock/) | Amazon Bedrock runtime data source for synchronous model invocations | | [datasource-opensearch](./datasource-opensearch/) | Amazon OpenSearch Service data source | | [datasource-rds](./datasource-rds/) | Relational Database (Aurora Serverless) data source | | [caching](./caching/) | Server-side caching configuration | diff --git a/examples/datasource-bedrock/resolvers/summarize.js b/examples/datasource-bedrock/resolvers/summarize.js new file mode 100644 index 00000000..1db46bab --- /dev/null +++ b/examples/datasource-bedrock/resolvers/summarize.js @@ -0,0 +1,19 @@ +import { invokeModel } from '@aws-appsync/utils/ai'; + +export function request(ctx) { + return invokeModel({ + modelId: 'eu.amazon.nova-micro-v1:0', + body: { + messages: [ + { + role: 'user', + content: [{ text: ctx.args.text }], + }, + ], + }, + }); +} + +export function response(ctx) { + return ctx.result.output.message.content[0].text; +} diff --git a/examples/datasource-bedrock/schema.graphql b/examples/datasource-bedrock/schema.graphql new file mode 100644 index 00000000..83b3a0f8 --- /dev/null +++ b/examples/datasource-bedrock/schema.graphql @@ -0,0 +1,3 @@ +type Query { + summarize(text: String!): AWSJSON +} diff --git a/examples/datasource-bedrock/serverless.yml b/examples/datasource-bedrock/serverless.yml new file mode 100644 index 00000000..15c928ba --- /dev/null +++ b/examples/datasource-bedrock/serverless.yml @@ -0,0 +1,29 @@ +service: appsync-datasource-bedrock + +provider: + name: aws + runtime: nodejs22.x + +plugins: + - serverless-appsync-plugin + +appSync: + name: datasource-bedrock + authentication: + type: API_KEY + apiKeys: + - name: default + + resolvers: + Query.summarize: + kind: UNIT + dataSource: bedrock + code: ./resolvers/summarize.js + + dataSources: + bedrock: + type: AMAZON_BEDROCK_RUNTIME + description: Amazon Bedrock runtime for synchronous model invocations + config: + models: + - amazon.nova-micro-v1:0 diff --git a/src/__tests__/__snapshots__/dataSources.test.ts.snap b/src/__tests__/__snapshots__/dataSources.test.ts.snap index 70785c69..82530aa8 100644 --- a/src/__tests__/__snapshots__/dataSources.test.ts.snap +++ b/src/__tests__/__snapshots__/dataSources.test.ts.snap @@ -239,6 +239,279 @@ exports[`DataSource AWS Lambda should generate default role with custom statemen } `; +exports[`DataSource Bedrock should expand a cross-region inference profile id into the profile and foundation-model ARNs 1`] = ` +{ + "GraphQlDsbedrockRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole", + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "appsync.amazonaws.com", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "bedrock:InvokeModel", + "bedrock:Converse", + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + ":", + [ + "arn", + { + "Ref": "AWS::Partition", + }, + "bedrock", + { + "Ref": "AWS::Region", + }, + { + "Ref": "AWS::AccountId", + }, + "inference-profile/eu.amazon.nova-2-lite-v1:0", + ], + ], + }, + { + "Fn::Join": [ + ":", + [ + "arn", + { + "Ref": "AWS::Partition", + }, + "bedrock", + "*", + "", + "foundation-model/amazon.nova-2-lite-v1:0", + ], + ], + }, + ], + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "AppSync-Datasource-bedrock", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, +} +`; + +exports[`DataSource Bedrock should generate Resource with default role 1`] = ` +{ + "GraphQlDsbedrock": { + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "GraphQlApi", + "ApiId", + ], + }, + "Description": "My Bedrock data source", + "Name": "bedrock", + "ServiceRoleArn": { + "Fn::GetAtt": [ + "GraphQlDsbedrockRole", + "Arn", + ], + }, + "Type": "AMAZON_BEDROCK_RUNTIME", + }, + "Type": "AWS::AppSync::DataSource", + }, + "GraphQlDsbedrockRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole", + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "appsync.amazonaws.com", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "bedrock:InvokeModel", + "bedrock:Converse", + ], + "Effect": "Allow", + "Resource": [ + "*", + ], + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "AppSync-Datasource-bedrock", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, +} +`; + +exports[`DataSource Bedrock should generate default role with custom statement 1`] = ` +{ + "GraphQlDsbedrockRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole", + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "appsync.amazonaws.com", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "bedrock:InvokeModel", + ], + "Effect": "Allow", + "Resource": [ + "*", + ], + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "AppSync-Datasource-bedrock", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, +} +`; + +exports[`DataSource Bedrock should generate default role with scoped models 1`] = ` +{ + "GraphQlDsbedrock": { + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "GraphQlApi", + "ApiId", + ], + }, + "Description": "My Bedrock data source", + "Name": "bedrock", + "ServiceRoleArn": { + "Fn::GetAtt": [ + "GraphQlDsbedrockRole", + "Arn", + ], + }, + "Type": "AMAZON_BEDROCK_RUNTIME", + }, + "Type": "AWS::AppSync::DataSource", + }, + "GraphQlDsbedrockRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole", + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "appsync.amazonaws.com", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "bedrock:InvokeModel", + "bedrock:Converse", + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + ":", + [ + "arn", + { + "Ref": "AWS::Partition", + }, + "bedrock", + { + "Ref": "AWS::Region", + }, + "", + "foundation-model/amazon.titan-text-lite-v1", + ], + ], + }, + "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.anthropic.claude-3-5-haiku-20241022-v1:0", + ], + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "AppSync-Datasource-bedrock", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, +} +`; + exports[`DataSource DynamoDB should generate Resource with default deltaSync 1`] = ` { "GraphQlDsdynamo": { diff --git a/src/__tests__/dataSources.test.ts b/src/__tests__/dataSources.test.ts index 9fbdacb1..b9eabbbf 100644 --- a/src/__tests__/dataSources.test.ts +++ b/src/__tests__/dataSources.test.ts @@ -196,6 +196,84 @@ describe('DataSource', () => { }); }); + describe('Bedrock', () => { + it('should generate Resource with default role', () => { + const api = new Api(given.appSyncConfig(), plugin); + const dataSource = new DataSource(api, { + type: 'AMAZON_BEDROCK_RUNTIME', + name: 'bedrock', + description: 'My Bedrock data source', + }); + + expect(dataSource.compile()).toMatchSnapshot(); + }); + + it('should generate default role with scoped models', () => { + const api = new Api(given.appSyncConfig(), plugin); + const dataSource = new DataSource(api, { + type: 'AMAZON_BEDROCK_RUNTIME', + name: 'bedrock', + description: 'My Bedrock data source', + config: { + models: [ + 'amazon.titan-text-lite-v1', + 'arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.anthropic.claude-3-5-haiku-20241022-v1:0', + ], + }, + }); + + expect(dataSource.compile()).toMatchSnapshot(); + }); + + it('should expand a cross-region inference profile id into the profile and foundation-model ARNs', () => { + const api = new Api(given.appSyncConfig(), plugin); + const dataSource = new DataSource(api, { + type: 'AMAZON_BEDROCK_RUNTIME', + name: 'bedrock', + description: 'My Bedrock data source', + config: { + models: ['eu.amazon.nova-2-lite-v1:0'], + }, + }); + + expect(dataSource.compileDataSourceIamRole()).toMatchSnapshot(); + }); + + it('should generate default role with custom statement', () => { + const api = new Api(given.appSyncConfig(), plugin); + const dataSource = new DataSource(api, { + type: 'AMAZON_BEDROCK_RUNTIME', + name: 'bedrock', + description: 'My Bedrock data source', + config: { + iamRoleStatements: [ + { + Effect: 'Allow', + Action: ['bedrock:InvokeModel'], + Resource: ['*'], + }, + ], + }, + }); + + expect(dataSource.compileDataSourceIamRole()).toMatchSnapshot(); + }); + + it('should not generate default role when a service role arn is passed', () => { + const api = new Api(given.appSyncConfig(), plugin); + const dataSource = new DataSource(api, { + type: 'AMAZON_BEDROCK_RUNTIME', + name: 'bedrock', + description: 'My Bedrock data source', + config: { + serviceRoleArn: 'arn:aws:iam:', + }, + }); + + expect(dataSource.compileDataSourceIamRole()).toBeUndefined(); + }); + }); + describe('AWS Lambda', () => { it('should generate Resource with default role', () => { const api = new Api(given.appSyncConfig(), plugin); diff --git a/src/__tests__/validation/__snapshots__/datasources.test.ts.snap b/src/__tests__/validation/__snapshots__/datasources.test.ts.snap index a59e51a2..0d1ec03f 100644 --- a/src/__tests__/validation/__snapshots__/datasources.test.ts.snap +++ b/src/__tests__/validation/__snapshots__/datasources.test.ts.snap @@ -1,7 +1,12 @@ // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[`Basic Invalid should validate: Invalid Datasource 1`] = ` -"/dataSources/myDynamoSource1/type: must be one of AMAZON_DYNAMODB, AMAZON_OPENSEARCH_SERVICE, AWS_LAMBDA, HTTP, NONE, RELATIONAL_DATABASE, AMAZON_EVENTBRIDGE +"/dataSources/myDynamoSource1/type: must be one of AMAZON_DYNAMODB, AMAZON_OPENSEARCH_SERVICE, AWS_LAMBDA, HTTP, NONE, RELATIONAL_DATABASE, AMAZON_EVENTBRIDGE, AMAZON_BEDROCK_RUNTIME +/dataSources: contains invalid data source definitions" +`; + +exports[`Bedrock Invalid should not validate: Invalid models type 1`] = ` +"/dataSources/myBedrockSource1/config/models: must be array /dataSources: contains invalid data source definitions" `; diff --git a/src/__tests__/validation/datasources.test.ts b/src/__tests__/validation/datasources.test.ts index c582c4de..05456787 100644 --- a/src/__tests__/validation/datasources.test.ts +++ b/src/__tests__/validation/datasources.test.ts @@ -307,6 +307,89 @@ describe('EventBridge', () => { }); }); +describe('Bedrock', () => { + describe('Valid', () => { + const assertions = [ + { + name: 'Valid config with type only', + config: { + dataSources: { + myBedrockSource1: { + type: 'AMAZON_BEDROCK_RUNTIME', + }, + }, + }, + }, + { + name: 'Valid config with models', + config: { + dataSources: { + myBedrockSource1: { + type: 'AMAZON_BEDROCK_RUNTIME', + config: { + models: ['amazon.titan-text-lite-v1'], + }, + }, + }, + }, + }, + { + name: 'Valid config, as array of maps', + config: { + dataSources: [ + { + myBedrockSource1: { + type: 'AMAZON_BEDROCK_RUNTIME', + config: { + models: [ + 'amazon.titan-text-lite-v1', + 'arn:aws:bedrock:us-east-1::foundation-model/amazon.titan-text-lite-v1', + ], + }, + }, + }, + ], + }, + }, + ]; + + assertions.forEach((config) => { + it(`should validate a ${config.name}`, () => { + expect(validateConfig({ ...basicConfig, ...config.config })).toBe(true); + }); + }); + }); + + describe('Invalid', () => { + const assertions = [ + { + name: 'Invalid models type', + config: { + dataSources: { + myBedrockSource1: { + type: 'AMAZON_BEDROCK_RUNTIME', + config: { + models: 'amazon.titan-text-lite-v1', + }, + }, + }, + }, + }, + ]; + + assertions.forEach((config) => { + it(`should not validate: ${config.name}`, () => { + expect(function () { + validateConfig({ + ...basicConfig, + ...config.config, + }); + }).toThrowErrorMatchingSnapshot(); + }); + }); + }); +}); + describe('Lambda', () => { describe('Valid', () => { const assertions = [ diff --git a/src/index.ts b/src/index.ts index 51aea2ea..2a7d206b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1257,7 +1257,7 @@ class ServerlessAppsyncPlugin { entryPoints: [codePath], bundle: true, write: false, - external: ['@aws-appsync/utils'], + external: ['@aws-appsync/utils', '@aws-appsync/utils/ai'], }); if (buildResult.errors.length > 0) { diff --git a/src/resources/DataSource.ts b/src/resources/DataSource.ts index 0dfc4ea7..c9de9197 100644 --- a/src/resources/DataSource.ts +++ b/src/resources/DataSource.ts @@ -60,7 +60,7 @@ export class DataSource { [logicalId]: resource, }; - if ('config' in this.config && this.config.config.serviceRoleArn) { + if ('config' in this.config && this.config.config?.serviceRoleArn) { resource.Properties.ServiceRoleArn = this.config.config.serviceRoleArn; } else { const role = this.compileDataSourceIamRole(); @@ -191,7 +191,7 @@ export class DataSource { } compileDataSourceIamRole(): CfnResources | undefined { - if ('config' in this.config && this.config.config.serviceRoleArn) { + if ('config' in this.config && this.config.config?.serviceRoleArn) { return; } @@ -209,7 +209,7 @@ export class DataSource { ); } - if ('config' in this.config && this.config.config.iamRoleStatements) { + if ('config' in this.config && this.config.config?.iamRoleStatements) { statements = this.config.config.iamRoleStatements; } else { // Try to generate default statements for the given this.config. @@ -427,6 +427,92 @@ export class DataSource { return [defaultEventBridgeStatement]; } + case 'AMAZON_BEDROCK_RUNTIME': { + const region = this.config.config?.region || { Ref: 'AWS::Region' }; + const models = this.config.config?.models; + const resources: (string | IntrinsicFunction)[] = + models && models.length + ? models.flatMap((model) => + this.getBedrockModelResources(model, region), + ) + : ['*']; + + const defaultBedrockStatement: IamStatement = { + Action: ['bedrock:InvokeModel', 'bedrock:Converse'], + Effect: 'Allow', + Resource: resources, + }; + + return [defaultBedrockStatement]; + } + } + } + + getBedrockModelResources( + model: string | IntrinsicFunction, + region: string | IntrinsicFunction, + ): (string | IntrinsicFunction)[] { + // Full ARNs (and intrinsic functions) are used as-is. + if (typeof model !== 'string' || model.startsWith('arn:')) { + return [model]; + } + + // Cross-region inference profile IDs are prefixed with a geographic + // region code (e.g. `us.`, `eu.`, `apac.`). Such models can only be + // invoked through their inference profile, which routes to the underlying + // foundation model in any of the profile's regions. We therefore grant + // access to both the inference profile (in this account/region) and the + // foundation model across all regions. + const inferenceProfilePrefix = /^(us-gov|us|eu|apac)\./; + const match = inferenceProfilePrefix.exec(model); + if (match) { + const baseModelId = model.slice(match[0].length); + + return [ + { + 'Fn::Join': [ + ':', + [ + 'arn', + { Ref: 'AWS::Partition' }, + 'bedrock', + region, + { Ref: 'AWS::AccountId' }, + `inference-profile/${model}`, + ], + ], + }, + { + 'Fn::Join': [ + ':', + [ + 'arn', + { Ref: 'AWS::Partition' }, + 'bedrock', + '*', + '', + `foundation-model/${baseModelId}`, + ], + ], + }, + ]; } + + // Bare foundation model ID. + return [ + { + 'Fn::Join': [ + ':', + [ + 'arn', + { Ref: 'AWS::Partition' }, + 'bedrock', + region, + '', + `foundation-model/${model}`, + ], + ], + }, + ]; } } diff --git a/src/resources/JsResolver.ts b/src/resources/JsResolver.ts index 0cc78480..270c822f 100644 --- a/src/resources/JsResolver.ts +++ b/src/resources/JsResolver.ts @@ -44,7 +44,7 @@ export class JsResolver { entryPoints: [this.config.path], bundle: true, write: false, - external: ['@aws-appsync/utils'], + external: ['@aws-appsync/utils', '@aws-appsync/utils/ai'], }); if (buildResult.errors.length > 0) { diff --git a/src/types/cloudFormation.ts b/src/types/cloudFormation.ts index ee2c66c8..fceb45bb 100644 --- a/src/types/cloudFormation.ts +++ b/src/types/cloudFormation.ts @@ -49,7 +49,8 @@ export type CfnDataSource = { | 'NONE' | 'HTTP' | 'RELATIONAL_DATABASE' - | 'AMAZON_EVENTBRIDGE'; + | 'AMAZON_EVENTBRIDGE' + | 'AMAZON_BEDROCK_RUNTIME'; ServiceRoleArn?: string | IntrinsicFunction; LambdaConfig?: { LambdaFunctionArn: string | IntrinsicFunction; diff --git a/src/types/common.ts b/src/types/common.ts index d173d622..e3283aa5 100644 --- a/src/types/common.ts +++ b/src/types/common.ts @@ -169,6 +169,16 @@ export type DsEventBridgeConfig = { }; }; +export type DsBedrockConfig = { + type: 'AMAZON_BEDROCK_RUNTIME'; + config?: { + serviceRoleArn?: string | IntrinsicFunction; + iamRoleStatements?: IamStatement[]; + models?: (string | IntrinsicFunction)[]; + region?: string | IntrinsicFunction; + }; +}; + export type DsRelationalDbConfig = { type: 'RELATIONAL_DATABASE'; config: { diff --git a/src/types/plugin.ts b/src/types/plugin.ts index 385385c2..f5d2c083 100644 --- a/src/types/plugin.ts +++ b/src/types/plugin.ts @@ -14,6 +14,7 @@ import { DsOpenSearchConfig, DsLambdaConfig, DsEventBridgeConfig, + DsBedrockConfig, DsNone, Substitutions, EnvironmentVariables, @@ -86,6 +87,7 @@ export type DataSourceConfig = { | DsOpenSearchConfig | DsLambdaConfig | DsEventBridgeConfig + | DsBedrockConfig | DsNone ); diff --git a/src/validation.ts b/src/validation.ts index c0028bb2..d4d556de 100644 --- a/src/validation.ts +++ b/src/validation.ts @@ -20,6 +20,7 @@ const DATASOURCE_TYPES = [ 'NONE', 'RELATIONAL_DATABASE', 'AMAZON_EVENTBRIDGE', + 'AMAZON_BEDROCK_RUNTIME', ] as const; export const appSyncSchema = { @@ -506,6 +507,18 @@ export const appSyncSchema = { }, required: ['config'], }, + else: { + if: { + properties: { type: { const: 'AMAZON_BEDROCK_RUNTIME' } }, + }, + then: { + properties: { + config: { + $ref: '#/definitions/datasourceBedrockConfig', + }, + }, + }, + }, }, }, }, @@ -655,6 +668,19 @@ export const appSyncSchema = { }, required: ['eventBusArn'], }, + datasourceBedrockConfig: { + type: 'object', + properties: { + serviceRoleArn: { $ref: '#/definitions/stringOrIntrinsicFunction' }, + iamRoleStatements: { $ref: '#/definitions/iamRoleStatements' }, + region: { $ref: '#/definitions/stringOrIntrinsicFunction' }, + models: { + type: 'array', + items: { $ref: '#/definitions/stringOrIntrinsicFunction' }, + }, + }, + required: [], + }, }, properties: { name: { type: 'string' },