Count = true. Works in terraform 11 but not terrafom 12 - amazon-web-services

I'm upgrading to terraform 12 and facing a few issues. We have an autoscaling module (non-root) that calls another module in a central repository (root).
So this module;
module "cef_fleet" {
source = "git::ssh://git#github.com/asg-repo.git?ref=terraform12"
instance_type = var.instance_type
ami = var.ami
etc ...
calls the repository "asg-repo" and in here some resources have a count function such as;
resource "aws_autoscaling_schedule" "schedule_stop" {
count = var.create_resource * var.auto_stop
These two variables in the central repository are both set to 'true'. This works with terraform 11 but when I upgraded to 12 I now get the error;
var.create_resource is true
Unsuitable value for left operand: number required.
Is the way to fix this, to simply put replace the true values with 1? Or should it be something like;
count = signum(count = var.create_resource * var.auto_start) - where both are also 1?

Use a ternary operator:
count = var.create_resource && var.auto_start ? 1 : 0

Related

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 : for_each one by one

I have created a module on terraform, this module creates aws_servicecatalog_provisioned_product resources.
When I call this module from the root I am using for_each to run into a list of objects.
The module runs into this list of objects and creates the aws_servicecatalog_provisioned_product resources in parallel.
Is there a way to create the resources one by one? I want that the module will wait for the first iteration to be done and to create the next just after.
Is there a way to create the resources one by one?
Sadly, there is not such way, unless you remove for_each and create all the modules separately with depends_on.
TF is not a procedural language, and it always will do things in parallel for for_each and count.
I am using terraform templatefile that creates resources with a depends on order, and then terraform creates resources one by one.
Here is the code:
locals {
expanded_accounts = [
{
AccountEmail = example1#example.com
AccountName = example1
ManagedOrganizationalUnit = example_ou1
SSOUserEmail = example1#example.com
SSOUserFirstName = Daniel
SSOUserLastName = Wor
ou_id = ou_id1
},
{
AccountEmail = example2#example.com
AccountName = example2
ManagedOrganizationalUnit = example_ou2
SSOUserEmail = example2#example.com
SSOUserFirstName = Ben
SSOUserLastName = John
ou_id = ou_id2
}
]
previous_resource = [
for acc in local.expanded_accounts :
acc.AccountName
]
resources = { res = local.expanded_accounts, previous = concat([""], local.previous_resource)
}
resource "local_file" "this" {
content = templatefile("./provisioned_accounts.tpl", local.resources)
filename = "./generated_provisioned_accounts.tf"
directory_permission = "0777"
file_permission = "0777"
lifecycle {
ignore_changes = [directory_permission, file_permission, filename]
}
}
provisioned_accounts.tpl configuration:
%{ for acc in res }
resource "aws_servicecatalog_provisioned_product" "${acc.AccountName}" {
name = "${acc.AccountName}"
product_id = replace(data.local_file.product_name.content, "\n", "")
provisioning_artifact_id = replace(data.local_file.pa_name.content, "\n", "")
provisioning_parameters {
key = "SSOUserEmail"
value = "${acc.SSOUserEmail}"
}
provisioning_parameters {
key = "AccountEmail"
value = "${acc.AccountEmail}"
}
provisioning_parameters {
key = "AccountName"
value = "${acc.AccountName}"
}
provisioning_parameters {
key = "ManagedOrganizationalUnit"
value = "${acc.ManagedOrganizationalUnit} (${acc.ou_id})"
}
provisioning_parameters {
key = "SSOUserLastName"
value = "${acc.SSOUserLastName}"
}
provisioning_parameters {
key = "SSOUserFirstName"
value = "${acc.SSOUserFirstName}"
}
timeouts {
create = "60m"
}
%{if index != 0 }
depends_on = [aws_servicecatalog_provisioned_product.${previous[index]}]
%{ endif }
}
%{~ endfor ~}
Why do you want it to wait for the previous creation? Terraform relies on the provider to know what can happen in parallel and will run in parallel where it can.
Setting the parallelism before the apply operation would be how I would limit it artificiality if I wanted to as it's an technical workaround that keeps your Terraform code simple to read.
TF_CLI_ARGS_apply="-parallelism=1"
terraform apply
If you find this is slowing down all Terraform creations but you need this particular set of resources to be deployed one at a time then it might be time to break these particular resources out into their own Terraform config directory and apply it in a different step to the rest of the resources again with the parallelism setting.
You have to remove the for_each and use depends_on for every element if you want to make sure that they are created one after another.
If you want only the first resource to be provisioned before other resources:
Separate the first resource only and use the for_each for the remaining resources. You can put an explicit dependency using depends_on for the remaining resources to depend on the first one. Because for_each expects a set or a map, this input would require some modification to be able to exclude the provisioning of the first resource.
A more drastic approach, if you really need to provision resources one by one, would be to run the apply command with -parallelism=1. This would reduce the number of resources provisioned in parallel to 1. This would apply to the whole project. I would not recommend this, since it would increase drastically the running time for the apply.

Terraform internal variable in resource definition

I have this question that I am not sure how to formulate. So let use an example. I have the following resource definition for was subnets based in an input parameter variable:
resource "aws_subnet" "monitoring_subnetwork" {
count = length(var.monitoring_subnets)
vpc_id = module.vpc.vpc_id
cidr_block = var.monitoring_subnets[count.index]
availability_zone= "${data.aws_availability_zones.available.names[count.index % length(data.aws_availability_zones.available.names)]}"
tags = {
Name = "Monitoring private-1${replace(
data.aws_availability_zones.available.names[count.index % length(data.aws_availability_zones.available.names)],
data.aws_availability_zones.available.id, "")}"
}
}
I want to simplify this code to make it more readable and maintainable.
I use a count.index to get an availability zone using round-robin, based on index % len_of_array, and the result of this mod is calculated twice (in other cases even three times).
I wonder if I could define an internal variable inside the resource, something like this:
zone_index = count.index % length(data.aws_availability_zones.available.names)
And reuse this index in the parts of the code where this operation is repeated.
Any thoughts? Also, any other recommendation to simplify this configuration would be appreciated :)
Sadly you can't do this. There are no custom functions in terraform. But there is already a github issue for that, so maybe in future there will be added:
Extending terraform with custom functions
So you have to keep repeating that computation.

How to enable terraform module only with a specific workspace

I define a provider containing modules.
I can call terraform apply at the provider level with two different workspace:
workspace list
default
wk-p1
* wk-p2
I would like the following module only to be launch when I use the workspace wk-1:
module "sync" {
source = "./../test-modules/sync"
workspace="${local.workspace}"
entity = "${local.entity}"
}
I would like something like this:
module "sync" {
if ("${local.workspace}" == "wk-p2") {
source = "./../test-modules/sync"
workspace="${local.workspace}"
entity = "${local.entity}"
}
}
Do you have any idea?
For this you could use the terraform.workspace variable in conjuction with the count directive
count = (terraform.workspace == "wk-p2") ? 1 : 0
This way when the workspace is wk-p2, terraform will create an instance of the module.
On the other hand if it evaluates to false it will create 0 instances of the modules, meaning it will not create anything
Maybe try with "count". Something like:
count = terraform.workspace == "default" ? 1 : 0

How to create an RDS instance from the most recent snapshot or from scratch

In terraform, is there a way to conditionally create an RDS instance from the most recent snapshot of a given database or to create an empty database depending on the value of a parameter?
I tried something like that:
variable "db_snapshot_source" {
default = ""
}
data "aws_db_snapshot" "last_snap" {
count = "${var.db_snapshot_source == "" ? 0 : 1}"
most_recent = true
db_instance_identifier = "${var.db_snapshot_source}"
}
resource "aws_db_instance" "db" {
[...]
snapshot_identifier = "${var.db_snapshot_source == "" ? "" : data.aws_db_snapshot.last_snap.db_snapshot_identifier}"
}
Unfortunately, it does not work because TF seems to dereference data.aws_db_snapshot.last_snap even if the ternary is false. I get the following error message: * aws_db_instance.db: Resource 'data.aws_db_snapshot.last_snap' not found for variable 'data.aws_db_snapshot.last_snap.db_snapshot_identifier'.
How can I achieve a such behaviour? The only option I see is to declare two aws_db_instance resources each with opposed count which is horrifying.
By defining a count you are saying the result of the data resource will be a list even if it is a zero value.
resource "aws_db_instance" "db" {
[...]
snapshot_identifier = "${
var.db_snapshot_source == "" ? "" :
element(
concat(data.aws_db_snapshot.last_snap.*.db_snapshot_identifier, list("")), 0)
}"
}
The concat is required if you expect the list to be empty. Otherwise you get an error
element: element() may not be used with an empty list...
Github issue describing the concat behaviour
The documentation reads as though specifying snapshot_identifier is what triggers using a snapshot or not, so passing in an empty string is not enough to avoid starting from a snapshot. In that case, you would need two aws_rds_instance resources, and then have ternary expressions for count on each resource to decide which one to create. As you mentioned, this is horrifying, but it might work ok.
Another way to think about it is if you had a blank snapshot in your inventory to start from. Then it's just a ternary operator away from deciding to use the custom snapshot or this blank snapshot. I don't know that you can create a blank snapshot in Terraform though, it's creation might be out of band.