diff --git a/internal/services/apimanagement/testdata/api_management_api2_test.pfx b/internal/services/apimanagement/testdata/api_management_api2_test.pfx index fb9d04295901..2df1ad7e2c8e 100644 Binary files a/internal/services/apimanagement/testdata/api_management_api2_test.pfx and b/internal/services/apimanagement/testdata/api_management_api2_test.pfx differ diff --git a/internal/services/apimanagement/testdata/api_management_api_operation_policy.xml b/internal/services/apimanagement/testdata/api_management_api_operation_policy.xml index 92705283cf73..e93ff69fc580 100644 --- a/internal/services/apimanagement/testdata/api_management_api_operation_policy.xml +++ b/internal/services/apimanagement/testdata/api_management_api_operation_policy.xml @@ -35,4 +35,4 @@ - \ No newline at end of file + diff --git a/internal/services/apimanagement/testdata/api_management_api_test.cer b/internal/services/apimanagement/testdata/api_management_api_test.cer index e2eb9a4dfdb9..64942a8aac7a 100755 --- a/internal/services/apimanagement/testdata/api_management_api_test.cer +++ b/internal/services/apimanagement/testdata/api_management_api_test.cer @@ -1,17 +1,17 @@ ------BEGIN CERTIFICATE----- -MIICsjCCAZoCCQCMdt7DvygPtDANBgkqhkiG9w0BAQsFADAbMRkwFwYDVQQDDBBh -cGkudGVycmFmb3JtLmlvMB4XDTE4MDcwNTEwMzMzMFoXDTI4MDcwMjEwMzMzMFow -GzEZMBcGA1UEAwwQYXBpLnRlcnJhZm9ybS5pbzCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBAKQW332Ol28CsidAheD1aL9Ul8JWnKLdaVxKZ3ssl5CXjPDO -mM7IXk0SgbQnUC8lIlPFZiDGbQ1sB6OTMun6ZZ4ipLp80dtl0roCLtCnDQOBGzCN -ArCYAoXRurjkXEY7tpD0wwtU72+37h3HQ4g0VS6VItJCqJ9QADV+HO2ZWuZTez70 -MhoL6OLfZP7HGYdJDKgfEVNF5XlbVzNAGkDIJFdhjNxyGGu5Nfsm1pfQhAyunkk7 -JVamjUg5IojRdo63IS9wwzMOdeGSAbBcsJfYeCfVg2kupR8q0TmZ+x93RmmOlbSi -66kEYxRzZ9YCQeHJmn1YfJ92BpCUiy9A6Z1iaKUCAwEAATANBgkqhkiG9w0BAQsF -AAOCAQEAJ7JhlecP7J48wI2QHTMbAMkkWBv/iWq1/QIF4ugH3Zb5PorOv+NfhQ0L -lWiw/SzN8Ae95vUixAGYHMSa28oumM5K1OsqKEkVIo1AoBH8nBz+VcTpRD/mHXot -AHPAZt9j5LqeHX+enR6RbINAf3jn+YU3MdVe0MsADdFASVDfjmQP2R7o9aJb/QqO -g3bZBWsiBDEISfyaH2+pgUM7wtwEoFWmEMlgjLK1MRBs1cDZXqnHaCd/rs+NmWV9 -naEu7x5fyQOk4HozkpweR+Jx1sBlTRsa49/qSHt/6ULKfO01/cTs4iF71ykXPbh3 -Kj9cI2uo9aYtXkxkhKrGyUpA7FJqWw== ------END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICsjCCAZoCCQCMdt7DvygPtDANBgkqhkiG9w0BAQsFADAbMRkwFwYDVQQDDBBh +cGkudGVycmFmb3JtLmlvMB4XDTE4MDcwNTEwMzMzMFoXDTI4MDcwMjEwMzMzMFow +GzEZMBcGA1UEAwwQYXBpLnRlcnJhZm9ybS5pbzCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAKQW332Ol28CsidAheD1aL9Ul8JWnKLdaVxKZ3ssl5CXjPDO +mM7IXk0SgbQnUC8lIlPFZiDGbQ1sB6OTMun6ZZ4ipLp80dtl0roCLtCnDQOBGzCN +ArCYAoXRurjkXEY7tpD0wwtU72+37h3HQ4g0VS6VItJCqJ9QADV+HO2ZWuZTez70 +MhoL6OLfZP7HGYdJDKgfEVNF5XlbVzNAGkDIJFdhjNxyGGu5Nfsm1pfQhAyunkk7 +JVamjUg5IojRdo63IS9wwzMOdeGSAbBcsJfYeCfVg2kupR8q0TmZ+x93RmmOlbSi +66kEYxRzZ9YCQeHJmn1YfJ92BpCUiy9A6Z1iaKUCAwEAATANBgkqhkiG9w0BAQsF +AAOCAQEAJ7JhlecP7J48wI2QHTMbAMkkWBv/iWq1/QIF4ugH3Zb5PorOv+NfhQ0L +lWiw/SzN8Ae95vUixAGYHMSa28oumM5K1OsqKEkVIo1AoBH8nBz+VcTpRD/mHXot +AHPAZt9j5LqeHX+enR6RbINAf3jn+YU3MdVe0MsADdFASVDfjmQP2R7o9aJb/QqO +g3bZBWsiBDEISfyaH2+pgUM7wtwEoFWmEMlgjLK1MRBs1cDZXqnHaCd/rs+NmWV9 +naEu7x5fyQOk4HozkpweR+Jx1sBlTRsa49/qSHt/6ULKfO01/cTs4iF71ykXPbh3 +Kj9cI2uo9aYtXkxkhKrGyUpA7FJqWw== +-----END CERTIFICATE----- diff --git a/internal/services/apimanagement/testdata/api_management_api_test.pfx b/internal/services/apimanagement/testdata/api_management_api_test.pfx index 6204aad9a6fd..2c80fd7e0259 100644 Binary files a/internal/services/apimanagement/testdata/api_management_api_test.pfx and b/internal/services/apimanagement/testdata/api_management_api_test.pfx differ diff --git a/internal/services/apimanagement/testdata/api_management_developer_portal_test.pfx b/internal/services/apimanagement/testdata/api_management_developer_portal_test.pfx index bbc36097180c..5332feae746b 100644 Binary files a/internal/services/apimanagement/testdata/api_management_developer_portal_test.pfx and b/internal/services/apimanagement/testdata/api_management_developer_portal_test.pfx differ diff --git a/internal/services/apimanagement/testdata/api_management_portal_test.pfx b/internal/services/apimanagement/testdata/api_management_portal_test.pfx index b4819dd3fd11..76b6159b23ef 100644 Binary files a/internal/services/apimanagement/testdata/api_management_portal_test.pfx and b/internal/services/apimanagement/testdata/api_management_portal_test.pfx differ diff --git a/internal/services/apimanagement/testdata/keyvaultcert.pfx b/internal/services/apimanagement/testdata/keyvaultcert.pfx index 28af8bf52e9a..d848a25b6cca 100644 Binary files a/internal/services/apimanagement/testdata/keyvaultcert.pfx and b/internal/services/apimanagement/testdata/keyvaultcert.pfx differ diff --git a/internal/services/appservice/testdata/host.json b/internal/services/appservice/testdata/host.json index 506376e88f95..c4ae96fe9fc3 100644 --- a/internal/services/appservice/testdata/host.json +++ b/internal/services/appservice/testdata/host.json @@ -4,4 +4,4 @@ "id": "Microsoft.Azure.Functions.ExtensionBundle", "version": "[2.*, 3.0.0)" } -} \ No newline at end of file +} diff --git a/internal/services/appservice/testdata/run.csx b/internal/services/appservice/testdata/run.csx index a439acd94de8..b3ba58f7c5e8 100644 --- a/internal/services/appservice/testdata/run.csx +++ b/internal/services/appservice/testdata/run.csx @@ -1,23 +1,23 @@ -#r "Newtonsoft.Json" - -using System.Net; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Primitives; -using Newtonsoft.Json; - -public static async Task Run(HttpRequest req, ILogger log) -{ - log.LogInformation("C# HTTP trigger function processed a request."); - - string name = req.Query["name"]; - - string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); - dynamic data = JsonConvert.DeserializeObject(requestBody); - name = name ?? data?.name; - - string responseMessage = string.IsNullOrEmpty(name) - ? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response." - : $"Hello, {name}. This HTTP triggered function executed successfully."; - - return new OkObjectResult(responseMessage); -} \ No newline at end of file +#r "Newtonsoft.Json" + +using System.Net; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Primitives; +using Newtonsoft.Json; + +public static async Task Run(HttpRequest req, ILogger log) +{ + log.LogInformation("C# HTTP trigger function processed a request."); + + string name = req.Query["name"]; + + string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); + dynamic data = JsonConvert.DeserializeObject(requestBody); + name = name ?? data?.name; + + string responseMessage = string.IsNullOrEmpty(name) + ? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response." + : $"Hello, {name}. This HTTP triggered function executed successfully."; + + return new OkObjectResult(responseMessage); +} diff --git a/internal/services/healthcare/client/client.go b/internal/services/healthcare/client/client.go index 07984b15646f..ac266932cd86 100644 --- a/internal/services/healthcare/client/client.go +++ b/internal/services/healthcare/client/client.go @@ -9,6 +9,7 @@ type Client struct { HealthcareServiceClient *healthcareapis.ServicesClient HealthcareWorkspaceClient *healthcareapis.WorkspacesClient HealthcareWorkspaceDicomServiceClient *healthcareapis.DicomServicesClient + HealthcareWorkspaceFhirServiceClient *healthcareapis.FhirServicesClient } func NewClient(o *common.ClientOptions) *Client { @@ -21,9 +22,13 @@ func NewClient(o *common.ClientOptions) *Client { HealthcareWorkspaceDicomServiceClient := healthcareapis.NewDicomServicesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&HealthcareWorkspaceDicomServiceClient.Client, o.ResourceManagerAuthorizer) + HealthcareWorkspaceFhirServiceClient := healthcareapis.NewFhirServicesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&HealthcareWorkspaceFhirServiceClient.Client, o.ResourceManagerAuthorizer) + return &Client{ HealthcareServiceClient: &HealthcareServiceClient, HealthcareWorkspaceClient: &HealthcareWorkspaceClient, HealthcareWorkspaceDicomServiceClient: &HealthcareWorkspaceDicomServiceClient, + HealthcareWorkspaceFhirServiceClient: &HealthcareWorkspaceFhirServiceClient, } } diff --git a/internal/services/healthcare/healthcare_fhir_data_source.go b/internal/services/healthcare/healthcare_fhir_data_source.go new file mode 100644 index 000000000000..1f321d3a0acf --- /dev/null +++ b/internal/services/healthcare/healthcare_fhir_data_source.go @@ -0,0 +1,189 @@ +package healthcare + +import ( + "fmt" + "time" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/commonschema" + "github.com/hashicorp/go-azure-helpers/resourcemanager/location" + "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "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" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/timeouts" + "github.com/hashicorp/terraform-provider-azurerm/utils" +) + +func dataSourceHealthcareApisFhirService() *pluginsdk.Resource { + return &pluginsdk.Resource{ + Read: dataSourceHealthcareApisFhirServiceRead, + + Timeouts: &pluginsdk.ResourceTimeout{ + Read: pluginsdk.DefaultTimeout(5 * time.Minute), + }, + + Schema: map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validate.FhirServiceName(), + }, + + "workspace_id": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validate.WorkspaceID, + }, + + "location": commonschema.LocationComputed(), + + "kind": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "access_policy_object_ids": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + + "authentication": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "authority": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "audience": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "smart_proxy_enabled": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + }, + }, + }, + + "identity": commonschema.SystemAssignedIdentityComputed(), + + "container_registry_login_server_url": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + + "cors": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "allowed_origins": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + + "allowed_headers": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + + "allowed_methods": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + + "max_age_in_seconds": { + Type: pluginsdk.TypeInt, + Computed: true, + }, + + "credentials_allowed": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + }, + }, + }, + + "configuration_export_storage_account_name": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "tags": commonschema.Tags(), + }, + } +} + +func dataSourceHealthcareApisFhirServiceRead(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).HealthCare.HealthcareWorkspaceFhirServiceClient + subscriptionId := meta.(*clients.Client).Account.SubscriptionId + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + workspaceId, err := parse.WorkspaceID(d.Get("workspace_id").(string)) + if err != nil { + return fmt.Errorf("parsing workspace id error: %+v", err) + } + id := parse.NewFhirServiceID(subscriptionId, workspaceId.ResourceGroup, workspaceId.Name, d.Get("name").(string)) + + resp, err := client.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.Name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + d.SetId("") + return nil + } + return fmt.Errorf("retrieving %s: %+v", id, err) + } + + d.SetId(id.ID()) + d.Set("name", id.Name) + + d.Set("workspace_id", workspaceId.ID()) + + if resp.Location != nil { + d.Set("location", location.NormalizeNilable(resp.Location)) + } + if err := d.Set("identity", flattenFhirManagedIdentity(resp.Identity)); err != nil { + return fmt.Errorf("setting `identity`: %+v", err) + } + if err := d.Set("kind", resp.Kind); err != nil { + return fmt.Errorf("setting `kind`: %+v", err) + } + + if props := resp.FhirServiceProperties; props != nil { + d.Set("access_policy_object_ids", flattenFhirAccessPolicy(props.AccessPolicies)) + d.Set("authentication", flattenFhirAuthentication(props.AuthenticationConfiguration)) + d.Set("cors", flattenFhirCorsConfiguration(props.CorsConfiguration)) + d.Set("container_registry_login_server_url", flattenFhirAcrLoginServer(props.AcrConfiguration)) + if props.ExportConfiguration != nil && props.ExportConfiguration.StorageAccountName != nil { + d.Set("configuration_export_storage_account_name", props.ExportConfiguration.StorageAccountName) + } + } + if err := tags.FlattenAndSet(d, resp.Tags); err != nil { + return err + } + + return nil +} diff --git a/internal/services/healthcare/healthcare_fhir_data_source_test.go b/internal/services/healthcare/healthcare_fhir_data_source_test.go new file mode 100644 index 000000000000..9a84a1b82a34 --- /dev/null +++ b/internal/services/healthcare/healthcare_fhir_data_source_test.go @@ -0,0 +1,35 @@ +package healthcare_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" +) + +type HealthCareFhirServiceDataSource struct{} + +func TestAccHealthCareFhirServiceDataSource_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_healthcare_fhir_service", "test") + r := HealthCareFhirServiceDataSource{} + + data.DataSourceTest(t, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).Key("name").Exists()), + }, + }) +} + +func (HealthCareFhirServiceDataSource) basic(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +data "azurerm_healthcare_fhir_service" "test" { + name = azurerm_healthcare_fhir_service.test.name + workspace_id = azurerm_healthcare_fhir_service.test.workspace_id +} +`, HealthcareApiFhirServiceResource{}.basic(data)) +} diff --git a/internal/services/healthcare/healthcare_fhir_resource.go b/internal/services/healthcare/healthcare_fhir_resource.go new file mode 100644 index 000000000000..dd0d6e96d3f0 --- /dev/null +++ b/internal/services/healthcare/healthcare_fhir_resource.go @@ -0,0 +1,641 @@ +package healthcare + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/Azure/azure-sdk-for-go/services/healthcareapis/mgmt/2021-11-01/healthcareapis" + "github.com/hashicorp/go-azure-helpers/lang/response" + "github.com/hashicorp/go-azure-helpers/resourcemanager/commonschema" + "github.com/hashicorp/go-azure-helpers/resourcemanager/identity" + "github.com/hashicorp/go-azure-helpers/resourcemanager/location" + "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/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/healthcare/validate" + "github.com/hashicorp/terraform-provider-azurerm/internal/tags" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" + "github.com/hashicorp/terraform-provider-azurerm/internal/timeouts" + "github.com/hashicorp/terraform-provider-azurerm/utils" +) + +func resourceHealthcareApisFhirService() *pluginsdk.Resource { + return &pluginsdk.Resource{ + Create: resourceHealthcareApisFhirServiceCreate, + Read: resourceHealthcareApisFhirServiceRead, + Update: resourceHealthcareApisFhirServiceUpdate, + Delete: resourceHealthcareApisFhirServiceDelete, + + Timeouts: &pluginsdk.ResourceTimeout{ + Create: pluginsdk.DefaultTimeout(90 * time.Minute), + Read: pluginsdk.DefaultTimeout(5 * time.Minute), + Update: pluginsdk.DefaultTimeout(90 * time.Minute), + Delete: pluginsdk.DefaultTimeout(30 * time.Minute), + }, + + Importer: pluginsdk.ImporterValidatingResourceId(func(id string) error { + _, err := parse.FhirServiceID(id) + return err + }), + + Schema: map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.FhirServiceName(), + }, + + "resource_group_name": commonschema.ResourceGroupName(), + + "workspace_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.WorkspaceID, + }, + + "location": commonschema.Location(), + + "kind": { + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + Default: string(healthcareapis.KindFhirR4), + ValidateFunc: validation.StringInSlice([]string{ + string(healthcareapis.KindFhirR4), + string(healthcareapis.KindFhirStu3), + }, false), + }, + + "access_policy_object_ids": { + Type: pluginsdk.TypeSet, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: validation.IsUUID, + }, + }, + + "authentication": { + Type: pluginsdk.TypeList, + Required: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "authority": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "audience": { + Type: pluginsdk.TypeString, + Required: true, + }, + + "smart_proxy_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + }, + }, + }, + }, + + "identity": commonschema.SystemAssignedIdentityOptional(), + + // 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, + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + + "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, + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + + "allowed_headers": { + Type: pluginsdk.TypeSet, + Required: true, + MaxItems: 64, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + + "allowed_methods": { + Type: pluginsdk.TypeSet, + Required: true, + MaxItems: 64, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: validation.StringInSlice([]string{ + "DELETE", + "GET", + "HEAD", + "MERGE", + "POST", + "OPTIONS", + "PUT", + }, false), + }, + }, + + "max_age_in_seconds": { + Type: pluginsdk.TypeInt, + Optional: true, + ValidateFunc: validation.IntBetween(0, 2000000000), + }, + + "credentials_allowed": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + }, + }, + }, + }, + + "configuration_export_storage_account_name": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "tags": commonschema.Tags(), + }, + } + +} + +func resourceHealthcareApisFhirServiceCreate(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).HealthCare.HealthcareWorkspaceFhirServiceClient + ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) + defer cancel() + log.Printf("[INFO] preparing arguments for AzureRM Healthcare Fhir Service creation.") + + workspace, err := parse.WorkspaceID(d.Get("workspace_id").(string)) + if err != nil { + return err + } + fhirServiceId := parse.NewFhirServiceID(workspace.SubscriptionId, workspace.ResourceGroup, workspace.Name, d.Get("name").(string)) + + if d.IsNewResource() { + existing, err := client.Get(ctx, fhirServiceId.ResourceGroup, fhirServiceId.WorkspaceName, fhirServiceId.Name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("checking for presence of existing %s: %+v", fhirServiceId, err) + } + } + + if !utils.ResponseWasNotFound(existing.Response) { + return tf.ImportAsExistsError("azurerm_healthcare_fhir_service", fhirServiceId.ID()) + } + } + + identity, err := expandFhirManagedIdentity(d.Get("identity").([]interface{})) + if err != nil { + return fmt.Errorf("expanding `identity`: %+v", err) + } + + parameters := healthcareapis.FhirService{ + Identity: identity, + Location: utils.String(azure.NormalizeLocation(d.Get("location").(string))), + Kind: healthcareapis.FhirServiceKind(d.Get("kind").(string)), + Tags: tags.Expand(d.Get("tags").(map[string]interface{})), + FhirServiceProperties: &healthcareapis.FhirServiceProperties{ + AuthenticationConfiguration: expandFhirAuthentication(d.Get("authentication").([]interface{})), + CorsConfiguration: expandFhirCorsConfiguration(d.Get("cors").([]interface{})), + }, + } + + accessPolicyObjectIds, hasValues := d.GetOk("access_policy_object_ids") + if hasValues { + parameters.FhirServiceProperties.AccessPolicies = expandAccessPolicy(accessPolicyObjectIds.(*pluginsdk.Set).List()) + } + + storageAcc, hasValues := d.GetOk("configuration_export_storage_account_name") + if hasValues { + parameters.FhirServiceProperties.ExportConfiguration = &healthcareapis.FhirServiceExportConfiguration{ + StorageAccountName: utils.String(storageAcc.(string)), + } + } + + acrConfig, hasValues := d.GetOk("container_registry_login_server_url") + if hasValues { + result := expandFhirAcrLoginServer(acrConfig.(*pluginsdk.Set).List()) + parameters.FhirServiceProperties.AcrConfiguration = result + } + + future, err := client.CreateOrUpdate(ctx, fhirServiceId.ResourceGroup, fhirServiceId.WorkspaceName, fhirServiceId.Name, parameters) + if err != nil { + return fmt.Errorf("creating %s: %+v", fhirServiceId, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for creation of %s: %+v", fhirServiceId, err) + } + stateConf := &pluginsdk.StateChangeConf{ + ContinuousTargetOccurence: 12, + Delay: 60 * time.Second, + MinTimeout: 10 * time.Second, + Pending: []string{"Creating", "Updating", "Verifying"}, + Target: []string{"Succeeded"}, + Refresh: fhirServiceCreateStateRefreshFunc(ctx, client, fhirServiceId), + Timeout: d.Timeout(pluginsdk.TimeoutUpdate), + } + if _, err := stateConf.WaitForStateContext(ctx); err != nil { + return fmt.Errorf("waiting for Fhir Service %s to settle down: %+v", fhirServiceId, err) + } + + d.SetId(fhirServiceId.ID()) + return resourceHealthcareApisFhirServiceRead(d, meta) +} + +func resourceHealthcareApisFhirServiceRead(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).HealthCare.HealthcareWorkspaceFhirServiceClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.FhirServiceID(d.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.Name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + d.SetId("") + return nil + } + return fmt.Errorf("retrieving %s: %+v", *id, err) + } + d.Set("name", id.Name) + d.Set("resource_group_name", id.ResourceGroup) + + workSpaceId := parse.NewWorkspaceID(id.SubscriptionId, id.ResourceGroup, id.WorkspaceName) + d.Set("workspace_id", workSpaceId.ID()) + + if resp.Location != nil { + d.Set("location", location.NormalizeNilable(resp.Location)) + } + + if err := d.Set("identity", flattenFhirManagedIdentity(resp.Identity)); err != nil { + return fmt.Errorf("setting `identity`: %+v", err) + } + d.Set("kind", resp.Kind) + + if props := resp.FhirServiceProperties; props != nil { + d.Set("access_policy_object_ids", flattenFhirAccessPolicy(props.AccessPolicies)) + d.Set("authentication", flattenFhirAuthentication(props.AuthenticationConfiguration)) + d.Set("cors", flattenFhirCorsConfiguration(props.CorsConfiguration)) + d.Set("container_registry_login_server_url", flattenFhirAcrLoginServer(props.AcrConfiguration)) + if props.ExportConfiguration != nil && props.ExportConfiguration.StorageAccountName != nil { + d.Set("configuration_export_storage_account_name", props.ExportConfiguration.StorageAccountName) + } + + if err := tags.FlattenAndSet(d, resp.Tags); err != nil { + return err + } + } + + return nil +} + +func resourceHealthcareApisFhirServiceUpdate(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).HealthCare.HealthcareWorkspaceFhirServiceClient + ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) + defer cancel() + + workspace, err := parse.WorkspaceID(d.Get("workspace_id").(string)) + if err != nil { + return err + } + fhirServiceId := parse.NewFhirServiceID(workspace.SubscriptionId, workspace.ResourceGroup, workspace.Name, d.Get("name").(string)) + + identity, err := expandFhirManagedIdentity(d.Get("identity").([]interface{})) + if err != nil { + return fmt.Errorf("expanding `identity`: %+v", err) + } + + parameters := healthcareapis.FhirService{ + Identity: identity, + Location: utils.String(azure.NormalizeLocation(d.Get("location").(string))), + Kind: healthcareapis.FhirServiceKind(d.Get("kind").(string)), + Tags: tags.Expand(d.Get("tags").(map[string]interface{})), + FhirServiceProperties: &healthcareapis.FhirServiceProperties{ + AuthenticationConfiguration: expandFhirAuthentication(d.Get("authentication").([]interface{})), + CorsConfiguration: expandFhirCorsConfiguration(d.Get("cors").([]interface{})), + AccessPolicies: expandAccessPolicy(d.Get("access_policy_object_ids").(*pluginsdk.Set).List()), + }, + } + + storageAcc, hasValues := d.GetOk("configuration_export_storage_account_name") + if hasValues { + parameters.FhirServiceProperties.ExportConfiguration = &healthcareapis.FhirServiceExportConfiguration{ + StorageAccountName: utils.String(storageAcc.(string)), + } + } + + acrConfig, hasValues := d.GetOk("container_registry_login_server_url") + if hasValues { + result := expandFhirAcrLoginServer(acrConfig.(*pluginsdk.Set).List()) + parameters.FhirServiceProperties.AcrConfiguration = result + } + + future, err := client.CreateOrUpdate(ctx, fhirServiceId.ResourceGroup, fhirServiceId.WorkspaceName, fhirServiceId.Name, parameters) + if err != nil { + return fmt.Errorf("updating %s: %+v", fhirServiceId, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for update of %s: %+v", fhirServiceId, err) + } + + d.SetId(fhirServiceId.ID()) + return resourceHealthcareApisFhirServiceRead(d, meta) +} + +func resourceHealthcareApisFhirServiceDelete(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).HealthCare.HealthcareWorkspaceFhirServiceClient + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.FhirServiceID(d.Id()) + if err != nil { + return err + } + + future, err := client.Delete(ctx, id.ResourceGroup, id.Name, id.WorkspaceName) + if err != nil { + if response.WasNotFound(future.Response()) { + return nil + } + return fmt.Errorf("deleting %s: %+v", *id, err) + } + + log.Printf("[DEBUG] Waiting for %s to be deleted..", id) + stateConf := &pluginsdk.StateChangeConf{ + Pending: []string{"Pending"}, + Target: []string{"Deleted"}, + Refresh: fhirServiceStateStatusCodeRefreshFunc(ctx, client, *id), + Timeout: d.Timeout(pluginsdk.TimeoutDelete), + ContinuousTargetOccurence: 3, + PollInterval: 10 * time.Second, + } + + if _, err := stateConf.WaitForStateContext(ctx); err != nil { + return fmt.Errorf("waiting for %s to be deleted: %+v", id, err) + } + return nil +} + +func fhirServiceStateStatusCodeRefreshFunc(ctx context.Context, client *healthcareapis.FhirServicesClient, id parse.FhirServiceId) pluginsdk.StateRefreshFunc { + return func() (interface{}, string, error) { + res, err := client.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.Name) + + if err != nil { + if utils.ResponseWasNotFound(res.Response) { + return res, "Deleted", nil + } + return nil, "Error", fmt.Errorf("polling for the status of %s: %+v", id, err) + } + + return res, "Pending", nil + } +} + +func expandFhirManagedIdentity(input []interface{}) (*healthcareapis.ServiceManagedIdentityIdentity, error) { + expanded, err := identity.ExpandSystemAssigned(input) + if err != nil { + return nil, err + } + + return &healthcareapis.ServiceManagedIdentityIdentity{ + Type: healthcareapis.ServiceManagedIdentityType(string(expanded.Type)), + }, nil +} + +func flattenFhirManagedIdentity(input *healthcareapis.ServiceManagedIdentityIdentity) []interface{} { + var transition *identity.SystemAssigned + + if input != nil { + transition = &identity.SystemAssigned{ + Type: identity.Type(string(input.Type)), + } + if input.PrincipalID != nil { + principalID := *input.PrincipalID + transition.PrincipalId = principalID.String() + } + if input.TenantID != nil { + tenantID := *input.TenantID + transition.TenantId = tenantID.String() + } + } + + return identity.FlattenSystemAssigned(transition) +} + +func expandFhirAuthentication(input []interface{}) *healthcareapis.FhirServiceAuthenticationConfiguration { + authConfig := input[0].(map[string]interface{}) + authority := authConfig["authority"].(string) + audience := authConfig["audience"].(string) + smartProxyEnabled := authConfig["smart_proxy_enabled"].(bool) + + auth := &healthcareapis.FhirServiceAuthenticationConfiguration{ + Authority: utils.String(authority), + Audience: utils.String(audience), + SmartProxyEnabled: utils.Bool(smartProxyEnabled), + } + + return auth +} +func expandAccessPolicy(input []interface{}) *[]healthcareapis.FhirServiceAccessPolicyEntry { + if len(input) == 0 { + return nil + } + + accessPolicySet := make([]healthcareapis.FhirServiceAccessPolicyEntry, 0) + + for _, objectId := range input { + accessPolicyObjectId := healthcareapis.FhirServiceAccessPolicyEntry{ + ObjectID: utils.String(objectId.(string)), + } + accessPolicySet = append(accessPolicySet, accessPolicyObjectId) + } + + return &accessPolicySet +} + +func expandFhirCorsConfiguration(input []interface{}) *healthcareapis.FhirServiceCorsConfiguration { + if len(input) == 0 { + return &healthcareapis.FhirServiceCorsConfiguration{ + Origins: &[]string{}, + Headers: &[]string{}, + Methods: &[]string{}, + AllowCredentials: utils.Bool(false), + } + } + + block := input[0].(map[string]interface{}) + + allowedOrigins := *utils.ExpandStringSlice(block["allowed_origins"].(*pluginsdk.Set).List()) + allowedHeaders := *utils.ExpandStringSlice(block["allowed_headers"].(*pluginsdk.Set).List()) + allowedMethods := *utils.ExpandStringSlice(block["allowed_methods"].(*pluginsdk.Set).List()) + allowCredentials := block["credentials_allowed"].(bool) + + cors := &healthcareapis.FhirServiceCorsConfiguration{ + Origins: &allowedOrigins, + Headers: &allowedHeaders, + Methods: &allowedMethods, + AllowCredentials: &allowCredentials, + } + + if v, ok := block["max_age_in_seconds"]; ok { + maxAgeInSeconds := int32(v.(int)) + cors.MaxAge = &maxAgeInSeconds + } + + return cors +} + +func expandFhirAcrLoginServer(input []interface{}) *healthcareapis.FhirServiceAcrConfiguration { + acrLoginServers := make([]string, 0) + + if len(input) == 0 { + return &healthcareapis.FhirServiceAcrConfiguration{ + LoginServers: &acrLoginServers, + } + } + + for _, item := range input { + acrLoginServers = append(acrLoginServers, item.(string)) + } + return &healthcareapis.FhirServiceAcrConfiguration{ + LoginServers: &acrLoginServers, + } +} + +func flattenFhirAcrLoginServer(acrLoginServer *healthcareapis.FhirServiceAcrConfiguration) []string { + result := make([]string, 0) + if acrLoginServer == nil { + return result + } + + if loginServer := acrLoginServer.LoginServers; loginServer != nil { + result = append(result, *loginServer...) + } + return result +} + +func flattenFhirAccessPolicy(policies *[]healthcareapis.FhirServiceAccessPolicyEntry) []string { + result := make([]string, 0) + + if policies == nil { + return result + } + + for _, policy := range *policies { + if objectId := policy.ObjectID; objectId != nil { + result = append(result, *objectId) + } + } + return result +} + +func flattenFhirCorsConfiguration(corsConfig *healthcareapis.FhirServiceCorsConfiguration) []interface{} { + if corsConfig == nil { + return []interface{}{} + } + + if corsConfig.Origins != nil && len(*corsConfig.Origins) == 0 && + corsConfig.Methods != nil && len(*corsConfig.Methods) == 0 && + corsConfig.Headers != nil && len(*corsConfig.Headers) == 0 && + corsConfig.AllowCredentials != nil && !*corsConfig.AllowCredentials { + return []interface{}{} + } + + var maxAge int + if corsConfig.MaxAge != nil { + maxAge = int(*corsConfig.MaxAge) + } + + allowCredentials := false + if corsConfig.AllowCredentials != nil { + allowCredentials = *corsConfig.AllowCredentials + } + + return []interface{}{ + map[string]interface{}{ + "credentials_allowed": allowCredentials, + "allowed_headers": utils.FlattenStringSlice(corsConfig.Headers), + "allowed_methods": utils.FlattenStringSlice(corsConfig.Methods), + "allowed_origins": utils.FlattenStringSlice(corsConfig.Origins), + "max_age_in_seconds": maxAge, + }, + } +} + +func flattenFhirAuthentication(authConfig *healthcareapis.FhirServiceAuthenticationConfiguration) []interface{} { + if authConfig == nil { + return []interface{}{} + } + + authority := "" + if authConfig.Authority != nil { + authority = *authConfig.Authority + } + + audience := "" + if authConfig.Audience != nil { + audience = *authConfig.Audience + } + + smartProxyEnabled := false + if authConfig.SmartProxyEnabled != nil { + smartProxyEnabled = *authConfig.SmartProxyEnabled + } + + return []interface{}{ + map[string]interface{}{ + "audience": audience, + "authority": authority, + "smart_proxy_enabled": smartProxyEnabled, + }, + } +} + +func fhirServiceCreateStateRefreshFunc(ctx context.Context, client *healthcareapis.FhirServicesClient, fhirServiceId parse.FhirServiceId) pluginsdk.StateRefreshFunc { + return func() (interface{}, string, error) { + resp, err := client.Get(ctx, fhirServiceId.ResourceGroup, fhirServiceId.WorkspaceName, fhirServiceId.Name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return nil, "", fmt.Errorf("unable to retrieve iot connector %q: %+v", fhirServiceId, err) + } + return nil, "Error", fmt.Errorf("polling for the status of %s: %+v", fhirServiceId, err) + } + + return resp, string(resp.ProvisioningState), nil + } +} diff --git a/internal/services/healthcare/healthcare_fhir_resource_test.go b/internal/services/healthcare/healthcare_fhir_resource_test.go new file mode 100644 index 000000000000..ec1ce504e99f --- /dev/null +++ b/internal/services/healthcare/healthcare_fhir_resource_test.go @@ -0,0 +1,432 @@ +package healthcare_test + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" + "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/healthcare/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/utils" +) + +type HealthcareApiFhirServiceResource struct{} + +func TestAccHealthcareApiFhirService_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_healthcare_fhir_service", "test") + r := HealthcareApiFhirServiceResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccHealthcareApiFhirService_complete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_healthcare_fhir_service", "test") + r := HealthcareApiFhirServiceResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.complete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccHealthcareApiFhirService_updateIdentity(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_healthcare_fhir_service", "test") + r := HealthcareApiFhirServiceResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.updateIdentity(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccHealthcareApiFhirService_updateAcrLoginServer(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_healthcare_fhir_service", "test") + r := HealthcareApiFhirServiceResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.complete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.updateAcrLoginServer(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.complete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccHealthcareApiFhirService_updateCors(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_healthcare_fhir_service", "test") + r := HealthcareApiFhirServiceResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.complete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.updateCors(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.complete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccHealthcareApiFhirService_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_healthcare_fhir_service", "test") + r := HealthcareApiFhirServiceResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.RequiresImportErrorStep(r.requiresImport), + }) +} + +func (HealthcareApiFhirServiceResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { + id, err := parse.FhirServiceID(state.ID) + if err != nil { + return nil, err + } + resp, err := clients.HealthCare.HealthcareWorkspaceFhirServiceClient.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.Name) + if err != nil { + return nil, fmt.Errorf("retrieving Healthcare api fhir service %s: %+v", *id, err) + } + return utils.Bool(resp.FhirServiceProperties != nil), nil +} + +func (r HealthcareApiFhirServiceResource) basic(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_healthcare_fhir_service" "test" { + name = "fhir%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + workspace_id = azurerm_healthcare_workspace.test.id + kind = "fhir-R4" + + authentication { + authority = "https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47" + audience = "https://acctestfhir.fhir.azurehealthcareapis.com" + } +} +`, r.template(data), data.RandomInteger) +} + +func (r HealthcareApiFhirServiceResource) updateIdentity(data acceptance.TestData) string { + return fmt.Sprintf(` +%s +resource "azurerm_healthcare_fhir_service" "test" { + name = "fhir%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + workspace_id = azurerm_healthcare_workspace.test.id + kind = "fhir-R4" + + authentication { + authority = "https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47" + audience = "https://acctestfhir.fhir.azurehealthcareapis.com" + } + + identity { + type = "SystemAssigned" + } +} +`, r.template(data), data.RandomInteger) +} + +func (r HealthcareApiFhirServiceResource) requiresImport(data acceptance.TestData) string { + return fmt.Sprintf(` +%s +resource "azurerm_healthcare_fhir_service" "import" { + name = azurerm_healthcare_fhir_service.test.name + location = azurerm_healthcare_fhir_service.test.location + resource_group_name = azurerm_healthcare_fhir_service.test.resource_group_name + workspace_id = azurerm_healthcare_fhir_service.test.workspace_id + kind = azurerm_healthcare_fhir_service.test.kind + + authentication { + authority = azurerm_healthcare_fhir_service.test.authentication[0].authority + audience = azurerm_healthcare_fhir_service.test.authentication[0].audience + } +} +`, r.basic(data)) +} + +func (r HealthcareApiFhirServiceResource) complete(data acceptance.TestData) string { + return fmt.Sprintf(` +data "azurerm_client_config" "current" { +} +%s + +resource "azurerm_container_registry" "test" { + name = "acc%d" + resource_group_name = azurerm_resource_group.test.name + location = "%s" + sku = "Premium" + admin_enabled = false + + georeplications { + location = "%s" + zone_redundancy_enabled = true + tags = {} + } +} + +resource "azurerm_storage_account" "test" { + name = "acc%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "GRS" + + tags = { + environment = "staging" + } +} + +resource "azurerm_healthcare_fhir_service" "test" { + name = "fhir%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + workspace_id = azurerm_healthcare_workspace.test.id + kind = "fhir-R4" + + authentication { + authority = "https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47" + audience = "https://acctestfhir.fhir.azurehealthcareapis.com" + } + + access_policy_object_ids = [ + data.azurerm_client_config.current.object_id + ] + + identity { + type = "SystemAssigned" + } + + container_registry_login_server_url = [azurerm_container_registry.test.login_server] + + cors { + allowed_origins = ["https://acctest.com:123", "https://acctest1.com:3389"] + allowed_headers = ["*"] + allowed_methods = ["GET", "DELETE", "PUT"] + max_age_in_seconds = 3600 + credentials_allowed = true + } + + configuration_export_storage_account_name = azurerm_storage_account.test.name +} +`, r.template(data), data.RandomInteger, data.Locations.Primary, data.Locations.Secondary, data.RandomInteger, data.RandomInteger) +} + +func (r HealthcareApiFhirServiceResource) updateAcrLoginServer(data acceptance.TestData) string { + return fmt.Sprintf(` +data "azurerm_client_config" "current" { +} +%s + +resource "azurerm_container_registry" "test" { + name = "acc%d" + resource_group_name = azurerm_resource_group.test.name + location = "%s" + sku = "Premium" + admin_enabled = false + + georeplications { + location = "%s" + zone_redundancy_enabled = true + tags = {} + } +} + +resource "azurerm_storage_account" "test" { + name = "acc%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "GRS" + + tags = { + environment = "staging" + } +} + +resource "azurerm_healthcare_fhir_service" "test" { + name = "fhir%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + workspace_id = azurerm_healthcare_workspace.test.id + kind = "fhir-R4" + + authentication { + authority = "https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47" + audience = "https://acctestfhir.fhir.azurehealthcareapis.com" + } + + access_policy_object_ids = [ + data.azurerm_client_config.current.object_id + ] + + identity { + type = "SystemAssigned" + } + + container_registry_login_server_url = [] + + cors { + allowed_origins = ["https://acctest.com:123", "https://acctest1.com:3389"] + allowed_headers = ["*"] + allowed_methods = ["GET", "DELETE", "PUT"] + max_age_in_seconds = 3600 + credentials_allowed = true + } + + configuration_export_storage_account_name = azurerm_storage_account.test.name +} +`, r.template(data), data.RandomInteger, data.Locations.Primary, data.Locations.Secondary, data.RandomInteger, data.RandomInteger) +} + +func (r HealthcareApiFhirServiceResource) updateCors(data acceptance.TestData) string { + return fmt.Sprintf(` +data "azurerm_client_config" "current" { +} +%s + +resource "azurerm_container_registry" "test" { + name = "acc%d" + resource_group_name = azurerm_resource_group.test.name + location = "%s" + sku = "Premium" + admin_enabled = false + + georeplications { + location = "%s" + zone_redundancy_enabled = true + tags = {} + } +} + +resource "azurerm_storage_account" "test" { + name = "acc%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "GRS" + + tags = { + environment = "staging" + } +} + +resource "azurerm_healthcare_fhir_service" "test" { + name = "fhir%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + workspace_id = azurerm_healthcare_workspace.test.id + kind = "fhir-R4" + + authentication { + authority = "https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47" + audience = "https://acctestfhir.fhir.azurehealthcareapis.com" + } + + access_policy_object_ids = [ + data.azurerm_client_config.current.object_id + ] + + identity { + type = "SystemAssigned" + } + + container_registry_login_server_url = [azurerm_container_registry.test.login_server] + + cors { + allowed_origins = ["https://acctest.com:123"] + allowed_headers = ["*"] + allowed_methods = ["GET", "DELETE"] + max_age_in_seconds = 0 + credentials_allowed = false + } + + configuration_export_storage_account_name = azurerm_storage_account.test.name +} +`, r.template(data), data.RandomInteger, data.Locations.Primary, data.Locations.Secondary, data.RandomInteger, data.RandomInteger) +} + +func (HealthcareApiFhirServiceResource) template(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-healthcareapi-%d" + location = "%s" +} + +resource "azurerm_healthcare_workspace" "test" { + name = "acc%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) +} diff --git a/internal/services/healthcare/parse/fhir_service.go b/internal/services/healthcare/parse/fhir_service.go new file mode 100644 index 000000000000..b28fa20a9244 --- /dev/null +++ b/internal/services/healthcare/parse/fhir_service.go @@ -0,0 +1,75 @@ +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 FhirServiceId struct { + SubscriptionId string + ResourceGroup string + WorkspaceName string + Name string +} + +func NewFhirServiceID(subscriptionId, resourceGroup, workspaceName, name string) FhirServiceId { + return FhirServiceId{ + SubscriptionId: subscriptionId, + ResourceGroup: resourceGroup, + WorkspaceName: workspaceName, + Name: name, + } +} + +func (id FhirServiceId) String() string { + segments := []string{ + fmt.Sprintf("Name %q", id.Name), + fmt.Sprintf("Workspace Name %q", id.WorkspaceName), + fmt.Sprintf("Resource Group %q", id.ResourceGroup), + } + segmentsStr := strings.Join(segments, " / ") + return fmt.Sprintf("%s: (%s)", "Fhir Service", segmentsStr) +} + +func (id FhirServiceId) ID() string { + fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.HealthcareApis/workspaces/%s/fhirservices/%s" + return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup, id.WorkspaceName, id.Name) +} + +// FhirServiceID parses a FhirService ID into an FhirServiceId struct +func FhirServiceID(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") + } + + if resourceId.WorkspaceName, err = id.PopSegment("workspaces"); err != nil { + return nil, err + } + 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 +} diff --git a/internal/services/healthcare/parse/fhir_service_test.go b/internal/services/healthcare/parse/fhir_service_test.go new file mode 100644 index 000000000000..45e3c7919c90 --- /dev/null +++ b/internal/services/healthcare/parse/fhir_service_test.go @@ -0,0 +1,128 @@ +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 = 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" + if actual != expected { + t.Fatalf("Expected %q but got %q", expected, actual) + } +} + +func TestFhirServiceID(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", + }, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/GROUP1/PROVIDERS/MICROSOFT.HEALTHCAREAPIS/WORKSPACES/WORKSPACE1/FHIRSERVICES/SERVICE1", + Error: true, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := FhirServiceID(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/registration.go b/internal/services/healthcare/registration.go index 632233c1fb59..31cb3f78c017 100644 --- a/internal/services/healthcare/registration.go +++ b/internal/services/healthcare/registration.go @@ -31,6 +31,7 @@ func (r Registration) SupportedDataSources() map[string]*pluginsdk.Resource { "azurerm_healthcare_service": dataSourceHealthcareService(), "azurerm_healthcare_workspace": dataSourceHealthcareWorkspace(), "azurerm_healthcare_dicom_service": dataSourceHealthcareDicomService(), + "azurerm_healthcare_fhir_service": dataSourceHealthcareApisFhirService(), } } @@ -40,5 +41,6 @@ func (r Registration) SupportedResources() map[string]*pluginsdk.Resource { "azurerm_healthcare_service": resourceHealthcareService(), "azurerm_healthcare_workspace": resourceHealthcareApisWorkspace(), "azurerm_healthcare_dicom_service": resourceHealthcareApisDicomService(), + "azurerm_healthcare_fhir_service": resourceHealthcareApisFhirService(), } } diff --git a/internal/services/healthcare/resourceids.go b/internal/services/healthcare/resourceids.go index b8b2510c7aa4..93b85f4d0b72 100644 --- a/internal/services/healthcare/resourceids.go +++ b/internal/services/healthcare/resourceids.go @@ -2,4 +2,5 @@ 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 diff --git a/internal/services/healthcare/validate/fhir_service_id.go b/internal/services/healthcare/validate/fhir_service_id.go new file mode 100644 index 000000000000..20ad7152fa1b --- /dev/null +++ b/internal/services/healthcare/validate/fhir_service_id.go @@ -0,0 +1,23 @@ +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/healthcare/parse" +) + +func FhirServiceID(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.FhirServiceID(v); err != nil { + errors = append(errors, err) + } + + return +} diff --git a/internal/services/healthcare/validate/fhir_service_id_test.go b/internal/services/healthcare/validate/fhir_service_id_test.go new file mode 100644 index 000000000000..511a3576807e --- /dev/null +++ b/internal/services/healthcare/validate/fhir_service_id_test.go @@ -0,0 +1,88 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import "testing" + +func TestFhirServiceID(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 WorkspaceName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/", + Valid: false, + }, + + { + // missing value for WorkspaceName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/", + Valid: false, + }, + + { + // missing Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.HealthcareApis/workspaces/workspace1/", + Valid: false, + }, + + { + // missing value for Name + 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", + Valid: true, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/GROUP1/PROVIDERS/MICROSOFT.HEALTHCAREAPIS/WORKSPACES/WORKSPACE1/FHIRSERVICES/SERVICE1", + Valid: false, + }, + } + for _, tc := range cases { + t.Logf("[DEBUG] Testing Value %s", tc.Input) + _, errors := FhirServiceID(tc.Input, "test") + valid := len(errors) == 0 + + if tc.Valid != valid { + t.Fatalf("Expected %t but got %t", tc.Valid, valid) + } + } +} diff --git a/internal/services/healthcare/validate/fhirservice_name.go b/internal/services/healthcare/validate/fhirservice_name.go new file mode 100644 index 000000000000..7e24bf5baba6 --- /dev/null +++ b/internal/services/healthcare/validate/fhirservice_name.go @@ -0,0 +1,16 @@ +package validate + +import ( + "regexp" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" +) + +// FhirServiceName validates Fhir Service names +func FhirServiceName() pluginsdk.SchemaValidateFunc { + return validation.StringMatch( + regexp.MustCompile(`^[0-9a-zA-Z][-0-9a-zA-Z]{1,22}[0-9a-zA-Z]$`), + `The service name must start with a letter or number. The account name can contain letters, numbers, and dashes. The final character must be a letter or a number. The service name length must be from 3 to 24 characters.`, + ) +} diff --git a/internal/services/network/virtual_network_gateway_nat_rule_resource.go b/internal/services/network/virtual_network_gateway_nat_rule_resource.go index 8eb54509771b..7d77a96848cd 100644 --- a/internal/services/network/virtual_network_gateway_nat_rule_resource.go +++ b/internal/services/network/virtual_network_gateway_nat_rule_resource.go @@ -5,7 +5,7 @@ import ( "log" "time" - "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-05-01/network" + "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-08-01/network" "github.com/hashicorp/terraform-provider-azurerm/helpers/azure" "github.com/hashicorp/terraform-provider-azurerm/helpers/tf" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" diff --git a/website/docs/d/healthcare_fhir_service.html.markdown b/website/docs/d/healthcare_fhir_service.html.markdown new file mode 100644 index 000000000000..e065255268b7 --- /dev/null +++ b/website/docs/d/healthcare_fhir_service.html.markdown @@ -0,0 +1,88 @@ +--- +subcategory: "Healthcare" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_healthcare_fhir_service" +description: |- + Get information about an existing Healthcare FHIR (Fast Healthcare Interoperability Resources) Service. +--- + +# Data Source: azurerm_healthcare_fhir_service + +Use this data source to access information about an existing Healthcare FHIR Service(Fast Healthcare Interoperability Resources). + +## Example Usage + +```hcl +data "azurerm_healthcare_fhir_service" "example" { + name = "example-healthcare_fhir_service" + workspace_id = "example-workspace" +} + +output "healthcare_fhir_service_id" { + value = data.azurerm_healthcare_fhir_service.example.id +} +``` + +## Argument Reference + +* `name` - The name of the Healthcare FHIR Service. + +* `workspace_id` - The name of the Healthcare Workspace in which the Healthcare FHIR Service exists. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the Healthcare FHIR Service. + +* `location` - The Azure Region where the Healthcare FHIR Service is located. + +* `kind` - The kind of the Healthcare FHIR Service. + +* `identity` - The `identity` block as defined below. + +* `access_policy_object_ids` - The list of the access policies of the service instance. + +* `cors` - The `cors` block as defined below. + +* `container_registry_login_server_url` - The list of azure container registry settings used for convert data operation of the service instance. + +* `authentication` - The `authentication` block as defined below. + +* `configuration_export_storage_account_name` - The name of the storage account which the operation configuration information is exported to. + +* `public_network_access_enabled` - Is public networks access enabled when data plane traffic coming from public networks while private endpoint is enabled? + +* `tags` - The map of tags assigned to the Healthcare FHIR Service. + +--- +An `identity` block exports the following: + +* `type` - The type of identity used for the Healthcare FHIR service. + +* `principal_id` - The Principal ID associated with this System Assigned Managed Service Identity. + +* `tenant_id` - The Tenant ID associated with this System Assigned Managed Service Identity. + +--- +A `cors` block exports the following: + +* `allowed_origins` - The set of origins to be allowed via CORS. +* `allowed_headers` - The set of headers to be allowed via CORS. +* `allowed_methods` - The methods to be allowed via CORS. +* `max_age_in_seconds` - The max age to be allowed via CORS. +* `credentials_allowed` - Are credentials allowed via CORS? + +--- +An `authentication` block exports the following: + +* `authority` - The Azure Active Directory (tenant) that serves as the authentication authority to access the service. The default authority is the Directory defined in the authentication scheme in use when running Terraform. + Authority must be registered to Azure AD and in the following format: https://{Azure-AD-endpoint}/{tenant-id}. +* `audience` - The intended audience to receive authentication tokens for the service. The default value is https://.fhir.azurehealthcareapis.com + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: + +* `read` - (Defaults to 5 minutes) Used when retrieving the Healthcare FHIR Service. + diff --git a/website/docs/r/healthcare_fhir_service.html.markdown b/website/docs/r/healthcare_fhir_service.html.markdown new file mode 100644 index 000000000000..92541bfa54c5 --- /dev/null +++ b/website/docs/r/healthcare_fhir_service.html.markdown @@ -0,0 +1,120 @@ +--- +subcategory: "Healthcare" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_healthcare_fhir_service" +description: |- + Manages a Healthcare FHIR (Fast Healthcare Interoperability Resources) Service. +--- + +# azurerm_healthcare_fhir_service + +Manages a Healthcare FHIR (Fast Healthcare Interoperability Resources) Service + +## Example Usage + +```hcl +data "azurerm_client_config" "current" { +} + +resource "azurerm_healthcare_fhir_service" "test" { + name = "tfexfhir" + location = "east us" + resource_group_name = "tfex-resource_group" + workspace_id = "tfex-workspace_id" + kind = "fhir-R4" + + authentication { + authority = "https://login.microsoftonline.com/tenantId" + audience = "https://tfexfhir.fhir.azurehealthcareapis.com" + } + + access_policy_object_ids = [ + data.azurerm_client_config.current.object_id + ] + + identity { + type = "SystemAssigned" + } + + container_registry_login_server_url = ["tfex-container_registry_login_server"] + + cors { + allowed_origins = ["https://tfex.com:123", "https://tfex1.com:3389"] + allowed_headers = ["*"] + allowed_methods = ["GET", "DELETE", "PUT"] + max_age_in_seconds = 3600 + credentials_allowed = true + } + + configuration_export_storage_account_name = "storage_account_name" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) Specifies the name of the Healthcare FHIR Service. Changing this forces a new Healthcare FHIR Service to be created. + +* `workspace_id` - (Required) Specifies the name of the Healthcare Workspace where the Healthcare FHIR Service should exist. Changing this forces a new Healthcare FHIR Service to be created. + +* `location` - (Required) Specifies the Azure Region where the Healthcare FHIR Service should be created. Changing this forces a new Healthcare FHIR Service to be created. + +* `kind` - (Required) Specifies the kind of the Healthcare FHIR Service. Possible values are: `fhir-Stu3` and `fhir-R4`. Defaults to `fhir-R4`. Changing this forces a new Healthcare FHIR Service to be created. + +* `identity` - (Optional) An `identity` block as defined below. + +* `access_policy_object_ids` - (Optional) A list of the access policies of the service instance. + +* `cors` - (Optional) A `cors` block as defined below. + +* `container_registry_login_server_url` - (Optional) A list of azure container registry settings used for convert data operation of the service instance. + +* `authentication` - (Required) An `authentication` block as defined below. + +* `configuration_export_storage_account_name` - (Optional) Specifies the name of the storage account which the operation configuration information is exported to. + +* `public_network_access_enabled` - (Optional) Whether to enabled public networks when data plane traffic coming from public networks while private endpoint is enabled. + +--- +An `identity` block supports the following: + +* `type` - (Required) The type of identity used for the Healthcare FHIR service. Possible values are `SystemAssigned`. + +--- +A `cors` block supports the following: + +* `allowed_origins` - (Required) A set of origins to be allowed via CORS. +* `allowed_headers` - (Required) A set of headers to be allowed via CORS. +* `allowed_methods` - (Required) The methods to be allowed via CORS. +* `max_age_in_seconds` - (Required) The max age to be allowed via CORS. +* `credentials_allowed` - (Optional) If credentials are allowed via CORS. + +--- +An `authentication` supports the following: + +* `authority` - (Optional) The Azure Active Directory (tenant) that serves as the authentication authority to access the service. The default authority is the Directory defined in the authentication scheme in use when running Terraform. + Authority must be registered to Azure AD and in the following format: https://{Azure-AD-endpoint}/{tenant-id}. +* `audience` - (Optional) The intended audience to receive authentication tokens for the service. The default value is https://.fhir.azurehealthcareapis.com + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the Healthcare FHIR Service. + +## Timeouts +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: + +* `create` - (Defaults to 30 minutes) Used when creating the Healthcare FHIR Service. +* `update` - (Defaults to 30 minutes) Used when updating the Healthcare FHIR Service. +* `read` - (Defaults to 5 minutes) Used when retrieving the Healthcare FHIR Service. +* `delete` - (Defaults to 30 minutes) Used when deleting the Healthcare FHIR Service. + +## Import + +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 +```