From 4f98966d026e5d2f424e9793fa8095da1c5058e7 Mon Sep 17 00:00:00 2001 From: Matthew Date: Wed, 5 Jun 2024 16:32:07 -0700 Subject: [PATCH 1/3] azurerm_virtual_network - split create and update function to fix lifecycle - ignore --- .../network/virtual_network_resource.go | 224 ++++++++++++++++-- 1 file changed, 210 insertions(+), 14 deletions(-) diff --git a/internal/services/network/virtual_network_resource.go b/internal/services/network/virtual_network_resource.go index 1bcca5cf26f6..8ad07ced7e67 100644 --- a/internal/services/network/virtual_network_resource.go +++ b/internal/services/network/virtual_network_resource.go @@ -7,6 +7,7 @@ import ( "bytes" "context" "fmt" + "github.com/tombuildsstuff/kermit/sdk/network/2022-07-01/network" "log" "time" @@ -36,9 +37,9 @@ var VirtualNetworkResourceName = "azurerm_virtual_network" func resourceVirtualNetwork() *pluginsdk.Resource { return &pluginsdk.Resource{ - Create: resourceVirtualNetworkCreateUpdate, + Create: resourceVirtualNetworkCreate, Read: resourceVirtualNetworkRead, - Update: resourceVirtualNetworkCreateUpdate, + Update: resourceVirtualNetworkUpdate, Delete: resourceVirtualNetworkDelete, Importer: pluginsdk.ImporterValidatingResourceId(func(id string) error { _, err := commonids.ParseVirtualNetworkID(id) @@ -199,26 +200,24 @@ func resourceVirtualNetworkSchema() map[string]*pluginsdk.Schema { return s } -func resourceVirtualNetworkCreateUpdate(d *pluginsdk.ResourceData, meta interface{}) error { +func resourceVirtualNetworkCreate(d *pluginsdk.ResourceData, meta interface{}) error { client := meta.(*clients.Client).Network.VirtualNetworks subscriptionId := meta.(*clients.Client).Account.SubscriptionId - ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) + ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) defer cancel() id := commonids.NewVirtualNetworkID(subscriptionId, d.Get("resource_group_name").(string), d.Get("name").(string)) - if d.IsNewResource() { - existing, err := client.Get(ctx, id, virtualnetworks.DefaultGetOperationOptions()) - if err != nil { - if !response.WasNotFound(existing.HttpResponse) { - return fmt.Errorf("checking for presence of existing %s: %s", id, err) - } - } - + existing, err := client.Get(ctx, id, virtualnetworks.DefaultGetOperationOptions()) + if err != nil { if !response.WasNotFound(existing.HttpResponse) { - return tf.ImportAsExistsError("azurerm_virtual_network", id.ID()) + return fmt.Errorf("checking for presence of existing %s: %s", id, err) } } + if !response.WasNotFound(existing.HttpResponse) { + return tf.ImportAsExistsError("azurerm_virtual_network", id.ID()) + } + vnetProperties, err := expandVirtualNetworkProperties(ctx, *client, id, d) if err != nil { return err @@ -255,7 +254,7 @@ func resourceVirtualNetworkCreateUpdate(d *pluginsdk.ResourceData, meta interfac defer locks.UnlockMultipleByName(&networkSecurityGroupNames, networkSecurityGroupResourceName) if err := client.CreateOrUpdateThenPoll(ctx, id, vnet); err != nil { - return fmt.Errorf("creating/updating %s: %+v", id, err) + return fmt.Errorf("creating %s: %+v", id, err) } timeout, _ := ctx.Deadline() @@ -345,6 +344,121 @@ func resourceVirtualNetworkRead(d *pluginsdk.ResourceData, meta interface{}) err return nil } +func resourceVirtualNetworkUpdate(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Network.VirtualNetworks + ctx, cancel := timeouts.ForUpdate(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := commonids.ParseVirtualNetworkID(d.Id()) + if err != nil { + return err + } + + existing, err := client.Get(ctx, *id, virtualnetworks.DefaultGetOperationOptions()) + if err != nil { + return fmt.Errorf("retrieving %s: %+v", id, err) + } + + if existing.Model == nil { + return fmt.Errorf("retrieving %s: `model` was nil", id) + } + + if existing.Model.Properties == nil { + return fmt.Errorf("retrieving %s: `properties` was nil", id) + } + + payload := existing.Model + + if d.HasChange("address_space") { + if payload.Properties.AddressSpace == nil { + payload.Properties.AddressSpace = &virtualnetworks.AddressSpace{} + } + if !features.FourPointOhBeta() { + payload.Properties.AddressSpace.AddressPrefixes = utils.ExpandStringSlice(d.Get("address_space").([]interface{})) + } else { + payload.Properties.AddressSpace.AddressPrefixes = utils.ExpandStringSlice(d.Get("address_space").(*pluginsdk.Set).List()) + } + } + + if d.HasChange("bgp_community") { + payload.Properties.BgpCommunities = &virtualnetworks.VirtualNetworkBgpCommunities{VirtualNetworkCommunity: d.Get("bgp_community").(string)} + } + + if d.HasChange("ddos_protection_plan") { + ddosProtectionPlanId, enabled := expandVirtualNetworkDdosProtectionPlan(d.Get("ddos_protection_plan").([]interface{})) + payload.Properties.DdosProtectionPlan = ddosProtectionPlanId + payload.Properties.EnableDdosProtection = enabled + } + + if d.HasChange("encryption") { + // nil out the current values in case `encryption` has been removed from the config file + payload.Properties.Encryption = expandVirtualNetworkEncryption(d.Get("encryption").([]interface{})) + } + + if d.HasChange("dns_servers") { + if payload.Properties.DhcpOptions == nil { + payload.Properties.DhcpOptions = &virtualnetworks.DhcpOptions{} + } + + payload.Properties.DhcpOptions.DnsServers = utils.ExpandStringSlice(d.Get("dns_servers").([]interface{})) + } + + if d.HasChange("flow_timeout_in_minutes") { + payload.Properties.FlowTimeoutInMinutes = utils.Int64(int64(d.Get("flow_timeout_in_minutes").(int))) + } + + if d.HasChange("subnet") { + subnets, err := expandVirtualNetworkSubnets(ctx, *client, d.Get("subnet").(*pluginsdk.Set).List(), *id) + if err != nil { + return fmt.Errorf("expanding `subnet`: %+v", err) + } + payload.Properties.Subnets = subnets + } + + if d.HasChange("tags") { + payload.Tags = tags.Expand(d.Get("tags").(map[string]interface{})) + } + + networkSecurityGroupNames := make([]string, 0) + if payload.Properties != nil && payload.Properties.Subnets != nil { + for _, subnet := range *payload.Properties.Subnets { + if subnet.Properties != nil && subnet.Properties.NetworkSecurityGroup != nil && subnet.Id != nil { + parsedNsgID, err := parse.NetworkSecurityGroupID(*subnet.Id) + if err != nil { + return err + } + + networkSecurityGroupName := parsedNsgID.Name + if !utils.SliceContainsValue(networkSecurityGroupNames, networkSecurityGroupName) { + networkSecurityGroupNames = append(networkSecurityGroupNames, networkSecurityGroupName) + } + } + } + } + + locks.MultipleByName(&networkSecurityGroupNames, networkSecurityGroupResourceName) + defer locks.UnlockMultipleByName(&networkSecurityGroupNames, networkSecurityGroupResourceName) + + if err := client.CreateOrUpdateThenPoll(ctx, *id, *payload); err != nil { + return fmt.Errorf("updating %s: %+v", id, err) + } + + timeout, _ := ctx.Deadline() + stateConf := &pluginsdk.StateChangeConf{ + Pending: []string{string(network.ProvisioningStateUpdating)}, + Target: []string{string(network.ProvisioningStateSucceeded)}, + Refresh: VirtualNetworkProvisioningStateRefreshFunc(ctx, meta.(*clients.Client).Network.VirtualNetworks, *id), + MinTimeout: 1 * time.Minute, + Timeout: time.Until(timeout), + } + if _, err = stateConf.WaitForStateContext(ctx); err != nil { + return fmt.Errorf("waiting for provisioning state of %s: %+v", id, err) + } + + d.SetId(id.ID()) + return resourceVirtualNetworkRead(d, meta) +} + func resourceVirtualNetworkDelete(d *pluginsdk.ResourceData, meta interface{}) error { client := meta.(*clients.Client).Network.VirtualNetworks ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) @@ -370,6 +484,88 @@ func resourceVirtualNetworkDelete(d *pluginsdk.ResourceData, meta interface{}) e return nil } +func expandVirtualNetworkDdosProtectionPlan(input []interface{}) (*virtualnetworks.SubResource, *bool) { + if len(input) == 0 || input[0] == nil { + return nil, nil + } + + var id string + var enabled bool + + ddosPPlan := input[0].(map[string]interface{}) + + if v, ok := ddosPPlan["id"]; ok { + id = v.(string) + } + + if v, ok := ddosPPlan["enable"]; ok { + enabled = v.(bool) + } + + return &virtualnetworks.SubResource{ + Id: pointer.To(id), + }, &enabled +} + +func expandVirtualNetworkEncryption(input []interface{}) *virtualnetworks.VirtualNetworkEncryption { + if len(input) == 0 || input[0] == nil { + return nil + } + + attr := input[0].(map[string]interface{}) + return &virtualnetworks.VirtualNetworkEncryption{ + Enabled: true, + Enforcement: pointer.To(virtualnetworks.VirtualNetworkEncryptionEnforcement(attr["enforcement"].(string))), + } +} + +func expandVirtualNetworkSubnets(ctx context.Context, client virtualnetworks.VirtualNetworksClient, input []interface{}, id commonids.VirtualNetworkId) (*[]virtualnetworks.Subnet, error) { + if len(input) == 0 { + return nil, nil + } + + subnets := make([]virtualnetworks.Subnet, 0) + for _, subnetRaw := range input { + if subnetRaw == nil { + continue + } + subnet := subnetRaw.(map[string]interface{}) + + name := subnet["name"].(string) + log.Printf("[INFO] setting subnets inside vNet, processing %q", name) + // since subnets can also be created outside of vNet definition (as root objects) + // do a GET on subnet properties from the server before setting them + subnetObj, err := getExistingSubnet(ctx, client, id, name) + if err != nil { + return nil, err + } + log.Printf("[INFO] Completed GET of Subnet props ") + + prefix := subnet["address_prefix"].(string) + secGroup := subnet["security_group"].(string) + + // set the props from config and leave the rest intact + subnetObj.Name = &name + if subnetObj.Properties == nil { + subnetObj.Properties = &virtualnetworks.SubnetPropertiesFormat{} + } + + subnetObj.Properties.AddressPrefix = &prefix + + if secGroup != "" { + subnetObj.Properties.NetworkSecurityGroup = &virtualnetworks.NetworkSecurityGroup{ + Id: &secGroup, + } + } else { + subnetObj.Properties.NetworkSecurityGroup = nil + } + + subnets = append(subnets, *subnetObj) + } + + return &subnets, nil +} + func expandVirtualNetworkProperties(ctx context.Context, client virtualnetworks.VirtualNetworksClient, id commonids.VirtualNetworkId, d *pluginsdk.ResourceData) (*virtualnetworks.VirtualNetworkPropertiesFormat, error) { subnets := make([]virtualnetworks.Subnet, 0) if subs := d.Get("subnet").(*pluginsdk.Set); subs.Len() > 0 { From c01ed3ad48efa77b4ea972d2779b66ceac3c6cb9 Mon Sep 17 00:00:00 2001 From: Matthew Date: Wed, 5 Jun 2024 17:51:35 -0700 Subject: [PATCH 2/3] Lint --- internal/services/network/virtual_network_resource.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/internal/services/network/virtual_network_resource.go b/internal/services/network/virtual_network_resource.go index 8ad07ced7e67..2fd071a9cbfa 100644 --- a/internal/services/network/virtual_network_resource.go +++ b/internal/services/network/virtual_network_resource.go @@ -7,7 +7,6 @@ import ( "bytes" "context" "fmt" - "github.com/tombuildsstuff/kermit/sdk/network/2022-07-01/network" "log" "time" @@ -445,8 +444,8 @@ func resourceVirtualNetworkUpdate(d *pluginsdk.ResourceData, meta interface{}) e timeout, _ := ctx.Deadline() stateConf := &pluginsdk.StateChangeConf{ - Pending: []string{string(network.ProvisioningStateUpdating)}, - Target: []string{string(network.ProvisioningStateSucceeded)}, + Pending: []string{string(virtualnetworks.ProvisioningStateUpdating)}, + Target: []string{string(virtualnetworks.ProvisioningStateSucceeded)}, Refresh: VirtualNetworkProvisioningStateRefreshFunc(ctx, meta.(*clients.Client).Network.VirtualNetworks, *id), MinTimeout: 1 * time.Minute, Timeout: time.Until(timeout), From 9a392d2e77d7066496b46274b1a5f1796861b18b Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 6 Jun 2024 11:49:21 -0700 Subject: [PATCH 3/3] Fix tests --- .../services/network/virtual_network_resource.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/internal/services/network/virtual_network_resource.go b/internal/services/network/virtual_network_resource.go index 2fd071a9cbfa..902457d53983 100644 --- a/internal/services/network/virtual_network_resource.go +++ b/internal/services/network/virtual_network_resource.go @@ -380,7 +380,11 @@ func resourceVirtualNetworkUpdate(d *pluginsdk.ResourceData, meta interface{}) e } if d.HasChange("bgp_community") { - payload.Properties.BgpCommunities = &virtualnetworks.VirtualNetworkBgpCommunities{VirtualNetworkCommunity: d.Get("bgp_community").(string)} + // nil out the current values in case `bgp_community` has been removed from the config file + payload.Properties.BgpCommunities = nil + if v := d.Get("bgp_community"); v.(string) != "" { + payload.Properties.BgpCommunities = &virtualnetworks.VirtualNetworkBgpCommunities{VirtualNetworkCommunity: v.(string)} + } } if d.HasChange("ddos_protection_plan") { @@ -403,7 +407,10 @@ func resourceVirtualNetworkUpdate(d *pluginsdk.ResourceData, meta interface{}) e } if d.HasChange("flow_timeout_in_minutes") { - payload.Properties.FlowTimeoutInMinutes = utils.Int64(int64(d.Get("flow_timeout_in_minutes").(int))) + payload.Properties.FlowTimeoutInMinutes = nil + if v := d.Get("flow_timeout_in_minutes"); v.(int) != 0 { + payload.Properties.FlowTimeoutInMinutes = utils.Int64(int64(v.(int))) + } } if d.HasChange("subnet") { @@ -519,11 +526,11 @@ func expandVirtualNetworkEncryption(input []interface{}) *virtualnetworks.Virtua } func expandVirtualNetworkSubnets(ctx context.Context, client virtualnetworks.VirtualNetworksClient, input []interface{}, id commonids.VirtualNetworkId) (*[]virtualnetworks.Subnet, error) { + subnets := make([]virtualnetworks.Subnet, 0) if len(input) == 0 { - return nil, nil + return &subnets, nil } - subnets := make([]virtualnetworks.Subnet, 0) for _, subnetRaw := range input { if subnetRaw == nil { continue