tfe_variable JSON to HCL support - terraform

I'm hitting Error: Incorrect attribute value type when passing var_3 below to a tfe_variable of type HCL.
Is there a way to convert the decoded JSON variable to HCL?
My config.json:
{
"vars": {
"var_1": "foo",
"var_2": "bar",
"var_3": {
"default": "foo"
}
}
}
My terraform config:
variable "tfe_token" {}
provider "tfe" {
hostname = "app.terraform.io"
token = var.tfe_token
}
data "tfe_workspace" "this" {
name = "my-workspace-name"
organization = "my-org-name"
}
locals {
json_config = jsondecode(file("config.json"))
}
resource "tfe_variable" "workspace" {
for_each = local.json_config.vars
workspace_id = data.tfe_workspace.this.id
key = each.key
value = each.value
category = "terraform"
hcl = true
sensitive = false
}

Related

Terraform dynamic block missing an argument or block definition

I am attempting to use a dynamic block to define multiple ip_rules and virtual_network exceptions in one resource. For some reason, when I attempt to use a variable as my for_each loop, it says the following errors.
variable "vnet_subnet_ids" {
description = "List of strings that are VNet Subnet IDs to whitelist."
type = list(string)
default = [
"/subscriptions/${subscription_id}/resourceGroups/${rg_name}/providers/Microsoft.Network/virtualNetworks/nonprod-vnet-gp-kubernetes/subnets/pods_pub_subnet_01",
"/subscriptions/${subscription_id}/resourceGroups/${rg_name}/providers/Microsoft.Network/virtualNetworks/nonprod-vnet-gp-kubernetes/subnets/pods_pub_subnet_02",
]
sensitive = false
}
resource "azurerm_container_registry" "devops" {
name = var.acr_name
resource_group_name = var.rg_name
location = var.rg_location
sku = var.acr_sku
admin_enabled = false
georeplication_locations = var.acr_geo_rep_locations
network_rule_set {
default_action = "Deny"
dynamic "ip_rule" {
for_each = [1]
content {
action = "Allow"
ip_range = "xxx.xxx.xxx.xxx/32"
}
}
#dynamic "ip_rule" {
# for_each = var.acr_ip_rules
# content {
# action = "Allow"
# ip_range = ip_rule.value
# }
#}
dynamic "virtual_network" {
for_each = var.vnet_subnet_ids
content {
action = "Allow"
subnet_id = virtual_network.value
}
}
tags = var.company_tags
}
However, I get the following error:
│ Error: Argument or block definition required
│
│ On ../../modules/azure/acr/main.tf line 41: An argument or block definition is required here.
╵
The part with the ip_rule works, but the virtual_network part does not. I do not understand why.
There seems to be an open bug related to this. I can't test this at the moment, but see if this variation works for you:
variable "acr_name" { default = "acr_name" }
variable "rg_location" { default = "rg_location" }
variable "acr_sku" { default = "acr_sku" }
variable "subscription_id" { default = "subscription_id" }
variable "rg_name" { default = "rg_name" }
variable "acr_geo_rep_locations" { default = "acr_geo_rep_locations" }
variable "company_tags" { default = "company_tags" }
variable "acr_ip_rules" { default = ["1", "2"]}
variable "vnet_subnet_ids" { default = ["1", "2"]}
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "=2.79.1"
}
}
}
provider "azurerm" {
features {}
}
locals {
allowed_ips = [for ip in var.acr_ip_rules : {
action = "Allow",
ip_range = ip
}]
allowed_virtual_networks = [for sub in var.vnet_subnet_ids : {
action = "Allow",
subnet_id = sub
}]
}
resource "azurerm_container_registry" "devops" {
name = var.acr_name
resource_group_name = var.rg_name
location = var.rg_location
sku = var.acr_sku
admin_enabled = false
georeplication_locations = var.acr_geo_rep_locations
network_rule_set {
default_action = "Deny"
ip_rule = local.allowed_ips
virtual_network = local.allowed_virtual_networks
}
tags = var.company_tags
}
Turns out I'm missing a }. So, I'm on to new errors listed here
https://github.com/hashicorp/terraform/issues/22340

create terraform hcl variable as a map

I am working on terraform workspace automation using tfe provider and want to create a terraform HCL variable as a map using custom_tags from the below data structure.
workspaces = {
"PROD" = {
"custom_tags" = {
"Application" = "demo"
"EnvironmentType" = "prod"
"NamePrefix" = "sof"
"ProductType" = "terraform"
}
"env_variables" = {}
"id" = "alfsdfksf"
"name" = "PROD"
"repo" = "github/something"
"tf_variables" = {}
}
"UAT" = {
"custom_tags" = {
"Application" = "demo"
"EnvironmentType" = "uat"
"NamePrefix" = "sof"
"ProductType" = "terraform"
}
"env_variables" = {}
"id" = "ws-k7KWYfsdfsdf"
"name" = "UAT"
"repo" = "github/otherthing"
"tf_variables" = {}
}
}
Here is my resource block
resource "tfe_variable" "terraform_hcl_variables" {
for_each = { for w in local.workspaces : w.name => w }
key = "custom_tags"
value = each.value.custom_tags
category = "terraform"
hcl = true
sensitive = false
workspace_id = tfe_workspace.main[each.key].id
}
And, I am getting this error. Any help is appreciated to resolve this.
**each.value.custom_tags is object with 4 attributes
Inappropriate value for attribute "value": string required.**
Expected outcome
custom_tags should be created as a HCL variable
custom_tags =
{
"Application" = "demo"
"EnvironmentType" = "prod"
"NamePrefix" = "sof"
"ProductType" = "terraform"
}
Sadly you can't do this. value attribute must be string, but you are trying to assign an "object with 4 attributes" to it.
You could convert your each.value.custom_tags into string using jsonencode, but this is probably not what you want.

Terraform dynamically generate attributes (not blocks)

I am trying to generate attributes dynamically in terraform 13. I've read through the docs but I can't seem to get this to work:
Given the following terraform:
#main.tf
locals {
secrets = {
secret1 = [
{
name = "user",
value = "secret"
},
{
name = "password",
value = "password123"
}
],
secret2 = [
{
name = "token",
value = "secret"
}
]
}
}
resource "kubernetes_secret" "secrets" {
for_each = local.secret
metadata {
name = each.key
}
data = {
[for name, value in each.value : name = value]
}
}
I would expect the following resources to be rendered:
resource "kubernetes_secret" "secrets[secret1]" {
metadata {
name = "secret1"
}
data = {
user = "secret"
password = "password123"
}
}
resource "kubernetes_secret" "secrets[secret2]" {
metadata {
name = "secret2"
}
data = {
token = "secret"
}
}
However I just get the following error:
Error: Invalid 'for' expression
on ../../main.tf line 96, in resource "kubernetes_secret" "secrets":
96: [for name, value in each.value : name = value]
Extra characters after the end of the 'for' expression.
Does anybody know how to make this work?
The correct syntax for generating a mapping using a for expression is the following:
data = {
for name, value in each.value : name => value
}
The above would actually be totally redundant, because it would produce the same value as each.value. However, because your local value has a list of objects with name and value attributes instead of maps from name to value, so to get a working result we'd either need to change the input to already be a map, like this:
locals {
secrets = {
secret1 = {
user = "secret"
password = "password123"
}
secret2 = {
token = "secret"
}
}
}
resource "kubernetes_secret" "secrets" {
for_each = local.secrets
metadata {
name = each.key
}
# each.value is already a map of a suitable shape
data = each.value
}
or, if the input being a list of objects is important for some reason, you can project from the list of objects to the mapping like this:
locals {
secrets = {
secret1 = [
{
name = "user",
value = "secret"
},
{
name = "password",
value = "password123"
}
],
secret2 = [
{
name = "token",
value = "secret"
}
]
}
}
resource "kubernetes_secret" "secrets" {
for_each = local.secrets
metadata {
name = each.key
}
data = {
for obj in each.value : obj.name => obj.value
}
}
Both of these should produce the same result, so which to choose will depend on what shape of local value data structure you find most readable or most convenient.

terraform How to use conditional if in for_each into map object

I have maps of variables like this:
users.tfvars
users = {
"testterform" = {
path = "/"
force_destroy = true
email_address = "testterform#example.com"
group_memberships = [ "test1" ]
tags = { department : "test" }
ssh_public_key = "ssh-rsa AAAAB3NzaC1yc2EAAA4l7"
}
"testterform2" = {
path = "/"
force_destroy = true
email_address = "testterform2#example.com"
group_memberships = [ "test1" ]
tags = { department : "test" }
ssh_public_key = ""
}
I would like to upload ssh key only if ssh_public_key not empty for the user. But don't understand how to check this
#main.tf
resource "aws_iam_user" "this" {
for_each = var.users
name = each.key
path = each.value["path"]
force_destroy = each.value["force_destroy"]
tags = merge(each.value["tags"], { Provisioner : var.provisioner, EmailAddress : each.value["email_address"] })
}
resource "aws_iam_user_group_membership" "this" {
for_each = var.users
user = each.key
groups = each.value["group_memberships"]
depends_on = [ aws_iam_user.this ]
}
resource "aws_iam_user_ssh_key" "this" {
for_each = var.users
username = each.key
encoding = "SSH"
public_key = each.value["ssh_public_key"]
depends_on = [ aws_iam_user.this ]
}
It sounds like what you need here is a derived "users that have non-empty SSH keys" map. You can use the if clause of a for expression to derive a new collection from an existing one while filtering out some of the elements:
resource "aws_iam_user_ssh_key" "this" {
for_each = {
for name, user in var.users : name => user
if user.ssh_public_key != ""
}
username = each.key
encoding = "SSH"
public_key = each.value.ssh_public_key
depends_on = [aws_iam_user.this]
}
The derived map here uses the same keys and values as the original var.users, but is just missing some of them. That means that the each.key results will correlate and so you'll still get the same username value you were expecting, and your instances will have addresses like aws_iam_user_ssh_key.this["testterform"].
You can use a for loop to exclude those blanks.
For example, you can do it on local:
variable "users" {
default = {
"testterform" = {
path = "/"
force_destroy = true
tags = { department : "test" }
ssh_public_key = "ssh-rsa AAAAB3NzaC1yc2EAAA4l7"
}
"testterform2" = {
path = "/"
force_destroy = true
tags = { department : "test" }
ssh_public_key = ""
}
}
}
locals {
public_key = flatten([
for key, value in var.users :
value.ssh_public_key if ! contains([""], value.ssh_public_key)
])
}
output "myout" {
value = local.public_key
}
that will output:
myout = [
"ssh-rsa AAAAB3NzaC1yc2EAAA4l7",
]
As you can see the empty ones have been removed, and you can add other stuff you want to exclude on that contains array.
Then you can use that local.public_key in the for_each for your ssh keys

Terraform .12 nested loop

variables.tf
variable "teams" {
type = map(any)
default = {}
}
input_value:
teams = {
{
team_id = "abc"
role_names = ["owner"]
},
{
team_id = "bcd"
role_names = ["read", "write"]
}
}
}
main.tf:
resource "mongodbatlas_project" "project" {
name = "testing"
org_id = "123456"
dynamic "teams" {
for_each = var.teams
content {
id = teams.value.team_id
names = [teams.value.role_names]
}
}
}
I have been trying the above code and it is not working. Is there an easier way to assign nested team value to the variable?
The teams variable does not seem to be correct for me and there are syntax errors (e.g. extra }in teams). I think it should be list, not map:
variable "teams" {
type = list(any)
default = []
}
and then
teams = [
{
team_id = "abc"
role_names = ["owner"]
},
{
team_id = "bcd"
role_names = ["read", "write"]
}
]
Then your resource could be:
resource "mongodbatlas_project" "project" {
name = "testing"
org_id = "123456"
dynamic "teams" {
for_each = toset(var.teams)
content {
id = teams.value.team_id
names = teams.value.role_names
}
}
}
When using dynamic blocks the iterator is called same as the block name.

Resources