From 29900012eec8f75f5766c1297034e81019f5fad6 Mon Sep 17 00:00:00 2001 From: Xiaxin <92154856+xiaxyi@users.noreply.github.com> Date: Thu, 4 Apr 2024 13:49:54 +0800 Subject: [PATCH] `azurerm_app_service_certificate_order` - fix the incorrect ID issue (#25428) * fix the incorrect app service certificate order issue * remove unnecessary fields for state migration --- .../app_service_certificate_order_resource.go | 6 + .../app_service_certificate_order.go | 159 ++++++++++++++++++ .../services/web/parse/certificate_order.go | 2 +- .../web/parse/certificate_order_old.go | 72 ++++++++ .../web/parse/certificate_order_old_test.go | 115 +++++++++++++ .../web/parse/certificate_order_test.go | 10 +- internal/services/web/resourceids.go | 3 +- .../web/validate/certificate_order_id_test.go | 8 +- .../web/validate/certificate_order_old_id.go | 26 +++ .../validate/certificate_order_old_id_test.go | 79 +++++++++ 10 files changed, 469 insertions(+), 11 deletions(-) create mode 100644 internal/services/web/migration/app_service_certificate_order.go create mode 100644 internal/services/web/parse/certificate_order_old.go create mode 100644 internal/services/web/parse/certificate_order_old_test.go create mode 100644 internal/services/web/validate/certificate_order_old_id.go create mode 100644 internal/services/web/validate/certificate_order_old_id_test.go diff --git a/internal/services/web/app_service_certificate_order_resource.go b/internal/services/web/app_service_certificate_order_resource.go index 415467db71a7..cf219be2e2ec 100644 --- a/internal/services/web/app_service_certificate_order_resource.go +++ b/internal/services/web/app_service_certificate_order_resource.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/helpers/azure" "github.com/hashicorp/terraform-provider-azurerm/helpers/tf" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/web/migration" "github.com/hashicorp/terraform-provider-azurerm/internal/services/web/parse" "github.com/hashicorp/terraform-provider-azurerm/internal/tags" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" @@ -32,6 +33,11 @@ func resourceAppServiceCertificateOrder() *pluginsdk.Resource { return err }), + SchemaVersion: 1, + StateUpgraders: pluginsdk.StateUpgrades(map[int]pluginsdk.StateUpgrade{ + 0: migration.AppServiceCertificateOrderResourceV0ToV1{}, + }), + Timeouts: &pluginsdk.ResourceTimeout{ Create: pluginsdk.DefaultTimeout(30 * time.Minute), Read: pluginsdk.DefaultTimeout(5 * time.Minute), diff --git a/internal/services/web/migration/app_service_certificate_order.go b/internal/services/web/migration/app_service_certificate_order.go new file mode 100644 index 000000000000..823f6ea42ae4 --- /dev/null +++ b/internal/services/web/migration/app_service_certificate_order.go @@ -0,0 +1,159 @@ +package migration + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-provider-azurerm/internal/services/web/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" +) + +var _ pluginsdk.StateUpgrade = AppServiceCertificateOrderResourceV0ToV1{} + +type AppServiceCertificateOrderResourceV0ToV1 struct{} + +func (AppServiceCertificateOrderResourceV0ToV1) Schema() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + }, + + "location": { + Type: pluginsdk.TypeString, + Required: true, + }, + + "resource_group_name": { + Type: pluginsdk.TypeString, + Required: true, + }, + + "auto_renew": { + Type: pluginsdk.TypeBool, + Optional: true, + }, + + "certificates": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "certificate_name": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "key_vault_id": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "key_vault_secret_name": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "provisioning_state": { + Type: pluginsdk.TypeString, + Computed: true, + }, + }, + }, + }, + + "csr": { + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + }, + + "distinguished_name": { + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + }, + + "key_size": { + Type: pluginsdk.TypeInt, + Optional: true, + }, + + "product_type": { + Type: pluginsdk.TypeString, + Optional: true, + }, + + "validity_in_years": { + Type: pluginsdk.TypeInt, + Optional: true, + }, + + "domain_verification_token": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "status": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "expiration_time": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "is_private_key_external": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + + "app_service_certificate_not_renewable_reasons": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + + "signed_certificate_thumbprint": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "root_thumbprint": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "intermediate_thumbprint": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "tags": { + Type: pluginsdk.TypeMap, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + } +} + +func (AppServiceCertificateOrderResourceV0ToV1) UpgradeFunc() pluginsdk.StateUpgraderFunc { + return func(ctx context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) { + oldIdRaw := rawState["id"].(string) + oldId, err := parse.CertificateOrderOldID(oldIdRaw) + if err != nil { + return rawState, fmt.Errorf("parsing ID %q to upgrade: %+v", oldIdRaw, err) + } + + appServiceCertOrderId := parse.NewCertificateOrderID(oldId.SubscriptionId, oldId.ResourceGroup, oldId.CertificateOrderName) + newId := appServiceCertOrderId.ID() + + rawState["id"] = newId + return rawState, nil + } +} diff --git a/internal/services/web/parse/certificate_order.go b/internal/services/web/parse/certificate_order.go index 3a154491a35b..5096f5d419ce 100644 --- a/internal/services/web/parse/certificate_order.go +++ b/internal/services/web/parse/certificate_order.go @@ -36,7 +36,7 @@ func (id CertificateOrderId) String() string { } func (id CertificateOrderId) ID() string { - fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Web/certificateOrders/%s" + fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.CertificateRegistration/certificateOrders/%s" return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup, id.Name) } diff --git a/internal/services/web/parse/certificate_order_old.go b/internal/services/web/parse/certificate_order_old.go new file mode 100644 index 000000000000..3d96cd9e3121 --- /dev/null +++ b/internal/services/web/parse/certificate_order_old.go @@ -0,0 +1,72 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + "strings" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" +) + +type CertificateOrderOldId struct { + SubscriptionId string + ResourceGroup string + CertificateOrderName string +} + +func NewCertificateOrderOldID(subscriptionId, resourceGroup, certificateOrderName string) CertificateOrderOldId { + return CertificateOrderOldId{ + SubscriptionId: subscriptionId, + ResourceGroup: resourceGroup, + CertificateOrderName: certificateOrderName, + } +} + +func (id CertificateOrderOldId) String() string { + segments := []string{ + fmt.Sprintf("Certificate Order Name %q", id.CertificateOrderName), + fmt.Sprintf("Resource Group %q", id.ResourceGroup), + } + segmentsStr := strings.Join(segments, " / ") + return fmt.Sprintf("%s: (%s)", "Certificate Order Old", segmentsStr) +} + +func (id CertificateOrderOldId) ID() string { + fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Web/certificateOrders/%s" + return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup, id.CertificateOrderName) +} + +// CertificateOrderOldID parses a CertificateOrderOld ID into an CertificateOrderOldId struct +func CertificateOrderOldID(input string) (*CertificateOrderOldId, error) { + id, err := resourceids.ParseAzureResourceID(input) + if err != nil { + return nil, fmt.Errorf("parsing %q as an CertificateOrderOld ID: %+v", input, err) + } + + resourceId := CertificateOrderOldId{ + SubscriptionId: id.SubscriptionID, + ResourceGroup: id.ResourceGroup, + } + + if resourceId.SubscriptionId == "" { + return nil, fmt.Errorf("ID was missing the 'subscriptions' element") + } + + if resourceId.ResourceGroup == "" { + return nil, fmt.Errorf("ID was missing the 'resourceGroups' element") + } + + if resourceId.CertificateOrderName, err = id.PopSegment("certificateOrders"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &resourceId, nil +} diff --git a/internal/services/web/parse/certificate_order_old_test.go b/internal/services/web/parse/certificate_order_old_test.go new file mode 100644 index 000000000000..bc83154d70f3 --- /dev/null +++ b/internal/services/web/parse/certificate_order_old_test.go @@ -0,0 +1,115 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "testing" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" +) + +var _ resourceids.Id = CertificateOrderOldId{} + +func TestCertificateOrderOldIDFormatter(t *testing.T) { + actual := NewCertificateOrderOldID("12345678-1234-9876-4563-123456789012", "resGroup1", "order1").ID() + expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Web/certificateOrders/order1" + if actual != expected { + t.Fatalf("Expected %q but got %q", expected, actual) + } +} + +func TestCertificateOrderOldID(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *CertificateOrderOldId + }{ + + { + // empty + Input: "", + Error: true, + }, + + { + // missing SubscriptionId + Input: "/", + Error: true, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Error: true, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Error: true, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Error: true, + }, + + { + // missing CertificateOrderName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Web/", + Error: true, + }, + + { + // missing value for CertificateOrderName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Web/certificateOrders/", + Error: true, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Web/certificateOrders/order1", + Expected: &CertificateOrderOldId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + CertificateOrderName: "order1", + }, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.WEB/CERTIFICATEORDERS/ORDER1", + Error: true, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := CertificateOrderOldID(v.Input) + if err != nil { + if v.Error { + continue + } + + t.Fatalf("Expect a value but got an error: %s", err) + } + if v.Error { + t.Fatal("Expect an error but didn't get one") + } + + if actual.SubscriptionId != v.Expected.SubscriptionId { + t.Fatalf("Expected %q but got %q for SubscriptionId", v.Expected.SubscriptionId, actual.SubscriptionId) + } + if actual.ResourceGroup != v.Expected.ResourceGroup { + t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expected.ResourceGroup, actual.ResourceGroup) + } + if actual.CertificateOrderName != v.Expected.CertificateOrderName { + t.Fatalf("Expected %q but got %q for CertificateOrderName", v.Expected.CertificateOrderName, actual.CertificateOrderName) + } + } +} diff --git a/internal/services/web/parse/certificate_order_test.go b/internal/services/web/parse/certificate_order_test.go index ef9db848cd5f..6a61346d1112 100644 --- a/internal/services/web/parse/certificate_order_test.go +++ b/internal/services/web/parse/certificate_order_test.go @@ -15,7 +15,7 @@ var _ resourceids.Id = CertificateOrderId{} func TestCertificateOrderIDFormatter(t *testing.T) { actual := NewCertificateOrderID("12345678-1234-9876-4563-123456789012", "resGroup1", "order1").ID() - expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Web/certificateOrders/order1" + expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.CertificateRegistration/certificateOrders/order1" if actual != expected { t.Fatalf("Expected %q but got %q", expected, actual) } @@ -60,19 +60,19 @@ func TestCertificateOrderID(t *testing.T) { { // missing Name - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Web/", + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.CertificateRegistration/", Error: true, }, { // missing value for Name - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Web/certificateOrders/", + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.CertificateRegistration/certificateOrders/", Error: true, }, { // valid - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Web/certificateOrders/order1", + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.CertificateRegistration/certificateOrders/order1", Expected: &CertificateOrderId{ SubscriptionId: "12345678-1234-9876-4563-123456789012", ResourceGroup: "resGroup1", @@ -82,7 +82,7 @@ func TestCertificateOrderID(t *testing.T) { { // upper-cased - Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.WEB/CERTIFICATEORDERS/ORDER1", + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.CERTIFICATEREGISTRATION/CERTIFICATEORDERS/ORDER1", Error: true, }, } diff --git a/internal/services/web/resourceids.go b/internal/services/web/resourceids.go index 583675d96961..1c3aa139bfbb 100644 --- a/internal/services/web/resourceids.go +++ b/internal/services/web/resourceids.go @@ -8,7 +8,8 @@ package web //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=AppServiceSlot -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Web/sites/site1/slots/slot1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=AppServiceSlotCustomHostnameBinding -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Web/sites/site1/slots/slot1/hostNameBindings/binding1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=Certificate -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Web/certificates/certificate1 -//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=CertificateOrder -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Web/certificateOrders/order1 +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=CertificateOrder -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.CertificateRegistration/certificateOrders/order1 +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=CertificateOrderOld -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Web/certificateOrders/order1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=FunctionApp -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Web/sites/site1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=FunctionAppSlot -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Web/sites/site1/slots/slot1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=HostnameBinding -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/mygroup1/providers/Microsoft.Web/sites/site1/hostNameBindings/binding1 diff --git a/internal/services/web/validate/certificate_order_id_test.go b/internal/services/web/validate/certificate_order_id_test.go index 3216c6c35cd1..51ab5b32dbc2 100644 --- a/internal/services/web/validate/certificate_order_id_test.go +++ b/internal/services/web/validate/certificate_order_id_test.go @@ -45,25 +45,25 @@ func TestCertificateOrderID(t *testing.T) { { // missing Name - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Web/", + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.CertificateRegistration/", Valid: false, }, { // missing value for Name - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Web/certificateOrders/", + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.CertificateRegistration/certificateOrders/", Valid: false, }, { // valid - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Web/certificateOrders/order1", + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.CertificateRegistration/certificateOrders/order1", Valid: true, }, { // upper-cased - Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.WEB/CERTIFICATEORDERS/ORDER1", + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.CERTIFICATEREGISTRATION/CERTIFICATEORDERS/ORDER1", Valid: false, }, } diff --git a/internal/services/web/validate/certificate_order_old_id.go b/internal/services/web/validate/certificate_order_old_id.go new file mode 100644 index 000000000000..19a5eec78e1e --- /dev/null +++ b/internal/services/web/validate/certificate_order_old_id.go @@ -0,0 +1,26 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + + "github.com/hashicorp/terraform-provider-azurerm/internal/services/web/parse" +) + +func CertificateOrderOldID(input interface{}, key string) (warnings []string, errors []error) { + v, ok := input.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected %q to be a string", key)) + return + } + + if _, err := parse.CertificateOrderOldID(v); err != nil { + errors = append(errors, err) + } + + return +} diff --git a/internal/services/web/validate/certificate_order_old_id_test.go b/internal/services/web/validate/certificate_order_old_id_test.go new file mode 100644 index 000000000000..22de06e892da --- /dev/null +++ b/internal/services/web/validate/certificate_order_old_id_test.go @@ -0,0 +1,79 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import "testing" + +func TestCertificateOrderOldID(t *testing.T) { + cases := []struct { + Input string + Valid bool + }{ + + { + // empty + Input: "", + Valid: false, + }, + + { + // missing SubscriptionId + Input: "/", + Valid: false, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Valid: false, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Valid: false, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Valid: false, + }, + + { + // missing CertificateOrderName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Web/", + Valid: false, + }, + + { + // missing value for CertificateOrderName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Web/certificateOrders/", + Valid: false, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Web/certificateOrders/order1", + Valid: true, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.WEB/CERTIFICATEORDERS/ORDER1", + Valid: false, + }, + } + for _, tc := range cases { + t.Logf("[DEBUG] Testing Value %s", tc.Input) + _, errors := CertificateOrderOldID(tc.Input, "test") + valid := len(errors) == 0 + + if tc.Valid != valid { + t.Fatalf("Expected %t but got %t", tc.Valid, valid) + } + } +}