Look for the resource to match a key word - terraform

Suppose I have two kineses, I'd like to get the one of them with the key word _consumer.
variable "kinesis" {
default = ["kinesis_publisher", "kinesis_consumer"]
}
resource "aws_kinesis_stream" "test_stream" {
count = "${length(var.kinesis)}"
name = "${var.kinesis[count.index]}"
shard_count = 1
retention_period = 48
shard_level_metrics = [
"IncomingBytes",
"OutgoingBytes",
]
tags = {
Environment = "test"
}
}
How do I get consumer arn only?
output "kinesis_consumer_arn" {
value = "??? lookup or matchkeys with _consumer ???"
}
It is not always the same sequence and will be many kinesis. So I can't use 0 or 1 directly.

You may create modules for each kafka stream , thereby giving more control on variables passed into and derived from the resources.
module "kinesis_publisher" {
source = "../modules/test_stream"
stream_name = "kinesis_publisher"
}
module "kinesis_consumer" {
source = "../modules/test_stream"
stream_name = "kinesis_consumer"
}
And then output can be filtered on basis of modules.
output "kinesis_consumer_arn" {
value = "{module.kinesis_consumer.arn}"
}

Related

adding multiple destinations to new relic workflows using terraform

I am trying to create a newrelic workflow using terraform modules. I am fine with creating a workflow with signle destination. But, I am trying to create a workflow with more than one destination.
slack channel ids
variable "channel_ids" {
type = set(string)
default = ["XXXXXXXXXX","YYYYYYYYY"]
}
creating notification channels using slack channel ids
resource "newrelic_notification_channel" "notification_channel" {
for_each = var.channel_ids
name = "test" # will modify if required
type = "SLACK" # will parameterize this
destination_id = "aaaaaaaaa-bbbbb-cccc-ddddd-eeeeeeeeee"
product = "IINT"
property {
key = "channelId"
value = each.value
}
}
Now I want to create something like below (two destinations)
resource "newrelic_workflow" "newrelic_workflow" {
name = "my-workflow"
muting_rules_handling = "NOTIFY_ALL_ISSUES"
issues_filter {
name = "Filter-name"
type = "FILTER"
predicate {
attribute = "accumulations.policyName"
operator = "EXACTLY_MATCHES"
values = [ "policy_name" ]
}
}
destination {
channel_id = newrelic_notification_channel.notification_channel.id
}
destination {
channel_id = newrelic_notification_channel.notification_channel.id
}
}
I tried using for_each and for loop but no luck. Any idea on how to get my desired output?
Is it possible to loop through and create multiple destinations within the same resource, like attaching multiple destination to a single workflow?
I was able to achieve this by using a dynamic block, which produces a dynamic number of destination blocks based on the number of elements of newrelic_notification_channel.notification_channel.
resource "newrelic_workflow" "newrelic_workflow" {
name = "my-workflow"
muting_rules_handling = "NOTIFY_ALL_ISSUES"
issues_filter {
name = "Filter-name"
type = "FILTER"
predicate {
attribute = "accumulations.policyName"
operator = "EXACTLY_MATCHES"
values = [ "policy_name" ]
}
}
dynamic "destination" {
for_each = newrelic_notification_channel.notification_channel
content {
channel_id = destination.value.id
}
}
}

Remove duplicates from output

I have the following output:
output "regions_data" {
value = regions({
for region, data in var.regions :
regions => "${region}/${data.postcode}"
})
}
Which contains duplicates like(it is intentional):
regions = {
reg1 = {
postcode = "1"
},
reg1 = {
postcode = "1"
},
reg2 = {
postcode = "2"
}
}
How can I remove the duplicates from the output?
Your code does not comply to the basic rules of maps or objects. Nor there is any regions function you use in the code. The provided code is not a proper Terraform syntax.
I believe however, you might have meant the following example:
variable "regions" {
default = {
reg1 = [
{
postcode = 1
area = "oak-county"
},
{
postcode = 2
area = "birch-county"
}
],
reg2 = [
{
postcode = 1
area = "fir-county"
},
{
postcode = 2
area = "pine-county"
}
],
}
}
In a case, when the two maps have the same keys, you can use flatten to break up everything to pieces, then rejoin everything back together:
locals {
flatten = flatten([
for region_key, region in var.regions : [
for area in region :
{
key = "${region_key}-${area.postcode}"
value = area.area
}
]
])
}
output "flattened_regions" {
value = local.flatten
}
output "remap" {
value = { for key, data in local.flatten :
data.key => data.value
}
}
Even if the code above doesn't exactly fit your case, please experiment in a similar manner - or, provide more complete example of variables you have and the outcome you need.
Source: https://www.terraform.io/language/functions/flatten
Probably what you want
I have no idea what you've meant by regions in value = regions({ but I assume that this code will do what you want:
locals {
regions = {
reg1 = {
postcode = "1"
},
reg1 = {
postcode = "1"
},
reg2 = {
postcode = "2"
}
}
}
output "regions_data" {
value = {
for region, data in local.regions :
region => "${region}/${data.postcode}"
}
}
Keep in mind that I replaced var with local to have one file.
And output of such is:
regions_data = {
"reg1" = "reg1/1"
"reg2" = "reg2/2"
}
Thought be warned that it will use one of keys. It doesn't check for duplicates. It just takes first one.
But why you shouldn't want it
This solution is quite bad for multiple reasons:
You provide variables - why the heck would you put duplicates? :)
As I said this merge will not necessarily provide the output you what
If var is provided by some terraform code (e.g. this regions_data is in module) then logic of merging should be done outside of module and probably terraform's own merge would be the answer.

snowflake terraform create multiple table in one tf files

I am trying to create multiple table through tf in snowflake.
Below are the sample code.
resource "snowflake_table" "table" {
database = "AMAYA"
schema = "public"
name = "info"
comment = "A table."
column {
name = "id"
type = "int"
nullable = true
default {
sequence = snowflake_sequence.sequence.fully_qualified_name
}
}
column {
name = "identity"
type = "NUMBER(38,0)"
nullable = true
identity {
start_num = 1
step_num = 3
}
}
resource "snowflake_table" "table" {
database = "AMAYA"
schema = "public"
name = "arch_info"
comment = "A table."
column {
name = "id"
type = "int"
nullable = true
default {
sequence = snowflake_sequence.sequence.fully_qualified_name
}
}
column {
name = "identity"
type = "NUMBER(38,0)"
nullable = true
identity {
start_num = 1
step_num = 3
}
}
}
When I run this script I get the error.
A snowflake_procedure resource named "table" was already declared at str.tf:16,1-38. Resource names must be unique per type in each module.
The only solution I have tried and worked is to create different files for different table. however I have 100 of tables to create, and was wondering if there is simpler way of putting all in one file and run the script
You can't use the same name for a resource more than once, like tablebelow:
resource "snowflake_table" "table" {
Use different names:
resource "snowflake_table" "table_1" {
You should look into for_each and dynamic functions when needing to create lots of the same resource with different parameters:
Terraform for_each
Terraform dynamic
With those, you can create complex maps that are defined on input and automatically create the required amount of resources, something like below (just an example with a couple of parameters):
locals {
snowflake_tables = {
info = {
database = "AMAYA"
...
columns = {
identity = {
type = "NUMBER(38,0)"
nullable = true
...
}
}
}
}
}
resource "snowflake_table" "table" {
for_each = local.snowflake_tables
name = each.key # info
database = each.value.database # AMAYA
...
dynamic "column" {
for_each = each.value.columns
content {
name = setting.key
type = setting.value["type"]
nullable = setting.value["nullable"]
...
}
}
}
With this technique, all you do is add more objects to the map for tables and columns. I've set the example in locals but you could have this as a variable input instead in a .tfvars file etc.

flattening output contents of a composite map

I have two module that output respectively
output "discovery_service_hostname" {
value = "${aws_appmesh_virtual_service.service.name}"
}
and
output "discovery_service_arn" {
value = zipmap( aws_service_discovery_service.sd[*].name, aws_service_discovery_service.sd[*].arn)
}
Both are used in the main script that outputs
output "services" {
value = {
"web" = "${module.web.discovery_service_hostname}"
"wwb-backend" = "${module.web_backend.discovery_service_hostname}"
"wwb-backend-n" = "${module.web_backend_n.discovery_service_hostname}"
}
}
in this case I used the 1st module for web andweb-backend, while I used the 2nd module for web-backend-n
I need to access the service arn via lookup function in a 3rd script, but I would avoid duplicating the whole code to handle the two cases
final output like this
discovery_service = {
"web" = "arn:xxx1"
"web-backend" = "arn:xxx2"
"web-backend-n" = {
"web-backend-n-1" = "arn:xxx3"
"web-backend-n-2" = "arn:xxx4
"web-backend-n-3" = "arn:xxx5"
}
Is there a way to have an output like
discovery_service = {
"web" = "arn:xxx1"
"web-backend" = "arn:xxx2"
"web-backend-n-1" = "arn:xxx3"
"web-backend-n-2" = "arn:xxx4
"web-backend-n-3" = "arn:xxx5"
}
thanks!
I will answer my own question. Solution is to always output a map (even from the module with single outputs) like this:
output "discovery_service_arn" {
value = zipmap( [ aws_service_discovery_service.sd.name ], [ aws_service_discovery_service.sd.arn ])
}
and
output "discovery_service_arn" {
value = zipmap( aws_service_discovery_service.sd[*].name, aws_service_discovery_service.sd[*].arn)
}
then in the final script use merge to get a single map like
output "discovery_service" {
value = merge(
module.web.discovery_service_arn,
module.web_backend.discovery_service_arn,
module.web_backend_n.discovery_service_arn
)
}

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