These notes document my first steps in using TerraForm to manage Azure infrastructure. This first post is how I brought some existing resources under TerraForm’s management.

My initial project is to bring an existing CosmosDB into TerraForm (prior to provisioning a new VM alongside it).

I initially created a new directory and then a .tf file within it - having discovered that terraform init in an empty directory complains that there are no TerraForm configuration files.

terraform {
    required_providers {
        azurerm = {
            source = "hashicorp/azurerm"
            version = ">=2.77.0"  # 2.77.0 added 'EnableServerless' cosmosdb capability, which we use
        }
    }
}

# Using the Azure plugin
provider "azurerm" {
    features {}
}

variable "base_tags" {
    type = map(String)
    description = "Base set of tags for all resources"
    default = {
        "Application Name" = "My Application"
        "Application Owner" = "Support Team"
        "Business Sector" = "R&D"
        "Country" = "UK"
        "Data Classification" = "Confidential"
        "Environment" = "PROD/DEV"  # Mandatory tag per company policy - set to correct value by more specific variables
        "Region" = "EMEA"
        # ... etc.
    }
}

variable "tags_dev" {
    type = map(String)
    description = "Development resources tags"
    default = {Environment = "DEV"}
}

variable "tags_live" {
    type = map(String)
    description = "Live resources tags"
    default = {Environment = "LIVE"}
}

resource "azurerm_resource_group" "rg" {
    name = "RG-MY_RG-001"
    location = "West Europe"
    tags = merge(var.base_tags, var.tags_dev)
}

resource "azurerm_cosmosdb_account" "db" {
    name = "cdb-my_cosmosdb"
    location = azurerm_resource_group.rg.location
    resource_group_name = azurerm_resource_group.rg.name
    offer_type = "Standard"
    kind = "MongoDB"
    public_network_access_enabled = false

    consistency_policy {
        consistency_level = "Session"
        max_interval_in_seconds = 5
        max_staleness_prefix = 100
    }

    capabilities {
        name = "DisableRateLimitingResponses"
    }

    capabilities {
        name = "EnableMongo"
    }

    capabilities {
        name = "EnableServerless"
    }

    geo_location {
        location = azurerm_resource_group.rg.location
        failover_priority = 0
    }

    backup {
        type = "Periodic"
        interval_in_minutes = 240
        retention_in_hours = 8
    }

    tags = merge(
        var.base_tags,
        var.tags_dev,
        {
            defaultExperience = "Azure Cosmos DB for MongoDB API
        }
    )
}

resource "azurerm_cosmosdb_mongo_database" "mongo_db" {
    name = "my_mongo_db"
    resource_group_name = azurerm_resource_group.rg.name
    account_name = azurerm_cosmosdb_account.db.name
}

resource "azurerm_cosmosdb_mongo_collection" "mongo_collection" {
    name = "my_collection"
    resource_group_name = azurerm_resource_group.rg.name
    account_name = azurerm_cosmosdb_account.db.name
    database_name = azurerm_cosmosdb_mongo_database.mongo_db.name

    default_ttl_seconds = 0

    index {
        keys = [
            "$**",
        ]
        unique = false
    }

    index {
        keys = [
            "_id",
        ]
        unique = true
    }
}

resource "azurerm_private_endpoint" "cosmos_endpoint" {
    name = "EP-MY_EP-001"
    location = azurerm_resource_group.rg.location
    resource_group_name = azurerm_resource_group.rg.name
    subnet_id = "...."

    private_service_connection {
        is_manual_connection = false
        name = "${azurerm_cosmosdb_account.db.name}-connection"
        private_connection_resource_id = azurerm_cosmosdb_account.db.id
        subresource_names = [azurerm_cosmosdb_account.db.kind]
    }

    tags = merge(var.base_tags, var.tags_live)
}

(Once the rest of the networking is brought under TerraForm, the hardcoded subnet_id will be replaced with a reference to the subnet’s id property.)

To update the installed azurerm provider, I had to pass -upgrade to terraform init:

terraform init -upgrade

In order to let TerraForm know it does not need to create everything, I have to tell it about the already deployed infrastructure using terraform import, so for the existing resource group, Cosmos database and private endpoint:

terraform import azurerm_resource_group.rg /subscriptions/$sub_id/resourceGroups/RG-MY_RG-001
terraform import azurerm_cosmosdb_account.db /subscriptions/$sub_id/resourceGroups/RG-MY_RG-001/providers/Microsoft.DocumentDB/databaseAccounts/cdb-my_cosmosdb
terraform import azurerm_cosmosdb_mongo_database.mongo_db /subscriptions/$sub_id/resourceGroups/RG-MY_RG-001/providers/Microsoft.DocumentDB/databaseAccounts/cdb-my_cosmosdb/mongodbDatabases/my_mongo_db
terraform import azurerm_cosmosdb_mongo_collection.mongo_collection /subscriptions/$sub_id/resourceGroups/RG-MY_RG-001/providers/Microsoft.DocumentDB/databaseAccounts/cdb-my_cosmosdb/mongodbDatabases/my_mongo_db/collections/my_collection
terraform import azurerm_private_endpoint.cosmos_endpoint /subscriptions/$sub_id/resourceGroups/RG-MY_RG-001/providers/Microsoft.Network/privateEndpoints/EP-My_EP-001

To preview what TerraForm will do, use the command:

terraform plan

Once happy with the plan, make the changes with:

terraform apply