Terraform: put variables in tfvar file not work - amazon-web-services

I defined a variable map my_role in terraform and set its value in abc.tfvar file as follows. if I assign account id as actual value, it works, if I set account id as a variable, it does not work. Does it mean tfvar file only allow actual value, not variable? By the way, I use terraform workspace. Therefore my_role is different based on workspace I select.
The following works:
my_role = {
dev = "arn:aws:iam::123456789012:role/myRole"
test = ...
prod = ...
}
The following does not work:
my_role = {
dev = "arn:aws:iam::${lookup(var.aws_account_id, terraform.workspace)}:role/myRole"
test = ...
prod = ...
}
The following does not work either:
lambdarole = {
dev = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/myRole"
test = ...
prod = ...
}
does

Have you tried following the example on Input Variables?
You can define your abc.tfvars file with:
variable "my_role" {
type = "map"
default = {
"dev" = "arn:aws:iam::123456789012:role/myRole"
"test" = "..."
"prod" = "..."
}
}
And access it with "${lookup(var.my_role, terraform.workspace)}".
Also, according to the from a file, the variables defined in .tfvars files are automatically loaded if you name the file terraform.tfvars, if not, you have to pass as an argument with -var-file=...
Cannot test it right now, but probably is something in this way.

I am replying when terraform 0.12 version is latest one. Solution is simple, you can create one file say vars.tf and declare variables in it.
Example - variable "xyz" {}
Now create terraform.tfvars and initialize it.
Example - xyz="abcd"
No need to pass any runtime args, it will be picked directly.

Terraform has aws_caller_identity data source. You do not need to mention or hand code account id anywhere. It can be fetched using this source.
In any of your .tf file, just include this source and then you can fetch relevant argument value.
This is how you can do it. Define this in any *.tf file
data "aws_caller_identity" "current" {}
Now where ever you want the value of arn or account id, it can be fetched using :
For account id(For terraform0.12):
data.aws_caller_identity.current.account_id
In your case, it would be like this :
dev = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/myRole"
But in order to this work, you need to define data source like shown above in any *.tf file.
For more help, refer following:
URL : https://www.terraform.io/docs/providers/aws/d/caller_identity.html

Related

Terraform - Check If Variable Ends With String & Remove

We've set up our TF GKE code so that the user can specify either the region or zone for the cluster.
However, we need to then check this variable and remove the zone suffix (if it exists) for the deployment of static IP addresses.
We have the following variable:
variable "k8s_cluster_location" {
type = string
default = "europe-west2"
validation {
condition = contains(["europe-west2", "europe-west2-a", "europe-west2-b", "europe-west2-c", "us-east4", "us-east4-a", "us-east4-b", "us-east4-c", "europe-west1", "europe-west1-a", "europe-west1-b", "europe-west1-c" ], var.k8s_cluster_location)
error_message = "Given GCP location not (yet) supported. Contact X if you think it should..."
}
description = "Location of the Kubernetes cluster."
}
If, for example, the variable is "europe-west2-a", we need to remove "-a" to acquire the parent region.
Would we need to incorporate a Regex check? Or could we use something like StartsWith()/EndsWith()?
I would definitely recommend the regular expression solution here as you suggest:
variable "k8s_cluster_location" {
type = string
default = "europe-west2"
validation {
condition = can(regex("(?:europe-west[12])|(?:us-east4)", var.k8s_cluster_location))
error_message = "Given GCP location not (yet) supported. Contact X if you think it should..."
}
description = "Location of the Kubernetes cluster."
}
Note that if you are using Terraform 1.3.x, then you can also use the var.k8s_cluster_location value in the error_message instead of "Given GCP location".
For your other suggestion of startswith(), you would need to do something like anytrue(startswith(var.k8s_cluster_location, "europe-west1"), startswith(var.k8s_cluster_location, "europe-west2"), startswith(var.k8s_cluster_location, "us-east4")), but that feels slightly messier to me.

Retrieving sensitive data from secret version in terraform

When showing the state of the secret version, just get shown this:
terraform state show aws_secretsmanager_secret_version.mysecret
secret_string = (sensitive value)
I want now to see, what the acctual value is, but i do not know how to do it. I have and saw answers like "use terraform output", but when using this:
terraform output aws_secretsmanager_secret_version.mysecret
I get:
The state file either has no outputs defined, or all the defined outputs are empty.
Can anyone help with this, please?
This is by design and for very good reason. Generally, console output will always mask sensitive data from being displayed. The output command you mentioned is only helpful if you have defined an output block that would display this resource or attribute. However, all is not lost. You can either look directly in the state file since the state file will hold the value in plain text. Or you can use terraform console command which is my preference since I prefer where possible to not touch the state file.
CDoyle#MINGW64 ~/PycharmProjects/stack
$ terraform state show random_password.this
# random_password.this:
resource "random_password" "this" {
bcrypt_hash = (sensitive value)
id = "none"
length = 10
lower = true
min_lower = 0
min_numeric = 0
min_special = 0
min_upper = 0
number = true
numeric = true
result = (sensitive value)
special = true
upper = true
}
CDoyle#MINGW64 ~/PycharmProjects/stack
$ terraform console
> nonsensitive(random_password.this.result)
"I]-q*DCL+&"

Terraform: Dynamically reference environment variables as values

How can I dynamically use the value of an environment variable as
a value in a Terraform resource, without having to declare a corresponding Terraform variable?
For example, imagine I declare a variable that contains a list of environment variable names:
variable "env_vars" {
description = "A list of env vars to use"
}
And I define a .tfvars file that sets the value of that list:
env_vars = ["ENV_VAR_A", "ENV_VAR_B", "ENV_VAR_C"]
Then imagine some arbitrary resource with a name argument. I want to dynamically create 1 resource for each item in the env_vars list, with the value of name set to the value of the environment variable of the same name:
resource "arbitrary_thing" "thing" {
count = length(var.env_vars)
name = "<VALUE OF ENV VAR NAMED var.env_vars[count.index] >"
}
How can I correctly set that name value?
This is not a supported functionality in Terraform.
However, HashiCorp (maintainers of Terraform) have posted a support/help article on how you can achieve a workaround.
resource "arbitrary_thing" "thing" {
count = length(var.env_vars)
name =var.env_vars[count.index]
}
variable "env_vars" {
description = "A list of env vars to use"
}
And I define a .tfvars file that sets the value of that list:
env_vars = ["ENV_VAR_A", "ENV_VAR_B", "ENV_VAR_C"]

terraform giving error: unsupported argument in module when running terraform plan?

I am getting the Error: Unsupported argument, when I run terraform plan in version 12.24.
Error: Unsupported argument
on .terraform/modules/app/main.tf line 261, in resource "aws_db_instance" "db_instance":
261: timeouts = {
An argument named "timeouts" is not expected here. Did you mean to define a
block of type "timeouts"?
This is the code in tf file:
timeouts = {
create = "${var.db_instance_create_timeout}"
update = "${var.db_instance_update_timeout}"
delete = "${var.db_instance_delete_timeout}"
}
I am not sure how to fix this error.
above error was fixed by removing "=" after timeouts.
I am also getting more errors, that need solutions:
Error: Unsupported argument
on .terraform/modules/rds/main.tf line 150, in resource "aws_db_parameter_group" "db_parameter_group":
150: parameter = concat(var.parameters, local.parameters[local.parameter_lookup])
An argument named "parameter" is not expected here. Did you mean to define a
block of type "parameter"?
Code in tf file:
parameter = concat(var.parameters, local.parameters[local.parameter_lookup])
how to fix this?
I am copying the solution that worked for me from github , credits to hashicorp member bflad :
In Terraform 0.12 (or higher), the configuration language parser is stricter about the distinction between arguments and configuration blocks. This error:
An argument named "XXX" is not expected here. Did you mean to
define a block of type "XXX"?
Generally means the = (equals sign) needs to be removed from an argument assignment so it parses correctly as a configuration block, e.g.
root_block_device {
This distinction in HCL syntax may seem trivial, but under the hood this stricter type checking allowed for consistency with JSON syntax. More information about this change can be found in the Terraform 0.12 Upgrade Guide. Speaking of which, in that guide it does point to the helpful terraform 0.12upgrade command, which should automatically fix issues like these across your Terraform configurations when upgrading from Terraform 0.11. 👍
Error
An argument named "secret_environment_variables" is not expected here.
Did you mean to define a block of type "secret_environment_variables"?
Problem
main.tf
resource "google_cloudfunctions_function" "this" {
secret_environment_variables = var.secret_environment_variables
}
variables.tf
variable "secret_environment_variables" {
type = any
default = {}
description = "Secret environment variables configuration."
}
Solution
resource "google_cloudfunctions_function" "this" {
secret_environment_variables {
key = var.secret_environment_variables_key
secret = var.secret_environment_variables_secret
version = var.secret_environment_variables_version
}
}
variable "secret_environment_variables_key" {
type = string
default = null
nullable = true
description = "Name of the environment variable."
}
variable "secret_environment_variables_secret" {
type = string
default = null
nullable = true
description = "ID of the secret in secret manager (not the full resource name)."
}
variable "secret_environment_variables_version" {
type = string
default = null
nullable = true
description = "Version of the secret (version number or the string `latest`). It is recommended to use a numeric version for secret environment variables as any updates to the secret value is not reflected until new clones start."
}

Unable to create dynamic terraform outputs for use in terraform_remote_state

I have the following code block for creating various IAM groups
resource "aws_iam_group" "environment-access" {
count = "${length(var.environments)}"
name = "access-${element(var.environments, count.index)}"
}
variable "environments" {
default = ["production", "non-production"]
type = "list"
}
I want to write the outputs of the IAM groups created in order to grab the ARN of each group to use as data via terraform_remote_state where it would look something like the following
Outputs:
access-production = arn:aws:iam::XXXXXXX:group/basepath/access-production
access-non-production = arn:aws:iam::XXXXXXX:group/basepath/access-non-production
I am having trouble creating the dynamic outputs as I am unsure how to dynamically create the output stanzas based on the the resource originally created as using the below code yields an error referencing unknown resource 'aws_iam_group.access-production' referenced.
output "access-production" {
value = "${aws_iam_group.access-production.arn}"
}
output "access-non-production" {
value = "${aws_iam_group.access-non-production.arn}"
}
An initial problem with this requirement is that it calls for having a single dynamic list of environments but multiple separate output values. In order to make this work, you'll need to either make the environment inputs separate values or produce a single output value describing the environments.
# Variant with a fixed set of environments (v0.11 syntax)
variable "production_environment_name" {
type = "string"
default = "production"
}
variable "non_production_environment_name" {
type = "string"
default = "non-production"
}
resource "aws_iam_group" "production_access" {
name = "access-${var.production_environment_name}"
}
resource "aws_iam_group" "non_production_access" {
name = "access-${var.non_production_environment_name}"
}
output "access_production" {
value = "aws_iam_group.production_access.arn"
}
output "access_non_production" {
value = "aws_iam_group.non_production_access.arn"
}
# Variant with dynamic set of environments (v0.11 syntax)
variable "environments" {
type = "list"
default = ["production", "non_production"]
}
resource "aws_iam_group" "access" {
count = "${length(var.environments)}"
name = "access-${var.environments[count.index]}"
}
output "access" {
value = "${aws_iam_group.access.*.arn}"
}
The key here is that the input variable and the output value must have the same form, so that we can make all of the necessary references between the objects. In the second example, the environment names are provided as a list, and the group ARNs are also provided as a list such that the indices correspond between the two.
You can also use a variant of the output "access" expression to combine the two with zipmap and get a map keyed by the environment names, which will probably be more convenient for the caller to use:
output "access" {
value = "${zipmap(var.environments, aws_iam_group.access.*.arn)}"
}
The new features in Terraform 0.12 allow tidying this up a bit. Here's an idiomatic Terraform 0.12 equivalent of the version that produces a map as a result:
# Variant with dynamic set of environments (v0.12 syntax)
variable "environments" {
type = set(string)
default = ["production", "non_production"]
}
resource "aws_iam_group" "access" {
for_each = var.environments
name = "access-${each.key}"
}
output "access" {
value = { for env, group in aws_iam_group.access : env => group.arn }
}
As well as having some slightly different syntax patterns, this 0.12 example has an additional practical advantage: Terraform will track those IAM groups with addresses like aws_iam_group.access["production"] and aws_iam_group.access["non_production"], so the positions of the environment names in the var.environments list are not important and it's possible to add and remove environments without potentially disturbing the groups from other environments due to the list element renumbering.
It achieves that by using resource for_each, which makes aws_iam_group.access appear as a map of objects where the environment names are keys, whereas count makes it a list of objects.