An argument or block definition is required here - terraform

I am using kubernetes_network_policy resource. I am trying loop over the ports and getting this issue.
╷
│ Error: Argument or block definition required
│
│ on main.tf line 16, in resource "kubernetes_network_policy" "example-policy":
│ 16: { for i in range(length(val.egress_number)):
│
│ An argument or block definition is required here.
My Resource
resource "kubernetes_network_policy" "example-policy" {
for_each = var.inputs
metadata {
name = each.value.name
namespace = each.value.namespace
}
spec {
pod_selector {
match_labels = {
app = each.value.selector
}
}
policy_types = each.value.policy
egress {
{ for i in range(length(egress_number)):
ports {
port = egress_number[i]
protocol = egress_protocol[i]
}
}
to {
namespace_selector {
match_labels = {
app = each.value.egress_label
}
}
}
}
}
}
My varibale.tf
variable "inputs" {
type = map(object({
name = string
namespace = string
selector = string
policy = list(string)
egress_number = list(string)
egress_protocol = list(string)
egress_label = string
}))
default = {}
}
My tfvars
inputs = {
app = {
name = "nignx"
namespace = "default"
selector = "nignix-app"
policy = ["Egress"]
egress_label = "play"
egress_number = ["443", "8080"]
egress_protocol = ["TCP", "TCP"]
}
}

You have to use dynamic blocks:
resource "kubernetes_network_policy" "example-policy" {
for_each = var.inputs
metadata {
name = each.value.name
namespace = each.value.namespace
}
spec {
pod_selector {
match_labels = {
app = each.value.selector
}
}
policy_types = each.value.policy
dynamic "egress" {
for_each = range(length(each.value.egress_number))
content {
ports {
port = each.value.egress_number[egress.value]
protocol = each.value.egress_protocol[egress.value]
}
to {
namespace_selector {
match_labels = {
app = each.value.egress_label
}
}
}
}
}
}
}
Instead of using range and length, one could also use zipmap in your case.

Related

Unsupported argument for_each from terraform

I'm trying to create a multiple delivery rules for the azure cdn endpoint through terraform.
But while trying to achieve the same getting below error,
Error: Unsupported argument
│
on main.tf line 71, in resource "azurerm_cdn_endpoint" "default":
71: for_each = {
│
An argument named "for_each" is not expected here.
╵
delivery_rule {
for_each = {
for deliveryrule in var.delivery_rules : deliveryrule.name => deliveryrule
}
name = each.value.name
order = each.value.order
request_scheme_condition {
operator = each.value.operator
match_values = each.value.match_values
}
url_redirect_action {
redirect_type = each.value.redirect_type #"Found"
protocol = each.value.protocol #"Https"
}
}
You have to use dynamic blocks:
dynamic "delivery_rule" {
for_each = {
for deliveryrule in var.delivery_rules : deliveryrule.name => deliveryrule
}
content {
name = delivery_rule.value.name
order = delivery_rule.value.order
request_scheme_condition {
operator = delivery_rule.value.operator
match_values = delivery_rule.value.match_values
}
url_redirect_action {
redirect_type = delivery_rule.value.redirect_type #"Found"
protocol = delivery_rule.value.protocol #"Https"
}
}
}

How to merge/combine two variables in to one variable in terraform?

I've been trying to merge/combine two variables into a single one (different one as : var1+var2 = merged into var3)
I am trying to create a for_each loop on the code and my variables are :
variable "apps" {
type = map(object({
app_name = string
labels = map(string)
annotations = map(string)
image = string
}))
default = {
"app1_name" = {
app_name = "app1_name"
labels = {
"name" = "stream-frontend"
"tier" = "web"
"owner" = "product"
}
annotations = {
"serviceClass" = "web-frontend"
"loadBalancer/class" = "external"
}
image = "nxinx"
}
"app2_name" = {
app_name = "app2_name"
labels = {
"name" = "stream-frontend"
"tier" = "web"
"owner" = "product"
}
annotations = {
"serviceClass" = "web-frontend"
"loadBalancer/class" = "external"
}
image = "nginx"
}
"app3_name" = {
app_name = "app3_name"
labels = {
"name" = "stream-database"
"tier" = "shared"
"owner" = "product"
}
annotations = {
"serviceClass" = "disabled"
"loadBalancer/class" = "disabled"
}
image = "Mongo"
}
}
}
variable "acl" {
type = map(object({
acl_name = string
ingress = string
egress = string
port = string
protocol = string
}))
default = {
"frontend" = {
acl_name = "acl_frontend"
ingress = "stream-frontend"
egress = "0.0.0.0/0"
port = "80"
protocol = "TCP"
},
"backend" = {
acl_name = "acl_backend"
ingress = "stream-backend"
egress = "0.0.0.0/0"
port = "80"
"protocol" = "TCP"
},
"database" = {
acl_name = "acl_database"
"ingress" = "stream-database"
"egress" = "172.17.0.0/24"
"port" = "27017"
"protocol" = "TCP"
}
}
}
Making a for_each loop to access values of the variables etc.
resource "kubernetes_network_policy" "acl" {
for_each = var.merged_vars
metadata {
name = format("%s-acl", each.value.acl_name)
namespace = each.value.acl_name
}
spec {
policy_types = ["Ingress", "Egress"]
pod_selector {
match_labels = {
tier = each.value.labels.tier
}
}
ingress {
from {
namespace_selector {
match_labels = {
name = each.value.ingress
}
}
}
ports {
port = each.value.port
protocol = each.value.protocol
}
}
egress {
to {
ip_block {
cidr = each.value.egress
}
}
}
}
}
eventually I need to have a way to access the "apps" and "acl" parameters as "var.apps.labels" and "var.acl.port" etc.
thank you for your help!
been trying:
variable "merged_vars" {
default = merge(var.apps, var.acl)
}
and the result i got is :
│ Error: Function calls not allowed
│
│ on NewVars.tf line 96, in variable "merged_vars":
│ 96: default = merge(var.apps, var.acl)
│
│ Functions may not be called here.
alo tried with concat and got the same result
As the error states, you can't create dynamic variables. But you can create local variables dynamically. So you can do:
locals {
merged_vars = merge(var.apps, var.acl)
}
And for the for_each you will use local:
for_each = local.merged_vars

how to use dynamic block in terraform?

main.tf
resource "azurerm_cdn_endpoint" "default" {
for_each = {
for cdn_endpoint in var.cdn_endpoints : cdn_endpoint.endpoint_name => cdn_endpoint
}
name = each.value.endpoint_name
location = coalesce(var.location, data.azurerm_resource_group.current.location)
resource_group_name = data.azurerm_resource_group.current.name
profile_name = azurerm_cdn_profile.default.name
tags = var.resource_tags
origin {
name = random_id.id[each.key].hex
host_name = each.value.origin_host_name
}
dynamic "delivery_rule" {
for_each = {
for deliveryrule in var.delivery_rules : deliveryrule.order => deliveryrule
}
content {
name = delivery_rule.value.name
order = delivery_rule.value.order
request_scheme_condition {
operator = delivery_rule.value.operator
match_values = delivery_rule.value.match_values
}
url_redirect_action {
redirect_type = delivery_rule.value.redirect_type #"Found"
protocol = delivery_rule.value.protocol #"Https"
}
}
}
}
variable.tf
variable "delivery_rules" {
default = {
name = "test"
order = 1
request_scheme_condition = {
match_values = ["HTTP"]
operator = "Equal"
}
}
type = object(
{
name = string
order = number
request_scheme_condition = object({
match_values = list(string)
operator = string
})
})
}
I'm trying to create rule engine for the azure cdn.
getting below error
Error: Unsupported attribute
│
│ on ../../main.tf line 86, in resource "azurerm_cdn_endpoint" "default":
│ 86: protocol = delivery_rule.value.protocol #"Https"
│ ├────────────────
│ │ delivery_rule.value is "test"
│
│ This value does not have any attributes.
╵
╷

Argument or block definition required

I am using kubernetes_network_policy resource. I have around ten network poilices and each of them is different. I want the from cidr block to be executed only when I pass a value to ingress_to_cidr. When I am trying execute terragrunt apply it errors out saying failed to expand IPBlock: null or empty input but terragrunt plan works fine. So I am trying to use if statement around the from cidr block, to see if I surpass the IPblock issue when I don't pass any value to ingress_to_cidr
│ Error: Argument or block definition required
│
│ on main.tf line 37, in resource "kubernetes_network_policy" "example-policy":
│ 37: length(var.ingress_to_cidr) != 0 ? 0 : from {
│
│ An argument or block definition is required here. To set an argument, use
│ the equals sign "=" to introduce the argument value.
╵
ERRO[0004] 1 error occurred:
* exit status 1
My resource
resource "kubernetes_network_policy" "example-policy" {
for_each = var.inputs
metadata {
name = each.value.name
namespace = each.value.namespace
}
spec {
pod_selector {
match_labels = {
app = each.value.selector
}
}
policy_types = each.value.policy
dynamic "ingress" {
for_each = each.value.egress_number == null ? [] :range(length(each.value.ingress_number))
content {
ports {
port = each.value.ingress_number[ingress.value]
protocol = each.value.ingress_protocol[ingress.value]
}
length(var.ingress_to_cidr) == null ? [] : from {
ip_block {
cidr = each.value.ingress_to_cidr
}
}
}
}
dynamic "egress" {
for_each = each.value.egress_number == null ? [] : range(length(each.value.egress_number))
content {
ports {
port = each.value.egress_number[egress.value]
protocol = each.value.egress_protocol[egress.value]
}
length(var.ingress_to_cidr) == null ? [] : to {
ip_block {
cidr = each.value.egress_to_cidr
}
}
}
}
}
}
You can nest dynamic blocks. So I think in your case it should be:
resource "kubernetes_network_policy" "example-policy" {
for_each = var.inputs
metadata {
name = each.value.name
namespace = each.value.namespace
}
spec {
pod_selector {
match_labels = {
app = each.value.selector
}
}
policy_types = each.value.policy
dynamic "ingress" {
for_each = each.value.egress_number == null ? [] : range(length(each.value.ingress_number))
content {
ports {
port = each.value.ingress_number[ingress.value]
protocol = each.value.ingress_protocol[ingress.value]
}
dynamic "from" {
for_each = each.value.ingress_to_cidr == null ? [] : [each.value.ingress_to_cidr]
content {
ip_block {
cidr = from.value
}
}
}
}
}
dynamic "egress" {
for_each = each.value.egress_number == null ? [] : range(length(each.value.egress_number))
content {
ports {
port = each.value.egress_number[egress.value]
protocol = each.value.egress_protocol[egress.value]
}
dynamic "from" {
for_each = each.value.egress_to_cidr == null ? [] : [each.value.egress_to_cidr]
content {
ip_block {
cidr = from.value
}
}
}
}
}
}
}

How to create multiple instances with multiple subnet ids using terraform?

I have 2 services test1,test2 and for each service i have to create 6 vm's.This 6 vm's should be placed in 3 subnet id's which created in 3 different zones in a same region
In this services,test1 will be in private subnets and test2 will be in public subnets.So i have to pass that correct subnet id when creating ec2 instances
root module:
provider "aws" {
region = var.region
}
module "ecom-vpc" {
source = "./modules/vpc"
}
module "ecom-public-subnet" {
source = "./modules/subnets/public"
vpc-id = module.ecom-vpc.vpc-id
}
module "ecom-private-subnet" {
source = "./modules/subnets/private"
vpc-id = module.ecom-vpc.vpc-id
}
module "ecom-instances-sg" {
source = "./modules/sg"
vpc-id = module.ecom-vpc.vpc-id
}
module "ecom-vm-instances" {
source = "./modules/vm"
priv-subnet-ids = module.ecom-private-subnet.ecom_private_subnets
pub-subnet-ids = module.ecom-public-subnet.ecom_public_subnets
instances-sg = module.ecom-instances-sg.ecom-inst-sg
}
From child modules - vpc,subnets,ec2
variable "service-names" {
type = list(any)
default = ["ecom-app-TEST1","ecom-app-TEST2"]
}
variable "availability_zones" {
type = map
default = {
ap-south-1a = {
private_subnet = "10.0.1.0/24"
public_subnet = "10.0.4.0/24"
}
ap-south-1b = {
private_subnet = "10.0.2.0/24"
public_subnet = "10.0.5.0/24"
}
ap-south-1c = {
private_subnet = "10.0.3.0/24"
public_subnet = "10.0.6.0/24"
}
}
}
resource "aws_vpc" "ecom-vpc" {
cidr_block = var.ecom-cidr
}
output "vpc-id" {
value = aws_vpc.ecom-vpc.id
}
resource "aws_subnet" "ecom-private" {
for_each = var.availability_zones
vpc_id = var.vpc-id
cidr_block = each.value.private_subnet
availability_zone = each.key
map_public_ip_on_launch = false
tags = {
Name = "${split("-", each.key)[2]}"
Subnet_Type = "private"
}
}
output "ecom_private_subnets" {
value = aws_subnet.ecom-private
}
resource "aws_subnet" "ecom-public" {
for_each = var.availability_zones
vpc_id = var.vpc-id
cidr_block = each.value.public_subnet
availability_zone = each.key
map_public_ip_on_launch = true
tags = {
Name = "${split("-", each.key)[2]}"
Subnet_Type = "public"
}
depends_on = [aws_internet_gateway.igw
]
}
output "ecom_public_subnets" {
value = aws_subnet.ecom-public
}
I'm trying to achieve the same by creating a locals which combines service names,priv,public subnet id's,instance count(2).But the problem is i'm not able to make it because i'm not able to create a unique combination of keys
locals {
service_subnets = {
for pair in setproduct(var.service-names, values(var.priv-subnet-ids),values(var.pub-subnet-ids),range(var.instance_count)) :
"${pair[0]}:${pair[1].availability_zone}-${pair[3]}" => {
service_name = pair[0]
priv-subnet = pair[1]
pub-subnet = pair[2]
}
}
}
resource "aws_instance" "ecom-instances" {
for_each = local.service_subnets
ami = data.aws_ami.ecom.id
instance_type = "t3.micro"
tags = {
Name = each.value.service_name
Service = each.value.service_name
}
vpc_security_group_ids = [var.instances-sg[each.value.service_name].id]
subnet_id = "${split("-", each.value.service_name)[2] == "TEST1" ? each.value.pub-subnet.id : each.value.priv-subnet.id }"
}
I'm getting the below error.
Two different items produced the key "ecom-app-TEST1:ap-south-1c-1" in this 'for' expression. If duplicates are expected, use the ellipsis (...)
│ after the value expression to enable grouping by key.
If i add ... and change it as below then it is converted as tuple and i'm not sure how to get and pass the value from each.value in the aws_instance resource
locals {
service_subnets = {
for pair in setproduct(var.service-names, values(var.priv-subnet-ids),values(var.pub-subnet-ids),range(var.instance_count)) :
"${pair[0]}:${pair[1].availability_zone}-${pair[3]}" => {
service_name = pair[0]
priv-subnet = pair[1]
pub-subnet = pair[2]
}
... }
}
on modules/vm/main.tf line 60, in resource "aws_instance" "ecom-instances":
│ 60: subnet_id = "${split("-", each.value.service_name)[2] == "TEST1" ? each.value.pub-subnet.id : each.value.priv-subnet.id }"
│ ├────────────────
│ │ each.value is tuple with 3 elements
│
│ This value does not have any attributes.
Please Guide me
You could try adding the index to your for loop and making it part of your name, might help you avoid elipsis/tuple conversion.
locals {
service_subnets = {
for index, pair in setproduct(var.service-names, values(var.priv-subnet-ids),values(var.pub-subnet-ids),range(var.instance_count)) :
"${pair[0]}:${pair[1].availability_zone}-${pair[3]}=${index}" => {
service_name = pair[0]
priv-subnet = pair[1]
pub-subnet = pair[2]
}
}
}

Resources