How can I reuse the terraform output values in another deployment - azure

I have a terraform code that deploys Azure resources and outputs a bunch of values I need to use for further configuration.
I have initialized terraform backend and the state is saved to an Azure Storage account, I see the tfstate file with all the correct values.
FYI I have added this configuration but still no luck, also I am running the terraform init command in the second location so the backend is initialized with the same state:
backend "azurerm" {
storage_account_name = "${var.STATE_STORAGE_ACCOUNT_NAME}"
container_name = "${var.STATE_CONTAINER_NAME}"
key = "${var.STATE_STORAGE_ACCOUNT_KEY}"
}
What I want to be able to do is pull this state in some way so I can do terraform output -raw some_output in a different location than where I deployed the resources.
I can't seem to find a way to do this. How could this be achieved? Thanks

It really depends on your use case. You can take two different approaches:
Import resource with "data" source
Data sources allow Terraform use information defined outside of Terraform, defined by another separate Terraform configuration, or modified by functions.
Terraform docs
For AWS it would be something like this:
// create ssm parameter in Terraform code A
resource "aws_ssm_parameter" "secret" {
name = "/secret"
type = "String"
value = "SecretValue"
}
// Import this resource in Terraform code B
data "aws_ssm_parameter" "imported_secret" {
name = "/secret"
}
// So later you can reference it
locals any {
secretValue = data.aws_ssm_parameter.secret.value
}
Create modules
Modules are containers for multiple resources that are used together. A module consists of a collection of .tf and/or .tf.json files kept together in a directory. Modules are the main way to package and reuse resource configurations with Terraform.
Terraform docs
It is basic example of Terraform modules. We created module vpc where source of this module is in ../../modules/vpc directory and we referenced this module by module.vpc in rds module.
module "vpc" {
source = "../../modules/vpc"
env = var.env
azs = var.azs
cidr = var.cidr
db_subnets = var.db_subnets
private_subnets = var.private_subnets
public_subnets = var.public_subnets
}
module "rds" {
source = "../../modules/rds"
db_subnets_cidr_blocks = module.vpc.db_subnets_cidr_block
private_subnets_cidr_blocks = module.vpc.private_subnets_cidr_block
public_subnets_cidr_blocks = module.vpc.public_subnets_cidr_block
vpc_id = module.vpc.vpc_id
env = var.env
db_subnets_ids = module.vpc.db_subnets
}

I didn't find a straight forward way to do this, so the solution was, since the state file was being saved to Azure Blob Storage:
Run an Azure CLI command to get the blob locally:
az storage blob download --container-name tstate --file $tf_state_file_name --name $tf_state_file_name --account-key $tf_state_key --account-name $tf_state_storage_account_name
Where the local file name is: $tf_state_file_name
Read the desired values using JQ:
jq '.outputs.storage_account_name.value' ./$tf_state_file_name -r
You can read the values raw thanks to the -r paramater. This is the same as doing:
terraform output -raw storage_account_name

Related

How to create Azure Databricks Notebook via Terraform?

So I am completely new to the terraform and I found that by using this in terraform main.tf I can create Azure Databricks infrastructure:
resource "azurerm_databricks_workspace" "bdcc" {
depends_on = [
azurerm_resource_group.bdcc
]
name = "dbw-${var.ENV}-${var.LOCATION}"
resource_group_name = azurerm_resource_group.bdcc.name
location = azurerm_resource_group.bdcc.location
sku = "standard"
tags = {
region = var.BDCC_REGION
env = var.ENV
}
}
And I also found here
That by using this I can even create particular notebook in this Azure DataBricks infrastructure:
resource "databricks_notebook" "notebook" {
content_base64 = base64encode(<<-EOT
# created from ${abspath(path.module)}
display(spark.range(10))
EOT
)
path = "/Shared/Demo"
language = "PYTHON"
}
But since I am new to this, I am not sure in what order I should put those pieces of code together.
It would be nice if someone could point me to the full example of how to create notebook via terraform on Azure Databricks.
Thank you beforehand!
In general you can put these objects in any order - it's a job of the Terraform to detect dependencies between the objects and create/update them in the correct order. For example, you don't need to have depends_on in the azurerm_databricks_workspace resource, because Terraform will find that it needs resource group before workspace could be created, so workspace creation will follow the creation of the resource group. And Terraform is trying to make the changes in the parallel if it's possible.
But because of this, it's becoming slightly more complex when you have workspace resource together with workspace objects, like, notebooks, clusters, etc. As there is no explicit dependency, Terraform will try create notebook in parallel with creation of workspace, and it will fail because workspace doesn't exist - usually you will get a message about authentication error.
The solution for that would be to have explicit dependency between notebook & workspace, plus you need to configure authentication of Databricks provider to point to newly created workspace (there are differences between user & service principal authentication - you can find more information in the docs). At the end your code would look like this:
resource "azurerm_databricks_workspace" "bdcc" {
name = "dbw-${var.ENV}-${var.LOCATION}"
resource_group_name = azurerm_resource_group.bdcc.name
location = azurerm_resource_group.bdcc.location
sku = "standard"
tags = {
region = var.BDCC_REGION
env = var.ENV
}
}
provider "databricks" {
host = azurerm_databricks_workspace.bdcc.workspace_url
}
resource "databricks_notebook" "notebook" {
depends_on = [azurerm_databricks_workspace.bdcc]
...
}
Unfortunately, there is no way to put depends_on on the provider level, so you will need to put it into every Databricks resource that is created together with workspace. Usually the best practice is to have a separate module for workspace creation & separate module for objects inside Databricks workspace.
P.S. I would recommend to read some book or documentation on Terraform. For example, Terraform: Up & Running is very good intro

Terraform: How can I reference Terraform cloud environmental variables?

I'm using Terraform cloud. I would like to take advantage of using AWS Tags with my resources. I want to tag each resource defined in Terraform with the current GIT Branch Name. That way I can separate dev from production.
Terraform has a list of Environmental Variables that do reference the GIT Branch Name with their service in the cloud as:
TFC_CONFIGURATION_VERSION_GIT_BRANCH - This is the name of the branch that the associated Terraform configuration version was ingressed from (e.g. master).
How can I reference the TFC_CONFIGURATION_VERSION_GIT_BRANCH environmental variable in the following resource for an example VPC?
resource "aws_vpc" "example_vpc" {
cidr_block = "10.0.0.0/16"
tags = {
product = var.product
stage = var.TFC_CONFIGURATION_VERSION_GIT_BRANCH
}
}
reference: https://www.terraform.io/docs/language/values/variables.html#environment-variables
I figured it out! Wish the documentation was clearer on this with the cloud.
You will have to set a empty variable. I defined mine in variables.tf as:
variable "TFC_CONFIGURATION_VERSION_GIT_BRANCH" {
type = string
default = ""
}
Per the documentation I linked in question. TFC_CONFIGURATION_VERSION_GIT_BRANCH is injected automatically into the environmental variables with each cloud run. Defining the full name of the environmental variable as the variable worked.
resource "aws_vpc" "example_vpc" {
cidr_block = "10.0.0.0/16"
tags = {
product = var.product
stage = var.TFC_CONFIGURATION_VERSION_GIT_BRANCH
}
}
Then the plan output was successful in the cloud:
Terraform will perform the following actions:
# aws_vpc.example_vpc will be updated in-place
~ resource "aws_vpc" "example_vpc" {
id = "vpc-0b19679e6464b8481"
~ tags = {
~ "stage" = "None" -> "develop"
# (1 unchanged element hidden)
}
# (14 unchanged attributes hidden)
}
Terraform Cloud serves as a remote execution environment for Terraform CLI (amongst other things) and so when you configure a workspace in Terraform Cloud many of the settings are about the context where Terraform CLI will run, and how Terraform Cloud will run it.
Part of that configuration model is the idea of environment variables, which correspond with the same environment variables you might set in your shell when running Terraform CLI locally. As with local Terraform, those environment variables are not directly usable from your configuration but are instead settings for other systems that Terraform and providers will interact with, such as the AWS_ACCESS_KEY_ID environment variable conventionally used by AWS software as a way to statically configure the access key identifier to use.
Terraform Cloud also allows you to set "Terraform Variables", and those correspond with Input Variables in the Terraform language. These are the settings for your Terraform configuration itself, as opposed to other software it will interact with, and so you can refer to these by declaring them using variable blocks and then using expressions like var.example elsewhere in the root module. Internally, Terraform Cloud is passing the configured values to Terraform CLI by generating a file called terraform.tfvars, which Terraform CLI looks for as a default source of variable values.
Both of these types of variables are useful for different purposes, and so most Terraform workspaces include a mixture of environment variables for configuring external systems and "Terraform variables" for configuring the current Terraform configuration itself.
For the benefit of folks using Terraform CLI outside of Terraform Cloud, Terraform CLI actually also offers a way to set Input Variables using environment variables, and technically you can do that within Terraform Cloud too because it's ultimately just running Terraform with environment variables set in the same way as you might locally. That's not the intended way to use Terraform Cloud, and so I'm mentioning it only for completeness because the terminology overlap here might be confusing.
Terraform cloud workspace variables can be set as category "terraform" or as category "env". In a remote execution setup, you are unable to reference workspace vars defined as "env" from your terraform code. Instead, those vars will be automatically injected into the execution environment. To be able to reference a workspace variable in terraform code, set the variable type to "terraform" but do not check the "HCL" tick. Defining a blank variable is still needed. Hope this helps someone! Unfortunately, the documentation is very unclear about this.
Terraform Cloud: Create a workspace variable
key: example_variable
value: example_value
category: terraform
Implementation: Configuration (.tf) file
Declare a blank variable
Reference your variable
# Declaring a blank variable
variable "example_variable" {}
# Referencing a variable
block "name" {
input = var.example_variable
Snowflake CI/CD Example
CI/CD pipeline for Snowflake with GitHub Actions and Terraform configured similar to Snowflake's quick start guide
Implemented a private key instead of a user password
variable "snowflake_private_key" {}
terraform {
required_providers {
snowflake = {
source = "chanzuckerberg/snowflake"
version = "0.25.17"
}
}
backend "remote" {
organization = "terraform-snowflake"
workspaces {
name = "gh-actions"
}
}
}
provider "snowflake" {
username = "snowflake-username"
account = "snowflake-account"
region = "snowflake-region"
private_key = var.snowflake_private_key
role = "SYSADMIN"
}

How to share Terraform variables across workpaces/modules?

Terraform Cloud Workspaces allow me to define variables, but I'm unable to find a way to share variables across more than one workspace.
In my example I have, lets say, two workspaces:
Database
Application
In both cases I'll be using the same AzureRM credentials for connectivity. The following are common values used by the workspaces to connect to my Azure subscription:
provider "azurerm" {
subscription_id = "00000000-0000-0000-0000-000000000000"
client_id = "00000000-0000-0000-0000-000000000000"
client_secret = "00000000000000000000000000000000"
tenant_id = "00000000-0000-0000-0000-000000000000"
}
It wouldn't make sense to duplicate values (in my case I'll have probably 10 workspaces).
Is there a way to do this?
Or the correct approach is to define "database" and "application" as a Module, and then use Workspaces (DEV, QA, PROD) to orchestrate them?
In Terraform Cloud, the Workspace object is currently the least granular location where you can specify variable values directly. There is no built in mechanism to share variable values between workspaces.
However, one way to approach this would be to manage Terraform Cloud with Terraform itself. The tfe provider (named after Terraform Enterprise for historical reasons, since it was built before Terraform Cloud launched) will allow Terraform to manage Terraform Cloud workspaces and their associated variables.
variable "workspaces" {
type = set(string)
}
variable "common_environment_variables" {
type = map(string)
}
provider "tfe" {
hostname = "app.terraform.io" # Terraform Cloud
}
resource "tfe_workspace" "example" {
for_each = var.workspaces
organization = "your-organization-name"
name = each.key
}
resource "tfe_variable" "example" {
# We'll need one tfe_variable instance for each
# combination of workspace and environment variable,
# so this one has a more complicated for_each expression.
for_each = {
for pair in setproduct(var.workspaces, keys(var.common_environment_variables)) : "${pair[0]}/${pair[1]}" => {
workspace_name = pair[0]
workspace_id = tfe_workspace.example[pair[0]].id
name = pair[1]
value = var.common_environment_variables[pair[1]]
}
}
workspace_id = each.value.workspace_id
category = "env"
key = each.value.name
value = each.value.value
sensitive = true
}
With the above configuration, you can set var.workspaces to contain the names of the workspaces you want Terraform to manage and var.common_environment_variables to the environment variables you want to set for all of them.
Note that for setting credentials on a provider the recommended approach is to set them in environment variables rather than Terraform variables, because that then makes the Terraform configuration itself agnostic to how those credentials are obtained. You could potentially apply the same Terraform configuration locally (outside of Terraform Cloud) using the integration with Azure CLI auth, while the Terraform Cloud execution environment would often use a service principal.
Therefore to provide the credentials in the Terraform Cloud environment you'd put the following environment variables in var.common_environment_variables:
ARM_CLIENT_ID
ARM_TENANT_ID
ARM_SUBSCRIPTION_ID
ARM_CLIENT_SECRET
If you use Terraform Cloud itself to run operations on this workspace managing Terraform Cloud (naturally, you'd need to set this one up manually to bootstrap, rather than having it self-manage) then you can configure var.common_environment_variables as a sensitive variable on that workspace.
If you instead set it via Terraform variables passed into the provider "azurerm" block (as you indicated in your example) then you force any person or system running the configuration to directly populate those variables, forcing them to use a service principal vs. one of the other mechanisms and preventing Terraform from automatically picking up credentials set using az login. The Terraform configuration should generally only describe what Terraform is managing, not settings related to who is running Terraform or where Terraform is being run.
Note though that the state for the Terraform Cloud self-management workspace will include
a copy of those credentials as is normal for objects Terraform is managing, so the permissions on this workspace should be set appropriately to restrict access to it.
You can now use variable sets to reuse variable across multiple workspaces

state management in terraform

I'm building terraform scripts to orcastrate Azure deployment. I used Azure blob storage to store a tfstate file. This file is shared with several pipelines IAC pipelines.
If for instance I create an Azure Resource Group with terraform, when that is done, I try to create a new custom role, terraform plan will mark the Resource Group for destruction.
This is the script for the role creation:
terraform {
backend "azurerm" {
storage_account_name = "saiac"
container_name = "tfstate"
key = "dev.terraform.tfstate"
resource_group_name = "rg-devops"
}
}
data "azurerm_subscription" "primary" {
}
resource "azurerm_role_definition" "roles" {
count = length(var.roles)
name = "${var.role_prefix}${var.roles[count.index]["suffix_name"]}${var.role_suffix}"
scope = "${data.azurerm_subscription.primary.id}"
permissions {
actions = split(",", var.roles[count.index]["actions"])
not_actions = split(",", var.roles[count.index]["not_actions"])
}
assignable_scopes = ["${data.azurerm_subscription.primary.id}"]
}
and this is script for resource group creation:
terraform {
backend "azurerm" {
storage_account_name = "saiac"
container_name = "tfstate"
key = "dev.terraform.tfstate"
resource_group_name = "rg-devops"
}
}
resource "azurerm_resource_group" "rg" {
count = "${length(var.rg_purposes)}"
name = "${var.rg_prefix}-${var.rg_postfix}-${var.rg_purposes[count.index]}"
location = "${var.rg_location}"
tags = "${var.rg_tags}"
}
If I remove the backend block, everything works as expected, does that mean I need the backend block?
Terraform use the .tfstate file to check and compare your code and existing cloud infra structure, it is like backbone of terraform.
If your code and existing infra is differe, terraform will destroy it and apply code changes.
To overcome this, terraform provides the import facility, you can import the existing resource and terraform will update it's .tfstate file.
This .tfstate file must be specify into your backend.tf file,best practices is to store your .tfstate file on cloude storage not in local directory.
When you run the terraform init command, it will check for the .tfstate file.
below is the sample file for backend.tf file (aws s3 is used):
backend "s3" {
bucket = "backends.terraform.file"
key = "my-terraform.tfstate_key"
region = "my-region-1"
encrypt = "false"
acl = "bucket-owner-full-control"
}
}
A terraform backend is not required for terraform. If you do not use it however no one else will be able to pull your code and run your terraform. The state will ONLY be stored in your .terraform directory. This means if you lose your local files your in trouble. It is recommended to use a backend that also supports state locking which azurerm does. With a backend in place the state will get pulled on terraform init after pulling the repo.

pass output from terraform to Azure Devops Pipeline with state file in azure backend store

I cannot seem to retrieve the public ip address output of Terraform for next step in build pipeline in AzureDevops.
Terraform state pull works and outputs to json file, cannot grep on output.
Terraform state show [options] ADDRESS does not support azure backend so cannot use or grep or filter the output
also tried to store as file and read in the value.
resource "local_file" "foo" {
content = "foo!"
filename = "${path.module}/foo.bar"
}
data "azurerm_public_ip" "buildserver-pip" {
name = "${azurerm_public_ip.buildserver-pip.name}"
resource_group_name = "${azurerm_virtual_machine.buildserver.resource_group_name}"
}
output "public_ip_address" {
value = "${data.azurerm_public_ip.buildserver-pip.ip_address}"
}
expect the public ip address to be passed out so can be used in ansible playbooks, bash or python script in next step
Building on #JleruOHeP answer above the following solution will automatically create a variable for every output provided by the terraform script
Create a PowerShell step in your release and insert the following inline PowerShell:
$json = Get-Content $env:jsonPath | Out-String | ConvertFrom-Json
foreach($prop in $json.psobject.properties) {
Write-Host("##vso[task.setvariable variable=$($prop.Name);]$($prop.Value.value)")
}
Make sure you have provided the environment variable jsonPath like this:
For your purpose, I will suggest you store the terraform in Azure storage account. Then you can use the remote state in another terraform file. Here is an example:
Create public IP and store the state in Azure Storage account blob:
terraform {
backend "azurerm" {
storage_account_name = "yourAccountName"
container_name = "yourContainerName"
key = "terraform.tfstate"
}
}
resource "azurerm_public_ip" "main" {
name = "terraform_backend_pip"
location = "East US"
resource_group_name = "yourResourceGroup"
allocation_method = "Static"
}
# this is important, you can get the remote outputs for this
output "public_address" {
value = "${azurerm_public_ip.main.ip_address}"
}
Quote the remote state in another Terraform file:
data "terraform_remote_state" "azure" {
backend = "azurerm"
config = {
storage_account_name = "charlescloudshell"
container_name = "terraform"
key = "terraform.tfstate"
}
}
# the remote state outputs contain all the output that you set in the above file
output "remote_backend" {
value = "${data.terraform_remote_state.azure.outputs.public_address}"
}
The result below:
You can follow the steps about How to store state in Azure Storage here.
Hope it helps. And if you have any more questions, please let me know. If it works for you, please accept it as the answer.
If I understand your question correctly, you wanted to provision something (public ip) with terraform and then have this available for further steps via a variable. All of it in a single Azure DevOps pipeline.
It can be done with a simple output and powershell script (can be inline!):
1) I assume you already use terraform task for the pipeline (https://github.com/microsoft/azure-pipelines-extensions/tree/master/Extensions/Terraform/Src/Tasks/TerraformTaskV1)
2) Another assumption that you have an output variable (from your example - you do)
3) You canspecify the output variable from this task:
4) And finally add a powershell step as the next step with the simplest script and set up its environment variable to be the $(TerraformOutput.jsonOutputVariablesPath)
$json = Get-Content $env:jsonPath | Out-String | ConvertFrom-Json
Write-Host "##vso[task.setvariable variable=MyNewIp]$($json.public_ip_address.value)"
5) ....
6) PROFIT! You have the IP address available as a pipeline variable MyNewIp now!
terraform output variable
1.As shown in above images you will get variables list only if you add "Terraform by Microsoft DevLabs" as task. Other terraform providers don't support output variables.
Terraform by Microsoft DevLabs
2.There is another way to convert terraform output variable to pipeline variable(In this you need to add Terraform CLI as task Terraform CLI by Charles Zipp)
Step1:Terraform code to output storage account access key
Step2:Add terraform output as task after terraform apply
terraform output variable will be prefixed with TF_OUT to convert it to pipeline variable
access pipeline variable by referencing as $(TF_OUT_variable_name)
Configuration directory of terraform output must be same as terraform apply
Step3:Powershell script to access pipeline variable
Reference:
Terraform Output to Pipeline Variables
https://marketplace.visualstudio.com/items?itemName=charleszipp.azure-pipelines-tasks-terraform

Resources