Azure Function AppSettings using Terraform and mutliple sources for map - azure

so in summary I am specifically looking to maintain the app settings for my azure functions using two different sources,
the first source is a map of custom settings that will be maintained manually or through code which might have little change
The second source of app settings map are key secret uri's as per the code before, this enables the azure function to use secret references as configuration value.
I am trying to automate the process of retrieving a subset of secrets dynamically from keyvault and merging it into the custom map app settings that I define in code.
Question:
My ideal world would be that i update the list secretKeys and the map appSettingsSecretsMap get's dynamically created and then consumed by resource creation resource "azurerm_function_app" "functionApp_workerFunctions" in its appsettings. Does anyone have a idea of how I might achieve this a bit more dynamically?
My full code is as per below:
variable "secretKeys" {
type = list(string)
default = [
"TestDbPassword",
"TestDbUserId"]
}
data "azurerm_key_vault" "keyvault" {
name = "source-keyvault"
resource_group_name = "source-keyvault-rg"
}
data "azurerm_key_vault_secret" "kvSecrets" {
for_each = toset(var.secretKeys)
name = each.key
key_vault_id = data.azurerm_key_vault.keyvault.id
}
# Testing Access to secret
output "TestDbPassword" {
value = data.azurerm_key_vault_secret.kvSecrets["TestDbPassword"].id
}
#https://learn.microsoft.com/en-us/azure/app-service/app-service-key-vault-references
variabe "appSettingsSecretsMap" {
type = map
default = {
DBPassword = "#Microsoft.KeyVault(SecretUri=${data.azurerm_key_vault_secret.kvSecrets["TestDbPassword"].id})"
DBUserId = "#Microsoft.KeyVault(SecretUri=${data.azurerm_key_vault_secret.kvSecrets["TestDbUserId"].id})"
}
}
# Reference for appSettings https://learn.microsoft.com/en-us/azure/azure-functions/functions-app-settings
variable "appSettingsCustomMap" {
type = map
default = {
WEBSITE_RUN_FROM_PACKAGE = ""
FUNCTIONS_WORKER_RUNTIME = ""
APPINSIGHTS_INSTRUMENTATIONKEY = ""
#FUNCTIONS_EXTENSION_VERSION = "~1"
}
}
resource "azurerm_function_app" "functionApp_workerFunctions" {
name = "worker-function-${var.ENVIRONMENT}"
location = "XYZ-Example"
resource_group_name = "XYZ-Example"
app_service_plan_id = "XYZ-Example"
storage_account_name = "XYZ-Example"
storage_account_access_key = "XYZ-Example"
app_settings = merge(var.appSettingsMap, var.appSettingsSecretsMap)
}

For the custom settings, I think it's better to set it in variable manually. It has a lot of things with a little change. If you make it automated, I think it will be a little redundancy. Just do it as you show in the question.
For key fault, I recommend you use the locals block:
locals {
appSettingsSecretsMap = {
DBPassword = "#Microsoft.KeyVault(SecretUri=${data.azurerm_key_vault_secret.kvSecrets[0].id})"
DBUserId = "#Microsoft.KeyVault(SecretUri=${data.azurerm_key_vault_secret.kvSecrets[1].id})"
}
}
The data for the key vault secrets will return a list with the element like this:
So you cannot quote it with the secret name. Just do it as what I show you.

Related

Accessing other resources created by the same for_each set

I'm trying to create an az ad app and credential for each entry in a locals set.
The objects in the locals set have values that are needed for both resources, but my issue is the credentials resource needs values from both the locals object as well as the ad application.
This would be easy normally, but I am using a for_each which is complicated, and the value of each for the credential resource is the ad application. Is there any way I can get access to the each of az app resource but from the credential resource?
locals {
github_repos_with_apps = {
tftesting_testing = {
repo = "tftesting-testing"
environment = "tfplan"
}
}
}
resource "azuread_application" "aadapp" {
for_each = local.github_repos_with_apps
display_name = join("-", ["github-actions", each.value.repo, each.value.environment])
owners = [data.azuread_client_config.current.object_id]
}
resource "azuread_application_federated_identity_credential" "cred" {
for_each = azuread_application.aadapp
application_object_id = each.value.object_id
display_name = "my-repo-deploy"
description = "Deployments for my-repo"
audiences = ["api://AzureADTokenExchange"]
issuer = "https://token.actions.githubusercontent.com"
subject = "repo:my-org/${each.value.<something?>.repo}:environment:${each.value.<something?>.environment}"
}
In the snippet above I need the cred resource to access aadapp.object_id but also reference the locals value in order to get rep and environment. Since both cred and aadapp both use for_each the meaning of each.value changes. I'd like to reference the each.value of aadapp from cred.
My problem line is the subject value in the cred resource:
subject = "repo:my-org/${each.value.<something?>.repo}:environment:${each.value.<something?>.environment}"
I think I may have to use modules to accomplish this, but I feel there is a quicker way, like being able to store a temporary value on aadapp that would let me reference it.
After scouring some examples I did find out how to achieve this.
If I change all resources to use for_each = local.github_repos_with_apps, I can then use 'each.key` as a lookup to get the other associated resources like so:
application_object_id = resource.azuread_application.aadapp[each.key].object_id
This allows the cred resource to reference the locals values directly
subject = "repo:my-org/${each.value.repo}:environment:${each.value.environment}"
Full code:
locals {
github_repos_with_apps = {
first_test : {
repo = "tftesting-testing"
environment = "tfplan"
}
second_test : {
repo = "bleep-testing"
environment = "tfplan"
}
}
}
resource "azuread_application" "aadapp" {
for_each = local.github_repos_with_apps
display_name = join("-", ["github-actions", each.value.repo, each.value.environment])
owners = [data.azuread_client_config.current.object_id]
lifecycle {
ignore_changes = [
required_resource_access
]
}
}
resource "azuread_application_federated_identity_credential" "cred" {
for_each = local.github_repos_with_apps
application_object_id = resource.azuread_application.aadapp[each.key].object_id
display_name = each.value.repo
description = "Deployments for my-repo"
audiences = ["api://AzureADTokenExchange"]
issuer = "https://token.actions.githubusercontent.com"
subject = "repo:my-org/${each.value.repo}:environment:${each.value.environment}"
}

The code works but I am unable to make place vcn in a particular compartment

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

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

terraform module depends_on Azure

I am building a production infrastructure in Azure cloud with terraform. My requirements are below.
Azure key vault should be provision first, as I will utilize a secret from there. But as terraform module doesn't support depend_on. Any workaround will be highly appricaiable.
source = "./../modules/azurekeyvault/"
username = "${var.username}"
tags_environment = "${var.tags_environment}"
}
module "mysql" {
source = "./../modules/mysql/"
}
Azure key vault module.
name = "${var.lsrkeyvault}"
location = "${data.azurerm_resource_group.lsr.location}"
resource_group_name = "${data.azurerm_resource_group.lsr.name}"
enabled_for_disk_encryption = true
tenant_id = "${data.azurerm_client_config.current.tenant_id}"
sku_name = "standard
resource "azurerm_key_vault_secret" "userlist" {
count = length(var.username)
name = "${var.username[count.index]}"
value = "${bcrypt(random_string.password.result)}"
key_vault_id = "${azurerm_key_vault.kvlsr.id}"
tags = {
environment = "${var.tags_environment}"
}
}
Mysql Module code:
name = "kyv-lsr-dev"
resource_group_name = "rgroup"
}
data "azurerm_key_vault_secret" "userlist" {
name = "mylab"
key_vault_id = "${data.azurerm_key_vault.keyvault.id}"
I don't know if this would work, but here is what I would recommend trying to help TF build out the correct dependency graph. I would make the keyvault id a variable in your module. That way when you use the module you will be explicitly calling the keyvault which should trigger it to be created before the module is executed.
module "mysql" {
source = "./../modules/mysql/"
keyvault_id = "${module.keyvault.id}"
}
This would require your mysql module to take keyvault_id as a variable and use that instead of the data resoruce. It would also require your keyvault module to output the keyvault id. Again, this may not work, but I think it will.

Error: azurerm_app_service.ci_rg: resource repeated multiple times

I am trying to deploy 2 different Azure Apps in the same resource group.
These Azure Apps are defined as docker images stored in an Azure Container Registry (where I previously pushed those docker images).
I am not able to deploy both of them at the same time because I think there is something wrong in the way I am defining them as Terraform is expecting to find only one azurerm_app_service, but I am not sure how I can work around this?
When I run this command: terraform plan -var-file test.tfvars, then I see this message in the output:
Error: azurerm_app_service.ci_rg: resource repeated multiple times
How do I define "2 different resources of the same type"?
This is the content of the main.tf file (where I inject the variables defined in variables.tf with the values defined in test.tfvars):
// the resource group definition
resource "azurerm_resource_group" "ci_rg" {
name = "${var.resource_group_name}"
location = "${var.azure_location}"
}
// the app service plan definition
resource "azurerm_app_service_plan" "ci_rg" {
name = "${var.app_service_plan}"
location = "${azurerm_resource_group.ci_rg.location}"
resource_group_name = "${azurerm_resource_group.ci_rg.name}"
kind = "Linux"
sku {
tier = "Standard"
size = "S1"
capacity = 2 // for both the docker containers
}
properties {
reserved = true
}
}
// the first azure app
resource "azurerm_app_service" "ci_rg" {
name = "${var.first_app_name}"
location = "${azurerm_resource_group.ci_rg.location}"
resource_group_name = "${azurerm_resource_group.ci_rg.name}"
app_service_plan_id = "${azurerm_app_service_plan.ci_rg.id}"
site_config {
linux_fx_version = "DOCKER|${var.first_app_docker_image_name}"
}
app_settings {
"CONF_ENV" = "${var.conf_env}"
"DOCKER_REGISTRY_SERVER_URL" = "${var.docker_registry_url}",
"DOCKER_REGISTRY_SERVER_USERNAME" = "${var.docker_registry_username}",
"DOCKER_REGISTRY_SERVER_PASSWORD" = "${var.docker_registry_password}",
}
}
// the second azure app
resource "azurerm_app_service" "ci_rg" {
name = "${var.second_app_name}"
location = "${azurerm_resource_group.ci_rg.location}"
resource_group_name = "${azurerm_resource_group.ci_rg.name}"
app_service_plan_id = "${azurerm_app_service_plan.ci_rg.id}"
site_config {
linux_fx_version = "DOCKER|${var.second_app_docker_image_name}"
}
app_settings {
"CONF_ENV" = "${var.conf_env}"
"DOCKER_REGISTRY_SERVER_URL" = "${var.docker_registry_url}",
"DOCKER_REGISTRY_SERVER_USERNAME" = "${var.docker_registry_username}",
"DOCKER_REGISTRY_SERVER_PASSWORD" = "${var.docker_registry_password}",
}
}
Edit:
I am not entirely sure about how this Terraform thing works, but I think the label azurerm_app_service is taken by the "syntax of Terraform". See the docs here: https://www.terraform.io/docs/providers/azurerm/r/app_service.html
where the title is azurerm_app_service. So I don't think I can change that.
My guess would be you need to rename the second one to something else. Like this: resource "azurerm_app_service" "ci_rg_second". It obviously doesnt like the fact that it has the same name.

Resources