I want to conditionally set arguments to resources when managing Github repositories with Terraform.
I would like to have different pages blocks depending on the value of the has_pages parameter, as in the following example
Example:
resource "github_repository" "example" {
name = "example"
description = "My awesome web page"
private = false
if ${var.has_page} == true:
pages {
source {
branch = "master"
path = "/docs"
}
}
end
}
I'm having trouble using dynamic as well!
Does anyone know how to make this happen?
For this you can use a combination of dynamic and for_each:
resource "github_repository" "example" {
name = "example"
description = "My awesome web page"
private = false
dynamic "pages" {
for_each = var.has_page ? [1] : []
content {
source {
branch = "master"
path = "/docs"
}
}
}
}
More information about how to use dynamic is in [1] and about for_each is in [2].
[1] https://www.terraform.io/language/expressions/dynamic-blocks
[2] https://www.terraform.io/language/meta-arguments/for_each
Related
I have deployed a cloud run application for currently two domains with a load balancer, which is already running. Now this setup needs to be rolled out to other domains. Because the resource setup is always the same, I face some issues:
I want to prevent repeating code (which is managed through a for_each)
Still there are some domain-specific values to cover, which i tried through a mapping table
Referencing resources, which are created with for_each in another resource
The first issue I solved like this, which seems to work:
Old:
resource "google_cloud_run_service" "cr_domain1" {
name = "cr-domain1"
location = "europe-west6"
project = "my_project"
template {
...
}
}
resource "google_cloud_run_service" "cr_domain2" {
name = "cr-domain2"
location = "europe-southwest1"
project = "my_project"
template {
...
}
}
New:
resource "google_cloud_run_service" "cr" {
for_each = toset( ["domain1", "domain2"] )
name = "cr-${each_key}"
location = "tdb" # This is my second issue
project = "my_project"
template {
...
}
}
Regarding second issue I still need domain-specific location setup, which I tried to solve like this, but I am getting errors:
variable "cr_location" {
type = list(object({
domain1 = string
domain2 = string
}))
default = [{
domain1 = "europe-west6"
domain2 = "europe-southwest1"
}]
}
resource "google_cloud_run_service" "cr" {
for_each = toset( ["domain1", "domain2"] )
name = "cr-${each_key}"
location = "${var.cr_location[0]}.${each.key}"
project = "my_project"
template {
...
}
}
Error is "Cannot include the given value in a string template: string required". But I have already declared it as a string in my variable "cr_location". Any idea what's the issue here? The expected output should be:
location = "europe-west6" # For domain1
location = "europe-southwest1" # For domain2
Also regarding issue 3 I do not understand how to referencing resources, which are created with for_each in another resource. So before my for_each in the cloud run resource block (see issue 1) I had this 2 resources:
resource "google_cloud_run_service" "cr_domain1"
resource "google_cloud_run_service" "cr_domain2"
Now I only have resource "google_cloud_run_service" "cr". But in my loadbalancer.tf I still have to references to the old namings (last coderow within "service"):
resource "google_compute_region_network_endpoint_group" "backendneg" {
for_each = toset( ["domain1", "domain2"] )
name = "backendneg-${each.key}"
project = "my_project"
network_endpoint_type = "SERVERLESS"
region = "${var.cr_location[0]}.${each.key}" # Here same issues as issue 2
cloud_run {
service = google_cloud_run_service.cr_domain1.name # Old reference
}
}
So if there is no "cr_domain1" anymore how do I reference to this resource? My issue is that I have to create over 20 resources like that and I couldn't figure it out how to do it. I appreciate any guideline here.
What I would suggest here is to try and refactor the variable because it is making a lot of things harder than they should be. So I would go for this kind of a variable definition:
variable "cr_location" {
type = map(string)
default = {
domain1 = "europe-west6"
domain2 = "europe-southwest1"
}
}
Then, the rest should be easy to create:
resource "google_cloud_run_service" "cr" {
for_each = var.cr_location
name = "cr-${each.key}"
location = each.value
project = "my_project"
template {
...
}
}
And for the network endpoint resource:
resource "google_compute_region_network_endpoint_group" "backendneg" {
for_each = var.cr_location
name = "backendneg-${each.key}"
project = "my_project"
network_endpoint_type = "SERVERLESS"
region = each.value
cloud_run {
service = google_cloud_run_service.cr[each.key].name
}
}
You could even try resource chaining with for_each [1] to make sure you are doing this for all the Cloud Run resources created:
resource "google_compute_region_network_endpoint_group" "backendneg" {
for_each = google_cloud_run_service.cr
name = "backendneg-${each.key}"
project = "my_project"
network_endpoint_type = "SERVERLESS"
region = each.value.location
cloud_run {
service = each.value.name
}
}
[1] https://www.terraform.io/language/meta-arguments/for_each#chaining-for_each-between-resources
I am trying to find a way to conditionally populate the pages block of the github_repository resource, when the resource itself is created using for_each. The dynamic block seems to be the appropriate way to achieve this, but I am struggling with the syntax.
I've tried with the code below and it fails.
variables.tf:
variable "repositories" {
description = "The repositories to create using terraform"
type = list(object({
name = string,
description = string,
vulnerability_alerts = bool,
pages_cname = string
}))
}
terraform.tfvars.json:
{
"repositories": [
{
"name": "repo-with-pages",
"description": "Repository with pages enabled.",
"vulnerability_alerts": true,
"pages_cname": "www.example.com"
},
{
"name": "repo-without-pages",
"description": "Repository without pages enabled.",
"vulnerability_alerts": true
}
]
}
main.tf:
resource "github_repository" "this" {
for_each = { for repo in var.repositories : repo.name => repo }
name = each.value.name
description = each.value.description
vulnerability_alerts = each.value.vulnerability_alerts
dynamic "pages" {
for_each = { for repo in var.repositories : repo.name => repo }
content {
source {
branch = "main"
path = "/docs"
}
cname = each.value.pages_cname
}
}
}
Result:
Error: Too many pages blocks
on main.tf line 43, in resource "github_repository" "this":
43: content {
No more than 1 "pages" blocks are allowed
This makes perfect sense because the for expression within the dynamic block for_each returns two values (repo-with-pages and repo-without-pages) and only 1 page block is allowed. So, what needs to happen is the for_each / for expression combination needs to return 1 value - the name of the repository created - IF pages are enabled.
Been looking at this for a while and beginning to wonder if what I want to do is even possible or if I am over complicating things. Any help most welcome. Thanks.
To make pages_cname optional it should be:
variable "repositories" {
description = "The repositories to create using terraform"
type = list(any)
then
dynamic "pages" {
for_each = contains(keys(each.value), "pages_cname") ? [1] : []
content {
source {
branch = "main"
path = "/docs"
}
cname = each.value.pages_cname
}
}
}
I'm going to make use of this Terraform registry module to create the VPC private hosted zones.
I've defined my parent module variable just like below, So I can use variables depends on my workspace to pick the appropriate Zone attributes and it works pretty well.
Now what I want to do is, I want to add the VPC_ID into the resource block from my data source output (data.aws_vpc.this.id), so I can use the variable block like in domain2.my. The reason is, if I use the variable, I want to hardcode the VPC ID (Just like in domain1.my). So I need to avoid hardcoded VPC ID and able add the VPC ID dynamically from data source and also should be able to add additional VPC IDs just like shown in the domain3.my
Can anyone help to solve this problem? Thank you!
variables.tf
variable "zone_name" {
description = "Domain name of The Zone"
default = {
dev = {
"domain1.my" = {
comment = "DEV Private Hosted Zone"
vpc = [
{
vpc_id = "xxxxx"
}
]
}
}
stg = {
"domain2.my" = {
comment = "STG Private Hosted Zone"
vpc = []
}
}
uat = {
"domain3.my" = {
comment = "UAT Private Hosted Zone"
vpc = [
{
vpc_id = "VPC ID"
},
{
vpc_id = "Another VPC ID"
}
]
}
}
}
main.tf
#source = "../../../../tf-modules/aws/route53/zones/"
module "zones" {
source = "terraform-aws-modules/route53/aws//modules/zones"
version = "~> 1.0"
zones = var.zone_name[local.workspace]
tags = {
ManagedBy = "Terraform"
}
}
data "aws_vpc" "this" {
tags = {
env = local.workspace
}
}
The code below works, but I am not able to add a -vcn at the end of the vcn name and also I am unable to make sure that the vcn sits in the compartment it is intended to sit in.
My variables.tf looks like -
#Compartment
variable "pv_compartment" {
type = map(string)
description = "Compartment Details"
}
variable "pv_enable_delete" {
description = "enable duplicate check on compartment names and delete on destroy"
}
variable "pv_subtenancy_ocid" {
description = "sub-tenancy ocid"
}
# VCN
variable "pv_vcn" {
type = map(string)
description = "VCN Details"
}
My main.tf looks like -
resource "oci_identity_compartment" "tf_compartment" {
for_each = var.pv_compartment
compartment_id = var.pv_subtenancy_ocid
description = each.value
name = each.key
enable_delete = var.pv_enable_delete
}
resource "oci_core_vcn" "tf_vcn" {
count = length(var.pv_vcn)
cidr_block = values(var.pv_vcn)[count.index]
compartment_id = element([for x in oci_identity_compartment.tf_compartment: x.id], count.index)
display_name = keys(var.pv_vcn)[count.index]
}
My terraform.tfvars looks like -
pv_subtenancy_ocid = "ocid1.tenancy.oc1..aaaaaaaa"
pv_compartment = {
mngmt-compartment = "Management Services Compartment"
app-compartment = "Application Compartment"
dmz-compartment = "DMZ Compartment"
db-compartment = "DB Compartment"
}
pv_enable_delete = "true"
#VCN Details
pv_vcn = {
mngmt = "10.234.0.0/23"
app = "10.234.10.0/23"
dmz = "10.234.2.0/23"
db = "10.234.16.0/23"
}
Please use concat like this:
${var.label_prefix}-${var.vcn_name}
#Kalyan
Your code is actually working well with very minor modifications - I tested it from cloud shell.
The mods I've done:
in terraform.tfvars used a compartment id instead of the tenancy ID in the variable pv_subtenancy_ocid (because I don't have access to create subcompartments under root). This may not be necessary in your case if you have rights to create resources (sub compartements) under root.
in main.tf, as suggested by #mrtaylor2112, added an interpolation like so
display_name = "${keys(var.pv_vcn)[count.index]}-vcn"
With proper authorization and provider setup, the config builds and applies as expected, creating VCNs in their corresponding sub compartments.
Regards
I designed an AWS codepipeline module using terraform module, I have multiple actual codepipelines using the codepipeline module. I use module as design pattern because all the codepipelines look similar, except that some of the codepipelines need approval stages, some do not need. How do I design the codepipeline module approval stages so that the actual codepipelines can be created based on different needs?
I tried to use count = 0 or 1 to control the stage but it does not work because the stage is not resource-level. Is there any tricky way or workaround?
I feel this link asked the similar question but I cannot figure out what is the answer:
Terraform & AWS CodePipeline - Dynamically define actions on a stage
Here is my codepipeline terraform module:
resource "aws_codepipeline" "dev" {
name = "my_codepipeline"
role_arn = ...
...
stage {
name = "Source"
...
}
stage {
name = "test"
...
}
stage {
# count = 0 # or 1. it does not work
name = "Approval"
action {
name = "Approval"
owner = "AWS"
category = "Approval"
provider = "Manual"
version = "1"
configuration {
NotificationArn = "..."
CustomData = "..."
ExternalEntityLink = "..."
}
}
}
stage {
name = "prod"
...
}
}
To dynamically add a stage (and not just an action) you could do the following:
dynamic "stage" {
for_each = var.production_approval ? [1] : []
content {
name = "Approve"
action {
configuration = {
NotificationArn = var.approve_sns_arn
CustomData = var.approve_comment
}
name = "Production-Approval"
category = "Approval"
owner = "AWS"
provider = "Manual"
version = "1"
}
}
}
When going through your use case, I have the feeling that it is very suitable with new terraform feature in v0.12.x
Below is a sample on how to use for_each to set dynamic target regions, you should be fine to do the same for stages.
dynamic "target_region" {
for_each = var.target_image_regions
content {
name = target_region.value
regional_replica_count = 1
}
}
let me know if this works for you or not.
Reference: https://www.hashicorp.com/blog/announcing-terraform-0-12
I figured that you can get this working in Terraform 0.12+ as BMW said, but only if you have number of block greater than 0.
At least 1 "action" blocks are required.
Unfortunately, my (and your) use case required 0/1 actions depending on the environment so we have to manage it manually for a while.
Cheers.
dynamic "action" {
for_each = local.production_approval # e.g. [] || [true]
content {
category = "Approval"
configuration = {}
input_artifacts = []
name = "Production-Approval"
output_artifacts = []
owner = "AWS"
provider = "Manual"
run_order = 1
version = "1"
}
}