Skip to content

Commit f193105

Browse files
stocaaroKamil Sobol
andauthored
fix: Lambda client env var name issue (#2324)
--------- Co-authored-by: Kamil Sobol <[email protected]>
1 parent 1400e6f commit f193105

File tree

15 files changed

+296
-150
lines changed

15 files changed

+296
-150
lines changed

.changeset/warm-sloths-tickle.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@aws-amplify/backend-function': minor
3+
'@aws-amplify/backend-data': patch
4+
'@aws-amplify/platform-core': minor
5+
---
6+
7+
Update getAmplifyDataClientConfig to work with named data backend

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/backend-data/src/factory.ts

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import { Bucket } from 'aws-cdk-lib/aws-s3';
5252
import { BucketDeployment, Source } from 'aws-cdk-lib/aws-s3-deployment';
5353

5454
const modelIntrospectionSchemaKey = 'modelIntrospectionSchema.json';
55+
const defaultName = 'amplifyData';
5556

5657
/**
5758
* Singleton factory for AmplifyGraphqlApi constructs that can be used in Amplify project files.
@@ -127,7 +128,7 @@ class DataGenerator implements ConstructContainerEntryGenerator {
127128
private readonly getInstanceProps: ConstructFactoryGetInstanceProps,
128129
private readonly outputStorageStrategy: BackendOutputStorageStrategy<GraphqlOutput>
129130
) {
130-
this.name = props.name ?? 'amplifyData';
131+
this.name = props.name ?? defaultName;
131132
}
132133

133134
generateContainerEntry = ({
@@ -307,14 +308,32 @@ class DataGenerator implements ConstructContainerEntryGenerator {
307308

308309
convertJsResolverDefinition(scope, amplifyApi, schemasJsFunctions);
309310

311+
const namePrefix = this.name === defaultName ? '' : defaultName;
312+
313+
const ssmEnvironmentScopeContext = {
314+
[`${namePrefix}${this.name}_GRAPHQL_ENDPOINT`]:
315+
amplifyApi.resources.cfnResources.cfnGraphqlApi.attrGraphQlUrl,
316+
[`${namePrefix}${this.name}_MODEL_INTROSPECTION_SCHEMA_BUCKET_NAME`]:
317+
modelIntrospectionSchemaBucket.bucketName,
318+
[`${namePrefix}${this.name}_MODEL_INTROSPECTION_SCHEMA_KEY`]:
319+
modelIntrospectionSchemaKey,
320+
['AMPLIFY_DATA_DEFAULT_NAME']: `${namePrefix}${this.name}`,
321+
};
322+
323+
const backwardsCompatibleScopeContext =
324+
`${this.name}_GRAPHQL_ENDPOINT` !==
325+
`${namePrefix}${this.name}_GRAPHQL_ENDPOINT`
326+
? {
327+
// @deprecated
328+
[`${this.name}_GRAPHQL_ENDPOINT`]:
329+
amplifyApi.resources.cfnResources.cfnGraphqlApi.attrGraphQlUrl,
330+
}
331+
: {};
332+
310333
const ssmEnvironmentEntries =
311334
ssmEnvironmentEntriesGenerator.generateSsmEnvironmentEntries({
312-
[`${this.name}_GRAPHQL_ENDPOINT`]:
313-
amplifyApi.resources.cfnResources.cfnGraphqlApi.attrGraphQlUrl,
314-
[`${this.name}_MODEL_INTROSPECTION_SCHEMA_BUCKET_NAME`]:
315-
modelIntrospectionSchemaBucket.bucketName,
316-
[`${this.name}_MODEL_INTROSPECTION_SCHEMA_KEY`]:
317-
modelIntrospectionSchemaKey,
335+
...ssmEnvironmentScopeContext,
336+
...backwardsCompatibleScopeContext,
318337
});
319338

320339
const policyGenerator = new AppSyncPolicyGenerator(

packages/backend-function/API.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,12 @@ type DataClientConfig = {
4545

4646
// @public (undocumented)
4747
type DataClientEnv = {
48-
AMPLIFY_DATA_GRAPHQL_ENDPOINT: string;
49-
AMPLIFY_DATA_MODEL_INTROSPECTION_SCHEMA_BUCKET_NAME: string;
50-
AMPLIFY_DATA_MODEL_INTROSPECTION_SCHEMA_KEY: string;
5148
AWS_ACCESS_KEY_ID: string;
5249
AWS_SECRET_ACCESS_KEY: string;
5350
AWS_SESSION_TOKEN: string;
5451
AWS_REGION: string;
55-
};
52+
AMPLIFY_DATA_DEFAULT_NAME: string;
53+
} & Record<string, unknown>;
5654

5755
// @public (undocumented)
5856
type DataClientError = {
@@ -111,7 +109,7 @@ const getAmplifyDataClientConfig: <T>(env: T, s3Client?: S3Client) => Promise<Da
111109

112110
// @public (undocumented)
113111
type InvalidConfig = unknown & {
114-
invalidType: 'This function needs to be granted `authorization((allow) => [allow.resource(fcn)])` on the data schema.';
112+
invalidType: 'Some of the AWS environment variables needed to configure Amplify are missing.';
115113
};
116114

117115
// @public (undocumented)

packages/backend-function/src/runtime/get_amplify_clients_configuration.test.ts

Lines changed: 148 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { NoSuchKey, S3, S3ServiceException } from '@aws-sdk/client-s3';
44

55
import { getAmplifyDataClientConfig } from './get_amplify_clients_configuration.js';
66

7-
const validEnv = {
7+
const validDefaultEnv = {
88
AMPLIFY_DATA_MODEL_INTROSPECTION_SCHEMA_BUCKET_NAME:
99
'TEST_VALUE for AMPLIFY_DATA_MODEL_INTROSPECTION_SCHEMA_BUCKET_NAME',
1010
AMPLIFY_DATA_MODEL_INTROSPECTION_SCHEMA_KEY:
@@ -14,6 +14,21 @@ const validEnv = {
1414
AWS_SESSION_TOKEN: 'TEST_VALUE for AWS_SESSION_TOKEN',
1515
AWS_REGION: 'TEST_VALUE for AWS_REGION',
1616
AMPLIFY_DATA_GRAPHQL_ENDPOINT: 'TEST_VALUE for AMPLIFY_DATA_GRAPHQL_ENDPOINT',
17+
AMPLIFY_DATA_DEFAULT_NAME: 'AmplifyData',
18+
};
19+
20+
const validNamedEnv = {
21+
AMPLIFY_DATA_TEST_NAME_MODEL_INTROSPECTION_SCHEMA_BUCKET_NAME:
22+
'TEST_VALUE for AMPLIFY_DATA_MODEL_INTROSPECTION_SCHEMA_BUCKET_NAME',
23+
AMPLIFY_DATA_TEST_NAME_MODEL_INTROSPECTION_SCHEMA_KEY:
24+
'TEST_VALUE for AMPLIFY_DATA_MODEL_INTROSPECTION_SCHEMA_KEY',
25+
AWS_ACCESS_KEY_ID: 'TEST_VALUE for AWS_ACCESS_KEY_ID',
26+
AWS_SECRET_ACCESS_KEY: 'TEST_VALUE for AWS_SECRET_ACCESS_KEY',
27+
AWS_SESSION_TOKEN: 'TEST_VALUE for AWS_SESSION_TOKEN',
28+
AWS_REGION: 'TEST_VALUE for AWS_REGION',
29+
AMPLIFY_DATA_TEST_NAME_GRAPHQL_ENDPOINT:
30+
'TEST_VALUE for AMPLIFY_DATA_GRAPHQL_ENDPOINT',
31+
AMPLIFY_DATA_DEFAULT_NAME: 'AmplifyDataTestName',
1732
};
1833

1934
let mockS3Client: S3;
@@ -23,110 +38,146 @@ void describe('getAmplifyDataClientConfig', () => {
2338
mockS3Client = new S3();
2439
});
2540

26-
Object.keys(validEnv).forEach((envFieldToExclude) => {
27-
void it(`returns empty config objects when ${envFieldToExclude} is not included`, async () => {
28-
const env = { ...validEnv } as Record<string, string>;
29-
delete env[envFieldToExclude];
30-
assert.deepEqual(await getAmplifyDataClientConfig(env), {
31-
resourceConfig: {},
32-
libraryOptions: {},
41+
[
42+
{
43+
name: 'no set name',
44+
dataBackendName: 'AMPLIFY_DATA',
45+
validEnv: validDefaultEnv,
46+
},
47+
{
48+
name: 'an explicit name',
49+
dataBackendName: 'AMPLIFY_DATA_TEST_NAME',
50+
validEnv: validNamedEnv,
51+
},
52+
].forEach(({ name, dataBackendName, validEnv }) => {
53+
void describe(`env variable with ${name} for the data backend`, () => {
54+
Object.keys(validEnv)
55+
.filter((k) => k !== 'AMPLIFY_DATA_DEFAULT_NAME')
56+
.forEach((envFieldToExclude) => {
57+
if (envFieldToExclude.includes(dataBackendName)) {
58+
void it(`throws error when ${envFieldToExclude} is not included`, async () => {
59+
const env = { ...validEnv } as Record<string, string>;
60+
delete env[envFieldToExclude];
61+
await assert.rejects(
62+
async () => await getAmplifyDataClientConfig(env),
63+
/The data environment variables are malformed/
64+
);
65+
});
66+
67+
void it(`throws error when ${envFieldToExclude} is not a string`, async () => {
68+
const env = { ...validEnv } as Record<string, unknown>;
69+
env[envFieldToExclude] = 123;
70+
await assert.rejects(
71+
async () => await getAmplifyDataClientConfig(env),
72+
/The data environment variables are malformed/
73+
);
74+
});
75+
} else {
76+
void it(`returns empty config objects when ${envFieldToExclude} is not included`, async () => {
77+
const env = { ...validEnv } as Record<string, string>;
78+
delete env[envFieldToExclude];
79+
assert.deepEqual(await getAmplifyDataClientConfig(env), {
80+
resourceConfig: {},
81+
libraryOptions: {},
82+
});
83+
});
84+
85+
void it(`returns empty config objects when ${envFieldToExclude} is not a string`, async () => {
86+
const env = { ...validEnv } as Record<string, unknown>;
87+
env[envFieldToExclude] = 123;
88+
assert.deepEqual(await getAmplifyDataClientConfig(env), {
89+
resourceConfig: {},
90+
libraryOptions: {},
91+
});
92+
});
93+
}
94+
});
95+
96+
void it('raises a custom error message when the model introspection schema is missing from the s3 bucket', async () => {
97+
const s3ClientSendMock = mock.method(mockS3Client, 'send', async () => {
98+
throw new NoSuchKey({ message: 'TEST_ERROR', $metadata: {} });
99+
});
100+
mock.method(mockS3Client, 'send', s3ClientSendMock);
101+
102+
await assert.rejects(
103+
async () => await getAmplifyDataClientConfig(validEnv, mockS3Client),
104+
new Error(
105+
'Error retrieving the schema from S3. Please confirm that your project has a `defineData` included in the `defineBackend` definition.'
106+
)
107+
);
33108
});
34-
});
35109

36-
void it(`returns empty config objects when ${envFieldToExclude} is not a string`, async () => {
37-
const env = { ...validEnv } as Record<string, unknown>;
38-
env[envFieldToExclude] = 123;
39-
assert.deepEqual(await getAmplifyDataClientConfig(env), {
40-
resourceConfig: {},
41-
libraryOptions: {},
110+
void it('raises a custom error message when there is a S3ServiceException error retrieving the model introspection schema from the s3 bucket', async () => {
111+
const s3ClientSendMock = mock.method(mockS3Client, 'send', async () => {
112+
throw new S3ServiceException({
113+
name: 'TEST_ERROR',
114+
message: 'TEST_MESSAGE',
115+
$fault: 'server',
116+
$metadata: {},
117+
});
118+
});
119+
mock.method(mockS3Client, 'send', s3ClientSendMock);
120+
121+
await assert.rejects(
122+
async () => await getAmplifyDataClientConfig(validEnv, mockS3Client),
123+
new Error(
124+
'Error retrieving the schema from S3. You may need to grant this function authorization on the schema. TEST_ERROR: TEST_MESSAGE.'
125+
)
126+
);
42127
});
43-
});
44-
});
45128

46-
void it('raises a custom error message when the model introspection schema is missing from the s3 bucket', async () => {
47-
const s3ClientSendMock = mock.method(mockS3Client, 'send', async () => {
48-
throw new NoSuchKey({ message: 'TEST_ERROR', $metadata: {} });
49-
});
50-
mock.method(mockS3Client, 'send', s3ClientSendMock);
51-
52-
await assert.rejects(
53-
async () => await getAmplifyDataClientConfig(validEnv, mockS3Client),
54-
new Error(
55-
'Error retrieving the schema from S3. Please confirm that your project has a `defineData` included in the `defineBackend` definition.'
56-
)
57-
);
58-
});
129+
void it('re-raises a non-S3 error received when retrieving the model introspection schema from the s3 bucket', async () => {
130+
const s3ClientSendMock = mock.method(mockS3Client, 'send', async () => {
131+
throw new Error('Test Error');
132+
});
133+
mock.method(mockS3Client, 'send', s3ClientSendMock);
59134

60-
void it('raises a custom error message when there is a S3ServiceException error retrieving the model introspection schema from the s3 bucket', async () => {
61-
const s3ClientSendMock = mock.method(mockS3Client, 'send', async () => {
62-
throw new S3ServiceException({
63-
name: 'TEST_ERROR',
64-
message: 'TEST_MESSAGE',
65-
$fault: 'server',
66-
$metadata: {},
135+
await assert.rejects(
136+
async () => await getAmplifyDataClientConfig(validEnv, mockS3Client),
137+
new Error('Test Error')
138+
);
67139
});
68-
});
69-
mock.method(mockS3Client, 'send', s3ClientSendMock);
70-
71-
await assert.rejects(
72-
async () => await getAmplifyDataClientConfig(validEnv, mockS3Client),
73-
new Error(
74-
'Error retrieving the schema from S3. You may need to grant this function authorization on the schema. TEST_ERROR: TEST_MESSAGE.'
75-
)
76-
);
77-
});
78140

79-
void it('re-raises a non-S3 error received when retrieving the model introspection schema from the s3 bucket', async () => {
80-
const s3ClientSendMock = mock.method(mockS3Client, 'send', async () => {
81-
throw new Error('Test Error');
82-
});
83-
mock.method(mockS3Client, 'send', s3ClientSendMock);
84-
85-
await assert.rejects(
86-
async () => await getAmplifyDataClientConfig(validEnv, mockS3Client),
87-
new Error('Test Error')
88-
);
89-
});
90-
91-
void it('returns the expected libraryOptions and resourceConfig values in the happy case', async () => {
92-
const s3ClientSendMock = mock.method(mockS3Client, 'send', () => {
93-
return Promise.resolve({
94-
Body: {
95-
transformToString: () => JSON.stringify({ testSchema: 'TESTING' }),
96-
},
141+
void it('returns the expected libraryOptions and resourceConfig values in the happy case', async () => {
142+
const s3ClientSendMock = mock.method(mockS3Client, 'send', () => {
143+
return Promise.resolve({
144+
Body: {
145+
transformToString: () =>
146+
JSON.stringify({ testSchema: 'TESTING' }),
147+
},
148+
});
149+
});
150+
mock.method(mockS3Client, 'send', s3ClientSendMock);
151+
152+
const { resourceConfig, libraryOptions } =
153+
await getAmplifyDataClientConfig(validEnv, mockS3Client);
154+
155+
assert.deepEqual(
156+
await libraryOptions.Auth.credentialsProvider.getCredentialsAndIdentityId?.(),
157+
{
158+
credentials: {
159+
accessKeyId: 'TEST_VALUE for AWS_ACCESS_KEY_ID',
160+
secretAccessKey: 'TEST_VALUE for AWS_SECRET_ACCESS_KEY',
161+
sessionToken: 'TEST_VALUE for AWS_SESSION_TOKEN',
162+
},
163+
}
164+
);
165+
assert.deepEqual(
166+
await libraryOptions.Auth.credentialsProvider.clearCredentialsAndIdentityId?.(),
167+
undefined
168+
);
169+
170+
assert.deepEqual(resourceConfig, {
171+
API: {
172+
GraphQL: {
173+
endpoint: 'TEST_VALUE for AMPLIFY_DATA_GRAPHQL_ENDPOINT',
174+
region: 'TEST_VALUE for AWS_REGION',
175+
defaultAuthMode: 'iam',
176+
modelIntrospection: { testSchema: 'TESTING' },
177+
},
178+
},
179+
});
97180
});
98181
});
99-
mock.method(mockS3Client, 'send', s3ClientSendMock);
100-
101-
const { resourceConfig, libraryOptions } = await getAmplifyDataClientConfig(
102-
validEnv,
103-
mockS3Client
104-
);
105-
106-
assert.deepEqual(
107-
await libraryOptions.Auth.credentialsProvider.getCredentialsAndIdentityId?.(),
108-
{
109-
credentials: {
110-
accessKeyId: 'TEST_VALUE for AWS_ACCESS_KEY_ID',
111-
secretAccessKey: 'TEST_VALUE for AWS_SECRET_ACCESS_KEY',
112-
sessionToken: 'TEST_VALUE for AWS_SESSION_TOKEN',
113-
},
114-
}
115-
);
116-
assert.deepEqual(
117-
await libraryOptions.Auth.credentialsProvider.clearCredentialsAndIdentityId?.(),
118-
undefined
119-
);
120-
121-
assert.deepEqual(resourceConfig, {
122-
API: {
123-
GraphQL: {
124-
endpoint: 'TEST_VALUE for AMPLIFY_DATA_GRAPHQL_ENDPOINT',
125-
region: 'TEST_VALUE for AWS_REGION',
126-
defaultAuthMode: 'iam',
127-
modelIntrospection: { testSchema: 'TESTING' },
128-
},
129-
},
130-
});
131182
});
132183
});

0 commit comments

Comments
 (0)