Kevin Smith
Kevin Smith

Reputation: 14436

Terraform Invalid count argument that depends on another resource

I'm getting the following error when trying to do a plan or an apply on a terraform script.

Error: Invalid count argument

  on main.tf line 157, in resource "azurerm_sql_firewall_rule" "sqldatabase_onetimeaccess_firewall_rule":
 157:   count               = length(split(",", azurerm_app_service.app_service.possible_outbound_ip_addresses))

The "count" value depends on resource attributes that cannot be determined
until apply, so Terraform cannot predict how many instances will be created.
To work around this, use the -target argument to first apply only the
resources that the count depends on.

I understand this is falling over because it doesn't know the count for the number of firewall rules to create until the app_service is created. I can just run the apply with an argument of -target=azurerm_app_service.app_service then run another apply after the app_service is created.

However, this isn't great for our CI process, if we want to create a whole new environment from our terraform scripts we'd like to just tell terraform to just go build it without having to tell it each target to build in order.

Is there a way in terraform to just say go build everything that is needed in order without having to add targets?

Also below is an example terraform script that gives the above error:

provider "azurerm" {
  version = "=1.38.0"
}

resource "azurerm_resource_group" "resourcegroup" {
  name     = "rg-stackoverflow60187000"
  location = "West Europe"
}

resource "azurerm_app_service_plan" "service_plan" {
  name                = "plan-stackoverflow60187000"
  resource_group_name = azurerm_resource_group.resourcegroup.name
  location            = azurerm_resource_group.resourcegroup.location
  kind                = "Linux"
  reserved            = true

  sku {
    tier = "Standard"
    size = "S1"
  }
}

resource "azurerm_app_service" "app_service" {
  name                = "app-stackoverflow60187000"
  resource_group_name = azurerm_resource_group.resourcegroup.name
  location            = azurerm_resource_group.resourcegroup.location
  app_service_plan_id = azurerm_app_service_plan.service_plan.id

  site_config {
    always_on        = true
    app_command_line = ""
    linux_fx_version = "DOCKER|nginxdemos/hello"
  }

  app_settings = {
    "WEBSITES_ENABLE_APP_SERVICE_STORAGE" = "false"
  }
}

resource "azurerm_sql_server" "sql_server" {
  name                         = "mysqlserver-stackoverflow60187000"
  resource_group_name = azurerm_resource_group.resourcegroup.name
  location            = azurerm_resource_group.resourcegroup.location
  version                      = "12.0"
  administrator_login          = "4dm1n157r470r"
  administrator_login_password = "4-v3ry-53cr37-p455w0rd"
}

resource "azurerm_sql_database" "sqldatabase" {
  name                             = "sqldatabase-stackoverflow60187000"
  resource_group_name              =  azurerm_sql_server.sql_server.resource_group_name
  location                         =  azurerm_sql_server.sql_server.location
  server_name                      =  azurerm_sql_server.sql_server.name
  edition                          = "Standard"
  requested_service_objective_name = "S1"
}

resource "azurerm_sql_firewall_rule" "sqldatabase_firewall_rule" {
  name                = "App Service Access (${count.index})"
  resource_group_name = azurerm_sql_database.sqldatabase.resource_group_name
  server_name         = azurerm_sql_database.sqldatabase.name
  start_ip_address    = element(split(",", azurerm_app_service.app_service.possible_outbound_ip_addresses), count.index)
  end_ip_address      = element(split(",", azurerm_app_service.app_service.possible_outbound_ip_addresses), count.index)
  count               = length(split(",", azurerm_app_service.app_service.possible_outbound_ip_addresses))
}

Upvotes: 4

Views: 7662

Answers (1)

Martin Atkins
Martin Atkins

Reputation: 74109

To make this work without the -target workaround described in the error message requires reframing the problem in terms of values that Terraform can know only from the configuration, rather than values that are generated by the providers at apply time.

The trick then would be to figure out what values in your configuration the Azure API is using to decide how many IP addresses to return, and to rely on those instead. I don't know Azure well enough to give you a specific answer, but I see on Inbound/Outbound IP addresses that this seems to be an operational detail of Azure App Services rather than something you can control yourself, and so unfortunately this problem may not be solvable.

If there really is no way to predict from configuration how many addresses will be in possible_outbound_ip_addresses, the alternative is to split your configuration into two parts where one depends on the other. The first would configure your App Service and anything else that makes sense to manage along with it, and then the second might use the azurerm_app_service data source to retrieve the data about the assumed-already-existing app service and make firewall rules based on it.

Either way you'll need to run Terraform twice to make the necessary data available. An advantage of using -target is that you only need to do a funny workflow once during initial bootstrapping, and so you could potentially do the initial create outside of CI to get the objects initially created and then use CI for ongoing changes. As long as the app service object is never replaced, subsequent Terraform plans will already know how many IP addresses are set and so should be able to complete as normal.

Upvotes: 5

Related Questions