An expression was successfully parsed, but extra characters were found after it - azure-virtual-machine

I have a "create_vm" module which is creating VM plus storage account, NIC etc.
My requirement is I want to pass IP address list from root module to create_vm module so anyone can restrict in bound connections to VM as per their requirement. Correct me I'm not using correct terminology for modules.
Directory structure looks like this:
subscription_1 subscription_2 Modules README.md
./subscription_1:
./subscription_2:
main.tf sqlvm.tf.bak terraform.tfstate terraform.tfstate.backup variables.tf
./Modules:
create_vm
./Modules/create_vm:
main.tf variable.tf
cat ./Modules/create_vm/main.tf
resource "azurerm_network_security_rule" "tf-nsr-5986" {
...
source_address_prefixes = "${var.allowed_source_ips}"
...
}
cat ./Modules/create_vm/variable.tf
variable "allowed_source_ips" {
description = "List of ips from which inbound connection to VMs is allowed"
type = "list"
}
Utilizing this in root module
cat ./subscription_2/main.tf
module "vm_app" {
...
allowed_source_ips = "${var.ip_list}"
...
}
cat ./subscription_2/variable.tf
variable "ip_list" {
description = "List of ips from which inbound connection to VMs is allowed"
type = "list"
}
Now, I am running terraform from my local vm, by passing arguments, exactly the way I would on Azure DevOps pipeline
terraform plan -var "resource_group_name=nxt-grp-prd-manage-rgp-au-se" -var "virtual_network_name=virtual_network_1" -var "sql_subnet_name=subnet_1" -var "app_subnet_name=subnet_2" -var "application_nsg=test_nsg" -var "count_vm=2" -var "sql_host_basename=sqlvms" -var "app_host_basename=appvms" -var "storage_account_suffix=sta" -var "virtual_machine_size=Standard_B1ms" -var "virtual_machine_image_publisher=MicrosoftWindowsServer" -var "virtual_machine_image_offer=WindowsServer" -var "virtual_machine_image_sku=2012-R2-Datacenter" -var "virtual_machine_image_version=latest" -var "username=devopsadmin" -var "password=Angular12#$%" -var "ip_list="a.b.c.d","p.q.r.s","x.y.z.l""
Unfortunately, I'm getting the error message as like below:
Error: Invalid number literal
on <value for var.ip_list> line 1:
(source code not available)
Failed to recognize the value of this number literal.
Error: Extra characters after expression
on <value for var.ip_list> line 1:
(source code not available)
An expression was successfully parsed, but extra characters were found after
it.
Has anyone solved this type of challenge before? Not really sure why it is complaining, I'm giving the same way I have given before in create_vm module.
Any help would be much appreciated.

You should assing a list to the ip_list variable, not a string. You're expecting a list but you're assigning a string (that's technically fine, it gets parsed into a list with single string in it. ) and then a comma, which is the extra character.
Instead you should pass a proper list as a parameter, notice the 'array' / [] (square brackets) notation.
... -var "ip_list=["a.b.c.d","p.q.r.s","x.y.z.l"] ...
I'd also strongly encourage you to use a variable file instead. You'll then get some linting/terraform language support. That will allow you to spot mistakes easier before running tf apply in ci/cd.
You would create a file variables.tfvars:
...
variable "ip_list" {
description = "List of ips ..."
default = ["a.b.c.d","p.q.r.s","x.y.z.l"]
}
...
You can then pass the whole file as a paramter to terraform apply -var-file="./variables.tfvars".

Look for type mismatches between your input variable and what you are actually sending in.
This is a generalized error that tends to surface when the type for an input is a map or list and a string was given. I believe it has something to do with the way Terraform tries to convert inputs to what is set in the input variable.
Example:
Given the following in a variables.tf in a module named test
variable "foo" {
type = map
}
If you instantiate your module like this:
module "example" {
source = "./test"
foo = <<-EOT
bar
EOT
}
This errors will surface.
In most cases Terraform tries to prevent this, but some things slip through (such as heredoc syntax).

Related

Convert multiple modules to single module terraform

Please instead of doing negative voting, kindly read complete problem first.
Hi I am new to terraform.
I have 3 modules in terraform.
/module1/eip/main.tf
/module1/eip/output.tf
/module2/eip/main.tf
/module2/eip/output.tf
/module3/eip/main.tf
/module3/eip/output.tf
These all 3 modules create an eip along with showing it in outputs.
From main.tf on root level i am using these functions like this.
module "module-one-eip" {
source = "./modules/module1/eip/"
instance = module.ec2-for-one-module.ec2-one-id
}
module "module-two-eip" {
source = "./modules/module2/eip/"
instance = module.ec2-for-two-module.ec2-two-id
}
module "module-three-eip" {
source = "./modules/module3/eip/"
instance = module.ec2-for-three-module.ec2-three-id
}
Now I want to remove repetitive files and I want to use one file for all modules, like all code from these 3 will reside in one file, and all outputs mentioned above will have in same file, but main problem here is how I will handle different instance variable data being passed and how it will be synced with right code section in same file.
/module/generic-eip/main.tf
/module/generic-eip/outputs.tf
and from main.tf I want to call it like this.
module "module-generic-eip" {
source = "./modules/generic-eip/"
instance = (how to manage different instance names for same file)
}
I know there is for_each and count stuff of terraform, problem is internal configurations are diffrent, i mean how i can make dynamic naming as well.
inside ./modules/eip/main.tf
resource "aws_eip" "(how to manage different names here)" {
instance = var.instance
vpc = true
}
Assuming you keep your inctance module separate, and only want to consolidate EIP module, it would be as follows:
locals {
instance_ids = [module.ec2-for-one-module.ec2-one-id,
module.ec2-for-two-module.ec2-two-id,
module.ec2-for-three-module.ec2-three-id]
}
module "module-generic-eip" {
source = "./modules/generic-eip/"
count = length(local.instance_ids)
instance = local.instance_ids[count.index]
}
Code inside ./modules/eip/main.tf does not change, as for each iteration of count, var.instance will be a single element from local.instance_ids.
Then you access, individual EIPs, using indieces, 0, 1, 2:
module.module-generic-eip[0]
module.module-generic-eip[1]
module.module-generic-eip[2]

How to execute one module using conditionals?

module "s3_bucket" {source = "./module/s3_bucket"}
module "s3_bucket_2" {source = "./module/s3_bucket_2"}
These are my two modules which I am calling in the main.tf file, but I want to use some conditions so that I can call any one module which I want at any point of time and only that module gets executed at that time, so is their any way to do that?
I didnt understand quite your question but i guess what you want or at least would be helpfull to answer your question is the following.
You can create a variable in variables.tf called for example create and then pass it to a module.
# Set a variable to know if the resources inside the module should be created
module "s3_bucket" {
source = "./module/s3_bucket"
create = var.create
}
# For every resource inside use the count to create or not each resource
resource "resource_type" "resource_name" {
count = var.create ? 1 : 0
... other resource attributes
}

Terraform - Optional SSM parameter lookup

I'm doing a lookup for an SSM parameter which may or may not exist depending on a variable passed in:
data "aws_ssm_parameter" "server_tags" {
name = "/${var.env_number}/server_tags"
}
I am then using it like below in my locals and passing to my module:
locals {
server_tags = data.aws_ssm_parameter.server_tags != null ? jsondecode(data.aws_ssm_parameter.server_tags.value) : {}
instance_tags = merge(var.instance_tags, local.server_tags)
}
This works fine when my parameter exists, but if I pass in a value where my parameter doesn't exist, I get an error:
Error describing SSM parameter (/997/server_tags): ParameterNotFound:
Is there anyway I can do a pre-check to see if the parameter exists or make it optional somehow?
Thanks
Sadly you can't do this. There is no way build-on mechanism for TF to check if a data source exists or not. But you can program your own logic for that using External Data Source.
Since you program the external data source, you can create a logic for checking if a resource exists or not.

Terraform - why can't I assign a value from 1 variable to another variable

I am trying to assign an output variable from a module to a local variable so that I can conveniently use a local variable. Is there another way ?
variable "vpc_id" {
default = "${module.vpc.vpc_id}"
}
Error I am getting is :
Error: Unsupported argument
on main.tf line 22, in variable "subnetid_private":
22: default = "${module.vpc.subnet_private}"
Variables may not be used here..
I spent good amount of time to google this but could not see any example. Am I missing something here. This is a pretty standard convenience feature of any language.
you can replace with locals
https://www.terraform.io/docs/configuration/locals.html
locals {
vpc_id = module.vpc.vpc_id
}
and later reference it as local.vpc_id
I had the same thought when I first started using Terraform.
The problem is naming. A Terraform variable block is more like a final or constant constructor or method parameter in other languages.
From the Local Value documentation, it says this:
A local value assigns a name to an expression, allowing it to be used multiple times within a module without repeating it.
Comparing modules to functions in a traditional programming language: if input variables are analogous to function arguments and outputs values are analogous to function return values, then local values are comparable to a function's local temporary symbols.
When you write:
variable "vpc_id" {
}
Terraform says "ah, you'd like callers of this module to be able to hand a string in called vpc_id". Definitely not what you are looking for.
For the kind of case you have, a Local Value is what Terraform provides:
locals {
vpc_id = module.vpc.vpc_id
}
In terraform 0.12, you can use variables in variables like so:
variable "var1" {
type = string
default = "this is var 1"
}
variable "var2" {
type = string
default = "$${variable.var1}"
}
output of this is:
$ terraform apply
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Accessing role_arn in different folder than where the role was created

I have a Terraform structure like:
prod
nonprod
applications
+-- continuous/common/iam/iam.tf <-- create the role
+-- dataflow/firehose/firehose.tf <-- want to refer to the role created above
I don't know how to do this. In the iam's .tf file I have:
resource "aws_iam_role" "policy_daily_process_role" {
...
}
output "svc_daily_process_role_arn" {
value = "${aws_iam_role.policy_daily_process_role.arn}"
}
I am not sure how (or if) I can then refer to the svc_daily_process_role_arn from the firehose's .tf.
My understanding that you already use modules to manage terraform codes.
So in your case, there should be two modules at least.
continuous/common
dataflow/firehose
In continuous/common module, you have defined output.tf
output "svc_daily_process_role_arn" {
value = "${aws_iam_role.policy_daily_process_role.arn}"
}
So you create the resources with common module first.
module "common" {
source = "./continuous/common"
...
}
Now you are fine to refer the output from module common with below codes:
module "firehost" {
source = "./dataflow/firehose"
some_variable = "${module.common.svc_daily_process_role_arn}"
...
}
Please go through below documents for better understanding.
https://www.terraform.io/docs/modules/usage.html#outputs
Using Terraform Modules.
https://www.terraform.io/docs/modules/usage.html
From a top level make a call to the two subdirectories.
In module 1 (your IAM role) add an output like you have, but ensure it's outputted from module 1.
In module 2 reference it via ${module..}
If you're not using modules (or even if you are), you can use remote state. This means you will save your state in S3 or Consul and then refer to it from anywhere in your code.