How to output the hostnames of app services created with for_each in terraform?

I have the following terraform module:

provider "azurerm" {
}

variable "env" {
    type = string
    description = "The SDLC environment (qa, dev, prod, etc...)"
}

variable "appsvc_names" {
    type = list(string)
    description = "The names of the app services to create under the same app service plan"
}

locals {
    location = "eastus2"
    resource_group_name = "app505-dfpg-${var.env}-web-${local.location}"
}

resource "azurerm_app_service_plan" "asp" {
    name                = "${local.resource_group_name}-asp"
    location            = local.location
    resource_group_name = local.resource_group_name
    kind                = "Linux"
    reserved            = true

    sku {
        tier = "Basic"
        size = "B1"
    }
}

resource "azurerm_app_service" "appsvc" {
    for_each            = toset(var.appsvc_names)

    name                = "${local.resource_group_name}-${each.value}-appsvc"
    location            = local.location
    resource_group_name = local.resource_group_name
    app_service_plan_id = azurerm_app_service_plan.asp.id
}

# output "hostnames" {
#     value       = azurerm_app_service.appsvc[*].default_site_hostname
#     description = "The hostnames of the created app services"
# }

It works, but I want to output the hostnames. Preferrably as a map, but for now just a list could be fine too.

When I uncomment the output statement and run terraform apply I get this:

Error: Unsupported attribute

  on ..\..\modules\web\main.tf line 42, in output "hostnames":
  42:     value       = azurerm_app_service.appsvc[*].default_site_hostname

This object does not have an attribute named "default_site_hostname".

So how do I output the list (or better the map) of hostnames of the new app services?

(The question is also posted here - https://stackoverflow.com/questions/59906907/how-to-output-the-hostnames-of-app-services-created-with-for-each-in-terraform)

Looks like the situation described here https://github.com/hashicorp/terraform/issues/22476

Good answer from the team in the comment dated September 18th:

Just wanted to clarify that it is by design that the splat operators don’t work with maps. Splat operators are for lists only, as described in the documentation. Note in particular the paragraph describing their other “useful effect”:

Splat expressions also have another useful effect: if they are applied to a value that is not a list or tuple then the value is automatically wrapped in a single-element list before processing. That is, var.single_object[*].id is equivalent to [var.single_object][*].id , or effectively [var.single_object.id] . This behavior is not interesting in most cases, but it is particularly useful when referring to resources that may or may not have count set, and thus may or may not produce a tuple value.

So, the reason applying [*] to a map doesn’t work is that it causes the map to be wrapped in a single-element list, per the rule above. This is the 0.12 equivalent of the Terraform 0.11 behavior where you could apply .* to a resource that doesn’t have count set at all and get a single-element list. It’s not possible to change that behavior because existing configurations are relying on it.

Aside from that special behavior (which isn’t useful when using for_each ), a splat expression is just a shorthand for a for expression that applies only to lists. When using for_each we must use a full for expression:

value = { for k, v in aws_eip.this : k => v.id }

or, if you want the result to be a list (in lexical order by the keys, discarding the keys themselves):

value = [for v in aws_eip.this : v.id]

or, if you want the result to be a set of strings, reflecting that the ids are not ordered and more convenient to use with for_each elsewhere in the configuration:

value = toset([for v in aws_eip.this : v.id])

As mentioned above, we cannot and do not intend to change this behavior to make splat expressions work with maps because that is incompatible with existing configurations. Based on the comments so far it’s not clear to us whether everyone is just reporting that splat expressions don’t work with for_each (which is by design, not a bug) or if some of you are reporting a bug. For that reason, we’re going to leave this open for now in case someone wants to add a bug report about Terraform behaving inconsistently to what we’ve described above in this comment.

So that’s why the splat doesn’t work, and how to construct a map containing the appsvc_names as key and the default_site_hostname as value.

But bear in mind that you don’t actually need to construct that map as you can always find the hostname by looking at azurerm_app_service.appsvc[each.key].default_site_hostname for any given appsvc, if you’re constructing other resources with a for_each = toset(var.appsvc_names) loop.

for_each is really a game-changer, allowing us to step up from lists to maps in many situations and make our TF configurations much more robust for day-two operations.