diff --git a/.changeset/rare-kiwis-live.md b/.changeset/rare-kiwis-live.md new file mode 100644 index 00000000000..368c420e788 --- /dev/null +++ b/.changeset/rare-kiwis-live.md @@ -0,0 +1,7 @@ +--- +'@aws-amplify/backend': patch +'@aws-amplify/backend-data': patch +'@aws-amplify/model-generator': patch +--- + +feat: Add mis build to S3 from the data construct factory diff --git a/package-lock.json b/package-lock.json index 325af864f32..d3d479ddd41 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31931,6 +31931,7 @@ "@aws-amplify/backend-output-storage": "^1.1.3", "@aws-amplify/data-construct": "^1.10.1", "@aws-amplify/data-schema-types": "^1.2.0", + "@aws-amplify/graphql-generator": "^0.5.0", "@aws-amplify/plugin-types": "^1.4.0" }, "devDependencies": { diff --git a/packages/backend-data/package.json b/packages/backend-data/package.json index 5e0384b04ec..67e1ee3f311 100644 --- a/packages/backend-data/package.json +++ b/packages/backend-data/package.json @@ -31,7 +31,8 @@ "@aws-amplify/backend-output-storage": "^1.1.3", "@aws-amplify/backend-output-schemas": "^1.4.0", "@aws-amplify/data-construct": "^1.10.1", - "@aws-amplify/plugin-types": "^1.4.0", - "@aws-amplify/data-schema-types": "^1.2.0" + "@aws-amplify/data-schema-types": "^1.2.0", + "@aws-amplify/graphql-generator": "^0.5.1", + "@aws-amplify/plugin-types": "^1.4.0" } } diff --git a/packages/backend-data/src/app_sync_policy_generator.ts b/packages/backend-data/src/app_sync_policy_generator.ts index 9962d1922ea..5db2eaea3d7 100644 --- a/packages/backend-data/src/app_sync_policy_generator.ts +++ b/packages/backend-data/src/app_sync_policy_generator.ts @@ -14,7 +14,10 @@ export class AppSyncPolicyGenerator { /** * Initialize with the GraphqlAPI that the policies will be scoped to */ - constructor(private readonly graphqlApi: IGraphqlApi) { + constructor( + private readonly graphqlApi: IGraphqlApi, + private readonly modelIntrospectionSchemaArn?: string + ) { this.stack = Stack.of(graphqlApi); } /** @@ -29,13 +32,25 @@ export class AppSyncPolicyGenerator { .map((action) => actionToTypeMap[action]) // convert Type to resourceName .map((type) => [this.graphqlApi.arn, 'types', type, '*'].join('/')); - return new Policy(this.stack, `${this.policyPrefix}${this.policyCount++}`, { - statements: [ + + const statements = [ + new PolicyStatement({ + actions: ['appsync:GraphQL'], + resources, + }), + ]; + + if (this.modelIntrospectionSchemaArn) { + statements.push( new PolicyStatement({ - actions: ['appsync:GraphQL'], - resources, - }), - ], + actions: ['s3:GetObject'], + resources: [this.modelIntrospectionSchemaArn], + }) + ); + } + + return new Policy(this.stack, `${this.policyPrefix}${this.policyCount++}`, { + statements, }); } } diff --git a/packages/backend-data/src/factory.test.ts b/packages/backend-data/src/factory.test.ts index 51e2aabf458..91e1fdeff6c 100644 --- a/packages/backend-data/src/factory.test.ts +++ b/packages/backend-data/src/factory.test.ts @@ -85,7 +85,6 @@ const createConstructContainerWithUserPoolAuthRegistered = ( authenticatedUserIamRole: new Role(stack, 'testAuthRole', { assumedBy: new ServicePrincipal('test.amazon.com'), }), - identityPoolId: 'identityPoolId', cfnResources: { cfnUserPool: new CfnUserPool(stack, 'CfnUserPool', {}), cfnUserPoolClient: new CfnUserPoolClient(stack, 'CfnUserPoolClient', { @@ -101,6 +100,7 @@ const createConstructContainerWithUserPoolAuthRegistered = ( ), }, groups: {}, + identityPoolId: 'identityPool', }, }), }); @@ -567,6 +567,23 @@ void describe('DataFactory', () => { }, ], }, + { + Action: 's3:GetObject', + Resource: { + 'Fn::Join': [ + '', + [ + { + 'Fn::GetAtt': [ + 'modelIntrospectionSchemaBucketF566B665', + 'Arn', + ], + }, + '/modelIntrospectionSchema.json', + ], + ], + }, + }, ], }, Roles: [ @@ -675,6 +692,23 @@ void describe('DataFactory', () => { ], }, }, + { + Action: 's3:GetObject', + Resource: { + 'Fn::Join': [ + '', + [ + { + 'Fn::GetAtt': [ + 'modelIntrospectionSchemaBucketF566B665', + 'Arn', + ], + }, + '/modelIntrospectionSchema.json', + ], + ], + }, + }, ], }, Roles: [ @@ -701,6 +735,23 @@ void describe('DataFactory', () => { ], }, }, + { + Action: 's3:GetObject', + Resource: { + 'Fn::Join': [ + '', + [ + { + 'Fn::GetAtt': [ + 'modelIntrospectionSchemaBucketF566B665', + 'Arn', + ], + }, + '/modelIntrospectionSchema.json', + ], + ], + }, + }, ], }, Roles: [ diff --git a/packages/backend-data/src/factory.ts b/packages/backend-data/src/factory.ts index 578d0cbcbc7..d81394f31f1 100644 --- a/packages/backend-data/src/factory.ts +++ b/packages/backend-data/src/factory.ts @@ -7,7 +7,6 @@ import { ConstructFactory, ConstructFactoryGetInstanceProps, GenerateContainerEntryProps, - ReferenceAuthResources, ResourceProvider, } from '@aws-amplify/plugin-types'; import { @@ -17,6 +16,7 @@ import { TranslationBehavior, } from '@aws-amplify/data-construct'; import { GraphqlOutput } from '@aws-amplify/backend-output-schemas'; +import { generateModelsSync } from '@aws-amplify/graphql-generator'; import * as path from 'path'; import { AmplifyDataError, DataProps } from './types.js'; import { @@ -46,6 +46,10 @@ import { FunctionSchemaAccess, JsResolver, } from '@aws-amplify/data-schema-types'; +import { Bucket } from 'aws-cdk-lib/aws-s3'; +import { BucketDeployment, Source } from 'aws-cdk-lib/aws-s3-deployment'; + +const modelIntrospectionSchemaKey = 'modelIntrospectionSchema.json'; /** * Singleton factory for AmplifyGraphqlApi constructs that can be used in Amplify project files. @@ -98,9 +102,9 @@ export class DataFactory implements ConstructFactory { this.props, buildConstructFactoryProvidedAuthConfig( props.constructContainer - .getConstructFactory< - ResourceProvider - >('AuthResources') + .getConstructFactory>( + 'AuthResources' + ) ?.getInstance(props) ), props, @@ -232,14 +236,21 @@ class DataGenerator implements ConstructContainerEntryGenerator { ...schemasLambdaFunctions, }); let amplifyApi = undefined; + let modelIntrospectionSchema: string | undefined = undefined; const isSandboxDeployment = scope.node.tryGetContext(CDKContextKey.DEPLOYMENT_TYPE) === 'sandbox'; try { + const combinedSchema = combineCDKSchemas(amplifyGraphqlDefinitions); + modelIntrospectionSchema = generateModelsSync({ + schema: combinedSchema.schema, + target: 'introspection', + })['model-introspection.json']; + amplifyApi = new AmplifyData(scope, this.name, { apiName: this.name, - definition: combineCDKSchemas(amplifyGraphqlDefinitions), + definition: combinedSchema, authorizationModes, outputStorageStrategy: this.outputStorageStrategy, functionNameMap, @@ -264,6 +275,20 @@ class DataGenerator implements ConstructContainerEntryGenerator { ); } + const modelIntrospectionSchemaBucket = new Bucket( + scope, + 'modelIntrospectionSchemaBucket', + { enforceSSL: true } + ); + new BucketDeployment(scope, 'modelIntrospectionSchemaBucketDeployment', { + // See https://github.com/aws-amplify/amplify-category-api/pull/1939 + memoryLimit: 1536, + destinationBucket: modelIntrospectionSchemaBucket, + sources: [ + Source.data(modelIntrospectionSchemaKey, modelIntrospectionSchema), + ], + }); + Tags.of(amplifyApi).add(TagName.FRIENDLY_NAME, this.name); /**; @@ -280,10 +305,15 @@ class DataGenerator implements ConstructContainerEntryGenerator { ssmEnvironmentEntriesGenerator.generateSsmEnvironmentEntries({ [`${this.name}_GRAPHQL_ENDPOINT`]: amplifyApi.resources.cfnResources.cfnGraphqlApi.attrGraphQlUrl, + [`${this.name}_MODEL_INTROSPECTION_SCHEMA_BUCKET_NAME`]: + modelIntrospectionSchemaBucket.bucketName, + [`${this.name}_MODEL_INTROSPECTION_SCHEMA_KEY`]: + modelIntrospectionSchemaKey, }); const policyGenerator = new AppSyncPolicyGenerator( - amplifyApi.resources.graphqlApi + amplifyApi.resources.graphqlApi, + `${modelIntrospectionSchemaBucket.bucketArn}/${modelIntrospectionSchemaKey}` ); schemasFunctionSchemaAccess.forEach((accessDefinition) => {