Terraform not showing output despite being declared - terraform

I am declaring the following output in a TF module in the output.tf file:
output "jenkins_username" {
value = "${local.jenkins_username}"
description = "Jenkins admin username"
#sensitive = true
}
output "jenkins_password" {
value = "${local.jenkins_password}"
description = "Jenkins admin password"
#sensitive = true
}
The corresponding locals have been declared in main.tf as follows:
locals {
jenkins_username = "${var.jenkins_username == "" ? random_string.j_username.result : var.jenkins_username}"
jenkins_password = "${var.jenkins_password == "" ? random_string.j_password.result : var.jenkins_password}"
}
However, after the apply has finished, I see no relevant output, and what is more, it is not displayed even when I call the explicit output command:
$ terraform output jenkins_password
The output variable requested could not be found in the state
file. If you recently added this to your configuration, be
sure to run `terraform apply`, since the state won't be updated
with new output variables until that command is run.

I was having the exact same problem. What worked for me was to comment out the output variables in the first deployment, and uncomment them once that would succeed.

Related

Load variable values from the given .tfvars file got an error to many arguments

I run terraform plan commnad with apllo.tfvars file
terraform plan -var-file=apllo.tfvars
│ Error: Too many command line arguments
│ To specify a working directory for the plan, use the global -chdir flag.
my variable.tf
variable "user" {
type = string
}
# number variable
variable "age" {
type = number
}
apllo.tfvars
user = "AWSUSER"
age = 222
output.tf
output "name" {
value = "hello ${var.user}"
}
output "age" {
value = "age ${var.age}"
}
If you are using Powershell for running Terraform, try specifying the .tfvar file using single or double quotes, such as:
terraform plan -var-file="apollo.tfvar"

Conditionally create a single module in Terraform

I have been trying to conditionally use a module from the root module, so that for certain environments this module is not created. Many people claim that by setting the count in the module to either 0 or 1 using a conditional does the trick.
module "conditionally_used_module" {
source = "./modules/my_module"
count = (var.create == true) ? 1 : 0
}
However, this changes the type of conditionally_used_module: instead of an object (or map) we will have a list (or tuple) containing a single object. Is there another way to achieve this, that does not imply changing the type of the module?
To conditionally create a module you can use a variable, lets say it's called create_module in the variables.tf file of the module conditionally_used_module.
Then for every resource inside the conditionally_used_module module you will use the count to conditionally create or not that specific resource.
The following example should work and provide you with the desired effect.
# Set a variable to know if the resources inside the module should be created
module "conditionally_used_module" {
source = "./modules/my_module"
create_module = var.create
}
# Inside the conditionally_used_module file
# ( ./modules/my_module/main.tf ) most likely
# for every resource inside use the count to create or not each resource
resource "resource_type" "resource_name" {
count = var.create_module ? 1 : 0
... other resource properties
}
I used this in conjunction with workspaces to build a resource only for certain envs. The advantage is for me that I get a single terraform.tfvars file to control the all the environments structure for a project.
Inside main.tf:
workspace = terraform.workspace
#....
module "gcp-internal-lb" {
source = "../../modules/gcp-internal-lb"
# Deploy conditionally based on deploy_internal_lb variable
count = var.deploy_internal_lb[local.workspace] == true ? 1 : 0
# module attributes here
}
Then in variables.tf
variable "deploy_internal_lb" {
description = "Set to true if you want to create an internal LB"
type = map(string)
}
And in terraform.tfvars:
deploy_internal_lb = {
# DEV
myproject-dev = false
# QA
myproject-qa = false
# PROD
myproject-prod = true
}
I hope it helps.

Terraform - How to use conditionally created resource's output in conditional operator?

I have a case where I have to create an aws_vpc resource if the user does not provide vpc id. After that I am supposed to create resources with that VPC.
Now, I am applying conditionals while creating an aws_vpc resource. For example, only create VPC if existing_vpc is false:
count = "${var.existing_vpc ? 0 : 1}"
Next, for example, I have to create nodes in the VPC. If the existing_vpc is true, use the var.vpc_id, else use the computed VPC ID from aws_vpc resource.
But, the issue is, if existing_vpc is true, aws_vpc will not create a new resource and the ternary condition is anyways trying to check if the aws_vpc resource is being created or not. If it doesn't get created, terraform errors out.
An example of the error when using conditional operator on aws_subnet:
Resource 'aws_subnet.xyz-subnet' not found for variable 'aws_subnet.xyz-subnet.id'
The code resulting in the error is:
subnet_id = "${var.existing_vpc ? var.subnet_id : aws_subnet.xyz-subnet.id}"
If both things are dependent on each other, how can we create conditional resources and assign values to other configuration based on them?
You can access dynamically created modules and resources as follows
output "vpc_id" {
value = length(module.vpc) > 0 ? module.vpc[*].id : null
}
If count = 0, output is null
If count > 0, output is list of vpc ids
If count = 1 and you want to receive a single vpc id you can specify:
output "vpc_id" {
value = length(module.vpc) > 0 ? one(module.vpc).id : null
}
The following example shows how to optionally specify whether a resource is created (using the conditional operator), and shows how to handle returning output when a resource is not created. This happens to be done using a module, and uses an object variable's element as a flag to indicate whether the resource should be created or not.
But to specifically answer your question, you can use the conditional operator as follows:
output "module_id" {
value = var.module_config.skip == true ? null : format("%v",null_resource.null.*.id)
}
And access the output in the calling main.tf:
module "use_conditionals" {
source = "../../scratch/conditionals-modules/m2" # << Change to your directory
a = module.skipped_module.module_id # Doesn't exist, so might need to handle that.
b = module.notskipped_module.module_id
c = module.default_module.module_id
}
Full example follows. NOTE: this is using terraform v0.14.2
# root/main.tf
provider "null" {}
module "skipped_module" {
source = "../../scratch/conditionals-modules/m1" # << Change to your directory
module_config = {
skip = true # explicitly skip this module.
name = "skipped"
}
}
module "notskipped_module" {
source = "../../scratch/conditionals-modules/m1" # << Change to your directory
module_config = {
skip = false # explicitly don't skip this module.
name = "notskipped"
}
}
module "default_module" {
source = "../../scratch/conditionals-modules/m1" # << Change to your directory
# The default position is, don't skip. see m1/variables.tf
}
module "use_conditionals" {
source = "../../scratch/conditionals-modules/m2" # << Change to your directory
a = module.skipped_module.module_id
b = module.notskipped_module.module_id
c = module.default_module.module_id
}
# root/outputs.tf
output skipped_module_name_and_id {
value = module.skipped_module.module_name_and_id
}
output notskipped_module_name_and_id {
value = module.notskipped_module.module_name_and_id
}
output default_module_name_and_id {
value = module.default_module.module_name_and_id
}
the module
# m1/main.tf
resource "null_resource" "null" {
count = var.module_config.skip ? 0 : 1 # If skip == true, then don't create the resource.
provisioner "local-exec" {
command = <<EOT
#!/usr/bin/env bash
echo "null resource, var.module_config.name: ${var.module_config.name}"
EOT
}
}
# m1/variables.tf
variable "module_config" {
type = object ({
skip = bool,
name = string
})
default = {
skip = false
name = "<NAME>"
}
}
# m1/outputs.tf
output "module_name_and_id" {
value = var.module_config.skip == true ? "SKIPPED" : format(
"%s id:%v",
var.module_config.name,
null_resource.null.*.id
)
}
output "module_id" {
value = var.module_config.skip == true ? null : format("%v",null_resource.null.*.id)
}
The current answers here are helpful when you are working with more modern versions of terraform, but as noted by OP here they do not work when you are working with terraform < 0.12 (If you're like me and still dealing with these older versions, I am sorry, I feel your pain.)
See the relevant issue from the terraform project for more info on why the below is necessary with the older versions.
but to avoid link rot, I'll use the OPs example subnet_id argument using the answers in the github issue.
subnet_id = "${element(compact(concat(aws_subnet.xyz-subnet.*.id, list(var.subnet_id))),0)}"
From the inside out:
concat will join the splat output list to list(var.subnet_id) -- per the background link 'When count = 0, the "splat syntax" expands to an empty list'
compact will remove the empty item
element will return your var.subnet_id only when compact recieves the empty splat output.

Why is my Terraform output not working in module?

I have the following simple setup:
~$ tree
.
├── main.tf
└── modules
└── world
└── main.tf
~$ cat main.tf
output "root_module_says" {
value = "hello from root module"
}
module "world" {
source = "modules/world"
}
~$ cat modules/world/main.tf
output "world_module_says" {
value = "hello from world module"
}
I then run:
~$ terraform get
~$ terraform apply
I expect to see world_module_says in the outputs, but I do not, I only see root_module_says.
This is really confusing as to why?
If it helps:
~$ terraform --version
v0.10.8
Terraform only shows the output from root (by default pre v0.12)
https://www.terraform.io/docs/commands/output.html
Prior to Terraform 0.12 you can get the output from the world module with:
terraform output -module=world
I think the logic here is that the output from the module would be consumed by root and if you actually needed the output then you'd output it in root too so main.tf might contain this:
output "root_module_says" {
value = "hello from root module"
}
output "world_module_says" {
value = "${module.world.world_module_says}"
}
module "world" {
source = "modules/world"
}
Beginning with Terraform 0.12 this is the only way to get the output from within a module.
Before Terraform 0.12
The below command shows the output for a specific module
terraform output -module=example_module
After Terraform 0.12
The below command fails in Terraform 0.12 and above with an error:
terraform output -module=example_module
Error: Unsupported option
The -module option is no longer supported since Terraform 0.12, because now
only root outputs are persisted in the state.
To get outputs from a module in Terraform 0.12 and above, you must export them from the root (e.g example_module) module using an output block in the caller module. This can be now done simply by adding one line in the caller module like below:
output "example_module_outputs" {
value = module.example_module
}
Now you can see the output running the following command:
terraform output
Expansion to the answers above when using multiple output varialbes:
When accessing a module output group (using the 'terraform output' command from a console or a script) then Terraform will printout all output variables within this group in a formatted way, or in json format using '-json' flag.
When extracting output values, we can use the '-raw' flag only if there is a single variable which has a string format.
If we have more than 1 outout in the output group within the module:
Lets say we declared in the 'root' module the following outout group in a module:
output "world_world_module_says" {
value = module.world.world_module_says
}
and in the module main.tf we have 2 vars:
output "world_module_says" {
value = "hello from world module"
}
output "world_module_says_again" {
value = "hi again from world module"
}
Two ways to extract a single output variable from the group:
using -json flag and manipulating the output using the jq command:
terraform output -json world_world_module_says |jq '.'"world_module_says"
The other way is to declare each output individually in the 'root' module to allow easy access using '-raw':
output "world_world_module_says" {
value = module.world.world_module_says
}
output "world_world_module_says_again" {
value = module.world.world_module_says_again
}

Terraform Module - Output error when count = 0

I'm relatively new to Terraform - I have a module setup as below, the issue I'm having is with the outputs if the module count is '0' when running a terraform plan. Output PW works fine now that I've used the element(concat workaround but the Output I'm having issues with is DCPWUn, I get the following error:
Error: Error refreshing state: 1 error(s) occurred:
* module.PrimaryDC.output.DCPWUn: At column 21, line 1: rsadecrypt: argument 1 should be type string, got type list in:
${element(concat("${rsadecrypt(aws_spot_instance_request.PrimaryDC.*.password_data,file("${var.PATH_TO_PRIVATE_KEY}"))}", list("")), 0)}
Code:
resource "aws_spot_instance_request" "PrimaryDC" {
wait_for_fulfillment = true
provisioner "local-exec" {
command = "aws ec2 create-tags --resources ${self.spot_instance_id} --tags Key=Name,Value=${var.ServerName}0${count.index +01}"
}
ami = "ami-629a7405"
spot_price = "0.01"
instance_type = "t2.micro"
count = "${var.count}"
key_name = "${var.KeyPair}"
subnet_id = "${var.Subnet}"
vpc_security_group_ids = ["${var.SecurityGroup}"]
get_password_data = "true"
user_data = <<EOF
<powershell>
Rename-computer -NewName "${var.ServerName}0${count.index +01}"
</powershell>
EOF
tags {
Name = "${var.ServerName}0${count.index +01}"
}
}
output "PW" {
value = "${element(concat("${aws_spot_instance_request.PrimaryDC.*.password_data}", list("")), 0)}"
}
output "DCPWUn" {
value = "${element(concat("${rsadecrypt(aws_spot_instance_request.PrimaryDC.*.password_data,file("${var.PATH_TO_PRIVATE_KEY}"))}", list("")), 0)}"
}
As the error says, rsadecrypt has an argument that is of type list, not string as it should be. If you want to ensure that the argument is a string, you need to invert your function call nesting to make sure that rsadecrypt gets a string:
output "DCPWUn" {
value = "${rsadecrypt(element(concat(aws_spot_instance_request.PrimaryDC.*.password_data, list("")), 0),file("${var.PATH_TO_PRIVATE_KEY}"))}"
}
The problem lies within this line
${element(concat("${rsadecrypt(aws_spot_instance_request.PrimaryDC.*.password_data,file("${var.PATH_TO_PRIVATE_KEY}"))}", list("")), 0)}
What are you trying to achieve? Let's break it down a little
element(…, 0): Get the first element of the following list.
concat(…,list("")): Concatenate the following list of strings and then append the concatenation of a list containing the empty string (Note that the second part is not useful, since you are appending an empty string).
rsadecrypt(…,file("${var.PATH_TO_PRIVATE_KEY}")): decrypt the following expression with the private key (Error: The following thing needs to be a string, you will be supplying a list)
aws_spot_instance_request.PrimaryDC.*.password_data This is a list of all password data (and not a string).
I don't know what your desired output should look like, but with the above list, you may be able to mix-and-match the functions to suit your needs.
edit: Fixed a mistake thanks to the comment by rahuljain1311.

Resources