Skip to content

Commit

Permalink
Merge pull request #1 from sudoblark/feature/initial-module-setup
Browse files Browse the repository at this point in the history
Initial module setup
  • Loading branch information
benjaminlukeclark authored Sep 13, 2024
2 parents 4091dcb + b34829d commit 0054cde
Show file tree
Hide file tree
Showing 26 changed files with 915 additions and 4 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/commit-to-pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
validation:
strategy:
matrix:
folder: ["add", "folders", "here"]
folder: ["./", "./examples/lambda_target", "./examples/state_machine_target"]
name: Terraform validate for ${{ matrix.folder }}
runs-on: ubuntu-20.04
steps:
Expand All @@ -41,7 +41,7 @@ jobs:
linting:
strategy:
matrix:
folder: ["add", "folders", "here"]
folder: ["./", "./examples/lambda_target", "./examples/state_machine_target"]
name: Terraform lint for ${{ matrix.folder }}
runs-on: ubuntu-20.04
steps:
Expand All @@ -59,7 +59,7 @@ jobs:
plan:
strategy:
matrix:
folder: ["add", "folders", "here"]
folder: ["./examples/lambda_target", "./examples/state_machine_target"]
name: Terraform plan for ${{ matrix.folder }}
runs-on: ubuntu-20.04
needs: [validation, linting]
Expand Down
1 change: 1 addition & 0 deletions .terraform-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.5.1
83 changes: 82 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,92 @@ The below documentation is intended to assist users in utilising the module, the
the module itself, and the [examples](#examples) section which has examples of how to utilise the module.

<!-- BEGIN_TF_DOCS -->
## Requirements

| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | ~> 1.5.0 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | ~> 5.27.0 |

## Providers

| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | 5.67.0 |

## Modules

| Name | Source | Version |
|------|--------|---------|
| <a name="module_rule"></a> [rule](#module\_rule) | ./modules/rule | n/a |
| <a name="module_target"></a> [target](#module\_target) | ./modules/target | n/a |

## Resources

| Name | Type |
|------|------|
| [aws_iam_policy.invoke_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
| [aws_iam_role.invoke_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [aws_iam_role_policy_attachment.invoke_role_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
| [aws_lambda_permission.allow_lambda_execution_from_event_bridge](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) | resource |
| [aws_iam_policy_document.allow_event_bridge_assume](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.event_bridge_target_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_application_name"></a> [application\_name](#input\_application\_name) | Name of the application utilising resource. | `string` | n/a | yes |
| <a name="input_environment"></a> [environment](#input\_environment) | Which environment this is being instantiated in. | `string` | n/a | yes |
| <a name="input_raw_event_bridge_rules"></a> [raw\_event\_bridge\_rules](#input\_raw\_event\_bridge\_rules) | Data structure<br>---------------<br>A list of dictionaries, where each dictionary has the following attributes:<br><br>REQUIRED<br>---------<br>- suffix : Friendly name for the rule in Event Bridge<br>- description : A friendly description of what the Event Bridge rule does<br>- targets : A list of dictionaries with the following attributes, defining what target this event triggers:<br>-- name : A friendly name for the target, if lambda this should be the lambda name<br>-- arn : The ARN of the resource being targeted<br>MUTUALLY EXCLUSIVE TARGETS INPUTS:<br>-- input : OPTIONAL JSON string of input to pass to target, defaults to null<br>-- input\_path : OPTIONAL value of the JSONPath that is used for extracting part of the matched event when passing it to the target, defaults to null.<br>-- input\_transformer : OPTIONAL parameters used when you are providing a custom input to a target based on certain event data, defaults to null.<br><br>One of the following, but not both:<br>- schedule : The scheduling expression. For example, cron(0 20 * * ? *) or rate(5 minutes)<br>- pattern : Pattern for the event to match on, should be jsonencoded dictionary<br><br>OPTIONAL<br>---------<br>By default we deploy event bridge rules as disabled, and ignore state on apply, such that<br>enabling/disabling event bridge rules is always a manual affair rather than doing via Terraform. But via the below<br>optional values this may be changed on a per-rule basis.<br><br>- state : By default DISABLED, can set to ENABLED or ENABLED\_WITH\_ALL\_CLOUDTRAIL\_MANAGEMENT\_EVENTS<br>- ignore\_state : By default true, can set to false.<br><br><br>IAM role Statement and Role Suffix to be used for this target when the rule is triggered.<br>Required if ecs\_target is used or target in arn is EC2 instance, Kinesis data stream, Step Functions state machine,<br>or Event Bus in different account or region.<br>- iam\_role\_suffix : IAM role suffix for the event bridge Role having permission to invoke target AWS Service<br>- iam\_policy\_statements : A list of dictionaries where each dictionary is an IAM statement defining Event Bridge permissions<br>-- conditions : An OPTIONAL list of dictionaries, which each defines:<br>--- test : Test condition for limiting the action<br>--- variable : Value to test<br>--- values : A list of strings, denoting what to test for | <pre>list(<br> object({<br> suffix = string,<br> description = string,<br> targets = optional(list(<br> object({<br> name = string,<br> arn = string,<br> input = optional(string, null)<br> input_path = optional(string, null)<br> input_transformer = optional(object({<br> input_template = string,<br> input_paths = optional(map(any), null)<br> }), null)<br> })), null),<br> schedule = optional(string, null),<br> pattern = optional(string, null),<br> iam_role_suffix = optional(string, ""),<br> iam_policy_statements = optional(list(<br> object({<br> sid = string,<br> actions = list(string),<br> resources = list(string),<br> conditions = optional(list(<br> object({<br> test : string,<br> variable : string,<br> values = list(string)<br> })<br> ), [])<br> })), []),<br> state = optional(string, "DISABLED"),<br> ignore_state = optional(bool, true)<br> })<br> )</pre> | n/a | yes |

## Outputs

No outputs.
<!-- END_TF_DOCS -->

## Data structure
<POPULATE WITH YOUR DATA STRUCTURE>
```
Data structure
---------------
A list of dictionaries, where each dictionary has the following attributes:
REQUIRED
---------
- suffix : Friendly name for the rule in Event Bridge
- description : A friendly description of what the Event Bridge rule does
- targets : A list of dictionaries with the following attributes, defining what target this event triggers:
-- name : A friendly name for the target, if lambda this should be the lambda name
-- arn : The ARN of the resource being targeted
MUTUALLY EXCLUSIVE TARGETS INPUTS:
-- input : OPTIONAL JSON string of input to pass to target, defaults to null
-- input_path : OPTIONAL value of the JSONPath that is used for extracting part of the matched event when passing it to the target, defaults to null.
-- input_transformer : OPTIONAL parameters used when you are providing a custom input to a target based on certain event data, defaults to null.
One of the following, but not both:
- schedule : The scheduling expression. For example, cron(0 20 * * ? *) or rate(5 minutes)
- pattern : Pattern for the event to match on, should be jsonencoded dictionary
OPTIONAL
---------
By default we deploy event bridge rules as disabled, and ignore state on apply, such that
enabling/disabling event bridge rules is always a manual affair rather than doing via Terraform. But via the below
optional values this may be changed on a per-rule basis.
- state : By default DISABLED, can set to ENABLED or ENABLED_WITH_ALL_CLOUDTRAIL_MANAGEMENT_EVENTS
- ignore_state : By default true, can set to false.
IAM role Statement and Role Suffix to be used for this target when the rule is triggered.
Required if ecs_target is used or target in arn is EC2 instance, Kinesis data stream, Step Functions state machine,
or Event Bus in different account or region.
- iam_role_suffix : IAM role suffix for the event bridge Role having permission to invoke target AWS Service
- iam_policy_statements : A list of dictionaries where each dictionary is an IAM statement defining Event Bridge permissions
-- conditions : An OPTIONAL list of dictionaries, which each defines:
--- test : Test condition for limiting the action
--- variable : Value to test
--- values : A list of strings, denoting what to test for
```

## Examples
See `examples` folder for an example setup.
42 changes: 42 additions & 0 deletions aws_iam_policy_document.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
locals {
actual_iam_policy_documents = {
for rule in var.raw_event_bridge_rules :
rule.suffix => {
statements = rule.iam_policy_statements
} if length(rule.iam_policy_statements) > 0
}
}

data "aws_iam_policy_document" "event_bridge_target_policy" {
for_each = local.actual_iam_policy_documents

dynamic "statement" {
for_each = each.value["statements"]

content {
sid = statement.value["sid"]
actions = statement.value["actions"]
resources = statement.value["resources"]

dynamic "condition" {
for_each = statement.value["conditions"]

content {
test = condition.value["test"]
variable = condition.value["variable"]
values = condition.value["values"]
}
}
}
}
}

data "aws_iam_policy_document" "allow_event_bridge_assume" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["events.amazonaws.com"]
}
}
}
13 changes: 13 additions & 0 deletions event_bridge_rules.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module "rule" {
source = "./modules/rule"
for_each = { for rule in var.raw_event_bridge_rules : rule.suffix => rule }

environment = var.environment
application_name = var.application_name
event_name_suffix = each.value["suffix"]
event_description = each.value["description"]
event_schedule = each.value["schedule"]
event_pattern = each.value["pattern"]
state = each.value["state"]
ignore_state = each.value["ignore_state"]
}
37 changes: 37 additions & 0 deletions event_bridge_target.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
locals {
event_bridge_targets = flatten([
for rule in var.raw_event_bridge_rules : [
for target in rule.targets : {
identifier = format("%s/%s", rule.suffix, target.name),
event_rule : module.rule[rule.suffix].name
event_target : target.name
event_target_arn : target.arn
event_target_role_arn : try(aws_iam_role.invoke_role[rule.suffix].arn, null)
event_target_input : target.input
event_target_input_path : target.input_path
event_target_input_transformer : target.input_transformer
}
]
])
}

module "target" {
source = "./modules/target"

for_each = { for target in local.event_bridge_targets : target.identifier => target }

event_rule = each.value["event_rule"]
event_target = each.value["event_target"]
event_target_arn = each.value["event_target_arn"]
event_target_role_arn = each.value["event_target_role_arn"]
event_target_input = each.value["event_target_input"]
event_target_input_path = each.value["event_target_input_path"]
event_target_input_transformer = each.value["event_target_input_transformer"]


depends_on = [
module.rule,
data.aws_iam_policy_document.event_bridge_target_policy,
data.aws_iam_policy_document.allow_event_bridge_assume
]
}
1 change: 1 addition & 0 deletions examples/lambda_target/.terraform-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.5.1
5 changes: 5 additions & 0 deletions examples/lambda_target/data.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Get current region
data "aws_region" "current_region" {}

# Retrieve the current AWS Account info
data "aws_caller_identity" "current_account" {}
84 changes: 84 additions & 0 deletions examples/lambda_target/locals.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
Data structure
---------------
A list of dictionaries, where each dictionary has the following attributes:
REQUIRED
---------
- suffix : Friendly name for the rule in Event Bridge
- description : A friendly description of what the Event Bridge rule does
- targets : A list of dictionaries with the following attributes, defining what target this event triggers:
-- name : A friendly name for the target, if lambda this should be the lambda name
-- arn : The ARN of the resource being targeted
MUTUALLY EXCLUSIVE TARGETS INPUTS:
-- input : OPTIONAL JSON string of input to pass to target, defaults to null
-- input_path : OPTIONAL value of the JSONPath that is used for extracting part of the matched event when passing it to the target, defaults to null.
-- input_transformer : OPTIONAL parameters used when you are providing a custom input to a target based on certain event data, defaults to null.
One of the following, but not both:
- schedule : The scheduling expression. For example, cron(0 20 * * ? *) or rate(5 minutes)
- pattern : Pattern for the event to match on, should be jsonencoded dictionary
OPTIONAL
---------
By default we deploy event bridge rules as disabled, and ignore state on apply, such that
enabling/disabling event bridge rules is always a manual affair rather than doing via Terraform. But via the below
optional values this may be changed on a per-rule basis.
- state : By default DISABLED, can set to ENABLED or ENABLED_WITH_ALL_CLOUDTRAIL_MANAGEMENT_EVENTS
- ignore_state : By default true, can set to false.
IAM role Statement and Role Suffix to be used for this target when the rule is triggered.
Required if ecs_target is used or target in arn is EC2 instance, Kinesis data stream, Step Functions state machine,
or Event Bus in different account or region.
- iam_role_suffix : IAM role suffix for the event bridge Role having permission to invoke target AWS Service
- iam_policy_statements : A list of dictionaries where each dictionary is an IAM statement defining Event Bridge permissions
-- conditions : An OPTIONAL list of dictionaries, which each defines:
--- test : Test condition for limiting the action
--- variable : Value to test
--- values : A list of strings, denoting what to test for
*/

locals {
raw_event_bridge_rules = [
# Rule is enabled and its state is managed with Terraform
{
suffix = "sagemaker-promotion"
description = "Trigger SageMaker model promotion when package state changes"
state = "ENABLED"
ignore_state = "false"
targets = [
{
name = "my-promotion-lambda"
arn = "arn:aws:lambda:${data.aws_region.current_region.name}:${data.aws_caller_identity.current_account.account_id}:function:my-promotion-lambda"
}
]
pattern = jsonencode({
source = ["aws.sagemaker"]
detail-type = ["SageMaker Model Package State Change"]
detail = {
"ModelPackageGroupName" : [
{
"exists" : true
}
]
}
})
},
# Rule is disabled, by state is not managed by Terraform, thus it may be enabled/disabled in the account
# manually by individuals
{
suffix = "hourly-healthcheck"
description = "EventBridge Schedule Rule to trigger hourly healthcheck lambda"
schedule = "cron(0 0 * * ? *)"
targets = [
{
name = "hourly-healthcheck-lambda"
arn = "arn:aws:lambda:${data.aws_region.current_region.name}:${data.aws_caller_identity.current_account.account_id}:function:hourly-healthcheck-lambda"
}
]
iam_role_suffix = "healthcheck"
},
]
}
21 changes: 21 additions & 0 deletions examples/lambda_target/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.27.0"
}
}
required_version = "~> 1.5.0"
}

provider "aws" {
region = "eu-west-2"
}

module "event_bridge" {
source = "github.com/sudoblark/sudoblark.terraform.module.aws.event_bridge_rule?ref=1.0.0"

application_name = var.application_name
environment = var.environment
raw_event_bridge_rules = local.raw_event_bridge_rules
}
15 changes: 15 additions & 0 deletions examples/lambda_target/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
variable "environment" {
description = "Which environment this is being instantiated in."
type = string
validation {
condition = contains(["dev", "test", "prod"], var.environment)
error_message = "Must be either dev, test or prod"
}
default = "prod"
}

variable "application_name" {
description = "Name of the application utilising the resource resource."
type = string
default = "demo-app"
}
1 change: 1 addition & 0 deletions examples/state_machine_target/.terraform-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.5.1
5 changes: 5 additions & 0 deletions examples/state_machine_target/data.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Get current region
data "aws_region" "current_region" {}

# Retrieve the current AWS Account info
data "aws_caller_identity" "current_account" {}
Loading

0 comments on commit 0054cde

Please sign in to comment.