snowflake terraform create multiple table in one tf files - terraform

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.

Related

Terraform: referencing each.key or each.value in module when calling variables

I'm trying to achieve (maybe by wrong means) something like that. I'd like to be able to create few types of endpoints in Azure (KV, SA for example).
module "endpoints" {
source = "./modules/private_endpoint"
for_each = toset(var.endpoint_type)
private_connection_resource_id = "var.${each.value}.private_connection_resource_id"
Where:
Endpoint_type is a list of endpoints (its value is "storage_account"),
private_connection_resource_id is in map(any) which looks like (there are other values, but I don't think they're important at this point):
storage_account = {
private_connection_resource_id = #VALUE
...
private_connection_resource_id = "var.${each.value}.private_connection_resource_id" --- this gets translated to literal string (var.storage_account.private_connection_resource_id), where I'd like it to get translated to exact value - the id of storage account (it's hardcoded in tfvars).
Thank you in advance for any tips!
Edit: It appears that Im as dumb as they come. Should've changed the map a bit:
endpoint_type = {
storage_account = {
private_connection_resource_id = #VALUE
...
And ref in module calling to: each.value.private_connection_resource_id
You cannot construct an expression inside a string and then evaluate it. Terraform always parses the full configuration first and then executes it once already parsed.
If you want to look up values dynamically based on a key then a map is the most appropriate data structure to use for that purpose. For example, you could define a input variables endpoint_types and endpoints like this:
variable "endpoint_types" {
type = map(object({
private_connection_resource_id = string
}})
}
variable "endpoints" {
type = map(object({
type = string
}))
}
My intent with the above example is that the type attribute inside the endpoints objects is a lookup key for the other map in endpoint_types.
When you then define your module block with for_each you will first refer to var.endpoints and then look up an appropriate endpoint type in var.endpoint_types based on its selected key:
module "endpoints" {
source = "./modules/private_endpoint"
for_each = var.endpoints
private_connection_resource_id = var.endpoint_types[each.value.type].private_connection_resource_id
}
The user of the outer module would need to provide both a map of endpoints and a map describing all of the possible types those endpoints might have, like this:
endpoints = {
storage_1 = {
type = "storage"
}
storage_2 = {
type = "storage"
}
other_1 = {
type = "other"
}
}
endpoint_types = {
storage = {
private_connection_resource_id = "something"
}
other = {
private_connection_resource_id = "something_else"
}
}

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
}
}
}

Is there any method for looping over all variables in Terraform?

I'd like to have a resource block that can loop over every variable that I've defined with variable blocks. Is this possible?
For Example: Assuming I set myfirstvar and mysecondvar in a tfvar file. I'm searching for a solution that would take the following template and deploy all the names, values and descriptions to Terraform Cloud.
variable "myfirstvar" {
type = string
description = "a var to upload"
}
variable "mysecondvar" {
type = string
description = "another var to upload"
}
resource "tfe_variable" "test" {
for_each = var
key = currentvar.key
value = currentvar.value
category = "terraform"
workspace_id = tfe_workspace.test.id
description = currentvar.description
}
So far the only solutions I can think of are to put all tfvars in a single list type variable, but that wouldn't have the additional info from the variable blocks. Or I could do some further parsing in another program on the variable blocks and tfvars files gather all the info necessary. Neither is ideal.
The the answer to your direct question is no, because var is not actually an object in Terraform. Instead, it's just a prefix that all variable symbols have. This distinction is important in Terraform because it works by creating a dependency graph based on your references between objects, and there is no node in the dependency graph representing "all variables" and so there's no way for Terraform to represent a reference to it.
With that said, the typical way to achieve a similar result is to write a single variable that has a map type, like this:
variable "tfe_variables" {
type = map(object({
value = string
description = string
}))
}
resource "tfe_variable" "test" {
for_each = var.tfe_variables
key = each.key
value = each.value.value
category = "terraform"
workspace_id = tfe_workspace.test.id
description = each.value.description
}
Then when you define this variable you can set it to a map of objects, like this:
tfe_variables = {
myfirstvar = {
value = "example"
description = "a var to upload"
}
mysecondvar = {
value = "example"
description = "another var to upload"
}
}
It seems like you'd prefer to have the variables of this configuration match the variables defined on the workspace you're managing with it. There's no direct way to do that, because the variable definitions of the current configuration are metadata for outside consumption and not directly accessible by the module itself. The closest you could get to it is to construct the necessary map explicitly, so that Terraform can see all of the necessary dependency edges in order to execute the program:
variable "myfirstvar" {
type = string
}
variable "mysecondvar" {
type = string
}
locals {
variables = tomap({
myfirstvar = {
value = var.myfirstvar
description = "a var to upload"
}
mysecondvar = {
value = var.mysecondvar
description = "another var to upload"
}
})
}
resource "tfe_variable" "test" {
for_each = local.variables
key = each.key
value = each.value.value
category = "terraform"
workspace_id = tfe_workspace.test.id
description = each.value.description
}

How to handle optional dynamic blocks in terraform

I am trying to iterate in resource "launchdarkly_feature_flag" variables with dynamic block that might have optional nested objects - "variations" (could be 0 or 2+):
variable "feature_flags" {
default = {
flag_1 = {
project_key = "project"
key = "number example"
name = "number example flag"
description = "this is a multivariate flag with number variations."
variation_type = "number"
variations = {
value = 100
}
variations = {
value = 300
}
tags = ["example"]
},
flag_2 = {
project_key = "project"
key = "boolean example"
name = "boolean example flag"
description = "this is a boolean flag"
variation_type = "boolean"
tags = ["example2"]
}
}
}
Ive tried various scenarios of how to get all flag and always face different problems. Piece of code:
resource "launchdarkly_feature_flag" "ffs" {
for_each = var.feature_flag_map
project_key = each.value.project_key
key = each.value.key
name = each.value.name
description = each.value.description
variation_type = each.value.variation_type
# main problem here
dynamic variations {
for_each = lookup(each.value, "variations", {}) == {} ? {} : {
content {
value = each.value.variations.value
}
}
}
tags = each.value.tags
}
Could you please help with that? I am using 0.14v of Terraform
The first step would be to tell Terraform what type of value this variable expects. While it's often okay to omit an explicit type for a simple value and let Terraform infer it automatically from the default, when the data structure is this complex it's better to tell Terraform what you intended, because then you can avoid it "guessing" incorrectly and giving you confusing error messages as a result.
The following looks like a suitable type constraint for the default value you showed:
variable "feature_flags" {
type = map(object({
project_key = string
key = string
name = string
description = string
variation_type = string
tags = set(string)
variations = list(object({
value = string
}))
}))
}
With the type written out, Terraform will guarantee that any var.feature_flags value conforms to that type constraint, which means that you can then make your dynamic decisions based on whether the values are null or not:
resource "launchdarkly_feature_flag" "ffs" {
for_each = var.feature_flags
project_key = each.value.project_key
key = each.value.key
name = each.value.name
description = each.value.description
variation_type = each.value.variation_type
tags = each.value.tags
dynamic "variations" {
for_each = each.value.variations != null ? each.value.variations : []
content {
variations.value.value
}
}
}
As written above, Terraform will require that all values in feature_flags have all of the attributes defined, although the caller can set them to null to indicate that they are unset.
At the time of writing, in Terraform v0.14, there is an experimental feature for marking attributes as optional which seems like it would, once stabilized, be suitable for this use-case. Marking some or all of the attributes as optional would allow callers to omit them and thus cause Terraform to automatically set them to null, rather than the caller having to explicitly write out the null value themselves.
Hopefully that feature is stabilized in v0.15, at which point you could return to this and add the optional annotations to some attributes without changing anything else about the module.

Terraform compare keys two different maps and copy value

I have module which outputs map, module.platformusers.paths, like this:
{
"user1_test" = "user1_value_path"
"user2_test" = "user2_value_path"
"user3_test" = "user3_value_path"
}
I have to iterate over local.musyers map and take the key and compares with module.platformusers.paths key, and if the key contains in the second map, then I have to copy the value
{
"user1" = "Allen"
"user2" = "john"
"user3" = "Rose"
}
I want to have something like this when I am assuming if the are individual
resource "aws_ssm_parameter" "userspath" {
name = "Allen"
type = "String"
value = "user1_value_path"
}
resource "aws_ssm_parameter" "userspath" {
name = "john"
type = "String"
value = "user2_value_path"
}
resource "aws_ssm_parameter" "userspath" {
name = "Rose"
type = "String"
value = "user3_value_path"
}
I am trying as shown here, but it is not working as my keys are not an exact match:
resource "aws_ssm_parameter" "userspath" {
for_each = module.platformusers.paths
name = ${each.value}
type = "String"
value = lookup( module.platformusers.paths, ${each.key}, "")
}
How to apply contains here ?
There might be better ways to do this, but here is one quick way i could think of..
If the difference in keys going to be just "_test" as you have shown above, it might be simple actually. You can use format function to add "_test" up and do a lookup.
In you sample code, you were not looping against local.musyers..
Note: its case sensitive. You can use UPPER function to make it insensitive.
resource "aws_ssm_parameter" "userspath" {
for_each = local.musyers
name = ${each.value}
type = "String"
value = lookup(module.platformusers.paths, format("%s_test", each.key) , "missing")
}

Resources