Creating a centralized secure storage for storing Terraform state

8 minute read

In my previous blog post, I discussed on how you can create Azure resources using Terraform by defining the resources configuration in a Terraform configuration *.tf files.

If you have not read my previous blog post, you can check them out below:

Getting Started with a centralized Terraform state secure storage

AzCLI

In this walkthrough section, I will demonstrate an example on how you can use AzCLI to deploy a centralized secure storage to store all your Terraform state.

Firstly, I will start by validating all existing resource group names before creating a new resource group in Azure using AzCLI.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
export \
  resource_group_name='rg-terraform' \
  resource_location='southeastasia' \
  storage_account_name='storageterraformstate' \
  storage_account_container_name='tfstate' ;

echo "# $(date '+%Y-%m-%d %H:%M:%S %:z') -" \
  "Validating resource group" ;

if [ "$(az group show \
  --name $resource_group_name \
  --query 'properties.provisioningState' \
  --output 'tsv')" = 'Succeeded' ] ;
then \
  
  echo "# $(date '+%Y-%m-%d %H:%M:%S %:z') -" \
    "Existing resource group name found" ;
  
  az group show \
    --name $resource_group_name ;

else \

  echo "# $(date '+%Y-%m-%d %H:%M:%S %:z') -" \
    "Existing resource group name not found" ;

  echo "# $(date '+%Y-%m-%d %H:%M:%S %:z') -" \
    "Creating resource group" ;
  
  az group create \
    --name $resource_group_name \
    --location $resource_location ;

fi ;

Next, I will validate all existing storage account names before creating a new storage account in Azure.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
echo "# $(date '+%Y-%m-%d %H:%M:%S %:z') -" \
  "Validating storage account" ;

if [ "$(az storage account show \
  --name $storage_account_name \
  --query 'provisioningState' \
  --output 'tsv')" = 'Succeeded' ] ;
then \
  
  echo "# $(date '+%Y-%m-%d %H:%M:%S %:z') -" \
    "Existing storage account name found" ;
  
  az storage account show \
    --name $storage_account_name ;

else \

  echo "# $(date '+%Y-%m-%d %H:%M:%S %:z') -" \
    "Existing storage account name not found" ;

  echo "# $(date '+%Y-%m-%d %H:%M:%S %:z') -" \
    "Creating storage account" ;
  
  az storage account create \
    --name $storage_account_name \
    --resource-group $resource_group_name \
    --location $resource_location \
    --kind 'StorageV2' \
    --sku 'Standard_LRS' ;

fi ;

Since the storage account is created or exist in Azure, I will obtain the storage account key. The storage account key will be used by Terraform later.

1
2
3
4
5
6
7
# Store Azure storage account key as variable
export \
  storage_account_key="$(az storage account keys list \
    --name $storage_account_name \
    --resource-group $resource_group_name  \
    --query [0].value \
    --output tsv)" ;

Finally, I will need to validate the existing blob container names in the storage account and create a new blob container is it does not existing in the storage account in Azure. The blob container will be used to contain the Terraform *.tfstate state files.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
echo "# $(date '+%Y-%m-%d %H:%M:%S %:z') -" \
  "Validating storage container" ;

if [ "$(az storage container show \
  --name $storage_account_container_name \
  --account-name $storage_account_name \
  --account-key $storage_account_key \
  --query 'name' \
  --output 'tsv')" = $storage_account_container_name ] ;
then \
  
  echo "# $(date '+%Y-%m-%d %H:%M:%S %:z') -" \
    "Existing storage container name found" ;
  
  az storage container show \
  --name $storage_account_container_name \
  --account-name $storage_account_name \
  --account-key $storage_account_key ;

else \

  echo "# $(date '+%Y-%m-%d %H:%M:%S %:z') -" \
    "Existing storage container name not found" ;

  echo "# $(date '+%Y-%m-%d %H:%M:%S %:z') -" \
    "Creating storage container" ;
  
  az storage container create \
    --name $storage_account_container_name \
    --account-name $storage_account_name \
    --account-key $storage_account_key ;

fi ;

Top


PowerShell

In this walkthrough section, I will demonstrate an example on how you can use PowerShell Az module to deploy a centralized secure storage to store all your Terraform state.

Firstly, I will start by validating all existing resource group names before creating a new resource group in Azure using PowerShell.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
$ResourceGroupName = 'rg-terraform' ;
$ResourceLocation = 'southeastasia' ;
$StorageAccountName = 'storageterraformstate' ;
$StorageAccountContainerName = 'tfstate' ;

Write-Host $("{0}{1}" `
  -f "# $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss zzz') - ", `
    "Validating resource group") ;

if( `
  $(Get-AzResourceGroup `
  -Name $ResourceGroupName).ProvisioningState -eq 'Succeeded' ) `
{ `

  Write-Host $("{0}{1}" `
    -f "# $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss zzz') - ", `
      "Existing resource group name found") ;

  Get-AzResourceGroup `
    -Name $ResourceGroupName ;
  
} `
else `
{ `

  Write-Host $("{0}{1}" `
    -f "# $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss zzz') - ", `
      "Existing resource group name not found") ;
  
  Write-Host $("{0}{1}" `
    -f "# $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss zzz') - ", `
      "Creating resource group") ;

  New-AzResourceGroup `
    -Name $ResourceGroupName `
    -Location $ResourceLocation ;

} ;

Next, I will validate all existing storage account names before creating a new storage account in Azure.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
Write-Host $("{0}{1}" `
  -f "# $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss zzz') - ", `
    "Validating storage account") ;

if( `
  $(Get-AzStorageAccount `
    -Name $StorageAccountName `
    -ResourceGroupName $ResourceGroupName).ProvisioningState -eq 'Succeeded' ) `
{ `
  
  Write-Host $("{0}{1}" `
    -f "# $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss zzz') - ", `
      "Existing storage account name found") ;
  
  Get-AzStorageAccount `
    -Name $StorageAccountName `
    -ResourceGroupName $ResourceGroupName ;

} `
else `
{ `

  Write-Host $("{0}{1}" `
    -f "# $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss zzz') - ", `
      "Existing storage account name not found") ;

  Write-Host $("{0}{1}" `
    -f "# $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss zzz') - ", `
      "Creating storage account") ;
  
  New-AzStorageAccount `
    -Name $StorageAccountName `
    -ResourceGroupName $ResourceGroupName `
    -Location $ResourceLocation `
    -Kind 'StorageV2' `
    -SkuName 'Standard_LRS' ;

} ;

Since the storage account is created or exist in Azure, I will obtain the storage account key. The storage account key will be used by Terraform later.

1
2
3
4
# Store Azure storage account key as variable
$StorageAccountKey = (Get-AzStorageAccountKey `
  -Name $StorageAccountName `
  -ResourceGroupName $ResourceGroupName).Value[0] ;

Finally, I will need to validate the existing blob container names in the storage account and create a new blob container is it does not existing in the storage account in Azure. The blob container will be used to contain the Terraform *.tfstate state files.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
Write-Host $("{0}{1}" `
  -f "# $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss zzz') - ", `
    "Validating storage container") ;

if( `
  $(Get-AzStorageContainer `
    -Name $StorageAccountContainerName `
    -Context $(Get-AzStorageAccount `
      -Name $StorageAccountName `
      -ResourceGroupName $ResourceGroupName).Context).Name -eq $StorageAccountContainerName ) `
{ `

  Write-Host $("{0}{1}" `
    -f "# $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss zzz') - ", `
      "Existing storage container name found") ;
  
  Get-AzStorageContainer `
    -Name $StorageAccountContainerName `
    -Context $(Get-AzStorageAccount `
      -Name $StorageAccountName `
      -ResourceGroupName $ResourceGroupName).Context ;
  
} `
else `
{ `

  Write-Host $("{0}{1}" `
    -f "# $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss zzz') - ", `
      "Existing storage container name not found") ;

  Write-Host $("{0}{1}" `
    -f "# $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss zzz') - ", `
      "Creating storage container") ;
  
  New-AzStorageContainer `
    -Name $StorageAccountContainerName `
    -Context $(Get-AzStorageAccount `
      -Name $StorageAccountName `
      -ResourceGroupName $ResourceGroupName).Context ;

} ;

Top


Example - Creating resource group using Terraform with centralized secure storage

Assuming that you already have terraform in your environment, let us begin creating a resource group using terraform as an example with the Terraform *.tfstate state file stored in the centralized secure storage in Azure instead of your local working directory.

If you do not have it, you can refer to my previous blog post on how to obtain it.

Note:

Depending on whether if you are using AzCLI or PowerShell, you will need to authenticate to Azure.

1
2
# Login to Azure using AzCLI
az login

or

1
2
# Login to Azure using PowerShell
Connect-AzAccount

Once you have authenticated, proceed with the steps below with your preferred console or terminal.

1
2
# Create a folder name for the terraform example
mkdir -p ~/Terraform/example2

Note:

Depending on which console or terminal that you are using, you may need to replace the \ line break for Shell with ` line break for PowerShell.

Firstly, define the provider in the main.tf file.

1
2
3
4
5
6
7
8
9
echo \
'# Configure the Azure provider
provider "azurerm" {
  version = "~>1.5"
}

terraform {
  backend "azurerm" {}
}' > ~/Terraform/example2/main.tf

Secondly, define the resource in the resource.tf file.

1
2
3
4
5
6
7
8
9
10
echo \
'# Create a new resource group
resource "azurerm_resource_group" "rg" {
  name     = "terraform-resource-group"
  location = "southeastasia"
  tags      = {
    Environment    = "Development"
    DeploymentType = "Terraform"
  }
}' > ~/Terraform/example2/resource.tf

Once you have defined the configuration in main.tf and resource.tf files, execute teeraform with init argument to initialize the working directory.

1
2
3
4
5
6
7
# Initialize a working directory containing Terraform configuration files
terraform init \
  -backend-config="storage_account_name=$StorageAccountName" \
  -backend-config="container_name=$StorageAccountContainerName" \
  -backend-config="access_key=$StorageAccountKey" \
  -backend-config="key=azure.terraform.tfstate" \
  ~/Terraform/example2

Next, use the plan argument with the -out parameter to generate the execution plan file from the working directory.

1
2
3
4
# Create an execution plan
terraform plan \
  -out ~/Terraform/example2/out.plan \
  ~/Terraform/example2

Finally, use the apply argument with the execution plan file to apply changes in Azure.

1
2
3
4
# Apply the changes
terraform apply \
  -state ~/Terraform/example2/terraform.tfstate \
  ~/Terraform/example2/out.plan

Now, go to your Azure and check if the resource group has been created. You can also find a Terraform *.tfstate file created in the storage account blob container as the centralized secure storage location.


Top


References


Top



Top