Skip to content

Commit 18309ae

Browse files
committed
Merge branch 'main' into stu/fix_naming_issue
2 parents 00f9a31 + 6d3b7db commit 18309ae

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+533
-192
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Changelog
22

3+
## [7.5.0](https://github.com/github-aws-runners/terraform-aws-github-runner/compare/v7.4.1...v7.5.0) (2026-03-11)
4+
5+
6+
### Features
7+
8+
* **lambdas:** add batch SSM parameter fetching to reduce API calls ([#5017](https://github.com/github-aws-runners/terraform-aws-github-runner/issues/5017)) ([24857c2](https://github.com/github-aws-runners/terraform-aws-github-runner/commit/24857c2a0d7d02e38cbd9b4dda2e652973fcf975))
9+
* **logging:** add log_class parameter to runner log files configuration ([#5036](https://github.com/github-aws-runners/terraform-aws-github-runner/issues/5036)) ([3509d4c](https://github.com/github-aws-runners/terraform-aws-github-runner/commit/3509d4c7afaff751715db940403287aa16be3c44))
10+
311
## [7.4.1](https://github.com/github-aws-runners/terraform-aws-github-runner/compare/v7.4.0...v7.4.1) (2026-03-09)
412

513

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ Join our discord community via [this invite link](https://discord.gg/bxgXW8jJGh)
157157
| <a name="input_lambda_security_group_ids"></a> [lambda\_security\_group\_ids](#input\_lambda\_security\_group\_ids) | List of security group IDs associated with the Lambda function. | `list(string)` | `[]` | no |
158158
| <a name="input_lambda_subnet_ids"></a> [lambda\_subnet\_ids](#input\_lambda\_subnet\_ids) | List of subnets in which the action runners will be launched, the subnets needs to be subnets in the `vpc_id`. | `list(string)` | `[]` | no |
159159
| <a name="input_lambda_tags"></a> [lambda\_tags](#input\_lambda\_tags) | Map of tags that will be added to all the lambda function resources. Note these are additional tags to the default tags. | `map(string)` | `{}` | no |
160+
| <a name="input_log_class"></a> [log\_class](#input\_log\_class) | The log class of the CloudWatch log groups. Valid values are `STANDARD` or `INFREQUENT_ACCESS`. | `string` | `"STANDARD"` | no |
160161
| <a name="input_log_level"></a> [log\_level](#input\_log\_level) | Logging level for lambda logging. Valid values are 'silly', 'trace', 'debug', 'info', 'warn', 'error', 'fatal'. | `string` | `"info"` | no |
161162
| <a name="input_logging_kms_key_id"></a> [logging\_kms\_key\_id](#input\_logging\_kms\_key\_id) | Specifies the kms key id to encrypt the logs with. | `string` | `null` | no |
162163
| <a name="input_logging_retention_in_days"></a> [logging\_retention\_in\_days](#input\_logging\_retention\_in\_days) | Specifies the number of days you want to retain log events for the lambda log group. Possible values are: 0, 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, and 3653. | `number` | `180` | no |
@@ -197,7 +198,7 @@ Join our discord community via [this invite link](https://discord.gg/bxgXW8jJGh)
197198
| <a name="input_runner_hook_job_completed"></a> [runner\_hook\_job\_completed](#input\_runner\_hook\_job\_completed) | Script to be ran in the runner environment at the end of every job | `string` | `""` | no |
198199
| <a name="input_runner_hook_job_started"></a> [runner\_hook\_job\_started](#input\_runner\_hook\_job\_started) | Script to be ran in the runner environment at the beginning of every job | `string` | `""` | no |
199200
| <a name="input_runner_iam_role_managed_policy_arns"></a> [runner\_iam\_role\_managed\_policy\_arns](#input\_runner\_iam\_role\_managed\_policy\_arns) | Attach AWS or customer-managed IAM policies (by ARN) to the runner IAM role | `list(string)` | `[]` | no |
200-
| <a name="input_runner_log_files"></a> [runner\_log\_files](#input\_runner\_log\_files) | (optional) Replaces the module default cloudwatch log config. See https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-Agent-Configuration-File-Details.html for details. | <pre>list(object({<br/> log_group_name = string<br/> prefix_log_group = bool<br/> file_path = string<br/> log_stream_name = string<br/> }))</pre> | `null` | no |
201+
| <a name="input_runner_log_files"></a> [runner\_log\_files](#input\_runner\_log\_files) | (optional) List of logfiles to send to CloudWatch, will only be used if `enable_cloudwatch_agent` is set to true. Object description: `log_group_name`: Name of the log group, `prefix_log_group`: If true, the log group name will be prefixed with `/github-self-hosted-runners/<var.prefix>`, `file_path`: path to the log file, `log_stream_name`: name of the log stream, `log_class`: The log class of the log group. Valid values are `STANDARD` or `INFREQUENT_ACCESS`. Defaults to `STANDARD`. | <pre>list(object({<br/> log_group_name = string<br/> prefix_log_group = bool<br/> file_path = string<br/> log_stream_name = string<br/> log_class = optional(string, "STANDARD")<br/> }))</pre> | `null` | no |
201202
| <a name="input_runner_metadata_options"></a> [runner\_metadata\_options](#input\_runner\_metadata\_options) | Metadata options for the ec2 runner instances. By default, the module uses metadata tags for bootstrapping the runner, only disable `instance_metadata_tags` when using custom scripts for starting the runner. | `map(any)` | <pre>{<br/> "http_endpoint": "enabled",<br/> "http_put_response_hop_limit": 1,<br/> "http_tokens": "required",<br/> "instance_metadata_tags": "enabled"<br/>}</pre> | no |
202203
| <a name="input_runner_name_prefix"></a> [runner\_name\_prefix](#input\_runner\_name\_prefix) | The prefix used for the GitHub runner name. The prefix will be used in the default start script to prefix the instance name when register the runner in GitHub. The value is available via an EC2 tag 'ghr:runner\_name\_prefix'. | `string` | `""` | no |
203204
| <a name="input_runner_os"></a> [runner\_os](#input\_runner\_os) | The EC2 Operating System type to use for action runner instances (linux,windows). | `string` | `"linux"` | no |

examples/multi-runner/main.tf

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@ module "runners" {
139139
# Enable debug logging for the lambda functions
140140
# log_level = "debug"
141141

142+
# Set log class to INFREQUENT_ACCESS for cost savings
143+
log_class = "STANDARD"
144+
142145
# Enable to track the spot instance termination warning
143146
# instance_termination_watcher = {
144147
# enable = true

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

Lines changed: 28 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,17 @@ import {
77
EC2Client,
88
Image,
99
} from '@aws-sdk/client-ec2';
10-
import {
11-
DescribeParametersCommand,
12-
DescribeParametersCommandOutput,
13-
GetParameterCommand,
14-
SSMClient,
15-
} from '@aws-sdk/client-ssm';
10+
import { DescribeParametersCommand, DescribeParametersCommandOutput, SSMClient } from '@aws-sdk/client-ssm';
11+
import { getParameters } from '@aws-github-runner/aws-ssm-util';
1612
import { mockClient } from 'aws-sdk-client-mock';
1713
import 'aws-sdk-client-mock-jest/vitest';
1814

1915
import { AmiCleanupOptions, amiCleanup, defaultAmiCleanupOptions } from './ami';
2016
import { describe, it, expect, beforeEach, vi } from 'vitest';
2117
import { fail } from 'assert';
2218

19+
vi.mock('@aws-github-runner/aws-ssm-util');
20+
2321
process.env.AWS_REGION = 'eu-east-1';
2422
const deleteAmisOlderThenDays = 30;
2523
const date31DaysAgo = new Date(new Date().setDate(new Date().getDate() - (deleteAmisOlderThenDays + 1)));
@@ -83,22 +81,12 @@ describe("delete AMI's", () => {
8381
mockSSMClient.reset();
8482

8583
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-
},
101-
});
84+
vi.mocked(getParameters).mockResolvedValue(
85+
new Map([
86+
['ami-id/ami-ssm0001', 'ami-ssm0001'],
87+
['ami-id/ami-ssm0002', 'ami-ssm0002'],
88+
]),
89+
);
10290

10391
mockEC2Client.on(DescribeLaunchTemplatesCommand).resolves({
10492
LaunchTemplates: [
@@ -143,13 +131,7 @@ describe("delete AMI's", () => {
143131
expect(mockEC2Client).toHaveReceivedCommand(DescribeLaunchTemplatesCommand);
144132
expect(mockEC2Client).toHaveReceivedCommand(DescribeLaunchTemplateVersionsCommand);
145133
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',
152-
});
134+
expect(getParameters).toHaveBeenCalledWith(['ami-id/ami-ssm0001', 'ami-id/ami-ssm0002']);
153135
});
154136

155137
it('should NOT delete instances in use.', async () => {
@@ -485,14 +467,7 @@ describe("delete AMI's", () => {
485467
],
486468
});
487469

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-
},
495-
});
470+
vi.mocked(getParameters).mockResolvedValue(new Map([['/github-runner/config/ami_id', 'ami-underscore0001']]));
496471

497472
await amiCleanup({
498473
minimumDaysOld: 0,
@@ -501,9 +476,7 @@ describe("delete AMI's", () => {
501476

502477
// AMI should not be deleted because it's referenced in SSM
503478
expect(mockEC2Client).not.toHaveReceivedCommand(DeregisterImageCommand);
504-
expect(mockSSMClient).toHaveReceivedCommandWith(GetParameterCommand, {
505-
Name: '/github-runner/config/ami_id',
506-
});
479+
expect(getParameters).toHaveBeenCalledWith(['/github-runner/config/ami_id']);
507480
expect(mockSSMClient).not.toHaveReceivedCommand(DescribeParametersCommand);
508481
});
509482

@@ -518,14 +491,7 @@ describe("delete AMI's", () => {
518491
],
519492
});
520493

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-
},
528-
});
494+
vi.mocked(getParameters).mockResolvedValue(new Map([['/github-runner/config/ami-id', 'ami-hyphen0001']]));
529495

530496
await amiCleanup({
531497
minimumDaysOld: 0,
@@ -534,9 +500,7 @@ describe("delete AMI's", () => {
534500

535501
// AMI should not be deleted because it's referenced in SSM
536502
expect(mockEC2Client).not.toHaveReceivedCommand(DeregisterImageCommand);
537-
expect(mockSSMClient).toHaveReceivedCommandWith(GetParameterCommand, {
538-
Name: '/github-runner/config/ami-id',
539-
});
503+
expect(getParameters).toHaveBeenCalledWith(['/github-runner/config/ami-id']);
540504
expect(mockSSMClient).not.toHaveReceivedCommand(DescribeParametersCommand);
541505
});
542506

@@ -561,14 +525,7 @@ describe("delete AMI's", () => {
561525
],
562526
});
563527

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-
});
528+
vi.mocked(getParameters).mockResolvedValue(new Map([['/some/path/ami-id', 'ami-wildcard0001']]));
572529

573530
await amiCleanup({
574531
minimumDaysOld: 0,
@@ -580,9 +537,7 @@ describe("delete AMI's", () => {
580537
expect(mockSSMClient).toHaveReceivedCommandWith(DescribeParametersCommand, {
581538
ParameterFilters: [{ Key: 'Name', Option: 'Contains', Values: ['ami-id'] }],
582539
});
583-
expect(mockSSMClient).toHaveReceivedCommandWith(GetParameterCommand, {
584-
Name: '/some/path/ami-id',
585-
});
540+
expect(getParameters).toHaveBeenCalledWith(['/some/path/ami-id']);
586541
});
587542

588543
it('handles wildcard SSM parameter patterns (*ami_id)', async () => {
@@ -606,14 +561,9 @@ describe("delete AMI's", () => {
606561
],
607562
});
608563

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-
},
616-
});
564+
vi.mocked(getParameters).mockResolvedValue(
565+
new Map([['/github-runner/config/ami_id', 'ami-wildcard-underscore0001']]),
566+
);
617567

618568
await amiCleanup({
619569
minimumDaysOld: 0,
@@ -625,9 +575,7 @@ describe("delete AMI's", () => {
625575
expect(mockSSMClient).toHaveReceivedCommandWith(DescribeParametersCommand, {
626576
ParameterFilters: [{ Key: 'Name', Option: 'Contains', Values: ['ami_id'] }],
627577
});
628-
expect(mockSSMClient).toHaveReceivedCommandWith(GetParameterCommand, {
629-
Name: '/github-runner/config/ami_id',
630-
});
578+
expect(getParameters).toHaveBeenCalledWith(['/github-runner/config/ami_id']);
631579
});
632580

633581
it('handles mixed explicit names and wildcard patterns', async () => {
@@ -649,14 +597,9 @@ describe("delete AMI's", () => {
649597
],
650598
});
651599

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-
},
659-
});
600+
vi.mocked(getParameters)
601+
.mockResolvedValueOnce(new Map([['/explicit/ami_id', 'ami-explicit0001']]))
602+
.mockResolvedValueOnce(new Map([['/discovered/ami-id', 'ami-wildcard0001']]));
660603

661604
mockSSMClient.on(DescribeParametersCommand).resolves({
662605
Parameters: [
@@ -668,15 +611,6 @@ describe("delete AMI's", () => {
668611
],
669612
});
670613

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-
},
678-
});
679-
680614
await amiCleanup({
681615
minimumDaysOld: 0,
682616
ssmParameterNames: ['/explicit/ami_id', '*ami-id'],
@@ -688,15 +622,11 @@ describe("delete AMI's", () => {
688622
ImageId: 'ami-unused0001',
689623
});
690624

691-
expect(mockSSMClient).toHaveReceivedCommandWith(GetParameterCommand, {
692-
Name: '/explicit/ami_id',
693-
});
625+
expect(getParameters).toHaveBeenCalledWith(['/explicit/ami_id']);
694626
expect(mockSSMClient).toHaveReceivedCommandWith(DescribeParametersCommand, {
695627
ParameterFilters: [{ Key: 'Name', Option: 'Contains', Values: ['ami-id'] }],
696628
});
697-
expect(mockSSMClient).toHaveReceivedCommandWith(GetParameterCommand, {
698-
Name: '/discovered/ami-id',
699-
});
629+
expect(getParameters).toHaveBeenCalledWith(['/discovered/ami-id']);
700630
});
701631

702632
it('handles SSM parameter fetch failures gracefully', async () => {
@@ -710,7 +640,7 @@ describe("delete AMI's", () => {
710640
],
711641
});
712642

713-
mockSSMClient.on(GetParameterCommand, { Name: '/nonexistent/ami_id' }).rejects(new Error('ParameterNotFound'));
643+
vi.mocked(getParameters).mockRejectedValue(new Error('ParameterNotFound'));
714644

715645
// Should not throw and should delete the AMI since SSM reference failed
716646
await amiCleanup({
@@ -768,7 +698,7 @@ describe("delete AMI's", () => {
768698
ImageId: 'ami-no-ssm0001',
769699
});
770700
expect(mockSSMClient).not.toHaveReceivedCommand(DescribeParametersCommand);
771-
expect(mockSSMClient).not.toHaveReceivedCommand(GetParameterCommand);
701+
expect(getParameters).not.toHaveBeenCalled();
772702
});
773703
});
774704
});

0 commit comments

Comments
 (0)