From 712f9ab0130fc3b7f722682ce9019ec3b675bf37 Mon Sep 17 00:00:00 2001 From: Heng Lu <79895375+ms-henglu@users.noreply.github.com> Date: Fri, 13 Sep 2024 10:25:23 +0800 Subject: [PATCH] bugfix: preflight request doesn not include identity (#613) * bugfix: preflight request doesn not include identity * add test * fix test --- docs/guides/2.0-upgrade-guide.md | 71 +++++++++++++++---- internal/services/azapi_resource.go | 2 +- .../services/azapi_resource_preflight_test.go | 46 ++++++++++++ internal/services/preflight/preflight.go | 16 ++++- templates/guides/2.0-upgrade-guide.md | 30 ++++---- 5 files changed, 131 insertions(+), 34 deletions(-) diff --git a/docs/guides/2.0-upgrade-guide.md b/docs/guides/2.0-upgrade-guide.md index e62b6785..d206ffe4 100644 --- a/docs/guides/2.0-upgrade-guide.md +++ b/docs/guides/2.0-upgrade-guide.md @@ -67,6 +67,7 @@ Below is an overview of the changes coming 2.0. Each topic is covered in more de * [Headers and Query Parameters Support](#headers-and-query-parameters-support) * [Replacement Triggers](#replacement-triggers) * [JMESPath Query Support](#jmespath-query-support) +* [Preflight Validation](#preflight-validation) * [Breaking Changes](#breaking-changes) @@ -211,14 +212,14 @@ The AzAPI Provider now supports customized retriable configuration for Azure API ```hcl data "azapi_resource" "test" { - type = "Microsoft.Resources/resourceGroups@2024-03-01" - name = "example" + type = "Microsoft.Resources/resourceGroups@2024-03-01" + name = "example" retry = { - error_message_regex = ["ResourceGroupNotFound"] - interval_seconds = 5 + error_message_regex = ["ResourceGroupNotFound"] + interval_seconds = 5 max_interval_seconds = 30 - multiplier = 1.5 + multiplier = 1.5 randomization_factor = 0.5 } } @@ -293,7 +294,7 @@ resource "azapi_resource" "example" { name = var.name type = "Microsoft.Network/publicIPAddresses@2023-11-01" parent_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/example" - body = { + body = { properties = { sku = var.sku zones = var.zones @@ -315,7 +316,7 @@ resource "azapi_resource" "example" { name = var.name type = "Microsoft.Network/publicIPAddresses@2023-11-01" parent_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/example" - body = { + body = { properties = { sku = var.sku zones = var.zones @@ -339,13 +340,13 @@ The `response_export_values` attribute accepts a map where the key is the name f ```hcl data "azapi_resource" "example" { - type = "Microsoft.ContainerRegistry/registries@2020-11-01-preview" - parent_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/example" - name = "example" - response_export_values = { - login_server = "properties.loginServer" - quarantine_status = "properties.policies.quarantinePolicy.status" - } + type = "Microsoft.ContainerRegistry/registries@2020-11-01-preview" + parent_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/example" + name = "example" + response_export_values = { + login_server = "properties.loginServer" + quarantine_status = "properties.policies.quarantinePolicy.status" + } } // it will output below object @@ -366,6 +367,46 @@ output "quarantine_status" { ``` +## Preflight Validation + +Preflight validation is a new feature in the AzAPI Provider that helps ensure your configuration will deploy successfully. To enable preflight validation, set the `enable_preflight` attribute to `true` in the provider block. + +In the example below, the provider block is configured to enable preflight validation. The `azapi_resource` block defines a virtual network resource with an invalid address prefix. When you run `terraform plan`, the preflight validation will fail and display an error message. + +```hcl +provider "azapi" { + enable_preflight = true +} + +resource "azapi_resource" "vnet" { + type = "Microsoft.Network/virtualNetworks@2024-01-01" + parent_id = azapi_resource.resourceGroup.id + name = "example-vnet" + location = "westus" + body = { + properties = { + addressSpace = { + addressPrefixes = [ + "10.0.0.0/160", + ] + } + } + } +} +``` + +When you run `terraform plan`, you will see the following error message: + +```json +{ + "code": "InvalidAddressPrefixFormat", + "target": "/subscriptions/....../resourceGroups/olgdwamq/providers/Microsoft.Network/virtualNetworks/example-vnet", + "message": "Address prefix 10.0.0.0/160 of resource /subscriptions/....../resourceGroups/olgdwamq/providers/Microsoft.Network/virtualNetworks/example-vnet is not formatted correctly. It should follow CIDR notation, for example 10.0.0.0/24.", + "details": [] +} +``` + + ## Breaking Changes - Provider field `default_naming_prefix` and `default_naming_suffix` are removed. @@ -453,4 +494,4 @@ output "quarantine_status" { - The `use_msi` field now defaults to `false`. How to fix: - Please set it to `true` explicitly if you want to authenticate using Managed Service Identity. \ No newline at end of file + Please set it to `true` explicitly if you want to authenticate using Managed Service Identity. diff --git a/internal/services/azapi_resource.go b/internal/services/azapi_resource.go index e7abe217..394a0293 100644 --- a/internal/services/azapi_resource.go +++ b/internal/services/azapi_resource.go @@ -561,7 +561,7 @@ func (r *AzapiResource) ModifyPlan(ctx context.Context, request resource.ModifyP name = preflight.NamePlaceholder() } - err = preflight.Validate(ctx, r.ProviderData.ResourceClient, plan.Type.ValueString(), parentId, name, plan.Location.ValueString(), plan.Body) + err = preflight.Validate(ctx, r.ProviderData.ResourceClient, plan.Type.ValueString(), parentId, name, plan.Location.ValueString(), plan.Body, plan.Identity) if err != nil { response.Diagnostics.AddError("Preflight Validation: Invalid configuration", err.Error()) return diff --git a/internal/services/azapi_resource_preflight_test.go b/internal/services/azapi_resource_preflight_test.go index 8f491fb4..503decb3 100644 --- a/internal/services/azapi_resource_preflight_test.go +++ b/internal/services/azapi_resource_preflight_test.go @@ -78,6 +78,18 @@ func TestAccGenericResource_preflightExtensionResourceValidation(t *testing.T) { }) } +func TestAccGenericResource_preflightWithIdentity(t *testing.T) { + data := acceptance.BuildTestData(t, "azapi_resource", "test") + r := GenericResource{} + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.preflightWithIdentity(data), + PlanOnly: true, + ExpectNonEmptyPlan: true, + }, + }) +} + func (r GenericResource) preflightMockPropertyValue(data acceptance.TestData) string { return fmt.Sprintf(` provider "azapi" { @@ -160,3 +172,37 @@ provider "azapi" { %s `, r.extensionScope(data)) } + +func (r GenericResource) preflightWithIdentity(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azapi" { + enable_preflight = true +} + +%[1]s + +resource "azapi_resource" "aksCluster" { + type = "Microsoft.ContainerService/managedClusters@2024-06-02-preview" + parent_id = azapi_resource.resourceGroup.id + name = azapi_resource.resourceGroup.id + location = "westus" + identity { + type = "SystemAssigned" + } + body = { + properties = { + agentPoolProfiles = [ + { + count = 1 + mode = "System" + name = "default" + vmSize = "Standard_DS2_v2" + }, + ] + dnsPrefix = "exampleaks" + } + } + schema_validation_enabled = false +} +`, r.template(data)) +} diff --git a/internal/services/preflight/preflight.go b/internal/services/preflight/preflight.go index fbf74af6..325cef07 100644 --- a/internal/services/preflight/preflight.go +++ b/internal/services/preflight/preflight.go @@ -8,6 +8,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" "github.com/Azure/terraform-provider-azapi/internal/azure" + "github.com/Azure/terraform-provider-azapi/internal/azure/identity" aztypes "github.com/Azure/terraform-provider-azapi/internal/azure/types" "github.com/Azure/terraform-provider-azapi/internal/clients" "github.com/Azure/terraform-provider-azapi/internal/services/dynamic" @@ -90,7 +91,7 @@ func IsSupported(resourceType string, parentId string) bool { } // Validate validates the resource using the preflight API -func Validate(ctx context.Context, client *clients.ResourceClient, resourceType string, parentId string, name string, location string, body types.Dynamic) error { +func Validate(ctx context.Context, client *clients.ResourceClient, resourceType string, parentId string, name string, location string, body types.Dynamic, identity types.List) error { azureResourceType, apiVersion, err := utils.GetAzureResourceTypeApiVersion(resourceType) if err != nil { return err @@ -104,7 +105,7 @@ func Validate(ctx context.Context, client *clients.ResourceClient, resourceType } resource := make(map[string]interface{}) - err = unmarshalPreflightBody(body, &resource) + err = unmarshalPreflightBody(body, identity, &resource) if err != nil { tflog.Warn(ctx, fmt.Sprintf("Skipping preflight validation for resource %s because the body is invalid: %v", resourceType, err)) return nil @@ -119,7 +120,7 @@ func Validate(ctx context.Context, client *clients.ResourceClient, resourceType return err } -func unmarshalPreflightBody(input types.Dynamic, out *map[string]interface{}) error { +func unmarshalPreflightBody(input types.Dynamic, identityList types.List, out *map[string]interface{}) error { if input.IsNull() || input.IsUnknown() || input.IsUnderlyingValueUnknown() { return fmt.Errorf("input is null or unknown") } @@ -150,6 +151,15 @@ func unmarshalPreflightBody(input types.Dynamic, out *map[string]interface{}) er return fmt.Errorf("unknown value found outside the properties bag") } } + + if (*out)["identity"] == nil && !identityList.IsNull() && !identityList.IsUnknown() { + identityModel := identity.FromList(identityList) + expandedIdentity, err := identity.ExpandIdentity(identityModel) + if err != nil { + return err + } + (*out)["identity"] = expandedIdentity + } return nil } diff --git a/templates/guides/2.0-upgrade-guide.md b/templates/guides/2.0-upgrade-guide.md index cdb7ae76..d206ffe4 100644 --- a/templates/guides/2.0-upgrade-guide.md +++ b/templates/guides/2.0-upgrade-guide.md @@ -212,14 +212,14 @@ The AzAPI Provider now supports customized retriable configuration for Azure API ```hcl data "azapi_resource" "test" { - type = "Microsoft.Resources/resourceGroups@2024-03-01" - name = "example" + type = "Microsoft.Resources/resourceGroups@2024-03-01" + name = "example" retry = { - error_message_regex = ["ResourceGroupNotFound"] - interval_seconds = 5 + error_message_regex = ["ResourceGroupNotFound"] + interval_seconds = 5 max_interval_seconds = 30 - multiplier = 1.5 + multiplier = 1.5 randomization_factor = 0.5 } } @@ -294,7 +294,7 @@ resource "azapi_resource" "example" { name = var.name type = "Microsoft.Network/publicIPAddresses@2023-11-01" parent_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/example" - body = { + body = { properties = { sku = var.sku zones = var.zones @@ -316,7 +316,7 @@ resource "azapi_resource" "example" { name = var.name type = "Microsoft.Network/publicIPAddresses@2023-11-01" parent_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/example" - body = { + body = { properties = { sku = var.sku zones = var.zones @@ -340,13 +340,13 @@ The `response_export_values` attribute accepts a map where the key is the name f ```hcl data "azapi_resource" "example" { - type = "Microsoft.ContainerRegistry/registries@2020-11-01-preview" - parent_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/example" - name = "example" - response_export_values = { - login_server = "properties.loginServer" - quarantine_status = "properties.policies.quarantinePolicy.status" - } + type = "Microsoft.ContainerRegistry/registries@2020-11-01-preview" + parent_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/example" + name = "example" + response_export_values = { + login_server = "properties.loginServer" + quarantine_status = "properties.policies.quarantinePolicy.status" + } } // it will output below object @@ -494,4 +494,4 @@ When you run `terraform plan`, you will see the following error message: - The `use_msi` field now defaults to `false`. How to fix: - Please set it to `true` explicitly if you want to authenticate using Managed Service Identity. \ No newline at end of file + Please set it to `true` explicitly if you want to authenticate using Managed Service Identity.