How can i use same cloudwatch logs and same sns target for each scheduled events in terraform?

I have 2 events:

in event-bridge/events/event1/main.tf

module "event1" {
  source              = "../../rule"
  name                = "event1"
  description         = "event1"
  schedule_expression = "cron(0 4 * * ? *)"
  input               = jsonencode({"job-type": "event1"})
  cloudwatch_arn      = module.targets.cloudwatch_arn
}

module "targets" {
  source = "../../targets"
}

in event-bridge/events/event2/main.tf

module "event2" {
  source              = "../../rule"
  name                = "event2"
  description         = "event2"
  schedule_expression = "cron(0 5 * * ? *)"
  input               = jsonencode({"job-type": "event2"})
  cloudwatch_arn      = module.targets.cloudwatch_arn
}

module "targets" {
  source = "../../targets"
}

in event-bridge/targets/main.tf

resource "aws_cloudwatch_log_group" "log_group_target" {
  name = "/aws/events/${local.rule_name}"
}

resource "aws_sns_topic" "sns_target" {
  name = local.rule_name
}

resource "aws_sns_topic_policy" "sns-target-policy" {
  arn    = aws_sns_topic.sns_target.arn
  policy = data.aws_iam_policy_document.events_to_sns_topic_policy.json
}

data "aws_iam_policy_document" "events_to_sns_topic_policy" {
  statement {
    effect  = "Allow"
    actions = ["SNS:Publish"]

    principals {
      type        = "Service"
      identifiers = ["events.amazonaws.com"]
    }

    resources = [aws_sns_topic.sns_target.arn]
  }
}

in event-bridge/targets/variables.tf

locals {
  rule_name = "scheduled-events"
}

in event-bridge/targets/output.tf

output "cloudwatch_arn" {
  value = aws_cloudwatch_log_group.log_group_target.arn
}

output "sns_topic_arn" {
  value = aws_sns_topic.sns_target.arn
}

in event-bridge/rule/main.tf

resource "aws_cloudwatch_event_rule" "rule" {
  name                = var.name
  description         = var.description
  schedule_expression = var.schedule_expression
}

resource "aws_cloudwatch_event_target" "cloud_watch_target" {
  rule  = local.rule_name
  arn   = var.cloudwatch_arn
}

resource "aws_cloudwatch_event_target" "sns_target" {
  rule      = local.rule_name
  arn       = var.sns_arn
  input     = var.input
}

in event-bridge/rule/variables.tf

locals {
  rule_name = "scheduled-events"
}

variable "name" {
  description = "A name of the event bridge rule to be created"
  type        = string
}

variable "description" {
  description = "A description of the event bridge rule to be created"
  type        = string
}

variable "input" {
  description = "A message for the events to be processed by this rule"
  type        = any
}

variable "schedule_expression" {
  description = "A scheduled time interval for the events to be processed"
  type        = any
}

variable "cloudwatch_arn" {
  description = "Logging cloudwatch arn for the events to be processed"
  type        = any
}

if i plan like this for each event another cloudwatch log group and sns target is created and when apply there is an error due to same name, but i want them to use same cloudwatch log group and sns target.. do you have any idea how i can achieve that? thanks a lot!!


Solution 1:

A resource can only be managed at one address in the state. You are creating two instances of the targets module: module.event1.module.targets and module.event2.module.targets.

If you want to use the same values created in one targets module in both events modules, then you must either pass the module outputs in as variables or use data sources in the events modules to reference the targets. In both cases, only create one targets module.


As requested, I am expanding the answer to clarify.

Consider the following directory structure.

  • rule/
    • main.tf
    • variables.tf
  • targets/
    • main.tf
    • outputs.tf
    • variables.tf
  • ./main.tf

Here main.tf at the root is the provider for the state. Here are the contents of the files, though I haven't tested this.

rule/main.tf

resource "aws_cloudwatch_event_rule" "rule" {
  name                = var.name
  description         = var.description
  schedule_expression = var.schedule_expression
}

resource "aws_cloudwatch_event_target" "cloud_watch_target" {
  rule = aws_cloudwatch_event_rule.rule.name
  arn  = var.cloudwatch_arn
}

resource "aws_cloudwatch_event_target" "sns_target" {
  rule  = aws_cloudwatch_event_rule.rule.name
  arn   = var.sns_topic_arn
  input = var.input
}

rule/variables.tf

variable "name" {
  description = "A name of the event bridge rule to be created"
  type        = string
}

variable "description" {
  description = "A description of the event bridge rule to be created"
  type        = string
}

variable "input" {
  description = "A message for the events to be processed by this rule"
  type        = any
}

variable "schedule_expression" {
  description = "A scheduled time interval for the events to be processed"
  type        = string
}

variable "cloudwatch_arn" {
  description = "Logging cloudwatch arn for the events to be processed"
  type        = string
}

variable "sns_topic_arn" {
  type = string
}

targets/main.tf

resource "aws_cloudwatch_log_group" "log_group_target" {
  name = "/aws/events/${local.rule_name}"
}

resource "aws_sns_topic" "sns_target" {
  name = local.rule_name
}

resource "aws_sns_topic_policy" "sns-target-policy" {
  arn    = aws_sns_topic.sns_target.arn
  policy = data.aws_iam_policy_document.events_to_sns_topic_policy.json
}

data "aws_iam_policy_document" "events_to_sns_topic_policy" {
  statement {
    effect  = "Allow"
    actions = ["SNS:Publish"]

    principals {
      type        = "Service"
      identifiers = ["events.amazonaws.com"]
    }

    resources = [aws_sns_topic.sns_target.arn]
  }
}

targets/outputs.tf

output "cloudwatch_arn" {
  value = aws_cloudwatch_log_group.log_group_target.arn
}

output "sns_topic_arn" {
  value = aws_sns_topic.sns_target.arn
}

targets/variables.tf

locals {
  rule_name = "scheduled-events"
}

the main top-level main.tf

module "targets" {
  source = "./targets"
}

module "event1" {
  source = "./rule"

  name                = "event1"
  description         = "event1"
  schedule_expression = "cron(0 4 * * ? *)"
  input               = jsonencode({ "job-type" : "event1" })
  cloudwatch_arn      = module.targets.cloudwatch_arn
  sns_topic_arn       = module.targets.sns_topic_arn
}

module "event2" {
  source = "./rule"

  name                = "event2"
  description         = "event2"
  schedule_expression = "cron(0 5 * * ? *)"
  input               = jsonencode({ "job-type" : "event2" })
  cloudwatch_arn      = module.targets.cloudwatch_arn
  sns_topic_arn       = module.targets.sns_topic_arn
}