Terraform resource generate multiple key_pair with modules - terraform

I'm trying to utilize terraform modules and am having issues with key creation. I want to generate a new aws_key_pair for each run of my module. I am receiving
aws_key_pair.default: Error import KeyPair: InvalidKeyPair.Duplicate: The keypair 'keyname' already exists.
I would like to generate a new key (with a different name) for each run of the module but am lost as to how to achieve this.
I have in variables.tf
variable "key_pair_name" {
description = "EC2 Key pair name"
default = ""
}
and in resources.tf
resource "key_pair" "default"
key_name = "keyname"
public_key = "${file("${var.key_path}")}"
Which generates the first key fine. When module runs it will try to recreate the same key again which already exists. I want it to create a second, third and so on key named seperately ie: keyname1, keyname2, keyname3 or a random string.
I am trying
resource "random_id" "key_pair_name" {
name = {
key_name = "${random_id.key_name}"
}
byte_length = 8
}
I am starting this off from a simple main.tf
module "one" {
source = "/modules/test-example"

Writting the module
In your variable.tf:
variable "key_pair_names" {
description = "EC2 Key pair names"
default = [
"keyname1",
"keyname2"
]
}
In your resources.tf:
resource "key_pair" "default" {
count = "${length(var.key_pair_names)}"
key_name = "${element(var.key_pair_names, count.index)}"
public_key = "${file("${element(var.key_pair_names, count.index)}")}"
}
NB: the name of the local file needs to be the same than the remote key pair created
To override the variables
If you are running terraform directly in that directory, run:
terraform apply -var-file=terraform.tfvars
With the `terraform.tfvars:
key_pair_names = [
"keyname1",
"keyname2",
"keyname3"
]
If your are using the module from an other main.tf file:
module "key_pair" {
path = "path/to/module"
key_pair_names = [
"keyname1",
"keyname2",
"keyname3"
]
}

If someone else has this problem I was able to solve this using terraform random_id
resource "random_id" "keypair" {
byte_length = 8
}
resource "keypair" "default" {
name = "${random_id.keypair.hex}"

To achieve unique keyname at every run, you can use uuid function in terraform (https://www.terraform.io/docs/configuration/interpolation.html#uuid-)
You can define your codeblock as
resource "key_pair" "default"
key_name = "keyname-${uuid()}"
public_key = "${file("${var.key_path}")}"
But what are you trying to achieve with different keypair against a public key?

Related

Unable to Create Terraform Resource Group when using modules

I am optimizing my terraform code by using modules. When i create a resource group module it works perfectly well but it creates two resource groups
i.e.
Temp-AppConfiguration-ResGrp
Temp-AppServices-ResGrp
instead it should only create
Temp-AppConfiguration-ResGrp
Code Resourcegroup.tf.
resource "azurerm_resource_group" "resource" {
name = "${var.environment}-${var.name_apptype}-ResGrp"
location = var.location
tags = {
environment = var.environment
}
}
output "resource_group_name" {
value = "${var.environment}-${var.name_apptype}-ResGrp"
}
output "resource_group_location" {
value = var.location
}
Variable.tf
variable "name_apptype" {
type = string
default = "AppServices"
}
variable "environment" {
type = string
default = "Temp"
}
variable "location" {
type = string
default = "eastus"
}
Main.tf
module "resourcegroup" {
source = "../Modules"
name_apptype = "AppConfiguration"
}
I want to pass name_apptype in main.tf when calling resource group module. So that i don't need to update variable.tf every time.
Any suggestions
where i am doing wrong. Plus i am also unable to output the value, i need it so that i could pass resource group name in the next module i want to create.
Thanks
You need to do that in the Main.tf
module "resourcegroup" {
source = "../Modules"
name_apptype = "AppConfiguration"
}
module "resourcegroup-appservices" {
source = "../Modules"
name_apptype = "AppServices"
}
These create a 2 resources groups with the values that you need, additionally you can remove the default value from the name_apptype variable.
If you want to create with the same module both resource groups you need to use count to iterate over an array of names

Writing DRY code with terraform for loop?

I am very new to terraform and had a task dropped upon me to create 2 AWS KMS keys.
So I am doing this:
resource "aws_kms_key" "ebs_encryption_key" {
description = "EBS encryption key"
... omitted for brevity ...
tags = merge(map(
"Name", format("%s-ebs-encryption-key", var.name_prefix),
"component", "kms",
"dataclassification","low",
), var.extra_tags)
}
resource "aws_kms_alias" "ebs_encryption_key" {
name = format("alias/%s-ebs-encryption-key", var.name_prefix)
target_key_id = aws_kms_key.ebs_encryption_key.key_id
}
# Repeated code!
resource "aws_kms_key" "rds_encryption_key" {
description = "RDS encryption key"
... omitted for brevity ...
tags = merge(map(
"Name", format("%s-rds-encryption-key", var.name_prefix),
"component", "kms",
"dataclassification","low",
), var.extra_tags)
}
resource "aws_kms_alias" "rds_encryption_key" {
name = format("alias/%s-rds-encryption-key", var.name_prefix)
target_key_id = "${aws_kms_key.rds_encryption_key.key_id}"
}
As you can see the only difference between the two blocks of code is "ebs" and "rds"?
How could I use a for loop to avoid repeating the code blocks?
This seems like it could be a candidate for a small module that encapsulates the details of declaring a key and an associated alias, since a key and an alias are typically declared together in your system.
The module itself would look something like this:
variable "name" {
type = string
}
variable "description" {
type = string
}
variable "tags" {
type = map(string)
}
resource "aws_kms_key" "main" {
description = var.description
# ...
tags = var.tags
}
resource "aws_kms_alias" "main" {
name = "alias/${var.name}"
target_key_id = aws_kms_key.main.key_id
}
output "key_id" {
value = aws_kms_key.main.key_id
}
output "alias_name" {
value = aws_kms_alias.main.name
}
(As written here this module feels a little silly because there's not really much here that isn't derived only from the variables, but I'm assuming that the interesting stuff you want to avoid repeating is in "omitted for brevity" in your example, which would go in place of # ... in my example.)
Your calling module can then include a module block that uses for_each to create two instances of the module, systematically setting the arguments to populate its input variables:
module "kms_key" {
for_each = {
kms = "KMS"
ebs = "EBS"
}
name = "${var.name_prefix}-${each.key}-encryption-key"
description = "${each.value} Encryption Key"
tags = merge(
var.extra_tags,
{
Name = "${var.name_prefix}-${each.key}-encryption-key"
component = "kms"
dataclassification = "low"
},
)
}
Since the for_each map here has the keys kms and ebs, the result of this will be to declare resource instances which should have the following addresses in the plan:
module.kms_key["kms"].aws_kms_key.main
module.kms_key["kms"].aws_kms_alias.main
module.kms_key["ebs"].aws_kms_key.main
module.kms_key["ebs"].aws_kms_alias.main
Since they are identified by the map keys, you can add new keys to that map in future to create new key/alias pairs without disturbing the existing ones.
If you need to use the key IDs or alias names elsewhere in your calling module then you can access them via the outputs exposed in module.kms_key elsewhere in that calling module:
module.kms_key["kms"].key_id
module.kms_key["kms"].alias_name
module.kms_key["ebs"].key_id
module.kms_key["ebs"].alias_name

Set aws access key and secret key with secretsmanager Terraform

I have the below terraform template which creates a user, access key and stores in secret manager.
resource "aws_iam_user" "test" {
name = "test"
}
resource "aws_iam_access_key" "test" {
user = aws_iam_user.test.name
}
resource "aws_secretsmanager_secret" "test" {
name = "credentials"
description = "My credentials"
}
resource "aws_secretsmanager_secret_version" "test" {
secret_id = "${aws_secretsmanager_secret.test.id}"
secret_string = "{\"AccessKey\": data.aws_iam_access_key.test.id,\"SecretAccessKey\": data.aws_iam_access_key.test.secret}"
}
The values in the secret_string is not getting set. Is this right usage? Please help me set the right values
secret_string = "{\"AccessKey\": data.aws_iam_access_key.test.id,\"SecretAccessKey\": data.aws_iam_access_key.test.secret}"
You can construct your secret_string argument value as a Map type, and then encode it into a JSON string using Terraform's native jsonencode function to ensure the value is passed correctly to the argument. Your resource would look like:
resource "aws_secretsmanager_secret_version" "test" {
secret_id = "${aws_secretsmanager_secret.test.id}"
secret_string = jsonencode({"AccessKey" = aws_iam_access_key.test.id, "SecretAccessKey" = aws_iam_access_key.test.secret})
}
Note also that aws_iam_access_key.test.id and aws_iam_access_key.test.secret are exported attributes from resources and not data, so the data prefix needs to be removed from their namespace.

How do I make terraform skip that block while creating multiple resources in loop from a CSV file?

Hi I am trying to create a Terraform script which will take inputs from the user in the form of a CSV file and create multiple Azure resources.
For example if the user wants to create: ResourceGroup>Vnet>Subnet in bulk, he will provide input in CSV format as below:
resourcegroup,RG_location,RG_tag,domainname,DNS_Zone_tag,virtualnetwork,VNET_location,addressspace
csvrg1,eastus2,Terraform RG,test.sd,Terraform RG,csvvnet1,eastus2,10.0.0.0/16,Terraform VNET,subnet1,10.0.0.0/24
csvrg2,westus,Terraform RG2,test2.sd,Terraform RG2,csvvnet2,westus,172.0.0.0/8,Terraform VNET2,subnet1,171.0.0.0/24
I have written the following working main.tf file:
# Configure the Microsoft Azure Provider
provider "azurerm" {
version = "=1.43.0"
subscription_id = var.subscription
tenant_id = var.tenant
client_id = var.client
client_secret = var.secret
}
#Decoding the csv file
locals {
vmcsv = csvdecode(file("${path.module}/computelanding.csv"))
}
# Create a resource group if it doesn’t exist
resource "azurerm_resource_group" "myterraformgroup" {
count = length(local.vmcsv)
name = local.vmcsv[count.index].resourcegroup
location = local.vmcsv[count.index].RG_location
tags = {
environment = local.vmcsv[count.index].RG_tag
}
}
# Create a DNS Zone
resource "azurerm_dns_zone" "dnsp-private" {
count = 1
name = local.vmcsv[count.index].domainname
resource_group_name = local.vmcsv[count.index].resourcegroup
depends_on = [azurerm_resource_group.myterraformgroup]
tags = {
environment = local.vmcsv[count.index].DNS_Zone_tag
}
}
To be continued....
The issue I am facing here what is in the second resource group, the user don't want a resource type, suppose the user want to skip the DNS zone in the resource group csvrg2. How do I make terraform skip that block ?
Edit: What I am trying to achieve is "based on some condition in the CSV file, not to create azurerm_dns_zone resource for the resource group csvrg2"
I have provided an example of the CSV file, how it may look like below:
resourcegroup,RG_location,RG_tag,DNS_required,domainname,DNS_Zone_tag,virtualnetwork,VNET_location,addressspace
csvrg1,eastus2,Terraform RG,1,test.sd,Terraform RG,csvvnet1,eastus2,10.0.0.0/16,Terraform VNET,subnet1,10.0.0.0/24
csvrg2,westus,Terraform RG2,0,test2.sd,Terraform RG2,csvvnet2,westus,172.0.0.0/8,Terraform VNET2,subnet1,171.0.0.0/24
you had already the right thought in your mind using the depends_on function. Although, you're using a count inside, which causes from my understanding, that once the first resource[0] is created, Terraform sees the dependency as solved and goes ahead as well.
I found this post with a workaround which you might be able to try:
https://github.com/hashicorp/terraform/issues/15285#issuecomment-447971852
That basically tells us to create a null_resource like in that example:
variable "instance_count" {
default = 0
}
resource "null_resource" "a" {
count = var.instance_count
}
resource "null_resource" "b" {
depends_on = [null_resource.a]
}
In your example, it might look like this:
# Create a resource group if it doesn’t exist
resource "azurerm_resource_group" "myterraformgroup" {
count = length(local.vmcsv)
name = local.vmcsv[count.index].resourcegroup
location = local.vmcsv[count.index].RG_location
tags = {
environment = local.vmcsv[count.index].RG_tag
}
}
# Create a DNS Zone
resource "azurerm_dns_zone" "dnsp-private" {
count = 1
name = local.vmcsv[count.index].domainname
resource_group_name = local.vmcsv[count.index].resourcegroup
depends_on = null_resource.example
tags = {
environment = local.vmcsv[count.index].DNS_Zone_tag
}
}
resource "null_resource" "example" {
...
depends_on = [azurerm_resource_group.myterraformgroup[length(local.vmcsv)]]
}
or depending on your Terraform version (0.12+ which you're using guessing your syntax)
# Create a resource group if it doesn’t exist
resource "azurerm_resource_group" "myterraformgroup" {
count = length(local.vmcsv)
name = local.vmcsv[count.index].resourcegroup
location = local.vmcsv[count.index].RG_location
tags = {
environment = local.vmcsv[count.index].RG_tag
}
}
# Create a DNS Zone
resource "azurerm_dns_zone" "dnsp-private" {
count = 1
name = local.vmcsv[count.index].domainname
resource_group_name = local.vmcsv[count.index].resourcegroup
depends_on = [azurerm_resource_group.myterraformgroup[length(local.vmcsv)]]
tags = {
environment = local.vmcsv[count.index].DNS_Zone_tag
}
}
I hope that helps.
Greetings

Variance in attributes based on count.index in terraform

I'm using Hashicorp terraform to create a MySQL cluster on AWS. I created a module named mysql and want to tag the first instance created as the master. However, per terraform documentation:
Modules don't currently support the count parameter.
How do I work around this problem? Currently, I have these in my files:
$ cat project/main.tf
module "mysql_cluster" {
source = "./modules/mysql"
cluster_role = "${count.index == "0" ? "master" : "slave"}"
}
$ cat project/modules/mysql/main.tf
..
resource "aws_instance" "mysql" {
ami = "ami-123456"
instance_type = "t2.xlarge"
key_name = "rsa_2048"
tags {
Role = "${var.cluster_role}"
}
count = 3
}
This throws an error:
$ project git:(master) ✗ terraform plan
Error: module "mysql_cluster": count variables are only valid within resources
I have the necessary variables declared in the variables.tf files in my mysql module and root module. How do I work around this problem? Thanks in advance for any help!
The way you have count in the module resource would infer that you want 3 modules created, rather than 3 resources within the module created. You can stipulate the count from the module resource but any logic using count.index needs to sit within the module.
main.tf
module "mysql_cluster" {
source = "./modules/mysql"
instance_count = 3
}
mysql.tf
resource "aws_instance" "mysql" {
count = "${var.instance_count}"
ami = "ami-123456"
instance_type = "t2.xlarge"
key_name = "rsa_2048"
tags {
Role = "${count.index == "0" ? "master" : "slave"}"
}
}
Since Terraform 0.13 you can use either for_each or count to create multiple instances of a module.
variable "regions" {
type = map(object({
region = string
network = string
subnetwork = string
ip_range_pods = string
ip_range_services = string
}))
}
module "kubernetes_cluster" {
source = "terraform-google-modules/kubernetes-engine/google"
for_each = var.regions
project_id = var.project_id
name = each.key
region = each.value.region
network = each.value.network
subnetwork = each.value.subnetwork
ip_range_pods = each.value.ip_range_pods
ip_range_services = each.value.ip_range_services
}
Code snipped from
https://github.com/hashicorp/terraform/tree/guide-v0.13-beta/module-repetition
Official documentation
https://www.terraform.io/docs/configuration/modules.html
module doesn't have count. It is available at resources only.

Resources