From 57a17ffd01e8b464ec3457c8b9a4805de2b3ff7a Mon Sep 17 00:00:00 2001 From: Neil Ye Date: Wed, 11 Nov 2020 05:38:14 +0800 Subject: [PATCH] Upgrade network connection monitor from v1 to v2 (#8640) --- .../network_connection_monitor_resource.go | 964 ++++++++++++++++-- .../network/network_watcher_resource.go | 21 +- .../parse/network_connection_monitor.go | 36 + .../parse/network_connection_monitor_test.go | 104 ++ .../services/network/parse/network_watcher.go | 45 + .../network/parse/network_watcher_test.go | 69 ++ ...etwork_connection_monitor_resource_test.go | 541 +++++++--- .../tests/network_watcher_resource_test.go | 58 +- .../validate/network_connection_monitor.go | 121 +++ .../network_connection_monitor_test.go | 161 +++ .../network/validate/network_watcher.go | 22 + .../network_connection_monitor.html.markdown | 257 ++++- 12 files changed, 2092 insertions(+), 307 deletions(-) create mode 100644 azurerm/internal/services/network/parse/network_connection_monitor.go create mode 100644 azurerm/internal/services/network/parse/network_connection_monitor_test.go create mode 100644 azurerm/internal/services/network/parse/network_watcher.go create mode 100644 azurerm/internal/services/network/parse/network_watcher_test.go create mode 100644 azurerm/internal/services/network/validate/network_connection_monitor.go create mode 100644 azurerm/internal/services/network/validate/network_connection_monitor_test.go create mode 100644 azurerm/internal/services/network/validate/network_watcher.go diff --git a/azurerm/internal/services/network/network_connection_monitor_resource.go b/azurerm/internal/services/network/network_connection_monitor_resource.go index 2aae8d483fd6..e4ac1380a93e 100644 --- a/azurerm/internal/services/network/network_connection_monitor_resource.go +++ b/azurerm/internal/services/network/network_connection_monitor_resource.go @@ -12,6 +12,10 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + computeValidate "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/compute/validate" + logAnalyticsValidate "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/loganalytics/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/parse" + networkValidate "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/validate" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" @@ -43,78 +47,390 @@ func resourceArmNetworkConnectionMonitor() *schema.Resource { ValidateFunc: validation.StringIsNotEmpty, }, - "resource_group_name": azure.SchemaResourceGroupName(), - - "network_watcher_name": { + "network_watcher_id": { Type: schema.TypeString, Required: true, ForceNew: true, - ValidateFunc: validation.StringIsNotEmpty, + ValidateFunc: networkValidate.NetworkWatcherID, }, "location": azure.SchemaLocation(), "auto_start": { - Type: schema.TypeBool, - Optional: true, - ForceNew: true, - Default: true, + Type: schema.TypeBool, + Optional: true, + Computed: true, + Deprecated: "The field belongs to the v1 network connection monitor, which is now deprecated in favor of v2 by Azure. Please check the document (https://www.terraform.io/docs/providers/azurerm/r/network_connection_monitor.html) for the v2 properties.", }, "interval_in_seconds": { Type: schema.TypeInt, Optional: true, - Default: 60, + Computed: true, ValidateFunc: validation.IntAtLeast(30), + Deprecated: "The field belongs to the v1 network connection monitor, which is now deprecated in favor of v2 by Azure. Please check the document (https://www.terraform.io/docs/providers/azurerm/r/network_connection_monitor.html) for the v2 properties.", }, "source": { - Type: schema.TypeList, - Required: true, - MaxItems: 1, + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Deprecated: "The field belongs to the v1 network connection monitor, which is now deprecated in favor of v2 by Azure. Please check the document (https://www.terraform.io/docs/providers/azurerm/r/network_connection_monitor.html) for the v2 properties.", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "virtual_machine_id": { Type: schema.TypeString, - Required: true, + Optional: true, + Computed: true, ValidateFunc: azure.ValidateResourceID, + Deprecated: "The field belongs to the v1 network connection monitor, which is now deprecated in favor of v2 by Azure. Please check the document (https://www.terraform.io/docs/providers/azurerm/r/network_connection_monitor.html) for the v2 properties.", }, + "port": { Type: schema.TypeInt, Optional: true, - Default: 0, + Computed: true, ValidateFunc: validate.PortNumberOrZero, + Deprecated: "The field belongs to the v1 network connection monitor, which is now deprecated in favor of v2 by Azure. Please check the document (https://www.terraform.io/docs/providers/azurerm/r/network_connection_monitor.html) for the v2 properties.", }, }, }, }, "destination": { - Type: schema.TypeList, - Required: true, - MaxItems: 1, + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Deprecated: "The field belongs to the v1 network connection monitor, which is now deprecated in favor of v2 by Azure. Please check the document (https://www.terraform.io/docs/providers/azurerm/r/network_connection_monitor.html) for the v2 properties.", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "virtual_machine_id": { Type: schema.TypeString, Optional: true, + Computed: true, ValidateFunc: azure.ValidateResourceID, ConflictsWith: []string{"destination.0.address"}, + Deprecated: "The field belongs to the v1 network connection monitor, which is now deprecated in favor of v2 by Azure. Please check the document (https://www.terraform.io/docs/providers/azurerm/r/network_connection_monitor.html) for the v2 properties.", }, + "address": { Type: schema.TypeString, Optional: true, + Computed: true, ConflictsWith: []string{"destination.0.virtual_machine_id"}, + Deprecated: "The field belongs to the v1 network connection monitor, which is now deprecated in favor of v2 by Azure. Please check the document (https://www.terraform.io/docs/providers/azurerm/r/network_connection_monitor.html) for the v2 properties.", }, + "port": { Type: schema.TypeInt, - Required: true, + Optional: true, + Computed: true, ValidateFunc: validate.PortNumber, + Deprecated: "The field belongs to the v1 network connection monitor, which is now deprecated in favor of v2 by Azure. Please check the document (https://www.terraform.io/docs/providers/azurerm/r/network_connection_monitor.html) for the v2 properties.", + }, + }, + }, + }, + + "endpoint": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "address": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.Any( + validation.IsIPv4Address, + networkValidate.NetworkConnectionMonitorEndpointAddress, + ), + }, + + "filter": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "item": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "address": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: azure.ValidateResourceID, + }, + + "type": { + Type: schema.TypeString, + Optional: true, + Default: string(network.AgentAddress), + ValidateFunc: validation.StringInSlice([]string{ + string(network.AgentAddress), + }, false), + }, + }, + }, + }, + + "type": { + Type: schema.TypeString, + Optional: true, + Default: string(network.Include), + ValidateFunc: validation.StringInSlice([]string{ + string(network.Include), + }, false), + }, + }, + }, + }, + + "virtual_machine_id": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: computeValidate.VirtualMachineID, }, }, }, }, + "test_configuration": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "protocol": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(network.ConnectionMonitorTestConfigurationProtocolTCP), + string(network.ConnectionMonitorTestConfigurationProtocolHTTP), + string(network.ConnectionMonitorTestConfigurationProtocolIcmp), + }, false), + }, + + "http_configuration": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "method": { + Type: schema.TypeString, + Optional: true, + Default: string(network.Get), + ValidateFunc: validation.StringInSlice([]string{ + string(network.Get), + string(network.Post), + }, false), + }, + + "path": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: networkValidate.NetworkConnectionMonitorHttpPath, + }, + + "port": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validate.PortNumber, + }, + + "prefer_https": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + "request_header": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "value": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + }, + }, + + "valid_status_code_ranges": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: networkValidate.NetworkConnectionMonitorValidStatusCodeRanges, + }, + }, + }, + }, + }, + + "icmp_configuration": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "trace_route_enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + }, + }, + }, + + "preferred_ip_version": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + string(network.PreferredIPVersionIPv4), + string(network.PreferredIPVersionIPv6), + }, false), + }, + + "success_threshold": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "checks_failed_percent": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntBetween(0, 100), + }, + + "round_trip_time_ms": { + Type: schema.TypeFloat, + Optional: true, + ValidateFunc: validation.FloatAtLeast(0), + }, + }, + }, + }, + + "tcp_configuration": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "port": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validate.PortNumber, + }, + + "trace_route_enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + }, + }, + }, + + "test_frequency_in_seconds": { + Type: schema.TypeInt, + Optional: true, + Default: 60, + ValidateFunc: validation.IntBetween(30, 1800), + }, + }, + }, + }, + + "test_group": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "destination_endpoints": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + + "source_endpoints": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + + "test_configuration_names": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + }, + }, + }, + + // API accepts any value including empty string. + "notes": { + Type: schema.TypeString, + Optional: true, + }, + + "output_workspace_resource_ids": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + ConfigMode: schema.SchemaConfigModeAttr, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: logAnalyticsValidate.LogAnalyticsWorkspaceID, + }, + }, + "tags": tags.Schema(), }, } @@ -126,22 +442,19 @@ func resourceArmNetworkConnectionMonitorCreateUpdate(d *schema.ResourceData, met defer cancel() name := d.Get("name").(string) - watcherName := d.Get("network_watcher_name").(string) - resourceGroup := d.Get("resource_group_name").(string) location := azure.NormalizeLocation(d.Get("location").(string)) - autoStart := d.Get("auto_start").(bool) - intervalInSeconds := int32(d.Get("interval_in_seconds").(int)) - dest, err := expandArmNetworkConnectionMonitorDestination(d) + watcherId := d.Get("network_watcher_id").(string) + id, err := parse.NetworkWatcherID(watcherId) if err != nil { return err } if d.IsNewResource() { - existing, err := client.Get(ctx, resourceGroup, watcherName, name) + existing, err := client.Get(ctx, id.ResourceGroup, id.Name, name) if err != nil { if !utils.ResponseWasNotFound(existing.Response) { - return fmt.Errorf("Error checking for presence of existing Connection Monitor %q (Watcher %q / Resource Group %q): %s", name, watcherName, resourceGroup, err) + return fmt.Errorf("Error checking for presence of existing Connection Monitor %q (Watcher %q / Resource Group %q): %s", name, id.Name, id.ResourceGroup, err) } } @@ -150,34 +463,36 @@ func resourceArmNetworkConnectionMonitorCreateUpdate(d *schema.ResourceData, met } } - t := d.Get("tags").(map[string]interface{}) - properties := network.ConnectionMonitor{ Location: utils.String(location), - Tags: tags.Expand(t), + Tags: tags.Expand(d.Get("tags").(map[string]interface{})), ConnectionMonitorParameters: &network.ConnectionMonitorParameters{ - Source: expandArmNetworkConnectionMonitorSource(d), - Destination: dest, - AutoStart: utils.Bool(autoStart), - MonitoringIntervalInSeconds: utils.Int32(intervalInSeconds), + Endpoints: expandArmNetworkConnectionMonitorEndpoint(d.Get("endpoint").(*schema.Set).List()), + Outputs: expandArmNetworkConnectionMonitorOutput(d.Get("output_workspace_resource_ids").(*schema.Set).List()), + TestConfigurations: expandArmNetworkConnectionMonitorTestConfiguration(d.Get("test_configuration").(*schema.Set).List()), + TestGroups: expandArmNetworkConnectionMonitorTestGroup(d.Get("test_group").(*schema.Set).List()), }, } - future, err := client.CreateOrUpdate(ctx, resourceGroup, watcherName, name, properties) + if notes, ok := d.GetOk("notes"); ok { + properties.Notes = utils.String(notes.(string)) + } + + future, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.Name, name, properties) if err != nil { - return fmt.Errorf("Error creating Connection Monitor %q (Watcher %q / Resource Group %q): %+v", name, watcherName, resourceGroup, err) + return fmt.Errorf("Error creating Connection Monitor %q (Watcher %q / Resource Group %q): %+v", name, id.Name, id.ResourceGroup, err) } if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { - return fmt.Errorf("Error waiting for completion of Connection Monitor %q (Watcher %q / Resource Group %q): %+v", name, watcherName, resourceGroup, err) + return fmt.Errorf("Error waiting for completion of Connection Monitor %q (Watcher %q / Resource Group %q): %+v", name, id.Name, id.ResourceGroup, err) } - resp, err := client.Get(ctx, resourceGroup, watcherName, name) + resp, err := client.Get(ctx, id.ResourceGroup, id.Name, name) if err != nil { - return fmt.Errorf("Error retrieving Connection Monitor %q (Watcher %q / Resource Group %q): %+v", name, watcherName, resourceGroup, err) + return fmt.Errorf("Error retrieving Connection Monitor %q (Watcher %q / Resource Group %q): %+v", name, id.Name, id.ResourceGroup, err) } if resp.ID == nil { - return fmt.Errorf("Cannot read Connection Monitor %q (Watcher %q / Resource Group %q) ID", name, watcherName, resourceGroup) + return fmt.Errorf("Cannot read Connection Monitor %q (Watcher %q / Resource Group %q) ID", name, id.Name, id.ResourceGroup) } d.SetId(*resp.ID) @@ -186,46 +501,55 @@ func resourceArmNetworkConnectionMonitorCreateUpdate(d *schema.ResourceData, met } func resourceArmNetworkConnectionMonitorRead(d *schema.ResourceData, meta interface{}) error { + subscriptionId := meta.(*clients.Client).Account.SubscriptionId client := meta.(*clients.Client).Network.ConnectionMonitorsClient ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) defer cancel() - id, err := azure.ParseAzureResourceID(d.Id()) + id, err := parse.NetworkConnectionMonitorID(d.Id()) if err != nil { return err } - resourceGroup := id.ResourceGroup - watcherName := id.Path["networkWatchers"] - name := id.Path["NetworkConnectionMonitors"] - resp, err := client.Get(ctx, resourceGroup, watcherName, name) + resp, err := client.Get(ctx, id.ResourceGroup, id.WatcherName, id.Name) if err != nil { if utils.ResponseWasNotFound(resp.Response) { d.SetId("") return nil } - return fmt.Errorf("Error reading Connection Monitor %q (Watcher %q / Resource Group %q) %+v", name, watcherName, resourceGroup, err) + return fmt.Errorf("Error reading Connection Monitor %q (Watcher %q / Resource Group %q) %+v", id.Name, id.WatcherName, id.ResourceGroup, err) + } + + if resp.ConnectionMonitorType == network.SingleSourceDestination { + return fmt.Errorf("the resource created via API version 2019-06-01 or before (a.k.a v1) isn't compatible to this version of provider. Please migrate to v2 resource.") } - d.Set("name", name) - d.Set("network_watcher_name", watcherName) - d.Set("resource_group_name", resourceGroup) + d.Set("name", id.Name) + + networkWatcherId := parse.NewNetworkWatcherID(id.ResourceGroup, id.WatcherName) + d.Set("network_watcher_id", networkWatcherId.ID(subscriptionId)) + if location := resp.Location; location != nil { d.Set("location", azure.NormalizeLocation(*location)) } if props := resp.ConnectionMonitorResultProperties; props != nil { - d.Set("auto_start", props.AutoStart) - d.Set("interval_in_seconds", props.MonitoringIntervalInSeconds) + d.Set("notes", props.Notes) + + if err := d.Set("endpoint", flattenArmNetworkConnectionMonitorEndpoint(props.Endpoints)); err != nil { + return fmt.Errorf("setting `endpoint`: %+v", err) + } + + if err := d.Set("output_workspace_resource_ids", flattenArmNetworkConnectionMonitorOutput(props.Outputs)); err != nil { + return fmt.Errorf("setting `output`: %+v", err) + } - source := flattenArmNetworkConnectionMonitorSource(props.Source) - if err := d.Set("source", source); err != nil { - return fmt.Errorf("Error setting `source`: %+v", err) + if err := d.Set("test_configuration", flattenArmNetworkConnectionMonitorTestConfiguration(props.TestConfigurations)); err != nil { + return fmt.Errorf("setting `test_configuration`: %+v", err) } - dest := flattenArmNetworkConnectionMonitorDestination(props.Destination) - if err := d.Set("destination", dest); err != nil { - return fmt.Errorf("Error setting `destination`: %+v", err) + if err := d.Set("test_group", flattenArmNetworkConnectionMonitorTestGroup(props.TestGroups)); err != nil { + return fmt.Errorf("setting `test_group`: %+v", err) } } @@ -237,102 +561,532 @@ func resourceArmNetworkConnectionMonitorDelete(d *schema.ResourceData, meta inte ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) defer cancel() - id, err := azure.ParseAzureResourceID(d.Id()) + id, err := parse.NetworkConnectionMonitorID(d.Id()) if err != nil { return err } - resourceGroup := id.ResourceGroup - watcherName := id.Path["networkWatchers"] - name := id.Path["NetworkConnectionMonitors"] - future, err := client.Delete(ctx, resourceGroup, watcherName, name) + future, err := client.Delete(ctx, id.ResourceGroup, id.WatcherName, id.Name) if err != nil { if !response.WasNotFound(future.Response()) { - return fmt.Errorf("Error deleting Connection Monitor %q (Watcher %q / Resource Group %q): %+v", name, watcherName, resourceGroup, err) + return fmt.Errorf("Error deleting Connection Monitor %q (Watcher %q / Resource Group %q): %+v", id.Name, id.WatcherName, id.ResourceGroup, err) } } if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { - return fmt.Errorf("Error waiting for the deletion of Connection Monitor %q (Watcher %q / Resource Group %q): %+v", name, watcherName, resourceGroup, err) + return fmt.Errorf("Error waiting for the deletion of Connection Monitor %q (Watcher %q / Resource Group %q): %+v", id.Name, id.WatcherName, id.ResourceGroup, err) } return nil } -func flattenArmNetworkConnectionMonitorSource(input *network.ConnectionMonitorSource) []interface{} { +func expandArmNetworkConnectionMonitorEndpoint(input []interface{}) *[]network.ConnectionMonitorEndpoint { + results := make([]network.ConnectionMonitorEndpoint, 0) + + for _, item := range input { + v := item.(map[string]interface{}) + + result := network.ConnectionMonitorEndpoint{ + Name: utils.String(v["name"].(string)), + Filter: expandArmNetworkConnectionMonitorEndpointFilter(v["filter"].([]interface{})), + } + + if address := v["address"]; address != "" { + result.Address = utils.String(address.(string)) + } + + if resourceId := v["virtual_machine_id"]; resourceId != "" { + result.ResourceID = utils.String(resourceId.(string)) + } + + results = append(results, result) + } + + return &results +} + +func expandArmNetworkConnectionMonitorEndpointFilter(input []interface{}) *network.ConnectionMonitorEndpointFilter { + if len(input) == 0 { + return nil + } + + v := input[0].(map[string]interface{}) + + return &network.ConnectionMonitorEndpointFilter{ + Type: network.ConnectionMonitorEndpointFilterType(v["type"].(string)), + Items: expandArmNetworkConnectionMonitorEndpointFilterItem(v["item"].(*schema.Set).List()), + } +} + +func expandArmNetworkConnectionMonitorEndpointFilterItem(input []interface{}) *[]network.ConnectionMonitorEndpointFilterItem { + if len(input) == 0 { + return nil + } + + results := make([]network.ConnectionMonitorEndpointFilterItem, 0) + + for _, item := range input { + v := item.(map[string]interface{}) + + result := network.ConnectionMonitorEndpointFilterItem{ + Type: network.ConnectionMonitorEndpointFilterItemType(v["type"].(string)), + } + + if address := v["address"]; address != "" { + result.Address = utils.String(address.(string)) + } + + results = append(results, result) + } + + return &results +} + +func expandArmNetworkConnectionMonitorTestConfiguration(input []interface{}) *[]network.ConnectionMonitorTestConfiguration { + results := make([]network.ConnectionMonitorTestConfiguration, 0) + + for _, item := range input { + v := item.(map[string]interface{}) + + result := network.ConnectionMonitorTestConfiguration{ + Name: utils.String(v["name"].(string)), + HTTPConfiguration: expandArmNetworkConnectionMonitorHTTPConfiguration(v["http_configuration"].([]interface{})), + IcmpConfiguration: expandArmNetworkConnectionMonitorIcmpConfiguration(v["icmp_configuration"].([]interface{})), + Protocol: network.ConnectionMonitorTestConfigurationProtocol(v["protocol"].(string)), + SuccessThreshold: expandArmNetworkConnectionMonitorSuccessThreshold(v["success_threshold"].([]interface{})), + TCPConfiguration: expandArmNetworkConnectionMonitorTCPConfiguration(v["tcp_configuration"].([]interface{})), + TestFrequencySec: utils.Int32(int32(v["test_frequency_in_seconds"].(int))), + } + + if preferredIPVersion := v["preferred_ip_version"]; preferredIPVersion != "" { + result.PreferredIPVersion = network.PreferredIPVersion(preferredIPVersion.(string)) + } + + results = append(results, result) + } + + return &results +} + +func expandArmNetworkConnectionMonitorHTTPConfiguration(input []interface{}) *network.ConnectionMonitorHTTPConfiguration { + if len(input) == 0 { + return nil + } + + v := input[0].(map[string]interface{}) + + props := &network.ConnectionMonitorHTTPConfiguration{ + Method: network.HTTPConfigurationMethod(v["method"].(string)), + PreferHTTPS: utils.Bool(v["prefer_https"].(bool)), + RequestHeaders: expandArmNetworkConnectionMonitorHTTPHeader(v["request_header"].(*schema.Set).List()), + } + + if path := v["path"]; path != "" { + props.Path = utils.String(path.(string)) + } + + if port := v["port"]; port != 0 { + props.Port = utils.Int32(int32(port.(int))) + } + + if ranges := v["valid_status_code_ranges"].(*schema.Set).List(); len(ranges) != 0 { + props.ValidStatusCodeRanges = utils.ExpandStringSlice(ranges) + } + + return props +} + +func expandArmNetworkConnectionMonitorTCPConfiguration(input []interface{}) *network.ConnectionMonitorTCPConfiguration { + if len(input) == 0 { + return nil + } + + v := input[0].(map[string]interface{}) + + return &network.ConnectionMonitorTCPConfiguration{ + Port: utils.Int32(int32(v["port"].(int))), + DisableTraceRoute: utils.Bool(!v["trace_route_enabled"].(bool)), + } +} + +func expandArmNetworkConnectionMonitorIcmpConfiguration(input []interface{}) *network.ConnectionMonitorIcmpConfiguration { + if len(input) == 0 { + return nil + } + + v := input[0].(map[string]interface{}) + + return &network.ConnectionMonitorIcmpConfiguration{ + DisableTraceRoute: utils.Bool(!v["trace_route_enabled"].(bool)), + } +} + +func expandArmNetworkConnectionMonitorSuccessThreshold(input []interface{}) *network.ConnectionMonitorSuccessThreshold { + if len(input) == 0 { + return nil + } + + v := input[0].(map[string]interface{}) + + return &network.ConnectionMonitorSuccessThreshold{ + ChecksFailedPercent: utils.Int32(int32(v["checks_failed_percent"].(int))), + RoundTripTimeMs: utils.Float(v["round_trip_time_ms"].(float64)), + } +} + +func expandArmNetworkConnectionMonitorHTTPHeader(input []interface{}) *[]network.HTTPHeader { + if len(input) == 0 { + return nil + } + + results := make([]network.HTTPHeader, 0) + + for _, item := range input { + v := item.(map[string]interface{}) + + result := network.HTTPHeader{ + Name: utils.String(v["name"].(string)), + Value: utils.String(v["value"].(string)), + } + + results = append(results, result) + } + + return &results +} + +func expandArmNetworkConnectionMonitorTestGroup(input []interface{}) *[]network.ConnectionMonitorTestGroup { + results := make([]network.ConnectionMonitorTestGroup, 0) + + for _, item := range input { + v := item.(map[string]interface{}) + + result := network.ConnectionMonitorTestGroup{ + Name: utils.String(v["name"].(string)), + Destinations: utils.ExpandStringSlice(v["destination_endpoints"].(*schema.Set).List()), + Disable: utils.Bool(!v["enabled"].(bool)), + Sources: utils.ExpandStringSlice(v["source_endpoints"].(*schema.Set).List()), + TestConfigurations: utils.ExpandStringSlice(v["test_configuration_names"].(*schema.Set).List()), + } + + results = append(results, result) + } + + return &results +} + +func expandArmNetworkConnectionMonitorOutput(input []interface{}) *[]network.ConnectionMonitorOutput { + results := make([]network.ConnectionMonitorOutput, 0) + + for _, item := range input { + result := network.ConnectionMonitorOutput{ + Type: network.Workspace, + WorkspaceSettings: &network.ConnectionMonitorWorkspaceSettings{ + WorkspaceResourceID: utils.String(item.(string)), + }, + } + + results = append(results, result) + } + + return &results +} + +func flattenArmNetworkConnectionMonitorEndpoint(input *[]network.ConnectionMonitorEndpoint) []interface{} { + results := make([]interface{}, 0) if input == nil { - return []interface{}{} + return results + } + + for _, item := range *input { + var name string + if item.Name != nil { + name = *item.Name + } + + var address string + if item.Address != nil { + address = *item.Address + } + + var resourceId string + if item.ResourceID != nil { + resourceId = *item.ResourceID + } + + v := map[string]interface{}{ + "name": name, + "address": address, + "filter": flattenArmNetworkConnectionMonitorEndpointFilter(item.Filter), + "virtual_machine_id": resourceId, + } + + results = append(results, v) + } + return results +} + +func flattenArmNetworkConnectionMonitorEndpointFilter(input *network.ConnectionMonitorEndpointFilter) []interface{} { + if input == nil { + return make([]interface{}, 0) + } + + var t network.ConnectionMonitorEndpointFilterType + if input.Type != "" { + t = input.Type + } + return []interface{}{ + map[string]interface{}{ + "item": flattenArmNetworkConnectionMonitorEndpointFilterItem(input.Items), + "type": t, + }, + } +} + +func flattenArmNetworkConnectionMonitorEndpointFilterItem(input *[]network.ConnectionMonitorEndpointFilterItem) []interface{} { + results := make([]interface{}, 0) + if input == nil { + return results + } + + for _, item := range *input { + var address string + if item.Address != nil { + address = *item.Address + } + + var t network.ConnectionMonitorEndpointFilterItemType + if item.Type != "" { + t = item.Type + } + + v := map[string]interface{}{ + "address": address, + "type": t, + } + + results = append(results, v) } - output := make(map[string]interface{}) + return results +} - if resourceID := input.ResourceID; resourceID != nil { - output["virtual_machine_id"] = *resourceID +func flattenArmNetworkConnectionMonitorTestConfiguration(input *[]network.ConnectionMonitorTestConfiguration) []interface{} { + results := make([]interface{}, 0) + if input == nil { + return results } - if port := input.Port; port != nil { - output["port"] = *port + + for _, item := range *input { + var name string + if item.Name != nil { + name = *item.Name + } + + var protocol network.ConnectionMonitorTestConfigurationProtocol + if item.Protocol != "" { + protocol = item.Protocol + } + + var preferredIpVersion network.PreferredIPVersion + if item.PreferredIPVersion != "" { + preferredIpVersion = item.PreferredIPVersion + } + + var testFrequencySec int32 + if item.TestFrequencySec != nil { + testFrequencySec = *item.TestFrequencySec + } + + v := map[string]interface{}{ + "name": name, + "protocol": protocol, + "http_configuration": flattenArmNetworkConnectionMonitorHTTPConfiguration(item.HTTPConfiguration), + "icmp_configuration": flattenArmNetworkConnectionMonitorIcmpConfiguration(item.IcmpConfiguration), + "preferred_ip_version": preferredIpVersion, + "success_threshold": flattenArmNetworkConnectionMonitorSuccessThreshold(item.SuccessThreshold), + "tcp_configuration": flattenArmNetworkConnectionMonitorTCPConfiguration(item.TCPConfiguration), + "test_frequency_in_seconds": testFrequencySec, + } + + results = append(results, v) } - return []interface{}{output} + return results } -func expandArmNetworkConnectionMonitorSource(d *schema.ResourceData) *network.ConnectionMonitorSource { - sources := d.Get("source").([]interface{}) - source := sources[0].(map[string]interface{}) +func flattenArmNetworkConnectionMonitorHTTPConfiguration(input *network.ConnectionMonitorHTTPConfiguration) []interface{} { + if input == nil { + return make([]interface{}, 0) + } + + var method network.HTTPConfigurationMethod + if input.Method != "" { + method = input.Method + } + + var p string + if input.Path != nil { + p = *input.Path + } + + var port int32 + if input.Port != nil { + port = *input.Port + } + + var preferHttps bool + if input.PreferHTTPS != nil { + preferHttps = *input.PreferHTTPS + } - monitorSource := network.ConnectionMonitorSource{} - if v := source["virtual_machine_id"]; v != "" { - monitorSource.ResourceID = utils.String(v.(string)) + return []interface{}{ + map[string]interface{}{ + "method": method, + "path": p, + "port": port, + "prefer_https": preferHttps, + "request_header": flattenArmNetworkConnectionMonitorHTTPHeader(input.RequestHeaders), + "valid_status_code_ranges": utils.FlattenStringSlice(input.ValidStatusCodeRanges), + }, } - if v := source["port"]; v != "" { - monitorSource.Port = utils.Int32(int32(v.(int))) +} + +func flattenArmNetworkConnectionMonitorIcmpConfiguration(input *network.ConnectionMonitorIcmpConfiguration) []interface{} { + if input == nil { + return make([]interface{}, 0) + } + + var enableTraceRoute bool + if input.DisableTraceRoute != nil { + enableTraceRoute = !*input.DisableTraceRoute } - return &monitorSource + return []interface{}{ + map[string]interface{}{ + "trace_route_enabled": enableTraceRoute, + }, + } } -func flattenArmNetworkConnectionMonitorDestination(input *network.ConnectionMonitorDestination) []interface{} { +func flattenArmNetworkConnectionMonitorSuccessThreshold(input *network.ConnectionMonitorSuccessThreshold) []interface{} { if input == nil { - return []interface{}{} + return make([]interface{}, 0) + } + + var checksFailedPercent int32 + if input.ChecksFailedPercent != nil { + checksFailedPercent = *input.ChecksFailedPercent + } + + var roundTripTimeMs float64 + if input.RoundTripTimeMs != nil { + roundTripTimeMs = *input.RoundTripTimeMs } - output := make(map[string]interface{}) + return []interface{}{ + map[string]interface{}{ + "checks_failed_percent": checksFailedPercent, + "round_trip_time_ms": roundTripTimeMs, + }, + } +} - // When monitoring a VM, the address field will contain the current address - // of the VM. We only want to copy over the address field if the virtual - // machine field is not set to avoid unwanted diffs. - if resourceID := input.ResourceID; resourceID != nil { - output["virtual_machine_id"] = *resourceID - } else if address := input.Address; address != nil { - output["address"] = *address +func flattenArmNetworkConnectionMonitorTCPConfiguration(input *network.ConnectionMonitorTCPConfiguration) []interface{} { + if input == nil { + return make([]interface{}, 0) } - if port := input.Port; port != nil { - output["port"] = *port + var enableTraceRoute bool + if input.DisableTraceRoute != nil { + enableTraceRoute = !*input.DisableTraceRoute } - return []interface{}{output} + var port int32 + if input.Port != nil { + port = *input.Port + } + + return []interface{}{ + map[string]interface{}{ + "trace_route_enabled": enableTraceRoute, + "port": port, + }, + } } -func expandArmNetworkConnectionMonitorDestination(d *schema.ResourceData) (*network.ConnectionMonitorDestination, error) { - dests := d.Get("destination").([]interface{}) - dest := dests[0].(map[string]interface{}) +func flattenArmNetworkConnectionMonitorHTTPHeader(input *[]network.HTTPHeader) []interface{} { + results := make([]interface{}, 0) + if input == nil { + return results + } - monitorDest := network.ConnectionMonitorDestination{} + for _, item := range *input { + var name string + if item.Name != nil { + name = *item.Name + } + + var value string + if item.Value != nil { + value = *item.Value + } - if v := dest["virtual_machine_id"]; v != "" { - monitorDest.ResourceID = utils.String(v.(string)) + v := map[string]interface{}{ + "name": name, + "value": value, + } + + results = append(results, v) } - if v := dest["address"]; v != "" { - monitorDest.Address = utils.String(v.(string)) + + return results +} + +func flattenArmNetworkConnectionMonitorTestGroup(input *[]network.ConnectionMonitorTestGroup) []interface{} { + results := make([]interface{}, 0) + if input == nil { + return results + } + + for _, item := range *input { + var name string + if item.Name != nil { + name = *item.Name + } + + var disable bool + if item.Disable != nil { + disable = *item.Disable + } + + v := map[string]interface{}{ + "name": name, + "destination_endpoints": utils.FlattenStringSlice(item.Destinations), + "source_endpoints": utils.FlattenStringSlice(item.Sources), + "test_configuration_names": utils.FlattenStringSlice(item.TestConfigurations), + "enabled": !disable, + } + + results = append(results, v) } - if v := dest["port"]; v != "" { - monitorDest.Port = utils.Int32(int32(v.(int))) + return results +} + +func flattenArmNetworkConnectionMonitorOutput(input *[]network.ConnectionMonitorOutput) []interface{} { + results := make([]interface{}, 0) + if input == nil { + return results } - if monitorDest.ResourceID == nil && monitorDest.Address == nil { - return nil, fmt.Errorf("Error: either `destination.virtual_machine_id` or `destination.address` must be specified") + for _, item := range *input { + var workspaceResourceId string + if item.WorkspaceSettings != nil && item.WorkspaceSettings.WorkspaceResourceID != nil { + workspaceResourceId = *item.WorkspaceSettings.WorkspaceResourceID + } + + results = append(results, workspaceResourceId) } - return &monitorDest, nil + return results } diff --git a/azurerm/internal/services/network/network_watcher_resource.go b/azurerm/internal/services/network/network_watcher_resource.go index d652832eb797..fec85ba4ac46 100644 --- a/azurerm/internal/services/network/network_watcher_resource.go +++ b/azurerm/internal/services/network/network_watcher_resource.go @@ -10,6 +10,7 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/parse" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" @@ -100,24 +101,22 @@ func resourceArmNetworkWatcherRead(d *schema.ResourceData, meta interface{}) err ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) defer cancel() - id, err := azure.ParseAzureResourceID(d.Id()) + id, err := parse.NetworkWatcherID(d.Id()) if err != nil { return err } - resourceGroup := id.ResourceGroup - name := id.Path["networkWatchers"] - resp, err := client.Get(ctx, resourceGroup, name) + resp, err := client.Get(ctx, id.ResourceGroup, id.Name) if err != nil { if utils.ResponseWasNotFound(resp.Response) { d.SetId("") return nil } - return fmt.Errorf("Error making Read request on Network Watcher %q (Resource Group %q): %+v", name, resourceGroup, err) + return fmt.Errorf("Error making Read request on Network Watcher %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) } d.Set("name", resp.Name) - d.Set("resource_group_name", resourceGroup) + d.Set("resource_group_name", id.ResourceGroup) if location := resp.Location; location != nil { d.Set("location", azure.NormalizeLocation(*location)) } @@ -130,22 +129,20 @@ func resourceArmNetworkWatcherDelete(d *schema.ResourceData, meta interface{}) e ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) defer cancel() - id, err := azure.ParseAzureResourceID(d.Id()) + id, err := parse.NetworkWatcherID(d.Id()) if err != nil { return err } - resourceGroup := id.ResourceGroup - name := id.Path["networkWatchers"] - future, err := client.Delete(ctx, resourceGroup, name) + future, err := client.Delete(ctx, id.ResourceGroup, id.Name) if err != nil { if !response.WasNotFound(future.Response()) { - return fmt.Errorf("Error deleting Network Watcher %q (Resource Group %q): %+v", name, resourceGroup, err) + return fmt.Errorf("Error deleting Network Watcher %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) } } if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { - return fmt.Errorf("Error waiting for the deletion of Network Watcher %q (Resource Group %q): %+v", name, resourceGroup, err) + return fmt.Errorf("Error waiting for the deletion of Network Watcher %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) } return nil diff --git a/azurerm/internal/services/network/parse/network_connection_monitor.go b/azurerm/internal/services/network/parse/network_connection_monitor.go new file mode 100644 index 000000000000..f0c4d6f8ce71 --- /dev/null +++ b/azurerm/internal/services/network/parse/network_connection_monitor.go @@ -0,0 +1,36 @@ +package parse + +import ( + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" +) + +type NetworkConnectionMonitorId struct { + ResourceGroup string + WatcherName string + Name string +} + +func NetworkConnectionMonitorID(input string) (*NetworkConnectionMonitorId, error) { + id, err := azure.ParseAzureResourceID(input) + if err != nil { + return nil, err + } + + connectionMonitor := NetworkConnectionMonitorId{ + ResourceGroup: id.ResourceGroup, + } + + if connectionMonitor.WatcherName, err = id.PopSegment("networkWatchers"); err != nil { + return nil, err + } + + if connectionMonitor.Name, err = id.PopSegment("connectionMonitors"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &connectionMonitor, nil +} diff --git a/azurerm/internal/services/network/parse/network_connection_monitor_test.go b/azurerm/internal/services/network/parse/network_connection_monitor_test.go new file mode 100644 index 000000000000..fbc7669dd3fc --- /dev/null +++ b/azurerm/internal/services/network/parse/network_connection_monitor_test.go @@ -0,0 +1,104 @@ +package parse + +import ( + "testing" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/resourceid" +) + +var _ resourceid.Formatter = NetworkWatcherId{} + +func TestNetworkWatcherIDFormatter(t *testing.T) { + subscriptionId := "12345678-1234-5678-1234-123456789012" + actual := NewNetworkWatcherID("group1", "watcher1").ID(subscriptionId) + expected := "/subscriptions/12345678-1234-5678-1234-123456789012/resourceGroups/group1/providers/Microsoft.Network/networkWatchers/watcher1" + + if actual != expected { + t.Fatalf("Expected %q but got %q", expected, actual) + } +} + +func TestNetworkConnectionMonitorID(t *testing.T) { + testData := []struct { + Name string + Input string + Error bool + Expect *NetworkConnectionMonitorId + }{ + { + Name: "Empty", + Input: "", + Error: true, + }, + { + Name: "No Resource Groups Segment", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000", + Error: true, + }, + { + Name: "No Resource Groups Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/", + Error: true, + }, + { + Name: "Resource Group ID", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/", + Error: true, + }, + { + Name: "Missing Network Watcher Key", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.Network/networkWatchers/", + Error: true, + }, + { + Name: "Missing Network Watcher Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.Network/networkWatchers/watcher1", + Error: true, + }, + { + Name: "Missing Network Connection Monitor Key", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.Network/networkWatchers/watcher1/connectionMonitors", + Error: true, + }, + { + Name: "Namespace Network Connection Monitor Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.Network/networkWatchers/watcher1/connectionMonitors/connectionMonitor1", + Error: false, + Expect: &NetworkConnectionMonitorId{ + ResourceGroup: "group1", + WatcherName: "watcher1", + Name: "connectionMonitor1", + }, + }, + { + Name: "Wrong Segment", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.Network/networkWatchers/watcher1/NetworkConnectionMonitors/connectionMonitor1", + Error: true, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Name) + + actual, err := NetworkConnectionMonitorID(v.Input) + if err != nil { + if v.Error { + continue + } + + t.Fatalf("Expected a value but got an error: %s", err) + } + + if actual.Name != v.Expect.Name { + t.Fatalf("Expected %q but got %q for Name", v.Expect.Name, actual.Name) + } + + if actual.WatcherName != v.Expect.WatcherName { + t.Fatalf("Expected %q but got %q for Name", v.Expect.WatcherName, actual.WatcherName) + } + + if actual.ResourceGroup != v.Expect.ResourceGroup { + t.Fatalf("Expected %q but got %q for Resource Group", v.Expect.ResourceGroup, actual.ResourceGroup) + } + } +} diff --git a/azurerm/internal/services/network/parse/network_watcher.go b/azurerm/internal/services/network/parse/network_watcher.go new file mode 100644 index 000000000000..417de4036fef --- /dev/null +++ b/azurerm/internal/services/network/parse/network_watcher.go @@ -0,0 +1,45 @@ +package parse + +import ( + "fmt" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" +) + +type NetworkWatcherId struct { + ResourceGroup string + Name string +} + +func NetworkWatcherID(input string) (*NetworkWatcherId, error) { + id, err := azure.ParseAzureResourceID(input) + if err != nil { + return nil, err + } + + watcher := NetworkWatcherId{ + ResourceGroup: id.ResourceGroup, + } + + if watcher.Name, err = id.PopSegment("networkWatchers"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &watcher, nil +} + +func (id NetworkWatcherId) ID(subscriptionId string) string { + return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/networkWatchers/%s", + subscriptionId, id.ResourceGroup, id.Name) +} + +func NewNetworkWatcherID(resourceGroup, name string) NetworkWatcherId { + return NetworkWatcherId{ + ResourceGroup: resourceGroup, + Name: name, + } +} diff --git a/azurerm/internal/services/network/parse/network_watcher_test.go b/azurerm/internal/services/network/parse/network_watcher_test.go new file mode 100644 index 000000000000..d9bce67ed020 --- /dev/null +++ b/azurerm/internal/services/network/parse/network_watcher_test.go @@ -0,0 +1,69 @@ +package parse + +import ( + "testing" +) + +func TestNetworkWatcherID(t *testing.T) { + testData := []struct { + Name string + Input string + Error bool + Expect *NetworkWatcherId + }{ + { + Name: "Empty", + Input: "", + Error: true, + }, + { + Name: "No Resource Groups Segment", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000", + Error: true, + }, + { + Name: "No Resource Groups Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/", + Error: true, + }, + { + Name: "Resource Group ID", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/", + Error: true, + }, + { + Name: "Missing Network Watcher Key", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.Network/networkWatchers/", + Error: true, + }, + { + Name: "Missing Network Watcher Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.Network/networkWatchers/watcher1", + Expect: &NetworkWatcherId{ + ResourceGroup: "group1", + Name: "watcher1", + }, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Name) + + actual, err := NetworkWatcherID(v.Input) + if err != nil { + if v.Error { + continue + } + + t.Fatalf("Expected a value but got an error: %s", err) + } + + if actual.Name != v.Expect.Name { + t.Fatalf("Expected %q but got %q for Name", v.Expect.Name, actual.Name) + } + + if actual.ResourceGroup != v.Expect.ResourceGroup { + t.Fatalf("Expected %q but got %q for Resource Group", v.Expect.ResourceGroup, actual.ResourceGroup) + } + } +} diff --git a/azurerm/internal/services/network/tests/network_connection_monitor_resource_test.go b/azurerm/internal/services/network/tests/network_connection_monitor_resource_test.go index 0da2b740393d..ae1ccc166b60 100644 --- a/azurerm/internal/services/network/tests/network_connection_monitor_resource_test.go +++ b/azurerm/internal/services/network/tests/network_connection_monitor_resource_test.go @@ -8,9 +8,9 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/terraform" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/parse" ) func testAccAzureRMNetworkConnectionMonitor_addressBasic(t *testing.T) { @@ -25,10 +25,6 @@ func testAccAzureRMNetworkConnectionMonitor_addressBasic(t *testing.T) { Config: testAccAzureRMNetworkConnectionMonitor_basicAddressConfig(data), Check: resource.ComposeTestCheckFunc( testCheckAzureRMNetworkConnectionMonitorExists(data.ResourceName), - resource.TestCheckResourceAttrSet(data.ResourceName, "resource_group_name"), - resource.TestCheckResourceAttr(data.ResourceName, "location", azure.NormalizeLocation(data.Locations.Primary)), - resource.TestCheckResourceAttr(data.ResourceName, "auto_start", "true"), - resource.TestCheckResourceAttr(data.ResourceName, "interval_in_seconds", "60"), ), }, data.ImportStep(), @@ -61,22 +57,15 @@ func testAccAzureRMNetworkConnectionMonitor_requiresImport(t *testing.T) { func testAccAzureRMNetworkConnectionMonitor_addressComplete(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_network_connection_monitor", "test") - autoStart := "false" - resource.Test(t, resource.TestCase{ PreCheck: func() { acceptance.PreCheck(t) }, Providers: acceptance.SupportedProviders, CheckDestroy: testCheckAzureRMNetworkConnectionMonitorDestroy, Steps: []resource.TestStep{ { - Config: testAccAzureRMNetworkConnectionMonitor_completeAddressConfig(data, autoStart), + Config: testAccAzureRMNetworkConnectionMonitor_completeAddressConfig(data), Check: resource.ComposeTestCheckFunc( testCheckAzureRMNetworkConnectionMonitorExists(data.ResourceName), - resource.TestCheckResourceAttr(data.ResourceName, "auto_start", "false"), - resource.TestCheckResourceAttr(data.ResourceName, "interval_in_seconds", "30"), - resource.TestCheckResourceAttr(data.ResourceName, "source.0.port", "20020"), - resource.TestCheckResourceAttr(data.ResourceName, "tags.%", "1"), - resource.TestCheckResourceAttr(data.ResourceName, "tags.env", "test"), ), }, data.ImportStep(), @@ -87,8 +76,6 @@ func testAccAzureRMNetworkConnectionMonitor_addressComplete(t *testing.T) { func testAccAzureRMNetworkConnectionMonitor_addressUpdate(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_network_connection_monitor", "test") - autoStart := "true" - resource.Test(t, resource.TestCase{ PreCheck: func() { acceptance.PreCheck(t) }, Providers: acceptance.SupportedProviders, @@ -101,14 +88,9 @@ func testAccAzureRMNetworkConnectionMonitor_addressUpdate(t *testing.T) { ), }, { - Config: testAccAzureRMNetworkConnectionMonitor_completeAddressConfig(data, autoStart), + Config: testAccAzureRMNetworkConnectionMonitor_completeAddressConfig(data), Check: resource.ComposeTestCheckFunc( testCheckAzureRMNetworkConnectionMonitorExists(data.ResourceName), - resource.TestCheckResourceAttr(data.ResourceName, "auto_start", "true"), - resource.TestCheckResourceAttr(data.ResourceName, "interval_in_seconds", "30"), - resource.TestCheckResourceAttr(data.ResourceName, "source.0.port", "20020"), - resource.TestCheckResourceAttr(data.ResourceName, "tags.%", "1"), - resource.TestCheckResourceAttr(data.ResourceName, "tags.env", "test"), ), }, data.ImportStep(), @@ -128,10 +110,6 @@ func testAccAzureRMNetworkConnectionMonitor_vmBasic(t *testing.T) { Config: testAccAzureRMNetworkConnectionMonitor_basicVmConfig(data), Check: resource.ComposeTestCheckFunc( testCheckAzureRMNetworkConnectionMonitorExists(data.ResourceName), - resource.TestCheckResourceAttrSet(data.ResourceName, "resource_group_name"), - resource.TestCheckResourceAttr(data.ResourceName, "location", azure.NormalizeLocation(data.Locations.Primary)), - resource.TestCheckResourceAttr(data.ResourceName, "auto_start", "true"), - resource.TestCheckResourceAttr(data.ResourceName, "interval_in_seconds", "60"), ), }, data.ImportStep(), @@ -142,22 +120,15 @@ func testAccAzureRMNetworkConnectionMonitor_vmBasic(t *testing.T) { func testAccAzureRMNetworkConnectionMonitor_vmComplete(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_network_connection_monitor", "test") - autoStart := "false" - resource.Test(t, resource.TestCase{ PreCheck: func() { acceptance.PreCheck(t) }, Providers: acceptance.SupportedProviders, CheckDestroy: testCheckAzureRMNetworkConnectionMonitorDestroy, Steps: []resource.TestStep{ { - Config: testAccAzureRMNetworkConnectionMonitor_completeVmConfig(data, autoStart), + Config: testAccAzureRMNetworkConnectionMonitor_completeVmConfig(data), Check: resource.ComposeTestCheckFunc( testCheckAzureRMNetworkConnectionMonitorExists(data.ResourceName), - resource.TestCheckResourceAttr(data.ResourceName, "auto_start", "false"), - resource.TestCheckResourceAttr(data.ResourceName, "interval_in_seconds", "30"), - resource.TestCheckResourceAttr(data.ResourceName, "source.0.port", "20020"), - resource.TestCheckResourceAttr(data.ResourceName, "tags.%", "1"), - resource.TestCheckResourceAttr(data.ResourceName, "tags.env", "test"), ), }, data.ImportStep(), @@ -180,14 +151,9 @@ func testAccAzureRMNetworkConnectionMonitor_vmUpdate(t *testing.T) { ), }, { - Config: testAccAzureRMNetworkConnectionMonitor_completeVmConfig(data, "true"), + Config: testAccAzureRMNetworkConnectionMonitor_completeVmConfig(data), Check: resource.ComposeTestCheckFunc( testCheckAzureRMNetworkConnectionMonitorExists(data.ResourceName), - resource.TestCheckResourceAttr(data.ResourceName, "auto_start", "true"), - resource.TestCheckResourceAttr(data.ResourceName, "interval_in_seconds", "30"), - resource.TestCheckResourceAttr(data.ResourceName, "source.0.port", "20020"), - resource.TestCheckResourceAttr(data.ResourceName, "tags.%", "1"), - resource.TestCheckResourceAttr(data.ResourceName, "tags.env", "test"), ), }, data.ImportStep(), @@ -207,21 +173,18 @@ func testAccAzureRMNetworkConnectionMonitor_destinationUpdate(t *testing.T) { Config: testAccAzureRMNetworkConnectionMonitor_basicAddressConfig(data), Check: resource.ComposeTestCheckFunc( testCheckAzureRMNetworkConnectionMonitorExists(data.ResourceName), - resource.TestCheckResourceAttrSet(data.ResourceName, "destination.0.address"), ), }, { Config: testAccAzureRMNetworkConnectionMonitor_basicVmConfig(data), Check: resource.ComposeTestCheckFunc( testCheckAzureRMNetworkConnectionMonitorExists(data.ResourceName), - resource.TestCheckResourceAttrSet(data.ResourceName, "destination.0.virtual_machine_id"), ), }, { Config: testAccAzureRMNetworkConnectionMonitor_basicAddressConfig(data), Check: resource.ComposeTestCheckFunc( testCheckAzureRMNetworkConnectionMonitorExists(data.ResourceName), - resource.TestCheckResourceAttrSet(data.ResourceName, "destination.0.address"), ), }, data.ImportStep(), @@ -239,7 +202,7 @@ func testAccAzureRMNetworkConnectionMonitor_missingDestination(t *testing.T) { Steps: []resource.TestStep{ { Config: testAccAzureRMNetworkConnectionMonitor_missingDestinationConfig(data), - ExpectError: regexp.MustCompile("Error: either `destination.virtual_machine_id` or `destination.address` must be specified"), + ExpectError: regexp.MustCompile("must have at least 2 endpoints"), }, }, }) @@ -255,8 +218,65 @@ func testAccAzureRMNetworkConnectionMonitor_conflictingDestinations(t *testing.T Steps: []resource.TestStep{ { Config: testAccAzureRMNetworkConnectionMonitor_conflictingDestinationsConfig(data), - ExpectError: regexp.MustCompile("conflicts with destination.0.address"), + ExpectError: regexp.MustCompile("don't allow creating different endpoints for the same VM"), + }, + }, + }) +} + +func testAccAzureRMNetworkConnectionMonitor_withAddressAndVirtualMachineId(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_network_connection_monitor", "test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMNetworkConnectionMonitorDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMNetworkConnectionMonitor_withAddressAndVirtualMachineIdConfig(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMNetworkConnectionMonitorExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + +func testAccAzureRMNetworkConnectionMonitor_httpConfiguration(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_network_connection_monitor", "test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMNetworkConnectionMonitorDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMNetworkConnectionMonitor_httpConfigurationConfig(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMNetworkConnectionMonitorExists(data.ResourceName), + ), }, + data.ImportStep(), + }, + }) +} + +func testAccAzureRMNetworkConnectionMonitor_icmpConfiguration(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_network_connection_monitor", "test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMNetworkConnectionMonitorDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMNetworkConnectionMonitor_icmpConfigurationConfig(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMNetworkConnectionMonitorExists(data.ResourceName), + ), + }, + data.ImportStep(), }, }) } @@ -271,17 +291,18 @@ func testCheckAzureRMNetworkConnectionMonitorExists(resourceName string) resourc return fmt.Errorf("Not found: %s", resourceName) } - resourceGroup := rs.Primary.Attributes["resource_group_name"] - watcherName := rs.Primary.Attributes["network_watcher_name"] - NetworkConnectionMonitorName := rs.Primary.Attributes["name"] + id, err := parse.NetworkConnectionMonitorID(rs.Primary.ID) + if err != nil { + return err + } - resp, err := client.Get(ctx, resourceGroup, watcherName, NetworkConnectionMonitorName) + resp, err := client.Get(ctx, id.ResourceGroup, id.WatcherName, id.Name) if err != nil { return fmt.Errorf("Bad: Get on NetworkConnectionMonitorsClient: %s", err) } if resp.StatusCode == http.StatusNotFound { - return fmt.Errorf("Connection Monitor does not exist: %s", NetworkConnectionMonitorName) + return fmt.Errorf("Connection Monitor does not exist: %s", id.Name) } return nil @@ -297,11 +318,12 @@ func testCheckAzureRMNetworkConnectionMonitorDestroy(s *terraform.State) error { continue } - resourceGroup := rs.Primary.Attributes["resource_group_name"] - watcherName := rs.Primary.Attributes["network_watcher_name"] - NetworkConnectionMonitorName := rs.Primary.Attributes["name"] + id, err := parse.NetworkConnectionMonitorID(rs.Primary.ID) + if err != nil { + return err + } - resp, err := client.Get(ctx, resourceGroup, watcherName, NetworkConnectionMonitorName) + resp, err := client.Get(ctx, id.ResourceGroup, id.WatcherName, id.Name) if err != nil { return nil @@ -322,18 +344,18 @@ provider "azurerm" { } resource "azurerm_resource_group" "test" { - name = "acctestRG-watcher-%d" + name = "acctestRG-Watcher-%d" location = "%s" } resource "azurerm_network_watcher" "test" { - name = "acctnw-%d" + name = "acctest-Watcher-%d" location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name } resource "azurerm_virtual_network" "test" { - name = "acctvn-%d" + name = "acctest-Vnet-%d" address_space = ["10.0.0.0/16"] location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name @@ -347,7 +369,7 @@ resource "azurerm_subnet" "test" { } resource "azurerm_network_interface" "src" { - name = "acctni-src%d" + name = "acctest-SrcNIC-%d" location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name @@ -359,7 +381,7 @@ resource "azurerm_network_interface" "src" { } resource "azurerm_virtual_machine" "src" { - name = "acctvm-src%d" + name = "acctest-SrcVM-%d" location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name network_interface_ids = [azurerm_network_interface.src.id] @@ -391,7 +413,7 @@ resource "azurerm_virtual_machine" "src" { } resource "azurerm_virtual_machine_extension" "src" { - name = "network-watcher" + name = "acctest-VMExtension" virtual_machine_id = azurerm_virtual_machine.src.id publisher = "Microsoft.Azure.NetworkWatcher" type = "NetworkWatcherAgentLinux" @@ -407,7 +429,7 @@ func testAccAzureRMNetworkConnectionMonitor_baseWithDestConfig(data acceptance.T %s resource "azurerm_network_interface" "dest" { - name = "acctni-dest%d" + name = "acctest-DestNic-%d" location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name @@ -419,7 +441,7 @@ resource "azurerm_network_interface" "dest" { } resource "azurerm_virtual_machine" "dest" { - name = "acctvm-dest%d" + name = "acctest-DestVM-%d" location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name network_interface_ids = [azurerm_network_interface.dest.id] @@ -458,18 +480,34 @@ func testAccAzureRMNetworkConnectionMonitor_basicAddressConfig(data acceptance.T %s resource "azurerm_network_connection_monitor" "test" { - name = "acctestcm-%d" - network_watcher_name = azurerm_network_watcher.test.name - resource_group_name = azurerm_resource_group.test.name - location = azurerm_network_watcher.test.location + name = "acctest-CM-%d" + network_watcher_id = azurerm_network_watcher.test.id + location = azurerm_network_watcher.test.location - source { + endpoint { + name = "source" virtual_machine_id = azurerm_virtual_machine.src.id } - destination { + endpoint { + name = "destination" address = "terraform.io" - port = 80 + } + + test_configuration { + name = "tcp" + protocol = "Tcp" + + tcp_configuration { + port = 80 + } + } + + test_group { + name = "testtg" + destination_endpoints = ["destination"] + source_endpoints = ["source"] + test_configuration_names = ["tcp"] } depends_on = [azurerm_virtual_machine_extension.src] @@ -477,37 +515,77 @@ resource "azurerm_network_connection_monitor" "test" { `, config, data.RandomInteger) } -func testAccAzureRMNetworkConnectionMonitor_completeAddressConfig(data acceptance.TestData, autoStart string) string { +func testAccAzureRMNetworkConnectionMonitor_completeAddressConfig(data acceptance.TestData) string { config := testAccAzureRMNetworkConnectionMonitor_baseConfig(data) return fmt.Sprintf(` %s -resource "azurerm_network_connection_monitor" "test" { - name = "acctestcm-%d" - network_watcher_name = azurerm_network_watcher.test.name - resource_group_name = azurerm_resource_group.test.name - location = azurerm_network_watcher.test.location +resource "azurerm_log_analytics_workspace" "test" { + name = "acctest-LAW-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + sku = "pergb2018" +} - auto_start = %s - interval_in_seconds = 30 +resource "azurerm_network_connection_monitor" "test" { + name = "acctest-CM-%d" + network_watcher_id = azurerm_network_watcher.test.id + location = azurerm_network_watcher.test.location - source { + endpoint { + name = "source" virtual_machine_id = azurerm_virtual_machine.src.id - port = 20020 + + filter { + item { + address = azurerm_virtual_machine.src.id + type = "AgentAddress" + } + + type = "Include" + } } - destination { + endpoint { + name = "destination" address = "terraform.io" - port = 443 } + test_configuration { + name = "tcp" + protocol = "Tcp" + test_frequency_in_seconds = 40 + preferred_ip_version = "IPv4" + + tcp_configuration { + port = 80 + } + + success_threshold { + checks_failed_percent = 50 + round_trip_time_ms = 40 + } + } + + test_group { + name = "testtg" + destination_endpoints = ["destination"] + source_endpoints = ["source"] + test_configuration_names = ["tcp"] + enabled = true + } + + notes = "testNote" + + output_workspace_resource_ids = [azurerm_log_analytics_workspace.test.id] + tags = { - env = "test" + ENv = "Test" } depends_on = [azurerm_virtual_machine_extension.src] } -`, config, data.RandomInteger, autoStart) +`, config, data.RandomInteger, data.RandomInteger) } func testAccAzureRMNetworkConnectionMonitor_basicVmConfig(data acceptance.TestData) string { @@ -516,18 +594,34 @@ func testAccAzureRMNetworkConnectionMonitor_basicVmConfig(data acceptance.TestDa %s resource "azurerm_network_connection_monitor" "test" { - name = "acctestcm-%d" - network_watcher_name = azurerm_network_watcher.test.name - resource_group_name = azurerm_resource_group.test.name - location = azurerm_network_watcher.test.location + name = "acctest-CM-%d" + network_watcher_id = azurerm_network_watcher.test.id + location = azurerm_network_watcher.test.location - source { + endpoint { + name = "source" virtual_machine_id = azurerm_virtual_machine.src.id } - destination { + endpoint { + name = "destination" virtual_machine_id = azurerm_virtual_machine.dest.id - port = 80 + } + + test_configuration { + name = "tcp" + protocol = "Tcp" + + tcp_configuration { + port = 80 + } + } + + test_group { + name = "testtg" + destination_endpoints = ["destination"] + source_endpoints = ["source"] + test_configuration_names = ["tcp"] } depends_on = [azurerm_virtual_machine_extension.src] @@ -535,37 +629,102 @@ resource "azurerm_network_connection_monitor" "test" { `, config, data.RandomInteger) } -func testAccAzureRMNetworkConnectionMonitor_completeVmConfig(data acceptance.TestData, autoStart string) string { +func testAccAzureRMNetworkConnectionMonitor_withAddressAndVirtualMachineIdConfig(data acceptance.TestData) string { config := testAccAzureRMNetworkConnectionMonitor_baseWithDestConfig(data) return fmt.Sprintf(` %s resource "azurerm_network_connection_monitor" "test" { - name = "acctestcm-%d" - network_watcher_name = azurerm_network_watcher.test.name - resource_group_name = azurerm_resource_group.test.name - location = azurerm_network_watcher.test.location + name = "acctest-CM-%d" + network_watcher_id = azurerm_network_watcher.test.id + location = azurerm_network_watcher.test.location - auto_start = %s - interval_in_seconds = 30 + endpoint { + name = "source" + virtual_machine_id = azurerm_virtual_machine.src.id + } + + endpoint { + name = "destination" + virtual_machine_id = azurerm_virtual_machine.dest.id + address = azurerm_network_interface.dest.private_ip_address + } + + test_configuration { + name = "tcp" + protocol = "Tcp" + + tcp_configuration { + port = 80 + } + } + + test_group { + name = "testtg" + destination_endpoints = ["destination"] + source_endpoints = ["source"] + test_configuration_names = ["tcp"] + } + + depends_on = [azurerm_virtual_machine_extension.src] +} +`, config, data.RandomInteger) +} - source { +func testAccAzureRMNetworkConnectionMonitor_completeVmConfig(data acceptance.TestData) string { + config := testAccAzureRMNetworkConnectionMonitor_baseWithDestConfig(data) + return fmt.Sprintf(` +%s + +resource "azurerm_network_connection_monitor" "test" { + name = "acctest-CM-%d" + network_watcher_id = azurerm_network_watcher.test.id + location = azurerm_network_watcher.test.location + + endpoint { + name = "source" virtual_machine_id = azurerm_virtual_machine.src.id - port = 20020 + + filter { + item { + address = azurerm_virtual_machine.src.id + type = "AgentAddress" + } + + type = "Include" + } } - destination { + endpoint { + name = "destination" virtual_machine_id = azurerm_virtual_machine.dest.id - port = 443 + } + + test_configuration { + name = "tcp" + protocol = "Tcp" + test_frequency_in_seconds = 40 + + tcp_configuration { + port = 80 + } + } + + test_group { + name = "testtg" + destination_endpoints = ["destination"] + source_endpoints = ["source"] + test_configuration_names = ["tcp"] + enabled = true } tags = { - env = "test" + ENv = "Test" } depends_on = [azurerm_virtual_machine_extension.src] } -`, config, data.RandomInteger, autoStart) +`, config, data.RandomInteger) } func testAccAzureRMNetworkConnectionMonitor_missingDestinationConfig(data acceptance.TestData) string { @@ -574,17 +733,29 @@ func testAccAzureRMNetworkConnectionMonitor_missingDestinationConfig(data accept %s resource "azurerm_network_connection_monitor" "test" { - name = "acctestcm-%d" - network_watcher_name = azurerm_network_watcher.test.name - resource_group_name = azurerm_resource_group.test.name - location = azurerm_network_watcher.test.location + name = "acctest-CM-%d" + network_watcher_id = azurerm_network_watcher.test.id + location = azurerm_network_watcher.test.location - source { + endpoint { + name = "source" virtual_machine_id = azurerm_virtual_machine.src.id } - destination { - port = 80 + test_configuration { + name = "tcp" + protocol = "Tcp" + + tcp_configuration { + port = 80 + } + } + + test_group { + name = "testtg" + destination_endpoints = [] + source_endpoints = ["source"] + test_configuration_names = ["tcp"] } depends_on = [azurerm_virtual_machine_extension.src] @@ -598,19 +769,34 @@ func testAccAzureRMNetworkConnectionMonitor_conflictingDestinationsConfig(data a %s resource "azurerm_network_connection_monitor" "test" { - name = "acctestcm-%d" - network_watcher_name = azurerm_network_watcher.test.name - resource_group_name = azurerm_resource_group.test.name - location = azurerm_network_watcher.test.location + name = "acctest-CM-%d" + network_watcher_id = azurerm_network_watcher.test.id + location = azurerm_network_watcher.test.location - source { + endpoint { + name = "source" virtual_machine_id = azurerm_virtual_machine.src.id } - destination { - address = "terraform.io" + endpoint { + name = "destination" virtual_machine_id = azurerm_virtual_machine.src.id - port = 80 + } + + test_configuration { + name = "tcp" + protocol = "Tcp" + + tcp_configuration { + port = 80 + } + } + + test_group { + name = "testtg" + destination_endpoints = ["destination"] + source_endpoints = ["source"] + test_configuration_names = ["tcp"] } depends_on = [azurerm_virtual_machine_extension.src] @@ -624,21 +810,128 @@ func testAccAzureRMNetworkConnectionMonitor_requiresImportConfig(data acceptance %s resource "azurerm_network_connection_monitor" "import" { - name = azurerm_network_connection_monitor.test.name - network_watcher_name = azurerm_network_connection_monitor.test.network_watcher_name - resource_group_name = azurerm_network_connection_monitor.test.resource_group_name - location = azurerm_network_connection_monitor.test.location + name = azurerm_network_connection_monitor.test.name + network_watcher_id = azurerm_network_connection_monitor.test.network_watcher_id + location = azurerm_network_connection_monitor.test.location - source { + endpoint { + name = "source" virtual_machine_id = azurerm_virtual_machine.src.id } - destination { + endpoint { + name = "destination" address = "terraform.io" - port = 80 + } + + test_configuration { + name = "tcp" + protocol = "Tcp" + + tcp_configuration { + port = 80 + } + } + + test_group { + name = "testtg" + destination_endpoints = ["destination"] + source_endpoints = ["source"] + test_configuration_names = ["tcp"] } depends_on = [azurerm_virtual_machine_extension.src] } `, config) } + +func testAccAzureRMNetworkConnectionMonitor_httpConfigurationConfig(data acceptance.TestData) string { + config := testAccAzureRMNetworkConnectionMonitor_baseConfig(data) + return fmt.Sprintf(` +%s + +resource "azurerm_network_connection_monitor" "test" { + name = "acctest-CM-%d" + network_watcher_id = azurerm_network_watcher.test.id + location = azurerm_network_watcher.test.location + + endpoint { + name = "source" + virtual_machine_id = azurerm_virtual_machine.src.id + } + + endpoint { + name = "destination" + address = "terraform.io" + } + + test_configuration { + name = "tcp" + protocol = "Http" + + http_configuration { + method = "Get" + port = 80 + path = "/a/b" + prefer_https = false + valid_status_code_ranges = ["200"] + + request_header { + name = "testHeader" + value = "testVal" + } + } + } + + test_group { + name = "testtg" + destination_endpoints = ["destination"] + source_endpoints = ["source"] + test_configuration_names = ["tcp"] + } + + depends_on = [azurerm_virtual_machine_extension.src] +} +`, config, data.RandomInteger) +} + +func testAccAzureRMNetworkConnectionMonitor_icmpConfigurationConfig(data acceptance.TestData) string { + config := testAccAzureRMNetworkConnectionMonitor_baseConfig(data) + return fmt.Sprintf(` +%s + +resource "azurerm_network_connection_monitor" "test" { + name = "acctest-CM-%d" + network_watcher_id = azurerm_network_watcher.test.id + location = azurerm_network_watcher.test.location + + endpoint { + name = "source" + virtual_machine_id = azurerm_virtual_machine.src.id + } + + endpoint { + name = "destination" + address = "terraform.io" + } + + test_configuration { + name = "tcp" + protocol = "Icmp" + + icmp_configuration { + trace_route_enabled = true + } + } + + test_group { + name = "testtg" + destination_endpoints = ["destination"] + source_endpoints = ["source"] + test_configuration_names = ["tcp"] + } + + depends_on = [azurerm_virtual_machine_extension.src] +} +`, config, data.RandomInteger) +} diff --git a/azurerm/internal/services/network/tests/network_watcher_resource_test.go b/azurerm/internal/services/network/tests/network_watcher_resource_test.go index d54bc015c381..a7748725dec4 100644 --- a/azurerm/internal/services/network/tests/network_watcher_resource_test.go +++ b/azurerm/internal/services/network/tests/network_watcher_resource_test.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/terraform" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/parse" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -35,16 +36,19 @@ func TestAccAzureRMNetworkWatcher(t *testing.T) { "requiresImport": testAccAzureRMPacketCapture_requiresImport, }, "ConnectionMonitor": { - "addressBasic": testAccAzureRMNetworkConnectionMonitor_addressBasic, - "addressComplete": testAccAzureRMNetworkConnectionMonitor_addressComplete, - "addressUpdate": testAccAzureRMNetworkConnectionMonitor_addressUpdate, - "vmBasic": testAccAzureRMNetworkConnectionMonitor_vmBasic, - "vmComplete": testAccAzureRMNetworkConnectionMonitor_vmComplete, - "vmUpdate": testAccAzureRMNetworkConnectionMonitor_vmUpdate, - "destinationUpdate": testAccAzureRMNetworkConnectionMonitor_destinationUpdate, - "missingDestinationInvalid": testAccAzureRMNetworkConnectionMonitor_missingDestination, - "bothDestinationsInvalid": testAccAzureRMNetworkConnectionMonitor_conflictingDestinations, - "requiresImport": testAccAzureRMNetworkConnectionMonitor_requiresImport, + "addressBasic": testAccAzureRMNetworkConnectionMonitor_addressBasic, + "addressComplete": testAccAzureRMNetworkConnectionMonitor_addressComplete, + "addressUpdate": testAccAzureRMNetworkConnectionMonitor_addressUpdate, + "vmBasic": testAccAzureRMNetworkConnectionMonitor_vmBasic, + "vmComplete": testAccAzureRMNetworkConnectionMonitor_vmComplete, + "vmUpdate": testAccAzureRMNetworkConnectionMonitor_vmUpdate, + "destinationUpdate": testAccAzureRMNetworkConnectionMonitor_destinationUpdate, + "missingDestinationInvalid": testAccAzureRMNetworkConnectionMonitor_missingDestination, + "bothDestinationsInvalid": testAccAzureRMNetworkConnectionMonitor_conflictingDestinations, + "requiresImport": testAccAzureRMNetworkConnectionMonitor_requiresImport, + "httpConfiguration": testAccAzureRMNetworkConnectionMonitor_httpConfiguration, + "icmpConfiguration": testAccAzureRMNetworkConnectionMonitor_icmpConfiguration, + "bothAddressAndVirtualMachineId": testAccAzureRMNetworkConnectionMonitor_withAddressAndVirtualMachineId, }, "PacketCapture": { "localDisk": testAccAzureRMNetworkPacketCapture_localDisk, @@ -189,16 +193,18 @@ func testCheckAzureRMNetworkWatcherExists(resourceName string) resource.TestChec return fmt.Errorf("Not found: %s", resourceName) } - name := rs.Primary.Attributes["name"] - resourceGroup, hasResourceGroup := rs.Primary.Attributes["resource_group_name"] - if !hasResourceGroup { - return fmt.Errorf("Bad: no resource group found in state for Network Watcher: %q", name) + id, err := parse.NetworkWatcherID(rs.Primary.ID) + if err != nil { + return err + } + if id.ResourceGroup == "" { + return fmt.Errorf("Bad: no resource group found in state for Network Watcher: %q", id.Name) } - resp, err := client.Get(ctx, resourceGroup, name) + resp, err := client.Get(ctx, id.ResourceGroup, id.Name) if err != nil { if utils.ResponseWasNotFound(resp.Response) { - return fmt.Errorf("Bad: Network Watcher %q (resource group: %q) does not exist", name, resourceGroup) + return fmt.Errorf("Bad: Network Watcher %q (resource group: %q) does not exist", id.Name, id.ResourceGroup) } return fmt.Errorf("Bad: Get on watcherClient: %+v", err) } @@ -217,13 +223,15 @@ func testCheckAzureRMNetworkWatcherDisappears(resourceName string) resource.Test return fmt.Errorf("Not found: %q", resourceName) } - name := rs.Primary.Attributes["name"] - resourceGroup, hasResourceGroup := rs.Primary.Attributes["resource_group_name"] - if !hasResourceGroup { - return fmt.Errorf("Bad: no resource group found in state for Network Watcher: %q", name) + id, err := parse.NetworkWatcherID(rs.Primary.ID) + if err != nil { + return err + } + if id.ResourceGroup == "" { + return fmt.Errorf("Bad: no resource group found in state for Network Watcher: %q", id.Name) } - future, err := client.Delete(ctx, resourceGroup, name) + future, err := client.Delete(ctx, id.ResourceGroup, id.Name) if err != nil { if !response.WasNotFound(future.Response()) { return fmt.Errorf("Bad: Delete on watcherClient: %+v", err) @@ -247,10 +255,12 @@ func testCheckAzureRMNetworkWatcherDestroy(s *terraform.State) error { continue } - name := rs.Primary.Attributes["name"] - resourceGroup := rs.Primary.Attributes["resource_group_name"] + id, err := parse.NetworkWatcherID(rs.Primary.ID) + if err != nil { + return err + } - resp, err := client.Get(ctx, resourceGroup, name) + resp, err := client.Get(ctx, id.ResourceGroup, id.Name) if err != nil { if !utils.ResponseWasNotFound(resp.Response) { diff --git a/azurerm/internal/services/network/validate/network_connection_monitor.go b/azurerm/internal/services/network/validate/network_connection_monitor.go new file mode 100644 index 000000000000..59cd9d096b65 --- /dev/null +++ b/azurerm/internal/services/network/validate/network_connection_monitor.go @@ -0,0 +1,121 @@ +package validate + +import ( + "fmt" + "net/url" + "regexp" + "strconv" + "strings" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/parse" +) + +func NetworkConnectionMonitorID(i interface{}, k string) (warnings []string, errors []error) { + v, ok := i.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected type of %q to be string", k)) + return + } + + if _, err := parse.NetworkConnectionMonitorID(v); err != nil { + errors = append(errors, fmt.Errorf("Can not parse %q as a resource id: %v", k, err)) + return + } + + return warnings, errors +} + +func NetworkConnectionMonitorHttpPath(v interface{}, k string) (warnings []string, errors []error) { + value := v.(string) + + if len(value) == 0 { + errors = append(errors, fmt.Errorf("%q cannot be an empty string: %q", k, value)) + return warnings, errors + } + + path, err := url.ParseRequestURI(value) + if err != nil { + errors = append(errors, fmt.Errorf("parsing %q: %q", k, value)) + return warnings, errors + } + + if path.IsAbs() { + errors = append(errors, fmt.Errorf("%q only accepts the absolute path: %q", k, value)) + return warnings, errors + } + + return warnings, errors +} + +func NetworkConnectionMonitorValidStatusCodeRanges(v interface{}, k string) (warnings []string, errors []error) { + value := v.(string) + + if len(value) == 0 { + errors = append(errors, fmt.Errorf("%q cannot be an empty string: %q", k, value)) + return warnings, errors + } + + if len(value) != 7 && len(value) != 3 { + errors = append(errors, fmt.Errorf("The len of %q should be 3 or 7: %q", k, value)) + return warnings, errors + } + + // Here the format of the expected code range is `301-304` + if len(value) == 7 { + if !regexp.MustCompile(`^([1-5][0-9][0-9]-([1-5][0-9][0-9]|600))$`).MatchString(value) { + errors = append(errors, fmt.Errorf("%q can contain hyphen: %q", k, value)) + return warnings, errors + } else { + vArray := strings.Split(value, "-") + + startNumber, err := strconv.Atoi(vArray[0]) + if err != nil { + errors = append(errors, fmt.Errorf("expected %s on the left of - to be an integer, got %v: %v", k, value, err)) + return warnings, errors + } + + endNumber, err := strconv.Atoi(vArray[1]) + if err != nil { + errors = append(errors, fmt.Errorf("expected %s on the right of - to be an integer, got %v: %v", k, value, err)) + return warnings, errors + } + + if startNumber >= endNumber { + errors = append(errors, fmt.Errorf("the start number of %q should less than the end number: %q", k, value)) + return warnings, errors + } + } + } + + // Here the format of the expected code ranges are `2xx` and `418` + if len(value) == 3 { + if !regexp.MustCompile(`^([1-5][0-9x][0-9x]|600)$`).MatchString(value) { + errors = append(errors, fmt.Errorf("%q can contain number with x or pure number: %q", k, value)) + return warnings, errors + } + } + + return warnings, errors +} + +func NetworkConnectionMonitorEndpointAddress(v interface{}, k string) (warnings []string, errors []error) { + value := v.(string) + + if len(value) == 0 { + errors = append(errors, fmt.Errorf("%q cannot be an empty string: %q", k, value)) + return warnings, errors + } + + url, err := url.Parse(value) + if err != nil { + errors = append(errors, fmt.Errorf("parsing %q: %q", k, value)) + return warnings, errors + } + + if url.Scheme != "" || url.RawQuery != "" { + errors = append(errors, fmt.Errorf("%q cannot contain scheme and query parameter: %q", k, value)) + return warnings, errors + } + + return warnings, errors +} diff --git a/azurerm/internal/services/network/validate/network_connection_monitor_test.go b/azurerm/internal/services/network/validate/network_connection_monitor_test.go new file mode 100644 index 000000000000..28028836b521 --- /dev/null +++ b/azurerm/internal/services/network/validate/network_connection_monitor_test.go @@ -0,0 +1,161 @@ +package validate + +import "testing" + +func TestNetworkConnectionMonitorHttpPath(t *testing.T) { + cases := []struct { + Value string + Errors int + }{ + { + Value: "", + Errors: 1, + }, + { + Value: "a/b", + Errors: 1, + }, + { + Value: "/ab/b1/", + Errors: 0, + }, + { + Value: "/a/b", + Errors: 0, + }, + { + Value: "http://www.terraform.io", + Errors: 1, + }, + { + Value: "/a/b/", + Errors: 0, + }, + } + + for _, tc := range cases { + t.Run(tc.Value, func(t *testing.T) { + _, errors := NetworkConnectionMonitorHttpPath(tc.Value, "path") + + if len(errors) != tc.Errors { + t.Fatalf("Expected Path to return %d error(s) not %d", tc.Errors, len(errors)) + } + }) + } +} + +func TestNetworkConnectionMonitorValidStatusCodeRanges(t *testing.T) { + cases := []struct { + Value string + Errors int + }{ + { + Value: "", + Errors: 1, + }, + { + Value: "100", + Errors: 0, + }, + { + Value: "599", + Errors: 0, + }, + { + Value: "600", + Errors: 0, + }, + { + Value: "1xx", + Errors: 0, + }, + { + Value: "10x", + Errors: 0, + }, + { + Value: "1x2", + Errors: 0, + }, + { + Value: "259-379", + Errors: 0, + }, + { + Value: "489-379", + Errors: 1, + }, + { + Value: "30x-4xx", + Errors: 1, + }, + { + Value: "7xx", + Errors: 1, + }, + { + Value: "99", + Errors: 1, + }, + { + Value: "888", + Errors: 1, + }, + { + Value: "1111", + Errors: 1, + }, + } + + for _, tc := range cases { + t.Run(tc.Value, func(t *testing.T) { + _, errors := NetworkConnectionMonitorValidStatusCodeRanges(tc.Value, "valid_status_code_ranges") + + if len(errors) != tc.Errors { + t.Fatalf("Expected valid_status_code_ranges to return %d error(s) not %d", tc.Errors, len(errors)) + } + }) + } +} + +func TestNetworkConnectionMonitorEndpointAddressWithDomainName(t *testing.T) { + cases := []struct { + Value string + Errors int + }{ + { + Value: "", + Errors: 1, + }, + { + Value: "a-b", + Errors: 0, + }, + { + Value: "terraform.io", + Errors: 0, + }, + { + Value: "www.google.com", + Errors: 0, + }, + { + Value: "http://www.google.com", + Errors: 1, + }, + { + Value: "www.google.com/a/b?a=1", + Errors: 1, + }, + } + + for _, tc := range cases { + t.Run(tc.Value, func(t *testing.T) { + _, errors := NetworkConnectionMonitorEndpointAddress(tc.Value, "address") + + if len(errors) != tc.Errors { + t.Fatalf("Expected address to return %d error(s) not %d", tc.Errors, len(errors)) + } + }) + } +} diff --git a/azurerm/internal/services/network/validate/network_watcher.go b/azurerm/internal/services/network/validate/network_watcher.go new file mode 100644 index 000000000000..e21a52c6e16c --- /dev/null +++ b/azurerm/internal/services/network/validate/network_watcher.go @@ -0,0 +1,22 @@ +package validate + +import ( + "fmt" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/parse" +) + +func NetworkWatcherID(i interface{}, k string) (warnings []string, errors []error) { + v, ok := i.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected type of %q to be string", k)) + return + } + + if _, err := parse.NetworkWatcherID(v); err != nil { + errors = append(errors, fmt.Errorf("Can not parse %q as a resource id: %v", k, err)) + return + } + + return warnings, errors +} diff --git a/website/docs/r/network_connection_monitor.html.markdown b/website/docs/r/network_connection_monitor.html.markdown index fbea96c73139..796fcdea7119 100644 --- a/website/docs/r/network_connection_monitor.html.markdown +++ b/website/docs/r/network_connection_monitor.html.markdown @@ -10,61 +10,144 @@ description: |- Manages a Network Connection Monitor. +~> **NOTE:** Any Network Connection Monitor resource created with API versions 2019-06-01 or earlier (v1) are now incompatible with Terraform, which now only supports v2. + ## Example Usage ```hcl -provider "azurerm" { - features {} +resource "azurerm_resource_group" "example" { + name = "example-Watcher-resources" + location = "West Europe" } -data "azurerm_resource_group" "example" { - name = "example-resources" +resource "azurerm_network_watcher" "example" { + name = "example-Watcher" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name } -resource "azurerm_network_watcher" "example" { - name = "example-nw" - location = data.azurerm_resource_group.example.location - resource_group_name = data.azurerm_resource_group.example.name +resource "azurerm_virtual_network" "example" { + name = "example-Vnet" + address_space = ["10.0.0.0/16"] + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name +} + +resource "azurerm_subnet" "example" { + name = "example-Subnet" + resource_group_name = azurerm_resource_group.example.name + virtual_network_name = azurerm_virtual_network.example.name + address_prefixes = ["10.0.2.0/24"] } -data "azurerm_virtual_machine" "src" { - name = "example-vm" - resource_group_name = data.azurerm_resource_group.example.name +resource "azurerm_network_interface" "example" { + name = "example-Nic" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + + ip_configuration { + name = "testconfiguration1" + subnet_id = azurerm_subnet.example.id + private_ip_address_allocation = "Dynamic" + } +} + +resource "azurerm_virtual_machine" "example" { + name = "example-VM" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + network_interface_ids = [azurerm_network_interface.example.id] + vm_size = "Standard_D2s_v3" + + storage_image_reference { + publisher = "Canonical" + offer = "UbuntuServer" + sku = "16.04-LTS" + version = "latest" + } + + storage_os_disk { + name = "osdisk-example01" + caching = "ReadWrite" + create_option = "FromImage" + managed_disk_type = "Standard_LRS" + } + + os_profile { + computer_name = "hostnametest01" + admin_username = "testadmin" + admin_password = "Password1234!" + } + + os_profile_linux_config { + disable_password_authentication = false + } } -resource "azurerm_virtual_machine_extension" "src" { - name = "network-watcher" - virtual_machine_id = data.azurerm_virtual_machine.src.id +resource "azurerm_virtual_machine_extension" "example" { + name = "example-VMExtension" + virtual_machine_id = azurerm_virtual_machine.example.id publisher = "Microsoft.Azure.NetworkWatcher" type = "NetworkWatcherAgentLinux" type_handler_version = "1.4" auto_upgrade_minor_version = true } +resource "azurerm_log_analytics_workspace" "example" { + name = "example-Workspace" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + sku = "pergb2018" +} + resource "azurerm_network_connection_monitor" "example" { - name = "example-ncm" + name = "example-Monitor" network_watcher_name = azurerm_network_watcher.example.name - resource_group_name = data.azurerm_resource_group.example.name + resource_group_name = azurerm_resource_group.example.name location = azurerm_network_watcher.example.location - auto_start = false - interval_in_seconds = 30 + endpoint { + name = "source" + virtual_machine_id = azurerm_virtual_machine.example.id - source { - virtual_machine_id = data.azurerm_virtual_machine.src.id - port = 20020 + filter { + item { + address = azurerm_virtual_machine.example.id + type = "AgentAddress" + } + + type = "Include" + } } - destination { + endpoint { + name = "destination" address = "terraform.io" - port = 443 } - tags = { - foo = "bar" + test_configuration { + name = "tcpName" + protocol = "Tcp" + test_frequency_in_seconds = 60 + + tcp_configuration { + port = 80 + } } - depends_on = [azurerm_virtual_machine_extension.src] + test_group { + name = "exampletg" + destination_endpoints = ["destination"] + source_endpoints = ["source"] + test_configuration_names = ["tcpName"] + disable = false + } + + notes = "examplenote" + + output_workspace_resource_ids = [azurerm_log_analytics_workspace.example.id] + + depends_on = [azurerm_virtual_machine_extension.example] } ``` @@ -72,47 +155,137 @@ resource "azurerm_network_connection_monitor" "example" { The following arguments are supported: -* `name` - (Required) The name which should be used for this Network Connection Monitor. Changing this forces a new Network Connection Monitor to be created. +* `name` - (Required) The name which should be used for this Network Connection Monitor. Changing this forces a new resource to be created. -* `resource_group_name` - (Required) The name of the Resource Group where the Network Connection Monitor should exist. Changing this forces a new Network Connection Monitor to be created. +* `location` - (Required) The Azure Region where the Network Connection Monitor should exist. Changing this forces a new resource to be created. -* `location` - (Required) The Azure Region where the Network Connection Monitor should exist. Changing this forces a new Network Connection Monitor to be created. +* `network_watcher_id` - (Required) The ID of the Network Watcher. Changing this forces a new resource to be created. -* `destination` - (Required) A `destination` block as defined below. +* `endpoint` - (Required) A `endpoint` block as defined below. -* `network_watcher_name` - (Required) The name of the Network Watcher. Changing this forces a new Network Connection Monitor to be created. +* `test_configuration` - (Required) A `test_configuration` block as defined below. -* `source` - (Required) A `source` block as defined below. +* `test_group` - (Required) A `test_group` block as defined below. --- -* `auto_start` - (Optional) Will the connection monitor start automatically once created? Changing this forces a new Network Connection Monitor to be created. +* `notes` - (Optional) The description of the Network Connection Monitor. -* `interval_in_seconds` - (Optional) Monitoring interval in seconds. +* `output_workspace_resource_ids` - (Optional) A list of IDs of the Log Analytics Workspace which will accept the output from the Network Connection Monitor. * `tags` - (Optional) A mapping of tags which should be assigned to the Network Connection Monitor. --- -A `destination` block supports the following: +A `endpoint` block supports the following: + +* `name` - (Required) The name of the endpoint for the Network Connection Monitor . + +* `address` - (Optional) The IP address or domain name of the Network Connection Monitor endpoint. + +* `filter` - (Optional) A `filter` block as defined below. + +* `virtual_machine_id` - (Optional) The ID of the Virtual Machine which is used as the endpoint by the Network Connection Monitor. + +--- -* `port` - (Required) The destination port used by connection monitor. +A `filter` block supports the following: -* `address` - (Optional) The address of the connection monitor destination (IP or domain name). Conflicts with `destination.0.virtual_machine_id` +* `type` - (Optional) The behavior type of this endpoint filter. Currently the only allowed value is `Include`. Defaults to `Include`. -* `virtual_machine_id` - (Optional) The ID of the virtual machine used as the destination by connection monitor. Conflicts with `destination.0.address` +* `item` - (Optional) A `item` block as defined below. --- -A `source` block supports the following: +A `item` block supports the following: + +* `type` - (Optional) The type of items included in the filter. Possible values are `AgentAddress`. Defaults to `AgentAddress`. + +* `address` - (Optional) The address of the filter item. + +--- + +A `test_configuration` block supports the following: + +* `name` - (Required) The name of test configuration for the Network Connection Monitor. + +* `protocol` - (Required) The protocol used to evaluate tests. Possible values are `Tcp`, `Http` and `Icmp`. + +* `test_frequency_in_seconds` - (Optional) The time interval in seconds at which the test evaluation will happen. Defaults to `60`. + +* `http_configuration` - (Optional) A `http_configuration` block as defined below. + +* `icmp_configuration` - (Optional) A `icmp_configuration` block as defined below. + +* `preferred_ip_version` - (Optional) The preferred IP version which is used in the test evaluation. Possible values are `IPv4` and `IPv6`. + +* `success_threshold` - (Optional) A `success_threshold` block as defined below. + +* `tcp_configuration` - (Optional) A `tcp_configuration` block as defined below. + +--- + +A `http_configuration` block supports the following: + +* `method` - (Optional) The HTTP method for the HTTP request. Possible values are `Get` and `Post`. Defaults to `Get`. + +* `port` - (Optional) The port for the HTTP connection. + +* `path` - (Optional) The path component of the URI. It only accepts the absolute path. + +* `prefer_https` - (Optional) Should HTTPS be preferred over HTTP in cases where the choice is not explicit? Defaults to `false`. + +* `request_header` - (Optional) A `request_header` block as defined below. + +* `valid_status_code_ranges` - (Optional) The HTTP status codes to consider successful. For instance, `2xx`, `301-304` and `418`. + +--- + +A `request_header` block supports the following: + +* `name` - (Required) The name of the HTTP header. + +* `value` - (Required) The value of the HTTP header. + +--- + +A `icmp_configuration` block supports the following: + +* `trace_route_enabled` - (Optional) Should path evaluation with trace route be enabled? Defaults to `true`. + +--- + +A `success_threshold` block supports the following: + +* `checks_failed_percent` - (Optional) The maximum percentage of failed checks permitted for a test to be successful. + +* `round_trip_time_ms` - (Optional) The maximum round-trip time in milliseconds permitted for a test to be successful. + +--- + +A `tcp_configuration` block supports the following: + +* `port` - (Required) The port for the Tcp connection. + +* `trace_route_enabled` - (Optional) Should path evaluation with trace route be enabled? Defaults to `true`. + +--- + +A `test_group` block supports the following: + +* `name` - (Required) The name of the test group for the Network Connection Monitor. + +* `destination_endpoints` - (Required) A list of destination endpoint names. + +* `source_endpoints` - (Required) A list of source endpoint names. -* `virtual_machine_id` - (Required) The ID of the virtual machine used as the source by connection monitor. +* `test_configuration_names` - (Required) A list of test configuration names. -* `port` - (Optional) The source port used by connection monitor. +* `enabled` - (Optional) Should the test group be enabled? Defaults to `true`. ## Attributes Reference -In addition to the Arguments listed above - the following Attributes are exported: +The following attributes are exported: * `id` - The ID of the Network Connection Monitor.