Terraform output variables as input - azure

I'm new to Terraform and trying to wrap my head around the use of output variables. we are on AKS, and I'm deploying the following resources: resource group, log analytics workspace, Azure Kubernetes. When Log analytics is deployed, I capture the workspace ID into an output variable. Now, when Terraform deploys Kubernetes, it needs to know the workspace ID, how can I pass the output value to the addon_profile (last line in the code below)?
Error:
environment = "${log_analytics_workspace_id.value}"
A managed resource "log_analytics_workspace_id" "value" has not been declared in the root module.
Code:
resource "azurerm_resource_group" "test" {
name = "${var.log}"
location = "${var.location}"
}
resource "azurerm_log_analytics_workspace" "test" {
name = "${var.logname}"
location = "${azurerm_resource_group.loganalytics.location}"
resource_group_name = "${azurerm_resource_group.loganalytics.name}"
sku = "PerGB2018"
retention_in_days = 30
}
**output "log_analytics_workspace_id" {
value = "${azurerm_log_analytics_workspace.test.workspace_id}"
}**
....................................................
addon_profile {
oms_agent {
enabled = true
**log_analytics_workspace_id = "${log_analytics_workspace_id.value}"**
}
}

Terraform's output values are like the "return values" of a module. In order to declare and use the log_analytics_workspace_id output value, you would need to put all of the code for the creation of the resource group, log analytics workspace, and Azure Kubernetes infrastructure into a single Terraform module, and then reference the output value from outside of the module:
# declare your module here, which contains creation code for all your Azure infrastructure + the output variable
module "azure_analytics" {
source = "git::ssh://git#github.com..."
}
# now, you can reference the output variable in your addon_profile from outside the module:
addon_profile {
oms_agent {
enabled = true
log_analytics_workspace_id = "${module.azure_analytics.log_analytics_workspace_id}"
}
}
On the other hand, if you just want to use the workspace_id value from your azurerm_log_analytics_workspace within the same code, just reference it like azurerm_log_analytics_workspace.test.workspace_id.

Related

Terraform returning the path of Azure Resource Group instead of ID

I'm using Terraform to manage my Infrastructure and Terratest for Testing.
In terraform, I have a module called ResourceGroup (located in the Modules folder)
resource "azurerm_resource_group" "azResourceGroup" {
name = var.resource_group_name
location = var.resource_region
tags = var.tags
}
output "resource_group_name" {
value = azurerm_resource_group.azResourceGroup.id
}
And I'm using this module in the main.tf to create an Azure Resource Group.
module "azResourceGroup" {
source = "./Modules/ResourceGroup"
resource_group_name = var.resource_group_name
tags = var.tags
}
output "resource_group_name" {
value = "${module.azResourceGroup.resource_group_name}"
}
When I execute terraform apply the output of main.tf is returning the full path of the newly created Resource Group instead of its ID.
Here's the Output
Changes to Outputs:
- resource_group_name = "/subscriptions/xxxxxxx-xxxx-xxxx-xxxx-x/resourceGroups/rg-svf-nprd-test" -> null
Preferably, it should return only rg-svf-nprd-test (Id from output variable).
Am I missing anything here? Please help me with this.

Terraform can't create resource in group already exist

I would create a sample azure web app using Terraform.
I use this code to create the resoucre.
This is my main.tf file :
resource "azurerm_resource_group" "rg" {
name = var.rgname
location = var.rglocation
}
resource "azurerm_app_service_plan" "plan" {
name = var.webapp_plan_name
location = var.rglocation
resource_group_name = var.rgname
sku {
tier = var.plan_settings["tier"]
size = var.plan_settings["size"]
capacity = var.plan_settings["capacity"]
}
}
resource "azurerm_app_service" "webapp" {
name = var.webapp_name
location = var.rglocation
resource_group_name = var.rgname
app_service_plan_id = azurerm_app_service_plan.plan.id
}
and this is the variable.tf
# variables for Resource Group
variable "rgname" {
description = "(Required)Name of the Resource Group"
type = string
default = "example-rg"
}
variable "rglocation" {
description = "Resource Group location like West Europe etc."
type = string
default = "eastus2"
}
# variables for web app plan
variable "webapp_plan_name" {
description = "Name of webapp"
type = string
default = "XXXXXXXXXx"
}
variable "plan_settings" {
type = map(string)
description = "Definition of the dedicated plan to use"
default = {
kind = "Linux"
size = "S1"
capacity = 1
tier = "Standard"
}
}
variable "webapp_name" {
description = "Name of webapp"
type = string
default = "XXXXXXXX"
}
The terraform apply --auto-approve show a error :
Error: creating/updating App Service Plan "XXXXXXXXXXX" (Resource Group "example-rg"): web.AppServicePlansClient#CreateOrUpdate: Failure sending request: StatusCode=0 -- Original Error: Code="ResourceGroupNotFound" Message="Resource group 'example-rg' could not be found."
but in Azure Portal , the ressource group is created
what's wrong in my code ?
Can i hold on Terraform to check if resource is created or not before pass to next resource ?
You need to reference the resources to indicate the dependency between them to terraform, so that it can guarantee, that the resource group is created first, and then the other resources. The error indicates, that the resource group does not exist, yet. Your code tries to create the ASP first and then the RG.
resource "azurerm_resource_group" "rg" {
name = var.rgname
location = var.rglocation
}
resource "azurerm_app_service_plan" "plan" {
...
resource_group_name = azurerm_resource_group.rg.name
...
}
... but I can't use variable?
To answer that question and give more detail about the current issue, what's happening in your original code is that you're not referencing terraform resources in a way that terraform can use to create its dependency graph.
if var.rgname equals <my-rg-name> and azurerm_resource_group.rg.name also equals <my-rg-name> then technically, that's the same, right?
Well, no, not necessarily. They indeed have the same value. The difference is that the first one just echos the value. The second one contains the same value but it's also instruction to terraform saying, "Hey, wait a minute. We need the name value from azurerm_resource_group.rg so let me make sure I set that up first, and then I'll provision this resource"
The difference is subtle but important. Using values in this way lets Terraform understand what it has to provision first and lets it create its dependency graph. Using variables alone does not. Especially in large projects, alway try to use the resource variable value instead of just input variables which will help avoid unnecessary issues.
It's quite common to use an input variable to define certain initial data, like the name of the resource group as you did above. After that, however, every time resource_group_name is referenced in the manifests you should always use terraform generated value, so resource_group_name = azurerm_resource_group.rg.name

Creating azure automation dsc configuration and dsc configuration node using terraform doesn't seems to be working

As a very first step of my release process I run the following terraform code
resource "azurerm_automation_account" "automation_account" {
for_each = data.terraform_remote_state.pod_bootstrap.outputs.ops_rg
name = "${local.automation_account_prefix}-${each.key}"
location = each.key
resource_group_name = each.value.name
sku_name = "Basic"
tags = {
environment = "development"
}
}
The automation accounts created as expected and I can see those in Azure portal.
I also have terraform code that creates a couple of windows VMs,each VM creation accompained by the following
resource "azurerm_virtual_machine_extension" "dsc" {
name = "DevOpsDSC"
virtual_machine_id = var.vm_id
publisher = "Microsoft.Powershell"
type = "DSC"
type_handler_version = "2.83"
settings = <<SETTINGS_JSON
{
"configurationArguments": {
"RegistrationUrl": "${var.dsc_server_endpoint}",
"NodeConfigurationName": "${var.dsc_config}",
"ConfigurationMode": "${var.dsc_mode}",
"ConfigurationModeFrequencyMins": 15,
"RefreshFrequencyMins": 30,
"RebootNodeIfNeeded": false,
"ActionAfterReboot": "continueConfiguration",
"AllowModuleOverwrite": true
}
}
SETTINGS_JSON
protected_settings = <<PROTECTED_SETTINGS_JSON
{
"configurationArguments": {
"RegistrationKey": {
"UserName": "PLACEHOLDER_DONOTUSE",
"Password": "${var.dsc_primary_access_key}"
}
}
}
PROTECTED_SETTINGS_JSON
}
The result is the following
So VM extension is created for each VM and the status says that provisioning succeeded.
For the next step I run the following terraform code
resource "azurerm_automation_dsc_configuration" "iswebserver" {
for_each = data.terraform_remote_state.pod_bootstrap.outputs.ops_rg
name = "iswebserver"
resource_group_name = each.value.name
automation_account_name = data.terraform_remote_state.ops.outputs.automation_account[each.key].name
location = each.key
content_embedded = "configuration iswebserver {}"
}
resource "azurerm_automation_dsc_nodeconfiguration" "iswebserver" {
for_each = data.terraform_remote_state.pod_bootstrap.outputs.ops_rg
name = "iswebserver.localhost"
resource_group_name = each.value.name
automation_account_name = data.terraform_remote_state.ops.outputs.automation_account[each.key].name
depends_on = [azurerm_automation_dsc_configuration.iswebserver]
content_embedded = file("${path.cwd}/iswebserver.mof")
}
The mof file content is the following
/*
#TargetNode='IsWebServer'
#GeneratedBy=P120bd0
#GenerationDate=02/25/2021 17:33:16
#GenerationHost=D-MJ05UA54
*/
instance of MSFT_RoleResource as $MSFT_RoleResource1ref
{
ResourceID = "[WindowsFeature]IIS";
IncludeAllSubFeature = True;
Ensure = "Present";
SourceInfo = "D:\\DSC\\testconfig.ps1::5::9::WindowsFeature";
Name = "Web-Server";
ModuleName = "PsDesiredStateConfiguration";
ModuleVersion = "1.0";
ConfigurationName = "TestConfig";
};
instance of OMI_ConfigurationDocument
{
Version="2.0.0";
MinimumCompatibleVersion = "1.0.0";
CompatibleVersionAdditionalProperties= {"Omi_BaseResource:ConfigurationName"};
Author="P120bd0";
GenerationDate="02/25/2021 17:33:16";
GenerationHost="D-MJ05UA54";
Name="TestConfig";
};
After running the code I have got the following result
The configuration is created as expected, clicking on configuration entry in UI grid, leads to the following
Meaning that node configuration is created as well. My expectation was that for each VM I will see the Node configured to run configuration provided in mof file but Nodes UI shows empty Nodes
So I was trying to configure node manually to connect all peaces together
and that fails with the following
So I am totally confisued. On the one hand there's azurerm_virtual_machine_extension that allows to create extension and bind it to the automation account. In addition there are azurerm_automation_dsc_configuration and azurerm_automation_dsc_nodeconfiguration that allows to create configuration and node configuration. But the bottom line is that you cannot connect all those dots to be able to create node.
Just to confirm that configuration is valid, I create additional vm without using azurerm_virtual_machine_extension and I was able succesfully add this MV to created node configuration
The problem was in azurerm_virtual_machine_extension dsc_configuration parameter. The value needs to be the same as name property of the azurerm_automation_dsc_nodeconfiguration resource.

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

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