Skip to content

Commit 9138518

Browse files
committed
feat(lambdas): add batch SSM parameter fetching to reduce API calls
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.
1 parent c638e38 commit 9138518

File tree

8 files changed

+289
-143
lines changed

8 files changed

+289
-143
lines changed

lambdas/functions/ami-housekeeper/src/ami.test.ts

Lines changed: 101 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
import {
1111
DescribeParametersCommand,
1212
DescribeParametersCommandOutput,
13-
GetParameterCommand,
13+
GetParametersCommand,
1414
SSMClient,
1515
} from '@aws-sdk/client-ssm';
1616
import { mockClient } from 'aws-sdk-client-mock';
@@ -83,21 +83,22 @@ describe("delete AMI's", () => {
8383
mockSSMClient.reset();
8484

8585
mockSSMClient.on(DescribeParametersCommand).resolves(ssmParameters);
86-
mockSSMClient.on(GetParameterCommand, { Name: 'ami-id/ami-ssm0001' }).resolves({
87-
Parameter: {
88-
Name: 'ami-id/ami-ssm0001',
89-
Type: 'String',
90-
Value: 'ami-ssm0001',
91-
Version: 1,
92-
},
93-
});
94-
mockSSMClient.on(GetParameterCommand, { Name: 'ami-id/ami-ssm0002' }).resolves({
95-
Parameter: {
96-
Name: 'ami-id/ami-ssm0002',
97-
Type: 'String',
98-
Value: 'ami-ssm0002',
99-
Version: 1,
100-
},
86+
mockSSMClient.on(GetParametersCommand).resolves({
87+
Parameters: [
88+
{
89+
Name: 'ami-id/ami-ssm0001',
90+
Type: 'String',
91+
Value: 'ami-ssm0001',
92+
Version: 1,
93+
},
94+
{
95+
Name: 'ami-id/ami-ssm0002',
96+
Type: 'String',
97+
Value: 'ami-ssm0002',
98+
Version: 1,
99+
},
100+
],
101+
InvalidParameters: [],
101102
});
102103

103104
mockEC2Client.on(DescribeLaunchTemplatesCommand).resolves({
@@ -143,12 +144,10 @@ describe("delete AMI's", () => {
143144
expect(mockEC2Client).toHaveReceivedCommand(DescribeLaunchTemplatesCommand);
144145
expect(mockEC2Client).toHaveReceivedCommand(DescribeLaunchTemplateVersionsCommand);
145146
expect(mockSSMClient).toHaveReceivedCommand(DescribeParametersCommand);
146-
expect(mockSSMClient).toHaveReceivedCommandTimes(GetParameterCommand, 2);
147-
expect(mockSSMClient).toHaveReceivedCommandWith(GetParameterCommand, {
148-
Name: 'ami-id/ami-ssm0001',
149-
});
150-
expect(mockSSMClient).toHaveReceivedCommandWith(GetParameterCommand, {
151-
Name: 'ami-id/ami-ssm0002',
147+
expect(mockSSMClient).toHaveReceivedCommandTimes(GetParametersCommand, 1);
148+
expect(mockSSMClient).toHaveReceivedCommandWith(GetParametersCommand, {
149+
Names: ['ami-id/ami-ssm0001', 'ami-id/ami-ssm0002'],
150+
WithDecryption: true,
152151
});
153152
});
154153

@@ -485,13 +484,16 @@ describe("delete AMI's", () => {
485484
],
486485
});
487486

488-
mockSSMClient.on(GetParameterCommand, { Name: '/github-runner/config/ami_id' }).resolves({
489-
Parameter: {
490-
Name: '/github-runner/config/ami_id',
491-
Type: 'String',
492-
Value: 'ami-underscore0001',
493-
Version: 1,
494-
},
487+
mockSSMClient.on(GetParametersCommand).resolves({
488+
Parameters: [
489+
{
490+
Name: '/github-runner/config/ami_id',
491+
Type: 'String',
492+
Value: 'ami-underscore0001',
493+
Version: 1,
494+
},
495+
],
496+
InvalidParameters: [],
495497
});
496498

497499
await amiCleanup({
@@ -501,8 +503,9 @@ describe("delete AMI's", () => {
501503

502504
// AMI should not be deleted because it's referenced in SSM
503505
expect(mockEC2Client).not.toHaveReceivedCommand(DeregisterImageCommand);
504-
expect(mockSSMClient).toHaveReceivedCommandWith(GetParameterCommand, {
505-
Name: '/github-runner/config/ami_id',
506+
expect(mockSSMClient).toHaveReceivedCommandWith(GetParametersCommand, {
507+
Names: ['/github-runner/config/ami_id'],
508+
WithDecryption: true,
506509
});
507510
expect(mockSSMClient).not.toHaveReceivedCommand(DescribeParametersCommand);
508511
});
@@ -518,13 +521,16 @@ describe("delete AMI's", () => {
518521
],
519522
});
520523

521-
mockSSMClient.on(GetParameterCommand, { Name: '/github-runner/config/ami-id' }).resolves({
522-
Parameter: {
523-
Name: '/github-runner/config/ami-id',
524-
Type: 'String',
525-
Value: 'ami-hyphen0001',
526-
Version: 1,
527-
},
524+
mockSSMClient.on(GetParametersCommand).resolves({
525+
Parameters: [
526+
{
527+
Name: '/github-runner/config/ami-id',
528+
Type: 'String',
529+
Value: 'ami-hyphen0001',
530+
Version: 1,
531+
},
532+
],
533+
InvalidParameters: [],
528534
});
529535

530536
await amiCleanup({
@@ -534,8 +540,9 @@ describe("delete AMI's", () => {
534540

535541
// AMI should not be deleted because it's referenced in SSM
536542
expect(mockEC2Client).not.toHaveReceivedCommand(DeregisterImageCommand);
537-
expect(mockSSMClient).toHaveReceivedCommandWith(GetParameterCommand, {
538-
Name: '/github-runner/config/ami-id',
543+
expect(mockSSMClient).toHaveReceivedCommandWith(GetParametersCommand, {
544+
Names: ['/github-runner/config/ami-id'],
545+
WithDecryption: true,
539546
});
540547
expect(mockSSMClient).not.toHaveReceivedCommand(DescribeParametersCommand);
541548
});
@@ -561,13 +568,16 @@ describe("delete AMI's", () => {
561568
],
562569
});
563570

564-
mockSSMClient.on(GetParameterCommand, { Name: '/some/path/ami-id' }).resolves({
565-
Parameter: {
566-
Name: '/some/path/ami-id',
567-
Type: 'String',
568-
Value: 'ami-wildcard0001',
569-
Version: 1,
570-
},
571+
mockSSMClient.on(GetParametersCommand).resolves({
572+
Parameters: [
573+
{
574+
Name: '/some/path/ami-id',
575+
Type: 'String',
576+
Value: 'ami-wildcard0001',
577+
Version: 1,
578+
},
579+
],
580+
InvalidParameters: [],
571581
});
572582

573583
await amiCleanup({
@@ -580,8 +590,9 @@ describe("delete AMI's", () => {
580590
expect(mockSSMClient).toHaveReceivedCommandWith(DescribeParametersCommand, {
581591
ParameterFilters: [{ Key: 'Name', Option: 'Contains', Values: ['ami-id'] }],
582592
});
583-
expect(mockSSMClient).toHaveReceivedCommandWith(GetParameterCommand, {
584-
Name: '/some/path/ami-id',
593+
expect(mockSSMClient).toHaveReceivedCommandWith(GetParametersCommand, {
594+
Names: ['/some/path/ami-id'],
595+
WithDecryption: true,
585596
});
586597
});
587598

@@ -606,13 +617,16 @@ describe("delete AMI's", () => {
606617
],
607618
});
608619

609-
mockSSMClient.on(GetParameterCommand, { Name: '/github-runner/config/ami_id' }).resolves({
610-
Parameter: {
611-
Name: '/github-runner/config/ami_id',
612-
Type: 'String',
613-
Value: 'ami-wildcard-underscore0001',
614-
Version: 1,
615-
},
620+
mockSSMClient.on(GetParametersCommand).resolves({
621+
Parameters: [
622+
{
623+
Name: '/github-runner/config/ami_id',
624+
Type: 'String',
625+
Value: 'ami-wildcard-underscore0001',
626+
Version: 1,
627+
},
628+
],
629+
InvalidParameters: [],
616630
});
617631

618632
await amiCleanup({
@@ -625,8 +639,9 @@ describe("delete AMI's", () => {
625639
expect(mockSSMClient).toHaveReceivedCommandWith(DescribeParametersCommand, {
626640
ParameterFilters: [{ Key: 'Name', Option: 'Contains', Values: ['ami_id'] }],
627641
});
628-
expect(mockSSMClient).toHaveReceivedCommandWith(GetParameterCommand, {
629-
Name: '/github-runner/config/ami_id',
642+
expect(mockSSMClient).toHaveReceivedCommandWith(GetParametersCommand, {
643+
Names: ['/github-runner/config/ami_id'],
644+
WithDecryption: true,
630645
});
631646
});
632647

@@ -649,13 +664,16 @@ describe("delete AMI's", () => {
649664
],
650665
});
651666

652-
mockSSMClient.on(GetParameterCommand, { Name: '/explicit/ami_id' }).resolves({
653-
Parameter: {
654-
Name: '/explicit/ami_id',
655-
Type: 'String',
656-
Value: 'ami-explicit0001',
657-
Version: 1,
658-
},
667+
mockSSMClient.on(GetParametersCommand, { Names: ['/explicit/ami_id'], WithDecryption: true }).resolves({
668+
Parameters: [
669+
{
670+
Name: '/explicit/ami_id',
671+
Type: 'String',
672+
Value: 'ami-explicit0001',
673+
Version: 1,
674+
},
675+
],
676+
InvalidParameters: [],
659677
});
660678

661679
mockSSMClient.on(DescribeParametersCommand).resolves({
@@ -668,13 +686,16 @@ describe("delete AMI's", () => {
668686
],
669687
});
670688

671-
mockSSMClient.on(GetParameterCommand, { Name: '/discovered/ami-id' }).resolves({
672-
Parameter: {
673-
Name: '/discovered/ami-id',
674-
Type: 'String',
675-
Value: 'ami-wildcard0001',
676-
Version: 1,
677-
},
689+
mockSSMClient.on(GetParametersCommand, { Names: ['/discovered/ami-id'], WithDecryption: true }).resolves({
690+
Parameters: [
691+
{
692+
Name: '/discovered/ami-id',
693+
Type: 'String',
694+
Value: 'ami-wildcard0001',
695+
Version: 1,
696+
},
697+
],
698+
InvalidParameters: [],
678699
});
679700

680701
await amiCleanup({
@@ -688,14 +709,16 @@ describe("delete AMI's", () => {
688709
ImageId: 'ami-unused0001',
689710
});
690711

691-
expect(mockSSMClient).toHaveReceivedCommandWith(GetParameterCommand, {
692-
Name: '/explicit/ami_id',
712+
expect(mockSSMClient).toHaveReceivedCommandWith(GetParametersCommand, {
713+
Names: ['/explicit/ami_id'],
714+
WithDecryption: true,
693715
});
694716
expect(mockSSMClient).toHaveReceivedCommandWith(DescribeParametersCommand, {
695717
ParameterFilters: [{ Key: 'Name', Option: 'Contains', Values: ['ami-id'] }],
696718
});
697-
expect(mockSSMClient).toHaveReceivedCommandWith(GetParameterCommand, {
698-
Name: '/discovered/ami-id',
719+
expect(mockSSMClient).toHaveReceivedCommandWith(GetParametersCommand, {
720+
Names: ['/discovered/ami-id'],
721+
WithDecryption: true,
699722
});
700723
});
701724

@@ -710,7 +733,7 @@ describe("delete AMI's", () => {
710733
],
711734
});
712735

713-
mockSSMClient.on(GetParameterCommand, { Name: '/nonexistent/ami_id' }).rejects(new Error('ParameterNotFound'));
736+
mockSSMClient.on(GetParametersCommand).rejects(new Error('ParameterNotFound'));
714737

715738
// Should not throw and should delete the AMI since SSM reference failed
716739
await amiCleanup({
@@ -768,7 +791,7 @@ describe("delete AMI's", () => {
768791
ImageId: 'ami-no-ssm0001',
769792
});
770793
expect(mockSSMClient).not.toHaveReceivedCommand(DescribeParametersCommand);
771-
expect(mockSSMClient).not.toHaveReceivedCommand(GetParameterCommand);
794+
expect(mockSSMClient).not.toHaveReceivedCommand(GetParametersCommand);
772795
});
773796
});
774797
});

0 commit comments

Comments
 (0)