diff --git a/azurerm/internal/services/cognitive/cognitive_account_resource.go b/azurerm/internal/services/cognitive/cognitive_account_resource.go index 6028cf6f9df8..c84ce8131a5f 100644 --- a/azurerm/internal/services/cognitive/cognitive_account_resource.go +++ b/azurerm/internal/services/cognitive/cognitive_account_resource.go @@ -15,8 +15,11 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/locks" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/cognitive/parse" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/cognitive/validate" + msiparse "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/msi/parse" + msiValidate "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/msi/validate" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network" networkParse "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/parse" + storageValidate "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/storage/validate" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/pluginsdk" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/set" @@ -83,6 +86,7 @@ func resourceCognitiveAccount() *pluginsdk.Resource { "ImmersiveReader", "LUIS", "LUIS.Authoring", + "MetricsAdvisor", "Personalizer", "QnAMaker", "Recommendations", @@ -104,10 +108,95 @@ func resourceCognitiveAccount() *pluginsdk.Resource { }, false), }, - "qna_runtime_endpoint": { + "custom_subdomain_name": { Type: pluginsdk.TypeString, Optional: true, - ValidateFunc: validation.IsURLWithHTTPorHTTPS, + ForceNew: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "fqdns": { + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + + "identity": { + Type: pluginsdk.TypeList, + Optional: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "type": { + Type: pluginsdk.TypeString, + Optional: true, + Default: string(cognitiveservices.ResourceIdentityTypeNone), + ValidateFunc: validation.StringInSlice([]string{ + string(cognitiveservices.ResourceIdentityTypeNone), + string(cognitiveservices.ResourceIdentityTypeSystemAssigned), + string(cognitiveservices.ResourceIdentityTypeUserAssigned), + string(cognitiveservices.ResourceIdentityTypeSystemAssignedUserAssigned), + }, false), + }, + + "principal_id": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "tenant_id": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "identity_ids": { + Type: pluginsdk.TypeSet, + Optional: true, + MinItems: 1, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: msiValidate.UserAssignedIdentityID, + }, + }, + }, + }, + }, + + "local_auth_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: true, + }, + + "metrics_advisor_aad_client_id": { + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IsUUID, + }, + + "metrics_advisor_aad_tenant_id": { + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IsUUID, + }, + + "metrics_advisor_super_user_name": { + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "metrics_advisor_website_name": { + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringIsNotEmpty, }, "network_acls": { @@ -146,11 +235,42 @@ func resourceCognitiveAccount() *pluginsdk.Resource { }, }, - "custom_subdomain_name": { + "outbound_network_access_restrited": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + }, + + "public_network_access_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: true, + }, + + "qna_runtime_endpoint": { Type: pluginsdk.TypeString, Optional: true, - ForceNew: true, - ValidateFunc: validation.StringIsNotEmpty, + ValidateFunc: validation.IsURLWithHTTPorHTTPS, + }, + + "storage": { + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "storage_account_id": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: storageValidate.StorageAccountID, + }, + + "identity_client_id": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.IsUUID, + }, + }, + }, }, "tags": tags.Schema(), @@ -218,26 +338,39 @@ func resourceCognitiveAccountCreate(d *pluginsdk.ResourceData, meta interface{}) locks.MultipleByName(&virtualNetworkNames, network.VirtualNetworkResourceName) defer locks.UnlockMultipleByName(&virtualNetworkNames, network.VirtualNetworkResourceName) + publicNetworkAccess := cognitiveservices.PublicNetworkAccessEnabled + if !d.Get("public_network_access_enabled").(bool) { + publicNetworkAccess = cognitiveservices.PublicNetworkAccessDisabled + } + + apiProps, err := expandCognitiveAccountAPIProperties(d) + if err != nil { + return err + } props := cognitiveservices.Account{ Kind: utils.String(kind), Location: utils.String(azure.NormalizeLocation(d.Get("location").(string))), Sku: sku, Properties: &cognitiveservices.AccountProperties{ - APIProperties: &cognitiveservices.APIProperties{}, - NetworkAcls: networkAcls, - CustomSubDomainName: utils.String(d.Get("custom_subdomain_name").(string)), + APIProperties: apiProps, + NetworkAcls: networkAcls, + CustomSubDomainName: utils.String(d.Get("custom_subdomain_name").(string)), + AllowedFqdnList: utils.ExpandStringSlice(d.Get("fqdns").([]interface{})), + PublicNetworkAccess: publicNetworkAccess, + UserOwnedStorage: expandCognitiveAccountStorage(d.Get("storage").([]interface{})), + RestrictOutboundNetworkAccess: utils.Bool(d.Get("outbound_network_access_restrited").(bool)), + DisableLocalAuth: utils.Bool(!d.Get("local_auth_enabled").(bool)), }, Tags: tags.Expand(d.Get("tags").(map[string]interface{})), } - if kind == "QnAMaker" { - if v, ok := d.GetOk("qna_runtime_endpoint"); ok && v != "" { - props.Properties.APIProperties.QnaRuntimeEndpoint = utils.String(v.(string)) - } else { - return fmt.Errorf("the QnAMaker runtime endpoint `qna_runtime_endpoint` is required when kind is set to `QnAMaker`") - } + identityRaw := d.Get("identity").([]interface{}) + identity, err := expandCognitiveAccountIdentity(identityRaw) + if err != nil { + return fmt.Errorf("Error expanding `identity`: %+v", err) } + props.Identity = identity if _, err := client.Create(ctx, id.ResourceGroup, id.Name, props); err != nil { return fmt.Errorf("creating %s: %+v", id, err) @@ -291,23 +424,36 @@ func resourceCognitiveAccountUpdate(d *pluginsdk.ResourceData, meta interface{}) locks.MultipleByName(&virtualNetworkNames, network.VirtualNetworkResourceName) defer locks.UnlockMultipleByName(&virtualNetworkNames, network.VirtualNetworkResourceName) + publicNetworkAccess := cognitiveservices.PublicNetworkAccessEnabled + if !d.Get("public_network_access_enabled").(bool) { + publicNetworkAccess = cognitiveservices.PublicNetworkAccessDisabled + } + + apiProps, err := expandCognitiveAccountAPIProperties(d) + if err != nil { + return err + } + props := cognitiveservices.Account{ Sku: sku, Properties: &cognitiveservices.AccountProperties{ - APIProperties: &cognitiveservices.APIProperties{}, - NetworkAcls: networkAcls, - CustomSubDomainName: utils.String(d.Get("custom_subdomain_name").(string)), + APIProperties: apiProps, + NetworkAcls: networkAcls, + CustomSubDomainName: utils.String(d.Get("custom_subdomain_name").(string)), + AllowedFqdnList: utils.ExpandStringSlice(d.Get("fqdns").([]interface{})), + PublicNetworkAccess: publicNetworkAccess, + UserOwnedStorage: expandCognitiveAccountStorage(d.Get("storage").([]interface{})), + RestrictOutboundNetworkAccess: utils.Bool(d.Get("outbound_network_access_restrited").(bool)), + DisableLocalAuth: utils.Bool(!d.Get("local_auth_enabled").(bool)), }, Tags: tags.Expand(d.Get("tags").(map[string]interface{})), } - - if kind := d.Get("kind"); kind == "QnAMaker" { - if v, ok := d.GetOk("qna_runtime_endpoint"); ok && v != "" { - props.Properties.APIProperties.QnaRuntimeEndpoint = utils.String(v.(string)) - } else { - return fmt.Errorf("the QnAMaker runtime endpoint `qna_runtime_endpoint` is required when kind is set to `QnAMaker`") - } + identityRaw := d.Get("identity").([]interface{}) + identity, err := expandCognitiveAccountIdentity(identityRaw) + if err != nil { + return fmt.Errorf("Error expanding `identity`: %+v", err) } + props.Identity = identity if _, err = client.Update(ctx, id.ResourceGroup, id.Name, props); err != nil { return fmt.Errorf("updating %s: %+v", *id, err) @@ -363,14 +509,35 @@ func resourceCognitiveAccountRead(d *pluginsdk.ResourceData, meta interface{}) e d.Set("sku_name", sku.Name) } + identity, err := flattenCognitiveAccountIdentity(resp.Identity) + if err != nil { + return err + } + d.Set("identity", identity) + if props := resp.Properties; props != nil { if apiProps := props.APIProperties; apiProps != nil { d.Set("qna_runtime_endpoint", apiProps.QnaRuntimeEndpoint) + d.Set("metrics_advisor_aad_client_id", apiProps.AadClientID) + d.Set("metrics_advisor_aad_tenant_id", apiProps.AadTenantID) + d.Set("metrics_advisor_super_user_name", apiProps.SuperUser) + d.Set("metrics_advisor_website_name", apiProps.WebsiteName) } d.Set("endpoint", props.Endpoint) d.Set("custom_subdomain_name", props.CustomSubDomainName) if err := d.Set("network_acls", flattenCognitiveAccountNetworkAcls(props.NetworkAcls)); err != nil { - return fmt.Errorf("setting `network_acls` for Cognitive Account %q: %+v", *resp.Name, err) + return fmt.Errorf("setting `network_acls` for Cognitive Account %q: %+v", id, err) + } + d.Set("fqdns", utils.FlattenStringSlice(props.AllowedFqdnList)) + d.Set("public_network_access_enabled", props.PublicNetworkAccess == cognitiveservices.PublicNetworkAccessEnabled) + if err := d.Set("storage", flattenCognitiveAccountStorage(props.UserOwnedStorage)); err != nil { + return fmt.Errorf("setting `storages` for Cognitive Account %q: %+v", id, err) + } + if props.RestrictOutboundNetworkAccess != nil { + d.Set("outbound_network_access_restrited", *props.RestrictOutboundNetworkAccess) + } + if props.DisableLocalAuth != nil { + d.Set("local_auth_enabled", !*props.DisableLocalAuth) } } @@ -492,6 +659,99 @@ func expandCognitiveAccountNetworkAcls(input []interface{}) (*cognitiveservices. return &ruleSet, subnetIds } +func expandCognitiveAccountStorage(input []interface{}) *[]cognitiveservices.UserOwnedStorage { + if len(input) == 0 { + return nil + } + results := make([]cognitiveservices.UserOwnedStorage, 0) + for _, v := range input { + value := v.(map[string]interface{}) + results = append(results, cognitiveservices.UserOwnedStorage{ + ResourceID: utils.String(value["storage_account_id"].(string)), + IdentityClientID: utils.String(value["identity_client_id"].(string)), + }) + } + return &results +} + +func expandCognitiveAccountIdentity(vs []interface{}) (*cognitiveservices.Identity, error) { + if len(vs) == 0 { + return &cognitiveservices.Identity{ + Type: cognitiveservices.ResourceIdentityTypeNone, + }, nil + } + + v := vs[0].(map[string]interface{}) + managedServiceIdentity := cognitiveservices.Identity{ + Type: cognitiveservices.ResourceIdentityType(v["type"].(string)), + } + + var identityIdSet []interface{} + if identityIds, ok := v["identity_ids"]; ok { + identityIdSet = identityIds.(*pluginsdk.Set).List() + } + + // If type contains `UserAssigned`, `identity_ids` must be specified and have at least 1 element + if managedServiceIdentity.Type == cognitiveservices.ResourceIdentityTypeUserAssigned || managedServiceIdentity.Type == cognitiveservices.ResourceIdentityTypeSystemAssignedUserAssigned { + if len(identityIdSet) == 0 { + return nil, fmt.Errorf("`identity_ids` must have at least 1 element when `type` includes `UserAssigned`") + } + + userAssignedIdentities := make(map[string]*cognitiveservices.UserAssignedIdentity) + for _, id := range identityIdSet { + userAssignedIdentities[id.(string)] = &cognitiveservices.UserAssignedIdentity{} + } + + managedServiceIdentity.UserAssignedIdentities = userAssignedIdentities + } else if len(identityIdSet) > 0 { + // If type does _not_ contain `UserAssigned` (i.e. is set to `SystemAssigned` or defaulted to `None`), `identity_ids` is not allowed + return nil, fmt.Errorf("`identity_ids` can only be specified when `type` includes `UserAssigned`; but `type` is currently %q", managedServiceIdentity.Type) + } + + return &managedServiceIdentity, nil +} + +func expandCognitiveAccountAPIProperties(d *pluginsdk.ResourceData) (*cognitiveservices.APIProperties, error) { + props := cognitiveservices.APIProperties{} + kind := d.Get("kind") + if kind == "QnAMaker" { + if v, ok := d.GetOk("qna_runtime_endpoint"); ok && v != "" { + props.QnaRuntimeEndpoint = utils.String(v.(string)) + } else { + return nil, fmt.Errorf("the QnAMaker runtime endpoint `qna_runtime_endpoint` is required when kind is set to `QnAMaker`") + } + } + if v, ok := d.GetOk("metrics_advisor_aad_client_id"); ok { + if kind == "MetricsAdvisor" { + props.AadClientID = utils.String(v.(string)) + } else { + return nil, fmt.Errorf("metrics_advisor_aad_client_id can only used set when kind is set to `MetricsAdvisor`") + } + } + if v, ok := d.GetOk("metrics_advisor_aad_tenant_id"); ok { + if kind == "MetricsAdvisor" { + props.AadTenantID = utils.String(v.(string)) + } else { + return nil, fmt.Errorf("metrics_advisor_aad_tenant_id can only used set when kind is set to `MetricsAdvisor`") + } + } + if v, ok := d.GetOk("metrics_advisor_super_user_name"); ok { + if kind == "MetricsAdvisor" { + props.SuperUser = utils.String(v.(string)) + } else { + return nil, fmt.Errorf("metrics_advisor_super_user_name can only used set when kind is set to `MetricsAdvisor`") + } + } + if v, ok := d.GetOk("metrics_advisor_website_name"); ok { + if kind == "MetricsAdvisor" { + props.WebsiteName = utils.String(v.(string)) + } else { + return nil, fmt.Errorf("metrics_advisor_website_name can only used set when kind is set to `MetricsAdvisor`") + } + } + return &props, nil +} + func flattenCognitiveAccountNetworkAcls(input *cognitiveservices.NetworkRuleSet) []interface{} { if input == nil { return []interface{}{} @@ -532,3 +792,52 @@ func flattenCognitiveAccountNetworkAcls(input *cognitiveservices.NetworkRuleSet) }, } } + +func flattenCognitiveAccountStorage(input *[]cognitiveservices.UserOwnedStorage) []interface{} { + if input == nil { + return []interface{}{} + } + results := make([]interface{}, 0) + for _, v := range *input { + value := make(map[string]interface{}) + if v.ResourceID != nil { + value["storage_account_id"] = *v.ResourceID + } + if v.IdentityClientID != nil { + value["identity_client_id"] = *v.IdentityClientID + } + results = append(results, value) + } + return results +} + +func flattenCognitiveAccountIdentity(identity *cognitiveservices.Identity) ([]interface{}, error) { + if identity == nil || identity.Type == cognitiveservices.ResourceIdentityTypeNone { + return make([]interface{}, 0), nil + } + + result := make(map[string]interface{}) + result["type"] = string(identity.Type) + + if identity.PrincipalID != nil { + result["principal_id"] = *identity.PrincipalID + } + + if identity.TenantID != nil { + result["tenant_id"] = *identity.TenantID + } + + identityIds := make([]interface{}, 0) + if identity.UserAssignedIdentities != nil { + for key := range identity.UserAssignedIdentities { + parsedId, err := msiparse.UserAssignedIdentityID(key) + if err != nil { + return nil, err + } + identityIds = append(identityIds, parsedId.ID()) + } + result["identity_ids"] = pluginsdk.NewSet(pluginsdk.HashString, identityIds) + } + + return []interface{}{result}, nil +} diff --git a/azurerm/internal/services/cognitive/cognitive_account_resource_test.go b/azurerm/internal/services/cognitive/cognitive_account_resource_test.go index 3d8516d05ff7..207d32be38e9 100644 --- a/azurerm/internal/services/cognitive/cognitive_account_resource_test.go +++ b/azurerm/internal/services/cognitive/cognitive_account_resource_test.go @@ -6,6 +6,7 @@ import ( "regexp" "testing" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance/check" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" @@ -55,6 +56,23 @@ func TestAccCognitiveAccount_speechServices(t *testing.T) { }) } +func TestAccCognitiveAccount_speechServicesWithStorage(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_cognitive_account", "test") + r := CognitiveAccountResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.speechServicesWithStorage(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("primary_access_key").Exists(), + check.That(data.ResourceName).Key("secondary_access_key").Exists(), + ), + }, + data.ImportStep(), + }) +} + func TestAccCognitiveAccount_requiresImport(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_cognitive_account", "test") r := CognitiveAccountResource{} @@ -218,6 +236,61 @@ func TestAccCognitiveAccount_networkAcls(t *testing.T) { }) } +func TestAccCognitiveAccount_identity(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_cognitive_account", "test") + r := CognitiveAccountResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.identitySystemAssignedUserAssigned(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("identity.0.principal_id").MatchesRegex(validate.UUIDRegExp), + check.That(data.ResourceName).Key("identity.0.tenant_id").MatchesRegex(validate.UUIDRegExp), + ), + }, + data.ImportStep(), + { + Config: r.identityUserAssigned(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.identitySystemAssigned(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("identity.0.principal_id").MatchesRegex(validate.UUIDRegExp), + check.That(data.ResourceName).Key("identity.0.tenant_id").MatchesRegex(validate.UUIDRegExp), + ), + }, + data.ImportStep(), + }) +} + +func TestAccCognitiveAccount_metricsAdvisor(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_cognitive_account", "test") + r := CognitiveAccountResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.metricsAdvisor(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func (t CognitiveAccountResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { id, err := parse.AccountID(state.ID) if err != nil { @@ -253,6 +326,96 @@ resource "azurerm_cognitive_account" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomInteger) } +func (CognitiveAccountResource) identitySystemAssigned(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-cognitive-%d" + location = "%s" +} + +resource "azurerm_cognitive_account" "test" { + name = "acctestcogacc-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + kind = "Face" + sku_name = "S0" + identity { + type = "SystemAssigned" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) +} + +func (CognitiveAccountResource) identityUserAssigned(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-cognitive-%d" + location = "%s" +} + +resource "azurerm_user_assigned_identity" "test" { + name = "acctestUAI-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name +} + +resource "azurerm_cognitive_account" "test" { + name = "acctestcogacc-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + kind = "Face" + sku_name = "S0" + identity { + type = "UserAssigned" + identity_ids = [ + azurerm_user_assigned_identity.test.id, + ] + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger) +} + +func (CognitiveAccountResource) identitySystemAssignedUserAssigned(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-cognitive-%d" + location = "%s" +} + +resource "azurerm_user_assigned_identity" "test" { + name = "acctestUAI-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name +} + +resource "azurerm_cognitive_account" "test" { + name = "acctestcogacc-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + kind = "Face" + sku_name = "S0" + identity { + type = "SystemAssigned, UserAssigned" + identity_ids = [ + azurerm_user_assigned_identity.test.id, + ] + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger) +} + func (CognitiveAccountResource) speechServices(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { @@ -271,7 +434,52 @@ resource "azurerm_cognitive_account" "test" { kind = "SpeechServices" sku_name = "S0" } -`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) +`, data.RandomInteger, data.Locations.Secondary, data.RandomInteger) +} + +func (CognitiveAccountResource) speechServicesWithStorage(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-cognitive-%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestrg%d" + resource_group_name = azurerm_resource_group.test.name + + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_user_assigned_identity" "test" { + name = "acctest-identity-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location +} + +resource "azurerm_cognitive_account" "test" { + name = "acctestcogacc-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + kind = "SpeechServices" + sku_name = "S0" + + identity { + type = "SystemAssigned" + } + + storage { + storage_account_id = azurerm_storage_account.test.id + identity_client_id = azurerm_user_assigned_identity.test.client_id + } +} +`, data.RandomInteger, data.Locations.Secondary, data.RandomIntOfLength(8), data.RandomInteger, data.RandomInteger) } func (CognitiveAccountResource) requiresImport(data acceptance.TestData) string { @@ -307,6 +515,11 @@ resource "azurerm_cognitive_account" "test" { kind = "Face" sku_name = "S0" + fqdns = ["foo.com", "bar.com"] + public_network_access_enabled = false + outbound_network_access_restrited = true + local_auth_enabled = false + tags = { Acceptance = "Test" } @@ -333,7 +546,7 @@ resource "azurerm_cognitive_account" "test" { qna_runtime_endpoint = "%s" sku_name = "S0" } -`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, url) +`, data.RandomInteger, "West US", data.RandomInteger, url) // QnAMaker only available in West US } func (CognitiveAccountResource) qnaRuntimeEndpointUnspecified(data acceptance.TestData) string { @@ -354,7 +567,7 @@ resource "azurerm_cognitive_account" "test" { kind = "QnAMaker" sku_name = "S0" } -`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) +`, data.RandomInteger, "West US", data.RandomInteger) // QnAMaker only available in West US } func (CognitiveAccountResource) cognitiveServices(data acceptance.TestData) string { @@ -378,6 +591,30 @@ resource "azurerm_cognitive_account" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomInteger) } +func (CognitiveAccountResource) metricsAdvisor(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} +resource "azurerm_resource_group" "test" { + name = "acctestRG-cognitive-%d" + location = "%s" +} +resource "azurerm_cognitive_account" "test" { + name = "acctestcogacc-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + kind = "MetricsAdvisor" + sku_name = "S0" + custom_subdomain_name = "acctestcogacc-%d" + metrics_advisor_aad_client_id = "310d7b2e-d1d1-4b87-9807-5b885b290c00" + metrics_advisor_aad_tenant_id = "72f988bf-86f1-41af-91ab-2d7cd011db47" + metrics_advisor_super_user_name = "mock_user1" + metrics_advisor_website_name = "mock_name2" +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger) +} + func (CognitiveAccountResource) withMultipleCognitiveAccounts(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { diff --git a/website/docs/r/cognitive_account.html.markdown b/website/docs/r/cognitive_account.html.markdown index f38c08a4ab0e..bde8d073e114 100644 --- a/website/docs/r/cognitive_account.html.markdown +++ b/website/docs/r/cognitive_account.html.markdown @@ -44,17 +44,37 @@ The following arguments are supported: * `location` - (Required) Specifies the supported Azure location where the resource exists. Changing this forces a new resource to be created. -* `kind` - (Required) Specifies the type of Cognitive Service Account that should be created. Possible values are `Academic`, `AnomalyDetector`, `Bing.Autosuggest`, `Bing.Autosuggest.v7`, `Bing.CustomSearch`, `Bing.Search`, `Bing.Search.v7`, `Bing.Speech`, `Bing.SpellCheck`, `Bing.SpellCheck.v7`, `CognitiveServices`, `ComputerVision`, `ContentModerator`, `CustomSpeech`, `CustomVision.Prediction`, `CustomVision.Training`, `Emotion`, `Face`,`FormRecognizer`, `ImmersiveReader`, `LUIS`, `LUIS.Authoring`, `Personalizer`, `QnAMaker`, `Recommendations`, `SpeakerRecognition`, `Speech`, `SpeechServices`, `SpeechTranslation`, `TextAnalytics`, `TextTranslation` and `WebLM`. Changing this forces a new resource to be created. +* `kind` - (Required) Specifies the type of Cognitive Service Account that should be created. Possible values are `Academic`, `AnomalyDetector`, `Bing.Autosuggest`, `Bing.Autosuggest.v7`, `Bing.CustomSearch`, `Bing.Search`, `Bing.Search.v7`, `Bing.Speech`, `Bing.SpellCheck`, `Bing.SpellCheck.v7`, `CognitiveServices`, `ComputerVision`, `ContentModerator`, `CustomSpeech`, `CustomVision.Prediction`, `CustomVision.Training`, `Emotion`, `Face`,`FormRecognizer`, `ImmersiveReader`, `LUIS`, `LUIS.Authoring`, `MetricsAdvisor`, `Personalizer`, `QnAMaker`, `Recommendations`, `SpeakerRecognition`, `Speech`, `SpeechServices`, `SpeechTranslation`, `TextAnalytics`, `TextTranslation` and `WebLM`. Changing this forces a new resource to be created. * `sku_name` - (Required) Specifies the SKU Name for this Cognitive Service Account. Possible values are `F0`, `F1`, `S`, `S0`, `S1`, `S2`, `S3`, `S4`, `S5`, `S6`, `P0`, `P1`, and `P2`. -* `qna_runtime_endpoint` - (Optional) A URL to link a QnAMaker cognitive account to a QnA runtime. +* `custom_subdomain_name` - (Optional) The subdomain name used for token-based authentication. Changing this forces a new resource to be created. + +* `fqdns` - (Optional) List of FQDNs allowed for the Cognitive Account. + +* `identity` - (Optional) An `identity` block is documented below. + +* `local_auth_enabled` - (Optional) Whether local authentication methods is enabled for the Cognitive Account. Defaults to `true`. + +* `metrics_advisor_aad_client_id` - (Optional) The Azure AD Client ID (Application ID). This attribute is only set when kind is `MetricsAdvisor`. Changing this forces a new resource to be created. + +* `metrics_advisor_aad_tenant_id` - (Optional) The Azure AD Tenant ID. This attribute is only set when kind is `MetricsAdvisor`. Changing this forces a new resource to be created. + +* `metrics_advisor_super_user_name` - (Optional) The super user of Metrics Advisor. This attribute is only set when kind is `MetricsAdvisor`. Changing this forces a new resource to be created. + +* `metrics_advisor_website_name` - (Optional) The website name of Metrics Advisor. This attribute is only set when kind is `MetricsAdvisor`. Changing this forces a new resource to be created. -> **NOTE:** This URL is mandatory if the `kind` is set to `QnAMaker`. * `network_acls` - (Optional) A `network_acls` block as defined below. -* `custom_subdomain_name` - (Optional) The subdomain name used for token-based authentication. Changing this forces a new resource to be created. +* `outbound_network_access_restrited` - (Optional) Whether outbound network access is restricted for the Cognitive Account. Defaults to `false`. + +* `public_network_access_enabled` - (Optional) Whether public network access is allowed for the Cognitive Account. Defaults to `true`. + +* `qna_runtime_endpoint` - (Optional) A URL to link a QnAMaker cognitive account to a QnA runtime. + +* `storage` - (Optional) An `identity` block is documented below. * `tags` - (Optional) A mapping of tags to assign to the resource. @@ -68,6 +88,24 @@ A `network_acls` block supports the following: * `virtual_network_subnet_ids` - (Optional) One or more Subnet ID's which should be able to access this Cognitive Account. +--- + +A `identity` block supports the following: + +* `type` - (Required) Specifies the type of Managed Service Identity that should be configured on the Cognitive Account. Possible values are `SystemAssigned`, `UserAssigned`, `SystemAssigned, UserAssigned` (to enable both). + +* `identity_ids` - (Optional) A list of IDs for User Assigned Managed Identity resources to be assigned. + +~> **NOTE:** This is required when `type` is set to `UserAssigned` or `SystemAssigned, UserAssigned`. + +--- + +A `storage` block supports the following: + +* `storage_account_id` - (Required) Full resource id of a Microsoft.Storage resource. + +* `identity_client_id` - (Optional) The client ID of the managed identity associated with the storage resource. + ## Attributes Reference The following attributes are exported: