Terraform: Specify specific Docker Network Name for Output - terraform

I have a Containerized Network Function (CNF) that connects to three Docker Networks:
...
ip_address = "172.17.0.3"
ip_prefix_length = 16
ipc_mode = "private"
log_driver = "json-file"
log_opts = {}
logs = false
max_retry_count = 0
memory = 4096
memory_swap = -1
must_run = true
name = "c-router-52"
network_data = [
{
gateway = "172.17.0.1"
ip_address = "172.17.0.3"
ip_prefix_length = 16
network_name = "bridge"
},
{
gateway = "172.31.0.1"
ip_address = "172.31.0.4"
ip_prefix_length = 16
network_name = "inside-net"
},
{
gateway = "172.30.0.1"
ip_address = "172.30.0.3"
ip_prefix_length = 16
network_name = "outside-net"
},
]
network_mode = "default"
...
And I am trying to grab the 'outside-net' IP address for use as an input for another container. I am specifying like so:
${docker_container.c-router-52.network_data[2].ip_address}
When its the third element, it works fine.... But the problem is that Terraform (or Docker, one of the two) doesn't always put the 'outside-net' as the third network :(
Is there a way to specify the [network_name="outside-net"] rather than an index number?

Since your code example isn't complete I'm having to guess a little here, but it seems like what you want is a mapping from network name to IP address. You can derive such a data structure from your resource configuration using a for expression, which you can assign to a local value for use elsewhere in the configuration:
locals {
container_ip_addresses = {
for net in docker_container.c-router-52.network_data :
net.network_name => net.ip_address
}
}
With the above definition in your module, you can refer to local.container_ip_addresses elsewhere in your module to refer to this mapping, such as local.container_ip_addresses["outside-net"] to access the outside-net address in particular.
With the network_data structure you showed in your configuration, local.container_ip_addresses would have the following value:
{
bridge = "172.17.0.3"
inside-net = "172.31.0.4"
outside-net = "172.30.0.3"
}
If you need to access the other attributes of those network_data objects, rather than just the ip_address, you can generalize this by making the values of the mapping be the full network objects:
locals {
container_networks = {
for net in docker_container.c-router-52.network_data :
net.network_name => net
}
}
...which would then allow you to access all of the attributes via the network name keys:
local.container_networks["outside-net"].ip_address
local.container_networks["outside-net"].gateway
local.container_networks["outside-net"].ip_prefix_length

Related

aws_network_interface_sg_attachment module on adding new SG , destroying existing SG

AWS instance , Elastic Network Interface (ENI) has existing security group (SG_main).
Adding new SG (sg-01) using aws_network_interface_sg_attachment resource.
sg-01 is getting added , SG_main has no affect on it.
Adding another sg-02 using aws_network_interface_sg_attachment resource.
destroy's sg-01 , attaches sg-02 and SG_main(no change).
Is there any other module don't destroy existing SG or Is there any other way to achieve this.
Input variable -
sg_attachment = [
{
attach_security_group_id = "sg-01"
}
]
Input variables are given in above form , as I was planning to give multiple values later like below.
["sg-01","sg-02","sg-03"] -> will be mapped with EC2[0]
["sg-04","sg-05"] -> will be mapped with EC2[1]
.. like wise.
sg_attachment = [
{
attach_security_group_id = ["sg-01","sg-02","sg-03"]
},
{
attach_security_group_id = ["sg-04","sg-05"]
}
]
resource block
resource "aws_network_interface_sg_attachment" "sg_attachment" {
count = length(var.sg_attachment) != 0 ? length(var.sg_attachment) : 0
security_group_id = var.sg_attachment[count.index].attach_security_group_id
network_interface_id = aws_instance.EC2[count.index].primary_network_interface_id
}
Is there any other way to achieve this.
You would have to use aws_network_interface data source to query for the existing security_groups for your ENI.
data "aws_network_interface" "existing_sgs" {
count = length(aws_instance.EC2)
id = aws_instance.EC2[count.index].primary_network_interface_id
}
Once you have existing_sgs you would have to concat them with the new ones. Otherwise, the old ones get replaced with the new ones as you already observed.

Pass/Override Terraform object variable via command line

I have a Terraform configuration (v0.14, AWS provider 3.32.0) in which I have defined the parameters for certain resource types as objects (input variables). Here is an (simplified) example:
variable "docker_config" {
type = object({
internal = number
external = number
protocol = string
})
default =
{
internal = 8300
external = 8300
protocol = "tcp"
}
}
However, I would like to be able to override individual values via command line or environment variable.
Can I overwrite individual values of the object without having to pass the whole object? So, use default values and override them as needed:
e. g.
TF_VAR_docker_config.internal = 1234
or
TF_VAR_docker_config = "{internal = 1234}"
And this would result in:
{
internal = 1234
external = 8300
protocol = "tcp"
}
My tests did not work. Is it possible? If yes, how?
Sadly, you can't do this directly. In near future optional should be added to object which will simplify this.
For now, a non-perfect workaround could be to use merge with regular map:
variable "docker_config_default" {
type = object({
internal = number
external = number
protocol = string
})
default = {
internal = 8300
external = 8300
protocol = "tcp"
}
}
variable "docker_config" {
type = map
validation {
# condition is not perfect and suited only for one
# attribute overwrite
condition = (can(var.docker_config["internal"])
|| can(var.docker_config["external"])
|| can(var.docker_config["protocol"]))
error_message = "Must have internal, external or protocol."
}
}
locals {
docker_config = merge(var.docker_config_default, var.docker_config)
}
output "test" {
value = local.docker_config
}
And you just use local.docker_config in your code.

GCP Terraform: Setting port for backend service

Reading through the docs here:
https://www.terraform.io/docs/providers/google/r/compute_backend_service.html
We can define backend service:
resource "google_compute_backend_service" "kubernetes-nginx-prod" {
name = "kubernetes-nginx-prod"
health_checks = [google_compute_health_check.kubernetes-nginx-prod-healthcheck.self_link]
backend {
group = replace(google_container_node_pool.pool-1.instance_group_urls[0], "instanceGroupManagers", "instanceGroups")
# TODO missing port 31443
}
}
It seems we are unable to set backend service port via the Terraform settings:
Recreating the backend service without this settings actually leads to downtime for us and the port must be written manually.
We need to reference the port name that we gave in the instance group for e.g.
resource "google_compute_backend_service" "test" {
name = "test-service"
port_name = "test-port"
protocol = "HTTP"
timeout_sec = 5
health_checks = []
backend {
group = "${google_compute_instance_group.test-ig.self_link}"
}
}
resource "google_compute_instance_group" "test-ig" {
name = "test-ig"
instances = []
named_port {
name = "test-port"
port = "${var.app_port}"
}
zone = "${var.zone}"
}
I resolved this by using a terraform data source to extract the instance group data from my google_container_node_pool, and then appending a google_compute_instance_group_named_port resource to it.
NOTE my google_container_node_pool spans 3 zones (a,b,c) and the below code highlights the solution for only zone c
# extract google_compute_instance_group from google_container_node_pool
data "google_compute_instance_group" "instance_group_zone_c" {
# .2 refers to the last of the 3 instance groups in my node pool (zone c)
name = regex("([^/]+)/?$", "${google_container_node_pool.k8s_node_pool.instance_group_urls.2}").0
zone = regex("${var.region}-?[abc]", "${google_container_node_pool.k8s_node_pool.instance_group_urls.2}")
project = var.project_id
}
# define a named port to attach to the google_compute_instance group in my node pool
resource "google_compute_instance_group_named_port" "named_port_zone_c" {
group = data.google_compute_instance_group.instance_group_zone_c.id
zone = data.google_compute_instance_group.instance_group_zone_c.zone
name = "port31090"
port = 31090
}

Terraform: How to get value of second public ip in module cloudposse ec2

How I can get value of second IP address from Terraform module EC2.
Module- https://github.com/cloudposse/terraform-aws-ec2-instance
I've created instance EC2 with parameter additional_ips_count = 1. In this situation instance has create with two network interface and I need get value public IP address of second network interface.
Normally the module allows me to extract the value of public ip from the value of public_ip. For example, if I create a module called server, I can get the value of the public IP address from the first network interface using the value module.server.public_ip but how to do it for the second network created using a variable additional_ips_count = 1.
You can parse out the values for the value returned from the output module.multiple_ip.additional_eni_ids.
module "multiple_ip" {
source = "git::https://github.com/cloudposse/terraform-aws-ec2-instance.git?ref=master"
ssh_key_pair = var.key_name
vpc_id = var.vpc_id
security_groups = [data.aws_security_group.this.id]
subnet = data.aws_subnet.private_subnet.id
associate_public_ip_address = true
name = "multiple-ip"
namespace = "eg"
stage = "dev"
additional_ips_count = 1
ebs_volume_count = 2
allowed_ports = [22, 80, 443]
instance_type = var.instance_type
}
output "multiple_ip_additional_eni_ids" {
value = module.multiple_ip.additional_eni_ids
}
output "z_additional_eni_public_ip" {
value = values(module.multiple_ip.additional_eni_ids)[0]
}
This will return the ip want.
Outputs:
multiple_ip_additional_eni_ids = {
"eni-0cc853fb9301b2bc8" = "100.20.97.243"
}
z_additional_eni_public_ip = 100.20.97.243

How to conditionally populate an argument value in terraform?

I am writing a Terraform script to spin up resources in Google Cloud Platform.
Some resources require one argument only if the other one set, how to populate one argument only if the other one is populated (or any other similar condition)?
For example:
resource "google_compute_router" "compute_router" {
name = "my-router"
network = "${google_compute_network.foobar.name}"
bgp {
asn = 64514
advertise_mode = "CUSTOM"
advertised_groups = ["ALL_SUBNETS"]
advertised_ip_ranges {
range = "1.2.3.4"
}
advertised_ip_ranges {
range = "6.7.0.0/16"
}
}
}
In the above resource (google_compute_router) the description for both advertised_groups and advertised_ip_ranges says This field can only be populated if advertise_mode is CUSTOM and is advertised to all peers of the router.
Now if I keep the value of advertise_mode as DEFAULT, my code looks something like below:
resource "google_compute_router" "compute_router" {
name = "my-router"
network = "${google_compute_network.foobar.name}"
bgp {
asn = 64514
#Changin only the value below
advertise_mode = "DEFAULT"
advertised_groups = ["ALL_SUBNETS"]
advertised_ip_ranges {
range = "1.2.3.4"
}
advertised_ip_ranges {
range = "6.7.0.0/16"
}
}
}
The above script however on running gives the following error:
* google_compute_router.compute_router_default: Error creating Router: googleapi: Error 400: Invalid value for field 'resource.bgp.advertiseMode': 'DEFAULT'. Router cannot have a custom advertisement configurati
on in default mode., invalid
As a workaround to the above, I have created two resources with different names doing almost the same thing. The script looks something like below:
resource "google_compute_router" "compute_router_default" {
count = "${var.advertise_mode == "DEFAULT" ? 1 : 0}"
name = "${var.router_name}"
region = "${var.region}"
network = "${var.network_name}"
bgp {
asn = "${var.asn}"
advertise_mode = "${var.advertise_mode}"
#Removed some codes from here
}
}
resource "google_compute_router" "compute_router_custom" {
count = "${var.advertise_mode == "CUSTOM" ? 1 : 0}"
name = "${var.router_name}"
region = "${var.region}"
network = "${var.network_name}"
bgp {
asn = "${var.asn}"
advertise_mode = "${var.advertise_mode}"
advertised_groups = ["${var.advertised_groups}"]
advertised_ip_ranges {
range = "${var.advertised_ip_range}"
description = "${var.advertised_ip_description}"
}
}
}
The above script works fine, however it seems like a lot of code repetition to me and a hack. Also, for two options (of dependent attributes) is fine, however, if there are more options say 5, the code repetition for such a small thing would be too much.
Is there a better way to do what I am trying to achieve?
This is pretty much what you are restricted to in Terraform < 0.12. Some resources allow you to use an empty string to omit basic values and the provider will interpret this as a null value, not passing it to the API endpoint so it won't complain about it not being set properly. But from my brief experience with the GCP provider this is not the case for most things there.
Terraform 0.12 introduces nullable arguments which would allow you to set these conditionally with something like the following:
variable "advertise_mode" {}
resource "google_compute_router" "compute_router" {
name = "my-router"
network = "${google_compute_network.foobar.name}"
bgp {
asn = 64514
advertise_mode = "${var.advertise_mode}"
advertised_groups = ["${var.advertise_mode == "DYNAMIC" ? ALL_SUBNETS : null}"]
advertised_ip_ranges {
range = "${var.advertise_mode == "DYNAMIC" ? 1.2.3.4 : null}"
}
advertised_ip_ranges {
range = "${var.advertise_mode == "DYNAMIC" ? 6.7.0.0/16 : null}"
}
}
}
It will also introduce dynamic blocks that you are able to loop over so you can also have a dynamic number of advertised_ip_ranges blocks.
The above answer is incorrect as 'advertised_ip_ranges' wont accept a null value; the solution to this is to leverage a dynamic block which can handle a null value for this resource and further enables the resource to accept a variable number of ip ranges.
variable custom_ranges {
default = ["172.16.31.0/24","172.16.32.0/24"]
}
resource "google_compute_router" "router_01" {
name = "cr-bgp-${var.gcp_bgp_asn}"
region = var.gcp_region
project = var.gcp_project
network = var.gcp_network
bgp {
asn = var.gcp_bgp_asn
advertise_mode = var.advertise_mode
advertised_groups = var.advertise_mode == "CUSTOM" ? ["ALL_SUBNETS"] : null
dynamic "advertised_ip_ranges" {
for_each = var.advertise_mode == "CUSTOM" ? var.custom_ranges : []
content {
range = advertised_ip_ranges.value
}
}
}
}
additional search keys: google_compute_router "bgp.0.advertised_ip_ranges.0.range" wont accept a null value.

Resources