hashicorp / terraform-provider-azurerm

Terraform provider for Azure Resource Manager
https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs
Mozilla Public License 2.0
4.6k stars 4.64k forks source link

azurerm_postgresql_flexible_server import forces recreate #15586

Open maikschwan opened 2 years ago

maikschwan commented 2 years ago

Community Note

Terraform (and AzureRM Provider) Version

terraform: v1.1.6 azurerm: 2.97.0

Affected Resource(s)

Terraform Configuration Files

resource "azurerm_postgresql_flexible_server" "server" {
  name                = var.server
  location            = azurerm_resource_group.resource_group.location
  resource_group_name = azurerm_resource_group.resource_group.name
  version             = var.postgresql_version

  administrator_login    = "postgres"
  administrator_password = random_password.password.result

  create_mode = "Default"

  storage_mb            = var.storage_mb
  backup_retention_days = 35

  zone = var.use_availability_zone ? 1 : null

  dynamic "high_availability" {
    for_each = var.ha_mode ? [var.server] : []
    content {
      mode                      = "ZoneRedundant"
      standby_availability_zone = 2
    }
  }

  sku_name = var.managed_dbms_sku

  maintenance_window {
    day_of_week  = var.maintenance_window_day
    start_hour   = var.maintenance_window_hour_utc
    start_minute = 0
  }

  tags = var.tags
}

terraform import 'azurerm_postgresql_flexible_server.server' '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/mygroup1/providers/Microsoft.DBforPostgreSQL/flexibleServers/server'

Expected Behaviour

terraform plan results in a no change plan.

Actual Behaviour

Terraform needs to recreate the server, because during the import the create_mode was imported as null not Default.

# azurerm_postgresql_flexible_server.server must be replaced
-/+resource "azurerm_postgresql_flexible_server" "server" {
       administrator_password        = (sensitive value)
       cmk_enabled                   = (known after apply)
       create_mode                   = "Default" # forces replacement
     ~ fqdn                          = "server.postgres.database.azure.com" -> (known after apply)
     ~ id                            = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/server-resource-group/providers/Microsoft.DBforPostgreSQL/flexibleServers/server" -> (known after apply)
       name                          = "server"
       private_dns_zone_id           = (known after apply)
     ~ public_network_access_enabled = true -> (known after apply)
        # (8 unchanged attributes hidden)
     - timeouts {}
        # (1 unchanged block hidden)
    }

Steps to Reproduce

  1. terraform apply
  2. terraform state rm azurerm_postgresql_flexible_server.server
  3. terraform import 'azurerm_postgresql_flexible_server.server' '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/mygroup1/providers/Microsoft.DBforPostgreSQL/flexibleServers/server'
  4. terraform plan
lonegunmanb commented 2 years ago

Hi @maikschwan , I think this issue was caused by both provider's code and service side. In postgresql_flexible_server_resource.go's resourcePostgresqlFlexibleSererRead function, the code missed setting create_mode. On the other hand, after we send the following request to API:

{
    "location": "westeurope",
    "properties": {
        "administratorLogin": "postgres",
        "administratorLoginPassword": "#############",
        "availabilityZone": "1",
        "backup": {
            "backupRetentionDays": 35,
            "geoRedundantBackup": "Disabled"
        },
        "createMode": "Default",
        "highAvailability": {
            "mode": "Disabled"
        },
        "network": {},
        "storage": {
            "storageSizeGB": 32
        },
        "version": "12"
    },
    "sku": {
        "name": "Standard_D4s_v3",
        "tier": "GeneralPurpose"
    },
    "tags": {}
}

When we try to import it the API response is:

{
    "sku": {
        "name": "Standard_D4s_v3",
        "tier": "GeneralPurpose"
    },
    "properties": {
        "fullyQualifiedDomainName": "zjhe-f15586.postgres.database.azure.com",
        "version": "12",
        "minorVersion": "8",
        "administratorLogin": "postgres",
        "state": "Ready",
        "availabilityZone": "1",
        "storage": {
            "storageSizeGB": 32
        },
        "backup": {
            "backupRetentionDays": 35,
            "geoRedundantBackup": "Disabled",
            "earliestRestoreDate": "2022-02-28T03:05:39.4892152+00:00"
        },
        "network": {
            "publicNetworkAccess": "Enabled"
        },
        "highAvailability": {
            "mode": "Disabled",
            "state": "NotEnabled"
        },
        "maintenanceWindow": {
            "customWindow": "Disabled",
            "dayOfWeek": 0,
            "startHour": 0,
            "startMinute": 0
        }
    },
    "location": "West Europe",
    "tags": {},
    "id": "/subscriptions/####################/resourceGroups/zjhe-f15586/providers/Microsoft.DBforPostgreSQL/flexibleServers/zjhe-f15586",
    "name": "zjhe-f15586",
    "type": "Microsoft.DBforPostgreSQL/flexibleServers"
}

The createMode in the request was missed in the response.

I've opened a ticket for this issue, when the service side has fix it, I'll submit a simple pr to fix the provider's code.

lonegunmanb commented 2 years ago

Hi @maikschwan sorry for the late reply, the service team response is the lack of createMode in the response is by design. I'm still communicating with them but no progress so far. Meanwhile, I think we can provide a workaround:

provider "azurerm" {
  features {}
}

resource "azurerm_resource_group" "example" {
  name     = "zjhe-f15586"
  location = "West Europe"
}

resource "azurerm_virtual_network" "example" {
  name                = "zjhe-f15586"
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name
  address_space       = ["10.0.0.0/16"]
}

resource "azurerm_subnet" "example" {
  name                 = "zjhe-f15586"
  resource_group_name  = azurerm_resource_group.example.name
  virtual_network_name = azurerm_virtual_network.example.name
  address_prefixes     = ["10.0.2.0/24"]
  service_endpoints    = ["Microsoft.Storage"]
  delegation {
    name = "fs"
    service_delegation {
      name    = "Microsoft.DBforPostgreSQL/flexibleServers"
      actions = [
        "Microsoft.Network/virtualNetworks/subnets/join/action",
      ]
    }
  }
}
resource "azurerm_private_dns_zone" "example" {
  name                = "example.postgres.database.azure.com"
  resource_group_name = azurerm_resource_group.example.name
}

resource "azurerm_private_dns_zone_virtual_network_link" "example" {
  name                  = "exampleVnetZone.com"
  private_dns_zone_name = azurerm_private_dns_zone.example.name
  virtual_network_id    = azurerm_virtual_network.example.id
  resource_group_name   = azurerm_resource_group.example.name
}

resource "random_password" "password" {
  length = 16
}

locals {
  create_mode = "Default"
}

resource "null_resource" "create_mode_trigger" {
  triggers = {
    on_change = local.create_mode
  }
}

resource "azurerm_postgresql_flexible_server" "server" {
  name                = "zjhe-f15586"
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name
  version             = "12"

  administrator_login    = "postgres"
  administrator_password = random_password.password.result

  create_mode = local.create_mode
  zone        = "1"

  storage_mb            = 32768
  backup_retention_days = 35
  sku_name              = "GP_Standard_D4s_v3"

  lifecycle {
    ignore_changes       = [create_mode]
    replace_triggered_by = [null_resource.create_mode_trigger.id]
  }
}

We can ignore create_mode and track create_mode's value via a null_resource and use replace_triggered_by to trigger a replacement when the create_mode's value has been changed.