Terraform State Storage + Principle account

Written by

State Storage

Installing Terraform

Creating a new folder for this mini project

Initialize Terraform

terraform init

Create a main,tf file containing definition of storage account with storage account resource group

provider "azurerm" {
  subscription_id = "<subscription_id>"
  features {}
}


resource "azurerm_resource_group" "rg" {
  name     = "rg-terraform-state"
  location = "East US"
}

resource "azurerm_storage_account" "storage_account" {
  name                     = "tfstatestorageup"
  resource_group_name       = azurerm_resource_group.rg.name
  location                 = azurerm_resource_group.rg.location
  account_tier              = "Standard"
  account_replication_type = "LRS"

  tags = {
    environment = "Terraform State"
  }
}

resource "azurerm_storage_container" "storage_container" {
  name                  = "tfstate"
  storage_account_name  = azurerm_storage_account.storage_account.name
  container_access_type = "private"
}

Create a sub folder for a new terraform module being whose the state will be stored to the storage account previously created.

Create a main.tf

# Resource group
provider "azurerm" {
  subscription_id = "<subscription_id>"
  features {}
}

resource "azurerm_resource_group" "example" {
  name     = "example-rg-up"
  location = "West Europe"
}

Create backend.tf (this will allow us to create update the state file remotely instead of locally)

terraform {
  backend "azurerm" {
    resource_group_name   = "rg-terraform-state"
    storage_account_name  = "tfstatestorageup"
    container_name        = "tfstate"
    key                   = "terraform.tfstate"
  }
}

Let’s look at the content of the state file in azure, in the storage account

https://portal.azure.com/#browse/Microsoft.Storage%2FStorageAccounts

Now, let’s make a add a new component in main.tf to the module service_plan

resource "azurerm_service_plan" "example" {
  name                = "example"
  resource_group_name = azurerm_resource_group.example.name
  location            = azurerm_resource_group.example.location
  os_type             = "Linux"
  sku_name            = "P1v2"
}

And let’s now update the changes to the remote

terraform plan
terraform apply

Now, let’s check the state file in the storage account

At some point if we decide to delete manually one component, for e.g the service plan

we can use terraform refresh to adapt the state based on what is currently there

The `terraform refresh` command reads the current settings from all managed remote objects and updates the Terraform state to match.

https://stackoverflow.com/questions/42628660/what-does-terraform-refresh-really-do

Before refresh

{
  "version": 4,
  "terraform_version": "1.5.7",
  "serial": 3,
  "lineage": "2797f07b-bcf9-0097-2b05-8d1abd328b89",
  "outputs": {},
  "resources": [
    {
      "mode": "managed",
      "type": "azurerm_resource_group",
      "name": "example",
      "provider": "provider[\"registry.terraform.io/hashicorp/azurerm\"]",
      "instances": [
        {
          "schema_version": 0,
          "attributes": {
            "id": "/subscriptions/<subscription_id>/resourceGroups/example-rg-up2",
            "location": "westeurope",
            "managed_by": "",
            "name": "example-rg-up2",
            "tags": null,
            "timeouts": null
          },
          "sensitive_attributes": [],
          "private": ""
        }
      ]
    },
    {
      "mode": "managed",
      "type": "azurerm_service_plan",
      "name": "example",
      "provider": "provider[\"registry.terraform.io/hashicorp/azurerm\"]",
      "instances": [
        {
          "schema_version": 1,
          "attributes": {
            "app_service_environment_id": "",
            "id": "/subscriptions/<subscription_id>/resourceGroups/example-rg-up2/providers/Microsoft.Web/serverFarms/example",
            "kind": "linux",
            "location": "westeurope",
            "maximum_elastic_worker_count": 1,
            "name": "example",
            "os_type": "Linux",
            "per_site_scaling_enabled": false,
            "premium_plan_auto_scale_enabled": false,
            "reserved": true,
            "resource_group_name": "example-rg-up2",
            "sku_name": "P1v2",
            "tags": null,
            "timeouts": null,
            "worker_count": 1,
            "zone_balancing_enabled": false
          },
          "sensitive_attributes": [],
          "private": "",
          "dependencies": [
            "azurerm_resource_group.example"
          ]
        }
      ]
    }
  ],
  "check_results": null
}

After refresh

{
  "version": 4,
  "terraform_version": "1.5.7",
  "serial": 4,
  "lineage": "",
  "outputs": {},
  "resources": [
    {
      "mode": "managed",
      "type": "azurerm_resource_group",
      "name": "example",
      "provider": "provider[\"registry.terraform.io/hashicorp/azurerm\"]",
      "instances": [
        {
          "schema_version": 0,
          "attributes": {
            "id": "/subscriptions/<subscription_id>/resourceGroups/example-rg-up2",
            "location": "westeurope",
            "managed_by": "",
            "name": "example-rg-up2",
            "tags": {},
            "timeouts": null
          },
          "sensitive_attributes": [],
          "private": ""
        }
      ]
    }
  ],
  "check_results": null
}

Principle account

https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/service_principal_client_secret

We recommend using either a Service Principal or Managed Service Identity when running Terraform non-interactively (such as when running Terraform in a CI server) – and authenticating using the Azure CLI when running Terraform locally.

Using the cli

az login
az account list
az account set --subscription="20000000-0000-0000-0000-000000000000"
az ad sp create-for-rbac --role="Contributor" --scopes="/subscriptions/20000000-0000-0000-0000-000000000000"
{
  "appId": "00000000-0000-0000-0000-000000000000",
  "displayName": "azure-cli-2017-06-05-10-41-15",
  "name": "http://azure-cli-2017-06-05-10-41-15",
  "password": "0000-0000-0000-0000-000000000000",
  "tenant": "00000000-0000-0000-0000-000000000000"
}

These values map to the Terraform variables like so:

  • appId is the client_id defined above.
  • password is the client_secret defined above.
  • tenant is the tenant_id defined above.

Test out if the values are well working by using the azure cli

az login --service-principal -u CLIENT_ID -p CLIENT_SECRET --tenant TENANT_ID
{
  "appId": "client_id", # client id
  "displayName": "azure-cli-2025-02-27-09-50-08",
  "password": "<client_secret>", # client secret
  "tenant": "tenant_id" # tenant id
}