From 6cf6d98292cf5566e527c2764d05b036616f5605 Mon Sep 17 00:00:00 2001 From: Matthew Frahry Date: Fri, 2 Dec 2022 14:26:49 -0800 Subject: [PATCH] `azure_healthcare_*` - a state migration to work around the previously incorrect id casing (#19511) --- .../healthcare/healthcare_dicom_resource.go | 6 + .../healthcare/healthcare_fhir_resource.go | 6 + .../healthcare_medtech_service_data_source.go | 4 +- ...dtech_service_fhir_destination_resource.go | 26 ++- ..._service_fhir_destination_resource_test.go | 2 +- .../healthcare_medtech_service_resource.go | 22 +- ...ealthcare_medtech_service_resource_test.go | 2 +- ...thcare_dicom_service_migration_v0_to_v1.go | 138 ++++++++++++ .../healthcare_fhir_migration_v0_to_v1.go | 212 ++++++++++++++++++ ...thcare_iot_connector_migration_v0_to_v1.go | 99 ++++++++ ...ice_fhir_destination_migration_v0_to_v1.go | 64 ++++++ .../healthcare/parse/dicom_service.go | 60 ++++- .../healthcare/parse/dicom_service_test.go | 142 +++++++++++- .../services/healthcare/parse/fhir_service.go | 60 ++++- .../healthcare/parse/fhir_service_test.go | 142 +++++++++++- .../healthcare/parse/med_tech_service.go | 70 +++++- .../med_tech_service_fhir_destination.go | 90 +++++++- .../med_tech_service_fhir_destination_test.go | 185 +++++++++++++-- .../healthcare/parse/med_tech_service_test.go | 152 ++++++++++++- internal/services/healthcare/resourceids.go | 8 +- .../validate/dicom_service_id_test.go | 4 +- .../validate/fhir_service_id_test.go | 4 +- ...d_tech_service_fhir_destination_id_test.go | 16 +- .../validate/med_tech_service_id_test.go | 8 +- website/docs/r/healthcare_dicom.html.markdown | 2 +- .../r/healthcare_fhir_service.html.markdown | 2 +- .../healthcare_medtech_service.html.markdown | 2 +- ...ech_service_fhir_destination.html.markdown | 2 +- 28 files changed, 1433 insertions(+), 97 deletions(-) create mode 100644 internal/services/healthcare/migration/healthcare_dicom_service_migration_v0_to_v1.go create mode 100644 internal/services/healthcare/migration/healthcare_fhir_migration_v0_to_v1.go create mode 100644 internal/services/healthcare/migration/healthcare_iot_connector_migration_v0_to_v1.go create mode 100644 internal/services/healthcare/migration/healthcare_medtech_service_fhir_destination_migration_v0_to_v1.go diff --git a/internal/services/healthcare/healthcare_dicom_resource.go b/internal/services/healthcare/healthcare_dicom_resource.go index 0b609725a8c1..4e74ba607aa6 100644 --- a/internal/services/healthcare/healthcare_dicom_resource.go +++ b/internal/services/healthcare/healthcare_dicom_resource.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/go-azure-helpers/resourcemanager/location" "github.com/hashicorp/terraform-provider-azurerm/helpers/tf" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/healthcare/migration" "github.com/hashicorp/terraform-provider-azurerm/internal/services/healthcare/parse" "github.com/hashicorp/terraform-provider-azurerm/internal/services/healthcare/validate" "github.com/hashicorp/terraform-provider-azurerm/internal/tags" @@ -35,6 +36,11 @@ func resourceHealthcareApisDicomService() *pluginsdk.Resource { Delete: pluginsdk.DefaultTimeout(30 * time.Minute), }, + SchemaVersion: 1, + StateUpgraders: pluginsdk.StateUpgrades(map[int]pluginsdk.StateUpgrade{ + 0: migration.HealthCareDicomServiceV0ToV1{}, + }), + Importer: pluginsdk.ImporterValidatingResourceId(func(id string) error { _, err := parse.DicomServiceID(id) return err diff --git a/internal/services/healthcare/healthcare_fhir_resource.go b/internal/services/healthcare/healthcare_fhir_resource.go index f3225388a397..85d273a71052 100644 --- a/internal/services/healthcare/healthcare_fhir_resource.go +++ b/internal/services/healthcare/healthcare_fhir_resource.go @@ -14,6 +14,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/healthcare/migration" "github.com/hashicorp/terraform-provider-azurerm/internal/services/healthcare/parse" "github.com/hashicorp/terraform-provider-azurerm/internal/services/healthcare/validate" "github.com/hashicorp/terraform-provider-azurerm/internal/tags" @@ -37,6 +38,11 @@ func resourceHealthcareApisFhirService() *pluginsdk.Resource { Delete: pluginsdk.DefaultTimeout(30 * time.Minute), }, + SchemaVersion: 1, + StateUpgraders: pluginsdk.StateUpgrades(map[int]pluginsdk.StateUpgrade{ + 0: migration.HealthCareFhirV0ToV1{}, + }), + Importer: pluginsdk.ImporterValidatingResourceId(func(id string) error { _, err := parse.FhirServiceID(id) return err diff --git a/internal/services/healthcare/healthcare_medtech_service_data_source.go b/internal/services/healthcare/healthcare_medtech_service_data_source.go index 70409f6e36f7..348b325ee3bb 100644 --- a/internal/services/healthcare/healthcare_medtech_service_data_source.go +++ b/internal/services/healthcare/healthcare_medtech_service_data_source.go @@ -75,7 +75,7 @@ func dataSourceHealthcareIotConnectorRead(d *pluginsdk.ResourceData, meta interf id := parse.NewMedTechServiceID(subscriptionId, workspaceId.ResourceGroup, workspaceId.Name, d.Get("name").(string)) - resp, err := client.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.IotconnectorName) + resp, err := client.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.IotConnectorName) if err != nil { if utils.ResponseWasNotFound(resp.Response) { d.SetId("") @@ -85,7 +85,7 @@ func dataSourceHealthcareIotConnectorRead(d *pluginsdk.ResourceData, meta interf } d.SetId(id.ID()) - d.Set("name", id.IotconnectorName) + d.Set("name", id.IotConnectorName) d.Set("workspace_id", workspaceId.ID()) diff --git a/internal/services/healthcare/healthcare_medtech_service_fhir_destination_resource.go b/internal/services/healthcare/healthcare_medtech_service_fhir_destination_resource.go index 24ea1139ce50..0647ca5eae3a 100644 --- a/internal/services/healthcare/healthcare_medtech_service_fhir_destination_resource.go +++ b/internal/services/healthcare/healthcare_medtech_service_fhir_destination_resource.go @@ -15,6 +15,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/healthcare/migration" "github.com/hashicorp/terraform-provider-azurerm/internal/services/healthcare/parse" "github.com/hashicorp/terraform-provider-azurerm/internal/services/healthcare/validate" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" @@ -41,6 +42,11 @@ func resourceHealthcareApisMedTechServiceFhirDestination() *pluginsdk.Resource { return err }), + SchemaVersion: 1, + StateUpgraders: pluginsdk.StateUpgrades(map[int]pluginsdk.StateUpgrade{ + 0: migration.HealthCareMedTechServiceFhirDestinationV0ToV1{}, + }), + Schema: map[string]*pluginsdk.Schema{ "name": { Type: pluginsdk.TypeString, @@ -93,10 +99,10 @@ func resourceHealthcareApisMedTechServiceFhirDestinationCreate(d *pluginsdk.Reso if err != nil { return fmt.Errorf("parsing Med Tech Service error: %+v", err) } - id := parse.NewMedTechServiceFhirDestinationID(medTechService.SubscriptionId, medTechService.ResourceGroup, medTechService.WorkspaceName, medTechService.IotconnectorName, d.Get("name").(string)) + id := parse.NewMedTechServiceFhirDestinationID(medTechService.SubscriptionId, medTechService.ResourceGroup, medTechService.WorkspaceName, medTechService.IotConnectorName, d.Get("name").(string)) if d.IsNewResource() { - existing, err := client.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.IotconnectorName, id.FhirdestinationName) + existing, err := client.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.IotConnectorName, id.FhirDestinationName) if err != nil { if !utils.ResponseWasNotFound(existing.Response) { return fmt.Errorf("checking for presence of existing %s: %+v", id, err) @@ -127,7 +133,7 @@ func resourceHealthcareApisMedTechServiceFhirDestinationCreate(d *pluginsdk.Reso } iotFhirServiceParameters.IotFhirDestinationProperties.FhirMapping = &fhirMap - medTechServiceDesFuture, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.WorkspaceName, id.IotconnectorName, id.FhirdestinationName, iotFhirServiceParameters) + medTechServiceDesFuture, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.WorkspaceName, id.IotConnectorName, id.FhirDestinationName, iotFhirServiceParameters) if err != nil { return fmt.Errorf("updating fhir service %s for the Med Tech Service err: %+v", id, err) } @@ -150,10 +156,10 @@ func resourceHealthcareApisMedTechServiceFhirDestinationRead(d *pluginsdk.Resour return err } - medTechServiceId := parse.NewMedTechServiceID(id.SubscriptionId, id.ResourceGroup, id.WorkspaceName, id.IotconnectorName) + medTechServiceId := parse.NewMedTechServiceID(id.SubscriptionId, id.ResourceGroup, id.WorkspaceName, id.IotConnectorName) d.Set("medtech_service_id", medTechServiceId.ID()) - resp, err := client.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.IotconnectorName, id.FhirdestinationName) + resp, err := client.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.IotConnectorName, id.FhirDestinationName) if err != nil { if utils.ResponseWasNotFound(resp.Response) { log.Printf("[WARN] Healthcare Apis Med Tech Service Fhir Destination %s was not found", id) @@ -163,7 +169,7 @@ func resourceHealthcareApisMedTechServiceFhirDestinationRead(d *pluginsdk.Resour return fmt.Errorf("retrieving %s: %+v", *id, err) } - d.Set("name", id.FhirdestinationName) + d.Set("name", id.FhirDestinationName) if resp.Location != nil { d.Set("location", location.NormalizeNilable(resp.Location)) @@ -208,7 +214,7 @@ func resourceHealthcareApisMedTechServiceFhirDestinationUpdate(d *pluginsdk.Reso if err != nil { return fmt.Errorf("parsing Med Tech Service error: %+v", err) } - id := parse.NewMedTechServiceFhirDestinationID(medTechService.SubscriptionId, medTechService.ResourceGroup, medTechService.WorkspaceName, medTechService.IotconnectorName, d.Get("name").(string)) + id := parse.NewMedTechServiceFhirDestinationID(medTechService.SubscriptionId, medTechService.ResourceGroup, medTechService.WorkspaceName, medTechService.IotConnectorName, d.Get("name").(string)) fhirServiceId, err := parse.FhirServiceID(d.Get("destination_fhir_service_id").(string)) if err != nil { @@ -230,7 +236,7 @@ func resourceHealthcareApisMedTechServiceFhirDestinationUpdate(d *pluginsdk.Reso } medTechFhirServiceParameters.IotFhirDestinationProperties.FhirMapping = &fhirMap - medTechServiceDesFuture, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.WorkspaceName, id.IotconnectorName, id.FhirdestinationName, medTechFhirServiceParameters) + medTechServiceDesFuture, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.WorkspaceName, id.IotConnectorName, id.FhirDestinationName, medTechFhirServiceParameters) if err != nil { return fmt.Errorf("updating fhir service %s for the Med Tech Service err: %+v", id, err) } @@ -253,7 +259,7 @@ func resourceHealthcareApisMedTechServiceFhirDestinationDelete(d *pluginsdk.Reso return err } - future, err := client.Delete(ctx, id.ResourceGroup, id.WorkspaceName, id.IotconnectorName, id.FhirdestinationName) + future, err := client.Delete(ctx, id.ResourceGroup, id.WorkspaceName, id.IotConnectorName, id.FhirDestinationName) if err != nil { if response.WasNotFound(future.Response()) { return nil @@ -278,7 +284,7 @@ func resourceHealthcareApisMedTechServiceFhirDestinationDelete(d *pluginsdk.Reso func healthcareApiMedTechServiceFhirDestinationStateCodeRefreshFunc(ctx context.Context, client *healthcareapis.IotConnectorFhirDestinationClient, id parse.MedTechServiceFhirDestinationId) pluginsdk.StateRefreshFunc { return func() (interface{}, string, error) { - res, err := client.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.IotconnectorName, id.FhirdestinationName) + res, err := client.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.IotConnectorName, id.FhirDestinationName) if err != nil { if utils.ResponseWasNotFound(res.Response) { diff --git a/internal/services/healthcare/healthcare_medtech_service_fhir_destination_resource_test.go b/internal/services/healthcare/healthcare_medtech_service_fhir_destination_resource_test.go index 56c2f86b5c94..b7590d79a09d 100644 --- a/internal/services/healthcare/healthcare_medtech_service_fhir_destination_resource_test.go +++ b/internal/services/healthcare/healthcare_medtech_service_fhir_destination_resource_test.go @@ -94,7 +94,7 @@ func (r HealthCareMedTechServiceFhirDestinationResource) Exists(ctx context.Cont if err != nil { return nil, err } - resp, err := client.HealthCare.HealthcareWorkspaceMedTechServiceFhirDestinationClient.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.IotconnectorName, id.FhirdestinationName) + resp, err := client.HealthCare.HealthcareWorkspaceMedTechServiceFhirDestinationClient.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.IotConnectorName, id.FhirDestinationName) if err != nil { return nil, fmt.Errorf("retrieving %s, %+v", *id, err) } diff --git a/internal/services/healthcare/healthcare_medtech_service_resource.go b/internal/services/healthcare/healthcare_medtech_service_resource.go index 6c5d45a959ea..9f99fcbd3440 100644 --- a/internal/services/healthcare/healthcare_medtech_service_resource.go +++ b/internal/services/healthcare/healthcare_medtech_service_resource.go @@ -17,6 +17,7 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/helpers/tf" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" eventhubValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/eventhub/validate" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/healthcare/migration" "github.com/hashicorp/terraform-provider-azurerm/internal/services/healthcare/parse" "github.com/hashicorp/terraform-provider-azurerm/internal/services/healthcare/validate" "github.com/hashicorp/terraform-provider-azurerm/internal/tags" @@ -39,6 +40,11 @@ func resourceHealthcareApisMedTechService() *pluginsdk.Resource { Delete: pluginsdk.DefaultTimeout(90 * time.Minute), }, + SchemaVersion: 1, + StateUpgraders: pluginsdk.StateUpgrades(map[int]pluginsdk.StateUpgrade{ + 0: migration.HealthCareIoTConnectorV0ToV1{}, + }), + Importer: pluginsdk.ImporterValidatingResourceId(func(id string) error { _, err := parse.MedTechServiceID(id) return err @@ -106,7 +112,7 @@ func resourceHealthcareApisMedTechServiceCreate(d *pluginsdk.ResourceData, meta medTechServiceId := parse.NewMedTechServiceID(workspace.SubscriptionId, workspace.ResourceGroup, workspace.Name, d.Get("name").(string)) if d.IsNewResource() { - existing, err := client.Get(ctx, medTechServiceId.ResourceGroup, medTechServiceId.WorkspaceName, medTechServiceId.IotconnectorName) + existing, err := client.Get(ctx, medTechServiceId.ResourceGroup, medTechServiceId.WorkspaceName, medTechServiceId.IotConnectorName) if err != nil { if !utils.ResponseWasNotFound(existing.Response) { return fmt.Errorf("checking for presence of existing %s: %+v", medTechServiceId, err) @@ -143,7 +149,7 @@ func resourceHealthcareApisMedTechServiceCreate(d *pluginsdk.ResourceData, meta } parameters.IotConnectorProperties.DeviceMapping = &deviceContentMap - future, err := client.CreateOrUpdate(ctx, medTechServiceId.ResourceGroup, medTechServiceId.WorkspaceName, medTechServiceId.IotconnectorName, parameters) + future, err := client.CreateOrUpdate(ctx, medTechServiceId.ResourceGroup, medTechServiceId.WorkspaceName, medTechServiceId.IotConnectorName, parameters) if err != nil { return fmt.Errorf("creating %s: %+v", medTechServiceId, err) } @@ -180,7 +186,7 @@ func resourceHealthcareApisMedTechServiceRead(d *pluginsdk.ResourceData, meta in return err } - resp, err := client.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.IotconnectorName) + resp, err := client.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.IotConnectorName) if err != nil { if utils.ResponseWasNotFound(resp.Response) { log.Printf("[WARN] Healthcare Apis MedTech Service %s was not found", id) @@ -190,7 +196,7 @@ func resourceHealthcareApisMedTechServiceRead(d *pluginsdk.ResourceData, meta in return fmt.Errorf("retrieving %s: %+v", *id, err) } - d.Set("name", id.IotconnectorName) + d.Set("name", id.IotConnectorName) workspaceId := parse.NewWorkspaceID(id.SubscriptionId, id.ResourceGroup, id.WorkspaceName) d.Set("workspace_id", workspaceId.ID()) @@ -280,7 +286,7 @@ func resourceHealthcareApisMedTechServiceUpdate(d *pluginsdk.ResourceData, meta } parameters.IotConnectorProperties.DeviceMapping = &deviceContentMap - future, err := client.CreateOrUpdate(ctx, medTechServiceId.ResourceGroup, medTechServiceId.WorkspaceName, medTechServiceId.IotconnectorName, parameters) + future, err := client.CreateOrUpdate(ctx, medTechServiceId.ResourceGroup, medTechServiceId.WorkspaceName, medTechServiceId.IotConnectorName, parameters) if err != nil { return fmt.Errorf("updating %s: %+v", medTechServiceId, err) } @@ -317,7 +323,7 @@ func resourceHealthcareApisMedTechServiceDelete(d *pluginsdk.ResourceData, meta return err } - future, err := client.Delete(ctx, id.ResourceGroup, id.IotconnectorName, id.WorkspaceName) + future, err := client.Delete(ctx, id.ResourceGroup, id.IotConnectorName, id.WorkspaceName) if err != nil { if response.WasNotFound(future.Response()) { return nil @@ -342,7 +348,7 @@ func resourceHealthcareApisMedTechServiceDelete(d *pluginsdk.ResourceData, meta func medTechServiceStateStatusCodeRefreshFunc(ctx context.Context, client *healthcareapis.IotConnectorsClient, id parse.MedTechServiceId) pluginsdk.StateRefreshFunc { return func() (interface{}, string, error) { - res, err := client.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.IotconnectorName) + res, err := client.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.IotConnectorName) if err != nil { if utils.ResponseWasNotFound(res.Response) { @@ -392,7 +398,7 @@ func suppressJsonOrderingDifference(_, old, new string, _ *pluginsdk.ResourceDat func medTechServiceCreateStateRefreshFunc(ctx context.Context, client *healthcareapis.IotConnectorsClient, medTechServiceId parse.MedTechServiceId) pluginsdk.StateRefreshFunc { return func() (interface{}, string, error) { - resp, err := client.Get(ctx, medTechServiceId.ResourceGroup, medTechServiceId.WorkspaceName, medTechServiceId.IotconnectorName) + resp, err := client.Get(ctx, medTechServiceId.ResourceGroup, medTechServiceId.WorkspaceName, medTechServiceId.IotConnectorName) if err != nil { if utils.ResponseWasNotFound(resp.Response) { return nil, "", fmt.Errorf("unable to retrieve MedTech Service %q: %+v", medTechServiceId, err) diff --git a/internal/services/healthcare/healthcare_medtech_service_resource_test.go b/internal/services/healthcare/healthcare_medtech_service_resource_test.go index 2dcf661c6eeb..6a330332a6b4 100644 --- a/internal/services/healthcare/healthcare_medtech_service_resource_test.go +++ b/internal/services/healthcare/healthcare_medtech_service_resource_test.go @@ -114,7 +114,7 @@ func (r HealthCareWorkspaceMedTechServiceResource) Exists(ctx context.Context, c if err != nil { return nil, err } - resp, err := client.HealthCare.HealthcareWorkspaceMedTechServiceClient.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.IotconnectorName) + resp, err := client.HealthCare.HealthcareWorkspaceMedTechServiceClient.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.IotConnectorName) if err != nil { return nil, fmt.Errorf("retrieving %s, %+v", *id, err) } diff --git a/internal/services/healthcare/migration/healthcare_dicom_service_migration_v0_to_v1.go b/internal/services/healthcare/migration/healthcare_dicom_service_migration_v0_to_v1.go new file mode 100644 index 000000000000..bb2c5fb6c4a1 --- /dev/null +++ b/internal/services/healthcare/migration/healthcare_dicom_service_migration_v0_to_v1.go @@ -0,0 +1,138 @@ +package migration + +import ( + "context" + "log" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/commonids" + "github.com/hashicorp/go-azure-helpers/resourcemanager/location" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/healthcare/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" +) + +type HealthCareDicomServiceV0ToV1 struct{} + +func (s HealthCareDicomServiceV0ToV1) Schema() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + }, + + "workspace_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + }, + + "location": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: location.EnhancedValidate, + StateFunc: location.StateFunc, + DiffSuppressFunc: location.DiffSuppressFunc, + }, + + "identity": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Required: true, + }, + "identity_ids": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: commonids.ValidateUserAssignedIdentityID, + }, + }, + "principal_id": { + Type: schema.TypeString, + Computed: true, + }, + "tenant_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + + "authentication": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "authority": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "audience": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Schema{Type: pluginsdk.TypeString}, + }, + }, + }, + }, + + "private_endpoint": { + Type: pluginsdk.TypeSet, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "id": { + Type: pluginsdk.TypeString, + Computed: true, + }, + }, + }, + }, + + "service_url": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "public_network_access_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + }, + + "tags": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + } +} + +func (s HealthCareDicomServiceV0ToV1) UpgradeFunc() pluginsdk.StateUpgraderFunc { + return func(ctx context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) { + oldId := rawState["id"].(string) + newId, err := parse.DicomServiceIDInsensitively(oldId) + if err != nil { + return nil, err + } + + log.Printf("[DEBUG] Updating ID from %q to %q", oldId, newId) + + rawState["id"] = newId.ID() + return rawState, nil + } +} diff --git a/internal/services/healthcare/migration/healthcare_fhir_migration_v0_to_v1.go b/internal/services/healthcare/migration/healthcare_fhir_migration_v0_to_v1.go new file mode 100644 index 000000000000..046465617879 --- /dev/null +++ b/internal/services/healthcare/migration/healthcare_fhir_migration_v0_to_v1.go @@ -0,0 +1,212 @@ +package migration + +import ( + "context" + "log" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/healthcare/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" +) + +type HealthCareFhirV0ToV1 struct{} + +func (s HealthCareFhirV0ToV1) Schema() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + }, + + "resource_group_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "workspace_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + }, + + "location": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "kind": { + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + }, + + "access_policy_object_ids": { + Type: pluginsdk.TypeSet, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + + "authentication": { + Type: pluginsdk.TypeList, + Required: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "authority": { + Type: pluginsdk.TypeString, + Required: true, + }, + + "audience": { + Type: pluginsdk.TypeString, + Required: true, + }, + + "smart_proxy_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + }, + }, + }, + }, + + "identity": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Required: true, + }, + "principal_id": { + Type: schema.TypeString, + Computed: true, + }, + "tenant_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + + // can't use the registry ID due to the ID cannot be obtained when setting the property in state file + "container_registry_login_server_url": { + Type: pluginsdk.TypeSet, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + + "oci_artifact": { + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "login_server": { + Type: pluginsdk.TypeString, + Required: true, + }, + + "image_name": { + Type: pluginsdk.TypeString, + Optional: true, + }, + + "digest": { + Type: pluginsdk.TypeString, + Optional: true, + }, + }, + }, + }, + + "cors": { + Type: pluginsdk.TypeList, + Optional: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "allowed_origins": { + Type: pluginsdk.TypeSet, + Required: true, + MaxItems: 64, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + + "allowed_headers": { + Type: pluginsdk.TypeSet, + Required: true, + MaxItems: 64, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + + "allowed_methods": { + Type: pluginsdk.TypeSet, + Required: true, + MaxItems: 64, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + + "max_age_in_seconds": { + Type: pluginsdk.TypeInt, + Optional: true, + }, + + "credentials_allowed": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + }, + }, + }, + }, + + "configuration_export_storage_account_name": { + Type: pluginsdk.TypeString, + Optional: true, + }, + + "public_network_access_enabled": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + + "tags": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + } +} + +func (s HealthCareFhirV0ToV1) UpgradeFunc() pluginsdk.StateUpgraderFunc { + return func(ctx context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) { + oldId := rawState["id"].(string) + newId, err := parse.FhirServiceIDInsensitively(oldId) + if err != nil { + return nil, err + } + + log.Printf("[DEBUG] Updating ID from %q to %q", oldId, newId) + + rawState["id"] = newId.ID() + return rawState, nil + } +} diff --git a/internal/services/healthcare/migration/healthcare_iot_connector_migration_v0_to_v1.go b/internal/services/healthcare/migration/healthcare_iot_connector_migration_v0_to_v1.go new file mode 100644 index 000000000000..e7c615918d5b --- /dev/null +++ b/internal/services/healthcare/migration/healthcare_iot_connector_migration_v0_to_v1.go @@ -0,0 +1,99 @@ +package migration + +import ( + "context" + "log" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/healthcare/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" +) + +type HealthCareIoTConnectorV0ToV1 struct{} + +func (s HealthCareIoTConnectorV0ToV1) Schema() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + }, + + "workspace_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + }, + + "location": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "identity": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Required: true, + }, + "principal_id": { + Type: schema.TypeString, + Computed: true, + }, + "tenant_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + + "eventhub_namespace_name": { + Type: pluginsdk.TypeString, + Required: true, + }, + + "eventhub_name": { + Type: pluginsdk.TypeString, + Required: true, + }, + + "eventhub_consumer_group_name": { + Type: pluginsdk.TypeString, + Required: true, + }, + + "device_mapping_json": { + Type: pluginsdk.TypeString, + Required: true, + }, + + "tags": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + } +} + +func (s HealthCareIoTConnectorV0ToV1) UpgradeFunc() pluginsdk.StateUpgraderFunc { + return func(ctx context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) { + oldId := rawState["id"].(string) + newId, err := parse.MedTechServiceIDInsensitively(oldId) + if err != nil { + return nil, err + } + + log.Printf("[DEBUG] Updating ID from %q to %q", oldId, newId) + + rawState["id"] = newId.ID() + return rawState, nil + } +} diff --git a/internal/services/healthcare/migration/healthcare_medtech_service_fhir_destination_migration_v0_to_v1.go b/internal/services/healthcare/migration/healthcare_medtech_service_fhir_destination_migration_v0_to_v1.go new file mode 100644 index 000000000000..d697f9a0b683 --- /dev/null +++ b/internal/services/healthcare/migration/healthcare_medtech_service_fhir_destination_migration_v0_to_v1.go @@ -0,0 +1,64 @@ +package migration + +import ( + "context" + "log" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/healthcare/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" +) + +type HealthCareMedTechServiceFhirDestinationV0ToV1 struct{} + +func (s HealthCareMedTechServiceFhirDestinationV0ToV1) Schema() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + }, + + "medtech_service_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + }, + + "location": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "destination_fhir_service_id": { + Type: pluginsdk.TypeString, + Required: true, + }, + + "destination_identity_resolution_type": { + Type: pluginsdk.TypeString, + Required: true, + }, + + "destination_fhir_mapping_json": { + Type: pluginsdk.TypeString, + Required: true, + }, + } +} + +func (s HealthCareMedTechServiceFhirDestinationV0ToV1) UpgradeFunc() pluginsdk.StateUpgraderFunc { + return func(ctx context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) { + oldId := rawState["id"].(string) + newId, err := parse.MedTechServiceFhirDestinationIDInsensitively(oldId) + if err != nil { + return nil, err + } + + log.Printf("[DEBUG] Updating ID from %q to %q", oldId, newId) + + rawState["id"] = newId.ID() + return rawState, nil + } +} diff --git a/internal/services/healthcare/parse/dicom_service.go b/internal/services/healthcare/parse/dicom_service.go index 6309633f0cc0..6cf01831f98b 100644 --- a/internal/services/healthcare/parse/dicom_service.go +++ b/internal/services/healthcare/parse/dicom_service.go @@ -36,7 +36,7 @@ func (id DicomServiceId) String() string { } func (id DicomServiceId) ID() string { - fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.HealthcareApis/workspaces/%s/dicomservices/%s" + fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.HealthcareApis/workspaces/%s/dicomServices/%s" return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup, id.WorkspaceName, id.Name) } @@ -63,7 +63,63 @@ func DicomServiceID(input string) (*DicomServiceId, error) { if resourceId.WorkspaceName, err = id.PopSegment("workspaces"); err != nil { return nil, err } - if resourceId.Name, err = id.PopSegment("dicomservices"); err != nil { + if resourceId.Name, err = id.PopSegment("dicomServices"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &resourceId, nil +} + +// DicomServiceIDInsensitively parses an DicomService ID into an DicomServiceId struct, insensitively +// This should only be used to parse an ID for rewriting, the DicomServiceID +// method should be used instead for validation etc. +// +// Whilst this may seem strange, this enables Terraform have consistent casing +// which works around issues in Core, whilst handling broken API responses. +func DicomServiceIDInsensitively(input string) (*DicomServiceId, error) { + id, err := resourceids.ParseAzureResourceID(input) + if err != nil { + return nil, err + } + + resourceId := DicomServiceId{ + 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") + } + + // find the correct casing for the 'workspaces' segment + workspacesKey := "workspaces" + for key := range id.Path { + if strings.EqualFold(key, workspacesKey) { + workspacesKey = key + break + } + } + if resourceId.WorkspaceName, err = id.PopSegment(workspacesKey); err != nil { + return nil, err + } + + // find the correct casing for the 'dicomServices' segment + dicomServicesKey := "dicomServices" + for key := range id.Path { + if strings.EqualFold(key, dicomServicesKey) { + dicomServicesKey = key + break + } + } + if resourceId.Name, err = id.PopSegment(dicomServicesKey); err != nil { return nil, err } diff --git a/internal/services/healthcare/parse/dicom_service_test.go b/internal/services/healthcare/parse/dicom_service_test.go index 473490011e54..23300191ebd9 100644 --- a/internal/services/healthcare/parse/dicom_service_test.go +++ b/internal/services/healthcare/parse/dicom_service_test.go @@ -12,7 +12,7 @@ var _ resourceids.Id = DicomServiceId{} func TestDicomServiceIDFormatter(t *testing.T) { actual := NewDicomServiceID("12345678-1234-9876-4563-123456789012", "group1", "workspace1", "service1").ID() - expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/dicomservices/service1" + expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/dicomServices/service1" if actual != expected { t.Fatalf("Expected %q but got %q", expected, actual) } @@ -75,13 +75,13 @@ func TestDicomServiceID(t *testing.T) { { // missing value for Name - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/dicomservices/", + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/dicomServices/", Error: true, }, { // valid - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/dicomservices/service1", + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/dicomServices/service1", Expected: &DicomServiceId{ SubscriptionId: "12345678-1234-9876-4563-123456789012", ResourceGroup: "group1", @@ -126,3 +126,139 @@ func TestDicomServiceID(t *testing.T) { } } } + +func TestDicomServiceIDInsensitively(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *DicomServiceId + }{ + + { + // 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 WorkspaceName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/", + Error: true, + }, + + { + // missing value for WorkspaceName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/", + Error: true, + }, + + { + // missing Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/", + Error: true, + }, + + { + // missing value for Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/dicomServices/", + Error: true, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/dicomServices/service1", + Expected: &DicomServiceId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "group1", + WorkspaceName: "workspace1", + Name: "service1", + }, + }, + + { + // lower-cased segment names + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/dicomservices/service1", + Expected: &DicomServiceId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "group1", + WorkspaceName: "workspace1", + Name: "service1", + }, + }, + + { + // upper-cased segment names + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/WORKSPACES/workspace1/DICOMSERVICES/service1", + Expected: &DicomServiceId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "group1", + WorkspaceName: "workspace1", + Name: "service1", + }, + }, + + { + // mixed-cased segment names + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/WoRkSpAcEs/workspace1/DiCoMsErViCeS/service1", + Expected: &DicomServiceId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "group1", + WorkspaceName: "workspace1", + Name: "service1", + }, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := DicomServiceIDInsensitively(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.WorkspaceName != v.Expected.WorkspaceName { + t.Fatalf("Expected %q but got %q for WorkspaceName", v.Expected.WorkspaceName, actual.WorkspaceName) + } + if actual.Name != v.Expected.Name { + t.Fatalf("Expected %q but got %q for Name", v.Expected.Name, actual.Name) + } + } +} diff --git a/internal/services/healthcare/parse/fhir_service.go b/internal/services/healthcare/parse/fhir_service.go index b28fa20a9244..e4e8745e7728 100644 --- a/internal/services/healthcare/parse/fhir_service.go +++ b/internal/services/healthcare/parse/fhir_service.go @@ -36,7 +36,7 @@ func (id FhirServiceId) String() string { } func (id FhirServiceId) ID() string { - fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.HealthcareApis/workspaces/%s/fhirservices/%s" + fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.HealthcareApis/workspaces/%s/fhirServices/%s" return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup, id.WorkspaceName, id.Name) } @@ -63,7 +63,63 @@ func FhirServiceID(input string) (*FhirServiceId, error) { if resourceId.WorkspaceName, err = id.PopSegment("workspaces"); err != nil { return nil, err } - if resourceId.Name, err = id.PopSegment("fhirservices"); err != nil { + if resourceId.Name, err = id.PopSegment("fhirServices"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &resourceId, nil +} + +// FhirServiceIDInsensitively parses an FhirService ID into an FhirServiceId struct, insensitively +// This should only be used to parse an ID for rewriting, the FhirServiceID +// method should be used instead for validation etc. +// +// Whilst this may seem strange, this enables Terraform have consistent casing +// which works around issues in Core, whilst handling broken API responses. +func FhirServiceIDInsensitively(input string) (*FhirServiceId, error) { + id, err := resourceids.ParseAzureResourceID(input) + if err != nil { + return nil, err + } + + resourceId := FhirServiceId{ + 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") + } + + // find the correct casing for the 'workspaces' segment + workspacesKey := "workspaces" + for key := range id.Path { + if strings.EqualFold(key, workspacesKey) { + workspacesKey = key + break + } + } + if resourceId.WorkspaceName, err = id.PopSegment(workspacesKey); err != nil { + return nil, err + } + + // find the correct casing for the 'fhirServices' segment + fhirServicesKey := "fhirServices" + for key := range id.Path { + if strings.EqualFold(key, fhirServicesKey) { + fhirServicesKey = key + break + } + } + if resourceId.Name, err = id.PopSegment(fhirServicesKey); err != nil { return nil, err } diff --git a/internal/services/healthcare/parse/fhir_service_test.go b/internal/services/healthcare/parse/fhir_service_test.go index 45e3c7919c90..ecb21fcebc6a 100644 --- a/internal/services/healthcare/parse/fhir_service_test.go +++ b/internal/services/healthcare/parse/fhir_service_test.go @@ -12,7 +12,7 @@ var _ resourceids.Id = FhirServiceId{} func TestFhirServiceIDFormatter(t *testing.T) { actual := NewFhirServiceID("12345678-1234-9876-4563-123456789012", "group1", "workspace1", "service1").ID() - expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/fhirservices/service1" + expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/fhirServices/service1" if actual != expected { t.Fatalf("Expected %q but got %q", expected, actual) } @@ -75,13 +75,13 @@ func TestFhirServiceID(t *testing.T) { { // missing value for Name - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/fhirservices/", + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/fhirServices/", Error: true, }, { // valid - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/fhirservices/service1", + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/fhirServices/service1", Expected: &FhirServiceId{ SubscriptionId: "12345678-1234-9876-4563-123456789012", ResourceGroup: "group1", @@ -126,3 +126,139 @@ func TestFhirServiceID(t *testing.T) { } } } + +func TestFhirServiceIDInsensitively(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *FhirServiceId + }{ + + { + // 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 WorkspaceName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/", + Error: true, + }, + + { + // missing value for WorkspaceName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/", + Error: true, + }, + + { + // missing Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/", + Error: true, + }, + + { + // missing value for Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/fhirServices/", + Error: true, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/fhirServices/service1", + Expected: &FhirServiceId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "group1", + WorkspaceName: "workspace1", + Name: "service1", + }, + }, + + { + // lower-cased segment names + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/fhirservices/service1", + Expected: &FhirServiceId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "group1", + WorkspaceName: "workspace1", + Name: "service1", + }, + }, + + { + // upper-cased segment names + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/WORKSPACES/workspace1/FHIRSERVICES/service1", + Expected: &FhirServiceId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "group1", + WorkspaceName: "workspace1", + Name: "service1", + }, + }, + + { + // mixed-cased segment names + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/WoRkSpAcEs/workspace1/FhIrSeRvIcEs/service1", + Expected: &FhirServiceId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "group1", + WorkspaceName: "workspace1", + Name: "service1", + }, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := FhirServiceIDInsensitively(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.WorkspaceName != v.Expected.WorkspaceName { + t.Fatalf("Expected %q but got %q for WorkspaceName", v.Expected.WorkspaceName, actual.WorkspaceName) + } + if actual.Name != v.Expected.Name { + t.Fatalf("Expected %q but got %q for Name", v.Expected.Name, actual.Name) + } + } +} diff --git a/internal/services/healthcare/parse/med_tech_service.go b/internal/services/healthcare/parse/med_tech_service.go index f13f611902f0..885a6392c02e 100644 --- a/internal/services/healthcare/parse/med_tech_service.go +++ b/internal/services/healthcare/parse/med_tech_service.go @@ -13,21 +13,21 @@ type MedTechServiceId struct { SubscriptionId string ResourceGroup string WorkspaceName string - IotconnectorName string + IotConnectorName string } -func NewMedTechServiceID(subscriptionId, resourceGroup, workspaceName, iotconnectorName string) MedTechServiceId { +func NewMedTechServiceID(subscriptionId, resourceGroup, workspaceName, iotConnectorName string) MedTechServiceId { return MedTechServiceId{ SubscriptionId: subscriptionId, ResourceGroup: resourceGroup, WorkspaceName: workspaceName, - IotconnectorName: iotconnectorName, + IotConnectorName: iotConnectorName, } } func (id MedTechServiceId) String() string { segments := []string{ - fmt.Sprintf("Iotconnector Name %q", id.IotconnectorName), + fmt.Sprintf("Iot Connector Name %q", id.IotConnectorName), fmt.Sprintf("Workspace Name %q", id.WorkspaceName), fmt.Sprintf("Resource Group %q", id.ResourceGroup), } @@ -36,8 +36,8 @@ func (id MedTechServiceId) String() string { } func (id MedTechServiceId) ID() string { - fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.HealthcareApis/workspaces/%s/iotconnectors/%s" - return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup, id.WorkspaceName, id.IotconnectorName) + fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.HealthcareApis/workspaces/%s/iotConnectors/%s" + return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup, id.WorkspaceName, id.IotConnectorName) } // MedTechServiceID parses a MedTechService ID into an MedTechServiceId struct @@ -63,7 +63,63 @@ func MedTechServiceID(input string) (*MedTechServiceId, error) { if resourceId.WorkspaceName, err = id.PopSegment("workspaces"); err != nil { return nil, err } - if resourceId.IotconnectorName, err = id.PopSegment("iotconnectors"); err != nil { + if resourceId.IotConnectorName, err = id.PopSegment("iotConnectors"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &resourceId, nil +} + +// MedTechServiceIDInsensitively parses an MedTechService ID into an MedTechServiceId struct, insensitively +// This should only be used to parse an ID for rewriting, the MedTechServiceID +// method should be used instead for validation etc. +// +// Whilst this may seem strange, this enables Terraform have consistent casing +// which works around issues in Core, whilst handling broken API responses. +func MedTechServiceIDInsensitively(input string) (*MedTechServiceId, error) { + id, err := resourceids.ParseAzureResourceID(input) + if err != nil { + return nil, err + } + + resourceId := MedTechServiceId{ + 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") + } + + // find the correct casing for the 'workspaces' segment + workspacesKey := "workspaces" + for key := range id.Path { + if strings.EqualFold(key, workspacesKey) { + workspacesKey = key + break + } + } + if resourceId.WorkspaceName, err = id.PopSegment(workspacesKey); err != nil { + return nil, err + } + + // find the correct casing for the 'iotConnectors' segment + iotConnectorsKey := "iotConnectors" + for key := range id.Path { + if strings.EqualFold(key, iotConnectorsKey) { + iotConnectorsKey = key + break + } + } + if resourceId.IotConnectorName, err = id.PopSegment(iotConnectorsKey); err != nil { return nil, err } diff --git a/internal/services/healthcare/parse/med_tech_service_fhir_destination.go b/internal/services/healthcare/parse/med_tech_service_fhir_destination.go index 42ee6d93b703..5bb4b29fb825 100644 --- a/internal/services/healthcare/parse/med_tech_service_fhir_destination.go +++ b/internal/services/healthcare/parse/med_tech_service_fhir_destination.go @@ -13,24 +13,24 @@ type MedTechServiceFhirDestinationId struct { SubscriptionId string ResourceGroup string WorkspaceName string - IotconnectorName string - FhirdestinationName string + IotConnectorName string + FhirDestinationName string } -func NewMedTechServiceFhirDestinationID(subscriptionId, resourceGroup, workspaceName, iotconnectorName, fhirdestinationName string) MedTechServiceFhirDestinationId { +func NewMedTechServiceFhirDestinationID(subscriptionId, resourceGroup, workspaceName, iotConnectorName, fhirDestinationName string) MedTechServiceFhirDestinationId { return MedTechServiceFhirDestinationId{ SubscriptionId: subscriptionId, ResourceGroup: resourceGroup, WorkspaceName: workspaceName, - IotconnectorName: iotconnectorName, - FhirdestinationName: fhirdestinationName, + IotConnectorName: iotConnectorName, + FhirDestinationName: fhirDestinationName, } } func (id MedTechServiceFhirDestinationId) String() string { segments := []string{ - fmt.Sprintf("Fhirdestination Name %q", id.FhirdestinationName), - fmt.Sprintf("Iotconnector Name %q", id.IotconnectorName), + fmt.Sprintf("Fhir Destination Name %q", id.FhirDestinationName), + fmt.Sprintf("Iot Connector Name %q", id.IotConnectorName), fmt.Sprintf("Workspace Name %q", id.WorkspaceName), fmt.Sprintf("Resource Group %q", id.ResourceGroup), } @@ -39,8 +39,8 @@ func (id MedTechServiceFhirDestinationId) String() string { } func (id MedTechServiceFhirDestinationId) ID() string { - fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.HealthcareApis/workspaces/%s/iotconnectors/%s/fhirdestinations/%s" - return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup, id.WorkspaceName, id.IotconnectorName, id.FhirdestinationName) + fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.HealthcareApis/workspaces/%s/iotConnectors/%s/fhirDestinations/%s" + return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup, id.WorkspaceName, id.IotConnectorName, id.FhirDestinationName) } // MedTechServiceFhirDestinationID parses a MedTechServiceFhirDestination ID into an MedTechServiceFhirDestinationId struct @@ -66,10 +66,78 @@ func MedTechServiceFhirDestinationID(input string) (*MedTechServiceFhirDestinati if resourceId.WorkspaceName, err = id.PopSegment("workspaces"); err != nil { return nil, err } - if resourceId.IotconnectorName, err = id.PopSegment("iotconnectors"); err != nil { + if resourceId.IotConnectorName, err = id.PopSegment("iotConnectors"); err != nil { return nil, err } - if resourceId.FhirdestinationName, err = id.PopSegment("fhirdestinations"); err != nil { + if resourceId.FhirDestinationName, err = id.PopSegment("fhirDestinations"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &resourceId, nil +} + +// MedTechServiceFhirDestinationIDInsensitively parses an MedTechServiceFhirDestination ID into an MedTechServiceFhirDestinationId struct, insensitively +// This should only be used to parse an ID for rewriting, the MedTechServiceFhirDestinationID +// method should be used instead for validation etc. +// +// Whilst this may seem strange, this enables Terraform have consistent casing +// which works around issues in Core, whilst handling broken API responses. +func MedTechServiceFhirDestinationIDInsensitively(input string) (*MedTechServiceFhirDestinationId, error) { + id, err := resourceids.ParseAzureResourceID(input) + if err != nil { + return nil, err + } + + resourceId := MedTechServiceFhirDestinationId{ + 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") + } + + // find the correct casing for the 'workspaces' segment + workspacesKey := "workspaces" + for key := range id.Path { + if strings.EqualFold(key, workspacesKey) { + workspacesKey = key + break + } + } + if resourceId.WorkspaceName, err = id.PopSegment(workspacesKey); err != nil { + return nil, err + } + + // find the correct casing for the 'iotConnectors' segment + iotConnectorsKey := "iotConnectors" + for key := range id.Path { + if strings.EqualFold(key, iotConnectorsKey) { + iotConnectorsKey = key + break + } + } + if resourceId.IotConnectorName, err = id.PopSegment(iotConnectorsKey); err != nil { + return nil, err + } + + // find the correct casing for the 'fhirDestinations' segment + fhirDestinationsKey := "fhirDestinations" + for key := range id.Path { + if strings.EqualFold(key, fhirDestinationsKey) { + fhirDestinationsKey = key + break + } + } + if resourceId.FhirDestinationName, err = id.PopSegment(fhirDestinationsKey); err != nil { return nil, err } diff --git a/internal/services/healthcare/parse/med_tech_service_fhir_destination_test.go b/internal/services/healthcare/parse/med_tech_service_fhir_destination_test.go index d45d68f77004..8fa520b3feca 100644 --- a/internal/services/healthcare/parse/med_tech_service_fhir_destination_test.go +++ b/internal/services/healthcare/parse/med_tech_service_fhir_destination_test.go @@ -12,7 +12,7 @@ var _ resourceids.Id = MedTechServiceFhirDestinationId{} func TestMedTechServiceFhirDestinationIDFormatter(t *testing.T) { actual := NewMedTechServiceFhirDestinationID("12345678-1234-9876-4563-123456789012", "group1", "workspace1", "iotconnector1", "destination1").ID() - expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotconnectors/iotconnector1/fhirdestinations/destination1" + expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotConnectors/iotconnector1/fhirDestinations/destination1" if actual != expected { t.Fatalf("Expected %q but got %q", expected, actual) } @@ -68,38 +68,38 @@ func TestMedTechServiceFhirDestinationID(t *testing.T) { }, { - // missing IotconnectorName + // missing IotConnectorName Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/", Error: true, }, { - // missing value for IotconnectorName - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotconnectors/", + // missing value for IotConnectorName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotConnectors/", Error: true, }, { - // missing FhirdestinationName - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotconnectors/iotconnector1/", + // missing FhirDestinationName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotConnectors/iotconnector1/", Error: true, }, { - // missing value for FhirdestinationName - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotconnectors/iotconnector1/fhirdestinations/", + // missing value for FhirDestinationName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotConnectors/iotconnector1/fhirDestinations/", Error: true, }, { // valid - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotconnectors/iotconnector1/fhirdestinations/destination1", + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotConnectors/iotconnector1/fhirDestinations/destination1", Expected: &MedTechServiceFhirDestinationId{ SubscriptionId: "12345678-1234-9876-4563-123456789012", ResourceGroup: "group1", WorkspaceName: "workspace1", - IotconnectorName: "iotconnector1", - FhirdestinationName: "destination1", + IotConnectorName: "iotconnector1", + FhirDestinationName: "destination1", }, }, @@ -134,11 +134,166 @@ func TestMedTechServiceFhirDestinationID(t *testing.T) { if actual.WorkspaceName != v.Expected.WorkspaceName { t.Fatalf("Expected %q but got %q for WorkspaceName", v.Expected.WorkspaceName, actual.WorkspaceName) } - if actual.IotconnectorName != v.Expected.IotconnectorName { - t.Fatalf("Expected %q but got %q for IotconnectorName", v.Expected.IotconnectorName, actual.IotconnectorName) + if actual.IotConnectorName != v.Expected.IotConnectorName { + t.Fatalf("Expected %q but got %q for IotConnectorName", v.Expected.IotConnectorName, actual.IotConnectorName) + } + if actual.FhirDestinationName != v.Expected.FhirDestinationName { + t.Fatalf("Expected %q but got %q for FhirDestinationName", v.Expected.FhirDestinationName, actual.FhirDestinationName) + } + } +} + +func TestMedTechServiceFhirDestinationIDInsensitively(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *MedTechServiceFhirDestinationId + }{ + + { + // 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 WorkspaceName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/", + Error: true, + }, + + { + // missing value for WorkspaceName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/", + Error: true, + }, + + { + // missing IotConnectorName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/", + Error: true, + }, + + { + // missing value for IotConnectorName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotConnectors/", + Error: true, + }, + + { + // missing FhirDestinationName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotConnectors/iotconnector1/", + Error: true, + }, + + { + // missing value for FhirDestinationName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotConnectors/iotconnector1/fhirDestinations/", + Error: true, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotConnectors/iotconnector1/fhirDestinations/destination1", + Expected: &MedTechServiceFhirDestinationId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "group1", + WorkspaceName: "workspace1", + IotConnectorName: "iotconnector1", + FhirDestinationName: "destination1", + }, + }, + + { + // lower-cased segment names + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotconnectors/iotconnector1/fhirdestinations/destination1", + Expected: &MedTechServiceFhirDestinationId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "group1", + WorkspaceName: "workspace1", + IotConnectorName: "iotconnector1", + FhirDestinationName: "destination1", + }, + }, + + { + // upper-cased segment names + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/WORKSPACES/workspace1/IOTCONNECTORS/iotconnector1/FHIRDESTINATIONS/destination1", + Expected: &MedTechServiceFhirDestinationId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "group1", + WorkspaceName: "workspace1", + IotConnectorName: "iotconnector1", + FhirDestinationName: "destination1", + }, + }, + + { + // mixed-cased segment names + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/WoRkSpAcEs/workspace1/IoTcOnNeCtOrS/iotconnector1/FhIrDeStInAtIoNs/destination1", + Expected: &MedTechServiceFhirDestinationId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "group1", + WorkspaceName: "workspace1", + IotConnectorName: "iotconnector1", + FhirDestinationName: "destination1", + }, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := MedTechServiceFhirDestinationIDInsensitively(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.WorkspaceName != v.Expected.WorkspaceName { + t.Fatalf("Expected %q but got %q for WorkspaceName", v.Expected.WorkspaceName, actual.WorkspaceName) + } + if actual.IotConnectorName != v.Expected.IotConnectorName { + t.Fatalf("Expected %q but got %q for IotConnectorName", v.Expected.IotConnectorName, actual.IotConnectorName) } - if actual.FhirdestinationName != v.Expected.FhirdestinationName { - t.Fatalf("Expected %q but got %q for FhirdestinationName", v.Expected.FhirdestinationName, actual.FhirdestinationName) + if actual.FhirDestinationName != v.Expected.FhirDestinationName { + t.Fatalf("Expected %q but got %q for FhirDestinationName", v.Expected.FhirDestinationName, actual.FhirDestinationName) } } } diff --git a/internal/services/healthcare/parse/med_tech_service_test.go b/internal/services/healthcare/parse/med_tech_service_test.go index d5e20d21f19d..94e5fd6f8cee 100644 --- a/internal/services/healthcare/parse/med_tech_service_test.go +++ b/internal/services/healthcare/parse/med_tech_service_test.go @@ -12,7 +12,7 @@ var _ resourceids.Id = MedTechServiceId{} func TestMedTechServiceIDFormatter(t *testing.T) { actual := NewMedTechServiceID("12345678-1234-9876-4563-123456789012", "group1", "workspace1", "iotconnector1").ID() - expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotconnectors/iotconnector1" + expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotConnectors/iotconnector1" if actual != expected { t.Fatalf("Expected %q but got %q", expected, actual) } @@ -68,25 +68,25 @@ func TestMedTechServiceID(t *testing.T) { }, { - // missing IotconnectorName + // missing IotConnectorName Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/", Error: true, }, { - // missing value for IotconnectorName - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotconnectors/", + // missing value for IotConnectorName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotConnectors/", Error: true, }, { // valid - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotconnectors/iotconnector1", + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotConnectors/iotconnector1", Expected: &MedTechServiceId{ SubscriptionId: "12345678-1234-9876-4563-123456789012", ResourceGroup: "group1", WorkspaceName: "workspace1", - IotconnectorName: "iotconnector1", + IotConnectorName: "iotconnector1", }, }, @@ -121,8 +121,144 @@ func TestMedTechServiceID(t *testing.T) { if actual.WorkspaceName != v.Expected.WorkspaceName { t.Fatalf("Expected %q but got %q for WorkspaceName", v.Expected.WorkspaceName, actual.WorkspaceName) } - if actual.IotconnectorName != v.Expected.IotconnectorName { - t.Fatalf("Expected %q but got %q for IotconnectorName", v.Expected.IotconnectorName, actual.IotconnectorName) + if actual.IotConnectorName != v.Expected.IotConnectorName { + t.Fatalf("Expected %q but got %q for IotConnectorName", v.Expected.IotConnectorName, actual.IotConnectorName) + } + } +} + +func TestMedTechServiceIDInsensitively(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *MedTechServiceId + }{ + + { + // 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 WorkspaceName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/", + Error: true, + }, + + { + // missing value for WorkspaceName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/", + Error: true, + }, + + { + // missing IotConnectorName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/", + Error: true, + }, + + { + // missing value for IotConnectorName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotConnectors/", + Error: true, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotConnectors/iotconnector1", + Expected: &MedTechServiceId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "group1", + WorkspaceName: "workspace1", + IotConnectorName: "iotconnector1", + }, + }, + + { + // lower-cased segment names + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotconnectors/iotconnector1", + Expected: &MedTechServiceId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "group1", + WorkspaceName: "workspace1", + IotConnectorName: "iotconnector1", + }, + }, + + { + // upper-cased segment names + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/WORKSPACES/workspace1/IOTCONNECTORS/iotconnector1", + Expected: &MedTechServiceId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "group1", + WorkspaceName: "workspace1", + IotConnectorName: "iotconnector1", + }, + }, + + { + // mixed-cased segment names + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/WoRkSpAcEs/workspace1/IoTcOnNeCtOrS/iotconnector1", + Expected: &MedTechServiceId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "group1", + WorkspaceName: "workspace1", + IotConnectorName: "iotconnector1", + }, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := MedTechServiceIDInsensitively(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.WorkspaceName != v.Expected.WorkspaceName { + t.Fatalf("Expected %q but got %q for WorkspaceName", v.Expected.WorkspaceName, actual.WorkspaceName) + } + if actual.IotConnectorName != v.Expected.IotConnectorName { + t.Fatalf("Expected %q but got %q for IotConnectorName", v.Expected.IotConnectorName, actual.IotConnectorName) } } } diff --git a/internal/services/healthcare/resourceids.go b/internal/services/healthcare/resourceids.go index 971de59a65c0..fc07ba81edc1 100644 --- a/internal/services/healthcare/resourceids.go +++ b/internal/services/healthcare/resourceids.go @@ -3,7 +3,7 @@ package healthcare //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=Service -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/services/service1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=Workspace -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=FhirService -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/fhirservices/service1 -//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=DicomService -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/dicomservices/service1 -//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=FhirService -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/fhirservices/service1 -//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=MedTechService -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotconnectors/iotconnector1 -//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=MedTechServiceFhirDestination -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotconnectors/iotconnector1/fhirdestinations/destination1 +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=DicomService -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/dicomServices/service1 -rewrite=true +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=FhirService -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/fhirServices/service1 -rewrite=true +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=MedTechService -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotConnectors/iotconnector1 -rewrite=true +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=MedTechServiceFhirDestination -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotConnectors/iotconnector1/fhirDestinations/destination1 -rewrite=true diff --git a/internal/services/healthcare/validate/dicom_service_id_test.go b/internal/services/healthcare/validate/dicom_service_id_test.go index 606a234bb022..728fb72755e9 100644 --- a/internal/services/healthcare/validate/dicom_service_id_test.go +++ b/internal/services/healthcare/validate/dicom_service_id_test.go @@ -60,13 +60,13 @@ func TestDicomServiceID(t *testing.T) { { // missing value for Name - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/dicomservices/", + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/dicomServices/", Valid: false, }, { // valid - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/dicomservices/service1", + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/dicomServices/service1", Valid: true, }, diff --git a/internal/services/healthcare/validate/fhir_service_id_test.go b/internal/services/healthcare/validate/fhir_service_id_test.go index 511a3576807e..b03f3cbe5833 100644 --- a/internal/services/healthcare/validate/fhir_service_id_test.go +++ b/internal/services/healthcare/validate/fhir_service_id_test.go @@ -60,13 +60,13 @@ func TestFhirServiceID(t *testing.T) { { // missing value for Name - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/fhirservices/", + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/fhirServices/", Valid: false, }, { // valid - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/fhirservices/service1", + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/fhirServices/service1", Valid: true, }, diff --git a/internal/services/healthcare/validate/med_tech_service_fhir_destination_id_test.go b/internal/services/healthcare/validate/med_tech_service_fhir_destination_id_test.go index ef031608f276..5cc4f3177847 100644 --- a/internal/services/healthcare/validate/med_tech_service_fhir_destination_id_test.go +++ b/internal/services/healthcare/validate/med_tech_service_fhir_destination_id_test.go @@ -53,32 +53,32 @@ func TestMedTechServiceFhirDestinationID(t *testing.T) { }, { - // missing IotconnectorName + // missing IotConnectorName Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/", Valid: false, }, { - // missing value for IotconnectorName - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotconnectors/", + // missing value for IotConnectorName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotConnectors/", Valid: false, }, { - // missing FhirdestinationName - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotconnectors/iotconnector1/", + // missing FhirDestinationName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotConnectors/iotconnector1/", Valid: false, }, { - // missing value for FhirdestinationName - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotconnectors/iotconnector1/fhirdestinations/", + // missing value for FhirDestinationName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotConnectors/iotconnector1/fhirDestinations/", Valid: false, }, { // valid - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotconnectors/iotconnector1/fhirdestinations/destination1", + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotConnectors/iotconnector1/fhirDestinations/destination1", Valid: true, }, diff --git a/internal/services/healthcare/validate/med_tech_service_id_test.go b/internal/services/healthcare/validate/med_tech_service_id_test.go index 0855f882e356..cd4ccbb6891d 100644 --- a/internal/services/healthcare/validate/med_tech_service_id_test.go +++ b/internal/services/healthcare/validate/med_tech_service_id_test.go @@ -53,20 +53,20 @@ func TestMedTechServiceID(t *testing.T) { }, { - // missing IotconnectorName + // missing IotConnectorName Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/", Valid: false, }, { - // missing value for IotconnectorName - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotconnectors/", + // missing value for IotConnectorName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotConnectors/", Valid: false, }, { // valid - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotconnectors/iotconnector1", + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotConnectors/iotconnector1", Valid: true, }, diff --git a/website/docs/r/healthcare_dicom.html.markdown b/website/docs/r/healthcare_dicom.html.markdown index 5a18d0a02610..e4cf2772745c 100644 --- a/website/docs/r/healthcare_dicom.html.markdown +++ b/website/docs/r/healthcare_dicom.html.markdown @@ -88,5 +88,5 @@ The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/l Healthcare DICOM Service can be imported using the resource`id`, e.g. ```shell -terraform import azurerm_healthcare_dicom_service.example /subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/dicomservices/service1 +terraform import azurerm_healthcare_dicom_service.example /subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/dicomServices/service1 ``` diff --git a/website/docs/r/healthcare_fhir_service.html.markdown b/website/docs/r/healthcare_fhir_service.html.markdown index 0cd3c9f19843..13a42d48848d 100644 --- a/website/docs/r/healthcare_fhir_service.html.markdown +++ b/website/docs/r/healthcare_fhir_service.html.markdown @@ -140,5 +140,5 @@ The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/l Healthcare FHIR Service can be imported using the resource`id`, e.g. ```shell -terraform import azurerm_healthcare_fhir_service.example /subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/fhirservices/service1 +terraform import azurerm_healthcare_fhir_service.example /subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/fhirServices/service1 ``` diff --git a/website/docs/r/healthcare_medtech_service.html.markdown b/website/docs/r/healthcare_medtech_service.html.markdown index 275fe479f76b..c0366939f43e 100644 --- a/website/docs/r/healthcare_medtech_service.html.markdown +++ b/website/docs/r/healthcare_medtech_service.html.markdown @@ -108,5 +108,5 @@ The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/d Healthcare Med Tech Service can be imported using the resource`id`, e.g. ```shell -terraform import azurerm_healthcare_medtech_service.example /subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotconnectors/iotconnector1 +terraform import azurerm_healthcare_medtech_service.example /subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotConnectors/iotconnector1 ``` diff --git a/website/docs/r/healthcare_medtech_service_fhir_destination.html.markdown b/website/docs/r/healthcare_medtech_service_fhir_destination.html.markdown index aedceaeed2ee..483d9418b852 100644 --- a/website/docs/r/healthcare_medtech_service_fhir_destination.html.markdown +++ b/website/docs/r/healthcare_medtech_service_fhir_destination.html.markdown @@ -82,5 +82,5 @@ The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/d Healthcare Med Tech Service Fhir Destination can be imported using the resource`id`, e.g. ```shell -terraform import azurerm_healthcare_medtech_service_fhir_destination.example /subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotconnectors/iotconnector1/fhirdestinations/destination1 +terraform import azurerm_healthcare_medtech_service_fhir_destination.example /subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/iotConnectors/iotconnector1/fhirDestinations/destination1 ```