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

r/network_interface - switching over to the association resources / introducing azurerm_network_interface_security_group_association #5784

Merged
merged 17 commits into from
Feb 18, 2020
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
adb6f59
r/network_interface: introducing a separate update method
tombuildsstuff Feb 14, 2020
9ea6620
r/network_interface: removing the `application_gateway_backend_addres…
tombuildsstuff Feb 14, 2020
bf5cf31
r/network_interface: removing the deprecated `application_security_gr…
tombuildsstuff Feb 14, 2020
a024810
r/network_interface: removing the deprecated `load_balancer_inbound_n…
tombuildsstuff Feb 14, 2020
380f32c
r/network_interface: removing the deprecated `load_balancer_backend_a…
tombuildsstuff Feb 14, 2020
367ca5b
r/network_interface_nat_rule_association: commiting the missing method
tombuildsstuff Feb 14, 2020
f50eb8d
r/network_interface: loading the existing nic details during a delta …
tombuildsstuff Feb 14, 2020
34340db
New Resource: `azurerm_network_interface_security_group_association`
tombuildsstuff Feb 17, 2020
1a18b60
r/network_interface: applying properties managed in other resources t…
tombuildsstuff Feb 17, 2020
a99d908
spelling
tombuildsstuff Feb 17, 2020
cad4978
r/network_interface: updating the tests / rewriting the docs
tombuildsstuff Feb 17, 2020
3ececb9
r/network_interface: conditionally updating the nics only for ipv4 co…
tombuildsstuff Feb 17, 2020
f22151a
r/network_interface_application_gateway_association: fixing the tests
tombuildsstuff Feb 17, 2020
44868cb
d/network_interface: updating the tests
tombuildsstuff Feb 17, 2020
8e874af
docs: adding the `azurerm_network_interface_security_group_associatio…
tombuildsstuff Feb 17, 2020
1310177
linting
tombuildsstuff Feb 18, 2020
065ef31
linting
tombuildsstuff Feb 18, 2020
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
11 changes: 11 additions & 0 deletions azurerm/helpers/azure/contains.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package azure

func SliceContainsValue(input []string, value string) bool {
for _, v := range input {
if v == value {
return true
}
}

return false
}
105 changes: 105 additions & 0 deletions azurerm/internal/azuresdkhacks/network_interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package azuresdkhacks

import (
"bytes"
"context"
"encoding/json"
"io/ioutil"
"net/http"

"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-09-01/network"
"github.com/Azure/go-autorest/autorest"
)

// UpdateNetworkInterfaceAllowingRemovalOfNSG patches our way around a design flaw in the Azure
// Resource Manager API <-> Azure SDK for Go where it's not possible to remove a Network Security Group
func UpdateNetworkInterfaceAllowingRemovalOfNSG(ctx context.Context, client *network.InterfacesClient, resourceGroupName string, networkInterfaceName string, parameters network.Interface) (result network.InterfacesCreateOrUpdateFuture, err error) {
req, err := updateNetworkInterfaceAllowingRemovalOfNSGPreparer(ctx, client, resourceGroupName, networkInterfaceName, parameters)
if err != nil {
err = autorest.NewErrorWithError(err, "network.InterfacesClient", "CreateOrUpdate", nil, "Failure preparing request")
return
}

result, err = client.CreateOrUpdateSender(req)
if err != nil {
err = autorest.NewErrorWithError(err, "network.InterfacesClient", "CreateOrUpdate", result.Response(), "Failure sending request")
return
}

return
}

// updateNetworkInterfaceAllowingRemovalOfNSGPreparer prepares the CreateOrUpdate request but applies the
// necessary patches to be able to remove the NSG if required
func updateNetworkInterfaceAllowingRemovalOfNSGPreparer(ctx context.Context, client *network.InterfacesClient, resourceGroupName string, networkInterfaceName string, parameters network.Interface) (*http.Request, error) {
pathParameters := map[string]interface{}{
"networkInterfaceName": autorest.Encode("path", networkInterfaceName),
"resourceGroupName": autorest.Encode("path", resourceGroupName),
"subscriptionId": autorest.Encode("path", client.SubscriptionID),
}

const APIVersion = "2019-09-01"
queryParameters := map[string]interface{}{
"api-version": APIVersion,
}

parameters.Etag = nil
preparer := autorest.CreatePreparer(
autorest.AsContentType("application/json; charset=utf-8"),
autorest.AsPut(),
autorest.WithBaseURL(client.BaseURI),
autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/networkInterfaces/{networkInterfaceName}", pathParameters),
withJsonWorkingAroundTheBrokenNetworkAPI(parameters),
autorest.WithQueryParameters(queryParameters))
return preparer.Prepare((&http.Request{}).WithContext(ctx))
}

func withJsonWorkingAroundTheBrokenNetworkAPI(v network.Interface) autorest.PrepareDecorator {
return func(p autorest.Preparer) autorest.Preparer {
return autorest.PreparerFunc(func(r *http.Request) (*http.Request, error) {
r, err := p.Prepare(r)
if err == nil {
b, err := json.Marshal(v)
if err == nil {
// there's a few fields which can be intentionally set to nil - as such here we need to check if they should be nil and force them to be nil
var out map[string]interface{}
if err := json.Unmarshal(b, &out); err != nil {
return r, err
}

// apply the hack
out = patchNICUpdateAPIIssue(v, out)

// then reserialize it as needed
b, err = json.Marshal(out)
if err == nil {
r.ContentLength = int64(len(b))
r.Body = ioutil.NopCloser(bytes.NewReader(b))
}
}
}
return r, err
})
}
}

func patchNICUpdateAPIIssue(nic network.Interface, input map[string]interface{}) map[string]interface{} {
if nic.InterfacePropertiesFormat == nil {
return input
}

output := input

if v, ok := output["properties"]; ok {
props := v.(map[string]interface{})

if nic.InterfacePropertiesFormat.NetworkSecurityGroup == nil {
var hack *string // a nil-pointered string
props["networkSecurityGroup"] = hack
}

output["properties"] = props
}

return output
}
15 changes: 15 additions & 0 deletions azurerm/internal/azuresdkhacks/notes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package azuresdkhacks

// There's a functional difference that exists between the Azure SDK for Go and Azure Resource Manager API
// where when performing a delta update unchanged fields are omitted from the response when they could
// also have a legitimate value of `null` (to remove/disable a sub-block).
//
// Ultimately the Azure SDK for Go has opted to serialise structs with `json:"name,omitempty"` which
// means that this value will be omitted if nil to allow for delta updates - however this means there's
// no means of removing/resetting a value of a nested object once provided since a `nil` object will be
// reset
//
// As such this set of well intentioned hacks is intended to force this behaviour where necessary.
//
// It's worth noting that these hacks are a last resort and the Swagger/API/SDK should almost always be
// fixed instead.
4 changes: 2 additions & 2 deletions azurerm/internal/services/keyvault/resource_arm_key_vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ func resourceArmKeyVaultCreateUpdate(d *schema.ResourceData, meta interface{}) e
}

virtualNetworkName := id.Path["virtualNetworks"]
if !network.SliceContainsValue(virtualNetworkNames, virtualNetworkName) {
if !azure.SliceContainsValue(virtualNetworkNames, virtualNetworkName) {
virtualNetworkNames = append(virtualNetworkNames, virtualNetworkName)
}
}
Expand Down Expand Up @@ -437,7 +437,7 @@ func resourceArmKeyVaultDelete(d *schema.ResourceData, meta interface{}) error {
}

virtualNetworkName := id.Path["virtualNetworks"]
if !network.SliceContainsValue(virtualNetworkNames, virtualNetworkName) {
if !azure.SliceContainsValue(virtualNetworkNames, virtualNetworkName) {
virtualNetworkNames = append(virtualNetworkNames, virtualNetworkName)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ func dataSourceArmNetworkInterface() *schema.Resource {
Computed: true,
},

//TODO: should this be renamed to private_ip_address_allocation_method or private_ip_allocation_method ?
"private_ip_address_allocation": {
Type: schema.TypeString,
Computed: true,
Expand Down Expand Up @@ -145,14 +144,6 @@ func dataSourceArmNetworkInterface() *schema.Resource {
Computed: true,
},

/**
* As of 2018-01-06: AN (aka. SR-IOV) on Azure is GA on Windows and Linux.
*
* Refer to: https://azure.microsoft.com/en-us/blog/maximize-your-vm-s-performance-with-accelerated-networking-now-generally-available-for-both-windows-and-linux/
*
* Refer to: https://docs.microsoft.com/en-us/azure/virtual-network/create-vm-accelerated-networking-cli
* For details, VM configuration and caveats.
*/
"enable_accelerated_networking": {
Type: schema.TypeBool,
Computed: true,
Expand Down
134 changes: 134 additions & 0 deletions azurerm/internal/services/network/network_interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package network

import (
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-09-01/network"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"
)

type networkInterfaceUpdateInformation struct {
applicationGatewayBackendAddressPoolIDs []string
applicationSecurityGroupIDs []string
loadBalancerBackendAddressPoolIDs []string
loadBalancerInboundNatRuleIDs []string
networkSecurityGroupID string
}

func parseFieldsFromNetworkInterface(input network.InterfacePropertiesFormat) networkInterfaceUpdateInformation {
networkSecurityGroupId := ""
if input.NetworkSecurityGroup != nil && input.NetworkSecurityGroup.ID != nil {
networkSecurityGroupId = *input.NetworkSecurityGroup.ID
}

var mapToSlice = func(input map[string]struct{}) []string {
output := make([]string, 0)

for id, _ := range input {
output = append(output, id)
}

return output
}

applicationSecurityGroupIds := make(map[string]struct{}, 0)
applicationGatewayBackendAddressPoolIds := make(map[string]struct{}, 0)
loadBalancerBackendAddressPoolIds := make(map[string]struct{}, 0)
loadBalancerInboundNatRuleIds := make(map[string]struct{}, 0)

if input.IPConfigurations != nil {
for _, v := range *input.IPConfigurations {
if v.InterfaceIPConfigurationPropertiesFormat == nil {
continue
}

props := *v.InterfaceIPConfigurationPropertiesFormat
if props.ApplicationSecurityGroups != nil {
for _, asg := range *props.ApplicationSecurityGroups {
if asg.ID != nil {
applicationSecurityGroupIds[*asg.ID] = struct{}{}
}
}
}

if props.ApplicationGatewayBackendAddressPools != nil {
for _, pool := range *props.ApplicationGatewayBackendAddressPools {
if pool.ID != nil {
applicationGatewayBackendAddressPoolIds[*pool.ID] = struct{}{}
}
}
}

if props.LoadBalancerBackendAddressPools != nil {
for _, pool := range *props.LoadBalancerBackendAddressPools {
if pool.ID != nil {
loadBalancerBackendAddressPoolIds[*pool.ID] = struct{}{}
}
}
}

if props.LoadBalancerInboundNatRules != nil {
for _, rule := range *props.LoadBalancerInboundNatRules {
if rule.ID != nil {
loadBalancerInboundNatRuleIds[*rule.ID] = struct{}{}
}
}
}
}
}

return networkInterfaceUpdateInformation{
applicationGatewayBackendAddressPoolIDs: mapToSlice(applicationGatewayBackendAddressPoolIds),
applicationSecurityGroupIDs: mapToSlice(applicationSecurityGroupIds),
loadBalancerBackendAddressPoolIDs: mapToSlice(loadBalancerBackendAddressPoolIds),
loadBalancerInboundNatRuleIDs: mapToSlice(loadBalancerInboundNatRuleIds),
networkSecurityGroupID: networkSecurityGroupId,
}
}

func mapFieldsToNetworkInterface(input *[]network.InterfaceIPConfiguration, info networkInterfaceUpdateInformation) *[]network.InterfaceIPConfiguration {
output := input

applicationSecurityGroups := make([]network.ApplicationSecurityGroup, 0)
for _, id := range info.applicationSecurityGroupIDs {
applicationSecurityGroups = append(applicationSecurityGroups, network.ApplicationSecurityGroup{
ID: utils.String(id),
})
}

applicationGatewayBackendAddressPools := make([]network.ApplicationGatewayBackendAddressPool, 0)
for _, id := range info.applicationGatewayBackendAddressPoolIDs {
applicationGatewayBackendAddressPools = append(applicationGatewayBackendAddressPools, network.ApplicationGatewayBackendAddressPool{
ID: utils.String(id),
})
}

loadBalancerBackendAddressPools := make([]network.BackendAddressPool, 0)
for _, id := range info.loadBalancerBackendAddressPoolIDs {
loadBalancerBackendAddressPools = append(loadBalancerBackendAddressPools, network.BackendAddressPool{
ID: utils.String(id),
})
}

loadBalancerInboundNatRules := make([]network.InboundNatRule, 0)
for _, id := range info.loadBalancerInboundNatRuleIDs {
loadBalancerInboundNatRules = append(loadBalancerInboundNatRules, network.InboundNatRule{
ID: utils.String(id),
})
}

for _, config := range *output {
if config.InterfaceIPConfigurationPropertiesFormat == nil {
continue
}

if config.InterfaceIPConfigurationPropertiesFormat.PrivateIPAddressVersion != network.IPv4 {
continue
}

config.ApplicationSecurityGroups = &applicationSecurityGroups
config.ApplicationGatewayBackendAddressPools = &applicationGatewayBackendAddressPools
config.LoadBalancerBackendAddressPools = &loadBalancerBackendAddressPools
config.LoadBalancerInboundNatRules = &loadBalancerInboundNatRules
}

return output
}
61 changes: 61 additions & 0 deletions azurerm/internal/services/network/network_interface_locking.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package network

import (
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-09-01/network"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/locks"
)

type networkInterfaceIPConfigurationLockingDetails struct {
subnetNamesToLock []string
virtualNetworkNamesToLock []string
}

func (details networkInterfaceIPConfigurationLockingDetails) lock() {
locks.MultipleByName(&details.subnetNamesToLock, SubnetResourceName)
locks.MultipleByName(&details.virtualNetworkNamesToLock, VirtualNetworkResourceName)
}

func (details networkInterfaceIPConfigurationLockingDetails) unlock() {
locks.UnlockMultipleByName(&details.subnetNamesToLock, SubnetResourceName)
locks.UnlockMultipleByName(&details.virtualNetworkNamesToLock, VirtualNetworkResourceName)
}

func determineResourcesToLockFromIPConfiguration(input *[]network.InterfaceIPConfiguration) (*networkInterfaceIPConfigurationLockingDetails, error) {
if input == nil {
return &networkInterfaceIPConfigurationLockingDetails{
subnetNamesToLock: []string{},
virtualNetworkNamesToLock: []string{},
}, nil
}

subnetNamesToLock := make([]string, 0)
virtualNetworkNamesToLock := make([]string, 0)

for _, config := range *input {
if config.Subnet == nil || config.Subnet.ID == nil {
continue
}

id, err := azure.ParseAzureResourceID(*config.Subnet.ID)
if err != nil {
return nil, err
}

virtualNetworkName := id.Path["virtualNetworks"]
subnetName := id.Path["subnets"]

if !azure.SliceContainsValue(virtualNetworkNamesToLock, virtualNetworkName) {
virtualNetworkNamesToLock = append(virtualNetworkNamesToLock, virtualNetworkName)
}

if !azure.SliceContainsValue(subnetNamesToLock, subnetName) {
subnetNamesToLock = append(subnetNamesToLock, subnetName)
}
}

return &networkInterfaceIPConfigurationLockingDetails{
subnetNamesToLock: subnetNamesToLock,
virtualNetworkNamesToLock: virtualNetworkNamesToLock,
}, nil
}
Loading