Currently, it is not possible to reuse blocks of configuration information, which gives way to large amounts of repetition.
As an example, I have several differing EC2 instances with remote-exec provisioners. The connection information is the same between them (bastions, private keys, etc). Is there anyway to reuse this information?
This would be solved with a global connection block, but it crops up again in other places, ie. identical ingress and egress rules for security groups.
@hydroxide could you not make a module out of this?
https://www.terraform.io/intro/getting-started/modules.html
https://www.terraform.io/docs/modules/usage.html
@mengesb Modules don't solve this problem particularly well. The instances share a connection block, but the other arguments are all unique and must be parametrized. This leads to more excess than it removes.
Additionally, this problem crops up in other places, such as security groups (mentioned above) which may share some set of ingress and egress rules but differ in others. Modules don't solve that problem at all.
I am looking for something akin to storing a block of configuration in a variable and using it where needed.
I'm looking for this as well. My use case is a provisioner
block that is used in several different resources for setting up some base configuration (made up VPN example):
resource "foo" "master" {
provisioner "file" {
content = <<EOF
Address = ${self.ipv4_address}
Connect = ${foo.master.0.ipv4_address}
EOF
destination = "/etc/vpn.d/conf"
}
}
resource "foo" "minion" {
provisioner "file" {
content = <<EOF
Address = ${self.ipv4_address}
Connect = ${foo.master.0.ipv4_address}
EOF
destination = "/etc/vpn.d/conf"
}
}
Using a template resource would make this somewhat nicer if not for #2167.
Just wondering if there was any solution found for this need for a global SSH connection block. Currently, we have about 6 different resources, each with a duplicated connection block.
Sometimes our Terraform users are outside the VPC and need to SSH connect to self.public_ip, sometimes they are inside the VPC and need to connect to self.private_ip. Currently, we are search/replacing the main.tf in multiple places for this public vs. private issue.
Is there anyway to specify the SSH connection block once to remove the need for this search and replace (or writing a Terraform pre-processor)? Any tips would be most appreciated.
@guydavis You can likely use inline ternary operators to swap on a var between public/private ips
I totally agree with @hydroxide. Whenever we try to introduce Terraform at our projects, this is one of the major issues we have to fight with and which ultimately leads to using other tools :(
Try to use locals: https://www.terraform.io/docs/configuration/locals.html
Locals does solve my issue (the example they provide with tags was perfect).
It'd be nice if there was a less verbose way to do the merging, though.
Something like this (using JS spread operator):
tags {
...local.common_tags
Name = "awesome-app-server"
Role = "server"
}
Instead of:
tags = "${merge(
local.common_tags,
map(
"Name", "awesome-app-server",
"Role", "server"
)
)}"
Still do not understand how can I use locals for connection.
Tried:
locals {
connect = {
type = "ssh"
user = "user"
password = "${var.admin_password["plain"]}"
bastion_host = "${var.bastion_host}"
bastion_user = "bastionuser"
bastion_password = "${var.admin_password["plain"]}"
}
}
resource "null_resource" "worker_provisioner" {
count = "${var.workers_number}"
connection = "${merge(local.connect, map(
"host", "${openstack_networking_port_v2.private_network_worker_port.*.fixed_ip.0.ip_address[count.index]}"
))}"
}
And got an error:
Error reading connection info for null_resource[worker_provisioner]: At 86:16: root: not an object type for map (*ast.LiteralType)
I am just in the process of introducing terraform at my company, and am facing the same issue. It would be nice to have a global connection block, which if needed, can be over-ridden in the
provisioner. If, however, a provisioner does not specify a connection for file or remote-exec, then
it can check and use the global connection block, or throw an error if one is not configured.
Right now, I find that I am duplicating connection blocks. Will look into @mengesb's suggestion
of using a module, but IMO it would be best if this can be provided out-of-the-box.
@harmanbirdi Isn't null_resource providing what you're looking for ?
https://www.terraform.io/docs/provisioners/null_resource.html
I can't see how. Example?
@hydroxide -- with the pending v0.12 release does this look to be solved there?
I'm interested in a solution, too.
Using locals seem not work:
locals {
connection = {
type = "winrm"
user = "Administrator"
password = "${aws_secretsmanager_secret_version.example.secret_string}"
timeout = "10m"
}
}
resource "aws_instance" "example" {
connection = "${local.connection}"
}
Same error like @gavvvr
I was hoping to accomplish something similar to this to avoid retyping the same thing over and over, but alas:
This is addressed here https://github.com/hashicorp/terraform/issues/17402
The best way I see this happening would be
locals {
connection = {
host = "${module.test.public_ip}"
user = "${var.user}"
private_key = "${file("${var.private_key_location}")}"
}
}
resource "null_resource" "example" {
provisioner "file" {
# [ ... ]
connection {
host = "${local.connection["host"]}"
user = "${local.connection["user"]}"
private_key = "${local.connection["private_key"]}"
}
}
}
Does anyone know a workaround to create a conditional connection without duplicating code?:
resource "null_resource" "example" {
provisioner "file" {
connection {
host = "${local.connection["host"]}"
user = "${local.connection["user"]}"
private_key = "${local.connection["private_key"]}"
// Want to add the following only when a condition is true
bastion_host = "example.local"
bastion_user = "core"
bastion_private_key = "${var.ssh_private_key}"
}
}
}
@remoe you should be able to use inline ternaries
bastion_host = "${var.is_bastion ? var.bastion["host"] : ""}"
or
bastion_host = "${length(var.bastion["host"]) > 0 ? var.bastion["host"] : ""}"
Ran into this use case today as well. Looking at the syntax used to define connection
indicated it is not an assignable property; similar to inline
or file
. IE there is not assignment operator, =
, between connection
and the desired configuration. As such assigning locals
or null_resource
will not work.
Sadly I am not that familiar with Golang so my search through the code base was fruitless. My _guess_ is that the solution will involved converting the data type of the connection
logic from it's current to an assignable type.
I can use local to pass argument, but cannot pass an reference to a block, which can greatly help me to reduce code redundancy銆俆he error always be 'Unsupported argument', seems that it is not possible to assign one reference variable to a block though '='. The terraform dynamic block seems only be able to use referenced inside a block. is there a better solution?
Adding details to @f91og's comment, I'm running into that exact issue with condition blocks on AWS IAM policies.
One can expect in the context of a large set of IAM policies that common conditions can and do arise. Things like, "this access is only granted in the context of a certain VPC" or "this resource can only be created if the creator provides values for a certain required tag(s)".
A simple piece of syntactic sugar that would allow us to express repeatable blocks as lists would solve this nicely by naturally allowing for a simple amount of indirection:
For anything that supports
condition {
...
}
condition {
...
}
a natural extension would be
conditions = [
{ ... },
{ ... }
]
With this simple improvement I could reference and reuse local variables for the contents of conditions, like so:
locals {
conditions = {
has_created_by_tag = {
test = "StringEquals"
values = ["created_by"]
variable = "aws:TagKeys"
}
is_in_target_vpc = {
test = "StringEquals"
values = [data.aws_vpc.target_vpc.arn]
variable = "ec2:vpc"
}
}
}
And reference them later:
statement {
sid = "..."
effect = "..."
actions = [
...
]
resources = [...]
# note that the module in question presently takes 0..n condition{ } blocks.
conditions = [
local.conditions.is_in_target_vpc,
local.has_created_by_tag
]
}
This would save me a bunch of code and allow me to stop repeating myself, which would in turn allow fewer opportunities for copy/paste errors and a central place to view and manage my conditions should they change.
N.B.: Dynamic blocks are way overkill for this use case, and actually increase the amount of code and repetition with respect to baseline.
Most helpful comment
Still do not understand how can I use locals for connection.
Tried:
And got an error: