diff --git a/.github/workflows/commit-to-pr.yaml b/.github/workflows/commit-to-pr.yaml
index 2bd9dc0..904c5fb 100644
--- a/.github/workflows/commit-to-pr.yaml
+++ b/.github/workflows/commit-to-pr.yaml
@@ -20,7 +20,7 @@ jobs:
validation:
strategy:
matrix:
- folder: ["add", "folders", "here"]
+ folder: ["./", "examples/step_function"]
name: Terraform validate for ${{ matrix.folder }}
runs-on: ubuntu-20.04
steps:
@@ -41,7 +41,7 @@ jobs:
linting:
strategy:
matrix:
- folder: ["add", "folders", "here"]
+ folder: ["./", "examples/step_function"]
name: Terraform lint for ${{ matrix.folder }}
runs-on: ubuntu-20.04
steps:
@@ -59,7 +59,7 @@ jobs:
plan:
strategy:
matrix:
- folder: ["add", "folders", "here"]
+ folder: ["examples/step_function"]
name: Terraform plan for ${{ matrix.folder }}
runs-on: ubuntu-20.04
needs: [validation, linting]
diff --git a/.terraform-version b/.terraform-version
new file mode 100644
index 0000000..8e03717
--- /dev/null
+++ b/.terraform-version
@@ -0,0 +1 @@
+1.5.1
\ No newline at end of file
diff --git a/README.md b/README.md
index 230c18a..e560d0f 100644
--- a/README.md
+++ b/README.md
@@ -42,11 +42,72 @@ 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.
+## Requirements
+| Name | Version |
+|------|---------|
+| [terraform](#requirement\_terraform) | ~> 1.5.0 |
+| [aws](#requirement\_aws) | >= 5.61.0 |
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | 5.67.0 |
+
+## Modules
+
+| Name | Source | Version |
+|------|--------|---------|
+| [step\_function\_state\_machine](#module\_step\_function\_state\_machine) | terraform-aws-modules/step-functions/aws | 4.2.0 |
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_caller_identity.current_account](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
+| [aws_iam_policy_document.attached_policies](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+| [aws_region.current_region](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [application\_name](#input\_application\_name) | Name of the application utilising resource. | `string` | n/a | yes |
+| [environment](#input\_environment) | Which environment this is being instantiated in. | `string` | n/a | yes |
+| [raw\_state\_machines](#input\_raw\_state\_machines) | Data structure
---------------
A list of dictionaries, where each dictionary has the following attributes:
REQUIRED
---------
- template\_file : File path which this machine corresponds to
- template\_input : A dictionary of key/value pairs, outlining in detail the inputs needed for a template to be instantiated
- suffix : Friendly name for the state function
- iam\_policy\_statements : A list of dictionaries where each dictionary is an IAM statement defining glue job permissions
-- Each dictionary in this list must define the following attributes:
--- sid: Friendly name for the policy, no spaces or special characters allowed
--- actions: A list of IAM actions the state machine is allowed to perform
--- resources: Which resource(s) the state machine may perform the above actions against
--- 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
OPTIONAL
---------
- cloudwatch\_retention : How many days logs should be retained for in Cloudwatch, defaults to 90 |
list(
object({
template_file = string,
template_input = map(string),
suffix = string,
iam_policy_statements = list(
object({
sid = string,
actions = list(string),
resources = list(string),
conditions = optional(list(
object({
test : string,
variable : string,
values = list(string)
})
), [])
})
),
cloudwatch_retention = optional(number, 90)
})
)
| n/a | yes |
+
+## Outputs
+
+No outputs.
## Data structure
-
+```
+Data structure
+---------------
+A list of dictionaries, where each dictionary has the following attributes:
+
+REQUIRED
+---------
+- template_file : File path which this machine corresponds to
+- template_input : A dictionary of key/value pairs, outlining in detail the inputs needed for a template to be instantiated
+- suffix : Friendly name for the state function
+- iam_policy_statements : A list of dictionaries where each dictionary is an IAM statement defining glue job permissions
+-- Each dictionary in this list must define the following attributes:
+--- sid: Friendly name for the policy, no spaces or special characters allowed
+--- actions: A list of IAM actions the state machine is allowed to perform
+--- resources: Which resource(s) the state machine may perform the above actions against
+--- 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
+
+
+OPTIONAL
+---------
+- cloudwatch_retention : How many days logs should be retained for in Cloudwatch, defaults to 90gi
+```
## Examples
See `examples` folder for an example setup.
diff --git a/aws_iam_policy_document.tf b/aws_iam_policy_document.tf
new file mode 100644
index 0000000..e0d1e45
--- /dev/null
+++ b/aws_iam_policy_document.tf
@@ -0,0 +1,79 @@
+locals {
+ actual_iam_policy_documents = {
+ for state_machine in var.raw_state_machines :
+ state_machine.suffix => {
+ statements = concat(state_machine.iam_policy_statements, local.barebones_statemachine_statements,
+ [
+ {
+ sid = "ListOwnExecutions",
+ actions = [
+ "states:ListExecutions"
+ ]
+ resources = [
+ format(
+ "arn:aws:states:%s:%s:stateMachine:%s-%s-%s-stepfunction",
+ lower(data.aws_region.current_region.name),
+ lower(data.aws_caller_identity.current_account.id),
+ lower(var.environment),
+ lower(var.application_name),
+ lower(state_machine.suffix)
+ )
+ ]
+ conditions = []
+ },
+ {
+ sid = "AllowCloudwatchStreamAccess",
+ actions = [
+ "logs:CreateLogStream",
+ "logs:PutLogEvents"
+ ],
+ resources = [
+ "arn:aws:logs:${data.aws_region.current_region.name}:${data.aws_caller_identity.current_account.account_id}:log-group:/aws/stepfunction/${format("%s-%s-%s-stepfunction", var.environment, var.application_name, state_machine.suffix)}",
+ "arn:aws:logs:${data.aws_region.current_region.name}:${data.aws_caller_identity.current_account.account_id}:log-group:/aws/stepfunction/${format("%s-%s-%s-stepfunction", var.environment, var.application_name, state_machine.suffix)}:*"
+ ]
+ conditions = []
+ },
+ {
+ sid = "AllowCloudwatchLogDelivery",
+ actions = [
+ "logs:CreateLogDelivery",
+ "logs:PutResourcePolicy",
+ "logs:UpdateLogDelivery",
+ "logs:DeleteLogDelivery",
+ "logs:DescribeResourcePolicies",
+ "logs:GetLogDelivery",
+ "logs:ListLogDeliveries",
+ "logs:DescribeLogGroups"
+ ],
+ resources = ["*"]
+ conditions = []
+ }
+ ]
+ )
+ }
+ }
+}
+
+data "aws_iam_policy_document" "attached_policies" {
+ 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"]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/common_iam_policies.tf b/common_iam_policies.tf
new file mode 100644
index 0000000..225051b
--- /dev/null
+++ b/common_iam_policies.tf
@@ -0,0 +1,19 @@
+locals {
+ barebones_statemachine_statements = [
+ {
+ sid = "BarebonesEventActionsForStatemachine"
+ actions = [
+ "events:PutEvents",
+ "events:DescribeRule",
+ "events:PutRule",
+ "events:PutTargets"
+ ]
+ resources = [
+ "arn:aws:events:${data.aws_region.current_region.name}:${data.aws_caller_identity.current_account.account_id}:rule/default/StepFunctionsGetEventsForECSTaskRule",
+ "arn:aws:events:${data.aws_region.current_region.name}:${data.aws_caller_identity.current_account.account_id}:rule/StepFunctionsGetEventsForECSTaskRule",
+ "arn:aws:events:${data.aws_region.current_region.name}:${data.aws_caller_identity.current_account.account_id}:event-bus/default"
+ ]
+ conditions = []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/data.tf b/data.tf
new file mode 100644
index 0000000..7ae4bae
--- /dev/null
+++ b/data.tf
@@ -0,0 +1,5 @@
+# Get current region
+data "aws_region" "current_region" {}
+
+# Retrieve the current AWS Account info
+data "aws_caller_identity" "current_account" {}
\ No newline at end of file
diff --git a/examples/step_function/.terraform-version b/examples/step_function/.terraform-version
new file mode 100644
index 0000000..8e03717
--- /dev/null
+++ b/examples/step_function/.terraform-version
@@ -0,0 +1 @@
+1.5.1
\ No newline at end of file
diff --git a/examples/step_function/data.tf b/examples/step_function/data.tf
new file mode 100644
index 0000000..7ae4bae
--- /dev/null
+++ b/examples/step_function/data.tf
@@ -0,0 +1,5 @@
+# Get current region
+data "aws_region" "current_region" {}
+
+# Retrieve the current AWS Account info
+data "aws_caller_identity" "current_account" {}
\ No newline at end of file
diff --git a/examples/step_function/files/step-function.json b/examples/step_function/files/step-function.json
new file mode 100644
index 0000000..852cd87
--- /dev/null
+++ b/examples/step_function/files/step-function.json
@@ -0,0 +1,11 @@
+{
+ "Comment": "A Hello World example of the Amazon States Language using an AWS Lambda Function",
+ "StartAt": "HelloWorld",
+ "States": {
+ "HelloWorld": {
+ "Type": "Task",
+ "Resource": "${lambda-arn}",
+ "End": true
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/step_function/locals.tf b/examples/step_function/locals.tf
new file mode 100644
index 0000000..94f45ba
--- /dev/null
+++ b/examples/step_function/locals.tf
@@ -0,0 +1,23 @@
+locals {
+ raw_state_machines = [
+ {
+ suffix : "hello-world",
+ template_file : "${path.module}/files/step-function.json",
+ template_input : {
+ "lambda-arn" : "arn:aws:lambda:${data.aws_region.current_region.name}:${data.aws_caller_identity.current_account.account_id}:function:hello-world-function"
+ },
+ iam_policy_statements : [
+ {
+ sid : "AllowLambdaExecution",
+ actions : [
+ "lambda:InvokeFunction",
+ "lambda:InvokeAsync",
+ ],
+ resources : [
+ "arn:aws:lambda:${data.aws_region.current_region.name}:${data.aws_caller_identity.current_account.account_id}:function:hello-world-function"
+ ]
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/examples/step_function/main.tf b/examples/step_function/main.tf
new file mode 100644
index 0000000..8a7561d
--- /dev/null
+++ b/examples/step_function/main.tf
@@ -0,0 +1,21 @@
+terraform {
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = ">= 5.61.0"
+ }
+ }
+ required_version = "~> 1.5.0"
+}
+
+provider "aws" {
+ region = "eu-west-2"
+}
+
+module "step_function" {
+ source = "github.com/sudoblark/sudoblark.terraform.module.aws.state_machine?ref=1.0.0"
+
+ application_name = var.application_name
+ environment = var.environment
+ raw_state_machines = local.raw_state_machines
+}
\ No newline at end of file
diff --git a/examples/step_function/variables.tf b/examples/step_function/variables.tf
new file mode 100644
index 0000000..9ccf4b8
--- /dev/null
+++ b/examples/step_function/variables.tf
@@ -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"
+}
\ No newline at end of file
diff --git a/main.tf b/main.tf
new file mode 100644
index 0000000..f022823
--- /dev/null
+++ b/main.tf
@@ -0,0 +1,9 @@
+terraform {
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = ">= 5.61.0"
+ }
+ }
+ required_version = "~> 1.5.0"
+}
\ No newline at end of file
diff --git a/state_machine.tf b/state_machine.tf
new file mode 100644
index 0000000..194ff72
--- /dev/null
+++ b/state_machine.tf
@@ -0,0 +1,45 @@
+locals {
+ actual_state_machines = {
+ for state_machine in var.raw_state_machines :
+ state_machine.suffix => merge(state_machine, {
+ state_machine_definition = templatefile(state_machine.template_file, state_machine.template_input)
+ policy_json = data.aws_iam_policy_document.attached_policies[state_machine.suffix].json
+ state_machine_name = format("%s-%s-%s-stepfunction", var.environment, var.application_name, state_machine.suffix)
+ })
+ }
+}
+
+module "step_function_state_machine" {
+ for_each = local.actual_state_machines
+
+ depends_on = [
+ data.aws_iam_policy_document.attached_policies
+ ]
+
+
+ source = "terraform-aws-modules/step-functions/aws"
+ version = "4.2.0"
+
+ name = each.value["state_machine_name"]
+ create_role = true
+ policy_jsons = [each.value["policy_json"]]
+
+ definition = each.value["state_machine_definition"]
+
+ logging_configuration = {
+ "include_execution_data" = true
+ "level" = "ALL"
+ }
+ cloudwatch_log_group_name = "/aws/vendedlogs/states/${each.value["state_machine_name"]}"
+ cloudwatch_log_group_retention_in_days = each.value["cloudwatch_retention"]
+
+ service_integrations = {
+ stepfunction_Sync = {
+ # Set to true to use the default events (otherwise, set this to a list of ARNs; see the docs linked in locals.tf
+ # for more information). Without events permissions, you will get an error similar to this:
+ # Error: AccessDeniedException: 'arn:aws:iam::xxxx:role/step-functions-role' is not authorized to
+ # create managed-rule
+ events = true
+ }
+ }
+}
\ No newline at end of file
diff --git a/variables.tf b/variables.tf
new file mode 100644
index 0000000..068ffe6
--- /dev/null
+++ b/variables.tf
@@ -0,0 +1,71 @@
+# Input variable definitions
+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"
+ }
+}
+
+variable "application_name" {
+ description = "Name of the application utilising resource."
+ type = string
+}
+
+variable "raw_state_machines" {
+ description = <= 0)
+ ])
+ error_message = "cloudwatch_retention for each state machine should be a valid integer greater than or equal to 0"
+ }
+}
\ No newline at end of file