Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

azurerm_cognitive_account - Add support for network_acls #11164

Merged
merged 2 commits into from
Mar 31, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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