Skip to content

Commit fae86ea

Browse files
committed
feat(terraform): add ami-updater module for automated ami updates
- create new terraform module for ami updater lambda function - add comprehensive documentation in readme - implement all required resources including lambda, iam, eventbridge, and ssm - add variables for flexible configuration - include outputs for module integration
1 parent 0999ea5 commit fae86ea

File tree

6 files changed

+434
-0
lines changed

6 files changed

+434
-0
lines changed

modules/ami-updater/README.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# AMI Updater Module
2+
3+
This module creates a Lambda function that automatically updates an SSM parameter with the latest AMI ID based on specified filters. The function runs on a schedule and can be configured to run in dry-run mode.
4+
5+
## Features
6+
7+
- Automatically finds the latest AMI based on configurable filters
8+
- Updates SSM parameter with the latest AMI ID
9+
- Supports dry-run mode for testing
10+
- Configurable schedule via EventBridge
11+
- Comprehensive logging and metrics using AWS Lambda Powertools
12+
- Optional VPC configuration
13+
- Optional X-Ray tracing
14+
15+
## Usage
16+
17+
```hcl
18+
module "ami_updater" {
19+
source = "./modules/ami-updater"
20+
21+
environment = "prod"
22+
ssm_parameter_name = "/github-action-runners/latest_ami_id"
23+
24+
config = {
25+
dry_run = false
26+
ami_filter = {
27+
owners = ["self"]
28+
filters = [
29+
{
30+
name = "tag:Environment"
31+
values = ["prod"]
32+
},
33+
{
34+
name = "state"
35+
values = ["available"]
36+
}
37+
]
38+
}
39+
}
40+
41+
# Optional configurations
42+
schedule_expression = "rate(1 day)"
43+
state = "ENABLED"
44+
lambda_memory_size = 512
45+
lambda_timeout = 30
46+
log_level = "info"
47+
48+
tags = {
49+
Environment = "prod"
50+
Project = "my-project"
51+
}
52+
}
53+
```
54+
55+
## Requirements
56+
57+
| Name | Version |
58+
|------|---------|
59+
| terraform | >= 1.0 |
60+
| aws | >= 4.0 |
61+
62+
## Providers
63+
64+
| Name | Version |
65+
|------|---------|
66+
| aws | >= 4.0 |
67+
68+
## Inputs
69+
70+
| Name | Description | Type | Default | Required |
71+
|------|-------------|------|---------|:--------:|
72+
| environment | The environment name for the resources. | `string` | n/a | yes |
73+
| ssm_parameter_name | The name of the SSM parameter to store the latest AMI ID. | `string` | `/github-action-runners/latest_ami_id` | no |
74+
| config | Configuration for the AMI updater. | `object` | n/a | yes |
75+
| aws_partition | The AWS partition to use (e.g., aws, aws-cn) | `string` | `"aws"` | no |
76+
| tags | Map of tags that will be added to created resources | `map(string)` | `{}` | no |
77+
| lambda_runtime | AWS Lambda runtime | `string` | `"nodejs20.x"` | no |
78+
| lambda_architecture | AWS Lambda architecture | `string` | `"x86_64"` | no |
79+
| lambda_timeout | Time out of the lambda in seconds | `number` | `30` | no |
80+
| lambda_memory_size | Lambda memory size limit | `number` | `512` | no |
81+
| role_path | The path that will be added to the role | `string` | `null` | no |
82+
| role_permissions_boundary | Permissions boundary for the role | `string` | `null` | no |
83+
| lambda_subnet_ids | List of subnet IDs for the Lambda VPC config | `list(string)` | `null` | no |
84+
| lambda_security_group_ids | List of security group IDs for the Lambda VPC config | `list(string)` | `null` | no |
85+
| logging_retention_in_days | CloudWatch log retention in days | `number` | `180` | no |
86+
| logging_kms_key_id | KMS key ID for CloudWatch log encryption | `string` | `null` | no |
87+
| schedule_expression | EventBridge schedule expression | `string` | `"rate(1 day)"` | no |
88+
| state | EventBridge rule state | `string` | `"ENABLED"` | no |
89+
| log_level | Lambda function log level | `string` | `"info"` | no |
90+
91+
## Outputs
92+
93+
| Name | Description |
94+
|------|-------------|
95+
| lambda | The Lambda function details |
96+
| role | The IAM role details |
97+
| eventbridge | The EventBridge rule details |
98+
99+
## License
100+
101+
This module is licensed under the MIT License. See the LICENSE file for details.

modules/ami-updater/data.tf

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
data "aws_iam_policy_document" "lambda_assume_role_policy" {
2+
statement {
3+
actions = ["sts:AssumeRole"]
4+
5+
principals {
6+
type = "Service"
7+
identifiers = ["lambda.amazonaws.com"]
8+
}
9+
}
10+
}
11+
12+
data "aws_iam_policy_document" "lambda_xray" {
13+
count = var.tracing_config.mode != null ? 1 : 0
14+
15+
statement {
16+
actions = [
17+
"xray:PutTraceSegments",
18+
"xray:PutTelemetryRecords",
19+
"xray:GetSamplingRules",
20+
"xray:GetSamplingTargets",
21+
"xray:GetSamplingStatisticSummaries"
22+
]
23+
resources = ["*"]
24+
}
25+
}

modules/ami-updater/main.tf

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
locals {
2+
lambda_zip = "${path.module}/../../lambdas/functions/ami-updater/dist/ami-updater.zip"
3+
role_path = var.role_path == null ? "/${var.environment}/" : var.role_path
4+
tags = merge(
5+
{
6+
Environment = var.environment
7+
Name = "${var.environment}-ami-updater"
8+
},
9+
var.tags
10+
)
11+
}
12+
13+
resource "aws_lambda_function" "ami_updater" {
14+
filename = local.lambda_zip
15+
source_code_hash = filebase64sha256(local.lambda_zip)
16+
function_name = "${var.environment}-ami-updater"
17+
role = aws_iam_role.ami_updater.arn
18+
handler = "index.handler"
19+
runtime = var.lambda_runtime
20+
timeout = var.lambda_timeout
21+
memory_size = var.lambda_memory_size
22+
architectures = [var.lambda_architecture]
23+
tags = local.tags
24+
25+
environment {
26+
variables = {
27+
LOG_LEVEL = var.log_level
28+
DRY_RUN = tostring(var.config.dry_run)
29+
POWERTOOLS_SERVICE_NAME = "ami-updater"
30+
SSM_PARAMETER_NAME = var.ssm_parameter_name
31+
AMI_FILTER = jsonencode(var.config.ami_filter)
32+
}
33+
}
34+
35+
dynamic "vpc_config" {
36+
for_each = var.lambda_subnet_ids != null && var.lambda_security_group_ids != null ? [true] : []
37+
content {
38+
security_group_ids = var.lambda_security_group_ids
39+
subnet_ids = var.lambda_subnet_ids
40+
}
41+
}
42+
43+
dynamic "tracing_config" {
44+
for_each = var.tracing_config.mode != null ? [true] : []
45+
content {
46+
mode = var.tracing_config.mode
47+
}
48+
}
49+
}
50+
51+
resource "aws_cloudwatch_log_group" "ami_updater" {
52+
name = "/aws/lambda/${aws_lambda_function.ami_updater.function_name}"
53+
retention_in_days = var.logging_retention_in_days
54+
kms_key_id = var.logging_kms_key_id
55+
tags = var.tags
56+
}
57+
58+
resource "aws_cloudwatch_event_rule" "ami_updater" {
59+
name = "${var.environment}-ami-updater"
60+
description = "Trigger AMI updater Lambda function"
61+
schedule_expression = var.schedule_expression
62+
state = var.state
63+
tags = var.tags
64+
}
65+
66+
resource "aws_cloudwatch_event_target" "ami_updater" {
67+
rule = aws_cloudwatch_event_rule.ami_updater.name
68+
target_id = "TriggerAMIUpdaterLambda"
69+
arn = aws_lambda_function.ami_updater.arn
70+
}
71+
72+
resource "aws_lambda_permission" "ami_updater" {
73+
statement_id = "AllowExecutionFromCloudWatch"
74+
action = "lambda:InvokeFunction"
75+
function_name = aws_lambda_function.ami_updater.function_name
76+
principal = "events.amazonaws.com"
77+
source_arn = aws_cloudwatch_event_rule.ami_updater.arn
78+
}
79+
80+
resource "aws_iam_role" "ami_updater" {
81+
name = "${var.environment}-ami-updater"
82+
assume_role_policy = data.aws_iam_policy_document.lambda_assume_role_policy.json
83+
path = local.role_path
84+
permissions_boundary = var.role_permissions_boundary
85+
tags = local.tags
86+
}
87+
88+
resource "aws_iam_role_policy" "ami_updater" {
89+
name = "ami-updater-policy"
90+
role = aws_iam_role.ami_updater.name
91+
policy = jsonencode({
92+
Version = "2012-10-17"
93+
Statement = [
94+
{
95+
Effect = "Allow"
96+
Action = [
97+
"ec2:DescribeImages"
98+
]
99+
Resource = "*"
100+
},
101+
{
102+
Effect = "Allow"
103+
Action = [
104+
"ssm:PutParameter",
105+
"ssm:GetParameter"
106+
]
107+
Resource = "arn:${var.aws_partition}:ssm:*:*:parameter${var.ssm_parameter_name}"
108+
}
109+
]
110+
})
111+
}
112+
113+
resource "aws_iam_role_policy" "ami_updater_logging" {
114+
name = "logging-policy"
115+
role = aws_iam_role.ami_updater.name
116+
policy = templatefile("${path.module}/policies/lambda-cloudwatch.json", {
117+
log_group_arn = aws_cloudwatch_log_group.ami_updater.arn
118+
})
119+
}
120+
121+
resource "aws_iam_role_policy_attachment" "ami_updater_vpc_execution_role" {
122+
count = length(var.lambda_subnet_ids) > 0 ? 1 : 0
123+
role = aws_iam_role.ami_updater.name
124+
policy_arn = "arn:${var.aws_partition}:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
125+
}
126+
127+
resource "aws_iam_role_policy" "ami_updater_xray" {
128+
count = var.tracing_config.mode != null ? 1 : 0
129+
name = "xray-policy"
130+
policy = data.aws_iam_policy_document.lambda_xray[0].json
131+
role = aws_iam_role.ami_updater.name
132+
}
133+
134+
resource "aws_ssm_parameter" "latest_ami_id" {
135+
name = var.ssm_parameter_name
136+
description = "Latest AMI ID for GitHub runners"
137+
type = "String"
138+
value = "placeholder" # Will be updated by Lambda function
139+
tags = var.tags
140+
}

modules/ami-updater/outputs.tf

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
output "lambda" {
2+
description = "The Lambda function"
3+
value = {
4+
function_name = aws_lambda_function.ami_updater.function_name
5+
arn = aws_lambda_function.ami_updater.arn
6+
}
7+
}
8+
9+
output "role" {
10+
description = "The IAM role of the Lambda function"
11+
value = {
12+
name = aws_iam_role.ami_updater.name
13+
arn = aws_iam_role.ami_updater.arn
14+
}
15+
}
16+
17+
output "eventbridge" {
18+
description = "The EventBridge rule"
19+
value = {
20+
name = aws_cloudwatch_event_rule.ami_updater.name
21+
arn = aws_cloudwatch_event_rule.ami_updater.arn
22+
}
23+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"Version": "2012-10-17",
3+
"Statement": [
4+
{
5+
"Effect": "Allow",
6+
"Action": [
7+
"logs:CreateLogStream",
8+
"logs:PutLogEvents"
9+
],
10+
"Resource": "${log_group_arn}:*"
11+
}
12+
]
13+
}

0 commit comments

Comments
 (0)