diff --git a/internal/services/compute/parse/vmss_instance.go b/internal/services/compute/parse/vmss_instance.go new file mode 100644 index 000000000000..60c0dc9e17f9 --- /dev/null +++ b/internal/services/compute/parse/vmss_instance.go @@ -0,0 +1,75 @@ +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + "strings" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" +) + +type VMSSInstanceId struct { + SubscriptionId string + ResourceGroup string + VirtualMachineScaleSetName string + VirtualMachineName string +} + +func NewVMSSInstanceID(subscriptionId, resourceGroup, virtualMachineScaleSetName, virtualMachineName string) VMSSInstanceId { + return VMSSInstanceId{ + SubscriptionId: subscriptionId, + ResourceGroup: resourceGroup, + VirtualMachineScaleSetName: virtualMachineScaleSetName, + VirtualMachineName: virtualMachineName, + } +} + +func (id VMSSInstanceId) String() string { + segments := []string{ + fmt.Sprintf("Virtual Machine Name %q", id.VirtualMachineName), + fmt.Sprintf("Virtual Machine Scale Set Name %q", id.VirtualMachineScaleSetName), + fmt.Sprintf("Resource Group %q", id.ResourceGroup), + } + segmentsStr := strings.Join(segments, " / ") + return fmt.Sprintf("%s: (%s)", "V M S S Instance", segmentsStr) +} + +func (id VMSSInstanceId) ID() string { + fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/virtualMachineScaleSets/%s/virtualMachines/%s" + return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup, id.VirtualMachineScaleSetName, id.VirtualMachineName) +} + +// VMSSInstanceID parses a VMSSInstance ID into an VMSSInstanceId struct +func VMSSInstanceID(input string) (*VMSSInstanceId, error) { + id, err := resourceids.ParseAzureResourceID(input) + if err != nil { + return nil, err + } + + resourceId := VMSSInstanceId{ + SubscriptionId: id.SubscriptionID, + ResourceGroup: id.ResourceGroup, + } + + if resourceId.SubscriptionId == "" { + return nil, fmt.Errorf("ID was missing the 'subscriptions' element") + } + + if resourceId.ResourceGroup == "" { + return nil, fmt.Errorf("ID was missing the 'resourceGroups' element") + } + + if resourceId.VirtualMachineScaleSetName, err = id.PopSegment("virtualMachineScaleSets"); err != nil { + return nil, err + } + if resourceId.VirtualMachineName, err = id.PopSegment("virtualMachines"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &resourceId, nil +} diff --git a/internal/services/compute/parse/vmss_instance_test.go b/internal/services/compute/parse/vmss_instance_test.go new file mode 100644 index 000000000000..1f7ccb4a74fa --- /dev/null +++ b/internal/services/compute/parse/vmss_instance_test.go @@ -0,0 +1,128 @@ +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "testing" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" +) + +var _ resourceids.Id = VMSSInstanceId{} + +func TestVMSSInstanceIDFormatter(t *testing.T) { + actual := NewVMSSInstanceID("12345678-1234-9876-4563-123456789012", "resGroup1", "vmss1", "vm1").ID() + expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/virtualMachineScaleSets/vmss1/virtualMachines/vm1" + if actual != expected { + t.Fatalf("Expected %q but got %q", expected, actual) + } +} + +func TestVMSSInstanceID(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *VMSSInstanceId + }{ + + { + // empty + Input: "", + Error: true, + }, + + { + // missing SubscriptionId + Input: "/", + Error: true, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Error: true, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Error: true, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Error: true, + }, + + { + // missing VirtualMachineScaleSetName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/", + Error: true, + }, + + { + // missing value for VirtualMachineScaleSetName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/virtualMachineScaleSets/", + Error: true, + }, + + { + // missing VirtualMachineName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/virtualMachineScaleSets/vmss1/", + Error: true, + }, + + { + // missing value for VirtualMachineName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/virtualMachineScaleSets/vmss1/virtualMachines/", + Error: true, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/virtualMachineScaleSets/vmss1/virtualMachines/vm1", + Expected: &VMSSInstanceId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + VirtualMachineScaleSetName: "vmss1", + VirtualMachineName: "vm1", + }, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.COMPUTE/VIRTUALMACHINESCALESETS/VMSS1/VIRTUALMACHINES/VM1", + Error: true, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := VMSSInstanceID(v.Input) + if err != nil { + if v.Error { + continue + } + + t.Fatalf("Expect a value but got an error: %s", err) + } + if v.Error { + t.Fatal("Expect an error but didn't get one") + } + + if actual.SubscriptionId != v.Expected.SubscriptionId { + t.Fatalf("Expected %q but got %q for SubscriptionId", v.Expected.SubscriptionId, actual.SubscriptionId) + } + if actual.ResourceGroup != v.Expected.ResourceGroup { + t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expected.ResourceGroup, actual.ResourceGroup) + } + if actual.VirtualMachineScaleSetName != v.Expected.VirtualMachineScaleSetName { + t.Fatalf("Expected %q but got %q for VirtualMachineScaleSetName", v.Expected.VirtualMachineScaleSetName, actual.VirtualMachineScaleSetName) + } + if actual.VirtualMachineName != v.Expected.VirtualMachineName { + t.Fatalf("Expected %q but got %q for VirtualMachineName", v.Expected.VirtualMachineName, actual.VirtualMachineName) + } + } +} diff --git a/internal/services/compute/resourceids.go b/internal/services/compute/resourceids.go index 03d28cd95b18..2d2d9f172c2c 100644 --- a/internal/services/compute/resourceids.go +++ b/internal/services/compute/resourceids.go @@ -19,3 +19,4 @@ package compute //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=DataDisk -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.Compute/virtualMachines/machine1/dataDisks/disk1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=Plan -id=/subscriptions/12345678-1234-9876-4563-123456789012/providers/Microsoft.MarketplaceOrdering/agreements/agreement1/offers/offer1/plans/hourly //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=HostGroup -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.Compute/hostGroups/hostgroup1 +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=VMSSInstance -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/virtualMachineScaleSets/vmss1/virtualMachines/vm1 diff --git a/internal/services/compute/validate/vmss_instance_id.go b/internal/services/compute/validate/vmss_instance_id.go new file mode 100644 index 000000000000..9e4268d00370 --- /dev/null +++ b/internal/services/compute/validate/vmss_instance_id.go @@ -0,0 +1,23 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + + "github.com/hashicorp/terraform-provider-azurerm/internal/services/compute/parse" +) + +func VMSSInstanceID(input interface{}, key string) (warnings []string, errors []error) { + v, ok := input.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected %q to be a string", key)) + return + } + + if _, err := parse.VMSSInstanceID(v); err != nil { + errors = append(errors, err) + } + + return +} diff --git a/internal/services/compute/validate/vmss_instance_id_test.go b/internal/services/compute/validate/vmss_instance_id_test.go new file mode 100644 index 000000000000..7cbca58119c6 --- /dev/null +++ b/internal/services/compute/validate/vmss_instance_id_test.go @@ -0,0 +1,88 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import "testing" + +func TestVMSSInstanceID(t *testing.T) { + cases := []struct { + Input string + Valid bool + }{ + + { + // empty + Input: "", + Valid: false, + }, + + { + // missing SubscriptionId + Input: "/", + Valid: false, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Valid: false, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Valid: false, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Valid: false, + }, + + { + // missing VirtualMachineScaleSetName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/", + Valid: false, + }, + + { + // missing value for VirtualMachineScaleSetName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/virtualMachineScaleSets/", + Valid: false, + }, + + { + // missing VirtualMachineName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/virtualMachineScaleSets/vmss1/", + Valid: false, + }, + + { + // missing value for VirtualMachineName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/virtualMachineScaleSets/vmss1/virtualMachines/", + Valid: false, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/virtualMachineScaleSets/vmss1/virtualMachines/vm1", + Valid: true, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.COMPUTE/VIRTUALMACHINESCALESETS/VMSS1/VIRTUALMACHINES/VM1", + Valid: false, + }, + } + for _, tc := range cases { + t.Logf("[DEBUG] Testing Value %s", tc.Input) + _, errors := VMSSInstanceID(tc.Input, "test") + valid := len(errors) == 0 + + if tc.Valid != valid { + t.Fatalf("Expected %t but got %t", tc.Valid, valid) + } + } +} diff --git a/internal/services/network/network_packet_capture_resource.go b/internal/services/network/network_packet_capture_resource.go index 062c759db666..cf9042ec0440 100644 --- a/internal/services/network/network_packet_capture_resource.go +++ b/internal/services/network/network_packet_capture_resource.go @@ -19,9 +19,11 @@ import ( func resourceNetworkPacketCapture() *pluginsdk.Resource { return &pluginsdk.Resource{ - Create: resourceNetworkPacketCaptureCreate, - Read: resourceNetworkPacketCaptureRead, - Delete: resourceNetworkPacketCaptureDelete, + Create: resourceNetworkPacketCaptureCreate, + Read: resourceNetworkPacketCaptureRead, + Delete: resourceNetworkPacketCaptureDelete, + DeprecationMessage: "The \"azurerm_network_packet_capture\" resource is deprecated and will be removed in favour of the `azurerm_virtual_machine_packet_capture` and `azurerm_virtual_machine_scale_set_packet_capture` resources in version 4.0 of the AzureRM Provider.", + Importer: pluginsdk.ImporterValidatingResourceId(func(id string) error { _, err := parse.PacketCaptureID(id) return err diff --git a/internal/services/network/network_packet_capture_resource_test.go b/internal/services/network/network_packet_capture_resource_test.go index 280c23a5d86f..ff3170fdf4b9 100644 --- a/internal/services/network/network_packet_capture_resource_test.go +++ b/internal/services/network/network_packet_capture_resource_test.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/features" "github.com/hashicorp/terraform-provider-azurerm/internal/services/network/parse" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" "github.com/hashicorp/terraform-provider-azurerm/utils" @@ -15,7 +16,11 @@ import ( type NetworkPacketCaptureResource struct{} +// todo remove for 4.0 func testAccNetworkPacketCapture_localDisk(t *testing.T) { + if features.FourPointOhBeta() { + t.Skip("this test requires 3.0 mode") + } data := acceptance.BuildTestData(t, "azurerm_network_packet_capture", "test") r := NetworkPacketCaptureResource{} @@ -30,7 +35,11 @@ func testAccNetworkPacketCapture_localDisk(t *testing.T) { }) } +// todo remove for 4.0 func testAccNetworkPacketCapture_requiresImport(t *testing.T) { + if features.FourPointOhBeta() { + t.Skip("this test requires 3.0 mode") + } data := acceptance.BuildTestData(t, "azurerm_network_packet_capture", "test") r := NetworkPacketCaptureResource{} @@ -48,7 +57,11 @@ func testAccNetworkPacketCapture_requiresImport(t *testing.T) { }) } +// todo remove for 4.0 func testAccNetworkPacketCapture_storageAccount(t *testing.T) { + if features.FourPointOhBeta() { + t.Skip("this test requires 3.0 mode") + } data := acceptance.BuildTestData(t, "azurerm_network_packet_capture", "test") r := NetworkPacketCaptureResource{} @@ -63,7 +76,11 @@ func testAccNetworkPacketCapture_storageAccount(t *testing.T) { }) } +// todo remove for 4.0 func testAccNetworkPacketCapture_storageAccountAndLocalDisk(t *testing.T) { + if features.FourPointOhBeta() { + t.Skip("this test requires 3.0 mode") + } data := acceptance.BuildTestData(t, "azurerm_network_packet_capture", "test") r := NetworkPacketCaptureResource{} @@ -78,7 +95,11 @@ func testAccNetworkPacketCapture_storageAccountAndLocalDisk(t *testing.T) { }) } +// todo remove for 4.0 func testAccNetworkPacketCapture_withFilters(t *testing.T) { + if features.FourPointOhBeta() { + t.Skip("this test requires 3.0 mode") + } data := acceptance.BuildTestData(t, "azurerm_network_packet_capture", "test") r := NetworkPacketCaptureResource{} diff --git a/internal/services/network/network_watcher_resource_test.go b/internal/services/network/network_watcher_resource_test.go index 9120e0fd5af9..5224d628fd23 100644 --- a/internal/services/network/network_watcher_resource_test.go +++ b/internal/services/network/network_watcher_resource_test.go @@ -58,6 +58,21 @@ func TestAccNetworkWatcher(t *testing.T) { "withFilters": testAccNetworkPacketCapture_withFilters, "requiresImport": testAccNetworkPacketCapture_requiresImport, }, + "VMPacketCapture": { + "localDisk": testAccVirtualMachinePacketCapture_localDisk, + "storageAccount": testAccVirtualMachinePacketCapture_storageAccount, + "storageAccountAndLocalDisk": testAccVirtualMachinePacketCapture_storageAccountAndLocalDisk, + "withFilters": testAccVirtualMachinePacketCapture_withFilters, + "requiresImport": testAccVirtualMachinePacketCapture_requiresImport, + }, + "VMSSPacketCapture": { + "localDisk": testAccVirtualMachineScaleSetPacketCapture_localDisk, + "storageAccount": testAccVirtualMachineScaleSetPacketCapture_storageAccount, + "storageAccountAndLocalDisk": testAccVirtualMachineScaleSetPacketCapture_storageAccountAndLocalDisk, + "withFilters": testAccVirtualMachineScaleSetPacketCapture_withFilters, + "requiresImport": testAccVirtualMachineScaleSetPacketCapture_requiresImport, + "machineScope": testAccVirtualMachineScaleSetPacketCapture_machineScope, + }, "FlowLog": { "basic": testAccNetworkWatcherFlowLog_basic, "requiresImport": testAccNetworkWatcherFlowLog_requiresImport, diff --git a/internal/services/network/registration.go b/internal/services/network/registration.go index 326962ca0a28..67c378011be6 100644 --- a/internal/services/network/registration.go +++ b/internal/services/network/registration.go @@ -128,6 +128,8 @@ func (r Registration) SupportedResources() map[string]*pluginsdk.Resource { "azurerm_virtual_hub_ip": resourceVirtualHubIP(), "azurerm_virtual_hub_route_table": resourceVirtualHubRouteTable(), "azurerm_virtual_hub_route_table_route": resourceVirtualHubRouteTableRoute(), + "azurerm_virtual_machine_packet_capture": resourceVirtualMachinePacketCapture(), + "azurerm_virtual_machine_scale_set_packet_capture": resourceVirtualMachineScaleSetPacketCapture(), "azurerm_virtual_network_dns_servers": resourceVirtualNetworkDnsServers(), "azurerm_virtual_network_gateway_connection": resourceVirtualNetworkGatewayConnection(), "azurerm_virtual_network_gateway_nat_rule": resourceVirtualNetworkGatewayNatRule(), diff --git a/internal/services/network/validate/file_path.go b/internal/services/network/validate/file_path.go new file mode 100644 index 000000000000..1475ad9e1efd --- /dev/null +++ b/internal/services/network/validate/file_path.go @@ -0,0 +1,16 @@ +package validate + +import ( + "fmt" + "regexp" +) + +func FilePath(v interface{}, k string) (warnings []string, errors []error) { + value := v.(string) + + if !regexp.MustCompile(`^(.)+.cap$`).MatchString(value) { + errors = append(errors, fmt.Errorf("%s must end with extension name '.cap'", k)) + } + + return warnings, errors +} diff --git a/internal/services/network/validate/file_path_test.go b/internal/services/network/validate/file_path_test.go new file mode 100644 index 000000000000..910371f02518 --- /dev/null +++ b/internal/services/network/validate/file_path_test.go @@ -0,0 +1,37 @@ +package validate + +import "testing" + +func TestFilePath(t *testing.T) { + cases := []struct { + Input string + Valid bool + }{ + { + Input: "", + Valid: false, + }, + { + Input: "testfilepath", + Valid: false, + }, + { + Input: "testfilepath.", + Valid: false, + }, + { + Input: "testfilepath.cap", + Valid: true, + }, + } + + for _, tc := range cases { + t.Logf("[DEBUG] Testing Value %s", tc.Input) + _, errors := FilePath(tc.Input, "test") + valid := len(errors) == 0 + + if tc.Valid != valid { + t.Fatalf("Expected %t but got %t", tc.Valid, valid) + } + } +} diff --git a/internal/services/network/virtual_machine_packet_capture_resource.go b/internal/services/network/virtual_machine_packet_capture_resource.go new file mode 100644 index 000000000000..22a84ee2a696 --- /dev/null +++ b/internal/services/network/virtual_machine_packet_capture_resource.go @@ -0,0 +1,384 @@ +package network + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform-provider-azurerm/helpers/tf" + "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + computeValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/compute/validate" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/network/parse" + networkValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/network/validate" + storageValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/storage/validate" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" + "github.com/hashicorp/terraform-provider-azurerm/internal/timeouts" + "github.com/hashicorp/terraform-provider-azurerm/utils" + "github.com/tombuildsstuff/kermit/sdk/network/2022-05-01/network" +) + +func resourceVirtualMachinePacketCapture() *pluginsdk.Resource { + return &pluginsdk.Resource{ + Create: resourceVirtualMachinePacketCaptureCreate, + Read: resourceVirtualMachinePacketCaptureRead, + Delete: resourceVirtualMachinePacketCaptureDelete, + + Importer: pluginsdk.ImporterValidatingResourceId(func(id string) error { + _, err := parse.PacketCaptureID(id) + return err + }), + + Timeouts: &pluginsdk.ResourceTimeout{ + Create: pluginsdk.DefaultTimeout(30 * time.Minute), + Read: pluginsdk.DefaultTimeout(5 * time.Minute), + Delete: pluginsdk.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "network_watcher_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: networkValidate.NetworkWatcherID, + }, + + "virtual_machine_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.Any( + computeValidate.VirtualMachineID, + ), + }, + + "maximum_bytes_per_packet": { + Type: pluginsdk.TypeInt, + Optional: true, + ForceNew: true, + Default: 0, + }, + + "maximum_bytes_per_session": { + Type: pluginsdk.TypeInt, + Optional: true, + ForceNew: true, + Default: 1073741824, + }, + + "maximum_capture_duration_in_seconds": { + Type: pluginsdk.TypeInt, + Optional: true, + ForceNew: true, + Default: 18000, + ValidateFunc: validation.IntBetween(1, 18000), + }, + + "storage_location": { + Type: pluginsdk.TypeList, + Required: true, + ForceNew: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "file_path": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: networkValidate.FilePath, + AtLeastOneOf: []string{"storage_location.0.file_path", "storage_location.0.storage_account_id"}, + }, + "storage_account_id": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: storageValidate.StorageAccountID, + AtLeastOneOf: []string{"storage_location.0.file_path", "storage_location.0.storage_account_id"}, + }, + "storage_path": { + Type: pluginsdk.TypeString, + Computed: true, + }, + }, + }, + }, + + "filter": { + Type: pluginsdk.TypeList, + Optional: true, + ForceNew: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "local_ip_address": { + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + }, + "local_port": { + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + }, + "protocol": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + string(network.PcProtocolAny), + string(network.PcProtocolTCP), + string(network.PcProtocolUDP), + }, false), + }, + "remote_ip_address": { + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + }, + "remote_port": { + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + }, + }, + }, + }, + }, + } +} + +func resourceVirtualMachinePacketCaptureCreate(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Network.PacketCapturesClient + subscriptionId := meta.(*clients.Client).Account.SubscriptionId + ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) + defer cancel() + + watcherId, err := parse.NetworkWatcherID(d.Get("network_watcher_id").(string)) + if err != nil { + return err + } + + id := parse.NewPacketCaptureID(subscriptionId, watcherId.ResourceGroup, watcherId.Name, d.Get("name").(string)) + + targetResourceId := d.Get("virtual_machine_id").(string) + bytesToCapturePerPacket := d.Get("maximum_bytes_per_packet").(int) + totalBytesPerSession := d.Get("maximum_bytes_per_session").(int) + timeLimitInSeconds := d.Get("maximum_capture_duration_in_seconds").(int) + + existing, err := client.Get(ctx, id.ResourceGroup, id.NetworkWatcherName, id.Name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("checking for presence of existing %s: %s", id, err) + } + } + + if !utils.ResponseWasNotFound(existing.Response) { + return tf.ImportAsExistsError("azurerm_virtual_machine_packet_capture", id.ID()) + } + + storageLocation, err := expandVirtualMachinePacketCaptureStorageLocation(d) + if err != nil { + return err + } + + properties := network.PacketCapture{ + PacketCaptureParameters: &network.PacketCaptureParameters{ + Target: utils.String(targetResourceId), + TargetType: network.PacketCaptureTargetTypeAzureVM, + StorageLocation: storageLocation, + BytesToCapturePerPacket: utils.Int64(int64(bytesToCapturePerPacket)), + TimeLimitInSeconds: utils.Int32(int32(timeLimitInSeconds)), + TotalBytesPerSession: utils.Int64(int64(totalBytesPerSession)), + Filters: expandVirtualMachinePacketCaptureFilters(d), + }, + } + + future, err := client.Create(ctx, id.ResourceGroup, id.NetworkWatcherName, id.Name, properties) + if err != nil { + return fmt.Errorf("creating %s: %+v", id, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for creation of %s: %+v", id, err) + } + + d.SetId(id.ID()) + + return resourceVirtualMachinePacketCaptureRead(d, meta) +} + +func resourceVirtualMachinePacketCaptureRead(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Network.PacketCapturesClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.PacketCaptureID(d.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, id.ResourceGroup, id.NetworkWatcherName, id.Name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[WARN] %s not found - removing from state", *id) + d.SetId("") + return nil + } + + return fmt.Errorf("reading %s: %+v", *id, err) + } + + d.Set("name", id.Name) + + networkWatcherId := parse.NewNetworkWatcherID(id.SubscriptionId, id.ResourceGroup, id.NetworkWatcherName) + d.Set("network_watcher_id", networkWatcherId.ID()) + + if props := resp.PacketCaptureResultProperties; props != nil { + d.Set("virtual_machine_id", props.Target) + d.Set("maximum_bytes_per_packet", int(*props.BytesToCapturePerPacket)) + d.Set("maximum_bytes_per_session", int(*props.TotalBytesPerSession)) + d.Set("maximum_capture_duration_in_seconds", int(*props.TimeLimitInSeconds)) + + location := flattenVirtualMachinePacketCaptureStorageLocation(props.StorageLocation) + if err := d.Set("storage_location", location); err != nil { + return fmt.Errorf("setting `storage_location`: %+v", err) + } + + filters := flattenVirtualMachinePacketCaptureFilters(props.Filters) + if err := d.Set("filter", filters); err != nil { + return fmt.Errorf("setting `filter`: %+v", err) + } + } + + return nil +} + +func resourceVirtualMachinePacketCaptureDelete(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Network.PacketCapturesClient + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.PacketCaptureID(d.Id()) + if err != nil { + return err + } + + future, err := client.Delete(ctx, id.ResourceGroup, id.NetworkWatcherName, id.Name) + if err != nil { + return fmt.Errorf("deleting %s: %+v", *id, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for the deletion of %s: %+v", *id, err) + } + + return nil +} + +func expandVirtualMachinePacketCaptureStorageLocation(d *pluginsdk.ResourceData) (*network.PacketCaptureStorageLocation, error) { + locations := d.Get("storage_location").([]interface{}) + if len(locations) == 0 { + return nil, fmt.Errorf("expandng `storage_location`: not found") + } + + location := locations[0].(map[string]interface{}) + + storageLocation := network.PacketCaptureStorageLocation{} + + if v := location["file_path"]; v != "" { + storageLocation.FilePath = utils.String(v.(string)) + } + if v := location["storage_account_id"]; v != "" { + storageLocation.StorageID = utils.String(v.(string)) + } + + return &storageLocation, nil +} + +func flattenVirtualMachinePacketCaptureStorageLocation(input *network.PacketCaptureStorageLocation) []interface{} { + if input == nil { + return []interface{}{} + } + + output := make(map[string]interface{}) + + if path := input.FilePath; path != nil { + output["file_path"] = *path + } + + if account := input.StorageID; account != nil { + output["storage_account_id"] = *account + } + + if path := input.StoragePath; path != nil { + output["storage_path"] = *path + } + + return []interface{}{output} +} + +func expandVirtualMachinePacketCaptureFilters(d *pluginsdk.ResourceData) *[]network.PacketCaptureFilter { + inputFilters := d.Get("filter").([]interface{}) + if len(inputFilters) == 0 { + return nil + } + + filters := make([]network.PacketCaptureFilter, 0) + + for _, v := range inputFilters { + inputFilter := v.(map[string]interface{}) + + localIPAddress := inputFilter["local_ip_address"].(string) + localPort := inputFilter["local_port"].(string) // TODO: should this be an int? + protocol := inputFilter["protocol"].(string) + remoteIPAddress := inputFilter["remote_ip_address"].(string) + remotePort := inputFilter["remote_port"].(string) + + filter := network.PacketCaptureFilter{ + LocalIPAddress: utils.String(localIPAddress), + LocalPort: utils.String(localPort), + Protocol: network.PcProtocol(protocol), + RemoteIPAddress: utils.String(remoteIPAddress), + RemotePort: utils.String(remotePort), + } + filters = append(filters, filter) + } + + return &filters +} + +func flattenVirtualMachinePacketCaptureFilters(input *[]network.PacketCaptureFilter) []interface{} { + filters := make([]interface{}, 0) + + if inFilter := input; inFilter != nil { + for _, v := range *inFilter { + filter := make(map[string]interface{}) + + if address := v.LocalIPAddress; address != nil { + filter["local_ip_address"] = *address + } + + if port := v.LocalPort; port != nil { + filter["local_port"] = *port + } + + filter["protocol"] = string(v.Protocol) + + if address := v.RemoteIPAddress; address != nil { + filter["remote_ip_address"] = *address + } + + if port := v.RemotePort; port != nil { + filter["remote_port"] = *port + } + + filters = append(filters, filter) + } + } + + return filters +} diff --git a/internal/services/network/virtual_machine_packet_capture_resource_test.go b/internal/services/network/virtual_machine_packet_capture_resource_test.go new file mode 100644 index 000000000000..595ef7f7fa36 --- /dev/null +++ b/internal/services/network/virtual_machine_packet_capture_resource_test.go @@ -0,0 +1,312 @@ +package network_test + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" + "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/network/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/utils" +) + +type VirtualMachinePacketCaptureResource struct{} + +func testAccVirtualMachinePacketCapture_localDisk(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_virtual_machine_packet_capture", "test") + r := VirtualMachinePacketCaptureResource{} + + data.ResourceSequentialTest(t, r, []acceptance.TestStep{ + { + Config: r.localDiskConfig(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func testAccVirtualMachinePacketCapture_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_virtual_machine_packet_capture", "test") + r := VirtualMachinePacketCaptureResource{} + + data.ResourceSequentialTest(t, r, []acceptance.TestStep{ + { + Config: r.localDiskConfig(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.RequiresImportErrorStep(r.localDiskConfig_requiresImport), + }) +} + +func testAccVirtualMachinePacketCapture_storageAccount(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_virtual_machine_packet_capture", "test") + r := VirtualMachinePacketCaptureResource{} + + data.ResourceSequentialTest(t, r, []acceptance.TestStep{ + { + Config: r.storageAccountConfig(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func testAccVirtualMachinePacketCapture_storageAccountAndLocalDisk(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_virtual_machine_packet_capture", "test") + r := VirtualMachinePacketCaptureResource{} + + data.ResourceSequentialTest(t, r, []acceptance.TestStep{ + { + Config: r.storageAccountAndLocalDiskConfig(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func testAccVirtualMachinePacketCapture_withFilters(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_virtual_machine_packet_capture", "test") + r := VirtualMachinePacketCaptureResource{} + + data.ResourceSequentialTest(t, r, []acceptance.TestStep{ + { + Config: r.localDiskConfigWithFilters(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func (t VirtualMachinePacketCaptureResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { + id, err := parse.PacketCaptureID(state.ID) + if err != nil { + return nil, err + } + + resp, err := clients.Network.PacketCapturesClient.Get(ctx, id.ResourceGroup, id.NetworkWatcherName, id.Name) + if err != nil { + return nil, fmt.Errorf("retrieving %s: %+v", id, err) + } + + return utils.Bool(resp.ID != nil), nil +} + +func (VirtualMachinePacketCaptureResource) template(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-watcher-%d" + location = "%s" +} + +resource "azurerm_network_watcher" "test" { + name = "acctestnw-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name +} + +resource "azurerm_virtual_network" "test" { + name = "acctvn-%d" + address_space = ["10.0.0.0/16"] + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name +} + +resource "azurerm_subnet" "test" { + name = "internal" + resource_group_name = azurerm_resource_group.test.name + virtual_network_name = azurerm_virtual_network.test.name + address_prefixes = ["10.0.2.0/24"] +} + +resource "azurerm_network_interface" "test" { + name = "acctni-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + ip_configuration { + name = "testconfiguration1" + subnet_id = azurerm_subnet.test.id + private_ip_address_allocation = "Dynamic" + } +} + +resource "azurerm_virtual_machine" "test" { + name = "acctvm-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + network_interface_ids = [azurerm_network_interface.test.id] + vm_size = "Standard_F2" + + storage_image_reference { + publisher = "Canonical" + offer = "UbuntuServer" + sku = "16.04-LTS" + version = "latest" + } + + storage_os_disk { + name = "osdisk" + caching = "ReadWrite" + create_option = "FromImage" + managed_disk_type = "Standard_LRS" + } + + delete_os_disk_on_termination = true + + os_profile { + computer_name = "hostname%d" + admin_username = "testadmin" + admin_password = "Password1234!" + } + + os_profile_linux_config { + disable_password_authentication = false + } +} + +resource "azurerm_virtual_machine_extension" "test" { + name = "network-watcher" + virtual_machine_id = azurerm_virtual_machine.test.id + publisher = "Microsoft.Azure.NetworkWatcher" + type = "NetworkWatcherAgentLinux" + type_handler_version = "1.4" + auto_upgrade_minor_version = true +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger, data.RandomInteger, data.RandomInteger) +} + +func (r VirtualMachinePacketCaptureResource) localDiskConfig(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_virtual_machine_packet_capture" "test" { + name = "acctestpc-%d" + network_watcher_id = azurerm_network_watcher.test.id + virtual_machine_id = azurerm_virtual_machine.test.id + + storage_location { + file_path = "/var/captures/packet.cap" + } + + depends_on = [azurerm_virtual_machine_extension.test] +} +`, r.template(data), data.RandomInteger) +} + +func (r VirtualMachinePacketCaptureResource) localDiskConfig_requiresImport(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_virtual_machine_packet_capture" "import" { + name = azurerm_virtual_machine_packet_capture.test.name + network_watcher_id = azurerm_virtual_machine_packet_capture.test.network_watcher_id + virtual_machine_id = azurerm_virtual_machine_packet_capture.test.virtual_machine_id + + storage_location { + file_path = "/var/captures/packet.cap" + } + + depends_on = [azurerm_virtual_machine_extension.test] +} +`, r.localDiskConfig(data)) +} + +func (r VirtualMachinePacketCaptureResource) localDiskConfigWithFilters(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_virtual_machine_packet_capture" "test" { + name = "acctestpc-%d" + network_watcher_id = azurerm_network_watcher.test.id + virtual_machine_id = azurerm_virtual_machine.test.id + + storage_location { + file_path = "/var/captures/packet.cap" + } + + filter { + local_ip_address = "127.0.0.1" + local_port = "8080;9020;" + protocol = "TCP" + } + + filter { + local_ip_address = "127.0.0.1" + local_port = "80;443;" + protocol = "UDP" + } + + depends_on = [azurerm_virtual_machine_extension.test] +} +`, r.template(data), data.RandomInteger) +} + +func (r VirtualMachinePacketCaptureResource) storageAccountConfig(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_storage_account" "test" { + name = "acctestsa%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_virtual_machine_packet_capture" "test" { + name = "acctestpc-%d" + network_watcher_id = azurerm_network_watcher.test.id + virtual_machine_id = azurerm_virtual_machine.test.id + + storage_location { + storage_account_id = azurerm_storage_account.test.id + } + + depends_on = [azurerm_virtual_machine_extension.test] +} +`, r.template(data), data.RandomString, data.RandomInteger) +} + +func (r VirtualMachinePacketCaptureResource) storageAccountAndLocalDiskConfig(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_storage_account" "test" { + name = "acctestsa%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_virtual_machine_packet_capture" "test" { + name = "acctestpc-%d" + network_watcher_id = azurerm_network_watcher.test.id + virtual_machine_id = azurerm_virtual_machine.test.id + + storage_location { + file_path = "/var/captures/packet.cap" + storage_account_id = azurerm_storage_account.test.id + } + + depends_on = [azurerm_virtual_machine_extension.test] +} +`, r.template(data), data.RandomString, data.RandomInteger) +} diff --git a/internal/services/network/virtual_machine_scale_set_packet_capture_resource.go b/internal/services/network/virtual_machine_scale_set_packet_capture_resource.go new file mode 100644 index 000000000000..d8ae20f54414 --- /dev/null +++ b/internal/services/network/virtual_machine_scale_set_packet_capture_resource.go @@ -0,0 +1,489 @@ +package network + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform-provider-azurerm/helpers/tf" + "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + computeParse "github.com/hashicorp/terraform-provider-azurerm/internal/services/compute/parse" + computeValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/compute/validate" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/network/parse" + networkValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/network/validate" + storageValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/storage/validate" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" + "github.com/hashicorp/terraform-provider-azurerm/internal/timeouts" + "github.com/hashicorp/terraform-provider-azurerm/utils" + "github.com/tombuildsstuff/kermit/sdk/network/2022-05-01/network" +) + +func resourceVirtualMachineScaleSetPacketCapture() *pluginsdk.Resource { + return &pluginsdk.Resource{ + Create: resourceVirtualMachineScaleSetPacketCaptureCreate, + Read: resourceVirtualMachineScaleSetPacketCaptureRead, + Delete: resourceVirtualMachineScaleSetPacketCaptureDelete, + + Importer: pluginsdk.ImporterValidatingResourceId(func(id string) error { + _, err := parse.PacketCaptureID(id) + return err + }), + + Timeouts: &pluginsdk.ResourceTimeout{ + Create: pluginsdk.DefaultTimeout(30 * time.Minute), + Read: pluginsdk.DefaultTimeout(5 * time.Minute), + Delete: pluginsdk.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "network_watcher_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: networkValidate.NetworkWatcherID, + }, + + "virtual_machine_scale_set_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.Any( + computeValidate.VirtualMachineScaleSetID, + ), + }, + + "maximum_bytes_per_packet": { + Type: pluginsdk.TypeInt, + Optional: true, + ForceNew: true, + Default: 0, + }, + + "maximum_bytes_per_session": { + Type: pluginsdk.TypeInt, + Optional: true, + ForceNew: true, + Default: 1073741824, + }, + + "maximum_capture_duration_in_seconds": { + Type: pluginsdk.TypeInt, + Optional: true, + ForceNew: true, + Default: 18000, + ValidateFunc: validation.IntBetween(1, 18000), + }, + + "storage_location": { + Type: pluginsdk.TypeList, + Required: true, + ForceNew: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "file_path": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: networkValidate.FilePath, + AtLeastOneOf: []string{"storage_location.0.file_path", "storage_location.0.storage_account_id"}, + }, + "storage_account_id": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: storageValidate.StorageAccountID, + AtLeastOneOf: []string{"storage_location.0.file_path", "storage_location.0.storage_account_id"}, + }, + "storage_path": { + Type: pluginsdk.TypeString, + Computed: true, + }, + }, + }, + }, + + "filter": { + Type: pluginsdk.TypeList, + Optional: true, + ForceNew: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "local_ip_address": { + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + }, + "local_port": { + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + }, + "protocol": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + string(network.PcProtocolAny), + string(network.PcProtocolTCP), + string(network.PcProtocolUDP), + }, false), + }, + "remote_ip_address": { + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + }, + "remote_port": { + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + }, + }, + }, + }, + + "machine_scope": { + Type: pluginsdk.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "exclude_instance_ids": { + Type: pluginsdk.TypeList, + Optional: true, + ForceNew: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + + "include_instance_ids": { + Type: pluginsdk.TypeList, + Optional: true, + ForceNew: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + }, + }, + }, + }, + } +} + +func resourceVirtualMachineScaleSetPacketCaptureCreate(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Network.PacketCapturesClient + subscriptionId := meta.(*clients.Client).Account.SubscriptionId + ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) + defer cancel() + + watcherId, err := parse.NetworkWatcherID(d.Get("network_watcher_id").(string)) + if err != nil { + return err + } + + id := parse.NewPacketCaptureID(subscriptionId, watcherId.ResourceGroup, watcherId.Name, d.Get("name").(string)) + + targetResourceId := d.Get("virtual_machine_scale_set_id").(string) + bytesToCapturePerPacket := d.Get("maximum_bytes_per_packet").(int) + totalBytesPerSession := d.Get("maximum_bytes_per_session").(int) + timeLimitInSeconds := d.Get("maximum_capture_duration_in_seconds").(int) + + existing, err := client.Get(ctx, id.ResourceGroup, id.NetworkWatcherName, id.Name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("checking for presence of existing %s: %s", id, err) + } + } + + if !utils.ResponseWasNotFound(existing.Response) { + return tf.ImportAsExistsError("azurerm_virtual_machine_scale_set_packet_capture", id.ID()) + } + + storageLocation, err := expandVirtualMachineScaleSetPacketCaptureStorageLocation(d) + if err != nil { + return err + } + + properties := network.PacketCapture{ + PacketCaptureParameters: &network.PacketCaptureParameters{ + Target: utils.String(targetResourceId), + TargetType: network.PacketCaptureTargetTypeAzureVMSS, + StorageLocation: storageLocation, + BytesToCapturePerPacket: utils.Int64(int64(bytesToCapturePerPacket)), + TimeLimitInSeconds: utils.Int32(int32(timeLimitInSeconds)), + TotalBytesPerSession: utils.Int64(int64(totalBytesPerSession)), + Filters: expandVirtualMachineScaleSetPacketCaptureFilters(d), + }, + } + + if v, ok := d.GetOk("machine_scope"); ok { + properties.PacketCaptureParameters.Scope = expandVirtualMachineScaleSetPacketCaptureMachineScope(v.([]interface{})) + } + + future, err := client.Create(ctx, id.ResourceGroup, id.NetworkWatcherName, id.Name, properties) + if err != nil { + return fmt.Errorf("creating %s: %+v", id, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for creation of %s: %+v", id, err) + } + + d.SetId(id.ID()) + + return resourceVirtualMachineScaleSetPacketCaptureRead(d, meta) +} + +func resourceVirtualMachineScaleSetPacketCaptureRead(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Network.PacketCapturesClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.PacketCaptureID(d.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, id.ResourceGroup, id.NetworkWatcherName, id.Name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[WARN] %s not found - removing from state", *id) + d.SetId("") + return nil + } + + return fmt.Errorf("reading %s: %+v", *id, err) + } + + d.Set("name", id.Name) + + networkWatcherId := parse.NewNetworkWatcherID(id.SubscriptionId, id.ResourceGroup, id.NetworkWatcherName) + d.Set("network_watcher_id", networkWatcherId.ID()) + + if props := resp.PacketCaptureResultProperties; props != nil { + d.Set("virtual_machine_scale_set_id", props.Target) + d.Set("maximum_bytes_per_packet", int(*props.BytesToCapturePerPacket)) + d.Set("maximum_bytes_per_session", int(*props.TotalBytesPerSession)) + d.Set("maximum_capture_duration_in_seconds", int(*props.TimeLimitInSeconds)) + + location := flattenVirtualMachineScaleSetPacketCaptureStorageLocation(props.StorageLocation) + if err := d.Set("storage_location", location); err != nil { + return fmt.Errorf("setting `storage_location`: %+v", err) + } + + filters := flattenVirtualMachineScaleSetPacketCaptureFilters(props.Filters) + if err := d.Set("filter", filters); err != nil { + return fmt.Errorf("setting `filter`: %+v", err) + } + + scope, err := flattenVirtualMachineScaleSetPacketCaptureMachineScope(props.Scope) + if err != nil { + return err + } + if err := d.Set("machine_scope", scope); err != nil { + return fmt.Errorf(`setting "machine_scope": %+v`, err) + } + } + + return nil +} + +func resourceVirtualMachineScaleSetPacketCaptureDelete(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Network.PacketCapturesClient + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.PacketCaptureID(d.Id()) + if err != nil { + return err + } + + future, err := client.Delete(ctx, id.ResourceGroup, id.NetworkWatcherName, id.Name) + if err != nil { + return fmt.Errorf("deleting %s: %+v", *id, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for the deletion of %s: %+v", *id, err) + } + + return nil +} + +func expandVirtualMachineScaleSetPacketCaptureStorageLocation(d *pluginsdk.ResourceData) (*network.PacketCaptureStorageLocation, error) { + locations := d.Get("storage_location").([]interface{}) + if len(locations) == 0 { + return nil, fmt.Errorf("expandng `storage_location`: not found") + } + + location := locations[0].(map[string]interface{}) + + storageLocation := network.PacketCaptureStorageLocation{} + + if v := location["file_path"]; v != "" { + storageLocation.FilePath = utils.String(v.(string)) + } + if v := location["storage_account_id"]; v != "" { + storageLocation.StorageID = utils.String(v.(string)) + } + + return &storageLocation, nil +} + +func flattenVirtualMachineScaleSetPacketCaptureStorageLocation(input *network.PacketCaptureStorageLocation) []interface{} { + if input == nil { + return []interface{}{} + } + + output := make(map[string]interface{}) + + if path := input.FilePath; path != nil { + output["file_path"] = *path + } + + if account := input.StorageID; account != nil { + output["storage_account_id"] = *account + } + + if path := input.StoragePath; path != nil { + output["storage_path"] = *path + } + + return []interface{}{output} +} + +func expandVirtualMachineScaleSetPacketCaptureFilters(d *pluginsdk.ResourceData) *[]network.PacketCaptureFilter { + inputFilters := d.Get("filter").([]interface{}) + if len(inputFilters) == 0 { + return nil + } + + filters := make([]network.PacketCaptureFilter, 0) + + for _, v := range inputFilters { + inputFilter := v.(map[string]interface{}) + + localIPAddress := inputFilter["local_ip_address"].(string) + localPort := inputFilter["local_port"].(string) // TODO: should this be an int? + protocol := inputFilter["protocol"].(string) + remoteIPAddress := inputFilter["remote_ip_address"].(string) + remotePort := inputFilter["remote_port"].(string) + + filter := network.PacketCaptureFilter{ + LocalIPAddress: utils.String(localIPAddress), + LocalPort: utils.String(localPort), + Protocol: network.PcProtocol(protocol), + RemoteIPAddress: utils.String(remoteIPAddress), + RemotePort: utils.String(remotePort), + } + filters = append(filters, filter) + } + + return &filters +} + +func flattenVirtualMachineScaleSetPacketCaptureFilters(input *[]network.PacketCaptureFilter) []interface{} { + filters := make([]interface{}, 0) + + if inFilter := input; inFilter != nil { + for _, v := range *inFilter { + filter := make(map[string]interface{}) + + if address := v.LocalIPAddress; address != nil { + filter["local_ip_address"] = *address + } + + if port := v.LocalPort; port != nil { + filter["local_port"] = *port + } + + filter["protocol"] = string(v.Protocol) + + if address := v.RemoteIPAddress; address != nil { + filter["remote_ip_address"] = *address + } + + if port := v.RemotePort; port != nil { + filter["remote_port"] = *port + } + + filters = append(filters, filter) + } + } + + return filters +} + +func expandVirtualMachineScaleSetPacketCaptureMachineScope(input []interface{}) *network.PacketCaptureMachineScope { + if len(input) == 0 || input[0] == nil { + return nil + } + + raw := input[0].(map[string]interface{}) + output := &network.PacketCaptureMachineScope{} + + if exclude := raw["exclude_instance_ids"].([]interface{}); len(exclude) > 0 { + output.Exclude = utils.ExpandStringSlice(exclude) + } + + if include := raw["include_instance_ids"].([]interface{}); len(include) > 0 { + output.Include = utils.ExpandStringSlice(include) + } + + return output +} + +func flattenVirtualMachineScaleSetPacketCaptureMachineScope(input *network.PacketCaptureMachineScope) ([]interface{}, error) { + outputs := make([]interface{}, 0) + if input == nil || (input.Exclude == nil && input.Include == nil) || (len(*input.Exclude) == 0 && len(*input.Include) == 0) { + return outputs, nil + } + + output := make(map[string]interface{}, 0) + + excludedInstanceIds, err := flattenVirtualMachineScaleSetPacketCaptureScopeInstanceIds(input.Exclude) + if err != nil { + return nil, err + } + output["exclude_instance_ids"] = excludedInstanceIds + + includedInstanceIds, err := flattenVirtualMachineScaleSetPacketCaptureScopeInstanceIds(input.Include) + if err != nil { + return nil, err + } + output["include_instance_ids"] = includedInstanceIds + + outputs = append(outputs, output) + + return outputs, nil +} + +func flattenVirtualMachineScaleSetPacketCaptureScopeInstanceIds(input *[]string) ([]string, error) { + instances := make([]string, 0) + if input == nil { + return instances, nil + } + + for _, instance := range *input { + instance, err := computeParse.VMSSInstanceID(instance) + if err != nil { + return nil, err + } + + instances = append(instances, instance.VirtualMachineName) + } + + return instances, nil +} diff --git a/internal/services/network/virtual_machine_scale_set_packet_capture_resource_test.go b/internal/services/network/virtual_machine_scale_set_packet_capture_resource_test.go new file mode 100644 index 000000000000..48d5be0120be --- /dev/null +++ b/internal/services/network/virtual_machine_scale_set_packet_capture_resource_test.go @@ -0,0 +1,342 @@ +package network_test + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" + "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/network/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/utils" +) + +type VirtualMachineScaleSetPacketCaptureResource struct{} + +func testAccVirtualMachineScaleSetPacketCapture_localDisk(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_virtual_machine_scale_set_packet_capture", "test") + r := VirtualMachineScaleSetPacketCaptureResource{} + + data.ResourceSequentialTest(t, r, []acceptance.TestStep{ + { + Config: r.localDiskConfig(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func testAccVirtualMachineScaleSetPacketCapture_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_virtual_machine_scale_set_packet_capture", "test") + r := VirtualMachineScaleSetPacketCaptureResource{} + + data.ResourceSequentialTest(t, r, []acceptance.TestStep{ + { + Config: r.localDiskConfig(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.RequiresImportErrorStep(r.localDiskConfig_requiresImport), + }) +} + +func testAccVirtualMachineScaleSetPacketCapture_storageAccount(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_virtual_machine_scale_set_packet_capture", "test") + r := VirtualMachineScaleSetPacketCaptureResource{} + + data.ResourceSequentialTest(t, r, []acceptance.TestStep{ + { + Config: r.storageAccountConfig(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func testAccVirtualMachineScaleSetPacketCapture_storageAccountAndLocalDisk(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_virtual_machine_scale_set_packet_capture", "test") + r := VirtualMachineScaleSetPacketCaptureResource{} + + data.ResourceSequentialTest(t, r, []acceptance.TestStep{ + { + Config: r.storageAccountAndLocalDiskConfig(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func testAccVirtualMachineScaleSetPacketCapture_withFilters(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_virtual_machine_scale_set_packet_capture", "test") + r := VirtualMachineScaleSetPacketCaptureResource{} + + data.ResourceSequentialTest(t, r, []acceptance.TestStep{ + { + Config: r.localDiskConfigWithFilters(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func testAccVirtualMachineScaleSetPacketCapture_machineScope(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_virtual_machine_scale_set_packet_capture", "test") + r := VirtualMachineScaleSetPacketCaptureResource{} + + data.ResourceSequentialTest(t, r, []acceptance.TestStep{ + { + Config: r.machineScope(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func (t VirtualMachineScaleSetPacketCaptureResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { + id, err := parse.PacketCaptureID(state.ID) + if err != nil { + return nil, err + } + + resp, err := clients.Network.PacketCapturesClient.Get(ctx, id.ResourceGroup, id.NetworkWatcherName, id.Name) + if err != nil { + return nil, fmt.Errorf("retrieving %s: %+v", id, err) + } + + return utils.Bool(resp.ID != nil), nil +} + +func (VirtualMachineScaleSetPacketCaptureResource) template(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-watcher-%d" + location = "%s" +} + +resource "azurerm_network_watcher" "test" { + name = "acctestnw-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name +} + +resource "azurerm_virtual_network" "test" { + name = "acctvn-%d" + address_space = ["10.0.0.0/16"] + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name +} + +resource "azurerm_subnet" "test" { + name = "internal" + resource_group_name = azurerm_resource_group.test.name + virtual_network_name = azurerm_virtual_network.test.name + address_prefixes = ["10.0.2.0/24"] +} + +resource "azurerm_linux_virtual_machine_scale_set" "test" { + name = "acctestvmss-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + sku = "Standard_F2" + instances = 4 + admin_username = "adminuser" + admin_password = "P@ssword1234!" + computer_name_prefix = "my-linux-computer-name-prefix" + upgrade_mode = "Automatic" + + disable_password_authentication = false + + source_image_reference { + publisher = "Canonical" + offer = "UbuntuServer" + sku = "16.04-LTS" + version = "latest" + } + + os_disk { + storage_account_type = "Standard_LRS" + caching = "ReadWrite" + } + + network_interface { + name = "example" + primary = true + + ip_configuration { + name = "internal" + primary = true + subnet_id = azurerm_subnet.test.id + } + } +} + +resource "azurerm_virtual_machine_scale_set_extension" "test" { + name = "network-watcher" + virtual_machine_scale_set_id = azurerm_linux_virtual_machine_scale_set.test.id + publisher = "Microsoft.Azure.NetworkWatcher" + type = "NetworkWatcherAgentLinux" + type_handler_version = "1.4" + auto_upgrade_minor_version = true + automatic_upgrade_enabled = true +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger) +} + +func (r VirtualMachineScaleSetPacketCaptureResource) localDiskConfig(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_virtual_machine_scale_set_packet_capture" "test" { + name = "acctestpc-%d" + network_watcher_id = azurerm_network_watcher.test.id + virtual_machine_scale_set_id = azurerm_linux_virtual_machine_scale_set.test.id + + storage_location { + file_path = "/var/captures/packet.cap" + } + + depends_on = [azurerm_virtual_machine_scale_set_extension.test] +} +`, r.template(data), data.RandomInteger) +} + +func (r VirtualMachineScaleSetPacketCaptureResource) localDiskConfig_requiresImport(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_virtual_machine_scale_set_packet_capture" "import" { + name = azurerm_virtual_machine_scale_set_packet_capture.test.name + network_watcher_id = azurerm_virtual_machine_scale_set_packet_capture.test.network_watcher_id + virtual_machine_scale_set_id = azurerm_virtual_machine_scale_set_packet_capture.test.virtual_machine_scale_set_id + + storage_location { + file_path = "/var/captures/packet.cap" + } + + depends_on = [azurerm_virtual_machine_scale_set_extension.test] +} +`, r.localDiskConfig(data)) +} + +func (r VirtualMachineScaleSetPacketCaptureResource) localDiskConfigWithFilters(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_virtual_machine_scale_set_packet_capture" "test" { + name = "acctestpc-%d" + network_watcher_id = azurerm_network_watcher.test.id + virtual_machine_scale_set_id = azurerm_linux_virtual_machine_scale_set.test.id + + storage_location { + file_path = "/var/captures/packet.cap" + } + + filter { + local_ip_address = "127.0.0.1" + local_port = "8080;9020;" + protocol = "TCP" + } + + filter { + local_ip_address = "127.0.0.1" + local_port = "80;443;" + protocol = "UDP" + } + + depends_on = [azurerm_virtual_machine_scale_set_extension.test] +} +`, r.template(data), data.RandomInteger) +} + +func (r VirtualMachineScaleSetPacketCaptureResource) storageAccountConfig(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_storage_account" "test" { + name = "acctestsa%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_virtual_machine_scale_set_packet_capture" "test" { + name = "acctestpc-%d" + network_watcher_id = azurerm_network_watcher.test.id + virtual_machine_scale_set_id = azurerm_linux_virtual_machine_scale_set.test.id + + storage_location { + storage_account_id = azurerm_storage_account.test.id + } + + depends_on = [azurerm_virtual_machine_scale_set_extension.test] +} +`, r.template(data), data.RandomString, data.RandomInteger) +} + +func (r VirtualMachineScaleSetPacketCaptureResource) storageAccountAndLocalDiskConfig(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_storage_account" "test" { + name = "acctestsa%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_virtual_machine_scale_set_packet_capture" "test" { + name = "acctestpc-%d" + network_watcher_id = azurerm_network_watcher.test.id + virtual_machine_scale_set_id = azurerm_linux_virtual_machine_scale_set.test.id + + storage_location { + file_path = "/var/captures/packet.cap" + storage_account_id = azurerm_storage_account.test.id + } + + depends_on = [azurerm_virtual_machine_scale_set_extension.test] +} +`, r.template(data), data.RandomString, data.RandomInteger) +} + +func (r VirtualMachineScaleSetPacketCaptureResource) machineScope(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_virtual_machine_scale_set_packet_capture" "test" { + name = "acctestpc-%d" + network_watcher_id = azurerm_network_watcher.test.id + virtual_machine_scale_set_id = azurerm_linux_virtual_machine_scale_set.test.id + + storage_location { + file_path = "/var/captures/packet.cap" + } + + machine_scope { + include_instance_ids = ["0", "1"] + exclude_instance_ids = ["2", "3"] + } + + depends_on = [azurerm_virtual_machine_scale_set_extension.test] +} +`, r.template(data), data.RandomInteger) +} diff --git a/website/docs/r/network_packet_capture.html.markdown b/website/docs/r/network_packet_capture.html.markdown index 40226d2e426d..d8d11aa2e7dd 100644 --- a/website/docs/r/network_packet_capture.html.markdown +++ b/website/docs/r/network_packet_capture.html.markdown @@ -7,10 +7,12 @@ description: |- --- -# azurerm_packet_capture +# azurerm_network_packet_capture Configures Network Packet Capturing against a Virtual Machine using a Network Watcher. +!> **NOTE:** The `azurerm_network_packet_capture` resource is deprecated and will be removed in favour of `azurerm_virtual_machine_packet_capture` and `azurerm_virtual_machine_scale_set_packet_capture` in version 4.0 of the AzureRM Provider. + ## Example Usage ```hcl diff --git a/website/docs/r/virtual_machine_packet_capture.html.markdown b/website/docs/r/virtual_machine_packet_capture.html.markdown new file mode 100644 index 000000000000..de467da3871c --- /dev/null +++ b/website/docs/r/virtual_machine_packet_capture.html.markdown @@ -0,0 +1,188 @@ +--- +subcategory: "Network" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_virtual_machine_packet_capture" +description: |- + Configures Packet Capturing against a Virtual Machine using a Network Watcher. + +--- + +# azurerm_virtual_machine_packet_capture + +Configures Network Packet Capturing against a Virtual Machine using a Network Watcher. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "example" { + name = "example-resources" + location = "West Europe" +} + +resource "azurerm_network_watcher" "example" { + name = "example-nw" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name +} + +resource "azurerm_virtual_network" "example" { + name = "example-network" + 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 = "internal" + resource_group_name = azurerm_resource_group.example.name + virtual_network_name = azurerm_virtual_network.example.name + address_prefixes = ["10.0.2.0/24"] +} + +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_F2" + + storage_image_reference { + publisher = "Canonical" + offer = "UbuntuServer" + sku = "16.04-LTS" + version = "latest" + } + + storage_os_disk { + name = "osdisk" + caching = "ReadWrite" + create_option = "FromImage" + managed_disk_type = "Standard_LRS" + } + + os_profile { + computer_name = "pctest-vm" + admin_username = "testadmin" + admin_password = "Password1234!" + } + + os_profile_linux_config { + disable_password_authentication = false + } +} + +resource "azurerm_virtual_machine_extension" "example" { + name = "network-watcher" + 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_storage_account" "example" { + name = "examplesa" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_virtual_machine_packet_capture" "example" { + name = "example-pc" + network_watcher_id = azurerm_network_watcher.example.id + virtual_machine_id = azurerm_virtual_machine.example.id + + storage_location { + storage_account_id = azurerm_storage_account.example.id + } + + depends_on = [azurerm_virtual_machine_extension.example] +} +``` + +~> **NOTE:** This Resource requires that [the Network Watcher Virtual Machine Extension](https://docs.microsoft.com/azure/network-watcher/network-watcher-packet-capture-manage-portal#before-you-begin) is installed on the Virtual Machine before capturing can be enabled which can be installed via [the `azurerm_virtual_machine_extension` resource](virtual_machine_extension.html). + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name to use for this Network Packet Capture. Changing this forces a new resource to be created. + +* `network_watcher_id` - (Required) The resource ID of the Network Watcher. Changing this forces a new resource to be created. + +* `virtual_machine_id` - (Required) The resource ID of the target Virtual Machine to capture packets from. Changing this forces a new resource to be created. + +* `maximum_bytes_per_packet` - (Optional) The number of bytes captured per packet. The remaining bytes are truncated. Defaults to `0` (Entire Packet Captured). Changing this forces a new resource to be created. + +* `maximum_bytes_per_session` - (Optional) Maximum size of the capture in Bytes. Defaults to `1073741824` (1GB). Changing this forces a new resource to be created. + +* `maximum_capture_duration_in_seconds` - (Optional) The maximum duration of the capture session in seconds. Defaults to `18000` (5 hours). Changing this forces a new resource to be created. + +* `storage_location` - (Required) A `storage_location` block as defined below. Changing this forces a new resource to be created. + +* `filter` - (Optional) One or more `filter` blocks as defined below. Changing this forces a new resource to be created. + +--- + +A `storage_location` block contains: + +* `file_path` - (Optional) A valid local path on the target Virtual Machine. Must include the name of the capture file (*.cap). For Linux Virtual Machines it must start with `/var/captures`. + +* `storage_account_id` - (Optional) The ID of the storage account where the packet capture sessions should be saved to. + +~> **NOTE:** At least one of `file_path` or `storage_account_id` must be specified. + +A `filter` block contains: + +* `local_ip_address` - (Optional) The local IP Address to be filtered on. Specify `127.0.0.1` for a single address entry, `127.0.0.1-127.0.0.255` for a range and `127.0.0.1;127.0.0.5` for multiple entries. Multiple ranges and mixing ranges with multiple entries are currently not supported. Changing this forces a new resource to be created. + +* `local_port` - (Optional) The local port to be filtered on. Specify `80` for single port entry, `80-85` for a range and `80;443;` for multiple entries. Multiple ranges and mixing ranges with multiple entries are currently not supported. Changing this forces a new resource to be created. + +* `protocol` - (Required) The Protocol to be filtered on. Possible values include `Any`, `TCP` and `UDP`. Changing this forces a new resource to be created. + +* `remote_ip_address` - (Optional) The remote IP Address to be filtered on. Specify `127.0.0.1` for a single address entry, `127.0.0.1-127.0.0.255` for a range and `127.0.0.1;127.0.0.5` for multiple entries. Multiple ranges and mixing ranges with multiple entries are currently not supported. Changing this forces a new resource to be created. + +* `remote_port` - (Optional) The remote port to be filtered on. Specify `80` for single port entry, `80-85` for a range and `80;443;` for multiple entries. Multiple ranges and mixing ranges with multiple entries are currently not supported. Changing this forces a new resource to be created. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The Virtual Machine Packet Capture ID. + +* `storage_location` - (Required) A `storage_location` block as defined below. + +--- + +A `storage_location` block contains: + +* `storage_path` - The URI of the storage path where the packet capture sessions are saved to. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/language/resources/syntax#operation-timeouts) for certain actions: + +* `create` - (Defaults to 30 minutes) Used when creating the Virtual Machine Packet Capture. +* `read` - (Defaults to 5 minutes) Used when retrieving the Virtual Machine Packet Capture. +* `delete` - (Defaults to 30 minutes) Used when deleting the Virtual Machine Packet Capture. + +## Import + +Virtual Machine Packet Captures can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_virtual_machine_packet_capture.capture1 /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/mygroup1/providers/Microsoft.Network/networkWatchers/watcher1/packetCaptures/capture1 +``` diff --git a/website/docs/r/virtual_machine_scale_set_packet_capture.html.markdown b/website/docs/r/virtual_machine_scale_set_packet_capture.html.markdown new file mode 100644 index 000000000000..b0727a36e8c3 --- /dev/null +++ b/website/docs/r/virtual_machine_scale_set_packet_capture.html.markdown @@ -0,0 +1,189 @@ +--- +subcategory: "Network" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_virtual_machine_scale_set_packet_capture" +description: |- + Configures Packet Capturing against a Virtual Machine Scale Set using a Network Watcher. + +--- + +# azurerm_virtual_machine_scale_set_packet_capture + +Configures Network Packet Capturing against a Virtual Machine Scale Set using a Network Watcher. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "example" { + name = "example-resources" + location = "West Europe" +} + +resource "azurerm_network_watcher" "example" { + name = "example-nw" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name +} + +resource "azurerm_virtual_network" "example" { + name = "example-vn" + 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 = "internal" + resource_group_name = azurerm_resource_group.example.name + virtual_network_name = azurerm_virtual_network.example.name + address_prefixes = ["10.0.2.0/24"] +} + +resource "azurerm_linux_virtual_machine_scale_set" "example" { + name = "example-vmss" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + sku = "Standard_F2" + instances = 4 + admin_username = "adminuser" + admin_password = "P@ssword1234!" + computer_name_prefix = "my-linux-computer-name-prefix" + upgrade_mode = "Automatic" + + disable_password_authentication = false + + source_image_reference { + publisher = "Canonical" + offer = "UbuntuServer" + sku = "16.04-LTS" + version = "latest" + } + + os_disk { + storage_account_type = "Standard_LRS" + caching = "ReadWrite" + } + + network_interface { + name = "example" + primary = true + + ip_configuration { + name = "internal" + primary = true + subnet_id = azurerm_subnet.example.id + } + } +} + +resource "azurerm_virtual_machine_scale_set_extension" "example" { + name = "network-watcher" + virtual_machine_scale_set_id = azurerm_linux_virtual_machine_scale_set.example.id + publisher = "Microsoft.Azure.NetworkWatcher" + type = "NetworkWatcherAgentLinux" + type_handler_version = "1.4" + auto_upgrade_minor_version = true + automatic_upgrade_enabled = true +} + +resource "azurerm_virtual_machine_scale_set_packet_capture" "example" { + name = "example-pc" + network_watcher_id = azurerm_network_watcher.example.id + virtual_machine_scale_set_id = azurerm_linux_virtual_machine_scale_set.example.id + + storage_location { + file_path = "/var/captures/packet.cap" + } + + machine_scope { + include_instance_ids = ["0"] + exclude_instance_ids = ["1"] + } + + depends_on = [azurerm_virtual_machine_scale_set_extension.example] +} +``` + +~> **NOTE:** This Resource requires that [the Network Watcher Extension](https://docs.microsoft.com/azure/network-watcher/network-watcher-packet-capture-manage-portal#before-you-begin) is installed on the Virtual Machine Scale Set before capturing can be enabled which can be installed via [the `azurerm_virtual_machine_scale_set_extension` resource](virtual_machine_scale_set_extension.html). + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name to use for this Network Packet Capture. Changing this forces a new resource to be created. + +* `network_watcher_id` - (Required) The resource ID of the Network Watcher. Changing this forces a new resource to be created. + +* `virtual_machine_scale_set_id` - (Required) The resource ID of the Virtual Machine Scale Set to capture packets from. Changing this forces a new resource to be created. + +* `maximum_bytes_per_packet` - (Optional) The number of bytes captured per packet. The remaining bytes are truncated. Defaults to `0` (Entire Packet Captured). Changing this forces a new resource to be created. + +* `maximum_bytes_per_session` - (Optional) Maximum size of the capture in Bytes. Defaults to `1073741824` (1GB). Changing this forces a new resource to be created. + +* `maximum_capture_duration_in_seconds` - (Optional) The maximum duration of the capture session in seconds. Defaults to `18000` (5 hours). Changing this forces a new resource to be created. + +* `storage_location` - (Required) A `storage_location` block as defined below. Changing this forces a new resource to be created. + +* `filter` - (Optional) One or more `filter` blocks as defined below. Changing this forces a new resource to be created. + +* `machine_scope` - (Optional) A `machine_scope` block as defined below. Changing this forces a new resource to be created. + +--- + +A `storage_location` block contains: + +* `file_path` - (Optional) A valid local path on the targeting VM. Must include the name of the capture file (*.cap). For Linux virtual machine it must start with `/var/captures`. + +* `storage_account_id` - (Optional) The ID of the storage account to save the packet capture session + +~> **NOTE:** At least one of `file_path` or `storage_account_id` must be specified. + +A `filter` block contains: + +* `local_ip_address` - (Optional) The local IP Address to be filtered on. Specify `127.0.0.1` for a single address entry, `127.0.0.1-127.0.0.255` for a range and `127.0.0.1;127.0.0.5` for multiple entries. Multiple ranges and mixing ranges with multiple entries are currently not supported. Changing this forces a new resource to be created. + +* `local_port` - (Optional) The local port to be filtered on. Specify `80` for single port entry, `80-85` for a range and `80;443;` for multiple entries. Multiple ranges and mixing ranges with multiple entries are currently not supported. Changing this forces a new resource to be created. + +* `protocol` - (Required) The Protocol to be filtered on. Possible values include `Any`, `TCP` and `UDP`. Changing this forces a new resource to be created. + +* `remote_ip_address` - (Optional) The remote IP Address to be filtered on. Specify `127.0.0.1` for a single address entry, `127.0.0.1-127.0.0.255` for a range and `127.0.0.1;127.0.0.5` for multiple entries. Multiple ranges and mixing ranges with multiple entries are currently not supported. Changing this forces a new resource to be created. + +* `remote_port` - (Optional) The remote port to be filtered on. Specify `80` for single port entry, `80-85` for a range and `80;443;` for multiple entries. Multiple ranges and mixing ranges with multiple entries are currently not supported. Changing this forces a new resource to be created. + +--- + +A `machine_scope` block contains: + +* `exclude_instance_ids` - (Optional) A list of Virtual Machine Scale Set instance IDs which should be excluded from running Packet Capture, e.g. `["0", "2"]`. Changing this forces a new resource to be created. + +* `include_instance_ids` - (Optional) A list of Virtual Machine Scale Set instance IDs which should be included for Packet Capture, e.g. `["1", "3"]`. Changing this forces a new resource to be created. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The Virtual Machine Scale Set Packet Capture ID. + +* `storage_location` - (Required) A `storage_location` block as defined below. + +--- + +A `storage_location` block contains: + +* `storage_path` - The URI of the storage path where the packet capture sessions are saved to. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/language/resources/syntax#operation-timeouts) for certain actions: + +* `create` - (Defaults to 30 minutes) Used when creating the Virtual Machine Scale Set Packet Capture. +* `read` - (Defaults to 5 minutes) Used when retrieving the Virtual Machine Scale Set Packet Capture. +* `delete` - (Defaults to 30 minutes) Used when deleting the Virtual Machine Scale Set Packet Capture. + +## Import + +Virtual Machine Scale Set Packet Captures can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_virtual_machine_scale_set_packet_capture.capture1 /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/mygroup1/providers/Microsoft.Network/networkWatchers/watcher1/packetCaptures/capture1 +```