Terraform Module: Scheduled Lambda + EventBridge
LiveA Terraform module for the ops-automation shape I kept rebuilding by hand: a Lambda function on an EventBridge schedule — the pattern behind alert digests, cleanup tasks, and report generation. Five resources, always the same, and always with the same two corners cut when assembled quickly: an over-broad execution role and a log group Lambda auto-creates with infinite retention. The module packages those five resources once, with the defaults done correctly.
Least-privilege by construction
Instead of attaching the AWS-managed AWSLambdaBasicExecutionRole
— which grants log actions across the account — the module builds its own
policy document granting exactly two actions,
logs:CreateLogStream and logs:PutLogEvents, scoped
to "${aws_cloudwatch_log_group.this.arn}:*": this function's log
group and its streams, nothing else. The log group itself
(/aws/lambda/${var.function_name}) is created explicitly with a
retention_in_days setting (default 30), and the function
declares depends_on against it, so retention is enforced from
the first invocation rather than after Lambda has already auto-created a
never-expiring group. When a job needs more than logging — say, reading one
SSM parameter — callers pass extra_policy_arns and attach scoped
policies to the role, rather than the module widening its own grants.
Scheduling and packaging
Scheduling is a single schedule_expression input that accepts
either form EventBridge supports — rate(1 hour) or
cron(0 6 * * ? *) — wired as a rule, a target pointing at the
function ARN, and an aws_lambda_permission whose
source_arn is pinned to that specific rule, so only this
schedule can invoke the function. A schedule_enabled boolean
flips the rule between ENABLED and DISABLED,
letting you pause a job without destroying it.
The module deliberately does not build the Lambda package. It takes
package_path to a pre-built zip and derives
source_code_hash via filebase64sha256() so
redeploys trigger only when the artifact actually changes — build tooling
stays in CI, not in Terraform. Four inputs are required
(function_name, package_path,
handler, schedule_expression); the rest default
sensibly: python3.12 runtime, 60s timeout, 128 MB memory, empty
environment map, and a tags map applied to every resource.
Outputs expose the function ARN, role ARN, schedule rule ARN, and log group
name for downstream wiring.
Example and pipeline
The examples/basic directory is a working nightly-cleanup job:
cron(0 6 * * ? *), a 120-second timeout, 14-day log retention,
and a LOG_LEVEL environment variable — a realistic override of
the defaults, not a toy. CI runs on every push and pull request:
terraform fmt -check, terraform validate against a
backendless init, and tflint with the AWS ruleset plugin
(pinned to 0.38.0). Releases are wired to release-please using
the terraform-module release type — conventional commits on
main will drive semantic version bumps and changelog entries as
the module matures, so consumers will be able to pin it by tag. Requires
Terraform >= 1.5 and AWS provider >= 5.0.