Skip to content

Commit

Permalink
azurerm_cognitive_account - Add support for network_acls (#11164)
Browse files Browse the repository at this point in the history
  • Loading branch information
mbfrahry authored Mar 31, 2021
1 parent 5285db1 commit aca32a0
Show file tree
Hide file tree
Showing 3 changed files with 299 additions and 2 deletions.
187 changes: 185 additions & 2 deletions azurerm/internal/services/cognitive/cognitive_account_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,16 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf"
commonValidate "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients"
"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"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network"
networkParse "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/parse"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags"
azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/schema"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/set"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"
)
Expand Down Expand Up @@ -107,6 +112,49 @@ func resourceCognitiveAccount() *schema.Resource {
ValidateFunc: validation.IsURLWithHTTPorHTTPS,
},

"network_acls": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
RequiredWith: []string{"custom_subdomain_name"},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"default_action": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{
string(cognitiveservices.Allow),
string(cognitiveservices.Deny),
}, false),
},
"ip_rules": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.Any(
commonValidate.IPv4Address,
commonValidate.CIDR,
),
},
Set: set.HashIPv4AddressOrCIDR,
},
"virtual_network_subnet_ids": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
},
},

"custom_subdomain_name": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: validation.StringIsNotEmpty,
},

"tags": tags.Schema(),

"endpoint": {
Expand Down Expand Up @@ -156,12 +204,31 @@ func resourceCognitiveAccountCreate(d *schema.ResourceData, meta interface{}) er
return fmt.Errorf("expanding sku_name for Cognitive Account %s (Resource Group %q): %v", name, resourceGroup, err)
}

networkAcls, subnetIds := expandCognitiveAccountNetworkAcls(d.Get("network_acls").([]interface{}))

// also lock on the Virtual Network ID's since modifications in the networking stack are exclusive
virtualNetworkNames := make([]string, 0)
for _, v := range subnetIds {
id, err := networkParse.SubnetIDInsensitively(v)
if err != nil {
return err
}
if !utils.SliceContainsValue(virtualNetworkNames, id.VirtualNetworkName) {
virtualNetworkNames = append(virtualNetworkNames, id.VirtualNetworkName)
}
}

locks.MultipleByName(&virtualNetworkNames, network.VirtualNetworkResourceName)
defer locks.UnlockMultipleByName(&virtualNetworkNames, network.VirtualNetworkResourceName)

props := cognitiveservices.Account{
Kind: utils.String(kind),
Location: utils.String(azure.NormalizeLocation(d.Get("location").(string))),
Sku: sku,
Properties: &cognitiveservices.AccountProperties{
APIProperties: &cognitiveservices.AccountAPIProperties{},
APIProperties: &cognitiveservices.AccountAPIProperties{},
NetworkAcls: networkAcls,
CustomSubDomainName: utils.String(d.Get("custom_subdomain_name").(string)),
},
Tags: tags.Expand(d.Get("tags").(map[string]interface{})),
}
Expand Down Expand Up @@ -215,10 +282,29 @@ func resourceCognitiveAccountUpdate(d *schema.ResourceData, meta interface{}) er
return fmt.Errorf("error expanding sku_name for Cognitive Account %s (Resource Group %q): %v", id.Name, id.ResourceGroup, err)
}

networkAcls, subnetIds := expandCognitiveAccountNetworkAcls(d.Get("network_acls").([]interface{}))

// also lock on the Virtual Network ID's since modifications in the networking stack are exclusive
virtualNetworkNames := make([]string, 0)
for _, v := range subnetIds {
id, err := networkParse.SubnetIDInsensitively(v)
if err != nil {
return err
}
if !utils.SliceContainsValue(virtualNetworkNames, id.VirtualNetworkName) {
virtualNetworkNames = append(virtualNetworkNames, id.VirtualNetworkName)
}
}

locks.MultipleByName(&virtualNetworkNames, network.VirtualNetworkResourceName)
defer locks.UnlockMultipleByName(&virtualNetworkNames, network.VirtualNetworkResourceName)

props := cognitiveservices.Account{
Sku: sku,
Properties: &cognitiveservices.AccountProperties{
APIProperties: &cognitiveservices.AccountAPIProperties{},
APIProperties: &cognitiveservices.AccountAPIProperties{},
NetworkAcls: networkAcls,
CustomSubDomainName: utils.String(d.Get("custom_subdomain_name").(string)),
},
Tags: tags.Expand(d.Get("tags").(map[string]interface{})),
}
Expand All @@ -235,6 +321,18 @@ func resourceCognitiveAccountUpdate(d *schema.ResourceData, meta interface{}) er
return fmt.Errorf("Error updating Cognitive Services Account %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err)
}

stateConf := &resource.StateChangeConf{
Pending: []string{"Accepted"},
Target: []string{"Succeeded"},
Refresh: cognitiveAccountStateRefreshFunc(ctx, client, id.ResourceGroup, id.Name),
MinTimeout: 15 * time.Second,
Timeout: d.Timeout(schema.TimeoutCreate),
}

if _, err = stateConf.WaitForState(); err != nil {
return fmt.Errorf("waiting for Cognitive Account (%s) to become available: %s", d.Get("name"), err)
}

return resourceCognitiveAccountRead(d, meta)
}

Expand Down Expand Up @@ -275,6 +373,10 @@ func resourceCognitiveAccountRead(d *schema.ResourceData, meta interface{}) erro
d.Set("qna_runtime_endpoint", apiProps.QnaRuntimeEndpoint)
}
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)
}
}

keys, err := client.ListKeys(ctx, id.ResourceGroup, id.Name)
Expand Down Expand Up @@ -342,3 +444,84 @@ func cognitiveAccountStateRefreshFunc(ctx context.Context, client *cognitiveserv
return res, string(res.Properties.ProvisioningState), nil
}
}

func expandCognitiveAccountNetworkAcls(input []interface{}) (*cognitiveservices.NetworkRuleSet, []string) {
subnetIds := make([]string, 0)
if len(input) == 0 || input[0] == nil {
return nil, subnetIds
}

v := input[0].(map[string]interface{})

defaultAction := v["default_action"].(string)

ipRulesRaw := v["ip_rules"].(*schema.Set)
ipRules := make([]cognitiveservices.IPRule, 0)

for _, v := range ipRulesRaw.List() {
rule := cognitiveservices.IPRule{
Value: utils.String(v.(string)),
}
ipRules = append(ipRules, rule)
}

networkRulesRaw := v["virtual_network_subnet_ids"].(*schema.Set)
networkRules := make([]cognitiveservices.VirtualNetworkRule, 0)
for _, v := range networkRulesRaw.List() {
rawId := v.(string)
subnetIds = append(subnetIds, rawId)
rule := cognitiveservices.VirtualNetworkRule{
ID: utils.String(rawId),
}
networkRules = append(networkRules, rule)
}

ruleSet := cognitiveservices.NetworkRuleSet{
DefaultAction: cognitiveservices.NetworkRuleAction(defaultAction),
IPRules: &ipRules,
VirtualNetworkRules: &networkRules,
}
return &ruleSet, subnetIds
}

func flattenCognitiveAccountNetworkAcls(input *cognitiveservices.NetworkRuleSet) []interface{} {
if input == nil {
return []interface{}{}
}

output := make(map[string]interface{})

output["default_action"] = string(input.DefaultAction)

ipRules := make([]interface{}, 0)
if input.IPRules != nil {
for _, v := range *input.IPRules {
if v.Value == nil {
continue
}

ipRules = append(ipRules, *v.Value)
}
}
output["ip_rules"] = schema.NewSet(schema.HashString, ipRules)

virtualNetworkRules := make([]interface{}, 0)
if input.VirtualNetworkRules != nil {
for _, v := range *input.VirtualNetworkRules {
if v.ID == nil {
continue
}

id := *v.ID
subnetId, err := networkParse.SubnetIDInsensitively(*v.ID)
if err == nil {
id = subnetId.ID()
}

virtualNetworkRules = append(virtualNetworkRules, id)
}
}
output["virtual_network_subnet_ids"] = schema.NewSet(schema.HashString, virtualNetworkRules)

return []interface{}{output}
}
101 changes: 101 additions & 0 deletions azurerm/internal/services/cognitive/cognitive_account_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,28 @@ func TestAccCognitiveAccount_withMultipleCognitiveAccounts(t *testing.T) {
})
}

func TestAccCognitiveAccount_networkAcls(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_cognitive_account", "test")
r := CognitiveAccountResource{}

data.ResourceTest(t, r, []resource.TestStep{
{
Config: r.networkAcls(data),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
{
Config: r.networkAclsUpdated(data),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
})
}

func (t CognitiveAccountResource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) {
id, err := parse.AccountID(state.ID)
if err != nil {
Expand Down Expand Up @@ -385,3 +407,82 @@ resource "azurerm_cognitive_account" "test2" {
}
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger)
}

func (r CognitiveAccountResource) networkAcls(data acceptance.TestData) string {
return fmt.Sprintf(`
%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"
custom_subdomain_name = "acctestcogacc-%d"
network_acls {
default_action = "Deny"
virtual_network_subnet_ids = [azurerm_subnet.test_a.id, azurerm_subnet.test_b.id]
}
}
`, r.networkAclsTemplate(data), data.RandomInteger, data.RandomInteger)
}

func (r CognitiveAccountResource) networkAclsUpdated(data acceptance.TestData) string {
return fmt.Sprintf(`
%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"
custom_subdomain_name = "acctestcogacc-%d"
network_acls {
default_action = "Allow"
ip_rules = ["123.0.0.101"]
virtual_network_subnet_ids = [azurerm_subnet.test_a.id]
}
}
`, r.networkAclsTemplate(data), data.RandomInteger, data.RandomInteger)
}

func (CognitiveAccountResource) networkAclsTemplate(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azurerm" {
features {}
}
data "azurerm_client_config" "current" {
}
resource "azurerm_resource_group" "test" {
name = "acctestRG-cognitive-%d"
location = "%s"
}
resource "azurerm_virtual_network" "test" {
name = "acctestvirtnet%d"
address_space = ["10.0.0.0/16"]
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
}
resource "azurerm_subnet" "test_a" {
name = "acctestsubneta%d"
resource_group_name = azurerm_resource_group.test.name
virtual_network_name = azurerm_virtual_network.test.name
address_prefix = "10.0.2.0/24"
service_endpoints = ["Microsoft.CognitiveServices"]
}
resource "azurerm_subnet" "test_b" {
name = "acctestsubnetb%d"
resource_group_name = azurerm_resource_group.test.name
virtual_network_name = azurerm_virtual_network.test.name
address_prefix = "10.0.4.0/24"
service_endpoints = ["Microsoft.CognitiveServices"]
}
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger)
}
13 changes: 13 additions & 0 deletions website/docs/r/cognitive_account.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,21 @@ The following arguments are supported:

-> **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.

* `tags` - (Optional) A mapping of tags to assign to the resource.

---

A `network_acls` block supports the following:

* `default_action` - (Required) The Default Action to use when no rules match from `ip_rules` / `virtual_network_subnet_ids`. Possible values are `Allow` and `Deny`.

* `ip_rules` - (Optional) One or more IP Addresses, or CIDR Blocks which should be able to access the Cognitive Account.

* `virtual_network_subnet_ids` - (Optional) One or more Subnet ID's which should be able to access this Cognitive Account.

## Attributes Reference

Expand Down

0 comments on commit aca32a0

Please sign in to comment.