Skip to content

feat(lambdas): add batch SSM parameter fetching to reduce API calls#5017

Open
thomasnemer wants to merge 4 commits intogithub-aws-runners:mainfrom
thomasnemer:batch-ssm-parameter-get
Open

feat(lambdas): add batch SSM parameter fetching to reduce API calls#5017
thomasnemer wants to merge 4 commits intogithub-aws-runners:mainfrom
thomasnemer:batch-ssm-parameter-get

Conversation

@thomasnemer
Copy link

@thomasnemer thomasnemer commented Feb 4, 2026

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:

  • 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.

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.
@thomasnemer thomasnemer requested a review from a team as a code owner February 4, 2026 13:03
@Brend-Smits Brend-Smits requested a review from Copilot February 5, 2026 12:33
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 to configLoadingErrors, but this path is not tested. Add a test case where getParameters() 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.

Comment on lines +32 to +37
const response = await ssmClient.send(
new GetParametersCommand({
Names: chunk,
WithDecryption: true,
}),
);
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +20 to +47
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;
}
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copilot uses AI. Check for mistakes.
Comment on lines +39 to +44
for (const param of response.Parameters ?? []) {
if (param.Name && param.Value) {
result.set(param.Name, param.Value);
}
}
}
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. Throwing an error if any requested parameters are not found in the response
  2. Checking the InvalidParameters field in the response to identify missing parameters
  3. 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.

Copilot uses AI. Check for mistakes.
Brend-Smits and others added 2 commits March 3, 2026 17:08
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@Brend-Smits
Copy link
Contributor

@thomasnemer this is great! Thanks a lot for your contribution.
Would you mind providing a quick test plan that maintainers can refer to in order to swiftly test your changes?
Also, please have a look at the remaining copilot feedback, and if not relevant or if you disagree, comment and resolve it.

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants