Terraform - Using merge function makes the argument not excepted - terraform

Terraform Version
Terraform v0.12.18
+ provider.aws v2.42.0
+ provider.grafana v1.5.0
+ provider.helm v0.10.4
+ provider.kubernetes v1.10.0
+ provider.local v1.4.0
+ provider.null v2.1.2
+ provider.ovh v0.5.0
+ provider.random v2.2.1
+ provider.template v2.1.2
+ provider.vault v2.7.0
Terraform Configuration Files
# mymodule/service.k8s.tf
resource "kubernetes_service" "wordpress" {
metadata {
labels = merge(var.deployment_config.labels, {
app = "myname
engine = "wordpress"
tier = "frontend"
})
}
...
}
# mymodule/variables.tf
variable "deployment_config" {
description = "The deployment configuration."
type = object({
labels = map(string)
})
}
# modules.tf
module "my-module" {
source = "./mymodule"
deployment_config = {
labels = {
environment = "production"
}
}
}
Error
An argument named "labels" is not expected here.
Expected Behavior
The metadata.labels argument of the resource "kubernetes_service" (and other kubernetes resources) attempt to receive a map(). This should simply create the resource with theses labels (of both maps):
labels = {
app = "myname"
engine = "wordpress"
tier = "frontend"
environment = "production"
}
Actual Behavior
Without the use of the merge() function by simply putting a single map is working.
But when the merge() function is used Terraform say that the argument is not excepted.
Steps to Reproduce
Have a module with an argument of type map(string) (in this case kubernetes_service with the argument metadata.labels) which merge a variable of type map(string) with an other one map(string).
Pass to this module the variable of type map(string).
Then, terraform apply
Question
Why using merge() function makes the argument not excepted ?
Thanks in advance for your help !

My guess is that in mymodule/service.k8s.tf, there is a missing end quotation for the map key app's value. When I tried to run a terraform apply with the missing end quotation, I received a Error: Invalid multi-line string. Perhaps that was the issue you received ?
This seems to work for me.
$ tree
.
├── main.tf
└── mymodule
└── main.tf
1 directory, 2 files
$ cat main.tf
module "mymodule" {
source = "./mymodule"
deployment_config = {
labels = {
environment = "production"
}
}
}
output "mymodule" {
value = module.mymodule
}
$ cat mymodule/main.tf
variable "deployment_config" {
description = "The deployment configuration."
type = object({
labels = map(string)
})
}
locals {
labels = merge(var.deployment_config.labels, {
app = "myname"
engine = "wordpress"
tier = "frontend"
})
}
output "deployment_config" {
value = local.labels
}
$ ls
main.tf mymodule/
$ terraform version
Terraform v0.12.18
$ terraform apply
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
mymodule = {
"deployment_config" = {
"app" = "myname"
"engine" = "wordpress"
"environment" = "production"
"tier" = "frontend"
}
}

Related

Why am I getting 'Unsupported argument errors' in my main.tf file?

I have a main.tf file with the following code block:
module "login_service" {
source = "/path/to/module"
name = var.name
image = "python:${var.image_version}"
port = var.port
command = var.command
}
# Other stuff below
I've defined a variables.tf file as follows:
variable "name" {
type = string
default = "login-service"
description = "Name of the login service module"
}
variable "command" {
type = list(string)
default = ["python", "-m", "LoginService"]
description = "Command to run the LoginService module"
}
variable "port" {
type = number
default = 8000
description = "Port number used by the LoginService module"
}
variable "image" {
type = string
default = "python:3.10-alpine"
description = "Image used to run the LoginService module"
}
Unfortunately, I keep getting this error when running terraform plan.
Error: Unsupported argument
│
│ on main.tf line 4, in module "login_service":
│ 4: name = var.name
│
│ An argument named "name" is not expected here.
This error repeats for the other variables. I've done a bit of research and read the terraform documentation on variables, and read other stack overflow answers but I haven't really found a good answer to the problem.
Any help appreciated.
A Terraform module block is only for referring to a Terraform module. It doesn't support any other kind of module. Terraform modules are a means for reusing Terraform declarations across many configurations, but th
Therefore in order for this to be valid you must have at least one .tf file in /path/to/module that declares the variables that you are trying to pass into the module.
From what you've said it seems like there's a missing step in your design: you are trying to declare something in Kubernetes using Terraform, but the configuration you've shown here doesn't include anything which would tell Terraform to interact with Kubernetes.
A typical way to manage Kubernetes objects with Terraform is using the hashicorp/kubernetes provider. A Terraform configuration using that provider would include a declaration of the dependency on that provider, the configuration for that provider, and at least one resource block declaring something that should exist in your Kubernetes cluster:
terraform {
required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
}
}
}
provider "kubernetes" {
host = "https://example.com/" # URL of your Kubernetes API
# ...
}
# For example only, a kubernetes_deployment resource
# that declares one Kubernetes deployment.
# In practice you can use any resource type from this
# provider, depending on what you want to declare.
resource "kubernetes_deployment" "example" {
metadata {
name = "terraform-example"
labels = {
test = "MyExampleApp"
}
}
spec {
replicas = 3
selector {
match_labels = {
test = "MyExampleApp"
}
}
template {
metadata {
labels = {
test = "MyExampleApp"
}
}
spec {
container {
image = "nginx:1.21.6"
name = "example"
resources {
limits = {
cpu = "0.5"
memory = "512Mi"
}
requests = {
cpu = "250m"
memory = "50Mi"
}
}
liveness_probe {
http_get {
path = "/"
port = 80
http_header {
name = "X-Custom-Header"
value = "Awesome"
}
}
initial_delay_seconds = 3
period_seconds = 3
}
}
}
}
}
}
Although you can arrange resources into separate modules in Terraform if you wish, I would suggest focusing on learning to directly describe resources in Terraform first and then once you are confident with that you can learn about techniques for code reuse using Terraform modules.

Terraform modules azure event subscription optional fields

I am trying to work with terraform modules to create event subscription pointing to storage queue as an endpoint to it.
Below is the module
resource "azurerm_eventgrid_event_subscription" "events" {
name = var.name
scope = var.scope
subject_filter = var.subject_filter
storage_queue_endpoint = var.storage_queue_endpoint
}
and terraform is
module "storage_account__event_subscription" {
source = "../modules/event"
name = "testevent"
scope = test
subject_filter = {
subject_begins_with = "/blobServices/default/containers/test/blobs/in"
}
storage_queue_endpoint = {
storage_account_id = test
queue_name = test
}
}
Error message:
: subject_filter {
Blocks of type "subject_filter" are not expected here.
Error: Unsupported block type
on azure.tf line 90, in module "storage_account__event_subscription":
: storage_queue_endpoint {
Blocks of type "storage_queue_endpoint" are not expected here.
How do i parse the optional fields properly in terraform modules ?
In you module:
resource "azurerm_eventgrid_event_subscription" "events" {
name = var.name
scope = var.scope
subject_filter = {
subject_begins_with = var.subject_begins_with
}
storage_queue_endpoint = var.storage_queue_endpoint
}
Formatting is off here so make sure to run terraform fmt to account for my poor formatting. Also add the variable to the variables.tf file.
Your Terraform file:
module "storage_account__event_subscription" {
source = "../modules/event"
name = "testevent"
scope = test
subject_begins_with = "/blobServices/default/containers/test/blobs/in"
storage_queue_endpoint = {
storage_account_id = test
queue_name = test
}
}
You create the full structure in the module and then you assign the variables in the terraform file.
Anything that will have the same, or generally the same, value can have a default value set in the variables.tf as well so that you get smaller chunks in the TF file.

Terraform Resource does not have attribute for variable

Running Terraform 0.11.7 and getting the following error:
module.frontend_cfg.var.web_acl: Resource 'data.terraform_remote_state.waf' does not have attribute 'waf_nonprod_id' for variable 'data.terraform_remote_state.waf.waf_nonprod_id'
Below is the terraform file:
module "frontend_cfg"
{
source = "../../../../modules/s3_fe/developers"
region = "us-east-1"
dev_shortname = "cfg"
web_acl = "${data.terraform_remote_state.waf.waf_nonprod_id}"
}
data "terraform_remote_state" "waf" {
backend = "local"
config = {
name = "../../../global/waf/terraform.tfstate"
}
}
The file which creates the tfstate file referenced above is below. This file has had no issues building.
resource "aws_waf_web_acl" "waf_fe_nonprod"
{
name = "fe_nonprod_waf"
metric_name = "fenonprodwaf"
default_action
{
type = "ALLOW"
}
}
output waf_nonprod_id
{
value = "${aws_waf_web_acl.waf_fe_nonprod.id}"
}
I will spare the full output of the cloudfront file, however, the following covers the text:
resource "aws_cloudfront_distribution" "fe_distribution"
{
web_acl_id = "${var.web_acl}"
}
If I put the ID of the waf ID into the web_acl variable, it works just fine, so I suspect the issue is something to do with the way I am calling data. This appears to match documentation though.
Use path instead of name in terraform_remote_state,
https://www.terraform.io/docs/backends/types/local.html
data "terraform_remote_state" "waf" {
backend = "local"
config = {
path = "../../../global/waf/terraform.tfstate"
}
}
or
data "terraform_remote_state" "waf" {
backend = "local"
config = {
path = "${path.module}/../../../global/waf/terraform.tfstate"
}
}
I tested it with terraform version 0.11.7 and 0.11.14
If you upgrade terraform to version 0.12.x, syntax using remote_state ouput has changed.
So change
web_acl = "${data.terraform_remote_state.waf.waf_nonprod_id}"
to
web_acl = data.terraform_remote_state.waf.outputs.waf_nonprod_id
or
web_acl = "${data.terraform_remote_state.waf.outputs.waf_nonprod_id}"

Terraform Modules - Unable to Access variable from root

I am trying to pass a variable from the root module to a child module with the following syntax and i'm unable to do that:
└── Terraform
├── main.tf
├── variable.tf
└── module
├──main.tf
├── variable.tf
Terraform Version:
Terraform v0.11.11
+ provider.openstack v1.15.0
Terraform Configuration Files
/Terraform/main.tf:
provider "openstack" {
openstack_user_name = "${var.openstack_user_name}"
openstack_tenant_name = "${var.openstack_tenant_name}"
openstack_password = "${var.openstack_password}"
openstack_auth_url = "${var.openstack_auth_url}"
domain_name = "${var.domain_name}"
}
module "testMod" {
name = "${var.name}"
imageId = "${var.imageId}"
flavor_name = "${var.flavor_name}"
openstack_keypair = "${var.openstack_keypair}"
tenant_network_id = "${var.tenant_network_id}"
tenant_network = "${var.tenant_network}"
source = "./modules"
}
/Terraform/variable.tf:
variable "name" {default = "XXX"}
variable "imageId" {default = "11-22-33"}
variable "flavor_name"{default = "flavor"}
...
/Terraform/modules/main.tf:
resource "openstack_compute_instance_v2" "test" {
name = "${var.name}"
imageId = "${var.imageId}"
flavor_name = "${var.flavor_name}"
openstack_keypair = "${var.openstack_keypair}"
security_groups = ["default"]
network {
tenant_network_id = "${var.tenant_network_id}"
tenant_network = "${var.tenant_network}"
}
}
/Terraform/modules/variable.tf:
variable "name" {}
variable "imageId" {}
variable "flavor_name" {}
variable "openstack_keypair" {}
variable "tenant_network_id"{}
variable "tenant_network" {}
Actual Behavior
Error: module.testMod.openstack_compute_instance_v2.test: : invalid or unknown key: imageId
Steps to Reproduce
terraform init
terraform apply
Unsure what is going wrong here
The error is alerting you to the unknown keyimageId. This message is accurate as, in fact, the key should be image_id. You can check the Terraform openstack_compute_instance_v2 resource documentation and note the presence of the image_id argument.
Your code would then look like:
resource "openstack_compute_instance_v2" "test" {
name = "${var.name}"
image_id = "${var.imageId}"
flavor_name = "${var.flavor_name}"
openstack_keypair = "${var.openstack_keypair}"
security_groups = ["default"]
}
I have this requirement to access variables from root variable.tf inside module/myservice/main.tf
How can we achive that ? By using var.environmentdoes not work
e.g:
root/variable.tf
variable "environment" {
type = string
description = "Resource environment e.g dev, test or prod."
default = "dev"
}
How to access above variable var.environment in root/module/myservice/main.tf using ${var.environment} does not work.
root/module/myservice/main.tf
locals {
name = "${var.environment}.myInstance"
}
resource "aws_network_interface" "foo" {
name = local.name
.
.
.
}

using count.index in terraform?

I am trying to generate a bunch of files from templates. I need to replace the hardcoded 1 with the count.index, not sure what format terraform will allow me to use.
resource "local_file" "foo" {
count = "${length(var.files)}"
content = "${data.template_file.tenant_repo_multi.1.rendered}"
#TODO: Replace 1 with count index.
filename = "${element(var.files, count.index)}"
}
data "template_file" "tenant_repo_multi" {
count = "${length(var.files)}"
template = "${file("templates/${element(var.files, count.index)}")}"
}
variable "files" {
type = "list"
default = ["filebeat-config_filebeat.yml",...]
}
I am running with:
Terraform v0.11.7
+ provider.gitlab v1.0.0
+ provider.local v1.1.0
+ provider.template v1.0.0
You can iterate through the tenant_repo_multi data source like so -
resource "local_file" "foo" {
count = "${length(var.files)}"
content = "${element(data.template_file.tenant_repo_multi.*.rendered, count.index)}"
filename = "${element(var.files, count.index)}"
}
However, have you considered using the template_dir resource in the Terraform Template provider. An example below -
resource "template_dir" "config" {
source_dir = "./unrendered"
destination_dir = "./rendered"
vars = {
message = "world"
}
}

Resources