feat(lambdas): add batch SSM parameter fetching to reduce API calls#5017
feat(lambdas): add batch SSM parameter fetching to reduce API calls#5017thomasnemer wants to merge 4 commits intogithub-aws-runners:mainfrom
Conversation
Add getParameters() function to aws-ssm-util that fetches multiple SSM parameters in a single API call with automatic chunking (max 10 per call per AWS API limits). Apply batch fetching to: - auth.ts: fetch App ID and Private Key in one call (2 calls → 1) - ConfigLoader.ts: fetch multiple matcher config paths in one call - ami.ts: batch resolve SSM parameter values for AMI lookups Also remove redundant appId SSM fetch in scale-up.ts that was only used for logging.
There was a problem hiding this comment.
Pull request overview
This PR optimizes AWS SSM API usage by implementing batch parameter fetching to reduce the number of API calls. The changes introduce a new getParameters() function that retrieves multiple SSM parameters in a single API call (with automatic chunking for AWS's 10-parameter limit) and applies this optimization across multiple Lambda functions.
Changes:
- Added
getParameters()function to aws-ssm-util for batch SSM parameter fetching with automatic chunking - Replaced sequential SSM calls with batch fetching in authentication, configuration loading, and AMI housekeeping
- Removed redundant appId SSM fetch in scale-up.ts that was only used for logging
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| lambdas/libs/aws-ssm-util/src/index.ts | Implements new getParameters() function for batch SSM parameter retrieval |
| lambdas/functions/webhook/src/ConfigLoader.ts | Replaces sequential parameter fetching with batch call for matcher configs |
| lambdas/functions/webhook/src/ConfigLoader.test.ts | Updates tests to mock getParameters() instead of individual getParameter() calls |
| lambdas/functions/control-plane/src/scale-runners/scale-up.ts | Removes redundant appId SSM fetch used only for logging |
| lambdas/functions/control-plane/src/github/auth.ts | Batches App ID and Private Key fetching into single SSM call |
| lambdas/functions/control-plane/src/github/auth.test.ts | Updates tests to verify batch parameter fetching behavior |
| lambdas/functions/ami-housekeeper/src/ami.ts | Refactors to use batch parameter fetching for AMI SSM lookups |
| lambdas/functions/ami-housekeeper/src/ami.test.ts | Updates tests to use GetParametersCommand instead of GetParameterCommand |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.
Comments suppressed due to low confidence (1)
lambdas/functions/webhook/src/ConfigLoader.test.ts:203
- There are no tests verifying the behavior when one or more parameters are missing from the
getParameters()response. The code at lines 110-117 handles this by adding an error toconfigLoadingErrors, but this path is not tested. Add a test case wheregetParameters()returns a Map with only some of the requested parameters to verify that appropriate error messages are added for the missing ones.
it('should load config successfully from multiple paths', async () => {
process.env.PARAMETER_RUNNER_MATCHER_CONFIG_PATH = '/path/to/matcher/config-1:/path/to/matcher/config-2';
process.env.PARAMETER_GITHUB_APP_WEBHOOK_SECRET = '/path/to/webhook/secret';
const partialMatcher1 =
'[{"id":"1","arn":"arn:aws:sqs:queue1","matcherConfig":{"labelMatchers":[["a"]],"exactMatch":true}}';
const partialMatcher2 =
',{"id":"2","arn":"arn:aws:sqs:queue2","matcherConfig":{"labelMatchers":[["b"]],"exactMatch":true}}]';
const combinedMatcherConfig = [
{ id: '1', arn: 'arn:aws:sqs:queue1', matcherConfig: { labelMatchers: [['a']], exactMatch: true } },
{ id: '2', arn: 'arn:aws:sqs:queue2', matcherConfig: { labelMatchers: [['b']], exactMatch: true } },
];
// Mock getParameters for batch fetching multiple paths
vi.mocked(getParameters).mockResolvedValue(
new Map([
['/path/to/matcher/config-1', partialMatcher1],
['/path/to/matcher/config-2', partialMatcher2],
]),
);
vi.mocked(getParameter).mockImplementation(async (paramPath: string) => {
if (paramPath === '/path/to/webhook/secret') return 'secret';
return '';
});
const config: ConfigWebhook = await ConfigWebhook.load();
expect(config.matcherConfig).toEqual(combinedMatcherConfig);
expect(config.webhookSecret).toBe('secret');
});
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const response = await ssmClient.send( | ||
| new GetParametersCommand({ | ||
| Names: chunk, | ||
| WithDecryption: true, | ||
| }), | ||
| ); |
There was a problem hiding this comment.
The function doesn't handle errors from individual chunk API calls. If one GetParametersCommand fails, the error will propagate and prevent the function from fetching subsequent chunks. This means that if you request 15 parameters and the first chunk of 10 fails, you won't get the remaining 5 parameters even if they would have succeeded.
Consider implementing error handling per chunk to continue fetching other chunks even if one fails, similar to how the ami.ts implementation handles this (lines 226-232 in the diff).
There was a problem hiding this comment.
I don't think continuing processing other chunks after an error is the proper way to go here. If we can't fetch some of the SSM parameters, it's probably better to stop than to provide incomplete results.
| export async function getParameters(parameter_names: string[]): Promise<Map<string, string>> { | ||
| if (parameter_names.length === 0) { | ||
| return new Map(); | ||
| } | ||
|
|
||
| const ssmClient = getTracedAWSV3Client(new SSMClient({ region: process.env.AWS_REGION })); | ||
| const result = new Map<string, string>(); | ||
|
|
||
| // AWS SSM GetParameters API has a limit of 10 parameters per call | ||
| const chunkSize = 10; | ||
| for (let i = 0; i < parameter_names.length; i += chunkSize) { | ||
| const chunk = parameter_names.slice(i, i + chunkSize); | ||
| const response = await ssmClient.send( | ||
| new GetParametersCommand({ | ||
| Names: chunk, | ||
| WithDecryption: true, | ||
| }), | ||
| ); | ||
|
|
||
| for (const param of response.Parameters ?? []) { | ||
| if (param.Name && param.Value) { | ||
| result.set(param.Name, param.Value); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return result; | ||
| } |
There was a problem hiding this comment.
The new getParameters() function lacks test coverage. The vitest configuration requires 100% test coverage for all code in lambdas/libs/, but no tests were added for this new function. Tests should cover:
- Happy path with multiple parameters
- Empty parameter list
- Chunking behavior when more than 10 parameters are provided
- Error handling when SSM API calls fail
- Handling of missing parameters (InvalidParameters in response)
- Behavior when some parameters exist and others don't
| for (const param of response.Parameters ?? []) { | ||
| if (param.Name && param.Value) { | ||
| result.set(param.Name, param.Value); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
The getParameters() function silently ignores parameters that don't have both Name and Value. If a parameter exists but has no value, it will be missing from the returned Map without any error or warning. This differs from the getParameter() function which explicitly throws an error when a parameter is not found (line 15). Consider either:
- Throwing an error if any requested parameters are not found in the response
- Checking the InvalidParameters field in the response to identify missing parameters
- At minimum, documenting this behavior in the function's JSDoc comment
This inconsistency could lead to subtle bugs where callers expect parameters to be present but receive an empty Map entry instead.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
|
@thomasnemer this is great! Thanks a lot for your contribution. Thank you! |
This PR intends to reduce SSM AWS API calls by doing the following:
Add
getParameters()function to aws-ssm-util that fetches multiple SSM parameters in a single API call with automatic chunking (max 10 per call per AWS API limits).Apply batch fetching to:
Also remove redundant appId SSM fetch in scale-up.ts that was only used for logging.