Use module to return nested block

Is there a way for modules to output nested blocks that would be used within resources the calling terraform template?

From what i’ve read modules can create resources and output variables. Can they output blocks?

Background

I am using the Fastly provider to create fastly_service_v1 resources.
There are significant differences between our Fastly services preventing us using a module to manage creation of these resources.
However, within these fastly resources there is a common logging configuration, specified via a nested syslog block.

The sample code below shows the current setup, where the syslog block is explicitly defined in the fastly resource using output variables from the logging module

In the spirit of DRY I’d like for the whole syslog block to be emitted by the module.

Is this possible?

Sample code
`module “logging” {
source = “app.terraform.io/our_org/logging/fastly
version = “0.0.1”

service_name = “example”
environment = “prd”
}

resource “fastly_service_v1” “example-service” {
name = “example-service”

domain {
name = “site1.example.com
}

backend {
address = “our.backend.com
auto_loadbalance = false
name = “addr our.backend.com
port = 80
ssl_check_cert = true
use_ssl = false
}

syslog {
address = “{module.logging.config.address}" format = "{module.logging.format}”
format_version = 2
message_type = “blank”
name = “Datadog logging”
port = module.logging.port
tls_ca_cert = “”
tls_hostname = “${module.logging.hostname}”
use_tls = true
}

force_destroy = true
}`

Hi @ashepherd-mvf!

Looking at what you wrote so far, it looks like it’s consistent with our usual recommendations of keeping modules decoupled: the result of that module seems generic and usable by any downstream system that supports the syslog protocol, and so I’d honestly probably just leave what you have as-is and let the configuration of the Fastly service be the place where you map the generic onto the specific.

However, if you want to factor out the rest of the values in that syslog block, you could write an intermediate module like this:

variable "service_name" {
  type = string
}

variable "environment" {
  type = string
}

module "logging" {
  source  = "app.terraform.io/our_org/logging/syslog"
  version = "0.0.1"

  service_name = var.service_name
  environment  = var.environment
}

output "config" {
  value = {
    name = "Datadog logging"

    address        = module.logging.address
    port           = module.logging.port
    format         = module.logging.format
    format_version = 2
    message_type   = "blank"

    tls_ca_cert  = ""
    tls_hostname = module.logging.hostname
    use_tls      = true
  }
}

This allows factoring out those values, but Terraform requires that you still write out the individual attributes, in order to ensure that a future reader can look at the service configuration and understand which arguments are being set:

  syslog {
    address      = module.fastly_logging.config.address
    format       = module.fastly_logging.config.format
    message_type = module.fastly_logging.config.message_type
    name         = module.fastly_logging.config.name
    port         = module.fastly_logging.config.port
    tls_ca_cert  = module.fastly_logging.config.tls_ca_cert
    tls_hostname = module.fastly_logging.config.tls_hostname
    use_tls      = module.fastly_logging.config.use_tls
  }

If you expect that in future some or all of these attributes might not be set in the config output, you can write them conditionally with defaults instead like this:

lookup(module.fastly_logging.config, "use_tls", false)

…though given the way you framed the problem, it seems more likely that you’d want to define the module such that it always produces all of the attributes and have it be responsible for providing any defaults for values that aren’t set, so that the callers are free of any logic and are responsible only for composing the module result into the service configuration.