Azure / terraform-provider-azapi

Terraform provider for Azure Resource Manager Rest API
https://registry.terraform.io/providers/Azure/azapi/latest
Mozilla Public License 2.0
193 stars 49 forks source link

Feature request: support provider functions #533

Closed ms-henglu closed 2 months ago

ms-henglu commented 4 months ago

To support functions like

  1. parse azure resource id
  2. build azure resource id

refs: https://developer.hashicorp.com/terraform/plugin/framework/functions/concepts

stemaMSFT commented 4 months ago

Other considerations here @ms-henglu can we do them?

  1. location listing
  2. flatten map function to simplify for each expressions General guidance by Terraform is to avoid anything that requires calls to the provider itself, which we should keep in mind. @matt-FFFFFF for FYI
matt-FFFFFF commented 4 months ago

Locations could work like this:

https://registry.terraform.io/modules/Azure/regions/azurerm/latest

Merging date from locations API and compute provider to determine if a location has zones support.

We should cache the API response in the provider to adhere to HashiCorp's recommendation. This means we get consistent results. Plus the data won't change that frequently.

hqhqhqhqhqhqhqhqhqhqhq commented 4 months ago

@stemaMSFT @grayzu

Hello! Looking for any suggestions on the function to build Azure resource IDs. I think there are two options:

  1. Allow users to provide parentID, resource type, and name to construct the resource ID, as described in this guide. Pros: quite straightforward & less parameters needed Cons: users may not know what the parent id is?

  2. Allow users to provide resource type, name, and other specifics like subscription ID and resource group name etc..., and use those to construct the parentID internally, which is then used to build the full resource ID. Pros: referring to more specific names that user knows about Cons: too many parameters & a lot of cases to think about in building the parent id internally

stemaMSFT commented 4 months ago

@hqhqhqhqhqhqhqhqhqhqhq I think it would make sense to do option 1, since that would be consistent with the behavior of AzAPI (as parent_id is already a parameter in resource definitions). It keeps consistent with Azure as well, which is what we're aiming for philosophically with AzAPI.

matt-FFFFFF commented 4 months ago

I think we could have a few functions.

The option 1 as mentioned above plus dedicated functions for the four major scopes in azure:

Tenant - just need resource type and name

Management group - need MG id plus resource type and name

Subscription - need sub id, resource type and name

Resource group - sub id, RG name, resource type and name

For more complex scenarios we can use the parent id option and maybe nest the functions?

stemaMSFT commented 3 months ago

@matt-FFFFFF would you mind elaborating on why you prefer four separate functions compared to one function to construct resource IDs generically? Maybe with some scenarios. Would help us understand which direction to take.

matt-FFFFFF commented 3 months ago

@matt-FFFFFF would you mind elaborating on why you prefer four separate functions compared to one function to construct resource IDs generically? Maybe with some scenarios. Would help us understand which direction to take.

@stemaMSFT

Thinking more I think this could be one function, with a variadic input.

E.g.

provider::azapi::resource_id("resource_group", "<subscription id>", "<resource group name>", "<resource type>", "<resource name>")

provider::azapi::resource_id("management_group", "<management group id>", "<resource type>", "<resource name>")

provider::azapi::resource_id("tenant", "<resource type>", "<resource name>")

provider::azapi::resource_id("parent", "<parent resource id>" , "<resource type>", "<resource name>")

And so on. As long as each mode was identified by the first input I think that would work.

stemaMSFT commented 3 months ago

Makes sense to me. @ms-henglu if you don't have any other concerns, I think we can move forward with this approach.

ms-henglu commented 3 months ago

Hi @matt-FFFFFF ,

Thanks for the suggestions! But unfortunately variadic function is supported by the Terraform provider function.

How about the below design proposed by @hqhqhqhqhqhqhqhqhqhqhq https://github.com/Azure/terraform-provider-azapi/pull/553#issue-2408065215

hqhqhqhqhqhqhqhqhqhqhq commented 3 months ago

@matt-FFFFFF @stemaMSFT Adding to above, I think the difference lies where the scope is defined. We can:

  1. Put it a part of the function e.g. build_management_group_scope_resource_id

pros:

cons:

  1. Use a generic function, and let user give the scope type, e.g. provider::azapi::resource_id("management_group", "", "", "")

pros:

cons:

ms-henglu commented 3 months ago

I just noticed that the bicep/ARM templates have similar functions: https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/bicep-functions-resource#subscriptionresourceid

So I'm proposing the following functions which are similar with the bicep's.

The # 1 function is just the provider function implementation of the azapi_resource_id data source. And the rest of them allow user to build the resource ID with only the "name" part, for example, the management group name, resource group name and each resource name. Users don't need to worry about the ID format.

And to support nested resource under different deployed scope, the last argument of these functions is a list of string. More details please see the below examples:

  1. resourceId(resourceType, parentId, name)

    Example:

resourceId("Microsoft.Network/virtualNetworks", "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg1", "vnet1")
= "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet1"
  1. tenantResourceId(resourceType, [resourceName1, resourceName2, ...])

    Example:

tenantResourceId("Microsoft.Billing/billingAccounts/billingProfiles", ["ba1", "bp1"])
= "/providers/Microsoft.Billing/billingAccounts/ba1/billingProfiles/bp1"
  1. subscriptionResourceId(subscriptionId, resourceType, [resourceName1, resourceName2, ...])

    Example:

subscriptionResourceId("00000000-0000-0000-0000-000000000000", "Microsoft.Resources/resourceGroups", ["rg1"])
= "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg1"
  1. managementGroupResourceId(managementGroupName, resourceType, [resourceName1, resourceName2, ...])

    Example:

managementGroupResourceId("mg1", "Microsoft.Billing/billingAccounts/billingProfiles", ["ba1", "bp1"])
= "/providers/Microsoft.Management/managementGroups/mg1/providers/Microsoft.Billing/billingAccounts/ba1/billingProfiles/bp1"
  1. extensionResourceId(resourceId, extensionName, [resourceName1, resourceName2, ...])

    Example:

extensionResourceId("/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet1", "Microsoft.Authorization/locks", ["mylock"])
= "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet1/providers/Microsoft.Authorization/locks/mylock"
  1. resourceGroupResourceId(subscriptionId, resourceGroupName, resourceType, [resourceName1, resourceName2, ...])

    Example:

resourceGroupResourceId("00000000-0000-0000-0000-000000000000", "rg1", "Microsoft.Network/virtualNetworks/subnets", ["vnet1", "subnet1"])
= "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet1/subnets/subnet1"
ms-henglu commented 2 months ago

Completed via https://github.com/Azure/terraform-provider-azapi/pull/553