Argument or block definition required - terraform

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

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 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.
╵
╷

An argument or block definition is required here

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.

How to skip certain part of a resource from being provisioned using Terraform

# main.tf
resource "azurerm_api_management" "apim_demo" {
name = var.apim_instance_name
location = azurerm_resource_group.apim_rg.location
resource_group_name = azurerm_resource_group.apim_rg.name
publisher_name = var.apim_publisher_name
publisher_email = var.apim_publisher_email
sku_name = var.apim_sku_name
identity {
type = "SystemAssigned"
}
hostname_configuration {
proxy {
default_ssl_binding = true
host_name = "qtech"
key_vault_id = "https://ssl-key-test789.vault.azure.net/secrets/my-ssl-certificate"
negotiate_client_certificate = true
}
proxy {
default_ssl_binding = false
host_name = "ftech"
key_vault_id = "https://ssl-key-test789.vault.azure.net/secrets/my-ssl-certificate2"
negotiate_client_certificate = true
#custom = var.custom_block
#count = var.test_condition ? 1 : 0
}
}
}
# variables.tf
variable "apim_instance_name" {}
variable "apim_publisher_name" {}
variable "apim_publisher_email" {}
variable "apim_sku_name" {}
variable "tenant_id" {
# description "Enter Tenant ID"
}
variable "client_id" {
# description "Enter Tenant ID"
}
variable "subscription_id" {
# description "Enter Subscription ID"
}
variable "client_secret" {
# description "Enter client secret"
}
variable "apim_resource_group_name" {
# description "RG-2"
}
variable "apim_location" {
type = map(any)
default = {
location1 = "eastus"
location2 = "westus"
}
}
#variable "subnets" {
# type = "list"
# default = ["10.0.1.0/24", "10.0.2.0/24"]
#}
variable "test_condition" {
type = bool
default = true
}
variable "custom_block" {
default = null
}
From the above terraform code, I want to avoid/skip the below (second proxy block) part of the resource from being provisioned
proxy {
default_ssl_binding = false
host_name = "ftech"
key_vault_id = "https://ssl-key-test789.vault.azure.net/secrets/my-ssl-certificate2"
negotiate_client_certificate = true
# custom = var.custom_block
# count = var.test_condition ? 1 : 0
}
I did try to use count logic to avoid but I guess it will work on a complete resource block, not on a certain part of a resource block. Anyway, I received the below error using count logic
Error: Unsupported argument
│
│ on apim-instance.tf line 35, in resource "azurerm_api_management" "apim_demo":
│ 35: count = var.test_condition ? 1 : 0
│
│ An argument named "count" is not expected here.
╵
I also try to use null logic to avoid but I guess it will also work on a complete resource block, not on a certain part of a resource block. Anyway, I got the below error using null logic.
│ Error: Unsupported argument
│
│ on apim-instance.tf line 34, in resource "azurerm_api_management" "apim_demo":
│ 34: custom = var.custom_block
│
│ An argument named "custom" is not expected here.
╵
use dynamic block. it will resolve your query.
https://www.terraform.io/docs/language/expressions/dynamic-blocks.html
variable "proxy" {
type = any
default = [
{
default_ssl_binding = true
host_name = "qtech"
key_vault_id = "https://ssl-key-test789.vault.azure.net/secrets/my-ssl-certificate"
negotiate_client_certificate = true
}
{
default_ssl_binding = true
host_name = "qtech"
key_vault_id = "https://ssl-key-test789.vault.azure.net/secrets/my-ssl-certificate"
negotiate_client_certificate = true
}
]
}
use like below.
hostname_configuration {
dynamic "proxy" {
for_each = var.proxy
content {
default_ssl_binding = proxy.value.default_ssl_binding
host_name = proxy.value.host_name
key_vault_id = proxy.value.key_vault_id
negotiate_client_certificate = proxy.value.negotiate_client_certificate
}
}
}
What if you just seperate this code blocks into modules for each stage?

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