Terraform: Improvement: Export all outputs of a sub module in the current module. Use the map key entries for generating separated output variables.

Created on 30 Aug 2016  ยท  7Comments  ยท  Source: hashicorp/terraform

I was not able to see another ticket mentioning this, so here I go:

I would like to pass-though/copy/declare all output variables of a sub module as output variables of my own module. This is normally not a problem when the sub module got two or three outputs, but it is a hassle when there are lot of them. Let me illustrate this by using a imaginary module.

# the prolific module
module "plenty" {
  source = "./output/plenty/of"
}

# the boilerplate
output "out1" {
  value = "${module.plenty.out1}"
}
output "out2" {
  value = "${module.plenty.out2}"
}
output "out3" {
  value = "${module.plenty.out3}"
}
output "out4" {
  value = "${module.plenty.out1}"
}
output "out5" {
  value = "${module.plenty.out2}"
}
output "out6" {
  value = "${module.plenty.out3}"
}

# and so on... you get the idea...

output "outN" {
  value = "${module.plenty.outN}"
}

Proposed Improvement

AFAIK outputs are stored in a map. If this is the case then I would like to be able to export the keys of any arbitrary map as separated output elements.

We could use a new keyword like export.

In order to add some flexibility we should be also be able to specify some selectors which could allow a regex/globbing syntax and with a default for all such as [ "*" ] or [ ".+" ]

Example: Using module outputs

# the prolific module again
module "plenty" {
  source = "./output/plenty/of"
}

export "many" {
  source = "${module.plenty.outputs}" # accept a map
  selectors = [".+"]                  # this could be the the default setup
  delimiter = "_"                     # same here, or just an empty string.
}

if we set the input variables to some sane default values we could then just write

export "many" {
  source = "${module.plenty.outputs}"
}

This will produce a set of outputs in the format export_name + delimter + individual_module_output_name like:

many_out2
many_out3
many_out4
many_out5
many_out6
...
...
...
many_outN

Example: selectors

# the same the prolific module
module "plenty" {
  source = "./output/plenty/of"
}

# select just some keys
outputs "many" {
  source = "${module.plenty}"
  selectors = ["out1", "out75", ".+text(10|11)-suffix"]
}

Example: using a custom map

variable "mymap" {
  type = "map"
  default = {
    key1 = "val1"
    key2 = "val2"
    key3 = "val3"
    key4 = "val4"
  }
}

export "many" {
  source = "${var.mymap}"
}

Opinions? Ideas?

config enhancement

Most helpful comment

Hi all! Sorry for the long silence here.

As part of the configuration language improvements we're working on for the next major release, it should be possible to return an entire module or resource as an object via an output:

# Not yet implemented, and details may change before release

module "plenty" {
  source = "./output/plenty/of"
}

output "plenty" {
  value = module.plenty
}

...or:

# Not yet implemented, and details may change before release

resource "aws_vpc" "example" {
  # ...
}

module "other" {
  source = "./other"

  vpc = aws_vpc.example
}

As @cpoole noted, this is what is being discussed over in #9067. I'd previously left a comment saying it wasn't clear yet whether this would make the initial release. It's still not 100% certain, since there are some wrinkles to work out in the implementation (changes lots of Terraform's internal assumptions), but we _do_ intend to make that work and it's just a matter of the effort required to get there.

While _this_ issue isn't describing exactly that idea, I think for the time being the capability to treat modules and resources as objects will be our proposed solution to the underlying need here, and so I'm going to close this one just to consolidate the discussion over in #9067. Once the change I described above is released and we've gathered some real-world experiences with it we can see if additional features are warranted to fill any remaining capability gaps.

Since this issue was originally opened, we've also added the concept of Local Values, which allow a name to be assigned to a local value within a module that can then be used elsewhere, and which (unlike variable defaults) can contain arbitrary expressions.

Thanks for sharing this use case and feature proposal, @martin-flaregames!

All 7 comments

Just noting my immediate thoughts: I'd be open to a special automatic variable on modules that always includes all the outputs.

A more or less related issue I keep forgetting: Assuming that the here proposed strategy gets implemented, then using a map for reducing the amount of code is not really viable due to the fact that variables cannot use interpolations, not event for defining a key within a map.

I just tried to export all my outputs into a single map. This is my code:

variable "init" {
  type = "map"
  default = {
    account_id = "${module.init.account_id}"
    opsworks_service_role_arn = "${module.init.opsworks_service_role_arn}"
    opsworks_instance_profile_arn = "${module.init.opsworks_instance_profile_arn}"
    hosted_zone_id = "${module.init.hosted_zone_id}"
    cloudwatch_alarm_mail_arn = "${module.init.cloudwatch_alarm_mail_arn}"
    cloudwatch_alarm_pagerduty_arn = "${module.init.cloudwatch_alarm_pagerduty_arn}"
    role_switch_link_devops = "${module.init.role_switch_link_devops}"
    role_switch_link_qa = "${module.init.role_switch_link_qa}"
  }
}

output "init" {
  value = "${var.init}"
}

This yielded the known error Variable 'init': cannot contain interpolations.

So, basically the proposal in this ticket, if implemented as described, would be only a half solution. It would only help exporting the module output variables, but not really more than that.

Something similar would be intersting on the cli like so: terraform inspect

terraform inspect root.modue.submodule.outputs.output_variable

Something around this should also be expected to work:
terraform inspect root.modue.*.outputs.output_variable
and could probably result, for enhanced usefulness:

{
"submodule1" : "output_variable_1_value",
"submodule2" : "output_variable_2_value",
}

Why not simply add a flag inside a resource that declares create_output = true and it takes the resource name and declares it as an output, which is equivalent to manually declaring an output?

Real case scenario:
I have a module which creates lots of subnets on AWS and I need to access each subnet's ID, therefore I need to declare an output for each subnet, quite a hassle.

So basically my idea is:

// subnet module
resource "aws_subnet" "subnet-d81e1aac-6" {
    vpc_id                  = "${var.vpc_production_id}"
    cidr_block              = "192.168.6.0/23"
    availability_zone       = "${var.aws_region}a"
    map_public_ip_on_launch = false

    create_output = true <--- Note this

    tags {
        "Name" = "6"
    }
}

This will automatically create an output that can be reached via ${module.vpc.subnet-d81e1aac-6.id}

This would be a fantastic feature to add. Right now our vpc instantiations have tons of boilerplate just to expose the vpc module outputs. If we change the vpc module and add or subtract any outputs, each of the dependent projects must be individually updated... this is clearly not a maintainable design pattern.

I recognize this feature likely has a large change to logic in core. Is this something Hashicorp has roadmapped for internal dev or is this something Hashicorp would accept a PR to add into current state?

seems like this ticket and https://github.com/hashicorp/terraform/issues/9067 are closely coupled in scope/implementation

Hi all! Sorry for the long silence here.

As part of the configuration language improvements we're working on for the next major release, it should be possible to return an entire module or resource as an object via an output:

# Not yet implemented, and details may change before release

module "plenty" {
  source = "./output/plenty/of"
}

output "plenty" {
  value = module.plenty
}

...or:

# Not yet implemented, and details may change before release

resource "aws_vpc" "example" {
  # ...
}

module "other" {
  source = "./other"

  vpc = aws_vpc.example
}

As @cpoole noted, this is what is being discussed over in #9067. I'd previously left a comment saying it wasn't clear yet whether this would make the initial release. It's still not 100% certain, since there are some wrinkles to work out in the implementation (changes lots of Terraform's internal assumptions), but we _do_ intend to make that work and it's just a matter of the effort required to get there.

While _this_ issue isn't describing exactly that idea, I think for the time being the capability to treat modules and resources as objects will be our proposed solution to the underlying need here, and so I'm going to close this one just to consolidate the discussion over in #9067. Once the change I described above is released and we've gathered some real-world experiences with it we can see if additional features are warranted to fill any remaining capability gaps.

Since this issue was originally opened, we've also added the concept of Local Values, which allow a name to be assigned to a local value within a module that can then be used elsewhere, and which (unlike variable defaults) can contain arbitrary expressions.

Thanks for sharing this use case and feature proposal, @martin-flaregames!

I'm going to lock this issue because it has been closed for _30 days_ โณ. This helps our maintainers find and focus on the active issues.

If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

zeninfinity picture zeninfinity  ยท  3Comments

c4milo picture c4milo  ยท  3Comments

ronnix picture ronnix  ยท  3Comments

pawelsawicz picture pawelsawicz  ยท  3Comments

jrnt30 picture jrnt30  ยท  3Comments