diff --git a/.changeset/warm-sloths-tickle.md b/.changeset/warm-sloths-tickle.md new file mode 100644 index 0000000000..ad85543a01 --- /dev/null +++ b/.changeset/warm-sloths-tickle.md @@ -0,0 +1,5 @@ +--- +'@aws-amplify/backend-function': patch +--- + +Lambda client env var name issue diff --git a/package-lock.json b/package-lock.json index 53ab71e34e..503a3ce89c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31607,18 +31607,18 @@ }, "packages/ai-constructs": { "name": "@aws-amplify/ai-constructs", - "version": "1.0.0", + "version": "1.1.0", "license": "Apache-2.0", "dependencies": { "@aws-amplify/backend-output-schemas": "^1.4.0", - "@aws-amplify/platform-core": "^1.2.0", - "@aws-amplify/plugin-types": "^1.3.1", + "@aws-amplify/platform-core": "^1.3.0", + "@aws-amplify/plugin-types": "^1.6.0", "@aws-sdk/client-bedrock-runtime": "^3.622.0", "@smithy/types": "^3.3.0", "json-schema-to-ts": "^3.1.1" }, "devDependencies": { - "@aws-amplify/backend-output-storage": "^1.1.3", + "@aws-amplify/backend-output-storage": "^1.1.4", "typescript": "^5.0.0" }, "peerDependencies": { @@ -31632,12 +31632,12 @@ }, "packages/auth-construct": { "name": "@aws-amplify/auth-construct", - "version": "1.5.0", + "version": "1.5.1", "license": "Apache-2.0", "dependencies": { "@aws-amplify/backend-output-schemas": "^1.4.0", - "@aws-amplify/backend-output-storage": "^1.1.3", - "@aws-amplify/plugin-types": "^1.4.0", + "@aws-amplify/backend-output-storage": "^1.1.4", + "@aws-amplify/plugin-types": "^1.6.0", "@aws-sdk/util-arn-parser": "^3.568.0" }, "peerDependencies": { @@ -31647,22 +31647,21 @@ }, "packages/backend": { "name": "@aws-amplify/backend", - "version": "1.8.0", + "version": "1.9.0", "license": "Apache-2.0", "dependencies": { - "@aws-amplify/backend-auth": "^1.4.1", - "@aws-amplify/backend-data": "^1.2.1", - "@aws-amplify/backend-function": "^1.8.0", + "@aws-amplify/backend-auth": "^1.4.2", + "@aws-amplify/backend-data": "^1.2.2", + "@aws-amplify/backend-function": "^1.9.0", "@aws-amplify/backend-output-schemas": "^1.4.0", - "@aws-amplify/backend-output-storage": "^1.1.3", + "@aws-amplify/backend-output-storage": "^1.1.4", "@aws-amplify/backend-secret": "^1.1.4", - "@aws-amplify/backend-storage": "^1.2.3", - "@aws-amplify/client-config": "^1.5.2", + "@aws-amplify/backend-storage": "^1.2.4", + "@aws-amplify/client-config": "^1.5.3", "@aws-amplify/data-schema": "^1.13.4", - "@aws-amplify/platform-core": "^1.2.1", - "@aws-amplify/plugin-types": "^1.5.0", - "@aws-sdk/client-amplify": "^3.624.0", - "lodash.snakecase": "^4.1.1" + "@aws-amplify/platform-core": "^1.3.0", + "@aws-amplify/plugin-types": "^1.6.0", + "@aws-sdk/client-amplify": "^3.624.0" }, "devDependencies": { "@types/aws-lambda": "^8.10.119", @@ -31676,15 +31675,15 @@ }, "packages/backend-ai": { "name": "@aws-amplify/backend-ai", - "version": "1.0.1", + "version": "1.1.0", "license": "Apache-2.0", "dependencies": { - "@aws-amplify/ai-constructs": "^1.0.0", + "@aws-amplify/ai-constructs": "^1.1.0", "@aws-amplify/backend-output-schemas": "^1.4.0", - "@aws-amplify/backend-output-storage": "^1.1.3", + "@aws-amplify/backend-output-storage": "^1.1.4", "@aws-amplify/data-schema-types": "^1.2.0", - "@aws-amplify/platform-core": "^1.2.1", - "@aws-amplify/plugin-types": "^1.5.0" + "@aws-amplify/platform-core": "^1.3.0", + "@aws-amplify/plugin-types": "^1.6.0" }, "peerDependencies": { "aws-cdk-lib": "^2.168.0", @@ -31693,17 +31692,17 @@ }, "packages/backend-auth": { "name": "@aws-amplify/backend-auth", - "version": "1.4.1", + "version": "1.4.2", "license": "Apache-2.0", "dependencies": { - "@aws-amplify/auth-construct": "^1.5.0", + "@aws-amplify/auth-construct": "^1.5.1", "@aws-amplify/backend-output-schemas": "^1.4.0", - "@aws-amplify/backend-output-storage": "^1.1.3", - "@aws-amplify/plugin-types": "^1.5.0" + "@aws-amplify/backend-output-storage": "^1.1.4", + "@aws-amplify/plugin-types": "^1.6.0" }, "devDependencies": { - "@aws-amplify/backend-platform-test-stubs": "^0.3.6", - "@aws-amplify/platform-core": "^1.2.1", + "@aws-amplify/backend-platform-test-stubs": "^0.3.7", + "@aws-amplify/platform-core": "^1.3.0", "@aws-sdk/client-cognito-identity": "^3.624.0", "@aws-sdk/client-cognito-identity-provider": "^3.624.0", "@types/aws-lambda": "^8.10.119", @@ -31716,20 +31715,20 @@ }, "packages/backend-data": { "name": "@aws-amplify/backend-data", - "version": "1.2.1", + "version": "1.2.2", "license": "Apache-2.0", "dependencies": { "@aws-amplify/backend-output-schemas": "^1.4.0", - "@aws-amplify/backend-output-storage": "^1.1.3", + "@aws-amplify/backend-output-storage": "^1.1.4", "@aws-amplify/data-construct": "^1.10.1", "@aws-amplify/data-schema-types": "^1.2.0", "@aws-amplify/graphql-generator": "^0.5.1", - "@aws-amplify/plugin-types": "^1.4.0" + "@aws-amplify/plugin-types": "^1.6.0" }, "devDependencies": { - "@aws-amplify/backend-platform-test-stubs": "^0.3.6", + "@aws-amplify/backend-platform-test-stubs": "^0.3.7", "@aws-amplify/data-schema": "^1.13.4", - "@aws-amplify/platform-core": "^1.2.1" + "@aws-amplify/platform-core": "^1.3.0" }, "peerDependencies": { "aws-cdk-lib": "^2.168.0", @@ -31738,11 +31737,11 @@ }, "packages/backend-deployer": { "name": "@aws-amplify/backend-deployer", - "version": "1.1.10", + "version": "1.1.11", "license": "Apache-2.0", "dependencies": { - "@aws-amplify/platform-core": "^1.2.2", - "@aws-amplify/plugin-types": "^1.4.0", + "@aws-amplify/platform-core": "^1.3.0", + "@aws-amplify/plugin-types": "^1.6.0", "execa": "^9.5.1", "strip-ansi": "^6.0.1", "tsx": "^4.6.1" @@ -31872,18 +31871,18 @@ }, "packages/backend-function": { "name": "@aws-amplify/backend-function", - "version": "1.8.0", + "version": "1.9.0", "license": "Apache-2.0", "dependencies": { "@aws-amplify/backend-output-schemas": "^1.4.0", - "@aws-amplify/backend-output-storage": "^1.1.3", - "@aws-amplify/plugin-types": "^1.5.0", + "@aws-amplify/backend-output-storage": "^1.1.4", + "@aws-amplify/plugin-types": "^1.6.0", "@aws-sdk/client-s3": "^3.624.0", "execa": "^9.5.1" }, "devDependencies": { - "@aws-amplify/backend-platform-test-stubs": "^0.3.6", - "@aws-amplify/platform-core": "^1.2.1", + "@aws-amplify/backend-platform-test-stubs": "^0.3.7", + "@aws-amplify/platform-core": "^1.3.0", "@aws-sdk/client-s3": "^3.624.0", "@aws-sdk/client-ssm": "^3.624.0", "aws-sdk": "^2.1550.0", @@ -32039,12 +32038,12 @@ }, "packages/backend-output-storage": { "name": "@aws-amplify/backend-output-storage", - "version": "1.1.3", + "version": "1.1.4", "license": "Apache-2.0", "dependencies": { "@aws-amplify/backend-output-schemas": "^1.2.0", - "@aws-amplify/platform-core": "^1.0.6", - "@aws-amplify/plugin-types": "^1.3.1" + "@aws-amplify/platform-core": "^1.3.0", + "@aws-amplify/plugin-types": "^1.6.0" }, "peerDependencies": { "aws-cdk-lib": "^2.168.0" @@ -32052,10 +32051,10 @@ }, "packages/backend-platform-test-stubs": { "name": "@aws-amplify/backend-platform-test-stubs", - "version": "0.3.6", + "version": "0.3.7", "license": "Apache-2.0", "dependencies": { - "@aws-amplify/plugin-types": "^1.3.1", + "@aws-amplify/plugin-types": "^1.6.0", "aws-cdk-lib": "^2.168.0", "constructs": "^10.0.0" } @@ -32075,16 +32074,16 @@ }, "packages/backend-storage": { "name": "@aws-amplify/backend-storage", - "version": "1.2.3", + "version": "1.2.4", "license": "Apache-2.0", "dependencies": { "@aws-amplify/backend-output-schemas": "^1.2.1", - "@aws-amplify/backend-output-storage": "^1.1.3", - "@aws-amplify/plugin-types": "^1.5.0" + "@aws-amplify/backend-output-storage": "^1.1.4", + "@aws-amplify/plugin-types": "^1.6.0" }, "devDependencies": { - "@aws-amplify/backend-platform-test-stubs": "^0.3.6", - "@aws-amplify/platform-core": "^1.2.1" + "@aws-amplify/backend-platform-test-stubs": "^0.3.7", + "@aws-amplify/platform-core": "^1.3.0" }, "peerDependencies": { "aws-cdk-lib": "^2.168.0", @@ -32093,21 +32092,21 @@ }, "packages/cli": { "name": "@aws-amplify/backend-cli", - "version": "1.4.2", + "version": "1.4.3", "license": "Apache-2.0", "dependencies": { - "@aws-amplify/backend-deployer": "^1.1.9", + "@aws-amplify/backend-deployer": "^1.1.11", "@aws-amplify/backend-output-schemas": "^1.4.0", "@aws-amplify/backend-secret": "^1.1.2", - "@aws-amplify/cli-core": "^1.2.0", - "@aws-amplify/client-config": "^1.5.1", + "@aws-amplify/cli-core": "^1.2.1", + "@aws-amplify/client-config": "^1.5.3", "@aws-amplify/deployed-backend-client": "^1.4.1", "@aws-amplify/form-generator": "^1.0.3", "@aws-amplify/model-generator": "^1.0.9", - "@aws-amplify/platform-core": "^1.2.0", - "@aws-amplify/plugin-types": "^1.4.0", - "@aws-amplify/sandbox": "^1.2.5", - "@aws-amplify/schema-generator": "^1.2.5", + "@aws-amplify/platform-core": "^1.3.0", + "@aws-amplify/plugin-types": "^1.6.0", + "@aws-amplify/sandbox": "^1.2.7", + "@aws-amplify/schema-generator": "^1.2.6", "@aws-sdk/client-amplify": "^3.624.0", "@aws-sdk/client-cloudformation": "^3.624.0", "@aws-sdk/client-s3": "^3.624.0", @@ -32140,10 +32139,10 @@ }, "packages/cli-core": { "name": "@aws-amplify/cli-core", - "version": "1.2.0", + "version": "1.2.1", "license": "Apache-2.0", "dependencies": { - "@aws-amplify/platform-core": "^1.0.5", + "@aws-amplify/platform-core": "^1.3.0", "@inquirer/prompts": "^3.0.0", "execa": "^9.5.1", "kleur": "^4.1.5" @@ -32483,14 +32482,14 @@ }, "packages/client-config": { "name": "@aws-amplify/client-config", - "version": "1.5.2", + "version": "1.5.3", "license": "Apache-2.0", "dependencies": { "@aws-amplify/backend-output-schemas": "^1.4.0", "@aws-amplify/deployed-backend-client": "^1.4.1", "@aws-amplify/model-generator": "^1.0.7", - "@aws-amplify/platform-core": "^1.0.7", - "@aws-amplify/plugin-types": "^1.3.1", + "@aws-amplify/platform-core": "^1.3.0", + "@aws-amplify/plugin-types": "^1.6.0", "zod": "^3.22.2" }, "devDependencies": { @@ -32505,12 +32504,12 @@ } }, "packages/create-amplify": { - "version": "1.0.6", + "version": "1.0.7", "license": "Apache-2.0", "dependencies": { - "@aws-amplify/cli-core": "^1.1.3", - "@aws-amplify/platform-core": "^1.0.3", - "@aws-amplify/plugin-types": "^1.2.2", + "@aws-amplify/cli-core": "^1.2.1", + "@aws-amplify/platform-core": "^1.3.0", + "@aws-amplify/plugin-types": "^1.6.0", "execa": "^9.5.1", "kleur": "^4.1.5", "yargs": "^17.7.2" @@ -32786,20 +32785,20 @@ }, "packages/integration-tests": { "name": "@aws-amplify/integration-tests", - "version": "0.6.0", + "version": "0.6.1", "license": "Apache-2.0", "devDependencies": { "@apollo/client": "^3.10.1", - "@aws-amplify/ai-constructs": "^1.0.0", - "@aws-amplify/auth-construct": "^1.4.0", - "@aws-amplify/backend": "^1.6.0", - "@aws-amplify/backend-ai": "^1.0.0", + "@aws-amplify/ai-constructs": "^1.1.0", + "@aws-amplify/auth-construct": "^1.5.1", + "@aws-amplify/backend": "^1.9.0", + "@aws-amplify/backend-ai": "^1.1.0", "@aws-amplify/backend-secret": "^1.1.4", - "@aws-amplify/client-config": "^1.5.1", + "@aws-amplify/client-config": "^1.5.3", "@aws-amplify/data-schema": "^1.13.4", "@aws-amplify/deployed-backend-client": "^1.4.1", - "@aws-amplify/platform-core": "^1.1.0", - "@aws-amplify/plugin-types": "^1.3.1", + "@aws-amplify/platform-core": "^1.3.0", + "@aws-amplify/plugin-types": "^1.6.0", "@aws-sdk/client-accessanalyzer": "^3.624.0", "@aws-sdk/client-amplify": "^3.624.0", "@aws-sdk/client-bedrock-runtime": "^3.622.0", @@ -32995,13 +32994,14 @@ }, "packages/platform-core": { "name": "@aws-amplify/platform-core", - "version": "1.2.2", + "version": "1.3.0", "license": "Apache-2.0", "dependencies": { - "@aws-amplify/plugin-types": "^1.5.0", + "@aws-amplify/plugin-types": "^1.6.0", "@aws-sdk/client-sts": "^3.624.0", "is-ci": "^3.0.1", "lodash.mergewith": "^4.6.2", + "lodash.snakecase": "^4.1.1", "semver": "^7.6.3", "uuid": "^9.0.1", "zod": "^3.22.2" @@ -33031,7 +33031,7 @@ }, "packages/plugin-types": { "name": "@aws-amplify/plugin-types", - "version": "1.5.0", + "version": "1.6.0", "license": "Apache-2.0", "peerDependencies": { "@aws-sdk/types": "^3.609.0", @@ -33041,16 +33041,16 @@ }, "packages/sandbox": { "name": "@aws-amplify/sandbox", - "version": "1.2.6", + "version": "1.2.7", "license": "Apache-2.0", "dependencies": { - "@aws-amplify/backend-deployer": "^1.1.8", + "@aws-amplify/backend-deployer": "^1.1.11", "@aws-amplify/backend-secret": "^1.1.2", - "@aws-amplify/cli-core": "^1.2.0", - "@aws-amplify/client-config": "^1.5.1", + "@aws-amplify/cli-core": "^1.2.1", + "@aws-amplify/client-config": "^1.5.3", "@aws-amplify/deployed-backend-client": "^1.4.1", - "@aws-amplify/platform-core": "^1.2.1", - "@aws-amplify/plugin-types": "^1.5.0", + "@aws-amplify/platform-core": "^1.3.0", + "@aws-amplify/plugin-types": "^1.6.0", "@aws-sdk/client-cloudwatch-logs": "^3.624.0", "@aws-sdk/client-lambda": "^3.624.0", "@aws-sdk/client-ssm": "^3.624.0", @@ -33072,11 +33072,11 @@ }, "packages/schema-generator": { "name": "@aws-amplify/schema-generator", - "version": "1.2.5", + "version": "1.2.6", "license": "Apache-2.0", "dependencies": { "@aws-amplify/graphql-schema-generator": "^0.11.0", - "@aws-amplify/platform-core": "^1.0.5" + "@aws-amplify/platform-core": "^1.3.0" } } } diff --git a/packages/backend-data/src/factory.ts b/packages/backend-data/src/factory.ts index 98d48b0667..a40b49173a 100644 --- a/packages/backend-data/src/factory.ts +++ b/packages/backend-data/src/factory.ts @@ -52,6 +52,7 @@ import { Bucket } from 'aws-cdk-lib/aws-s3'; import { BucketDeployment, Source } from 'aws-cdk-lib/aws-s3-deployment'; const modelIntrospectionSchemaKey = 'modelIntrospectionSchema.json'; +const defaultName = 'amplifyData'; /** * Singleton factory for AmplifyGraphqlApi constructs that can be used in Amplify project files. @@ -127,7 +128,7 @@ class DataGenerator implements ConstructContainerEntryGenerator { private readonly getInstanceProps: ConstructFactoryGetInstanceProps, private readonly outputStorageStrategy: BackendOutputStorageStrategy ) { - this.name = props.name ?? 'amplifyData'; + this.name = props.name ?? defaultName; } generateContainerEntry = ({ @@ -307,14 +308,20 @@ class DataGenerator implements ConstructContainerEntryGenerator { convertJsResolverDefinition(scope, amplifyApi, schemasJsFunctions); + const namePrefix = this.name === defaultName ? '' : defaultName; + const ssmEnvironmentEntries = ssmEnvironmentEntriesGenerator.generateSsmEnvironmentEntries({ - [`${this.name}_GRAPHQL_ENDPOINT`]: + [`${namePrefix}${this.name}_GRAPHQL_ENDPOINT`]: amplifyApi.resources.cfnResources.cfnGraphqlApi.attrGraphQlUrl, - [`${this.name}_MODEL_INTROSPECTION_SCHEMA_BUCKET_NAME`]: + [`${namePrefix}${this.name}_MODEL_INTROSPECTION_SCHEMA_BUCKET_NAME`]: modelIntrospectionSchemaBucket.bucketName, - [`${this.name}_MODEL_INTROSPECTION_SCHEMA_KEY`]: + [`${namePrefix}${this.name}_MODEL_INTROSPECTION_SCHEMA_KEY`]: modelIntrospectionSchemaKey, + ['AMPLIFY_DATA_DEFAULT_NAME']: `${namePrefix}${this.name}`, + // @deprecated: This backwards compatible name without a prefix will be removed + [`${this.name}_GRAPHQL_ENDPOINT`]: + amplifyApi.resources.cfnResources.cfnGraphqlApi.attrGraphQlUrl, }); const policyGenerator = new AppSyncPolicyGenerator( diff --git a/packages/backend-function/API.md b/packages/backend-function/API.md index fed504b411..db54778b04 100644 --- a/packages/backend-function/API.md +++ b/packages/backend-function/API.md @@ -45,14 +45,12 @@ type DataClientConfig = { // @public (undocumented) type DataClientEnv = { - AMPLIFY_DATA_GRAPHQL_ENDPOINT: string; - AMPLIFY_DATA_MODEL_INTROSPECTION_SCHEMA_BUCKET_NAME: string; - AMPLIFY_DATA_MODEL_INTROSPECTION_SCHEMA_KEY: string; AWS_ACCESS_KEY_ID: string; AWS_SECRET_ACCESS_KEY: string; AWS_SESSION_TOKEN: string; AWS_REGION: string; -}; + AMPLIFY_DATA_DEFAULT_NAME: string; +} & Record; // @public (undocumented) type DataClientError = { @@ -110,7 +108,7 @@ const getAmplifyDataClientConfig: (env: T, s3Client?: S3Client) => Promise [allow.resource(fcn)])` on the data schema.'; + invalidType: 'Some of the AWS environment variables needed to configure Amplify are missing.'; }; // @public (undocumented) diff --git a/packages/backend-function/src/runtime/get_amplify_clients_configuration.test.ts b/packages/backend-function/src/runtime/get_amplify_clients_configuration.test.ts index e2e5d45407..1287b11922 100644 --- a/packages/backend-function/src/runtime/get_amplify_clients_configuration.test.ts +++ b/packages/backend-function/src/runtime/get_amplify_clients_configuration.test.ts @@ -4,7 +4,7 @@ import { NoSuchKey, S3, S3ServiceException } from '@aws-sdk/client-s3'; import { getAmplifyDataClientConfig } from './get_amplify_clients_configuration.js'; -const validEnv = { +const validDefaultEnv = { AMPLIFY_DATA_MODEL_INTROSPECTION_SCHEMA_BUCKET_NAME: 'TEST_VALUE for AMPLIFY_DATA_MODEL_INTROSPECTION_SCHEMA_BUCKET_NAME', AMPLIFY_DATA_MODEL_INTROSPECTION_SCHEMA_KEY: @@ -14,6 +14,21 @@ const validEnv = { AWS_SESSION_TOKEN: 'TEST_VALUE for AWS_SESSION_TOKEN', AWS_REGION: 'TEST_VALUE for AWS_REGION', AMPLIFY_DATA_GRAPHQL_ENDPOINT: 'TEST_VALUE for AMPLIFY_DATA_GRAPHQL_ENDPOINT', + AMPLIFY_DATA_DEFAULT_NAME: 'AmplifyData', +}; + +const validNamedEnv = { + AMPLIFY_DATA_TEST_NAME_MODEL_INTROSPECTION_SCHEMA_BUCKET_NAME: + 'TEST_VALUE for AMPLIFY_DATA_MODEL_INTROSPECTION_SCHEMA_BUCKET_NAME', + AMPLIFY_DATA_TEST_NAME_MODEL_INTROSPECTION_SCHEMA_KEY: + 'TEST_VALUE for AMPLIFY_DATA_MODEL_INTROSPECTION_SCHEMA_KEY', + AWS_ACCESS_KEY_ID: 'TEST_VALUE for AWS_ACCESS_KEY_ID', + AWS_SECRET_ACCESS_KEY: 'TEST_VALUE for AWS_SECRET_ACCESS_KEY', + AWS_SESSION_TOKEN: 'TEST_VALUE for AWS_SESSION_TOKEN', + AWS_REGION: 'TEST_VALUE for AWS_REGION', + AMPLIFY_DATA_TEST_NAME_GRAPHQL_ENDPOINT: + 'TEST_VALUE for AMPLIFY_DATA_GRAPHQL_ENDPOINT', + AMPLIFY_DATA_DEFAULT_NAME: 'AmplifyDataTestName', }; let mockS3Client: S3; @@ -23,110 +38,146 @@ void describe('getAmplifyDataClientConfig', () => { mockS3Client = new S3(); }); - Object.keys(validEnv).forEach((envFieldToExclude) => { - void it(`returns empty config objects when ${envFieldToExclude} is not included`, async () => { - const env = { ...validEnv } as Record; - delete env[envFieldToExclude]; - assert.deepEqual(await getAmplifyDataClientConfig(env), { - resourceConfig: {}, - libraryOptions: {}, + [ + { + name: 'no set name', + dataBackendName: 'AMPLIFY_DATA', + validEnv: validDefaultEnv, + }, + { + name: 'an explicit name', + dataBackendName: 'AMPLIFY_DATA_TEST_NAME', + validEnv: validNamedEnv, + }, + ].forEach(({ name, dataBackendName, validEnv }) => { + void describe(`env variable with ${name} for the data backend`, () => { + Object.keys(validEnv) + .filter((k) => k !== 'AMPLIFY_DATA_DEFAULT_NAME') + .forEach((envFieldToExclude) => { + if (envFieldToExclude.includes(dataBackendName)) { + void it(`throws error when ${envFieldToExclude} is not included`, async () => { + const env = { ...validEnv } as Record; + delete env[envFieldToExclude]; + await assert.rejects( + async () => await getAmplifyDataClientConfig(env), + /The data environment variables are malformed/ + ); + }); + + void it(`throws error when ${envFieldToExclude} is not a string`, async () => { + const env = { ...validEnv } as Record; + env[envFieldToExclude] = 123; + await assert.rejects( + async () => await getAmplifyDataClientConfig(env), + /The data environment variables are malformed/ + ); + }); + } else { + void it(`returns empty config objects when ${envFieldToExclude} is not included`, async () => { + const env = { ...validEnv } as Record; + delete env[envFieldToExclude]; + assert.deepEqual(await getAmplifyDataClientConfig(env), { + resourceConfig: {}, + libraryOptions: {}, + }); + }); + + void it(`returns empty config objects when ${envFieldToExclude} is not a string`, async () => { + const env = { ...validEnv } as Record; + env[envFieldToExclude] = 123; + assert.deepEqual(await getAmplifyDataClientConfig(env), { + resourceConfig: {}, + libraryOptions: {}, + }); + }); + } + }); + + void it('raises a custom error message when the model introspection schema is missing from the s3 bucket', async () => { + const s3ClientSendMock = mock.method(mockS3Client, 'send', async () => { + throw new NoSuchKey({ message: 'TEST_ERROR', $metadata: {} }); + }); + mock.method(mockS3Client, 'send', s3ClientSendMock); + + await assert.rejects( + async () => await getAmplifyDataClientConfig(validEnv, mockS3Client), + new Error( + 'Error retrieving the schema from S3. Please confirm that your project has a `defineData` included in the `defineBackend` definition.' + ) + ); }); - }); - void it(`returns empty config objects when ${envFieldToExclude} is not a string`, async () => { - const env = { ...validEnv } as Record; - env[envFieldToExclude] = 123; - assert.deepEqual(await getAmplifyDataClientConfig(env), { - resourceConfig: {}, - libraryOptions: {}, + void it('raises a custom error message when there is a S3ServiceException error retrieving the model introspection schema from the s3 bucket', async () => { + const s3ClientSendMock = mock.method(mockS3Client, 'send', async () => { + throw new S3ServiceException({ + name: 'TEST_ERROR', + message: 'TEST_MESSAGE', + $fault: 'server', + $metadata: {}, + }); + }); + mock.method(mockS3Client, 'send', s3ClientSendMock); + + await assert.rejects( + async () => await getAmplifyDataClientConfig(validEnv, mockS3Client), + new Error( + 'Error retrieving the schema from S3. You may need to grant this function authorization on the schema. TEST_ERROR: TEST_MESSAGE.' + ) + ); }); - }); - }); - void it('raises a custom error message when the model introspection schema is missing from the s3 bucket', async () => { - const s3ClientSendMock = mock.method(mockS3Client, 'send', async () => { - throw new NoSuchKey({ message: 'TEST_ERROR', $metadata: {} }); - }); - mock.method(mockS3Client, 'send', s3ClientSendMock); - - await assert.rejects( - async () => await getAmplifyDataClientConfig(validEnv, mockS3Client), - new Error( - 'Error retrieving the schema from S3. Please confirm that your project has a `defineData` included in the `defineBackend` definition.' - ) - ); - }); + void it('re-raises a non-S3 error received when retrieving the model introspection schema from the s3 bucket', async () => { + const s3ClientSendMock = mock.method(mockS3Client, 'send', async () => { + throw new Error('Test Error'); + }); + mock.method(mockS3Client, 'send', s3ClientSendMock); - void it('raises a custom error message when there is a S3ServiceException error retrieving the model introspection schema from the s3 bucket', async () => { - const s3ClientSendMock = mock.method(mockS3Client, 'send', async () => { - throw new S3ServiceException({ - name: 'TEST_ERROR', - message: 'TEST_MESSAGE', - $fault: 'server', - $metadata: {}, + await assert.rejects( + async () => await getAmplifyDataClientConfig(validEnv, mockS3Client), + new Error('Test Error') + ); }); - }); - mock.method(mockS3Client, 'send', s3ClientSendMock); - - await assert.rejects( - async () => await getAmplifyDataClientConfig(validEnv, mockS3Client), - new Error( - 'Error retrieving the schema from S3. You may need to grant this function authorization on the schema. TEST_ERROR: TEST_MESSAGE.' - ) - ); - }); - void it('re-raises a non-S3 error received when retrieving the model introspection schema from the s3 bucket', async () => { - const s3ClientSendMock = mock.method(mockS3Client, 'send', async () => { - throw new Error('Test Error'); - }); - mock.method(mockS3Client, 'send', s3ClientSendMock); - - await assert.rejects( - async () => await getAmplifyDataClientConfig(validEnv, mockS3Client), - new Error('Test Error') - ); - }); - - void it('returns the expected libraryOptions and resourceConfig values in the happy case', async () => { - const s3ClientSendMock = mock.method(mockS3Client, 'send', () => { - return Promise.resolve({ - Body: { - transformToString: () => JSON.stringify({ testSchema: 'TESTING' }), - }, + void it('returns the expected libraryOptions and resourceConfig values in the happy case', async () => { + const s3ClientSendMock = mock.method(mockS3Client, 'send', () => { + return Promise.resolve({ + Body: { + transformToString: () => + JSON.stringify({ testSchema: 'TESTING' }), + }, + }); + }); + mock.method(mockS3Client, 'send', s3ClientSendMock); + + const { resourceConfig, libraryOptions } = + await getAmplifyDataClientConfig(validEnv, mockS3Client); + + assert.deepEqual( + await libraryOptions.Auth.credentialsProvider.getCredentialsAndIdentityId?.(), + { + credentials: { + accessKeyId: 'TEST_VALUE for AWS_ACCESS_KEY_ID', + secretAccessKey: 'TEST_VALUE for AWS_SECRET_ACCESS_KEY', + sessionToken: 'TEST_VALUE for AWS_SESSION_TOKEN', + }, + } + ); + assert.deepEqual( + await libraryOptions.Auth.credentialsProvider.clearCredentialsAndIdentityId?.(), + undefined + ); + + assert.deepEqual(resourceConfig, { + API: { + GraphQL: { + endpoint: 'TEST_VALUE for AMPLIFY_DATA_GRAPHQL_ENDPOINT', + region: 'TEST_VALUE for AWS_REGION', + defaultAuthMode: 'iam', + modelIntrospection: { testSchema: 'TESTING' }, + }, + }, + }); }); }); - mock.method(mockS3Client, 'send', s3ClientSendMock); - - const { resourceConfig, libraryOptions } = await getAmplifyDataClientConfig( - validEnv, - mockS3Client - ); - - assert.deepEqual( - await libraryOptions.Auth.credentialsProvider.getCredentialsAndIdentityId?.(), - { - credentials: { - accessKeyId: 'TEST_VALUE for AWS_ACCESS_KEY_ID', - secretAccessKey: 'TEST_VALUE for AWS_SECRET_ACCESS_KEY', - sessionToken: 'TEST_VALUE for AWS_SESSION_TOKEN', - }, - } - ); - assert.deepEqual( - await libraryOptions.Auth.credentialsProvider.clearCredentialsAndIdentityId?.(), - undefined - ); - - assert.deepEqual(resourceConfig, { - API: { - GraphQL: { - endpoint: 'TEST_VALUE for AMPLIFY_DATA_GRAPHQL_ENDPOINT', - region: 'TEST_VALUE for AWS_REGION', - defaultAuthMode: 'iam', - modelIntrospection: { testSchema: 'TESTING' }, - }, - }, - }); }); }); diff --git a/packages/backend-function/src/runtime/get_amplify_clients_configuration.ts b/packages/backend-function/src/runtime/get_amplify_clients_configuration.ts index 2f377fe349..cf1c888ed1 100644 --- a/packages/backend-function/src/runtime/get_amplify_clients_configuration.ts +++ b/packages/backend-function/src/runtime/get_amplify_clients_configuration.ts @@ -1,3 +1,4 @@ +import { toScreamingSnakeCase } from '@aws-amplify/platform-core'; import { GetObjectCommand, NoSuchKey, @@ -5,37 +6,40 @@ import { S3ServiceException, } from '@aws-sdk/client-s3'; +const dataKeyNameContent = '_MODEL_INTROSPECTION_SCHEMA_KEY'; +const dataBucketNameContent = '_MODEL_INTROSPECTION_SCHEMA_BUCKET_NAME'; +const dataEndpointNameContent = '_GRAPHQL_ENDPOINT'; + export type DataClientEnv = { /* eslint-disable @typescript-eslint/naming-convention */ - AMPLIFY_DATA_GRAPHQL_ENDPOINT: string; - AMPLIFY_DATA_MODEL_INTROSPECTION_SCHEMA_BUCKET_NAME: string; - AMPLIFY_DATA_MODEL_INTROSPECTION_SCHEMA_KEY: string; AWS_ACCESS_KEY_ID: string; AWS_SECRET_ACCESS_KEY: string; AWS_SESSION_TOKEN: string; AWS_REGION: string; + AMPLIFY_DATA_DEFAULT_NAME: string; /* eslint-enable @typescript-eslint/naming-convention */ +} & Record; + +type DataEnvExtension = { + dataBucket: string; + dataKey: string; + dataEndpoint: string; }; -const isDataClientEnv = (env: unknown): env is DataClientEnv => { +type ExtendedAmplifyClientEnv = DataClientEnv & DataEnvExtension; + +const isAmplifyClientEnv = (env: object): env is DataClientEnv => { return ( - env !== null && - typeof env === 'object' && - 'AMPLIFY_DATA_MODEL_INTROSPECTION_SCHEMA_BUCKET_NAME' in env && - 'AMPLIFY_DATA_MODEL_INTROSPECTION_SCHEMA_KEY' in env && 'AWS_ACCESS_KEY_ID' in env && - 'AWS_SECRET_ACCESS_KEY' in env && - 'AWS_SESSION_TOKEN' in env && - 'AWS_REGION' in env && - 'AMPLIFY_DATA_GRAPHQL_ENDPOINT' in env && - typeof env.AMPLIFY_DATA_MODEL_INTROSPECTION_SCHEMA_BUCKET_NAME === - 'string' && - typeof env.AMPLIFY_DATA_MODEL_INTROSPECTION_SCHEMA_KEY === 'string' && typeof env.AWS_ACCESS_KEY_ID === 'string' && + 'AWS_SECRET_ACCESS_KEY' in env && typeof env.AWS_SECRET_ACCESS_KEY === 'string' && + 'AWS_SESSION_TOKEN' in env && typeof env.AWS_SESSION_TOKEN === 'string' && + 'AWS_REGION' in env && typeof env.AWS_REGION === 'string' && - typeof env.AMPLIFY_DATA_GRAPHQL_ENDPOINT === 'string' + 'AMPLIFY_DATA_DEFAULT_NAME' in env && + typeof env.AMPLIFY_DATA_DEFAULT_NAME === 'string' ); }; @@ -56,16 +60,15 @@ export type ResourceConfig = { /* eslint-enable @typescript-eslint/naming-convention */ const getResourceConfig = ( - env: DataClientEnv, + env: ExtendedAmplifyClientEnv, modelIntrospectionSchema: object ): ResourceConfig => { return { API: { GraphQL: { - endpoint: env.AMPLIFY_DATA_GRAPHQL_ENDPOINT, + endpoint: env.dataEndpoint, region: env.AWS_REGION, defaultAuthMode: 'iam' as const, - modelIntrospection: modelIntrospectionSchema, }, }, @@ -108,7 +111,7 @@ const getLibraryOptions = (env: DataClientEnv): LibraryOptions => { }; export type InvalidConfig = unknown & { - invalidType: 'This function needs to be granted `authorization((allow) => [allow.resource(fcn)])` on the data schema.'; + invalidType: 'Some of the AWS environment variables needed to configure Amplify are missing.'; }; export type DataClientError = { @@ -125,6 +128,38 @@ export type DataClientReturn = T extends DataClientEnv ? DataClientConfig : DataClientError; +const extendEnv = ( + env: DataClientEnv & Record, + dataName: string +): ExtendedAmplifyClientEnv => { + const bucketName = `${dataName}${dataBucketNameContent}`; + const keyName = `${dataName}${dataKeyNameContent}`; + const endpointName = `${dataName}${dataEndpointNameContent}`; + if ( + !( + bucketName in env && + keyName in env && + endpointName in env && + typeof env[bucketName] === 'string' && + typeof env[keyName] === 'string' && + typeof env[endpointName] === 'string' + ) + ) { + throw new Error('The data environment variables are malformed'); + } + + const dataBucket = env[bucketName] as string; + const dataKey = env[keyName] as string; + const dataEndpoint = env[endpointName] as string; + + return { + ...env, + dataBucket, + dataKey, + dataEndpoint, + }; +}; + /** * Generate the `resourceConfig` and `libraryOptions` need to configure * Amplify for the data client in a lambda. @@ -141,17 +176,24 @@ export const getAmplifyDataClientConfig = async ( if (!s3Client) { s3Client = new S3Client(); } + if (env === null || typeof env !== 'object') { + throw new Error(`Invalid environment variables: ${JSON.stringify(env)}`); + } - if (!isDataClientEnv(env)) { + if (!isAmplifyClientEnv(env)) { return { resourceConfig: {}, libraryOptions: {} } as DataClientReturn; } + + const dataName = toScreamingSnakeCase(env.AMPLIFY_DATA_DEFAULT_NAME); + const extendedEnv = extendEnv(env, dataName); + let modelIntrospectionSchema: object; try { const response = await s3Client.send( new GetObjectCommand({ - Bucket: env.AMPLIFY_DATA_MODEL_INTROSPECTION_SCHEMA_BUCKET_NAME, - Key: env.AMPLIFY_DATA_MODEL_INTROSPECTION_SCHEMA_KEY, + Bucket: extendedEnv.dataBucket, + Key: extendedEnv.dataKey, }) ); const modelIntrospectionSchemaJson = @@ -173,7 +215,10 @@ export const getAmplifyDataClientConfig = async ( const libraryOptions = getLibraryOptions(env); - const resourceConfig = getResourceConfig(env, modelIntrospectionSchema); + const resourceConfig = getResourceConfig( + extendedEnv, + modelIntrospectionSchema + ); return { resourceConfig, libraryOptions } as DataClientReturn; }; diff --git a/packages/backend/package.json b/packages/backend/package.json index 7ef401f5aa..6ff0d2c384 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -41,8 +41,7 @@ "@aws-amplify/client-config": "^1.5.3", "@aws-amplify/platform-core": "^1.3.0", "@aws-amplify/plugin-types": "^1.6.0", - "@aws-sdk/client-amplify": "^3.624.0", - "lodash.snakecase": "^4.1.1" + "@aws-sdk/client-amplify": "^3.624.0" }, "peerDependencies": { "aws-cdk-lib": "^2.168.0", diff --git a/packages/backend/src/engine/backend_id_scoped_ssm_environment_entries_generator.ts b/packages/backend/src/engine/backend_id_scoped_ssm_environment_entries_generator.ts index 067ccf32ba..ba1d9a6d75 100644 --- a/packages/backend/src/engine/backend_id_scoped_ssm_environment_entries_generator.ts +++ b/packages/backend/src/engine/backend_id_scoped_ssm_environment_entries_generator.ts @@ -1,11 +1,13 @@ -import { ParameterPathConversions } from '@aws-amplify/platform-core'; +import { + ParameterPathConversions, + toScreamingSnakeCase, +} from '@aws-amplify/platform-core'; import { BackendIdentifier, SsmEnvironmentEntriesGenerator, } from '@aws-amplify/plugin-types'; import { StringParameter } from 'aws-cdk-lib/aws-ssm'; import { Construct } from 'constructs'; -import { toScreamingSnakeCase } from './naming_convention_conversions.js'; /** * Generates SsmEnvironmentEntry[] with SSM parameters that are scoped to a specific backend identifier diff --git a/packages/integration-tests/src/test-projects/data_access_from_function/amplify/data/resource.ts b/packages/integration-tests/src/test-projects/data_access_from_function/amplify/data/resource.ts index 4c3442bdc7..2bb4831790 100644 --- a/packages/integration-tests/src/test-projects/data_access_from_function/amplify/data/resource.ts +++ b/packages/integration-tests/src/test-projects/data_access_from_function/amplify/data/resource.ts @@ -31,6 +31,7 @@ const schema = a export type Schema = ClientSchema; export const data = defineData({ + name: 'DATATEST', schema, authorizationModes: { defaultAuthorizationMode: 'apiKey', diff --git a/packages/platform-core/API.md b/packages/platform-core/API.md index f40d1c5364..09fe3de26c 100644 --- a/packages/platform-core/API.md +++ b/packages/platform-core/API.md @@ -211,6 +211,9 @@ export enum TagName { FRIENDLY_NAME = "amplify:friendly-name" } +// @public +export const toScreamingSnakeCase: (input: string) => string; + // @public export const USAGE_DATA_TRACKING_ENABLED = "telemetry.enabled"; diff --git a/packages/platform-core/package.json b/packages/platform-core/package.json index 6ea68ea197..bce16548a8 100644 --- a/packages/platform-core/package.json +++ b/packages/platform-core/package.json @@ -34,6 +34,7 @@ "is-ci": "^3.0.1", "lodash.mergewith": "^4.6.2", "semver": "^7.6.3", + "lodash.snakecase": "^4.1.1", "uuid": "^9.0.1", "zod": "^3.22.2" }, diff --git a/packages/platform-core/src/index.ts b/packages/platform-core/src/index.ts index 7e05a510ea..97987eade8 100644 --- a/packages/platform-core/src/index.ts +++ b/packages/platform-core/src/index.ts @@ -11,3 +11,4 @@ export { CDKContextKey } from './cdk_context_key.js'; export * from './parameter_path_conversions.js'; export * from './object_accumulator.js'; export { TagName } from './tag_name.js'; +export * from './naming_convention_conversions.js'; diff --git a/packages/backend/src/engine/naming_convention_conversions.test.ts b/packages/platform-core/src/naming_convention_conversions.test.ts similarity index 100% rename from packages/backend/src/engine/naming_convention_conversions.test.ts rename to packages/platform-core/src/naming_convention_conversions.test.ts diff --git a/packages/backend/src/engine/naming_convention_conversions.ts b/packages/platform-core/src/naming_convention_conversions.ts similarity index 100% rename from packages/backend/src/engine/naming_convention_conversions.ts rename to packages/platform-core/src/naming_convention_conversions.ts