From c1b571aad97a535f7cde8f4b4bb40df2f35a8f34 Mon Sep 17 00:00:00 2001 From: Dave Seepersad <“davesee@microsoft.com”> Date: Wed, 16 Sep 2020 14:00:07 -0400 Subject: [PATCH 01/56] feat: adding Virtual Machine module --- .../terraform-azure-vm-example/README.md | 155 +++++++ .../azure/terraform-azure-vm-example/main.tf | 136 ++++++ .../terraform-azure-vm-example/outputs.tf | 32 ++ .../terraform-azure-vm-example/variables.tf | 79 ++++ modules/azure/availabilityset.go | 132 ++++++ modules/azure/compute.go | 407 ++++++++++++++++-- modules/azure/disk.go | 90 ++++ modules/azure/networkinterface.go | 142 ++++++ modules/azure/publicaddress.go | 117 +++++ modules/azure/resourceid.go | 36 ++ modules/azure/virtualnetwork.go | 228 ++++++++++ test/azure/compute_test.go | 175 +++++++- test/azure/terraform_azure_vm_test.go | 171 ++++++++ 13 files changed, 1854 insertions(+), 46 deletions(-) create mode 100644 examples/azure/terraform-azure-vm-example/README.md create mode 100644 examples/azure/terraform-azure-vm-example/main.tf create mode 100644 examples/azure/terraform-azure-vm-example/outputs.tf create mode 100644 examples/azure/terraform-azure-vm-example/variables.tf create mode 100644 modules/azure/availabilityset.go create mode 100644 modules/azure/disk.go create mode 100644 modules/azure/networkinterface.go create mode 100644 modules/azure/publicaddress.go create mode 100644 modules/azure/resourceid.go create mode 100644 modules/azure/virtualnetwork.go create mode 100644 test/azure/terraform_azure_vm_test.go diff --git a/examples/azure/terraform-azure-vm-example/README.md b/examples/azure/terraform-azure-vm-example/README.md new file mode 100644 index 000000000..e57ced5c8 --- /dev/null +++ b/examples/azure/terraform-azure-vm-example/README.md @@ -0,0 +1,155 @@ +# Terraform Azure Virtual Machine Example + +This folder contains a complete Terraform VM module that deploys resources in [Azure](https://azure.microsoft.com/) to demonstrate +how you can use Terratest to write automated tests for your Azure Virtual Machine Terraform code. This module deploys these resources: + +* A [Virtual Machine](https://azure.microsoft.com/en-us/services/virtual-machines/) and gives that VM the following: + * [Virtual Machine](https://docs.microsoft.com/en-us/azure/virtual-machines/) with the namne specified in the `vm_name` variable. + * [Managed Disk](https://docs.microsoft.com/en-us/azure/virtual-machines/managed-disks-overview) with the namne specified in the `managed_disk_name` variable. + * [Availability Set](https://docs.microsoft.com/en-us/azure/virtual-machines/availability) with the namne specified in the `availability_set_name` variable. +* A [Virtual Network](https://azure.microsoft.com/en-us/services/virtual-network/) module that gives the following resources: + * [Virtual Network](https://docs.microsoft.com/en-us/azure/virtual-network/) with the name specified in the `virtual_network_name` variable. + * [Subnet](https://docs.microsoft.com/en-us/rest/api/virtualnetwork/subnets) with the name specified in the `subnet_name` variable. + * [Public Address](https://docs.microsoft.com/en-us/azure/virtual-network/public-ip-addresses) with the name specified in the `public_ip_name` variable. + * [Network Interface](https://docs.microsoft.com/en-us/azure/virtual-network/virtual-network-network-interface) with the name specified in the `network_interface_name` variable. + +Check out [test/azure/terraform_azure_vm_test.go](/test/azure/terraform_azure_vm_test.go) to see how you can write +automated tests for this module. + +Note that the Virtual Machine madule creates a Microsoft Windows Server Image with a managed disk, availability set and network configuration for demonstration purposes. + +**WARNING**: This module and the automated tests for it deploy real resources into your Azure account which can cost you +money. The resources are all part of the [Azure Free Account](https://azure.microsoft.com/en-us/free/), so if you haven't used that up, +it should be free, but you are completely responsible for all Azure charges. + +## Running this module manually + +1. Sign up for [Azure](https://azure.microsoft.com/). +1. Configure your Azure credentials using one of the [supported methods for Azure CLI + tools](https://docs.microsoft.com/en-us/cli/azure/azure-cli-configuration?view=azure-cli-latest). +1. Install [Terraform](https://www.terraform.io/) and make sure it's on your `PATH`. +1. Run `terraform init`. +1. Run `terraform apply`. +1. When you're done, run `terraform destroy`. + +## Running automated tests against this module + +1. Sign up for [Azure](https://azure.microsoft.com/). +1. Configure your Azure credentials using one of the [supported methods for Azure CLI + tools](https://docs.microsoft.com/en-us/cli/azure/azure-cli-configuration?view=azure-cli-latest). +1. Install [Terraform](https://www.terraform.io/) and make sure it's on your `PATH`. +1. [Review environment variables](#review-environment-variables). +1. Install [Golang](https://golang.org/) and make sure this code is checked out into your `GOPATH`. +1. `cd test` +1. Make sure [the azure-sdk-for-go versions match](#check-go-dependencies) in [/go.mod](/go.mod) and in [test/azure/terraform_azure_vm_test.go](/test/azure/terraform_azure_vm_test.go). +1. `go build terraform_azure_vm_test.go` +1. `go test -v -run TestTerraformAzureVmExample -timeout 20m` + * Note the extra -timeout flag of 20 minutes ensures proper Azure resource removal time. + +## Module test APIs + +- `VirtualMachineExists` indicates whether the speficied Azure Virtual Machine exists +func VirtualMachineExists(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) bool + +- `GetVirtualMachineAdminUser` gets the Admin Username of the specified Azure Virtual Machine +func GetVirtualMachineAdminUser(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) string + +- `GetVirtualMachineAvailabilitySetID` gets the Availability Set ID of the specified Azure Virtual Machine +func GetVirtualMachineAvailabilitySetID(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) string + +- `GetVirtualMachineImage` gets the Image of the specified Azure Virtual Machine +func GetVirtualMachineImage(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) VMImage + ``` + type VMImage struct { + Publisher string + Offer string + SKU string + Version string + } + ``` +- `GetVirtualMachineManagedDiskCount` gets the Managed Disk count of the specified Azure Virtual Machine\ +func GetVirtualMachineManagedDiskCount(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) int + +- `GetVirtualMachineManagedDisks` gets the list of Managed Disk names of the specified Azure Virtual Machine\ +func GetVirtualMachineManagedDisks(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) []string + +- `GetVirtualMachineNicCount` gets the Network Interface count of the specified Azure Virtual Machine\ +func GetVirtualMachineNicCount(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) int + +- `GetVirtualMachineNics` gets a list of Network Interface names for a speficied Azure Virtual Machine\ +func GetVirtualMachineNics(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) []string + +- `GetVirtualMachineOsDiskName` gets the OS Disk name of the specified Azure Virtual Machine\ +func GetVirtualMachineOsDiskName(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) string + +- `GetVirtualMachineSize` gets the Size Type of the specified Azure Virtual Machine\ +func GetVirtualMachineSize(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) compute.VirtualMachineSizeTypes + +- `GetVirtualMachineState` gets the State of the specified Azure Virtual Machine\ +func GetVirtualMachineState(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) string + +- `GetVirtualMachineTags` gets the Tags of the specified Virtual Machine as a map\ +func GetVirtualMachineTags(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) map[string]string + +- `GetVirtualMachineInstance` gets a local Virtual Machine instance in the specified Resource Group\ +func GetVirtualMachineInstance(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) *Instance + +- `GetVirtualMachineInstanceSize` gets the size of the Virtual Machine\ +func (vm *Instance) GetVirtualMachineInstanceSize() compute.VirtualMachineSizeTypes + +- `GetResourceGroupVirtualMachines` gets a list of all Virtual Machine names in the specified Resource Group\ +func GetResourceGroupVirtualMachines(t testing.TestingT, resGroupName string, subscriptionID string) []string + +- `GetResourceGroupVirtualMachinesObjects` gets all Virtual Machine objects in the specified Resource Group\ +func GetResourceGroupVirtualMachinesObjects(t testing.TestingT, resGroupName string, subscriptionID string) *map[string]compute.VirtualMachineProperties + +- `GetVirtualMachine` gets a Virtual Machine in the specified Azure Resource Group\ +func GetVirtualMachine(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) *compute.VirtualMachine + +- `GetVirtualMachineClientE` creates a Azure Virtual Machine client in the specified Azure Subscription\ +func GetVirtualMachineClientE(subscriptionID string) (*compute.VirtualMachinesClient, error) + +## Check Go Dependencies + +Check that the `github.com/Azure/azure-sdk-for-go` version in your generated `go.mod` for this test matches the version in the terratest [go.mod](https://github.com/gruntwork-io/terratest/blob/master/go.mod) file. + +> This was tested with **go1.14.4**. + +### Check Azure-sdk-for-go version + +Let's make sure [go.mod](https://github.com/gruntwork-io/terratest/blob/master/go.mod) includes the appropriate [azure-sdk-for-go version](https://github.com/Azure/azure-sdk-for-go/releases/tag/v46.1.0): + +```go +require ( + ... + github.com/Azure/azure-sdk-for-go v46.1.0+incompatible + ... +) +``` + +If we make changes to either the **go.mod** or the **go test file**, we should make sure that the go build command works still. + +```powershell +go build terraform_azure_vm_test.go +``` + +## Review Environment Variables + +As part of configuring terraform for Azure, we'll want to check that we have set the appropriate [credentials](https://docs.microsoft.com/en-us/azure/terraform/terraform-install-configure?toc=https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fterraform%2Ftoc.json&bc=https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fbread%2Ftoc.json#set-up-terraform-access-to-azure) and also that we set the [environment variables](https://docs.microsoft.com/en-us/azure/terraform/terraform-install-configure?toc=https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fterraform%2Ftoc.json&bc=https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fbread%2Ftoc.json#configure-terraform-environment-variables) on the testing host. + +```bash +export ARM_CLIENT_ID=your_app_id +export ARM_CLIENT_SECRET=your_password +export ARM_SUBSCRIPTION_ID=your_subscription_id +export ARM_TENANT_ID=your_tenant_id +``` + +Note, in a Windows environment, these should be set as **system environment variables**. We can use a PowerShell console with administrative rights to update these environment variables: + +```powershell +[System.Environment]::SetEnvironmentVariable("ARM_CLIENT_ID",$your_app_id,[System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable("ARM_CLIENT_SECRET",$your_password,[System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable("ARM_SUBSCRIPTION_ID",$your_subscription_id,[System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable("ARM_TENANT_ID",$your_tenant_id,[System.EnvironmentVariableTarget]::Machine) +``` + diff --git a/examples/azure/terraform-azure-vm-example/main.tf b/examples/azure/terraform-azure-vm-example/main.tf new file mode 100644 index 000000000..87ece33d4 --- /dev/null +++ b/examples/azure/terraform-azure-vm-example/main.tf @@ -0,0 +1,136 @@ +provider "azurerm" { + version = "=2.20.0" + features {} +} + +# --------------------------------------------------------------------------------------------------------------------- +# PIN TERRAFORM VERSION TO >= 0.12 +# The examples have been upgraded to 0.12 syntax +# --------------------------------------------------------------------------------------------------------------------- + +terraform { + required_version = ">= 0.12" +} + +# --------------------------------------------------------------------------------------------------------------------- +# DEPLOY A RESOURCE GROUP +# See test/terraform_azure_example_test.go for how to write automated tests for this code. +# --------------------------------------------------------------------------------------------------------------------- + +resource "azurerm_resource_group" "main" { + name = "${var.prefix}-resources" + location = var.location +} + +# --------------------------------------------------------------------------------------------------------------------- +# DEPLOY VIRTUAL NETWORK RESOURCES +# --------------------------------------------------------------------------------------------------------------------- + +resource "azurerm_virtual_network" "main" { + name = "${var.prefix}-network" + address_space = ["10.0.0.0/16"] + location = azurerm_resource_group.main.location + resource_group_name = azurerm_resource_group.main.name +} + +resource "azurerm_subnet" "main" { + name = "${var.prefix}-subnet" + resource_group_name = azurerm_resource_group.main.name + virtual_network_name = azurerm_virtual_network.main.name + address_prefixes = [var.subnet_prefix] +} + +resource "azurerm_public_ip" "main" { + name = "${var.prefix}-pip" + resource_group_name = azurerm_resource_group.main.name + location = azurerm_resource_group.main.location + allocation_method = "Static" + ip_version = "IPv4" + sku = "Standard" + idle_timeout_in_minutes = "4" +} + +resource "azurerm_network_interface" "main" { + name = "${var.prefix}-nic" + location = azurerm_resource_group.main.location + resource_group_name = azurerm_resource_group.main.name + + ip_configuration { + name = "terratestconfiguration1" + subnet_id = azurerm_subnet.main.id + private_ip_address_allocation = "Static" + private_ip_address = var.private_ip + public_ip_address_id = azurerm_public_ip.main.id + } +} + +# --------------------------------------------------------------------------------------------------------------------- +# DEPLOY AVAILABILITY SET +# --------------------------------------------------------------------------------------------------------------------- + +resource "azurerm_availability_set" "main" { + name = "${var.prefix}-avs" + location = azurerm_resource_group.main.location + resource_group_name = azurerm_resource_group.main.name + platform_fault_domain_count = 2 + managed = true +} + +# --------------------------------------------------------------------------------------------------------------------- +# DEPLOY A VIRTUAL MACHINE RUNNING WINDOWS SERVER +# --------------------------------------------------------------------------------------------------------------------- + +resource "azurerm_virtual_machine" "main" { + name = "${var.prefix}-01" + location = azurerm_resource_group.main.location + resource_group_name = azurerm_resource_group.main.name + network_interface_ids = [azurerm_network_interface.main.id] + availability_set_id = azurerm_availability_set.main.id + vm_size = var.vm_size + license_type = "Windows_Server" + delete_os_disk_on_termination = true + delete_data_disks_on_termination = true + + storage_image_reference { + publisher = "MicrosoftWindowsServer" + offer = "WindowsServer" + sku = var.vm_image_sku + version = var.vm_image_version + } + + storage_os_disk { + name = "${var.prefix}-osdisk" + caching = "ReadWrite" + create_option = "FromImage" + managed_disk_type = var.disk_type + } + + os_profile { + computer_name = "${var.prefix}-01" + admin_username = var.user_name + admin_password = var.password + } + os_profile_windows_config { + provision_vm_agent = true + } +} + +# --------------------------------------------------------------------------------------------------------------------- +# DEPLOY AND ATTACH MANAGED DISK TO VIRTUAL MACHINE +# --------------------------------------------------------------------------------------------------------------------- + +resource "azurerm_managed_disk" "main" { + name = "${var.prefix}-disk" + location = azurerm_resource_group.main.location + resource_group_name = azurerm_resource_group.main.name + storage_account_type = var.disk_type + create_option = "Empty" + disk_size_gb = 10 +} + +resource "azurerm_virtual_machine_data_disk_attachment" "main" { + managed_disk_id = azurerm_managed_disk.main.id + virtual_machine_id = azurerm_virtual_machine.main.id + caching = "ReadWrite" + lun = 10 +} \ No newline at end of file diff --git a/examples/azure/terraform-azure-vm-example/outputs.tf b/examples/azure/terraform-azure-vm-example/outputs.tf new file mode 100644 index 000000000..c26700cd7 --- /dev/null +++ b/examples/azure/terraform-azure-vm-example/outputs.tf @@ -0,0 +1,32 @@ + +output "resource_group_name" { + value = azurerm_resource_group.main.name +} + +output "vm_name" { + value = azurerm_virtual_machine.main.name +} + +output "virtual_network_name" { + value = azurerm_virtual_network.main.name +} + +output "subnet_name" { + value = azurerm_subnet.main.name +} + +output "public_ip_name" { + value = azurerm_public_ip.main.name +} + +output "network_interface_name" { + value = azurerm_network_interface.main.name +} + +output "availability_set_name" { + value = azurerm_availability_set.main.name +} + +output "managed_disk_name" { + value = azurerm_managed_disk.main.name +} \ No newline at end of file diff --git a/examples/azure/terraform-azure-vm-example/variables.tf b/examples/azure/terraform-azure-vm-example/variables.tf new file mode 100644 index 000000000..ab62b32b6 --- /dev/null +++ b/examples/azure/terraform-azure-vm-example/variables.tf @@ -0,0 +1,79 @@ +# --------------------------------------------------------------------------------------------------------------------- +# ENVIRONMENT VARIABLES +# Define these secrets as environment variables +# --------------------------------------------------------------------------------------------------------------------- + +# ARM_CLIENT_ID +# ARM_CLIENT_SECRET +# ARM_SUBSCRIPTION_ID +# ARM_TENANT_ID + +# --------------------------------------------------------------------------------------------------------------------- +# REQUIRED PARAMETERS +# You must provide a value for each of these parameters. +# --------------------------------------------------------------------------------------------------------------------- + +# --------------------------------------------------------------------------------------------------------------------- +# OPTIONAL PARAMETERS +# These parameters have reasonable defaults. +# --------------------------------------------------------------------------------------------------------------------- + +variable "disk_type" { + description = "temp" + type = string + default = "Standard_LRS" +} + +variable "location" { + description = "The Azure location to deploy resources too" + type = string + default = "East US" +} + +variable "password" { + description = "the password to configure for ssh access" + type = string + default = "horriblepassword1234!" +} + +variable "prefix" { + description = "The prefix that will be attached to all resources deployed" + type = string + default = "terratest-vm" +} + +variable "private_ip" { + description = "The Static Private IP for the Internal NIC" + type = string + default = "10.0.17.4" +} + +variable "subnet_prefix" { + description = "The subnet range of IPs for the Virtual Network" + type = string + default = "10.0.17.0/24" +} + +variable "user_name" { + description = "The username to be provisioned into the vm" + type = string + default = "testadmin" +} + +variable "vm_image_sku" { + description = "The storage image reference SKU from which the VM is created" + type = string + default = "2016-Datacenter" +} + +variable "vm_image_version" { + description = "The storage image reference Version from which the VM is created" + type = string + default = "latest" +} + +variable "vm_size" { + description = "The Azure VM Size of the VM" + type = string + default = "Standard_DS1_v2" +} diff --git a/modules/azure/availabilityset.go b/modules/azure/availabilityset.go new file mode 100644 index 000000000..d6938a37d --- /dev/null +++ b/modules/azure/availabilityset.go @@ -0,0 +1,132 @@ +package azure + +import ( + "context" + "strings" + + "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-07-01/compute" + "github.com/gruntwork-io/terratest/modules/collections" + "github.com/gruntwork-io/terratest/modules/testing" + "github.com/stretchr/testify/require" +) + +// AvailabilitySetExists indicates whether the speficied Azure Availability Set exists +func AvailabilitySetExists(t testing.TestingT, avsName string, resGroupName string, subscriptionID string) bool { + exists, err := AvailabilitySetExistsE(t, avsName, resGroupName, subscriptionID) + require.NoError(t, err) + return exists +} + +// AvailabilitySetExistsE indicates whether the speficied Azure Availability Set exists +func AvailabilitySetExistsE(t testing.TestingT, avsName string, resGroupName string, subscriptionID string) (bool, error) { + _, err := GetAvailabilitySetE(t, avsName, resGroupName, subscriptionID) + if err != nil { + return false, err + } + return true, nil +} + +// CheckAvailabilitySetContainsVM checks if the Virtual Machine is contained in the Availability Set VMs +func CheckAvailabilitySetContainsVM(t testing.TestingT, vmName string, avsName string, resGroupName string, subscriptionID string) bool { + avsVMs, err := GetAvailabilitySetVMsE(t, avsName, resGroupName, subscriptionID) + if err != nil { + return false + } + + return collections.ListContains(avsVMs, strings.ToLower(vmName)) +} + +// GetAvailabilitySetVMs gets a list of VM names in the specified Azure Availability Set +func GetAvailabilitySetVMs(t testing.TestingT, avsName string, resGroupName string, subscriptionID string) []string { + vms, err := GetAvailabilitySetVMsE(t, avsName, resGroupName, subscriptionID) + require.NoError(t, err) + return vms +} + +// GetAvailabilitySetVMsE gets a list of VM names in the specified Azure Availability Set +func GetAvailabilitySetVMsE(t testing.TestingT, avsName string, resGroupName string, subscriptionID string) ([]string, error) { + client, err := GetAvailabilitySetClientE(subscriptionID) + if err != nil { + return nil, err + } + + avs, err := client.Get(context.Background(), resGroupName, avsName) + if err != nil { + return nil, err + } + + vms := []string{} + + for _, vm := range *avs.VirtualMachines { + vms = append(vms, strings.ToLower(GetNameFromResourceID(*vm.ID))) + } + + return vms, nil +} + +// GetAvailabilitySetFaultDomainCount gets the Fault Domain Count for the specified Azure Availability Set +func GetAvailabilitySetFaultDomainCount(t testing.TestingT, avsName string, resGroupName string, subscriptionID string) int32 { + avsFaultDomainCount, err := GetAvailabilitySetFaultDomainCountE(t, avsName, resGroupName, subscriptionID) + require.NoError(t, err) + + return avsFaultDomainCount +} + +// GetAvailabilitySetFaultDomainCountE gets the Fault Domain Count for the specified Azure Availability Set +func GetAvailabilitySetFaultDomainCountE(t testing.TestingT, avsName string, resGroupName string, subscriptionID string) (int32, error) { + client, err := GetAvailabilitySetClientE(subscriptionID) + if err != nil { + return -1, err + } + + avs, err := client.Get(context.Background(), resGroupName, avsName) + if err != nil { + return -1, err + } + + return *avs.PlatformFaultDomainCount, nil +} + +// GetAvailabilitySetE gets an Availability Set in the specified Azure Resource Group +func GetAvailabilitySetE(t testing.TestingT, avsName string, resGroupName string, subscriptionID string) (*compute.AvailabilitySet, error) { + // Validate resource group name and subscription ID + resGroupName, err := getTargetAzureResourceGroupName(resGroupName) + if err != nil { + return nil, err + } + + // Get the client refrence + client, err := GetAvailabilitySetClientE(subscriptionID) + if err != nil { + return nil, err + } + + // Get the Availability Set + avs, err := client.Get(context.Background(), resGroupName, avsName) + if err != nil { + return nil, err + } + + return &avs, nil +} + +// GetAvailabilitySetClientE gets a new Availability Set client in the specified Azure Subscription +func GetAvailabilitySetClientE(subscriptionID string) (*compute.AvailabilitySetsClient, error) { + // Validate Azure subscription ID + subscriptionID, err := getTargetAzureSubscription(subscriptionID) + if err != nil { + return nil, err + } + + // Get the Availability Set client + client := compute.NewAvailabilitySetsClient(subscriptionID) + + // Create an authorizer + authorizer, err := NewAuthorizer() + if err != nil { + return nil, err + } + client.Authorizer = *authorizer + + return &client, nil +} diff --git a/modules/azure/compute.go b/modules/azure/compute.go index 8607176fc..cb5c4eaac 100644 --- a/modules/azure/compute.go +++ b/modules/azure/compute.go @@ -2,59 +2,251 @@ package azure import ( "context" + "errors" "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-07-01/compute" "github.com/gruntwork-io/terratest/modules/testing" "github.com/stretchr/testify/require" ) -// GetVirtualMachineClient is a helper function that will setup an Azure Virtual Machine client on your behalf -func GetVirtualMachineClient(subscriptionID string) (*compute.VirtualMachinesClient, error) { - // Validate Azure subscription ID - subscriptionID, err := getTargetAzureSubscription(subscriptionID) +// VirtualMachineExists indicates whether the speficied Azure Virtual Machine exists +func VirtualMachineExists(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) bool { + exists, err := VirtualMachineExistsE(t, vmName, resGroupName, subscriptionID) + require.NoError(t, err) + return exists +} + +// VirtualMachineExistsE indicates whether the speficied Azure Virtual Machine exists +func VirtualMachineExistsE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (bool, error) { + // Get VM Object + _, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) if err != nil { - return nil, err + return false, err } + return true, nil +} - // Create a VM client - vmClient := compute.NewVirtualMachinesClient(subscriptionID) +// GetVirtualMachineNics gets a list of Network Interface names for a speficied Azure Virtual Machine +func GetVirtualMachineNics(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) []string { + nicList, err := GetVirtualMachineNicsE(t, vmName, resGroupName, subscriptionID) + require.NoError(t, err) - // Create an authorizer - authorizer, err := NewAuthorizer() + return nicList +} + +// GetVirtualMachineNicsE gets a list of Network Interface names for a specified Azure Virtual Machine +func GetVirtualMachineNicsE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) ([]string, error) { + nics := []string{} + + // Get VM Object + vm, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) if err != nil { - return nil, err + return nics, err } - // Attach authorizer to the client - vmClient.Authorizer = *authorizer + vmNICs := *vm.NetworkProfile.NetworkInterfaces + if len(vmNICs) == 0 { + // No VM NICs attached is still valid but returning a meaningful error + return nics, errors.New("No network interface attached to this Virtual Machine") + } - return &vmClient, nil + // Get the attached NIC names + for _, nic := range vmNICs { + nics = append(nics, GetNameFromResourceID(*nic.ID)) + } + return nics, nil } -// GetSizeOfVirtualMachine gets the size type of the given Azure Virtual Machine -func GetSizeOfVirtualMachine(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) compute.VirtualMachineSizeTypes { - size, err := GetSizeOfVirtualMachineE(t, vmName, resGroupName, subscriptionID) +// GetVirtualMachineNicCount gets the Network Interface count of the specified Azure Virtual Machine +func GetVirtualMachineNicCount(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) int { + nicCount, err := GetVirtualMachineNicCountE(t, vmName, resGroupName, subscriptionID) require.NoError(t, err) - return size + return nicCount } -// GetSizeOfVirtualMachineE gets the size type of the given Azure Virtual Machine -func GetSizeOfVirtualMachineE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (compute.VirtualMachineSizeTypes, error) { - // Validate resource group name and subscription ID - resGroupName, err := getTargetAzureResourceGroupName(resGroupName) +// GetVirtualMachineNicCountE gets the Network Interface count of the specified Azure Virtual Machine +func GetVirtualMachineNicCountE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (int, error) { + nicCount := 0 + + // Get VM Object + vm, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) + if err != nil { + return nicCount, err + } + + return len(*vm.NetworkProfile.NetworkInterfaces), nil +} + +// GetVirtualMachineManagedDisks gets the list of Managed Disk names of the specified Azure Virtual Machine +func GetVirtualMachineManagedDisks(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) []string { + diskNames, err := GetVirtualMachineManagedDisksE(t, vmName, resGroupName, subscriptionID) + require.NoError(t, err) + + return diskNames +} + +// GetVirtualMachineManagedDisksE gets the list of Managed Disk names of the specified Azure Virtual Machine +func GetVirtualMachineManagedDisksE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) ([]string, error) { + diskNames := []string{} + + // Get VM Object + vm, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) + if err != nil { + return diskNames, err + } + + // Get VM Attached Disks + vmDisks := *vm.StorageProfile.DataDisks + for _, v := range vmDisks { + diskNames = append(diskNames, *v.Name) + } + + return diskNames, nil +} + +// GetVirtualMachineManagedDiskCount gets the Managed Disk count of the specified Azure Virtual Machine +func GetVirtualMachineManagedDiskCount(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) int { + mngDiskCount, err := GetVirtualMachineManagedDiskCountE(t, vmName, resGroupName, subscriptionID) + require.NoError(t, err) + + return mngDiskCount +} + +// GetVirtualMachineManagedDiskCountE gets the Managed Disk count of the specified Azure Virtual Machine +func GetVirtualMachineManagedDiskCountE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (int, error) { + mngDiskCount := -1 + + // Get VM Object + vm, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) + if err != nil { + return mngDiskCount, err + } + + return len(*vm.StorageProfile.DataDisks), nil +} + +// GetVirtualMachineOsDiskName gets the OS Disk name of the specified Azure Virtual Machine +func GetVirtualMachineOsDiskName(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) string { + osDiskName, err := GetVirtualMachineOsDiskNameE(t, vmName, resGroupName, subscriptionID) + require.NoError(t, err) + + return osDiskName +} + +// GetVirtualMachineOsDiskNameE gets the OS Disk name of the specified Azure Virtual Machine +func GetVirtualMachineOsDiskNameE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (string, error) { + // Get VM Object + vm, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) + if err != nil { + return "", err + } + + return *vm.StorageProfile.OsDisk.Name, nil +} + +// GetVirtualMachineState gets the State of the specified Azure Virtual Machine +func GetVirtualMachineState(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) string { + vmState, err := GetVirtualMachineStateE(t, vmName, resGroupName, subscriptionID) + require.NoError(t, err) + return vmState +} + +// GetVirtualMachineStateE gets the State of the specified Azure Virtual Machine +func GetVirtualMachineStateE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (string, error) { + // Get VM Object + vm, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) if err != nil { return "", err } - // Create a VM client - vmClient, err := GetVirtualMachineClient(subscriptionID) + return vm.Status, nil +} + +// GetVirtualMachineAvailabilitySetID gets the Availability Set ID of the specified Azure Virtual Machine +func GetVirtualMachineAvailabilitySetID(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) string { + adminUser, err := GetVirtualMachineAvailabilitySetIDE(t, vmName, resGroupName, subscriptionID) + require.NoError(t, err) + + return adminUser +} + +// GetVirtualMachineAvailabilitySetIDE gets the Availability Set ID of the specified Azure Virtual Machine +func GetVirtualMachineAvailabilitySetIDE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (string, error) { + // Get VM Object + vm, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) if err != nil { return "", err } - // Get the details of the target virtual machine - vm, err := vmClient.Get(context.Background(), resGroupName, vmName, compute.InstanceView) + return GetNameFromResourceID(*vm.AvailabilitySet.ID), nil +} + +// VMImage represents the storage image for the specified Azure Virtual Machine +type VMImage struct { + Publisher string + Offer string + SKU string + Version string +} + +// GetVirtualMachineImage gets the Image of the specified Azure Virtual Machine +func GetVirtualMachineImage(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) VMImage { + adminUser, err := GetVirtualMachineImageE(t, vmName, resGroupName, subscriptionID) + require.NoError(t, err) + + return adminUser +} + +// GetVirtualMachineImageE gets the Image of the specified Azure Virtual Machine +func GetVirtualMachineImageE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (VMImage, error) { + vmImage := VMImage{} + + // Get VM Object + vm, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) + if err != nil { + return vmImage, err + } + + // Populate VM Image + vmImage.Publisher = *vm.StorageProfile.ImageReference.Publisher + vmImage.Offer = *vm.StorageProfile.ImageReference.Offer + vmImage.SKU = *vm.StorageProfile.ImageReference.Sku + vmImage.Version = *vm.StorageProfile.ImageReference.Version + + return vmImage, nil +} + +// GetVirtualMachineAdminUser gets the Admin Username of the specified Azure Virtual Machine +func GetVirtualMachineAdminUser(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) string { + adminUser, err := GetVirtualMachineAdminUserE(t, vmName, resGroupName, subscriptionID) + require.NoError(t, err) + return adminUser +} + +// GetVirtualMachineAdminUserE gets the Admin Username of the specified Azure Virtual Machine +func GetVirtualMachineAdminUserE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (string, error) { + // Get VM Object + vm, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) + if err != nil { + return "", err + } + + return string(*vm.OsProfile.AdminUsername), nil +} + +// GetVirtualMachineSize gets the Size Type of the specified Azure Virtual Machine +func GetVirtualMachineSize(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) compute.VirtualMachineSizeTypes { + size, err := GetVirtualMachineSizeE(t, vmName, resGroupName, subscriptionID) + require.NoError(t, err) + + return size +} + +// GetVirtualMachineSizeE gets the Size Type of the specified Azure Virtual Machine +func GetVirtualMachineSizeE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (compute.VirtualMachineSizeTypes, error) { + // Get VM Object + vm, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) if err != nil { return "", err } @@ -62,41 +254,174 @@ func GetSizeOfVirtualMachineE(t testing.TestingT, vmName string, resGroupName st return vm.VirtualMachineProperties.HardwareProfile.VMSize, nil } -// GetTagsForVirtualMachine gets the tags of the given Virtual Machine as a map -func GetTagsForVirtualMachine(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) map[string]string { - tags, err := GetTagsForVirtualMachineE(t, vmName, resGroupName, subscriptionID) +// GetVirtualMachineTags gets the Tags of the specified Virtual Machine as a map +func GetVirtualMachineTags(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) map[string]string { + tags, err := GetVirtualMachineTagsE(t, vmName, resGroupName, subscriptionID) require.NoError(t, err) return tags } -// GetTagsForVirtualMachineE gets the tags of the given Virtual Machine as a map -func GetTagsForVirtualMachineE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (map[string]string, error) { +// GetVirtualMachineTagsE gets the Tags of the specified Virtual Machine as a map +func GetVirtualMachineTagsE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (map[string]string, error) { // Setup a blank map to populate and return tags := make(map[string]string) + // Get VM Object + vm, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) + if err != nil { + return nil, err + } + + // Range through existing tags and populate above map accordingly + for k, v := range vm.Tags { + tags[k] = *v + } + + return tags, nil +} + +// ***************************************************** // +// Get multiple Virtual Machines from a Resource Group +// ***************************************************** // + +// GetResourceGroupVirtualMachines gets a list of all Virtual Machine names in the specified Resource Group +func GetResourceGroupVirtualMachines(t testing.TestingT, resGroupName string, subscriptionID string) []string { + vms, err := GetResourceGroupVirtualMachinesE(t, resGroupName, subscriptionID) + require.NoError(t, err) + return vms +} + +// GetResourceGroupVirtualMachinesE gets a list of all Virtual Machine names in the specified Resource Group +func GetResourceGroupVirtualMachinesE(t testing.TestingT, resourceGroupName string, subscriptionID string) ([]string, error) { + vmDetails := []string{} + + vmClient, err := GetVirtualMachineClientE(subscriptionID) + if err != nil { + return nil, err + } + + vms, err := vmClient.List(context.Background(), resourceGroupName) + if err != nil { + return nil, err + } + + for _, v := range vms.Values() { + vmDetails = append(vmDetails, *v.Name) + } + return vmDetails, nil +} + +// GetResourceGroupVirtualMachinesObjects gets all Virtual Machine objects in the specified Resource Group +func GetResourceGroupVirtualMachinesObjects(t testing.TestingT, resGroupName string, subscriptionID string) *map[string]compute.VirtualMachineProperties { + vms, err := GetResourceGroupVirtualMachinesObjectsE(t, resGroupName, subscriptionID) + require.NoError(t, err) + return vms +} + +// GetResourceGroupVirtualMachinesObjectsE gets all Virtual Machine objects in the specified Resource Group +func GetResourceGroupVirtualMachinesObjectsE(t testing.TestingT, resourceGroupName string, subscriptionID string) (*map[string]compute.VirtualMachineProperties, error) { + vmClient, err := GetVirtualMachineClientE(subscriptionID) + if err != nil { + return nil, err + } + + vms, err := vmClient.List(context.Background(), resourceGroupName) + if err != nil { + return nil, err + } + + vmDetails := make(map[string]compute.VirtualMachineProperties) + for _, v := range vms.Values() { + machineName := v.Name + vmProperties := v.VirtualMachineProperties + vmDetails[*machineName] = *vmProperties + } + return &vmDetails, nil +} + +// ******************************************************************** // +// Get VM using Instance and Instance property get, reducing SKD calls +// ******************************************************************** // + +// Instance of the VM +type Instance struct { + *compute.VirtualMachine +} + +// GetVirtualMachineInstance gets a local Virtual Machine instance in the specified Resource Group +func GetVirtualMachineInstance(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) *Instance { + vm, err := GetVirtualMachineInstanceE(t, vmName, resGroupName, subscriptionID) + require.NoError(t, err) + return vm +} + +// GetVirtualMachineInstanceE gets a local Virtual Machine instance in the specified Resource Group +func GetVirtualMachineInstanceE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (*Instance, error) { + // Get VM Object + vm, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) + if err != nil { + return nil, err + } + + return &Instance{vm}, nil +} + +// GetVirtualMachineInstanceSize gets the size of the Virtual Machine +func (vm *Instance) GetVirtualMachineInstanceSize() compute.VirtualMachineSizeTypes { + return vm.VirtualMachineProperties.HardwareProfile.VMSize +} + +// *********************** // +// Get the base VM Object +// *********************** // + +// GetVirtualMachine gets a Virtual Machine in the specified Azure Resource Group +func GetVirtualMachine(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) *compute.VirtualMachine { + vm, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) + require.NoError(t, err) + return vm +} + +// GetVirtualMachineE gets a Virtual Machine in the specified Azure Resource Group +func GetVirtualMachineE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (*compute.VirtualMachine, error) { // Validate resource group name and subscription ID resGroupName, err := getTargetAzureResourceGroupName(resGroupName) if err != nil { - return tags, err + return nil, err } - // Create a VM client - vmClient, err := GetVirtualMachineClient(subscriptionID) + // Get the client refrence + client, err := GetVirtualMachineClientE(subscriptionID) if err != nil { - return tags, err + return nil, err } - // Get the details of the target virtual machine - vm, err := vmClient.Get(context.Background(), resGroupName, vmName, compute.InstanceView) + vm, err := client.Get(context.Background(), resGroupName, vmName, compute.InstanceView) if err != nil { - return tags, err + return nil, err } - // Range through existing tags and populate above map accordingly - for k, v := range vm.Tags { - tags[k] = *v + return &vm, nil +} + +// GetVirtualMachineClientE creates a Azure Virtual Machine client in the specified Azure Subscription +func GetVirtualMachineClientE(subscriptionID string) (*compute.VirtualMachinesClient, error) { + // Validate Azure subscription ID + subscriptionID, err := getTargetAzureSubscription(subscriptionID) + if err != nil { + return nil, err } - return tags, nil + // Get the VM client + client := compute.NewVirtualMachinesClient(subscriptionID) + + // Create an authorizer + authorizer, err := NewAuthorizer() + if err != nil { + return nil, err + } + client.Authorizer = *authorizer + + return &client, nil } diff --git a/modules/azure/disk.go b/modules/azure/disk.go new file mode 100644 index 000000000..f49106d63 --- /dev/null +++ b/modules/azure/disk.go @@ -0,0 +1,90 @@ +package azure + +import ( + "context" + + "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-07-01/compute" + "github.com/gruntwork-io/terratest/modules/testing" + "github.com/stretchr/testify/require" +) + +// DiskExists indicates whether the speficied Azure Managed Disk exists +func DiskExists(t testing.TestingT, diskName string, resGroupName string, subscriptionID string) bool { + exists, err := DiskExistsE(t, diskName, resGroupName, subscriptionID) + require.NoError(t, err) + return exists +} + +// DiskExistsE indicates whether the speficied Azure Managed Disk exists +func DiskExistsE(t testing.TestingT, diskName string, resGroupName string, subscriptionID string) (bool, error) { + // Get the Disk object + _, err := GetDiskE(t, diskName, resGroupName, subscriptionID) + if err != nil { + return false, err + } + return true, nil +} + +// GetDiskType returns the Disk Storage Account Type of the Azure Managed Disk +// This property also accessible from the VM client disk storage object but only works +// when the VM is online, while this direct call to GetDiskType always works. +func GetDiskType(t testing.TestingT, diskName string, resGroupName string, subscriptionID string) compute.DiskStorageAccountTypes { + disk, err := GetDiskTypeE(t, diskName, resGroupName, subscriptionID) + require.NoError(t, err) + return disk +} + +// GetDiskTypeE returns the Disk Storage Account Type of the Azure Managed Disk +func GetDiskTypeE(t testing.TestingT, diskName string, resGroupName string, subscriptionID string) (compute.DiskStorageAccountTypes, error) { + // Get the Disk object + disk, err := GetDiskE(t, diskName, resGroupName, subscriptionID) + if err != nil { + return "", err + } + + return disk.Sku.Name, nil +} + +// GetDiskE returns a Disk in the specified Azure Resource Group +func GetDiskE(t testing.TestingT, diskName string, resGroupName string, subscriptionID string) (*compute.Disk, error) { + // Validate resource group name and subscription ID + resGroupName, err := getTargetAzureResourceGroupName(resGroupName) + if err != nil { + return nil, err + } + + // Get the client refrence + client, err := GetDiskClientE(subscriptionID) + if err != nil { + return nil, err + } + + // Get the Disk + disk, err := client.Get(context.Background(), resGroupName, diskName) + if err != nil { + return nil, err + } + + return &disk, nil +} + +// GetDiskClientE returns a new Disk client in the specified Azure Subscription +func GetDiskClientE(subscriptionID string) (*compute.DisksClient, error) { + // Validate Azure subscription ID + subscriptionID, err := getTargetAzureSubscription(subscriptionID) + if err != nil { + return nil, err + } + + // Get the Disk client + client := compute.NewDisksClient(subscriptionID) + + // Create an authorizer + authorizer, err := NewAuthorizer() + if err != nil { + return nil, err + } + client.Authorizer = *authorizer + + return &client, nil +} diff --git a/modules/azure/networkinterface.go b/modules/azure/networkinterface.go new file mode 100644 index 000000000..74184f6c2 --- /dev/null +++ b/modules/azure/networkinterface.go @@ -0,0 +1,142 @@ +package azure + +import ( + "context" + "encoding/json" + "strings" + + "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-09-01/network" + "github.com/gruntwork-io/terratest/modules/testing" + "github.com/stretchr/testify/require" +) + +// NetworkInterfaceExists indicates whether the speficied Azure Network Interface exists +func NetworkInterfaceExists(t testing.TestingT, nicName string, resGroupName string, subscriptionID string) bool { + exists, err := NetworkInterfaceExistsE(t, nicName, resGroupName, subscriptionID) + require.NoError(t, err) + return exists +} + +// NetworkInterfaceExistsE indicates whether the speficied Azure Network Interface exists +func NetworkInterfaceExistsE(t testing.TestingT, nicName string, resGroupName string, subscriptionID string) (bool, error) { + // Get the Network Interface + _, err := GetNetworkInterfaceE(t, nicName, resGroupName, subscriptionID) + if err != nil { + return false, err + } + return true, nil +} + +// GetNetworkInterfacePublicIPs returns a list of all the Public IPs found in the Network Interface configurations +func GetNetworkInterfacePublicIPs(t testing.TestingT, nicName string, resGroupName string, subscriptionID string) []string { + IPs, err := GetNetworkInterfacePublicIPsE(t, nicName, resGroupName, subscriptionID) + require.NoError(t, err) + return IPs +} + +// GetNetworkInterfacePublicIPsE returns a list of all the Public IPs found in the Network Interface configurations +func GetNetworkInterfacePublicIPsE(t testing.TestingT, nicName string, resGroupName string, subscriptionID string) ([]string, error) { + publicIPs := []string{} + + // Get the Network Interface client + nic, err := GetNetworkInterfaceE(t, nicName, resGroupName, subscriptionID) + if err != nil { + return publicIPs, err + } + + // Get the Public IPs from each configuration + for _, IPConfiguration := range *nic.IPConfigurations { + if ipConfigHasPublicIP(&IPConfiguration) { + publicAddressID := GetNameFromResourceID(*IPConfiguration.PublicIPAddress.ID) + + // Get the Public Ip from the Public Address resource + publicIP := GetPublicAddressIP(t, publicAddressID, resGroupName, subscriptionID) + publicIPs = append(publicIPs, publicIP) + } + } + + return publicIPs, nil +} + +// ipConfigHasPublicIP returns true if an IP Configuration has a Public IP Address +// This helper method was created since a config without a public address causes a nil pointer panic +func ipConfigHasPublicIP(ipConfig *network.InterfaceIPConfiguration) bool { + var byteIPConfig []byte + + byteIPConfig, err := json.Marshal(ipConfig) + if err != nil { + return false + } + + return strings.Contains(string(byteIPConfig), "publicIPAddress") +} + +// GetNetworkInterfacePrivateIPs gets a list of the Private IPs of a Network Interface configs +func GetNetworkInterfacePrivateIPs(t testing.TestingT, nicName string, resGroupName string, subscriptionID string) []string { + IPs, err := GetNetworkInterfacePrivateIPsE(t, nicName, resGroupName, subscriptionID) + require.NoError(t, err) + + return IPs +} + +// GetNetworkInterfacePrivateIPsE gets a list of the Private IPs of a Network Interface configs +func GetNetworkInterfacePrivateIPsE(t testing.TestingT, nicName string, resGroupName string, subscriptionID string) ([]string, error) { + privateIPs := []string{} + + // Get the Network Interface client + nic, err := GetNetworkInterfaceE(t, nicName, resGroupName, subscriptionID) + if err != nil { + return privateIPs, err + } + + // Get the Private IPs from each configuration + for _, IPConfiguration := range *nic.IPConfigurations { + privateIPs = append(privateIPs, *IPConfiguration.PrivateIPAddress) + } + + return privateIPs, nil +} + +// GetNetworkInterfaceE gets a Network Interface in the specified Azure Resource Group +func GetNetworkInterfaceE(t testing.TestingT, nicName string, resGroupName string, subscriptionID string) (*network.Interface, error) { + // Validate Azure Resource Group + resGroupName, err := getTargetAzureResourceGroupName(resGroupName) + if err != nil { + return nil, err + } + + // Get the client refrence + client, err := GetNetworkInterfaceClientE(subscriptionID) + if err != nil { + return nil, err + } + + // Get the Network Interface + nic, err := client.Get(context.Background(), resGroupName, nicName, "") + if err != nil { + return nil, err + } + + return &nic, nil +} + +// GetNetworkInterfaceClientE creates a new Network Interface client in the specified Azure Subscription +func GetNetworkInterfaceClientE(subscriptionID string) (*network.InterfacesClient, error) { + // Validate Azure Subscription ID + subscriptionID, err := getTargetAzureSubscription(subscriptionID) + if err != nil { + return nil, err + } + + // Get the NIC client + client := network.NewInterfacesClient(subscriptionID) + + // Create an authorizer + authorizer, err := NewAuthorizer() + if err != nil { + return nil, err + } + client.Authorizer = *authorizer + + return &client, nil +} diff --git a/modules/azure/publicaddress.go b/modules/azure/publicaddress.go new file mode 100644 index 000000000..647aab0e4 --- /dev/null +++ b/modules/azure/publicaddress.go @@ -0,0 +1,117 @@ +package azure + +import ( + "context" + + "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-09-01/network" + "github.com/gruntwork-io/terratest/modules/testing" + "github.com/stretchr/testify/require" +) + +// PublicAddressExists indicates whether the speficied AzurePublic Address exists +func PublicAddressExists(t testing.TestingT, publicAddressName string, resGroupName string, subscriptionID string) bool { + exists, err := PublicAddressExistsE(t, publicAddressName, resGroupName, subscriptionID) + require.NoError(t, err) + return exists +} + +// PublicAddressExistsE indicates whether the speficied AzurePublic Address exists +func PublicAddressExistsE(t testing.TestingT, publicAddressName string, resGroupName string, subscriptionID string) (bool, error) { + // Get the Public Address + _, err := GetPublicIPAddressE(t, publicAddressName, resGroupName, subscriptionID) + if err != nil { + return false, err + } + return true, nil +} + +// GetPublicAddressIP gets the IP of a Public IP Address +func GetPublicAddressIP(t testing.TestingT, publicAddressName string, resGroupName string, subscriptionID string) string { + IP, err := GetPublicAddressIPE(t, publicAddressName, resGroupName, subscriptionID) + require.NoError(t, err) + return IP +} + +// GetPublicAddressIPE gets the IP of a Public IP Address with error +func GetPublicAddressIPE(t testing.TestingT, publicAddressName string, resGroupName string, subscriptionID string) (string, error) { + // Validate Azure subscription ID + subscriptionID, err := getTargetAzureSubscription(subscriptionID) + if err != nil { + return "", err + } + + // Create a NIC client + pip, err := GetPublicIPAddressE(t, publicAddressName, resGroupName, subscriptionID) + if err != nil { + return "", err + } + + return *pip.IPAddress, nil +} + +// CheckPublicDNSNameAvailability checks whether a Domain Name in the cloudapp.azure.com zone is available for use +func CheckPublicDNSNameAvailability(t testing.TestingT, location string, domainNameLabel string, subscriptionID string) bool { + available, err := CheckPublicDNSNameAvailabilityE(t, location, domainNameLabel, subscriptionID) + if err != nil { + return false + } + return available +} + +// CheckPublicDNSNameAvailabilityE checks whether a Domain Name in the cloudapp.azure.com zone is available for use +func CheckPublicDNSNameAvailabilityE(t testing.TestingT, location string, domainNameLabel string, subscriptionID string) (bool, error) { + client, err := GetPublicIPAddressClientE(subscriptionID) + if err != nil { + return false, err + } + + res, err := client.CheckDNSNameAvailability(context.Background(), location, domainNameLabel) + if err != nil { + return false, err + } + + return *res.Available, nil +} + +// GetPublicIPAddressE gets a Public IP Addresses in the specified Azure Resource Group +func GetPublicIPAddressE(t testing.TestingT, publicIPAddressName string, resGroupName string, subscriptionID string) (*network.PublicIPAddress, error) { + // Validate resource group name and subscription ID + resGroupName, err := getTargetAzureResourceGroupName(resGroupName) + if err != nil { + return nil, err + } + + // Get the client refrence + client, err := GetPublicIPAddressClientE(subscriptionID) + if err != nil { + return nil, err + } + + // Get the Public IP Address + pip, err := client.Get(context.Background(), resGroupName, publicIPAddressName, "") + if err != nil { + return nil, err + } + return &pip, nil +} + +// GetPublicIPAddressClientE creates a Public IP Addresses client in the specified Azure Subscription +func GetPublicIPAddressClientE(subscriptionID string) (*network.PublicIPAddressesClient, error) { + // Validate Azure subscription ID + subscriptionID, err := getTargetAzureSubscription(subscriptionID) + if err != nil { + return nil, err + } + + // Get the Public IP Address client + client := network.NewPublicIPAddressesClient(subscriptionID) + + // Create an authorizer + authorizer, err := NewAuthorizer() + if err != nil { + return nil, err + } + client.Authorizer = *authorizer + + return &client, nil +} diff --git a/modules/azure/resourceid.go b/modules/azure/resourceid.go new file mode 100644 index 000000000..815313f60 --- /dev/null +++ b/modules/azure/resourceid.go @@ -0,0 +1,36 @@ +package azure + +import ( + "errors" + "strings" +) + +// GetNameFromResourceID gets the Name from an Azure Resource ID +func GetNameFromResourceID(resourceID string) string { + lastValue, err := GetSliceLastValueE(resourceID, "/") + if err != nil { + return "" + } + return lastValue +} + +// GetSliceLastValueE will take a source string and returns the last value when split by the seperaror char +func GetSliceLastValueE(source string, seperator string) (string, error) { + if !(len(source) == 0 || len(seperator) == 0 || !strings.Contains(source, seperator)) { + tmp := strings.Split(source, seperator) + return tmp[len(tmp)-1], nil + } + return "", errors.New("invalid input or no slice available") +} + +// GetSliceIndexValueE will take a source string and returns the value at the given index when split by the seperaror char +func GetSliceIndexValueE(source string, seperator string, index int) (string, error) { + if !(len(source) == 0 || len(seperator) == 0 || !strings.Contains(source, seperator) || index < 0) { + tmp := strings.Split(source, seperator) + if !(len(tmp) >= index) { + return "", errors.New("index out of slice range") + } + return tmp[index], nil + } + return "", errors.New("invalid input or no slice available") +} diff --git a/modules/azure/virtualnetwork.go b/modules/azure/virtualnetwork.go new file mode 100644 index 000000000..366dea241 --- /dev/null +++ b/modules/azure/virtualnetwork.go @@ -0,0 +1,228 @@ +package azure + +import ( + "context" + "fmt" + "net" + + "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-09-01/network" + "github.com/gruntwork-io/terratest/modules/testing" + "github.com/stretchr/testify/require" +) + +// VirtualNetworkExists indicates whether the speficied Azure Virtual Network exists +func VirtualNetworkExists(t testing.TestingT, vnetName string, resGroupName string, subscriptionID string) bool { + exists, err := VirtualNetworkExistsE(t, vnetName, resGroupName, subscriptionID) + require.NoError(t, err) + return exists +} + +// VirtualNetworkExistsE indicates whether the speficied Azure Virtual Network exists +func VirtualNetworkExistsE(t testing.TestingT, vnetName string, resGroupName string, subscriptionID string) (bool, error) { + // Get the Virtual Network + _, err := GetVirtualNetworkE(t, vnetName, resGroupName, subscriptionID) + if err != nil { + return false, err + } + return true, nil +} + +// SubnetExists indicates whether the speficied Azure Virtual Network Subnet exists +func SubnetExists(t testing.TestingT, subnetName string, vnetName string, resGroupName string, subscriptionID string) bool { + exists, err := SubnetExistsE(t, subnetName, vnetName, resGroupName, subscriptionID) + require.NoError(t, err) + return exists +} + +// SubnetExistsE indicates whether the speficied Azure Virtual Network Subnet exists +func SubnetExistsE(t testing.TestingT, subnetName string, vnetName string, resGroupName string, subscriptionID string) (bool, error) { + // Get the Subnet + _, err := GetSubnetE(t, subnetName, vnetName, resGroupName, subscriptionID) + if err != nil { + return false, err + } + return true, nil +} + +// CheckSubnetContainsIP checks if the Private IP is contined in the Subnet Address Range +func CheckSubnetContainsIP(t testing.TestingT, IP string, subnetName string, vnetName string, resGroupName string, subscriptionID string) bool { + inRange, err := CheckSubnetContainsIPE(t, IP, subnetName, vnetName, resGroupName, subscriptionID) + require.NoError(t, err) + return inRange +} + +// CheckSubnetContainsIPE checks if the Private IP is contined in the Subnet Address Range +func CheckSubnetContainsIPE(t testing.TestingT, ipAddress string, subnetName string, vnetName string, resGroupName string, subscriptionID string) (bool, error) { + subnetPrefix, err := GetSubnetIPRangeE(t, subnetName, vnetName, resGroupName, subscriptionID) + if err != nil { + return false, err + } + + // Convert the IP to a net IP address + ip := net.ParseIP(ipAddress) + + if ip == nil { + return false, fmt.Errorf("Failed to parse IP address %s", ipAddress) + } + + // Check if the IP is in the Subnet Range + _, ipNet, err := net.ParseCIDR(subnetPrefix) + if err != nil { + return false, fmt.Errorf("Failed to parse subnet range %s", subnetPrefix) + } + + return ipNet.Contains(ip), nil +} + +// GetSubnetIPRange gets the IPv4 Range of the specified Subnet +func GetSubnetIPRange(t testing.TestingT, subnetName string, vnetName string, resGroupName string, subscriptionID string) string { + vnetDNSIPs, err := GetSubnetIPRangeE(t, subnetName, vnetName, resGroupName, subscriptionID) + require.NoError(t, err) + return vnetDNSIPs +} + +// GetSubnetIPRangeE gets the IPv4 Range of the specified Subnet +func GetSubnetIPRangeE(t testing.TestingT, subnetName string, vnetName string, resGroupName string, subscriptionID string) (string, error) { + // Get Subnet + subnet, err := GetSubnetE(t, subnetName, vnetName, resGroupName, subscriptionID) + if err != nil { + return "", err + } + + return *subnet.AddressPrefix, nil +} + +// GetVirtualNetworkSubnets gets all Subnet names and their respective address prefixes in the specified Virtual Network +func GetVirtualNetworkSubnets(t testing.TestingT, vnetName string, resGroupName string, subscriptionID string) map[string]string { + subnets, err := GetVirtualNetworkSubnetsE(t, vnetName, resGroupName, subscriptionID) + require.NoError(t, err) + return subnets +} + +// GetVirtualNetworkSubnetsE gets all Subnet names and their respective address prefixes in the specified Virtual Network +func GetVirtualNetworkSubnetsE(t testing.TestingT, vnetName string, resGroupName string, subscriptionID string) (map[string]string, error) { + client, err := GetSubnetClientE(subscriptionID) + if err != nil { + return nil, err + } + + subnets, err := client.List(context.Background(), resGroupName, vnetName) + if err != nil { + return nil, err + } + + subNetDetails := make(map[string]string) + for _, v := range subnets.Values() { + subnetName := v.Name + subNetAddressPrefix := v.AddressPrefix + + subNetDetails[string(*subnetName)] = string(*subNetAddressPrefix) + } + return subNetDetails, nil +} + +// GetVirtualNetworkDNSServerIPs gets a list of all Virtual Network DNS server IPs +func GetVirtualNetworkDNSServerIPs(t testing.TestingT, vnetName string, resGroupName string, subscriptionID string) []string { + vnetDNSIPs, err := GetVirtualNetworkDNSServerIPsE(t, vnetName, resGroupName, subscriptionID) + require.NoError(t, err) + + return vnetDNSIPs +} + +// GetVirtualNetworkDNSServerIPsE gets a list of all Virtual Network DNS server IPs with Error +func GetVirtualNetworkDNSServerIPsE(t testing.TestingT, vnetName string, resGroupName string, subscriptionID string) ([]string, error) { + // Get Virtual Network + vnet, err := GetVirtualNetworkE(t, vnetName, resGroupName, subscriptionID) + if err != nil { + return nil, err + } + + return *vnet.DhcpOptions.DNSServers, nil +} + +// GetSubnetE gets a subnet +func GetSubnetE(t testing.TestingT, subnetName string, vnetName string, resGroupName string, subscriptionID string) (*network.Subnet, error) { + // Validate Azure Resource Group + resGroupName, err := getTargetAzureResourceGroupName(resGroupName) + if err != nil { + return nil, err + } + + // Get the client refrence + client, err := GetSubnetClientE(subscriptionID) + if err != nil { + return nil, err + } + + // Get the Subnet + subnet, err := client.Get(context.Background(), resGroupName, vnetName, subnetName, "") + if err != nil { + return nil, err + } + + return &subnet, nil +} + +// GetSubnetClientE creates a subnet client +func GetSubnetClientE(subscriptionID string) (*network.SubnetsClient, error) { + // Validate Azure subscription ID + subscriptionID, err := getTargetAzureSubscription(subscriptionID) + if err != nil { + return nil, err + } + + // Get the Subnet client + client := network.NewSubnetsClient(subscriptionID) + + // Create an authorizer + authorizer, err := NewAuthorizer() + if err != nil { + return nil, err + } + client.Authorizer = *authorizer + + return &client, nil +} + +// GetVirtualNetworkE gets Virtual Network in the specified Azure Resource Group +func GetVirtualNetworkE(t testing.TestingT, vnetName string, resGroupName string, subscriptionID string) (*network.VirtualNetwork, error) { + // Validate Azure Resource Group + resGroupName, err := getTargetAzureResourceGroupName(resGroupName) + if err != nil { + return nil, err + } + + // Get the client refrence + client, err := GetVirtualNetworksClientE(subscriptionID) + if err != nil { + return nil, err + } + + // Get the Virtual Network + vnet, err := client.Get(context.Background(), resGroupName, vnetName, "") + if err != nil { + return nil, err + } + return &vnet, nil +} + +// GetVirtualNetworksClientE creates a virtual network client in the specified Azure Subscription +func GetVirtualNetworksClientE(subscriptionID string) (*network.VirtualNetworksClient, error) { + // Validate Azure subscription ID + subscriptionID, err := getTargetAzureSubscription(subscriptionID) + if err != nil { + return nil, err + } + + // Get the Virtual Network client + client := network.NewVirtualNetworksClient(subscriptionID) + + // Create an authorizer + authorizer, err := NewAuthorizer() + if err != nil { + return nil, err + } + client.Authorizer = *authorizer + + return &client, nil +} diff --git a/test/azure/compute_test.go b/test/azure/compute_test.go index 600952a6e..beb31b532 100644 --- a/test/azure/compute_test.go +++ b/test/azure/compute_test.go @@ -9,7 +9,6 @@ import ( "testing" "github.com/gruntwork-io/terratest/modules/azure" - "github.com/stretchr/testify/require" ) @@ -19,26 +18,192 @@ If/when CRUD methods are introduced for Azure Virtual Machines, these tests can (see AWS S3 tests for reference). */ -func TestGetTagsForVirtualMachineE(t *testing.T) { +func TestGetVirtualMachineE(t *testing.T) { + t.Parallel() + + vmName := "" + rgName := "" + subID := "" + + _, err := azure.GetVirtualMachineE(t, vmName, rgName, subID) + + require.Error(t, err) +} + +func TestGetVirtualMachineInstanceE(t *testing.T) { + t.Parallel() + + vmName := "" + rgName := "" + subID := "" + + _, err := azure.GetVirtualMachineInstanceE(t, vmName, rgName, subID) + + require.Error(t, err) +} + +func TestGetResourceGroupVirtualMachinesObjectsE(t *testing.T) { + t.Parallel() + + rgName := "" + subID := "" + + _, err := azure.GetResourceGroupVirtualMachinesObjectsE(t, rgName, subID) + + require.Error(t, err) +} + +func TestGetResourceGroupVirtualMachinesE(t *testing.T) { + t.Parallel() + + rgName := "" + subID := "" + + _, err := azure.GetResourceGroupVirtualMachinesE(t, rgName, subID) + + require.Error(t, err) +} + +func TestGetVirtualMachineTagsE(t *testing.T) { + t.Parallel() + + vmName := "" + rgName := "" + subID := "" + + _, err := azure.GetVirtualMachineTagsE(t, vmName, rgName, subID) + + require.Error(t, err) +} + +func TestGetVirtualMachineSizeE(t *testing.T) { + t.Parallel() + + vmName := "" + rgName := "" + subID := "" + + _, err := azure.GetVirtualMachineSizeE(t, vmName, rgName, subID) + + require.Error(t, err) +} + +func TestGetVirtualMachineAdminUserE(t *testing.T) { + t.Parallel() + + vmName := "" + rgName := "" + subID := "" + + _, err := azure.GetVirtualMachineAdminUserE(t, vmName, rgName, subID) + + require.Error(t, err) +} + +func TestGetVirtualMachineImageE(t *testing.T) { + t.Parallel() + + vmName := "" + rgName := "" + subID := "" + + _, err := azure.GetVirtualMachineImageE(t, vmName, rgName, subID) + + require.Error(t, err) +} + +func TestGetVirtualMachineAvailabilitySetIDE(t *testing.T) { + t.Parallel() + + vmName := "" + rgName := "" + subID := "" + + _, err := azure.GetVirtualMachineAvailabilitySetIDE(t, vmName, rgName, subID) + + require.Error(t, err) +} + +func TestGetVirtualMachineStateE(t *testing.T) { + t.Parallel() + + vmName := "" + rgName := "" + subID := "" + + _, err := azure.GetVirtualMachineStateE(t, vmName, rgName, subID) + + require.Error(t, err) +} + +func TestGetVirtualMachineOsDiskNameE(t *testing.T) { + t.Parallel() + + vmName := "" + rgName := "" + subID := "" + + _, err := azure.GetVirtualMachineOsDiskNameE(t, vmName, rgName, subID) + + require.Error(t, err) +} + +func TestGetVirtualMachineManagedDiskCountE(t *testing.T) { + t.Parallel() + + vmName := "" + rgName := "" + subID := "" + + _, err := azure.GetVirtualMachineManagedDiskCountE(t, vmName, rgName, subID) + + require.Error(t, err) +} + +func TestGetVirtualMachineManagedDisksE(t *testing.T) { + t.Parallel() + + vmName := "" + rgName := "" + subID := "" + + _, err := azure.GetVirtualMachineManagedDisksE(t, vmName, rgName, subID) + + require.Error(t, err) +} + +func TestGetVirtualMachineNicCountE(t *testing.T) { + t.Parallel() + + vmName := "" + rgName := "" + subID := "" + + _, err := azure.GetVirtualMachineNicCountE(t, vmName, rgName, subID) + + require.Error(t, err) +} + +func TestGetVirtualMachineNicsE(t *testing.T) { t.Parallel() vmName := "" rgName := "" subID := "" - _, err := azure.GetTagsForVirtualMachineE(t, vmName, rgName, subID) + _, err := azure.GetVirtualMachineNicsE(t, vmName, rgName, subID) require.Error(t, err) } -func TestGetSizeOfVirtualMachineE(t *testing.T) { +func TestVirtualMachineExistsE(t *testing.T) { t.Parallel() vmName := "" rgName := "" subID := "" - _, err := azure.GetSizeOfVirtualMachineE(t, vmName, rgName, subID) + _, err := azure.VirtualMachineExistsE(t, vmName, rgName, subID) require.Error(t, err) } diff --git a/test/azure/terraform_azure_vm_test.go b/test/azure/terraform_azure_vm_test.go new file mode 100644 index 000000000..51a7767e4 --- /dev/null +++ b/test/azure/terraform_azure_vm_test.go @@ -0,0 +1,171 @@ +// +build azure + +// NOTE: We use build tags to differentiate azure testing because we currently do not have azure access setup for +// CircleCI. + +package test + +import ( + "strings" + "testing" + + "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-07-01/compute" + "github.com/gruntwork-io/terratest/modules/azure" + "github.com/gruntwork-io/terratest/modules/terraform" + "github.com/stretchr/testify/assert" +) + +func TestTerraformAzureVmExample(t *testing.T) { + t.Parallel() + + subscriptionID := "" // Subscription ID, leave blank if available as an Environment Var + prefix := "terratest-vm" + expectedVmAdminUser := "testadmin" + expectedVMSize := "Standard_DS1_v2" + expectedImageSKU := "2016-Datacenter" + expectedImageVersion := "latest" + expectedDiskType := "Standard_LRS" + expectedSubnetAddressRange := "10.0.17.0/24" + expectedPrivateIPAddress := "10.0.17.4" + var expectedAvsFaultDomainCount int32 = 2 + expectedManagedDiskCount := 1 + expectedNicCount := 1 + + // Configure Terraform setting up a path to Terraform code. + terraformOptions := &terraform.Options{ + // The path to where our Terraform code is located + TerraformDir: "../../examples/azure/terraform-azure-vm-example", + + // Variables to pass to our Terraform code using -var options + // "username" and "password" should not be passed from here in a production scenario. + Vars: map[string]interface{}{ + "prefix": prefix, + "user_name": expectedVmAdminUser, + "vm_size": expectedVMSize, + "vm_image_sku": expectedImageSKU, + "vm_image_version": expectedImageVersion, + "disk_type": expectedDiskType, + "private_ip": expectedPrivateIPAddress, + "subnet_prefix": expectedSubnetAddressRange, + }, + } + + // At the end of the test, run `terraform destroy` to clean up any resources that were created + defer terraform.Destroy(t, terraformOptions) + + // Run `terraform init` and `terraform apply`. Fail the test if there are any errors. + terraform.InitAndApply(t, terraformOptions) + + // Run `terraform output` to get the values of output variables + resourceGroupName := terraform.Output(t, terraformOptions, "resource_group_name") + vmName := terraform.Output(t, terraformOptions, "vm_name") + vnetName := terraform.Output(t, terraformOptions, "virtual_network_name") + subnetName := terraform.Output(t, terraformOptions, "subnet_name") + publicIpName := terraform.Output(t, terraformOptions, "public_ip_name") + nicName := terraform.Output(t, terraformOptions, "network_interface_name") + avsName := terraform.Output(t, terraformOptions, "availability_set_name") + osDiskName := prefix + "-osdisk" + diskName := terraform.Output(t, terraformOptions, "managed_disk_name") + + t.Run("Strategies", func(t *testing.T) { + // Check the VM Size directly + actualVMSize := azure.GetVirtualMachineSize(t, vmName, resourceGroupName, subscriptionID) + assert.Equal(t, expectedVMSize, string(actualVMSize)) + + // Check the VM Size by object ref + vmRef := azure.GetVirtualMachine(t, vmName, resourceGroupName, subscriptionID) + actualVMSize = vmRef.HardwareProfile.VMSize + assert.Equal(t, expectedVMSize, string(actualVMSize)) + + // Check the VM Size by instance getter + vmInstance := azure.GetVirtualMachineInstance(t, vmName, resourceGroupName, subscriptionID) + actualVMSize = vmInstance.GetVirtualMachineInstanceSize() + assert.Equal(t, expectedVMSize, string(actualVMSize)) + }) + + t.Run("MultipleVMs", func(t *testing.T) { + // Get a list of all VMs and confirm one (or more) VMs exist + vmList := azure.GetResourceGroupVirtualMachines(t, resourceGroupName, subscriptionID) + assert.True(t, len(vmList) > 0) + assert.Contains(t, vmList, vmName) + + // Get all VMs by ref (warning: pointer deref painc if vm is not in list!) + vmsByRef := azure.GetResourceGroupVirtualMachinesObjects(t, resourceGroupName, subscriptionID) + assert.True(t, len(*vmsByRef) > 0) + thisVm := (*vmsByRef)[vmName] + assert.Equal(t, expectedVMSize, string(thisVm.HardwareProfile.VMSize)) + }) + + t.Run("Information", func(t *testing.T) { + // Check the Virtual Machine exists + assert.True(t, azure.VirtualMachineExists(t, vmName, resourceGroupName, subscriptionID)) + + // Check the Admin User + actualVmAdminUser := azure.GetVirtualMachineAdminUser(t, vmName, resourceGroupName, subscriptionID) + assert.Equal(t, expectedVmAdminUser, actualVmAdminUser) + + // Check the Storage Image reference + actualImage := azure.GetVirtualMachineImage(t, vmName, resourceGroupName, subscriptionID) + assert.Equal(t, expectedImageSKU, actualImage.SKU) + assert.Equal(t, expectedImageVersion, actualImage.Version) + }) + + t.Run("AvailablitySet", func(t *testing.T) { + // Check the Availability Set + actualAvsName := azure.GetVirtualMachineAvailabilitySetID(t, vmName, resourceGroupName, subscriptionID) + assert.True(t, strings.EqualFold(avsName, actualAvsName)) + + // Check the Availability set fault domain counts + actualAvsFaultDomainCount := azure.GetAvailabilitySetFaultDomainCount(t, avsName, resourceGroupName, subscriptionID) + assert.Equal(t, expectedAvsFaultDomainCount, actualAvsFaultDomainCount) + + actualVMsInAvs := azure.GetAvailabilitySetVMs(t, avsName, resourceGroupName, subscriptionID) + assert.Contains(t, actualVMsInAvs, strings.ToLower(vmName)) + }) + + t.Run("Disk", func(t *testing.T) { + // Check the OS Disk name + actualOSDiskName := azure.GetVirtualMachineOsDiskName(t, vmName, resourceGroupName, subscriptionID) + assert.Equal(t, osDiskName, actualOSDiskName) + + // Check the Managed Disk count + actualManagedDiskCount := azure.GetVirtualMachineManagedDiskCount(t, vmName, resourceGroupName, subscriptionID) + assert.Equal(t, expectedManagedDiskCount, actualManagedDiskCount) + + // Check the VM Managed Disk exists in the list of all VM Managed Disks + actualManagedDiskNames := azure.GetVirtualMachineManagedDisks(t, vmName, resourceGroupName, subscriptionID) + assert.Contains(t, actualManagedDiskNames, diskName) + + // Check the Disk Type + actualDiskType := azure.GetDiskType(t, diskName, resourceGroupName, subscriptionID) + assert.Equal(t, compute.DiskStorageAccountTypes(expectedDiskType), actualDiskType) + }) + + t.Run("NetworkInterface", func(t *testing.T) { + // Check the Network Interface count + actualNicCount := azure.GetVirtualMachineNicCount(t, vmName, resourceGroupName, subscriptionID) + assert.Equal(t, expectedNicCount, actualNicCount) + + // Check the VM Network Interface exists in the list of all VM Network Interfaces + actualNics := azure.GetVirtualMachineNics(t, vmName, resourceGroupName, subscriptionID) + assert.Contains(t, actualNics, nicName) + + // Check the Private IP + actualNicIPs := azure.GetNetworkInterfacePrivateIPs(t, nicName, resourceGroupName, subscriptionID) + assert.Contains(t, actualNicIPs, expectedPrivateIPAddress) + + // Check the Public IP exists + actualPublicIP := azure.GetPublicAddressIP(t, publicIpName, resourceGroupName, subscriptionID) + assert.NotNil(t, actualPublicIP) + }) + + t.Run("Vnet&Subnet", func(t *testing.T) { + // Check the Subnet exists in the Virtual Network Subnets + actualVnetSubnets := azure.GetVirtualNetworkSubnets(t, vnetName, resourceGroupName, subscriptionID) + assert.NotNil(t, actualVnetSubnets[vnetName]) + + // Check the Private IP is in the Subnet Range + actualVMNicIPInSubnet := azure.CheckSubnetContainsIP(t, expectedPrivateIPAddress, subnetName, vnetName, resourceGroupName, subscriptionID) + assert.True(t, actualVMNicIPInSubnet) + }) +} From a5a3c0c1e5e10df0a6aa7c6f99a691c460ad3d6b Mon Sep 17 00:00:00 2001 From: Dave Seepersad <“davesee@microsoft.com”> Date: Wed, 16 Sep 2020 17:26:13 -0400 Subject: [PATCH 02/56] Updated original compute test --- test/azure/terraform_azure_example_test.go | 4 ++-- test/azure/terraform_azure_vm_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/azure/terraform_azure_example_test.go b/test/azure/terraform_azure_example_test.go index e7744b154..c2e3a5263 100644 --- a/test/azure/terraform_azure_example_test.go +++ b/test/azure/terraform_azure_example_test.go @@ -1,4 +1,4 @@ -// +build azure +// +build azure azureslim,compute // NOTE: We use build tags to differentiate azure testing because we currently do not have azure access setup for // CircleCI. @@ -34,7 +34,7 @@ func TestTerraformAzureExample(t *testing.T) { resourceGroupName := terraform.Output(t, terraformOptions, "resource_group_name") // website::tag::4:: Look up the size of the given Virtual Machine and ensure it matches the output. - actualVMSize := azure.GetSizeOfVirtualMachine(t, vmName, resourceGroupName, "") + actualVMSize := azure.GetVirtualMachineSize(t, vmName, resourceGroupName, "") expectedVMSize := compute.VirtualMachineSizeTypes("Standard_B1s") assert.Equal(t, expectedVMSize, actualVMSize) } diff --git a/test/azure/terraform_azure_vm_test.go b/test/azure/terraform_azure_vm_test.go index 51a7767e4..a2d01aae3 100644 --- a/test/azure/terraform_azure_vm_test.go +++ b/test/azure/terraform_azure_vm_test.go @@ -1,4 +1,4 @@ -// +build azure +// +build azure azureslim,compute // NOTE: We use build tags to differentiate azure testing because we currently do not have azure access setup for // CircleCI. From 18fe471820b8a5be68a0bf6ec172762afcd6ad12 Mon Sep 17 00:00:00 2001 From: Dave Seepersad <“davesee@microsoft.com”> Date: Wed, 16 Sep 2020 18:27:20 -0400 Subject: [PATCH 03/56] Updated subnet name to be unique --- examples/azure/terraform-azure-example/main.tf | 2 +- examples/azure/terraform-azure-vm-example/main.tf | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/azure/terraform-azure-example/main.tf b/examples/azure/terraform-azure-example/main.tf index 733272694..50a9d99f5 100644 --- a/examples/azure/terraform-azure-example/main.tf +++ b/examples/azure/terraform-azure-example/main.tf @@ -33,7 +33,7 @@ resource "azurerm_virtual_network" "main" { } resource "azurerm_subnet" "internal" { - name = "internal" + name = "${var.prefix}-subnet" resource_group_name = azurerm_resource_group.main.name virtual_network_name = azurerm_virtual_network.main.name address_prefix = "10.0.17.0/24" diff --git a/examples/azure/terraform-azure-vm-example/main.tf b/examples/azure/terraform-azure-vm-example/main.tf index 87ece33d4..246683539 100644 --- a/examples/azure/terraform-azure-vm-example/main.tf +++ b/examples/azure/terraform-azure-vm-example/main.tf @@ -81,7 +81,7 @@ resource "azurerm_availability_set" "main" { # --------------------------------------------------------------------------------------------------------------------- resource "azurerm_virtual_machine" "main" { - name = "${var.prefix}-01" + name = "${var.prefix}-vm" location = azurerm_resource_group.main.location resource_group_name = azurerm_resource_group.main.name network_interface_ids = [azurerm_network_interface.main.id] @@ -106,7 +106,7 @@ resource "azurerm_virtual_machine" "main" { } os_profile { - computer_name = "${var.prefix}-01" + computer_name = var.prefix admin_username = var.user_name admin_password = var.password } From 3254f129241210d8ab0120a45e2c756c2f6f1d9d Mon Sep 17 00:00:00 2001 From: Dave Seepersad <“davesee@microsoft.com”> Date: Wed, 16 Sep 2020 18:29:45 -0400 Subject: [PATCH 04/56] Reduce ForbiddenRegions iter count --- test/azure/region_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/azure/region_test.go b/test/azure/region_test.go index 9d986056f..ca20160b5 100644 --- a/test/azure/region_test.go +++ b/test/azure/region_test.go @@ -26,7 +26,7 @@ func TestGetRandomRegionExcludesForbiddenRegions(t *testing.T) { approvedRegions := []string{"canadacentral", "eastus", "eastus2", "westus", "westus2", "westeurope", "northeurope", "uksouth", "southeastasia", "eastasia", "japaneast", "australiacentral"} forbiddenRegions := []string{"westus2", "japaneast"} - for i := 0; i < 1000; i++ { + for i := 0; i < 50; i++ { randomRegion := azure.GetRandomRegion(t, approvedRegions, forbiddenRegions, "") assert.NotContains(t, forbiddenRegions, randomRegion) } From 3e7f7221626847b63ffde4c35f2866ad86d8992d Mon Sep 17 00:00:00 2001 From: Dave Seepersad <“davesee@microsoft.com”> Date: Tue, 22 Sep 2020 11:21:24 -0400 Subject: [PATCH 05/56] Initial changes --- examples/azure/README.md | 54 ++++++ .../terraform-azure-vm-example/README.md | 156 +++--------------- .../azure/terraform-azure-vm-example/main.tf | 92 ++++++----- .../terraform-azure-vm-example/outputs.tf | 2 +- .../terraform-azure-vm-example/variables.tf | 4 +- {test => modules}/azure/common_test.go | 11 +- {test => modules}/azure/compute_test.go | 35 ++-- modules/azure/errors.go | 15 ++ modules/azure/networkinterface.go | 6 +- modules/azure/networkinterface_test.go | 65 ++++++++ modules/azure/publicaddress.go | 6 - modules/azure/publicaddress_test.go | 65 ++++++++ {test => modules}/azure/region_test.go | 8 +- modules/azure/resourceid.go | 30 +--- modules/azure/resourceid_test.go | 29 ++++ modules/azure/virtualnetwork.go | 44 ++--- modules/azure/virtualnetwork_test.go | 105 ++++++++++++ modules/collections/errors.go | 17 ++ modules/collections/stringslicevalue.go | 26 +++ modules/collections/stringslicevalue_test.go | 77 +++++++++ 20 files changed, 579 insertions(+), 268 deletions(-) create mode 100644 examples/azure/README.md rename {test => modules}/azure/common_test.go (78%) rename {test => modules}/azure/compute_test.go (69%) create mode 100644 modules/azure/networkinterface_test.go create mode 100644 modules/azure/publicaddress_test.go rename {test => modules}/azure/region_test.go (88%) create mode 100644 modules/azure/resourceid_test.go create mode 100644 modules/azure/virtualnetwork_test.go create mode 100644 modules/collections/errors.go create mode 100644 modules/collections/stringslicevalue.go create mode 100644 modules/collections/stringslicevalue_test.go diff --git a/examples/azure/README.md b/examples/azure/README.md new file mode 100644 index 000000000..42cf21ea0 --- /dev/null +++ b/examples/azure/README.md @@ -0,0 +1,54 @@ +# Terratest Configuration and Setup + +Terratest uses Go to make calls to Azure through the azure-sdk-for-go library and independently confirm the actual Azure resource property matches the expected state provided by Terraform output variables. + +- Instructions for running each Azure Terratest module are included in each Terraform example sub-folder: + - examples/azure/terraform-azure-\*-example/README.md +- Tests wich assert against expected Terraform output values are located in the the respective go files of the folder: + - [test/azure/terraform-azure-\*-example_test.go](../../test/azure) +- Test APIs which provide the actual Azure resource property values via the azure-sdk-for-go are located in the folder: + - [modules/azure](../../modules/azure) + +## Go Dependencies + +Install [Golang](https://golang.org/) and make sure this code is checked out into your `GOPATH` + +These modules are currently using the latest version of Go and was tested with **go1.14.4**. + +## Azure-sdk-for-go version + +Let's make sure [go.mod](https://github.com/gruntwork-io/terratest/blob/master/go.mod) includes the appropriate [azure-sdk-for-go version](https://github.com/Azure/azure-sdk-for-go/releases/tag/v46.1.0): + +```go +require ( + ... + github.com/Azure/azure-sdk-for-go v46.1.0+incompatible + ... +) +``` + +If we make changes to either the **go.mod** or the **go test file**, we should make sure that the go build command works still. + +```powershell +go build terraform_azure_*_test.go +``` + +## Review Environment Variables + +As part of configuring terraform for Azure, we'll want to check that we have set the appropriate [credentials](https://docs.microsoft.com/en-us/azure/terraform/terraform-install-configure?toc=https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fterraform%2Ftoc.json&bc=https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fbread%2Ftoc.json#set-up-terraform-access-to-azure) and also that we set the [environment variables](https://docs.microsoft.com/en-us/azure/terraform/terraform-install-configure?toc=https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fterraform%2Ftoc.json&bc=https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fbread%2Ftoc.json#configure-terraform-environment-variables) on the testing host. + +```bash +export ARM_CLIENT_ID=your_app_id +export ARM_CLIENT_SECRET=your_password +export ARM_SUBSCRIPTION_ID=your_subscription_id +export ARM_TENANT_ID=your_tenant_id +``` + +Note, in a Windows environment, these should be set as **system environment variables**. We can use a PowerShell console with administrative rights to update these environment variables: + +```powershell +[System.Environment]::SetEnvironmentVariable("ARM_CLIENT_ID",$your_app_id,[System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable("ARM_CLIENT_SECRET",$your_password,[System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable("ARM_SUBSCRIPTION_ID",$your_subscription_id,[System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable("ARM_TENANT_ID",$your_tenant_id,[System.EnvironmentVariableTarget]::Machine) +``` diff --git a/examples/azure/terraform-azure-vm-example/README.md b/examples/azure/terraform-azure-vm-example/README.md index e57ced5c8..6efe83378 100644 --- a/examples/azure/terraform-azure-vm-example/README.md +++ b/examples/azure/terraform-azure-vm-example/README.md @@ -3,15 +3,15 @@ This folder contains a complete Terraform VM module that deploys resources in [Azure](https://azure.microsoft.com/) to demonstrate how you can use Terratest to write automated tests for your Azure Virtual Machine Terraform code. This module deploys these resources: -* A [Virtual Machine](https://azure.microsoft.com/en-us/services/virtual-machines/) and gives that VM the following: - * [Virtual Machine](https://docs.microsoft.com/en-us/azure/virtual-machines/) with the namne specified in the `vm_name` variable. - * [Managed Disk](https://docs.microsoft.com/en-us/azure/virtual-machines/managed-disks-overview) with the namne specified in the `managed_disk_name` variable. - * [Availability Set](https://docs.microsoft.com/en-us/azure/virtual-machines/availability) with the namne specified in the `availability_set_name` variable. -* A [Virtual Network](https://azure.microsoft.com/en-us/services/virtual-network/) module that gives the following resources: - * [Virtual Network](https://docs.microsoft.com/en-us/azure/virtual-network/) with the name specified in the `virtual_network_name` variable. - * [Subnet](https://docs.microsoft.com/en-us/rest/api/virtualnetwork/subnets) with the name specified in the `subnet_name` variable. - * [Public Address](https://docs.microsoft.com/en-us/azure/virtual-network/public-ip-addresses) with the name specified in the `public_ip_name` variable. - * [Network Interface](https://docs.microsoft.com/en-us/azure/virtual-network/virtual-network-network-interface) with the name specified in the `network_interface_name` variable. +- A [Virtual Machine](https://azure.microsoft.com/en-us/services/virtual-machines/) and gives that VM the following resources: + - [Virtual Machine](https://docs.microsoft.com/en-us/azure/virtual-machines/) with the name specified in the `vm_name` variable. + - [Managed Disk](https://docs.microsoft.com/en-us/azure/virtual-machines/managed-disks-overview) with the namne specified in the `managed_disk_name` variable. + - [Availability Set](https://docs.microsoft.com/en-us/azure/virtual-machines/availability) with the namne specified in the `availability_set_name` variable. +- A [Virtual Network](https://azure.microsoft.com/en-us/services/virtual-network/) module that contains the following resources: + - [Virtual Network](https://docs.microsoft.com/en-us/azure/virtual-network/) with the name specified in the `virtual_network_name` variable. + - [Subnet](https://docs.microsoft.com/en-us/rest/api/virtualnetwork/subnets) with the name specified in the `subnet_name` variable. + - [Public Address](https://docs.microsoft.com/en-us/azure/virtual-network/public-ip-addresses) with the name specified in the `public_ip_name` variable. + - [Network Interface](https://docs.microsoft.com/en-us/azure/virtual-network/virtual-network-network-interface) with the name specified in the `network_interface_name` variable. Check out [test/azure/terraform_azure_vm_test.go](/test/azure/terraform_azure_vm_test.go) to see how you can write automated tests for this module. @@ -24,132 +24,22 @@ it should be free, but you are completely responsible for all Azure charges. ## Running this module manually -1. Sign up for [Azure](https://azure.microsoft.com/). -1. Configure your Azure credentials using one of the [supported methods for Azure CLI - tools](https://docs.microsoft.com/en-us/cli/azure/azure-cli-configuration?view=azure-cli-latest). -1. Install [Terraform](https://www.terraform.io/) and make sure it's on your `PATH`. -1. Run `terraform init`. -1. Run `terraform apply`. -1. When you're done, run `terraform destroy`. +1. Sign up for [Azure](https://azure.microsoft.com/) +1. Configure your Azure credentials using one of the [supported methods for Azure CL + tools](https://docs.microsoft.com/en-us/cli/azure/azure-cli-configuration?view=azure-cli-latest) +1. Install [Terraform](https://www.terraform.io/) and make sure it's on your `PATH` +1. Ensure [environment variables](../README.md#review-environment-variables) are available +1. Run `terraform init` +1. Run `terraform apply` +1. When you're done, run `terraform destroy` ## Running automated tests against this module -1. Sign up for [Azure](https://azure.microsoft.com/). +1. Sign up for [Azure](https://azure.microsoft.com/) 1. Configure your Azure credentials using one of the [supported methods for Azure CLI - tools](https://docs.microsoft.com/en-us/cli/azure/azure-cli-configuration?view=azure-cli-latest). -1. Install [Terraform](https://www.terraform.io/) and make sure it's on your `PATH`. -1. [Review environment variables](#review-environment-variables). -1. Install [Golang](https://golang.org/) and make sure this code is checked out into your `GOPATH`. -1. `cd test` -1. Make sure [the azure-sdk-for-go versions match](#check-go-dependencies) in [/go.mod](/go.mod) and in [test/azure/terraform_azure_vm_test.go](/test/azure/terraform_azure_vm_test.go). + tools](https://docs.microsoft.com/en-us/cli/azure/azure-cli-configuration?view=azure-cli-latest) +1. Install [Terraform](https://www.terraform.io/) and make sure it's on your `PATH` +1. Configure your Terratest [Go test environment](../README.md) +1. `cd test/azure` 1. `go build terraform_azure_vm_test.go` -1. `go test -v -run TestTerraformAzureVmExample -timeout 20m` - * Note the extra -timeout flag of 20 minutes ensures proper Azure resource removal time. - -## Module test APIs - -- `VirtualMachineExists` indicates whether the speficied Azure Virtual Machine exists -func VirtualMachineExists(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) bool - -- `GetVirtualMachineAdminUser` gets the Admin Username of the specified Azure Virtual Machine -func GetVirtualMachineAdminUser(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) string - -- `GetVirtualMachineAvailabilitySetID` gets the Availability Set ID of the specified Azure Virtual Machine -func GetVirtualMachineAvailabilitySetID(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) string - -- `GetVirtualMachineImage` gets the Image of the specified Azure Virtual Machine -func GetVirtualMachineImage(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) VMImage - ``` - type VMImage struct { - Publisher string - Offer string - SKU string - Version string - } - ``` -- `GetVirtualMachineManagedDiskCount` gets the Managed Disk count of the specified Azure Virtual Machine\ -func GetVirtualMachineManagedDiskCount(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) int - -- `GetVirtualMachineManagedDisks` gets the list of Managed Disk names of the specified Azure Virtual Machine\ -func GetVirtualMachineManagedDisks(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) []string - -- `GetVirtualMachineNicCount` gets the Network Interface count of the specified Azure Virtual Machine\ -func GetVirtualMachineNicCount(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) int - -- `GetVirtualMachineNics` gets a list of Network Interface names for a speficied Azure Virtual Machine\ -func GetVirtualMachineNics(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) []string - -- `GetVirtualMachineOsDiskName` gets the OS Disk name of the specified Azure Virtual Machine\ -func GetVirtualMachineOsDiskName(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) string - -- `GetVirtualMachineSize` gets the Size Type of the specified Azure Virtual Machine\ -func GetVirtualMachineSize(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) compute.VirtualMachineSizeTypes - -- `GetVirtualMachineState` gets the State of the specified Azure Virtual Machine\ -func GetVirtualMachineState(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) string - -- `GetVirtualMachineTags` gets the Tags of the specified Virtual Machine as a map\ -func GetVirtualMachineTags(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) map[string]string - -- `GetVirtualMachineInstance` gets a local Virtual Machine instance in the specified Resource Group\ -func GetVirtualMachineInstance(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) *Instance - -- `GetVirtualMachineInstanceSize` gets the size of the Virtual Machine\ -func (vm *Instance) GetVirtualMachineInstanceSize() compute.VirtualMachineSizeTypes - -- `GetResourceGroupVirtualMachines` gets a list of all Virtual Machine names in the specified Resource Group\ -func GetResourceGroupVirtualMachines(t testing.TestingT, resGroupName string, subscriptionID string) []string - -- `GetResourceGroupVirtualMachinesObjects` gets all Virtual Machine objects in the specified Resource Group\ -func GetResourceGroupVirtualMachinesObjects(t testing.TestingT, resGroupName string, subscriptionID string) *map[string]compute.VirtualMachineProperties - -- `GetVirtualMachine` gets a Virtual Machine in the specified Azure Resource Group\ -func GetVirtualMachine(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) *compute.VirtualMachine - -- `GetVirtualMachineClientE` creates a Azure Virtual Machine client in the specified Azure Subscription\ -func GetVirtualMachineClientE(subscriptionID string) (*compute.VirtualMachinesClient, error) - -## Check Go Dependencies - -Check that the `github.com/Azure/azure-sdk-for-go` version in your generated `go.mod` for this test matches the version in the terratest [go.mod](https://github.com/gruntwork-io/terratest/blob/master/go.mod) file. - -> This was tested with **go1.14.4**. - -### Check Azure-sdk-for-go version - -Let's make sure [go.mod](https://github.com/gruntwork-io/terratest/blob/master/go.mod) includes the appropriate [azure-sdk-for-go version](https://github.com/Azure/azure-sdk-for-go/releases/tag/v46.1.0): - -```go -require ( - ... - github.com/Azure/azure-sdk-for-go v46.1.0+incompatible - ... -) -``` - -If we make changes to either the **go.mod** or the **go test file**, we should make sure that the go build command works still. - -```powershell -go build terraform_azure_vm_test.go -``` - -## Review Environment Variables - -As part of configuring terraform for Azure, we'll want to check that we have set the appropriate [credentials](https://docs.microsoft.com/en-us/azure/terraform/terraform-install-configure?toc=https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fterraform%2Ftoc.json&bc=https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fbread%2Ftoc.json#set-up-terraform-access-to-azure) and also that we set the [environment variables](https://docs.microsoft.com/en-us/azure/terraform/terraform-install-configure?toc=https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fterraform%2Ftoc.json&bc=https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fbread%2Ftoc.json#configure-terraform-environment-variables) on the testing host. - -```bash -export ARM_CLIENT_ID=your_app_id -export ARM_CLIENT_SECRET=your_password -export ARM_SUBSCRIPTION_ID=your_subscription_id -export ARM_TENANT_ID=your_tenant_id -``` - -Note, in a Windows environment, these should be set as **system environment variables**. We can use a PowerShell console with administrative rights to update these environment variables: - -```powershell -[System.Environment]::SetEnvironmentVariable("ARM_CLIENT_ID",$your_app_id,[System.EnvironmentVariableTarget]::Machine) -[System.Environment]::SetEnvironmentVariable("ARM_CLIENT_SECRET",$your_password,[System.EnvironmentVariableTarget]::Machine) -[System.Environment]::SetEnvironmentVariable("ARM_SUBSCRIPTION_ID",$your_subscription_id,[System.EnvironmentVariableTarget]::Machine) -[System.Environment]::SetEnvironmentVariable("ARM_TENANT_ID",$your_tenant_id,[System.EnvironmentVariableTarget]::Machine) -``` - +1. `go test -run -v -timeout 20m TestTerraformAzureVmExample` diff --git a/examples/azure/terraform-azure-vm-example/main.tf b/examples/azure/terraform-azure-vm-example/main.tf index 246683539..626224916 100644 --- a/examples/azure/terraform-azure-vm-example/main.tf +++ b/examples/azure/terraform-azure-vm-example/main.tf @@ -1,3 +1,7 @@ +# --------------------------------------------------------------------------------------------------------------------- +# See test/azure/terraform_azure_vm_example_test.go for how to write automated tests for this code. +# --------------------------------------------------------------------------------------------------------------------- + provider "azurerm" { version = "=2.20.0" features {} @@ -5,7 +9,6 @@ provider "azurerm" { # --------------------------------------------------------------------------------------------------------------------- # PIN TERRAFORM VERSION TO >= 0.12 -# The examples have been upgraded to 0.12 syntax # --------------------------------------------------------------------------------------------------------------------- terraform { @@ -13,11 +16,20 @@ terraform { } # --------------------------------------------------------------------------------------------------------------------- -# DEPLOY A RESOURCE GROUP -# See test/terraform_azure_example_test.go for how to write automated tests for this code. +# GENERATE RANDOMIZATION +# This helps avoid resource name collisions and improve test security # --------------------------------------------------------------------------------------------------------------------- -resource "azurerm_resource_group" "main" { +resource "random_password" "vmexample" { + length = 16 + override_special = "-_%@" +} + +# --------------------------------------------------------------------------------------------------------------------- +# DEPLOY RESOURCE GROUP TO CONTAIN TEST RESOURCES +# --------------------------------------------------------------------------------------------------------------------- + +resource "azurerm_resource_group" "vmexample" { name = "${var.prefix}-resources" location = var.location } @@ -26,41 +38,41 @@ resource "azurerm_resource_group" "main" { # DEPLOY VIRTUAL NETWORK RESOURCES # --------------------------------------------------------------------------------------------------------------------- -resource "azurerm_virtual_network" "main" { +resource "azurerm_virtual_network" "vmexample" { name = "${var.prefix}-network" address_space = ["10.0.0.0/16"] - location = azurerm_resource_group.main.location - resource_group_name = azurerm_resource_group.main.name + location = azurerm_resource_group.vmexample.location + resource_group_name = azurerm_resource_group.vmexample.name } -resource "azurerm_subnet" "main" { +resource "azurerm_subnet" "vmexample" { name = "${var.prefix}-subnet" - resource_group_name = azurerm_resource_group.main.name - virtual_network_name = azurerm_virtual_network.main.name - address_prefixes = [var.subnet_prefix] + resource_group_name = azurerm_resource_group.vmexample.name + virtual_network_name = azurerm_virtual_network.vmexample.name + address_prefixes = [var.subnet_prefix] } -resource "azurerm_public_ip" "main" { +resource "azurerm_public_ip" "vmexample" { name = "${var.prefix}-pip" - resource_group_name = azurerm_resource_group.main.name - location = azurerm_resource_group.main.location + resource_group_name = azurerm_resource_group.vmexample.name + location = azurerm_resource_group.vmexample.location allocation_method = "Static" ip_version = "IPv4" sku = "Standard" idle_timeout_in_minutes = "4" } -resource "azurerm_network_interface" "main" { +resource "azurerm_network_interface" "vmexample" { name = "${var.prefix}-nic" - location = azurerm_resource_group.main.location - resource_group_name = azurerm_resource_group.main.name + location = azurerm_resource_group.vmexample.location + resource_group_name = azurerm_resource_group.vmexample.name ip_configuration { name = "terratestconfiguration1" - subnet_id = azurerm_subnet.main.id + subnet_id = azurerm_subnet.vmexample.id private_ip_address_allocation = "Static" private_ip_address = var.private_ip - public_ip_address_id = azurerm_public_ip.main.id + public_ip_address_id = azurerm_public_ip.vmexample.id } } @@ -68,24 +80,24 @@ resource "azurerm_network_interface" "main" { # DEPLOY AVAILABILITY SET # --------------------------------------------------------------------------------------------------------------------- -resource "azurerm_availability_set" "main" { - name = "${var.prefix}-avs" - location = azurerm_resource_group.main.location - resource_group_name = azurerm_resource_group.main.name - platform_fault_domain_count = 2 - managed = true +resource "azurerm_availability_set" "vmexample" { + name = "${var.prefix}-avs" + location = azurerm_resource_group.vmexample.location + resource_group_name = azurerm_resource_group.vmexample.name + platform_fault_dovmexample_count = 2 + managed = true } # --------------------------------------------------------------------------------------------------------------------- # DEPLOY A VIRTUAL MACHINE RUNNING WINDOWS SERVER # --------------------------------------------------------------------------------------------------------------------- -resource "azurerm_virtual_machine" "main" { +resource "azurerm_virtual_machine" "vmexample" { name = "${var.prefix}-vm" - location = azurerm_resource_group.main.location - resource_group_name = azurerm_resource_group.main.name - network_interface_ids = [azurerm_network_interface.main.id] - availability_set_id = azurerm_availability_set.main.id + location = azurerm_resource_group.vmexample.location + resource_group_name = azurerm_resource_group.vmexample.name + network_interface_ids = [azurerm_network_interface.vmexample.id] + availability_set_id = azurerm_availability_set.vmexample.id vm_size = var.vm_size license_type = "Windows_Server" delete_os_disk_on_termination = true @@ -100,15 +112,15 @@ resource "azurerm_virtual_machine" "main" { storage_os_disk { name = "${var.prefix}-osdisk" - caching = "ReadWrite" + caching = "None" create_option = "FromImage" managed_disk_type = var.disk_type } os_profile { - computer_name = var.prefix + computer_name = "vmexample" admin_username = var.user_name - admin_password = var.password + admin_password = random_password.vmexample.result } os_profile_windows_config { provision_vm_agent = true @@ -119,18 +131,18 @@ resource "azurerm_virtual_machine" "main" { # DEPLOY AND ATTACH MANAGED DISK TO VIRTUAL MACHINE # --------------------------------------------------------------------------------------------------------------------- -resource "azurerm_managed_disk" "main" { +resource "azurerm_managed_disk" "vmexample" { name = "${var.prefix}-disk" - location = azurerm_resource_group.main.location - resource_group_name = azurerm_resource_group.main.name + location = azurerm_resource_group.vmexample.location + resource_group_name = azurerm_resource_group.vmexample.name storage_account_type = var.disk_type create_option = "Empty" disk_size_gb = 10 } -resource "azurerm_virtual_machine_data_disk_attachment" "main" { - managed_disk_id = azurerm_managed_disk.main.id - virtual_machine_id = azurerm_virtual_machine.main.id +resource "azurerm_virtual_machine_data_disk_attachment" "vmexample" { + managed_disk_id = azurerm_managed_disk.vmexample.id + virtual_machine_id = azurerm_virtual_machine.vmexample.id caching = "ReadWrite" lun = 10 -} \ No newline at end of file +} diff --git a/examples/azure/terraform-azure-vm-example/outputs.tf b/examples/azure/terraform-azure-vm-example/outputs.tf index c26700cd7..54d194196 100644 --- a/examples/azure/terraform-azure-vm-example/outputs.tf +++ b/examples/azure/terraform-azure-vm-example/outputs.tf @@ -29,4 +29,4 @@ output "availability_set_name" { output "managed_disk_name" { value = azurerm_managed_disk.main.name -} \ No newline at end of file +} diff --git a/examples/azure/terraform-azure-vm-example/variables.tf b/examples/azure/terraform-azure-vm-example/variables.tf index ab62b32b6..ec22167b9 100644 --- a/examples/azure/terraform-azure-vm-example/variables.tf +++ b/examples/azure/terraform-azure-vm-example/variables.tf @@ -21,7 +21,7 @@ variable "disk_type" { description = "temp" type = string - default = "Standard_LRS" + default = "StandardSSD_LRS" } variable "location" { @@ -75,5 +75,5 @@ variable "vm_image_version" { variable "vm_size" { description = "The Azure VM Size of the VM" type = string - default = "Standard_DS1_v2" + default = "Standard_B1s" } diff --git a/test/azure/common_test.go b/modules/azure/common_test.go similarity index 78% rename from test/azure/common_test.go rename to modules/azure/common_test.go index 66fa16be1..403dd01bf 100644 --- a/test/azure/common_test.go +++ b/modules/azure/common_test.go @@ -3,13 +3,12 @@ // NOTE: We use build tags to differentiate azure testing because we currently do not have azure access setup for // CircleCI. -package test +package azure import ( "os" "testing" - "github.com/gruntwork-io/terratest/modules/azure" "github.com/stretchr/testify/require" ) @@ -17,7 +16,7 @@ func TestGetTargetAzureSubscription(t *testing.T) { t.Parallel() //Check that ARM_SUBSCRIPTION_ID env variable is set, CI requires this value to run all test. - require.NotEmpty(t, os.Getenv(azure.AzureSubscriptionID), "ARM_SUBSCRIPTION_ID environment variable not set.") + require.NotEmpty(t, os.Getenv(AzureSubscriptionID), "ARM_SUBSCRIPTION_ID environment variable not set.") type args struct { subID string @@ -30,12 +29,12 @@ func TestGetTargetAzureSubscription(t *testing.T) { wantErr bool }{ {name: "subIDProvidedAsArg", args: args{subID: "test"}, want: "test", wantErr: false}, - {name: "subIDNotProvidedFallbackToEnv", args: args{subID: ""}, want: os.Getenv(azure.AzureSubscriptionID), wantErr: false}, + {name: "subIDNotProvidedFallbackToEnv", args: args{subID: ""}, want: os.Getenv(AzureSubscriptionID), wantErr: false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := azure.GetTargetAzureSubscription(tt.args.subID) + got, err := GetTargetAzureSubscription(tt.args.subID) if tt.wantErr { require.Error(t, err) @@ -65,7 +64,7 @@ func TestGetTargetAzureResourceGroupName(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := azure.GetTargetAzureResourceGroupName(tt.args.rgName) + got, err := GetTargetAzureResourceGroupName(tt.args.rgName) if tt.wantErr { require.Error(t, err) diff --git a/test/azure/compute_test.go b/modules/azure/compute_test.go similarity index 69% rename from test/azure/compute_test.go rename to modules/azure/compute_test.go index beb31b532..7b8914754 100644 --- a/test/azure/compute_test.go +++ b/modules/azure/compute_test.go @@ -3,12 +3,11 @@ // NOTE: We use build tags to differentiate azure testing because we currently do not have azure access setup for // CircleCI. -package test +package azure import ( "testing" - "github.com/gruntwork-io/terratest/modules/azure" "github.com/stretchr/testify/require" ) @@ -25,7 +24,7 @@ func TestGetVirtualMachineE(t *testing.T) { rgName := "" subID := "" - _, err := azure.GetVirtualMachineE(t, vmName, rgName, subID) + _, err := GetVirtualMachineE(t, vmName, rgName, subID) require.Error(t, err) } @@ -37,7 +36,7 @@ func TestGetVirtualMachineInstanceE(t *testing.T) { rgName := "" subID := "" - _, err := azure.GetVirtualMachineInstanceE(t, vmName, rgName, subID) + _, err := GetVirtualMachineInstanceE(t, vmName, rgName, subID) require.Error(t, err) } @@ -48,7 +47,7 @@ func TestGetResourceGroupVirtualMachinesObjectsE(t *testing.T) { rgName := "" subID := "" - _, err := azure.GetResourceGroupVirtualMachinesObjectsE(t, rgName, subID) + _, err := GetResourceGroupVirtualMachinesObjectsE(t, rgName, subID) require.Error(t, err) } @@ -59,7 +58,7 @@ func TestGetResourceGroupVirtualMachinesE(t *testing.T) { rgName := "" subID := "" - _, err := azure.GetResourceGroupVirtualMachinesE(t, rgName, subID) + _, err := GetResourceGroupVirtualMachinesE(t, rgName, subID) require.Error(t, err) } @@ -71,7 +70,7 @@ func TestGetVirtualMachineTagsE(t *testing.T) { rgName := "" subID := "" - _, err := azure.GetVirtualMachineTagsE(t, vmName, rgName, subID) + _, err := GetVirtualMachineTagsE(t, vmName, rgName, subID) require.Error(t, err) } @@ -83,7 +82,7 @@ func TestGetVirtualMachineSizeE(t *testing.T) { rgName := "" subID := "" - _, err := azure.GetVirtualMachineSizeE(t, vmName, rgName, subID) + _, err := GetVirtualMachineSizeE(t, vmName, rgName, subID) require.Error(t, err) } @@ -95,7 +94,7 @@ func TestGetVirtualMachineAdminUserE(t *testing.T) { rgName := "" subID := "" - _, err := azure.GetVirtualMachineAdminUserE(t, vmName, rgName, subID) + _, err := GetVirtualMachineAdminUserE(t, vmName, rgName, subID) require.Error(t, err) } @@ -107,7 +106,7 @@ func TestGetVirtualMachineImageE(t *testing.T) { rgName := "" subID := "" - _, err := azure.GetVirtualMachineImageE(t, vmName, rgName, subID) + _, err := GetVirtualMachineImageE(t, vmName, rgName, subID) require.Error(t, err) } @@ -119,7 +118,7 @@ func TestGetVirtualMachineAvailabilitySetIDE(t *testing.T) { rgName := "" subID := "" - _, err := azure.GetVirtualMachineAvailabilitySetIDE(t, vmName, rgName, subID) + _, err := GetVirtualMachineAvailabilitySetIDE(t, vmName, rgName, subID) require.Error(t, err) } @@ -131,7 +130,7 @@ func TestGetVirtualMachineStateE(t *testing.T) { rgName := "" subID := "" - _, err := azure.GetVirtualMachineStateE(t, vmName, rgName, subID) + _, err := GetVirtualMachineStateE(t, vmName, rgName, subID) require.Error(t, err) } @@ -143,7 +142,7 @@ func TestGetVirtualMachineOsDiskNameE(t *testing.T) { rgName := "" subID := "" - _, err := azure.GetVirtualMachineOsDiskNameE(t, vmName, rgName, subID) + _, err := GetVirtualMachineOsDiskNameE(t, vmName, rgName, subID) require.Error(t, err) } @@ -155,7 +154,7 @@ func TestGetVirtualMachineManagedDiskCountE(t *testing.T) { rgName := "" subID := "" - _, err := azure.GetVirtualMachineManagedDiskCountE(t, vmName, rgName, subID) + _, err := GetVirtualMachineManagedDiskCountE(t, vmName, rgName, subID) require.Error(t, err) } @@ -167,7 +166,7 @@ func TestGetVirtualMachineManagedDisksE(t *testing.T) { rgName := "" subID := "" - _, err := azure.GetVirtualMachineManagedDisksE(t, vmName, rgName, subID) + _, err := GetVirtualMachineManagedDisksE(t, vmName, rgName, subID) require.Error(t, err) } @@ -179,7 +178,7 @@ func TestGetVirtualMachineNicCountE(t *testing.T) { rgName := "" subID := "" - _, err := azure.GetVirtualMachineNicCountE(t, vmName, rgName, subID) + _, err := GetVirtualMachineNicCountE(t, vmName, rgName, subID) require.Error(t, err) } @@ -191,7 +190,7 @@ func TestGetVirtualMachineNicsE(t *testing.T) { rgName := "" subID := "" - _, err := azure.GetVirtualMachineNicsE(t, vmName, rgName, subID) + _, err := GetVirtualMachineNicsE(t, vmName, rgName, subID) require.Error(t, err) } @@ -203,7 +202,7 @@ func TestVirtualMachineExistsE(t *testing.T) { rgName := "" subID := "" - _, err := azure.VirtualMachineExistsE(t, vmName, rgName, subID) + _, err := VirtualMachineExistsE(t, vmName, rgName, subID) require.Error(t, err) } diff --git a/modules/azure/errors.go b/modules/azure/errors.go index 148957d16..f8ea4a896 100644 --- a/modules/azure/errors.go +++ b/modules/azure/errors.go @@ -15,3 +15,18 @@ type ResourceGroupNameNotFound struct{} func (err ResourceGroupNameNotFound) Error() string { return fmt.Sprintf("Could not find an Azure Resource Group name in expected environment variable %s and one was not provided for this test.", AzureResGroupName) } + +// FailedToParseError is returned when an object cannot be parsed +type FailedToParseError struct { + objectType string + objectID string +} + +func (err FailedToParseError) Error() string { + return fmt.Sprintf("Failed to parse %s with ID %s", err.objectType, err.objectID) +} + +// NewFailedToParseError creates a new not found error when an expected object is not found in the search space +func NewFailedToParseError(objectType string, objectID string) FailedToParseError { + return FailedToParseError{objectType, objectID} +} diff --git a/modules/azure/networkinterface.go b/modules/azure/networkinterface.go index 74184f6c2..a17c576c9 100644 --- a/modules/azure/networkinterface.go +++ b/modules/azure/networkinterface.go @@ -44,12 +44,13 @@ func GetNetworkInterfacePublicIPsE(t testing.TestingT, nicName string, resGroupN return publicIPs, err } - // Get the Public IPs from each configuration + // Get the Public IPs from each configuration available for _, IPConfiguration := range *nic.IPConfigurations { if ipConfigHasPublicIP(&IPConfiguration) { + // Get the ID from the long string NIC representation publicAddressID := GetNameFromResourceID(*IPConfiguration.PublicIPAddress.ID) - // Get the Public Ip from the Public Address resource + // Get the Public Ip from the Public Address client publicIP := GetPublicAddressIP(t, publicAddressID, resGroupName, subscriptionID) publicIPs = append(publicIPs, publicIP) } @@ -60,6 +61,7 @@ func GetNetworkInterfacePublicIPsE(t testing.TestingT, nicName string, resGroupN // ipConfigHasPublicIP returns true if an IP Configuration has a Public IP Address // This helper method was created since a config without a public address causes a nil pointer panic +// and the string representation is searched for the publicIPAddress text to identify it's presence. func ipConfigHasPublicIP(ipConfig *network.InterfaceIPConfiguration) bool { var byteIPConfig []byte diff --git a/modules/azure/networkinterface_test.go b/modules/azure/networkinterface_test.go new file mode 100644 index 000000000..25c704b6a --- /dev/null +++ b/modules/azure/networkinterface_test.go @@ -0,0 +1,65 @@ +// +build azure azureslim,network + +// NOTE: We use build tags to differentiate azure testing because we currently do not have azure access setup for +// CircleCI. + +package azure + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +/* +The below tests are currently stubbed out, with the expectation that they will throw errors. +If/when methods can be mocked or Create/Delete APIs are added, these tests can be extended. +*/ + +func TestGetNetworkInterfaceE(t *testing.T) { + t.Parallel() + + nicName := "" + rgName := "" + subID := "" + + _, err := GetNetworkInterfaceE(t, nicName, rgName, subID) + + require.Error(t, err) +} + +func TestGetNetworkInterfacePrivateIPsE(t *testing.T) { + t.Parallel() + + nicName := "" + rgName := "" + subID := "" + + _, err := GetNetworkInterfacePrivateIPsE(t, nicName, rgName, subID) + + require.Error(t, err) +} + +func TestGetNetworkInterfacePublicIPsE(t *testing.T) { + t.Parallel() + + nicName := "" + rgName := "" + subID := "" + + _, err := GetNetworkInterfacePublicIPsE(t, nicName, rgName, subID) + + require.Error(t, err) +} + +func TestNetworkInterfaceExistsE(t *testing.T) { + t.Parallel() + + nicName := "" + rgName := "" + subID := "" + + _, err := NetworkInterfaceExistsE(t, nicName, rgName, subID) + + require.Error(t, err) +} diff --git a/modules/azure/publicaddress.go b/modules/azure/publicaddress.go index 647aab0e4..98e720492 100644 --- a/modules/azure/publicaddress.go +++ b/modules/azure/publicaddress.go @@ -34,12 +34,6 @@ func GetPublicAddressIP(t testing.TestingT, publicAddressName string, resGroupNa // GetPublicAddressIPE gets the IP of a Public IP Address with error func GetPublicAddressIPE(t testing.TestingT, publicAddressName string, resGroupName string, subscriptionID string) (string, error) { - // Validate Azure subscription ID - subscriptionID, err := getTargetAzureSubscription(subscriptionID) - if err != nil { - return "", err - } - // Create a NIC client pip, err := GetPublicIPAddressE(t, publicAddressName, resGroupName, subscriptionID) if err != nil { diff --git a/modules/azure/publicaddress_test.go b/modules/azure/publicaddress_test.go new file mode 100644 index 000000000..265844e0d --- /dev/null +++ b/modules/azure/publicaddress_test.go @@ -0,0 +1,65 @@ +// +build azure azureslim,network + +// NOTE: We use build tags to differentiate azure testing because we currently do not have azure access setup for +// CircleCI. + +package azure + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +/* +The below tests are currently stubbed out, with the expectation that they will throw errors. +If/when methods can be mocked or Create/Delete APIs are added, these tests can be extended. +*/ + +func TestGetPublicIPAddressE(t *testing.T) { + t.Parallel() + + paName := "" + rgName := "" + subID := "" + + _, err := GetPublicIPAddressE(t, paName, rgName, subID) + + require.Error(t, err) +} + +func TestCheckPublicDNSNameAvailabilityE(t *testing.T) { + t.Parallel() + + location := "" + domain := "" + subID := "" + + _, err := CheckPublicDNSNameAvailabilityE(t, location, domain, subID) + + require.Error(t, err) +} + +func TestGetPublicAddressIPE(t *testing.T) { + t.Parallel() + + paName := "" + rgName := "" + subID := "" + + _, err := GetPublicAddressIPE(t, paName, rgName, subID) + + require.Error(t, err) +} + +func TestPublicAddressExistsE(t *testing.T) { + t.Parallel() + + paName := "" + rgName := "" + subID := "" + + _, err := PublicAddressExistsE(t, paName, rgName, subID) + + require.Error(t, err) +} diff --git a/test/azure/region_test.go b/modules/azure/region_test.go similarity index 88% rename from test/azure/region_test.go rename to modules/azure/region_test.go index ca20160b5..eeb45807d 100644 --- a/test/azure/region_test.go +++ b/modules/azure/region_test.go @@ -3,7 +3,7 @@ // NOTE: We use build tags to differentiate azure testing because we currently do not have azure access setup for // CircleCI. -package test +package azure import ( "testing" @@ -26,8 +26,8 @@ func TestGetRandomRegionExcludesForbiddenRegions(t *testing.T) { approvedRegions := []string{"canadacentral", "eastus", "eastus2", "westus", "westus2", "westeurope", "northeurope", "uksouth", "southeastasia", "eastasia", "japaneast", "australiacentral"} forbiddenRegions := []string{"westus2", "japaneast"} - for i := 0; i < 50; i++ { - randomRegion := azure.GetRandomRegion(t, approvedRegions, forbiddenRegions, "") + for i := 0; i < 48; i++ { + randomRegion := GetRandomRegion(t, approvedRegions, forbiddenRegions, "") assert.NotContains(t, forbiddenRegions, randomRegion) } } @@ -35,7 +35,7 @@ func TestGetRandomRegionExcludesForbiddenRegions(t *testing.T) { func TestGetAllAzureRegions(t *testing.T) { t.Parallel() - regions := azure.GetAllAzureRegions(t, "") + regions := GetAllAzureRegions(t, "") // The typical subscription had access to 30+ live regions as of July 2019: https://azure.microsoft.com/en-us/global-infrastructure/regions/ assert.True(t, len(regions) >= 30, "Number of regions: %d", len(regions)) diff --git a/modules/azure/resourceid.go b/modules/azure/resourceid.go index 815313f60..8d13ef51e 100644 --- a/modules/azure/resourceid.go +++ b/modules/azure/resourceid.go @@ -1,36 +1,12 @@ package azure -import ( - "errors" - "strings" -) +import "github.com/gruntwork-io/terratest/modules/collections" // GetNameFromResourceID gets the Name from an Azure Resource ID func GetNameFromResourceID(resourceID string) string { - lastValue, err := GetSliceLastValueE(resourceID, "/") + id, err := collections.GetSliceLastValueE(resourceID, "/") if err != nil { return "" } - return lastValue -} - -// GetSliceLastValueE will take a source string and returns the last value when split by the seperaror char -func GetSliceLastValueE(source string, seperator string) (string, error) { - if !(len(source) == 0 || len(seperator) == 0 || !strings.Contains(source, seperator)) { - tmp := strings.Split(source, seperator) - return tmp[len(tmp)-1], nil - } - return "", errors.New("invalid input or no slice available") -} - -// GetSliceIndexValueE will take a source string and returns the value at the given index when split by the seperaror char -func GetSliceIndexValueE(source string, seperator string, index int) (string, error) { - if !(len(source) == 0 || len(seperator) == 0 || !strings.Contains(source, seperator) || index < 0) { - tmp := strings.Split(source, seperator) - if !(len(tmp) >= index) { - return "", errors.New("index out of slice range") - } - return tmp[index], nil - } - return "", errors.New("invalid input or no slice available") + return id } diff --git a/modules/azure/resourceid_test.go b/modules/azure/resourceid_test.go new file mode 100644 index 000000000..dac80ed2f --- /dev/null +++ b/modules/azure/resourceid_test.go @@ -0,0 +1,29 @@ +// +build azure + +// NOTE: We use build tags to differentiate azure testing because we currently do not have azure access setup for +// CircleCI. + +package azure + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetNameFromResourceID(t *testing.T) { + t.Parallel() + + // set slice variables + sliceSource := "this/is/a/long/slash/separated/string/ResourceID" + sliceResult := "ResourceID" + sliceNotFound := "noresourcepresent" + + // verify success + resultSuccess := GetNameFromResourceID(sliceSource) + assert.Equal(t, sliceResult, resultSuccess) + + // verify error when seperator not found + resultBadSeperator := GetNameFromResourceID(sliceNotFound) + assert.Equal(t, "", resultBadSeperator) +} diff --git a/modules/azure/virtualnetwork.go b/modules/azure/virtualnetwork.go index 366dea241..1cb149bc9 100644 --- a/modules/azure/virtualnetwork.go +++ b/modules/azure/virtualnetwork.go @@ -2,7 +2,6 @@ package azure import ( "context" - "fmt" "net" "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-09-01/network" @@ -53,43 +52,28 @@ func CheckSubnetContainsIP(t testing.TestingT, IP string, subnetName string, vne // CheckSubnetContainsIPE checks if the Private IP is contined in the Subnet Address Range func CheckSubnetContainsIPE(t testing.TestingT, ipAddress string, subnetName string, vnetName string, resGroupName string, subscriptionID string) (bool, error) { - subnetPrefix, err := GetSubnetIPRangeE(t, subnetName, vnetName, resGroupName, subscriptionID) - if err != nil { - return false, err - } - // Convert the IP to a net IP address ip := net.ParseIP(ipAddress) - if ip == nil { - return false, fmt.Errorf("Failed to parse IP address %s", ipAddress) + return false, NewFailedToParseError("IP Address", ipAddress) } - // Check if the IP is in the Subnet Range - _, ipNet, err := net.ParseCIDR(subnetPrefix) + // Get Subnet + subnet, err := GetSubnetE(t, subnetName, vnetName, resGroupName, subscriptionID) if err != nil { - return false, fmt.Errorf("Failed to parse subnet range %s", subnetPrefix) + return false, err } - return ipNet.Contains(ip), nil -} - -// GetSubnetIPRange gets the IPv4 Range of the specified Subnet -func GetSubnetIPRange(t testing.TestingT, subnetName string, vnetName string, resGroupName string, subscriptionID string) string { - vnetDNSIPs, err := GetSubnetIPRangeE(t, subnetName, vnetName, resGroupName, subscriptionID) - require.NoError(t, err) - return vnetDNSIPs -} + // Get Subnet IP range, this required field is never nil therefore no exception handling required + subnetPrefix := *subnet.AddressPrefix -// GetSubnetIPRangeE gets the IPv4 Range of the specified Subnet -func GetSubnetIPRangeE(t testing.TestingT, subnetName string, vnetName string, resGroupName string, subscriptionID string) (string, error) { - // Get Subnet - subnet, err := GetSubnetE(t, subnetName, vnetName, resGroupName, subscriptionID) + // Check if the IP is in the Subnet Range using the net package + _, ipNet, err := net.ParseCIDR(subnetPrefix) if err != nil { - return "", err + return false, NewFailedToParseError("Subnet Range", subnetPrefix) } - return *subnet.AddressPrefix, nil + return ipNet.Contains(ip), nil } // GetVirtualNetworkSubnets gets all Subnet names and their respective address prefixes in the specified Virtual Network @@ -100,18 +84,20 @@ func GetVirtualNetworkSubnets(t testing.TestingT, vnetName string, resGroupName } // GetVirtualNetworkSubnetsE gets all Subnet names and their respective address prefixes in the specified Virtual Network +// Returning both the name and prefix together helps reduce calls for these commonly accessed properties func GetVirtualNetworkSubnetsE(t testing.TestingT, vnetName string, resGroupName string, subscriptionID string) (map[string]string, error) { + subNetDetails := map[string]string{} + client, err := GetSubnetClientE(subscriptionID) if err != nil { - return nil, err + return subNetDetails, err } subnets, err := client.List(context.Background(), resGroupName, vnetName) if err != nil { - return nil, err + return subNetDetails, err } - subNetDetails := make(map[string]string) for _, v := range subnets.Values() { subnetName := v.Name subNetAddressPrefix := v.AddressPrefix diff --git a/modules/azure/virtualnetwork_test.go b/modules/azure/virtualnetwork_test.go new file mode 100644 index 000000000..2c22d4071 --- /dev/null +++ b/modules/azure/virtualnetwork_test.go @@ -0,0 +1,105 @@ +// +build azure azureslim,network + +// NOTE: We use build tags to differentiate azure testing because we currently do not have azure access setup for +// CircleCI. + +package azure + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +/* +The below tests are currently stubbed out, with the expectation that they will throw errors. +If/when methods can be mocked or Create/Delete APIs are added, these tests can be extended. +*/ + +func TestGetVirtualNetworkE(t *testing.T) { + t.Parallel() + + vnetName := "" + rgName := "" + subID := "" + + _, err := GetVirtualNetworkE(t, vnetName, rgName, subID) + + require.Error(t, err) +} + +func TestGetSubnetE(t *testing.T) { + t.Parallel() + + subnetName := "" + vnetName := "" + rgName := "" + subID := "" + + _, err := GetSubnetE(t, subnetName, vnetName, rgName, subID) + + require.Error(t, err) +} + +func TestGetVirtualNetworkDNSServerIPsE(t *testing.T) { + t.Parallel() + + vnetName := "" + rgName := "" + subID := "" + + _, err := GetVirtualNetworkDNSServerIPsE(t, vnetName, rgName, subID) + + require.Error(t, err) +} + +func TestGetVirtualNetworkSubnetsE(t *testing.T) { + t.Parallel() + + vnetName := "" + rgName := "" + subID := "" + + _, err := GetVirtualNetworkSubnetsE(t, vnetName, rgName, subID) + + require.Error(t, err) +} + +func TestCheckSubnetContainsIPE(t *testing.T) { + t.Parallel() + + ipAddress := "" + subnetName := "" + vnetName := "" + rgName := "" + subID := "" + + _, err := CheckSubnetContainsIPE(t, ipAddress, subnetName, vnetName, rgName, subID) + + require.Error(t, err) +} + +func TestSubnetExistsE(t *testing.T) { + t.Parallel() + + subnetName := "" + vnetName := "" + rgName := "" + subID := "" + + _, err := SubnetExistsE(t, subnetName, vnetName, rgName, subID) + + require.Error(t, err) +} + +func TestVirtualNetworkExistsE(t *testing.T) { + t.Parallel() + + vnetName := "" + rgName := "" + subID := "" + + _, err := VirtualNetworkExistsE(t, vnetName, rgName, subID) + + require.Error(t, err) +} diff --git a/modules/collections/errors.go b/modules/collections/errors.go new file mode 100644 index 000000000..56afccea2 --- /dev/null +++ b/modules/collections/errors.go @@ -0,0 +1,17 @@ +package collections + +import "fmt" + +// SliceValueNotFoundError is returned when a provided values file input is not found on the host path. +type SliceValueNotFoundError struct { + sourceString string +} + +func (err SliceValueNotFoundError) Error() string { + return fmt.Sprintf("Could not resolve requested slice value from string %s", err.sourceString) +} + +// NewSliceValueNotFoundError creates a new slice found error +func NewSliceValueNotFoundError(sourceString string) SliceValueNotFoundError { + return SliceValueNotFoundError{sourceString} +} diff --git a/modules/collections/stringslicevalue.go b/modules/collections/stringslicevalue.go new file mode 100644 index 000000000..2d93212c0 --- /dev/null +++ b/modules/collections/stringslicevalue.go @@ -0,0 +1,26 @@ +package collections + +import ( + "strings" +) + +// GetSliceLastValueE will take a source string and returns the last value when split by the seperaror char +func GetSliceLastValueE(source string, seperator string) (string, error) { + if len(source) > 0 && len(seperator) > 0 && strings.Contains(source, seperator) { + tmp := strings.Split(source, seperator) + return tmp[len(tmp)-1], nil + } + return "", NewSliceValueNotFoundError(source) +} + +// GetSliceIndexValueE will take a source string and returns the value at the given index when split by the seperaror char +func GetSliceIndexValueE(source string, seperator string, index int) (string, error) { + if len(source) > 0 && len(seperator) > 0 && strings.Contains(source, seperator) && index >= 0 { + tmp := strings.Split(source, seperator) + if index > len(tmp) { + return "", NewSliceValueNotFoundError(source) + } + return tmp[index], nil + } + return "", NewSliceValueNotFoundError(source) +} diff --git a/modules/collections/stringslicevalue_test.go b/modules/collections/stringslicevalue_test.go new file mode 100644 index 000000000..06448b360 --- /dev/null +++ b/modules/collections/stringslicevalue_test.go @@ -0,0 +1,77 @@ +package collections + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGetSliceLastValue(t *testing.T) { + t.Parallel() + + var testCases = []struct { + testName string + sliceSource string + sliceSeperator string + expectedReturn string + expectedError bool + }{ + {"longSlice", "this/is/a/long/slash/separated/string/success", "/", "success", false}, + {"shortendSlice", "this/is/a/long/slash/separated", "/", "separated", false}, + {"dashSlice", "this-is-a-long-dash-separated-string-success", "-", "success", false}, + {"seperatorNotPresent", "this-is-a-long-dash-separated-string-success", "/", "", true}, + {"sourceNoSeperator", "noslicepresent", "/", "", true}, + {"emptyStrings", "", "", "", true}, + } + + for _, tc := range testCases { + testFor := tc //necessary range capture + + t.Run(testFor.testName, func(t *testing.T) { + actualReturn, err := GetSliceLastValueE(testFor.sliceSource, testFor.sliceSeperator) + switch testFor.expectedError { + case true: + require.Error(t, err) + case false: + require.NoError(t, err) + } + assert.Equal(t, testFor.expectedReturn, actualReturn) + }) + } +} + +func TestGetSliceIndexValue(t *testing.T) { + t.Parallel() + + var testCases = []struct { + sliceIndex int + expectedReturn string + expectedError bool + }{ + {-1, "", true}, + {0, "this", false}, + {4, "slash", false}, + {7, "success", false}, + {10, "", true}, + } + + sliceSource := "this/is/a/long/slash/separated/string/success" + sliceSeperator := "/" + + for _, tc := range testCases { + testFor := tc //necessary range capture + + t.Run(fmt.Sprintf("Index_%v", testFor.sliceIndex), func(t *testing.T) { + actualReturn, err := GetSliceIndexValueE(sliceSource, sliceSeperator, testFor.sliceIndex) + switch testFor.expectedError { + case true: + require.Error(t, err) + case false: + require.NoError(t, err) + } + assert.Equal(t, testFor.expectedReturn, actualReturn) + }) + } +} From 287bf9377db80a3c5642cd862e615eb5464f9e10 Mon Sep 17 00:00:00 2001 From: Dave Seepersad <“davesee@microsoft.com”> Date: Wed, 23 Sep 2020 11:00:56 -0400 Subject: [PATCH 06/56] Updated method comments for test error --- modules/azure/availabilityset_test.go | 90 +++++++++++++++++++++++++++ modules/azure/compute.go | 86 ++++++++++++++----------- modules/azure/networkinterface.go | 21 ++++--- modules/azure/publicaddress.go | 18 +++--- modules/azure/region_test.go | 3 +- modules/azure/virtualnetwork.go | 37 ++++++----- 6 files changed, 186 insertions(+), 69 deletions(-) create mode 100644 modules/azure/availabilityset_test.go diff --git a/modules/azure/availabilityset_test.go b/modules/azure/availabilityset_test.go new file mode 100644 index 000000000..314225419 --- /dev/null +++ b/modules/azure/availabilityset_test.go @@ -0,0 +1,90 @@ +// +build azure + +// NOTE: We use build tags to differentiate azure testing because we currently do not have azure access setup for +// CircleCI. + +package azure + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +/* +The below tests are currently stubbed out, with the expectation that they will throw errors. +If/when methods to create and delete network resources are added, these tests can be extended. +*/ + +func TestGetAvailabilitySetClientE(t *testing.T) { + t.Parallel() + + subscriptionID := "" + + client, err := GetAvailabilitySetClientE(subscriptionID) + + require.NoError(t, err) + assert.NotEmpty(t, *client) +} + +func TestGetAvailabilitySetE(t *testing.T) { + t.Parallel() + + avsName := "" + rgName := "" + subscriptionID := "" + + _, err := GetAvailabilitySetE(t, avsName, rgName, subscriptionID) + + require.Error(t, err) +} + +func TestCheckAvailabilitySetContainsVME(t *testing.T) { + t.Parallel() + + vmName := "" + avsName := "" + rgName := "" + subscriptionID := "" + + _, err := CheckAvailabilitySetContainsVME(t, vmName, avsName, rgName, subscriptionID) + + require.Error(t, err) +} + +func TestGetAvailabilitySetVMNamesInCapsE(t *testing.T) { + t.Parallel() + + avsName := "" + rgName := "" + subscriptionID := "" + + _, err := GetAvailabilitySetVMNamesInCapsE(t, avsName, rgName, subscriptionID) + + require.Error(t, err) +} + +func TestGetAvailabilitySetFaultDomainCountE(t *testing.T) { + t.Parallel() + + avsName := "" + rgName := "" + subscriptionID := "" + + _, err := GetAvailabilitySetFaultDomainCountE(t, avsName, rgName, subscriptionID) + + require.Error(t, err) +} + +func TestAvailabilitySetExistsE(t *testing.T) { + t.Parallel() + + avsName := "" + rgName := "" + subscriptionID := "" + + _, err := AvailabilitySetExistsE(t, avsName, rgName, subscriptionID) + + require.Error(t, err) +} diff --git a/modules/azure/compute.go b/modules/azure/compute.go index cb5c4eaac..559e722fb 100644 --- a/modules/azure/compute.go +++ b/modules/azure/compute.go @@ -9,14 +9,15 @@ import ( "github.com/stretchr/testify/require" ) -// VirtualMachineExists indicates whether the speficied Azure Virtual Machine exists +// VirtualMachineExists indicates whether the speficied Azure Virtual Machine exists. +// This function would fail the test if there is an error. func VirtualMachineExists(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) bool { exists, err := VirtualMachineExistsE(t, vmName, resGroupName, subscriptionID) require.NoError(t, err) return exists } -// VirtualMachineExistsE indicates whether the speficied Azure Virtual Machine exists +// VirtualMachineExistsE indicates whether the speficied Azure Virtual Machine exists. func VirtualMachineExistsE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (bool, error) { // Get VM Object _, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) @@ -26,7 +27,8 @@ func VirtualMachineExistsE(t testing.TestingT, vmName string, resGroupName strin return true, nil } -// GetVirtualMachineNics gets a list of Network Interface names for a speficied Azure Virtual Machine +// GetVirtualMachineNics gets a list of Network Interface names for a speficied Azure Virtual Machine. +// This function would fail the test if there is an error. func GetVirtualMachineNics(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) []string { nicList, err := GetVirtualMachineNicsE(t, vmName, resGroupName, subscriptionID) require.NoError(t, err) @@ -34,7 +36,7 @@ func GetVirtualMachineNics(t testing.TestingT, vmName string, resGroupName strin return nicList } -// GetVirtualMachineNicsE gets a list of Network Interface names for a specified Azure Virtual Machine +// GetVirtualMachineNicsE gets a list of Network Interface names for a specified Azure Virtual Machine. func GetVirtualMachineNicsE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) ([]string, error) { nics := []string{} @@ -57,7 +59,8 @@ func GetVirtualMachineNicsE(t testing.TestingT, vmName string, resGroupName stri return nics, nil } -// GetVirtualMachineNicCount gets the Network Interface count of the specified Azure Virtual Machine +// GetVirtualMachineNicCount gets the Network Interface count of the specified Azure Virtual Machine. +// This function would fail the test if there is an error. func GetVirtualMachineNicCount(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) int { nicCount, err := GetVirtualMachineNicCountE(t, vmName, resGroupName, subscriptionID) require.NoError(t, err) @@ -65,7 +68,7 @@ func GetVirtualMachineNicCount(t testing.TestingT, vmName string, resGroupName s return nicCount } -// GetVirtualMachineNicCountE gets the Network Interface count of the specified Azure Virtual Machine +// GetVirtualMachineNicCountE gets the Network Interface count of the specified Azure Virtual Machine. func GetVirtualMachineNicCountE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (int, error) { nicCount := 0 @@ -78,7 +81,8 @@ func GetVirtualMachineNicCountE(t testing.TestingT, vmName string, resGroupName return len(*vm.NetworkProfile.NetworkInterfaces), nil } -// GetVirtualMachineManagedDisks gets the list of Managed Disk names of the specified Azure Virtual Machine +// GetVirtualMachineManagedDisks gets the list of Managed Disk names of the specified Azure Virtual Machine. +// This function would fail the test if there is an error. func GetVirtualMachineManagedDisks(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) []string { diskNames, err := GetVirtualMachineManagedDisksE(t, vmName, resGroupName, subscriptionID) require.NoError(t, err) @@ -86,7 +90,7 @@ func GetVirtualMachineManagedDisks(t testing.TestingT, vmName string, resGroupNa return diskNames } -// GetVirtualMachineManagedDisksE gets the list of Managed Disk names of the specified Azure Virtual Machine +// GetVirtualMachineManagedDisksE gets the list of Managed Disk names of the specified Azure Virtual Machine. func GetVirtualMachineManagedDisksE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) ([]string, error) { diskNames := []string{} @@ -105,7 +109,8 @@ func GetVirtualMachineManagedDisksE(t testing.TestingT, vmName string, resGroupN return diskNames, nil } -// GetVirtualMachineManagedDiskCount gets the Managed Disk count of the specified Azure Virtual Machine +// GetVirtualMachineManagedDiskCount gets the Managed Disk count of the specified Azure Virtual Machine. +// This function would fail the test if there is an error. func GetVirtualMachineManagedDiskCount(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) int { mngDiskCount, err := GetVirtualMachineManagedDiskCountE(t, vmName, resGroupName, subscriptionID) require.NoError(t, err) @@ -113,7 +118,7 @@ func GetVirtualMachineManagedDiskCount(t testing.TestingT, vmName string, resGro return mngDiskCount } -// GetVirtualMachineManagedDiskCountE gets the Managed Disk count of the specified Azure Virtual Machine +// GetVirtualMachineManagedDiskCountE gets the Managed Disk count of the specified Azure Virtual Machine. func GetVirtualMachineManagedDiskCountE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (int, error) { mngDiskCount := -1 @@ -126,7 +131,8 @@ func GetVirtualMachineManagedDiskCountE(t testing.TestingT, vmName string, resGr return len(*vm.StorageProfile.DataDisks), nil } -// GetVirtualMachineOsDiskName gets the OS Disk name of the specified Azure Virtual Machine +// GetVirtualMachineOsDiskName gets the OS Disk name of the specified Azure Virtual Machine. +// This function would fail the test if there is an error. func GetVirtualMachineOsDiskName(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) string { osDiskName, err := GetVirtualMachineOsDiskNameE(t, vmName, resGroupName, subscriptionID) require.NoError(t, err) @@ -134,7 +140,7 @@ func GetVirtualMachineOsDiskName(t testing.TestingT, vmName string, resGroupName return osDiskName } -// GetVirtualMachineOsDiskNameE gets the OS Disk name of the specified Azure Virtual Machine +// GetVirtualMachineOsDiskNameE gets the OS Disk name of the specified Azure Virtual Machine. func GetVirtualMachineOsDiskNameE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (string, error) { // Get VM Object vm, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) @@ -145,14 +151,15 @@ func GetVirtualMachineOsDiskNameE(t testing.TestingT, vmName string, resGroupNam return *vm.StorageProfile.OsDisk.Name, nil } -// GetVirtualMachineState gets the State of the specified Azure Virtual Machine +// GetVirtualMachineState gets the State of the specified Azure Virtual Machine. +// This function would fail the test if there is an error. func GetVirtualMachineState(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) string { vmState, err := GetVirtualMachineStateE(t, vmName, resGroupName, subscriptionID) require.NoError(t, err) return vmState } -// GetVirtualMachineStateE gets the State of the specified Azure Virtual Machine +// GetVirtualMachineStateE gets the State of the specified Azure Virtual Machine. func GetVirtualMachineStateE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (string, error) { // Get VM Object vm, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) @@ -163,7 +170,8 @@ func GetVirtualMachineStateE(t testing.TestingT, vmName string, resGroupName str return vm.Status, nil } -// GetVirtualMachineAvailabilitySetID gets the Availability Set ID of the specified Azure Virtual Machine +// GetVirtualMachineAvailabilitySetID gets the Availability Set ID of the specified Azure Virtual Machine. +// This function would fail the test if there is an error. func GetVirtualMachineAvailabilitySetID(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) string { adminUser, err := GetVirtualMachineAvailabilitySetIDE(t, vmName, resGroupName, subscriptionID) require.NoError(t, err) @@ -171,7 +179,7 @@ func GetVirtualMachineAvailabilitySetID(t testing.TestingT, vmName string, resGr return adminUser } -// GetVirtualMachineAvailabilitySetIDE gets the Availability Set ID of the specified Azure Virtual Machine +// GetVirtualMachineAvailabilitySetIDE gets the Availability Set ID of the specified Azure Virtual Machine. func GetVirtualMachineAvailabilitySetIDE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (string, error) { // Get VM Object vm, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) @@ -182,7 +190,7 @@ func GetVirtualMachineAvailabilitySetIDE(t testing.TestingT, vmName string, resG return GetNameFromResourceID(*vm.AvailabilitySet.ID), nil } -// VMImage represents the storage image for the specified Azure Virtual Machine +// VMImage represents the storage image for the specified Azure Virtual Machine. type VMImage struct { Publisher string Offer string @@ -190,7 +198,8 @@ type VMImage struct { Version string } -// GetVirtualMachineImage gets the Image of the specified Azure Virtual Machine +// GetVirtualMachineImage gets the Image of the specified Azure Virtual Machine. +// This function would fail the test if there is an error. func GetVirtualMachineImage(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) VMImage { adminUser, err := GetVirtualMachineImageE(t, vmName, resGroupName, subscriptionID) require.NoError(t, err) @@ -198,7 +207,7 @@ func GetVirtualMachineImage(t testing.TestingT, vmName string, resGroupName stri return adminUser } -// GetVirtualMachineImageE gets the Image of the specified Azure Virtual Machine +// GetVirtualMachineImageE gets the Image of the specified Azure Virtual Machine. func GetVirtualMachineImageE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (VMImage, error) { vmImage := VMImage{} @@ -217,14 +226,15 @@ func GetVirtualMachineImageE(t testing.TestingT, vmName string, resGroupName str return vmImage, nil } -// GetVirtualMachineAdminUser gets the Admin Username of the specified Azure Virtual Machine +// GetVirtualMachineAdminUser gets the Admin Username of the specified Azure Virtual Machine. +// This function would fail the test if there is an error. func GetVirtualMachineAdminUser(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) string { adminUser, err := GetVirtualMachineAdminUserE(t, vmName, resGroupName, subscriptionID) require.NoError(t, err) return adminUser } -// GetVirtualMachineAdminUserE gets the Admin Username of the specified Azure Virtual Machine +// GetVirtualMachineAdminUserE gets the Admin Username of the specified Azure Virtual Machine. func GetVirtualMachineAdminUserE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (string, error) { // Get VM Object vm, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) @@ -235,7 +245,8 @@ func GetVirtualMachineAdminUserE(t testing.TestingT, vmName string, resGroupName return string(*vm.OsProfile.AdminUsername), nil } -// GetVirtualMachineSize gets the Size Type of the specified Azure Virtual Machine +// GetVirtualMachineSize gets the Size Type of the specified Azure Virtual Machine. +// This function would fail the test if there is an error. func GetVirtualMachineSize(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) compute.VirtualMachineSizeTypes { size, err := GetVirtualMachineSizeE(t, vmName, resGroupName, subscriptionID) require.NoError(t, err) @@ -243,7 +254,7 @@ func GetVirtualMachineSize(t testing.TestingT, vmName string, resGroupName strin return size } -// GetVirtualMachineSizeE gets the Size Type of the specified Azure Virtual Machine +// GetVirtualMachineSizeE gets the Size Type of the specified Azure Virtual Machine. func GetVirtualMachineSizeE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (compute.VirtualMachineSizeTypes, error) { // Get VM Object vm, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) @@ -254,7 +265,8 @@ func GetVirtualMachineSizeE(t testing.TestingT, vmName string, resGroupName stri return vm.VirtualMachineProperties.HardwareProfile.VMSize, nil } -// GetVirtualMachineTags gets the Tags of the specified Virtual Machine as a map +// GetVirtualMachineTags gets the Tags of the specified Virtual Machine as a map. +// This function would fail the test if there is an error. func GetVirtualMachineTags(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) map[string]string { tags, err := GetVirtualMachineTagsE(t, vmName, resGroupName, subscriptionID) require.NoError(t, err) @@ -262,7 +274,7 @@ func GetVirtualMachineTags(t testing.TestingT, vmName string, resGroupName strin return tags } -// GetVirtualMachineTagsE gets the Tags of the specified Virtual Machine as a map +// GetVirtualMachineTagsE gets the Tags of the specified Virtual Machine as a map. func GetVirtualMachineTagsE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (map[string]string, error) { // Setup a blank map to populate and return tags := make(map[string]string) @@ -285,14 +297,15 @@ func GetVirtualMachineTagsE(t testing.TestingT, vmName string, resGroupName stri // Get multiple Virtual Machines from a Resource Group // ***************************************************** // -// GetResourceGroupVirtualMachines gets a list of all Virtual Machine names in the specified Resource Group +// GetResourceGroupVirtualMachines gets a list of all Virtual Machine names in the specified Resource Group. +// This function would fail the test if there is an error. func GetResourceGroupVirtualMachines(t testing.TestingT, resGroupName string, subscriptionID string) []string { vms, err := GetResourceGroupVirtualMachinesE(t, resGroupName, subscriptionID) require.NoError(t, err) return vms } -// GetResourceGroupVirtualMachinesE gets a list of all Virtual Machine names in the specified Resource Group +// GetResourceGroupVirtualMachinesE gets a list of all Virtual Machine names in the specified Resource Group. func GetResourceGroupVirtualMachinesE(t testing.TestingT, resourceGroupName string, subscriptionID string) ([]string, error) { vmDetails := []string{} @@ -312,14 +325,15 @@ func GetResourceGroupVirtualMachinesE(t testing.TestingT, resourceGroupName stri return vmDetails, nil } -// GetResourceGroupVirtualMachinesObjects gets all Virtual Machine objects in the specified Resource Group +// GetResourceGroupVirtualMachinesObjects gets all Virtual Machine objects in the specified Resource Group. +// This function would fail the test if there is an error. func GetResourceGroupVirtualMachinesObjects(t testing.TestingT, resGroupName string, subscriptionID string) *map[string]compute.VirtualMachineProperties { vms, err := GetResourceGroupVirtualMachinesObjectsE(t, resGroupName, subscriptionID) require.NoError(t, err) return vms } -// GetResourceGroupVirtualMachinesObjectsE gets all Virtual Machine objects in the specified Resource Group +// GetResourceGroupVirtualMachinesObjectsE gets all Virtual Machine objects in the specified Resource Group. func GetResourceGroupVirtualMachinesObjectsE(t testing.TestingT, resourceGroupName string, subscriptionID string) (*map[string]compute.VirtualMachineProperties, error) { vmClient, err := GetVirtualMachineClientE(subscriptionID) if err != nil { @@ -349,14 +363,15 @@ type Instance struct { *compute.VirtualMachine } -// GetVirtualMachineInstance gets a local Virtual Machine instance in the specified Resource Group +// GetVirtualMachineInstance gets a local Virtual Machine instance in the specified Resource Group. +// This function would fail the test if there is an error. func GetVirtualMachineInstance(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) *Instance { vm, err := GetVirtualMachineInstanceE(t, vmName, resGroupName, subscriptionID) require.NoError(t, err) return vm } -// GetVirtualMachineInstanceE gets a local Virtual Machine instance in the specified Resource Group +// GetVirtualMachineInstanceE gets a local Virtual Machine instance in the specified Resource Group. func GetVirtualMachineInstanceE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (*Instance, error) { // Get VM Object vm, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) @@ -367,7 +382,7 @@ func GetVirtualMachineInstanceE(t testing.TestingT, vmName string, resGroupName return &Instance{vm}, nil } -// GetVirtualMachineInstanceSize gets the size of the Virtual Machine +// GetVirtualMachineInstanceSize gets the size of the Virtual Machine. func (vm *Instance) GetVirtualMachineInstanceSize() compute.VirtualMachineSizeTypes { return vm.VirtualMachineProperties.HardwareProfile.VMSize } @@ -376,14 +391,15 @@ func (vm *Instance) GetVirtualMachineInstanceSize() compute.VirtualMachineSizeTy // Get the base VM Object // *********************** // -// GetVirtualMachine gets a Virtual Machine in the specified Azure Resource Group +// GetVirtualMachine gets a Virtual Machine in the specified Azure Resource Group. +// This function would fail the test if there is an error. func GetVirtualMachine(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) *compute.VirtualMachine { vm, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) require.NoError(t, err) return vm } -// GetVirtualMachineE gets a Virtual Machine in the specified Azure Resource Group +// GetVirtualMachineE gets a Virtual Machine in the specified Azure Resource Group. func GetVirtualMachineE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (*compute.VirtualMachine, error) { // Validate resource group name and subscription ID resGroupName, err := getTargetAzureResourceGroupName(resGroupName) @@ -405,7 +421,7 @@ func GetVirtualMachineE(t testing.TestingT, vmName string, resGroupName string, return &vm, nil } -// GetVirtualMachineClientE creates a Azure Virtual Machine client in the specified Azure Subscription +// GetVirtualMachineClientE creates a Azure Virtual Machine client in the specified Azure Subscription. func GetVirtualMachineClientE(subscriptionID string) (*compute.VirtualMachinesClient, error) { // Validate Azure subscription ID subscriptionID, err := getTargetAzureSubscription(subscriptionID) diff --git a/modules/azure/networkinterface.go b/modules/azure/networkinterface.go index a17c576c9..5bbe3a394 100644 --- a/modules/azure/networkinterface.go +++ b/modules/azure/networkinterface.go @@ -10,14 +10,15 @@ import ( "github.com/stretchr/testify/require" ) -// NetworkInterfaceExists indicates whether the speficied Azure Network Interface exists +// NetworkInterfaceExists indicates whether the speficied Azure Network Interface exists. +// This function would fail the test if there is an error. func NetworkInterfaceExists(t testing.TestingT, nicName string, resGroupName string, subscriptionID string) bool { exists, err := NetworkInterfaceExistsE(t, nicName, resGroupName, subscriptionID) require.NoError(t, err) return exists } -// NetworkInterfaceExistsE indicates whether the speficied Azure Network Interface exists +// NetworkInterfaceExistsE indicates whether the speficied Azure Network Interface exists. func NetworkInterfaceExistsE(t testing.TestingT, nicName string, resGroupName string, subscriptionID string) (bool, error) { // Get the Network Interface _, err := GetNetworkInterfaceE(t, nicName, resGroupName, subscriptionID) @@ -27,14 +28,15 @@ func NetworkInterfaceExistsE(t testing.TestingT, nicName string, resGroupName st return true, nil } -// GetNetworkInterfacePublicIPs returns a list of all the Public IPs found in the Network Interface configurations +// GetNetworkInterfacePublicIPs returns a list of all the Public IPs found in the Network Interface configurations. +// This function would fail the test if there is an error. func GetNetworkInterfacePublicIPs(t testing.TestingT, nicName string, resGroupName string, subscriptionID string) []string { IPs, err := GetNetworkInterfacePublicIPsE(t, nicName, resGroupName, subscriptionID) require.NoError(t, err) return IPs } -// GetNetworkInterfacePublicIPsE returns a list of all the Public IPs found in the Network Interface configurations +// GetNetworkInterfacePublicIPsE returns a list of all the Public IPs found in the Network Interface configurations. func GetNetworkInterfacePublicIPsE(t testing.TestingT, nicName string, resGroupName string, subscriptionID string) ([]string, error) { publicIPs := []string{} @@ -59,7 +61,7 @@ func GetNetworkInterfacePublicIPsE(t testing.TestingT, nicName string, resGroupN return publicIPs, nil } -// ipConfigHasPublicIP returns true if an IP Configuration has a Public IP Address +// ipConfigHasPublicIP returns true if an IP Configuration has a Public IP Address. // This helper method was created since a config without a public address causes a nil pointer panic // and the string representation is searched for the publicIPAddress text to identify it's presence. func ipConfigHasPublicIP(ipConfig *network.InterfaceIPConfiguration) bool { @@ -73,7 +75,8 @@ func ipConfigHasPublicIP(ipConfig *network.InterfaceIPConfiguration) bool { return strings.Contains(string(byteIPConfig), "publicIPAddress") } -// GetNetworkInterfacePrivateIPs gets a list of the Private IPs of a Network Interface configs +// GetNetworkInterfacePrivateIPs gets a list of the Private IPs of a Network Interface configs. +// This function would fail the test if there is an error. func GetNetworkInterfacePrivateIPs(t testing.TestingT, nicName string, resGroupName string, subscriptionID string) []string { IPs, err := GetNetworkInterfacePrivateIPsE(t, nicName, resGroupName, subscriptionID) require.NoError(t, err) @@ -81,7 +84,7 @@ func GetNetworkInterfacePrivateIPs(t testing.TestingT, nicName string, resGroupN return IPs } -// GetNetworkInterfacePrivateIPsE gets a list of the Private IPs of a Network Interface configs +// GetNetworkInterfacePrivateIPsE gets a list of the Private IPs of a Network Interface configs. func GetNetworkInterfacePrivateIPsE(t testing.TestingT, nicName string, resGroupName string, subscriptionID string) ([]string, error) { privateIPs := []string{} @@ -99,7 +102,7 @@ func GetNetworkInterfacePrivateIPsE(t testing.TestingT, nicName string, resGroup return privateIPs, nil } -// GetNetworkInterfaceE gets a Network Interface in the specified Azure Resource Group +// GetNetworkInterfaceE gets a Network Interface in the specified Azure Resource Group. func GetNetworkInterfaceE(t testing.TestingT, nicName string, resGroupName string, subscriptionID string) (*network.Interface, error) { // Validate Azure Resource Group resGroupName, err := getTargetAzureResourceGroupName(resGroupName) @@ -122,7 +125,7 @@ func GetNetworkInterfaceE(t testing.TestingT, nicName string, resGroupName strin return &nic, nil } -// GetNetworkInterfaceClientE creates a new Network Interface client in the specified Azure Subscription +// GetNetworkInterfaceClientE creates a new Network Interface client in the specified Azure Subscription. func GetNetworkInterfaceClientE(subscriptionID string) (*network.InterfacesClient, error) { // Validate Azure Subscription ID subscriptionID, err := getTargetAzureSubscription(subscriptionID) diff --git a/modules/azure/publicaddress.go b/modules/azure/publicaddress.go index 98e720492..713dd68e2 100644 --- a/modules/azure/publicaddress.go +++ b/modules/azure/publicaddress.go @@ -8,14 +8,15 @@ import ( "github.com/stretchr/testify/require" ) -// PublicAddressExists indicates whether the speficied AzurePublic Address exists +// PublicAddressExists indicates whether the speficied AzurePublic Address exists. +// This function would fail the test if there is an error. func PublicAddressExists(t testing.TestingT, publicAddressName string, resGroupName string, subscriptionID string) bool { exists, err := PublicAddressExistsE(t, publicAddressName, resGroupName, subscriptionID) require.NoError(t, err) return exists } -// PublicAddressExistsE indicates whether the speficied AzurePublic Address exists +// PublicAddressExistsE indicates whether the speficied AzurePublic Address exists. func PublicAddressExistsE(t testing.TestingT, publicAddressName string, resGroupName string, subscriptionID string) (bool, error) { // Get the Public Address _, err := GetPublicIPAddressE(t, publicAddressName, resGroupName, subscriptionID) @@ -25,14 +26,14 @@ func PublicAddressExistsE(t testing.TestingT, publicAddressName string, resGroup return true, nil } -// GetPublicAddressIP gets the IP of a Public IP Address +// GetPublicAddressIP gets the IP of a Public IP Address. This function would fail the test if there is an error. func GetPublicAddressIP(t testing.TestingT, publicAddressName string, resGroupName string, subscriptionID string) string { IP, err := GetPublicAddressIPE(t, publicAddressName, resGroupName, subscriptionID) require.NoError(t, err) return IP } -// GetPublicAddressIPE gets the IP of a Public IP Address with error +// GetPublicAddressIPE gets the IP of a Public IP Address. func GetPublicAddressIPE(t testing.TestingT, publicAddressName string, resGroupName string, subscriptionID string) (string, error) { // Create a NIC client pip, err := GetPublicIPAddressE(t, publicAddressName, resGroupName, subscriptionID) @@ -43,7 +44,8 @@ func GetPublicAddressIPE(t testing.TestingT, publicAddressName string, resGroupN return *pip.IPAddress, nil } -// CheckPublicDNSNameAvailability checks whether a Domain Name in the cloudapp.azure.com zone is available for use +// CheckPublicDNSNameAvailability checks whether a Domain Name in the cloudapp.azure.com zone +// is available for use. This function would fail the test if there is an error. func CheckPublicDNSNameAvailability(t testing.TestingT, location string, domainNameLabel string, subscriptionID string) bool { available, err := CheckPublicDNSNameAvailabilityE(t, location, domainNameLabel, subscriptionID) if err != nil { @@ -52,7 +54,7 @@ func CheckPublicDNSNameAvailability(t testing.TestingT, location string, domainN return available } -// CheckPublicDNSNameAvailabilityE checks whether a Domain Name in the cloudapp.azure.com zone is available for use +// CheckPublicDNSNameAvailabilityE checks whether a Domain Name in the cloudapp.azure.com zone is available for use. func CheckPublicDNSNameAvailabilityE(t testing.TestingT, location string, domainNameLabel string, subscriptionID string) (bool, error) { client, err := GetPublicIPAddressClientE(subscriptionID) if err != nil { @@ -67,7 +69,7 @@ func CheckPublicDNSNameAvailabilityE(t testing.TestingT, location string, domain return *res.Available, nil } -// GetPublicIPAddressE gets a Public IP Addresses in the specified Azure Resource Group +// GetPublicIPAddressE gets a Public IP Addresses in the specified Azure Resource Group. func GetPublicIPAddressE(t testing.TestingT, publicIPAddressName string, resGroupName string, subscriptionID string) (*network.PublicIPAddress, error) { // Validate resource group name and subscription ID resGroupName, err := getTargetAzureResourceGroupName(resGroupName) @@ -89,7 +91,7 @@ func GetPublicIPAddressE(t testing.TestingT, publicIPAddressName string, resGrou return &pip, nil } -// GetPublicIPAddressClientE creates a Public IP Addresses client in the specified Azure Subscription +// GetPublicIPAddressClientE creates a Public IP Addresses client in the specified Azure Subscription. func GetPublicIPAddressClientE(subscriptionID string) (*network.PublicIPAddressesClient, error) { // Validate Azure subscription ID subscriptionID, err := getTargetAzureSubscription(subscriptionID) diff --git a/modules/azure/region_test.go b/modules/azure/region_test.go index eeb45807d..8b3d041f1 100644 --- a/modules/azure/region_test.go +++ b/modules/azure/region_test.go @@ -37,7 +37,8 @@ func TestGetAllAzureRegions(t *testing.T) { regions := GetAllAzureRegions(t, "") - // The typical subscription had access to 30+ live regions as of July 2019: https://azure.microsoft.com/en-us/global-infrastructure/regions/ + // The typical subscription had access to 30+ live regions as of + // July 2019: https://azure.microsoft.com/en-us/global-infrastructure/regions/ assert.True(t, len(regions) >= 30, "Number of regions: %d", len(regions)) for _, region := range regions { assertLooksLikeRegionName(t, region) diff --git a/modules/azure/virtualnetwork.go b/modules/azure/virtualnetwork.go index 1cb149bc9..ecbb6bc87 100644 --- a/modules/azure/virtualnetwork.go +++ b/modules/azure/virtualnetwork.go @@ -9,14 +9,15 @@ import ( "github.com/stretchr/testify/require" ) -// VirtualNetworkExists indicates whether the speficied Azure Virtual Network exists +// VirtualNetworkExists indicates whether the speficied Azure Virtual Network exists. +// This function would fail the test if there is an error. func VirtualNetworkExists(t testing.TestingT, vnetName string, resGroupName string, subscriptionID string) bool { exists, err := VirtualNetworkExistsE(t, vnetName, resGroupName, subscriptionID) require.NoError(t, err) return exists } -// VirtualNetworkExistsE indicates whether the speficied Azure Virtual Network exists +// VirtualNetworkExistsE indicates whether the speficied Azure Virtual Network exists. func VirtualNetworkExistsE(t testing.TestingT, vnetName string, resGroupName string, subscriptionID string) (bool, error) { // Get the Virtual Network _, err := GetVirtualNetworkE(t, vnetName, resGroupName, subscriptionID) @@ -26,14 +27,15 @@ func VirtualNetworkExistsE(t testing.TestingT, vnetName string, resGroupName str return true, nil } -// SubnetExists indicates whether the speficied Azure Virtual Network Subnet exists +// SubnetExists indicates whether the speficied Azure Virtual Network Subnet exists. +// This function would fail the test if there is an error. func SubnetExists(t testing.TestingT, subnetName string, vnetName string, resGroupName string, subscriptionID string) bool { exists, err := SubnetExistsE(t, subnetName, vnetName, resGroupName, subscriptionID) require.NoError(t, err) return exists } -// SubnetExistsE indicates whether the speficied Azure Virtual Network Subnet exists +// SubnetExistsE indicates whether the speficied Azure Virtual Network Subnet exists. func SubnetExistsE(t testing.TestingT, subnetName string, vnetName string, resGroupName string, subscriptionID string) (bool, error) { // Get the Subnet _, err := GetSubnetE(t, subnetName, vnetName, resGroupName, subscriptionID) @@ -43,14 +45,15 @@ func SubnetExistsE(t testing.TestingT, subnetName string, vnetName string, resGr return true, nil } -// CheckSubnetContainsIP checks if the Private IP is contined in the Subnet Address Range +// CheckSubnetContainsIP checks if the Private IP is contined in the Subnet Address Range. +// This function would fail the test if there is an error. func CheckSubnetContainsIP(t testing.TestingT, IP string, subnetName string, vnetName string, resGroupName string, subscriptionID string) bool { inRange, err := CheckSubnetContainsIPE(t, IP, subnetName, vnetName, resGroupName, subscriptionID) require.NoError(t, err) return inRange } -// CheckSubnetContainsIPE checks if the Private IP is contined in the Subnet Address Range +// CheckSubnetContainsIPE checks if the Private IP is contined in the Subnet Address Range. func CheckSubnetContainsIPE(t testing.TestingT, ipAddress string, subnetName string, vnetName string, resGroupName string, subscriptionID string) (bool, error) { // Convert the IP to a net IP address ip := net.ParseIP(ipAddress) @@ -64,7 +67,7 @@ func CheckSubnetContainsIPE(t testing.TestingT, ipAddress string, subnetName str return false, err } - // Get Subnet IP range, this required field is never nil therefore no exception handling required + // Get Subnet IP range, this required field is never nil therefore no exception handling required. subnetPrefix := *subnet.AddressPrefix // Check if the IP is in the Subnet Range using the net package @@ -76,15 +79,16 @@ func CheckSubnetContainsIPE(t testing.TestingT, ipAddress string, subnetName str return ipNet.Contains(ip), nil } -// GetVirtualNetworkSubnets gets all Subnet names and their respective address prefixes in the specified Virtual Network +// GetVirtualNetworkSubnets gets all Subnet names and their respective address prefixes in the +// specified Virtual Network. This function would fail the test if there is an error. func GetVirtualNetworkSubnets(t testing.TestingT, vnetName string, resGroupName string, subscriptionID string) map[string]string { subnets, err := GetVirtualNetworkSubnetsE(t, vnetName, resGroupName, subscriptionID) require.NoError(t, err) return subnets } -// GetVirtualNetworkSubnetsE gets all Subnet names and their respective address prefixes in the specified Virtual Network -// Returning both the name and prefix together helps reduce calls for these commonly accessed properties +// GetVirtualNetworkSubnetsE gets all Subnet names and their respective address prefixes in the specified Virtual Network. +// Returning both the name and prefix together helps reduce calls for these frequently accessed properties. func GetVirtualNetworkSubnetsE(t testing.TestingT, vnetName string, resGroupName string, subscriptionID string) (map[string]string, error) { subNetDetails := map[string]string{} @@ -107,7 +111,8 @@ func GetVirtualNetworkSubnetsE(t testing.TestingT, vnetName string, resGroupName return subNetDetails, nil } -// GetVirtualNetworkDNSServerIPs gets a list of all Virtual Network DNS server IPs +// GetVirtualNetworkDNSServerIPs gets a list of all Virtual Network DNS server IPs. +// This function would fail the test if there is an error. func GetVirtualNetworkDNSServerIPs(t testing.TestingT, vnetName string, resGroupName string, subscriptionID string) []string { vnetDNSIPs, err := GetVirtualNetworkDNSServerIPsE(t, vnetName, resGroupName, subscriptionID) require.NoError(t, err) @@ -115,7 +120,7 @@ func GetVirtualNetworkDNSServerIPs(t testing.TestingT, vnetName string, resGroup return vnetDNSIPs } -// GetVirtualNetworkDNSServerIPsE gets a list of all Virtual Network DNS server IPs with Error +// GetVirtualNetworkDNSServerIPsE gets a list of all Virtual Network DNS server IPs with Error. func GetVirtualNetworkDNSServerIPsE(t testing.TestingT, vnetName string, resGroupName string, subscriptionID string) ([]string, error) { // Get Virtual Network vnet, err := GetVirtualNetworkE(t, vnetName, resGroupName, subscriptionID) @@ -126,7 +131,7 @@ func GetVirtualNetworkDNSServerIPsE(t testing.TestingT, vnetName string, resGrou return *vnet.DhcpOptions.DNSServers, nil } -// GetSubnetE gets a subnet +// GetSubnetE gets a subnet. func GetSubnetE(t testing.TestingT, subnetName string, vnetName string, resGroupName string, subscriptionID string) (*network.Subnet, error) { // Validate Azure Resource Group resGroupName, err := getTargetAzureResourceGroupName(resGroupName) @@ -149,7 +154,7 @@ func GetSubnetE(t testing.TestingT, subnetName string, vnetName string, resGroup return &subnet, nil } -// GetSubnetClientE creates a subnet client +// GetSubnetClientE creates a subnet client. func GetSubnetClientE(subscriptionID string) (*network.SubnetsClient, error) { // Validate Azure subscription ID subscriptionID, err := getTargetAzureSubscription(subscriptionID) @@ -170,7 +175,7 @@ func GetSubnetClientE(subscriptionID string) (*network.SubnetsClient, error) { return &client, nil } -// GetVirtualNetworkE gets Virtual Network in the specified Azure Resource Group +// GetVirtualNetworkE gets Virtual Network in the specified Azure Resource Group. func GetVirtualNetworkE(t testing.TestingT, vnetName string, resGroupName string, subscriptionID string) (*network.VirtualNetwork, error) { // Validate Azure Resource Group resGroupName, err := getTargetAzureResourceGroupName(resGroupName) @@ -192,7 +197,7 @@ func GetVirtualNetworkE(t testing.TestingT, vnetName string, resGroupName string return &vnet, nil } -// GetVirtualNetworksClientE creates a virtual network client in the specified Azure Subscription +// GetVirtualNetworksClientE creates a virtual network client in the specified Azure Subscription. func GetVirtualNetworksClientE(subscriptionID string) (*network.VirtualNetworksClient, error) { // Validate Azure subscription ID subscriptionID, err := getTargetAzureSubscription(subscriptionID) From 44729cf449a242409a6b5d66b7b0cccded19b4d9 Mon Sep 17 00:00:00 2001 From: Dave Seepersad <“davesee@microsoft.com”> Date: Fri, 25 Sep 2020 10:58:12 -0400 Subject: [PATCH 07/56] Prefix to postfix --- .../azure/terraform-azure-vm-example/main.tf | 126 +++++++++--------- .../terraform-azure-vm-example/outputs.tf | 16 +-- .../terraform-azure-vm-example/variables.tf | 39 ++++-- 3 files changed, 99 insertions(+), 82 deletions(-) diff --git a/examples/azure/terraform-azure-vm-example/main.tf b/examples/azure/terraform-azure-vm-example/main.tf index 626224916..6e38c5c3e 100644 --- a/examples/azure/terraform-azure-vm-example/main.tf +++ b/examples/azure/terraform-azure-vm-example/main.tf @@ -16,133 +16,137 @@ terraform { } # --------------------------------------------------------------------------------------------------------------------- -# GENERATE RANDOMIZATION -# This helps avoid resource name collisions and improve test security +# DEPLOY A RESOURCE GROUP # --------------------------------------------------------------------------------------------------------------------- -resource "random_password" "vmexample" { - length = 16 - override_special = "-_%@" -} - -# --------------------------------------------------------------------------------------------------------------------- -# DEPLOY RESOURCE GROUP TO CONTAIN TEST RESOURCES -# --------------------------------------------------------------------------------------------------------------------- - -resource "azurerm_resource_group" "vmexample" { - name = "${var.prefix}-resources" +resource "azurerm_resource_group" "vm" { + name = "terratest-vs-rg-${var.postfix}" location = var.location } # --------------------------------------------------------------------------------------------------------------------- -# DEPLOY VIRTUAL NETWORK RESOURCES +# DEPLOY NETWORK RESOURCES +# This network includes a public address for integration tests # --------------------------------------------------------------------------------------------------------------------- -resource "azurerm_virtual_network" "vmexample" { - name = "${var.prefix}-network" +resource "azurerm_virtual_network" "vm" { + name = "vnet-${var.postfix}" address_space = ["10.0.0.0/16"] - location = azurerm_resource_group.vmexample.location - resource_group_name = azurerm_resource_group.vmexample.name + location = azurerm_resource_group.vm.location + resource_group_name = azurerm_resource_group.vm.name } -resource "azurerm_subnet" "vmexample" { - name = "${var.prefix}-subnet" - resource_group_name = azurerm_resource_group.vmexample.name - virtual_network_name = azurerm_virtual_network.vmexample.name +resource "azurerm_subnet" "vm" { + name = "subnet-${var.postfix}" + resource_group_name = azurerm_resource_group.vm.name + virtual_network_name = azurerm_virtual_network.vm.name address_prefixes = [var.subnet_prefix] } -resource "azurerm_public_ip" "vmexample" { - name = "${var.prefix}-pip" - resource_group_name = azurerm_resource_group.vmexample.name - location = azurerm_resource_group.vmexample.location +resource "azurerm_public_ip" "vm" { + name = "pip-${var.postfix}" + resource_group_name = azurerm_resource_group.vm.name + location = azurerm_resource_group.vm.location allocation_method = "Static" ip_version = "IPv4" sku = "Standard" idle_timeout_in_minutes = "4" } -resource "azurerm_network_interface" "vmexample" { - name = "${var.prefix}-nic" - location = azurerm_resource_group.vmexample.location - resource_group_name = azurerm_resource_group.vmexample.name +resource "azurerm_network_interface" "vm" { + name = "nic-${var.postfix}" + location = azurerm_resource_group.vm.location + resource_group_name = azurerm_resource_group.vm.name ip_configuration { name = "terratestconfiguration1" - subnet_id = azurerm_subnet.vmexample.id + subnet_id = azurerm_subnet.vm.id private_ip_address_allocation = "Static" private_ip_address = var.private_ip - public_ip_address_id = azurerm_public_ip.vmexample.id + public_ip_address_id = azurerm_public_ip.vm.id } } # --------------------------------------------------------------------------------------------------------------------- -# DEPLOY AVAILABILITY SET +# DEPLOY AN AVAILABILITY SET # --------------------------------------------------------------------------------------------------------------------- -resource "azurerm_availability_set" "vmexample" { - name = "${var.prefix}-avs" - location = azurerm_resource_group.vmexample.location - resource_group_name = azurerm_resource_group.vmexample.name - platform_fault_dovmexample_count = 2 - managed = true +resource "azurerm_availability_set" "vm" { + name = "avs-${var.postfix}" + location = azurerm_resource_group.vm.location + resource_group_name = azurerm_resource_group.vm.name + platform_fault_domain_count = 2 + managed = true } # --------------------------------------------------------------------------------------------------------------------- -# DEPLOY A VIRTUAL MACHINE RUNNING WINDOWS SERVER +# DEPLOY VIRTUAL MACHINE +# This VM does not actually do anything and is the smallest size VM available with a Windows image # --------------------------------------------------------------------------------------------------------------------- -resource "azurerm_virtual_machine" "vmexample" { - name = "${var.prefix}-vm" - location = azurerm_resource_group.vmexample.location - resource_group_name = azurerm_resource_group.vmexample.name - network_interface_ids = [azurerm_network_interface.vmexample.id] - availability_set_id = azurerm_availability_set.vmexample.id +resource "azurerm_virtual_machine" "vm" { + name = "$vm-${var.postfix}" + location = azurerm_resource_group.vm.location + resource_group_name = azurerm_resource_group.vm.name + network_interface_ids = [azurerm_network_interface.vm.id] + availability_set_id = azurerm_availability_set.vm.id vm_size = var.vm_size - license_type = "Windows_Server" + license_type = var.vm_license_type delete_os_disk_on_termination = true delete_data_disks_on_termination = true storage_image_reference { - publisher = "MicrosoftWindowsServer" - offer = "WindowsServer" + publisher = var.vm_image_publisher + offer = var.vm_image_offer sku = var.vm_image_sku version = var.vm_image_version } storage_os_disk { - name = "${var.prefix}-osdisk" - caching = "None" + name = "osdisk-${var.postfix}" + caching = "ReadWrite" create_option = "FromImage" managed_disk_type = var.disk_type } os_profile { - computer_name = "vmexample" + computer_name = "vm-${var.postfix}" admin_username = var.user_name - admin_password = random_password.vmexample.result + admin_password = random_password.vm.result } os_profile_windows_config { provision_vm_agent = true } + + depends_on = [random_password.vm] +} + +# Random password used to improve test security +resource "random_password" "vm" { + length = 16 + override_special = "-_%@" + min_upper = "1" + min_lower = "1" + min_numeric = "1" + min_special = "1" } # --------------------------------------------------------------------------------------------------------------------- -# DEPLOY AND ATTACH MANAGED DISK TO VIRTUAL MACHINE +# ATTACH A MANAGED DISK TO THE VIRTUAL MACHINE # --------------------------------------------------------------------------------------------------------------------- -resource "azurerm_managed_disk" "vmexample" { - name = "${var.prefix}-disk" - location = azurerm_resource_group.vmexample.location - resource_group_name = azurerm_resource_group.vmexample.name +resource "azurerm_managed_disk" "vm" { + name = "${var.postfix}-disk" + location = azurerm_resource_group.vm.location + resource_group_name = azurerm_resource_group.vm.name storage_account_type = var.disk_type create_option = "Empty" disk_size_gb = 10 } -resource "azurerm_virtual_machine_data_disk_attachment" "vmexample" { - managed_disk_id = azurerm_managed_disk.vmexample.id - virtual_machine_id = azurerm_virtual_machine.vmexample.id +resource "azurerm_virtual_machine_data_disk_attachment" "vm" { + managed_disk_id = azurerm_managed_disk.vm.id + virtual_machine_id = azurerm_virtual_machine.vm.id caching = "ReadWrite" lun = 10 } diff --git a/examples/azure/terraform-azure-vm-example/outputs.tf b/examples/azure/terraform-azure-vm-example/outputs.tf index 54d194196..847bcfd77 100644 --- a/examples/azure/terraform-azure-vm-example/outputs.tf +++ b/examples/azure/terraform-azure-vm-example/outputs.tf @@ -1,32 +1,32 @@ output "resource_group_name" { - value = azurerm_resource_group.main.name + value = azurerm_resource_group.vm.name } output "vm_name" { - value = azurerm_virtual_machine.main.name + value = azurerm_virtual_machine.vm.name } output "virtual_network_name" { - value = azurerm_virtual_network.main.name + value = azurerm_virtual_network.vm.name } output "subnet_name" { - value = azurerm_subnet.main.name + value = azurerm_subnet.vm.name } output "public_ip_name" { - value = azurerm_public_ip.main.name + value = azurerm_public_ip.vm.name } output "network_interface_name" { - value = azurerm_network_interface.main.name + value = azurerm_network_interface.vm.name } output "availability_set_name" { - value = azurerm_availability_set.main.name + value = azurerm_availability_set.vm.name } output "managed_disk_name" { - value = azurerm_managed_disk.main.name + value = azurerm_managed_disk.vm.name } diff --git a/examples/azure/terraform-azure-vm-example/variables.tf b/examples/azure/terraform-azure-vm-example/variables.tf index ec22167b9..8123f826f 100644 --- a/examples/azure/terraform-azure-vm-example/variables.tf +++ b/examples/azure/terraform-azure-vm-example/variables.tf @@ -19,27 +19,21 @@ # --------------------------------------------------------------------------------------------------------------------- variable "disk_type" { - description = "temp" + description = "The type of the Virtual Machine disks" type = string - default = "StandardSSD_LRS" + default = "Standard_LRS" } variable "location" { - description = "The Azure location to deploy resources too" + description = "The Azure location where to deploy your resources too" type = string default = "East US" } -variable "password" { - description = "the password to configure for ssh access" +variable "postfix" { + description = "A postfix string to centrally mitigate resource name collisions" type = string - default = "horriblepassword1234!" -} - -variable "prefix" { - description = "The prefix that will be attached to all resources deployed" - type = string - default = "terratest-vm" + default = "resource" } variable "private_ip" { @@ -60,10 +54,23 @@ variable "user_name" { default = "testadmin" } +# Small Windows Server Image available with Free Account for Windows demo only +variable "vm_image_publisher" { + description = "The storage image reference Publisher from which the VM is created" + type = string + default = "MicrosoftWindowsServer" +} + +variable "vm_image_offer" { + description = "The storage image reference Offer from which the VM is created" + type = string + default = "WindowsServer" +} + variable "vm_image_sku" { description = "The storage image reference SKU from which the VM is created" type = string - default = "2016-Datacenter" + default = "2019-Datacenter-Core-smalldisk" } variable "vm_image_version" { @@ -72,6 +79,12 @@ variable "vm_image_version" { default = "latest" } +variable "vm_license_type" { + description = "The License Type from which the VM is created" + type = string + default = "Windows_Server" +} + variable "vm_size" { description = "The Azure VM Size of the VM" type = string From fc3fc64096ceded9c2e563d8477fb24dcd3ce544 Mon Sep 17 00:00:00 2001 From: Dave Seepersad <“davesee@microsoft.com”> Date: Fri, 25 Sep 2020 15:53:55 -0400 Subject: [PATCH 08/56] Update postfix and spelling --- .../azure/terraform-azure-vm-example/main.tf | 19 ++- .../terraform-azure-vm-example/outputs.tf | 32 ++--- .../terraform-azure-vm-example/variables.tf | 2 +- modules/azure/disk.go | 20 +++- modules/azure/networkinterface.go | 113 +++++++++++------- modules/azure/publicaddress.go | 6 +- modules/azure/virtualnetwork.go | 12 +- modules/collections/stringslicevalue.go | 5 +- ....go => terraform_azure_vm_example_test.go} | 82 ++++++------- 9 files changed, 173 insertions(+), 118 deletions(-) rename test/azure/{terraform_azure_vm_test.go => terraform_azure_vm_example_test.go} (59%) diff --git a/examples/azure/terraform-azure-vm-example/main.tf b/examples/azure/terraform-azure-vm-example/main.tf index 6e38c5c3e..14c805c06 100644 --- a/examples/azure/terraform-azure-vm-example/main.tf +++ b/examples/azure/terraform-azure-vm-example/main.tf @@ -1,18 +1,26 @@ # --------------------------------------------------------------------------------------------------------------------- +# DEPLOY A VIRTUAL MACHINE +# This is an advanced example of how to deploy an Azure Virtual Machine in an availability set, managed disk +# and Networking with a Public IP. +# --------------------------------------------------------------------------------------------------------------------- # See test/azure/terraform_azure_vm_example_test.go for how to write automated tests for this code. # --------------------------------------------------------------------------------------------------------------------- provider "azurerm" { - version = "=2.20.0" + version = "~> 2.20" features {} } # --------------------------------------------------------------------------------------------------------------------- # PIN TERRAFORM VERSION TO >= 0.12 +# The examples have been upgraded to 0.12 syntax # --------------------------------------------------------------------------------------------------------------------- terraform { - required_version = ">= 0.12" + # This module is now only being tested with Terraform 0.13.x. However, to make upgrading easier, we are setting + # 0.12.26 as the minimum version, as that version added support for required_providers with source URLs, making it + # forwards compatible with 0.13.x code. + required_version = ">= 0.12.26" } # --------------------------------------------------------------------------------------------------------------------- @@ -20,7 +28,7 @@ terraform { # --------------------------------------------------------------------------------------------------------------------- resource "azurerm_resource_group" "vm" { - name = "terratest-vs-rg-${var.postfix}" + name = "terratest-vm-rg-${var.postfix}" location = var.location } @@ -85,7 +93,7 @@ resource "azurerm_availability_set" "vm" { # --------------------------------------------------------------------------------------------------------------------- resource "azurerm_virtual_machine" "vm" { - name = "$vm-${var.postfix}" + name = "vm-${var.postfix}" location = azurerm_resource_group.vm.location resource_group_name = azurerm_resource_group.vm.name network_interface_ids = [azurerm_network_interface.vm.id] @@ -121,7 +129,6 @@ resource "azurerm_virtual_machine" "vm" { depends_on = [random_password.vm] } -# Random password used to improve test security resource "random_password" "vm" { length = 16 override_special = "-_%@" @@ -136,7 +143,7 @@ resource "random_password" "vm" { # --------------------------------------------------------------------------------------------------------------------- resource "azurerm_managed_disk" "vm" { - name = "${var.postfix}-disk" + name = "disk-${var.postfix}" location = azurerm_resource_group.vm.location resource_group_name = azurerm_resource_group.vm.name storage_account_type = var.disk_type diff --git a/examples/azure/terraform-azure-vm-example/outputs.tf b/examples/azure/terraform-azure-vm-example/outputs.tf index 847bcfd77..a89020f3b 100644 --- a/examples/azure/terraform-azure-vm-example/outputs.tf +++ b/examples/azure/terraform-azure-vm-example/outputs.tf @@ -1,32 +1,36 @@ -output "resource_group_name" { - value = azurerm_resource_group.vm.name +output "availability_set_name" { + value = azurerm_availability_set.vm.name } -output "vm_name" { - value = azurerm_virtual_machine.vm.name +output "managed_disk_name" { + value = azurerm_managed_disk.vm.name } -output "virtual_network_name" { - value = azurerm_virtual_network.vm.name +output "network_interface_name" { + value = azurerm_network_interface.vm.name } -output "subnet_name" { - value = azurerm_subnet.vm.name +output "os_disk_name" { + value = azurerm_virtual_machine.vm.storage_os_disk[0].name } output "public_ip_name" { value = azurerm_public_ip.vm.name } -output "network_interface_name" { - value = azurerm_network_interface.vm.name +output "resource_group_name" { + value = azurerm_resource_group.vm.name } -output "availability_set_name" { - value = azurerm_availability_set.vm.name +output "subnet_name" { + value = azurerm_subnet.vm.name } -output "managed_disk_name" { - value = azurerm_managed_disk.vm.name +output "virtual_network_name" { + value = azurerm_virtual_network.vm.name +} + +output "vm_name" { + value = azurerm_virtual_machine.vm.name } diff --git a/examples/azure/terraform-azure-vm-example/variables.tf b/examples/azure/terraform-azure-vm-example/variables.tf index 8123f826f..0da27769e 100644 --- a/examples/azure/terraform-azure-vm-example/variables.tf +++ b/examples/azure/terraform-azure-vm-example/variables.tf @@ -54,7 +54,7 @@ variable "user_name" { default = "testadmin" } -# Small Windows Server Image available with Free Account for Windows demo only +# Small Windows Server Image available with Azure Free Account variable "vm_image_publisher" { description = "The storage image reference Publisher from which the VM is created" type = string diff --git a/modules/azure/disk.go b/modules/azure/disk.go index f49106d63..97a4459fd 100644 --- a/modules/azure/disk.go +++ b/modules/azure/disk.go @@ -8,14 +8,15 @@ import ( "github.com/stretchr/testify/require" ) -// DiskExists indicates whether the speficied Azure Managed Disk exists +// DiskExists indicates whether the specified Azure Managed Disk exists +// This function would fail the test if there is an error. func DiskExists(t testing.TestingT, diskName string, resGroupName string, subscriptionID string) bool { exists, err := DiskExistsE(t, diskName, resGroupName, subscriptionID) require.NoError(t, err) return exists } -// DiskExistsE indicates whether the speficied Azure Managed Disk exists +// DiskExistsE indicates whether the specified Azure Managed Disk exists func DiskExistsE(t testing.TestingT, diskName string, resGroupName string, subscriptionID string) (bool, error) { // Get the Disk object _, err := GetDiskE(t, diskName, resGroupName, subscriptionID) @@ -28,10 +29,11 @@ func DiskExistsE(t testing.TestingT, diskName string, resGroupName string, subsc // GetDiskType returns the Disk Storage Account Type of the Azure Managed Disk // This property also accessible from the VM client disk storage object but only works // when the VM is online, while this direct call to GetDiskType always works. +// This function would fail the test if there is an error. func GetDiskType(t testing.TestingT, diskName string, resGroupName string, subscriptionID string) compute.DiskStorageAccountTypes { - disk, err := GetDiskTypeE(t, diskName, resGroupName, subscriptionID) + diskType, err := GetDiskTypeE(t, diskName, resGroupName, subscriptionID) require.NoError(t, err) - return disk + return diskType } // GetDiskTypeE returns the Disk Storage Account Type of the Azure Managed Disk @@ -45,6 +47,14 @@ func GetDiskTypeE(t testing.TestingT, diskName string, resGroupName string, subs return disk.Sku.Name, nil } +// GetDisk returns a Disk in the specified Azure Resource Group +// This function would fail the test if there is an error. +func GetDisk(t testing.TestingT, diskName string, resGroupName string, subscriptionID string) *compute.Disk { + disk, err := GetDiskE(t, diskName, resGroupName, subscriptionID) + require.NoError(t, err) + return disk +} + // GetDiskE returns a Disk in the specified Azure Resource Group func GetDiskE(t testing.TestingT, diskName string, resGroupName string, subscriptionID string) (*compute.Disk, error) { // Validate resource group name and subscription ID @@ -53,7 +63,7 @@ func GetDiskE(t testing.TestingT, diskName string, resGroupName string, subscrip return nil, err } - // Get the client refrence + // Get the client reference client, err := GetDiskClientE(subscriptionID) if err != nil { return nil, err diff --git a/modules/azure/networkinterface.go b/modules/azure/networkinterface.go index 5bbe3a394..6a370730f 100644 --- a/modules/azure/networkinterface.go +++ b/modules/azure/networkinterface.go @@ -2,15 +2,14 @@ package azure import ( "context" - "encoding/json" - "strings" + "reflect" "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-09-01/network" "github.com/gruntwork-io/terratest/modules/testing" "github.com/stretchr/testify/require" ) -// NetworkInterfaceExists indicates whether the speficied Azure Network Interface exists. +// NetworkInterfaceExists indicates whether the specified Azure Network Interface exists. // This function would fail the test if there is an error. func NetworkInterfaceExists(t testing.TestingT, nicName string, resGroupName string, subscriptionID string) bool { exists, err := NetworkInterfaceExistsE(t, nicName, resGroupName, subscriptionID) @@ -18,7 +17,7 @@ func NetworkInterfaceExists(t testing.TestingT, nicName string, resGroupName str return exists } -// NetworkInterfaceExistsE indicates whether the speficied Azure Network Interface exists. +// NetworkInterfaceExistsE indicates whether the specified Azure Network Interface exists. func NetworkInterfaceExistsE(t testing.TestingT, nicName string, resGroupName string, subscriptionID string) (bool, error) { // Get the Network Interface _, err := GetNetworkInterfaceE(t, nicName, resGroupName, subscriptionID) @@ -28,6 +27,33 @@ func NetworkInterfaceExistsE(t testing.TestingT, nicName string, resGroupName st return true, nil } +// GetNetworkInterfacePrivateIPs gets a list of the Private IPs of a Network Interface configs. +// This function would fail the test if there is an error. +func GetNetworkInterfacePrivateIPs(t testing.TestingT, nicName string, resGroupName string, subscriptionID string) []string { + IPs, err := GetNetworkInterfacePrivateIPsE(t, nicName, resGroupName, subscriptionID) + require.NoError(t, err) + + return IPs +} + +// GetNetworkInterfacePrivateIPsE gets a list of the Private IPs of a Network Interface configs. +func GetNetworkInterfacePrivateIPsE(t testing.TestingT, nicName string, resGroupName string, subscriptionID string) ([]string, error) { + var privateIPs []string + + // Get the Network Interface client + nic, err := GetNetworkInterfaceE(t, nicName, resGroupName, subscriptionID) + if err != nil { + return privateIPs, err + } + + // Get the Private IPs from each configuration + for _, IPConfiguration := range *nic.IPConfigurations { + privateIPs = append(privateIPs, *IPConfiguration.PrivateIPAddress) + } + + return privateIPs, nil +} + // GetNetworkInterfacePublicIPs returns a list of all the Public IPs found in the Network Interface configurations. // This function would fail the test if there is an error. func GetNetworkInterfacePublicIPs(t testing.TestingT, nicName string, resGroupName string, subscriptionID string) []string { @@ -38,7 +64,7 @@ func GetNetworkInterfacePublicIPs(t testing.TestingT, nicName string, resGroupNa // GetNetworkInterfacePublicIPsE returns a list of all the Public IPs found in the Network Interface configurations. func GetNetworkInterfacePublicIPsE(t testing.TestingT, nicName string, resGroupName string, subscriptionID string) ([]string, error) { - publicIPs := []string{} + var publicIPs []string // Get the Network Interface client nic, err := GetNetworkInterfaceE(t, nicName, resGroupName, subscriptionID) @@ -48,58 +74,65 @@ func GetNetworkInterfacePublicIPsE(t testing.TestingT, nicName string, resGroupN // Get the Public IPs from each configuration available for _, IPConfiguration := range *nic.IPConfigurations { - if ipConfigHasPublicIP(&IPConfiguration) { - // Get the ID from the long string NIC representation - publicAddressID := GetNameFromResourceID(*IPConfiguration.PublicIPAddress.ID) - - // Get the Public Ip from the Public Address client - publicIP := GetPublicAddressIP(t, publicAddressID, resGroupName, subscriptionID) - publicIPs = append(publicIPs, publicIP) + // Iterate each config, for successful configurations check for a Public Address reference. + // Not failing on errors as as this is an optimistic accumulator. + nicConfig, err := GetNetworkInterfaceConfigurationE(t, nicName, *IPConfiguration.Name, resGroupName, subscriptionID) + if err == nil { + if publicAddressID := nicConfig.PublicIPAddress; !reflect.ValueOf(nicConfig.PublicIPAddress).IsNil() { + // Get Public Address IP for configs with using Public Address client + publicIP, err := GetPublicAddressIPE(t, GetNameFromResourceID(*publicAddressID.ID), resGroupName, subscriptionID) + if err == nil { + publicIPs = append(publicIPs, publicIP) + } + } } } return publicIPs, nil } -// ipConfigHasPublicIP returns true if an IP Configuration has a Public IP Address. -// This helper method was created since a config without a public address causes a nil pointer panic -// and the string representation is searched for the publicIPAddress text to identify it's presence. -func ipConfigHasPublicIP(ipConfig *network.InterfaceIPConfiguration) bool { - var byteIPConfig []byte - - byteIPConfig, err := json.Marshal(ipConfig) +// GetNetworkInterfaceConfigurationE gets a Network Interface Configuration in the specified Azure Resource Group. +func GetNetworkInterfaceConfigurationE(t testing.TestingT, nicName string, nicConfigName string, resGroupName string, subscriptionID string) (*network.InterfaceIPConfiguration, error) { + // Validate Azure Resource Group + resGroupName, err := getTargetAzureResourceGroupName(resGroupName) if err != nil { - return false + return nil, err } - return strings.Contains(string(byteIPConfig), "publicIPAddress") -} + // Get the client reference + client, err := GetNetworkInterfaceConfigurationClientE(subscriptionID) + if err != nil { + return nil, err + } -// GetNetworkInterfacePrivateIPs gets a list of the Private IPs of a Network Interface configs. -// This function would fail the test if there is an error. -func GetNetworkInterfacePrivateIPs(t testing.TestingT, nicName string, resGroupName string, subscriptionID string) []string { - IPs, err := GetNetworkInterfacePrivateIPsE(t, nicName, resGroupName, subscriptionID) - require.NoError(t, err) + // Get the Network Interface + nicConfig, err := client.Get(context.Background(), resGroupName, nicName, nicConfigName) + if err != nil { + return nil, err + } - return IPs + return &nicConfig, nil } -// GetNetworkInterfacePrivateIPsE gets a list of the Private IPs of a Network Interface configs. -func GetNetworkInterfacePrivateIPsE(t testing.TestingT, nicName string, resGroupName string, subscriptionID string) ([]string, error) { - privateIPs := []string{} - - // Get the Network Interface client - nic, err := GetNetworkInterfaceE(t, nicName, resGroupName, subscriptionID) +// GetNetworkInterfaceConfigurationClientE creates a new Network Interface Configuration client in the specified Azure Subscription. +func GetNetworkInterfaceConfigurationClientE(subscriptionID string) (*network.InterfaceIPConfigurationsClient, error) { + // Validate Azure Subscription ID + subscriptionID, err := getTargetAzureSubscription(subscriptionID) if err != nil { - return privateIPs, err + return nil, err } - // Get the Private IPs from each configuration - for _, IPConfiguration := range *nic.IPConfigurations { - privateIPs = append(privateIPs, *IPConfiguration.PrivateIPAddress) + // Get the NIC client + client := network.NewInterfaceIPConfigurationsClient(subscriptionID) + + // Create an authorizer + authorizer, err := NewAuthorizer() + if err != nil { + return nil, err } + client.Authorizer = *authorizer - return privateIPs, nil + return &client, nil } // GetNetworkInterfaceE gets a Network Interface in the specified Azure Resource Group. @@ -110,7 +143,7 @@ func GetNetworkInterfaceE(t testing.TestingT, nicName string, resGroupName strin return nil, err } - // Get the client refrence + // Get the client reference client, err := GetNetworkInterfaceClientE(subscriptionID) if err != nil { return nil, err diff --git a/modules/azure/publicaddress.go b/modules/azure/publicaddress.go index 713dd68e2..89417d122 100644 --- a/modules/azure/publicaddress.go +++ b/modules/azure/publicaddress.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/require" ) -// PublicAddressExists indicates whether the speficied AzurePublic Address exists. +// PublicAddressExists indicates whether the specified AzurePublic Address exists. // This function would fail the test if there is an error. func PublicAddressExists(t testing.TestingT, publicAddressName string, resGroupName string, subscriptionID string) bool { exists, err := PublicAddressExistsE(t, publicAddressName, resGroupName, subscriptionID) @@ -16,7 +16,7 @@ func PublicAddressExists(t testing.TestingT, publicAddressName string, resGroupN return exists } -// PublicAddressExistsE indicates whether the speficied AzurePublic Address exists. +// PublicAddressExistsE indicates whether the specified AzurePublic Address exists. func PublicAddressExistsE(t testing.TestingT, publicAddressName string, resGroupName string, subscriptionID string) (bool, error) { // Get the Public Address _, err := GetPublicIPAddressE(t, publicAddressName, resGroupName, subscriptionID) @@ -77,7 +77,7 @@ func GetPublicIPAddressE(t testing.TestingT, publicIPAddressName string, resGrou return nil, err } - // Get the client refrence + // Get the client reference client, err := GetPublicIPAddressClientE(subscriptionID) if err != nil { return nil, err diff --git a/modules/azure/virtualnetwork.go b/modules/azure/virtualnetwork.go index ecbb6bc87..f75304ac2 100644 --- a/modules/azure/virtualnetwork.go +++ b/modules/azure/virtualnetwork.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/require" ) -// VirtualNetworkExists indicates whether the speficied Azure Virtual Network exists. +// VirtualNetworkExists indicates whether the specified Azure Virtual Network exists. // This function would fail the test if there is an error. func VirtualNetworkExists(t testing.TestingT, vnetName string, resGroupName string, subscriptionID string) bool { exists, err := VirtualNetworkExistsE(t, vnetName, resGroupName, subscriptionID) @@ -17,7 +17,7 @@ func VirtualNetworkExists(t testing.TestingT, vnetName string, resGroupName stri return exists } -// VirtualNetworkExistsE indicates whether the speficied Azure Virtual Network exists. +// VirtualNetworkExistsE indicates whether the specified Azure Virtual Network exists. func VirtualNetworkExistsE(t testing.TestingT, vnetName string, resGroupName string, subscriptionID string) (bool, error) { // Get the Virtual Network _, err := GetVirtualNetworkE(t, vnetName, resGroupName, subscriptionID) @@ -27,7 +27,7 @@ func VirtualNetworkExistsE(t testing.TestingT, vnetName string, resGroupName str return true, nil } -// SubnetExists indicates whether the speficied Azure Virtual Network Subnet exists. +// SubnetExists indicates whether the specified Azure Virtual Network Subnet exists. // This function would fail the test if there is an error. func SubnetExists(t testing.TestingT, subnetName string, vnetName string, resGroupName string, subscriptionID string) bool { exists, err := SubnetExistsE(t, subnetName, vnetName, resGroupName, subscriptionID) @@ -35,7 +35,7 @@ func SubnetExists(t testing.TestingT, subnetName string, vnetName string, resGro return exists } -// SubnetExistsE indicates whether the speficied Azure Virtual Network Subnet exists. +// SubnetExistsE indicates whether the specified Azure Virtual Network Subnet exists. func SubnetExistsE(t testing.TestingT, subnetName string, vnetName string, resGroupName string, subscriptionID string) (bool, error) { // Get the Subnet _, err := GetSubnetE(t, subnetName, vnetName, resGroupName, subscriptionID) @@ -139,7 +139,7 @@ func GetSubnetE(t testing.TestingT, subnetName string, vnetName string, resGroup return nil, err } - // Get the client refrence + // Get the client reference client, err := GetSubnetClientE(subscriptionID) if err != nil { return nil, err @@ -183,7 +183,7 @@ func GetVirtualNetworkE(t testing.TestingT, vnetName string, resGroupName string return nil, err } - // Get the client refrence + // Get the client reference client, err := GetVirtualNetworksClientE(subscriptionID) if err != nil { return nil, err diff --git a/modules/collections/stringslicevalue.go b/modules/collections/stringslicevalue.go index 3afa02c52..0fede5e41 100644 --- a/modules/collections/stringslicevalue.go +++ b/modules/collections/stringslicevalue.go @@ -4,7 +4,7 @@ import ( "strings" ) -// GetSliceLastValueE will take a source string and returns the last value when split by the separator char +// GetSliceLastValueE will take a source string and returns the last value when split by the separator char. func GetSliceLastValueE(source string, separator string) (string, error) { if len(source) > 0 && len(separator) > 0 && strings.Contains(source, separator) { tmp := strings.Split(source, separator) @@ -13,7 +13,8 @@ func GetSliceLastValueE(source string, separator string) (string, error) { return "", NewSliceValueNotFoundError(source) } -// GetSliceIndexValueE will take a source string and returns the value at the given index when split by the separator char +// GetSliceIndexValueE will take a source string and returns the value at the given index when split by +// the separator char. func GetSliceIndexValueE(source string, separator string, index int) (string, error) { if len(source) > 0 && len(separator) > 0 && strings.Contains(source, separator) && index >= 0 { tmp := strings.Split(source, separator) diff --git a/test/azure/terraform_azure_vm_test.go b/test/azure/terraform_azure_vm_example_test.go similarity index 59% rename from test/azure/terraform_azure_vm_test.go rename to test/azure/terraform_azure_vm_example_test.go index a2d01aae3..d999f1fa4 100644 --- a/test/azure/terraform_azure_vm_test.go +++ b/test/azure/terraform_azure_vm_example_test.go @@ -11,6 +11,7 @@ import ( "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-07-01/compute" "github.com/gruntwork-io/terratest/modules/azure" + "github.com/gruntwork-io/terratest/modules/random" "github.com/gruntwork-io/terratest/modules/terraform" "github.com/stretchr/testify/assert" ) @@ -19,10 +20,10 @@ func TestTerraformAzureVmExample(t *testing.T) { t.Parallel() subscriptionID := "" // Subscription ID, leave blank if available as an Environment Var - prefix := "terratest-vm" + uniquePostfix := random.UniqueId() expectedVmAdminUser := "testadmin" expectedVMSize := "Standard_DS1_v2" - expectedImageSKU := "2016-Datacenter" + expectedImageSKU := "2019-Datacenter-Core-smalldisk" expectedImageVersion := "latest" expectedDiskType := "Standard_LRS" expectedSubnetAddressRange := "10.0.17.0/24" @@ -37,9 +38,8 @@ func TestTerraformAzureVmExample(t *testing.T) { TerraformDir: "../../examples/azure/terraform-azure-vm-example", // Variables to pass to our Terraform code using -var options - // "username" and "password" should not be passed from here in a production scenario. Vars: map[string]interface{}{ - "prefix": prefix, + "postfix": uniquePostfix, "user_name": expectedVmAdminUser, "vm_size": expectedVMSize, "vm_image_sku": expectedImageSKU, @@ -58,27 +58,27 @@ func TestTerraformAzureVmExample(t *testing.T) { // Run `terraform output` to get the values of output variables resourceGroupName := terraform.Output(t, terraformOptions, "resource_group_name") - vmName := terraform.Output(t, terraformOptions, "vm_name") - vnetName := terraform.Output(t, terraformOptions, "virtual_network_name") - subnetName := terraform.Output(t, terraformOptions, "subnet_name") - publicIpName := terraform.Output(t, terraformOptions, "public_ip_name") - nicName := terraform.Output(t, terraformOptions, "network_interface_name") - avsName := terraform.Output(t, terraformOptions, "availability_set_name") - osDiskName := prefix + "-osdisk" - diskName := terraform.Output(t, terraformOptions, "managed_disk_name") + expectedVMName := terraform.Output(t, terraformOptions, "vm_name") + expectedVNetName := terraform.Output(t, terraformOptions, "virtual_network_name") + expectedSubnetName := terraform.Output(t, terraformOptions, "subnet_name") + expectedPublicAddressName := terraform.Output(t, terraformOptions, "public_ip_name") + expectedNicName := terraform.Output(t, terraformOptions, "network_interface_name") + expectedAvsName := terraform.Output(t, terraformOptions, "availability_set_name") + expectedOSDiskName := terraform.Output(t, terraformOptions, "os_disk_name") + expectedDiskName := terraform.Output(t, terraformOptions, "managed_disk_name") t.Run("Strategies", func(t *testing.T) { // Check the VM Size directly - actualVMSize := azure.GetVirtualMachineSize(t, vmName, resourceGroupName, subscriptionID) + actualVMSize := azure.GetVirtualMachineSize(t, expectedVMName, resourceGroupName, subscriptionID) assert.Equal(t, expectedVMSize, string(actualVMSize)) // Check the VM Size by object ref - vmRef := azure.GetVirtualMachine(t, vmName, resourceGroupName, subscriptionID) + vmRef := azure.GetVirtualMachine(t, expectedVMName, resourceGroupName, subscriptionID) actualVMSize = vmRef.HardwareProfile.VMSize assert.Equal(t, expectedVMSize, string(actualVMSize)) // Check the VM Size by instance getter - vmInstance := azure.GetVirtualMachineInstance(t, vmName, resourceGroupName, subscriptionID) + vmInstance := azure.GetVirtualMachineInstance(t, expectedVMName, resourceGroupName, subscriptionID) actualVMSize = vmInstance.GetVirtualMachineInstanceSize() assert.Equal(t, expectedVMSize, string(actualVMSize)) }) @@ -87,85 +87,85 @@ func TestTerraformAzureVmExample(t *testing.T) { // Get a list of all VMs and confirm one (or more) VMs exist vmList := azure.GetResourceGroupVirtualMachines(t, resourceGroupName, subscriptionID) assert.True(t, len(vmList) > 0) - assert.Contains(t, vmList, vmName) + assert.Contains(t, vmList, expectedVMName) - // Get all VMs by ref (warning: pointer deref painc if vm is not in list!) + // Get all VMs by ref (warning: pointer de-ref panic if vm is not in list!) vmsByRef := azure.GetResourceGroupVirtualMachinesObjects(t, resourceGroupName, subscriptionID) assert.True(t, len(*vmsByRef) > 0) - thisVm := (*vmsByRef)[vmName] + thisVm := (*vmsByRef)[expectedVMName] assert.Equal(t, expectedVMSize, string(thisVm.HardwareProfile.VMSize)) }) t.Run("Information", func(t *testing.T) { // Check the Virtual Machine exists - assert.True(t, azure.VirtualMachineExists(t, vmName, resourceGroupName, subscriptionID)) + assert.True(t, azure.VirtualMachineExists(t, expectedVMName, resourceGroupName, subscriptionID)) // Check the Admin User - actualVmAdminUser := azure.GetVirtualMachineAdminUser(t, vmName, resourceGroupName, subscriptionID) + actualVmAdminUser := azure.GetVirtualMachineAdminUser(t, expectedVMName, resourceGroupName, subscriptionID) assert.Equal(t, expectedVmAdminUser, actualVmAdminUser) // Check the Storage Image reference - actualImage := azure.GetVirtualMachineImage(t, vmName, resourceGroupName, subscriptionID) + actualImage := azure.GetVirtualMachineImage(t, expectedVMName, resourceGroupName, subscriptionID) assert.Equal(t, expectedImageSKU, actualImage.SKU) assert.Equal(t, expectedImageVersion, actualImage.Version) }) - t.Run("AvailablitySet", func(t *testing.T) { + t.Run("AvailabilitySet", func(t *testing.T) { // Check the Availability Set - actualAvsName := azure.GetVirtualMachineAvailabilitySetID(t, vmName, resourceGroupName, subscriptionID) - assert.True(t, strings.EqualFold(avsName, actualAvsName)) + actualexpectedAvsName := azure.GetVirtualMachineAvailabilitySetID(t, expectedVMName, resourceGroupName, subscriptionID) + assert.True(t, strings.EqualFold(expectedAvsName, actualexpectedAvsName)) // Check the Availability set fault domain counts - actualAvsFaultDomainCount := azure.GetAvailabilitySetFaultDomainCount(t, avsName, resourceGroupName, subscriptionID) + actualAvsFaultDomainCount := azure.GetAvailabilitySetFaultDomainCount(t, expectedAvsName, resourceGroupName, subscriptionID) assert.Equal(t, expectedAvsFaultDomainCount, actualAvsFaultDomainCount) - actualVMsInAvs := azure.GetAvailabilitySetVMs(t, avsName, resourceGroupName, subscriptionID) - assert.Contains(t, actualVMsInAvs, strings.ToLower(vmName)) + actualVMsInAvs := azure.GetAvailabilitySetVMNamesInCaps(t, expectedAvsName, resourceGroupName, subscriptionID) + assert.Contains(t, actualVMsInAvs, strings.ToUpper(expectedVMName)) }) t.Run("Disk", func(t *testing.T) { // Check the OS Disk name - actualOSDiskName := azure.GetVirtualMachineOsDiskName(t, vmName, resourceGroupName, subscriptionID) - assert.Equal(t, osDiskName, actualOSDiskName) + actualOSDiskName := azure.GetVirtualMachineOsDiskName(t, expectedVMName, resourceGroupName, subscriptionID) + assert.Equal(t, expectedOSDiskName, actualOSDiskName) // Check the Managed Disk count - actualManagedDiskCount := azure.GetVirtualMachineManagedDiskCount(t, vmName, resourceGroupName, subscriptionID) + actualManagedDiskCount := azure.GetVirtualMachineManagedDiskCount(t, expectedVMName, resourceGroupName, subscriptionID) assert.Equal(t, expectedManagedDiskCount, actualManagedDiskCount) // Check the VM Managed Disk exists in the list of all VM Managed Disks - actualManagedDiskNames := azure.GetVirtualMachineManagedDisks(t, vmName, resourceGroupName, subscriptionID) - assert.Contains(t, actualManagedDiskNames, diskName) + actualManagedDiskNames := azure.GetVirtualMachineManagedDisks(t, expectedVMName, resourceGroupName, subscriptionID) + assert.Contains(t, actualManagedDiskNames, expectedDiskName) // Check the Disk Type - actualDiskType := azure.GetDiskType(t, diskName, resourceGroupName, subscriptionID) + actualDiskType := azure.GetDiskType(t, expectedDiskName, resourceGroupName, subscriptionID) assert.Equal(t, compute.DiskStorageAccountTypes(expectedDiskType), actualDiskType) }) t.Run("NetworkInterface", func(t *testing.T) { // Check the Network Interface count - actualNicCount := azure.GetVirtualMachineNicCount(t, vmName, resourceGroupName, subscriptionID) + actualNicCount := azure.GetVirtualMachineNicCount(t, expectedVMName, resourceGroupName, subscriptionID) assert.Equal(t, expectedNicCount, actualNicCount) // Check the VM Network Interface exists in the list of all VM Network Interfaces - actualNics := azure.GetVirtualMachineNics(t, vmName, resourceGroupName, subscriptionID) - assert.Contains(t, actualNics, nicName) + actualNics := azure.GetVirtualMachineNics(t, expectedVMName, resourceGroupName, subscriptionID) + assert.Contains(t, actualNics, expectedNicName) // Check the Private IP - actualNicIPs := azure.GetNetworkInterfacePrivateIPs(t, nicName, resourceGroupName, subscriptionID) + actualNicIPs := azure.GetNetworkInterfacePrivateIPs(t, expectedNicName, resourceGroupName, subscriptionID) assert.Contains(t, actualNicIPs, expectedPrivateIPAddress) // Check the Public IP exists - actualPublicIP := azure.GetPublicAddressIP(t, publicIpName, resourceGroupName, subscriptionID) + actualPublicIP := azure.GetPublicAddressIP(t, expectedPublicAddressName, resourceGroupName, subscriptionID) assert.NotNil(t, actualPublicIP) }) t.Run("Vnet&Subnet", func(t *testing.T) { // Check the Subnet exists in the Virtual Network Subnets - actualVnetSubnets := azure.GetVirtualNetworkSubnets(t, vnetName, resourceGroupName, subscriptionID) - assert.NotNil(t, actualVnetSubnets[vnetName]) + actualVnetSubnets := azure.GetVirtualNetworkSubnets(t, expectedVNetName, resourceGroupName, subscriptionID) + assert.NotNil(t, actualVnetSubnets[expectedVNetName]) // Check the Private IP is in the Subnet Range - actualVMNicIPInSubnet := azure.CheckSubnetContainsIP(t, expectedPrivateIPAddress, subnetName, vnetName, resourceGroupName, subscriptionID) + actualVMNicIPInSubnet := azure.CheckSubnetContainsIP(t, expectedPrivateIPAddress, expectedSubnetName, expectedVNetName, resourceGroupName, subscriptionID) assert.True(t, actualVMNicIPInSubnet) }) } From 7ad37c1ed0829635a32632729b280a7fb9bdc4df Mon Sep 17 00:00:00 2001 From: Dave Seepersad <“davesee@microsoft.com”> Date: Fri, 25 Sep 2020 18:08:31 -0400 Subject: [PATCH 09/56] Improved comments, testing subtest in parallel CI --- .../terraform-azure-vm-example/README.md | 6 +- .../terraform-azure-vm-example/variables.tf | 2 +- modules/azure/compute.go | 2 +- test/azure/terraform_azure_vm_example_test.go | 107 ++++++++++++------ 4 files changed, 76 insertions(+), 41 deletions(-) diff --git a/examples/azure/terraform-azure-vm-example/README.md b/examples/azure/terraform-azure-vm-example/README.md index 6efe83378..8622ad06d 100644 --- a/examples/azure/terraform-azure-vm-example/README.md +++ b/examples/azure/terraform-azure-vm-example/README.md @@ -5,8 +5,8 @@ how you can use Terratest to write automated tests for your Azure Virtual Machin - A [Virtual Machine](https://azure.microsoft.com/en-us/services/virtual-machines/) and gives that VM the following resources: - [Virtual Machine](https://docs.microsoft.com/en-us/azure/virtual-machines/) with the name specified in the `vm_name` variable. - - [Managed Disk](https://docs.microsoft.com/en-us/azure/virtual-machines/managed-disks-overview) with the namne specified in the `managed_disk_name` variable. - - [Availability Set](https://docs.microsoft.com/en-us/azure/virtual-machines/availability) with the namne specified in the `availability_set_name` variable. + - [Managed Disk](https://docs.microsoft.com/en-us/azure/virtual-machines/managed-disks-overview) with the name specified in the `managed_disk_name` variable. + - [Availability Set](https://docs.microsoft.com/en-us/azure/virtual-machines/availability) with the name specified in the `availability_set_name` variable. - A [Virtual Network](https://azure.microsoft.com/en-us/services/virtual-network/) module that contains the following resources: - [Virtual Network](https://docs.microsoft.com/en-us/azure/virtual-network/) with the name specified in the `virtual_network_name` variable. - [Subnet](https://docs.microsoft.com/en-us/rest/api/virtualnetwork/subnets) with the name specified in the `subnet_name` variable. @@ -16,7 +16,7 @@ how you can use Terratest to write automated tests for your Azure Virtual Machin Check out [test/azure/terraform_azure_vm_test.go](/test/azure/terraform_azure_vm_test.go) to see how you can write automated tests for this module. -Note that the Virtual Machine madule creates a Microsoft Windows Server Image with a managed disk, availability set and network configuration for demonstration purposes. +Note that the Virtual Machine module creates a Microsoft Windows Server Image with a managed disk, availability set and network configuration for demonstration purposes. **WARNING**: This module and the automated tests for it deploy real resources into your Azure account which can cost you money. The resources are all part of the [Azure Free Account](https://azure.microsoft.com/en-us/free/), so if you haven't used that up, diff --git a/examples/azure/terraform-azure-vm-example/variables.tf b/examples/azure/terraform-azure-vm-example/variables.tf index 0da27769e..9a3889b24 100644 --- a/examples/azure/terraform-azure-vm-example/variables.tf +++ b/examples/azure/terraform-azure-vm-example/variables.tf @@ -54,7 +54,7 @@ variable "user_name" { default = "testadmin" } -# Small Windows Server Image available with Azure Free Account +# Small Windows Server Image, available with Azure Free Account variable "vm_image_publisher" { description = "The storage image reference Publisher from which the VM is created" type = string diff --git a/modules/azure/compute.go b/modules/azure/compute.go index c4a49dbbe..b57acfb74 100644 --- a/modules/azure/compute.go +++ b/modules/azure/compute.go @@ -349,7 +349,7 @@ func GetResourceGroupVirtualMachinesObjectsE(t testing.TestingT, resourceGroupNa for _, v := range vms.Values() { machineName := v.Name vmProperties := v.VirtualMachineProperties - vmDetails[*machineName] = *vmProperties + vmDetails[string(*machineName)] = *vmProperties } return &vmDetails, nil } diff --git a/test/azure/terraform_azure_vm_example_test.go b/test/azure/terraform_azure_vm_example_test.go index d999f1fa4..59dee6186 100644 --- a/test/azure/terraform_azure_vm_example_test.go +++ b/test/azure/terraform_azure_vm_example_test.go @@ -6,6 +6,7 @@ package test import ( + "fmt" "strings" "testing" @@ -19,29 +20,29 @@ import ( func TestTerraformAzureVmExample(t *testing.T) { t.Parallel() - subscriptionID := "" // Subscription ID, leave blank if available as an Environment Var + // subscriptionID is overridden by the environment variable "ARM_SUBSCRIPTION_ID" + subscriptionID := "" uniquePostfix := random.UniqueId() expectedVmAdminUser := "testadmin" - expectedVMSize := "Standard_DS1_v2" + vmSize := "Standard_B1s" expectedImageSKU := "2019-Datacenter-Core-smalldisk" expectedImageVersion := "latest" expectedDiskType := "Standard_LRS" expectedSubnetAddressRange := "10.0.17.0/24" expectedPrivateIPAddress := "10.0.17.4" - var expectedAvsFaultDomainCount int32 = 2 expectedManagedDiskCount := 1 expectedNicCount := 1 // Configure Terraform setting up a path to Terraform code. terraformOptions := &terraform.Options{ - // The path to where our Terraform code is located + // The path to where our Terraform code is located. TerraformDir: "../../examples/azure/terraform-azure-vm-example", - // Variables to pass to our Terraform code using -var options + // Variables to pass to our Terraform code using -var options. Vars: map[string]interface{}{ "postfix": uniquePostfix, "user_name": expectedVmAdminUser, - "vm_size": expectedVMSize, + "vm_size": vmSize, "vm_image_sku": expectedImageSKU, "vm_image_version": expectedImageVersion, "disk_type": expectedDiskType, @@ -50,13 +51,13 @@ func TestTerraformAzureVmExample(t *testing.T) { }, } - // At the end of the test, run `terraform destroy` to clean up any resources that were created + // At the end of the test, run `terraform destroy` to clean up any resources that were created. defer terraform.Destroy(t, terraformOptions) // Run `terraform init` and `terraform apply`. Fail the test if there are any errors. terraform.InitAndApply(t, terraformOptions) - // Run `terraform output` to get the values of output variables + // Run `terraform output` to get the values of output variables. resourceGroupName := terraform.Output(t, terraformOptions, "resource_group_name") expectedVMName := terraform.Output(t, terraformOptions, "vm_name") expectedVNetName := terraform.Output(t, terraformOptions, "virtual_network_name") @@ -66,105 +67,139 @@ func TestTerraformAzureVmExample(t *testing.T) { expectedAvsName := terraform.Output(t, terraformOptions, "availability_set_name") expectedOSDiskName := terraform.Output(t, terraformOptions, "os_disk_name") expectedDiskName := terraform.Output(t, terraformOptions, "managed_disk_name") + expectedVMSize := compute.VirtualMachineSizeTypes(vmSize) + + // Comment to be removed: + // Please let me know if there are too many tests or alternate examples, happy to reduce the + // complexity and amount of code to be maintained. I tried to illustrate different approaches + // we have used in various scenarios to give some flexability in Terratest. t.Run("Strategies", func(t *testing.T) { - // Check the VM Size directly + t.Parallel() + + // Check the VM Size directly. actualVMSize := azure.GetVirtualMachineSize(t, expectedVMName, resourceGroupName, subscriptionID) - assert.Equal(t, expectedVMSize, string(actualVMSize)) + assert.Equal(t, expectedVMSize, actualVMSize) - // Check the VM Size by object ref + // Check the VM Size by reference alternate example. + // This strategy is beneficial when checking multiple properties by using one VM reference, avoiding + // multiple SDK calls. vmRef := azure.GetVirtualMachine(t, expectedVMName, resourceGroupName, subscriptionID) actualVMSize = vmRef.HardwareProfile.VMSize - assert.Equal(t, expectedVMSize, string(actualVMSize)) + assert.Equal(t, expectedVMSize, actualVMSize) - // Check the VM Size by instance getter + // Check the VM Size by instance alternate example. + // This strategy is beneficial when checking multiple properties by using one VM instance and making + // calls against it with the added benefit of module abstraction. vmInstance := azure.GetVirtualMachineInstance(t, expectedVMName, resourceGroupName, subscriptionID) actualVMSize = vmInstance.GetVirtualMachineInstanceSize() - assert.Equal(t, expectedVMSize, string(actualVMSize)) + assert.Equal(t, expectedVMSize, actualVMSize) }) t.Run("MultipleVMs", func(t *testing.T) { - // Get a list of all VMs and confirm one (or more) VMs exist + t.Parallel() + + // Ths approach is beneficial when multiple VMs need to be tested at once. + + // Check against all VM names in a Resource Group. vmList := azure.GetResourceGroupVirtualMachines(t, resourceGroupName, subscriptionID) assert.True(t, len(vmList) > 0) assert.Contains(t, vmList, expectedVMName) - // Get all VMs by ref (warning: pointer de-ref panic if vm is not in list!) + // Get all VMs in a Resource Group by reference alternate example. + // This strategy is beneficial when checking multiple VMs & their properties by avoiding + // multiple SDK calls. The penalty for this approach is introducing direct references + // which need to be checked for nil when not required. vmsByRef := azure.GetResourceGroupVirtualMachinesObjects(t, resourceGroupName, subscriptionID) assert.True(t, len(*vmsByRef) > 0) - thisVm := (*vmsByRef)[expectedVMName] - assert.Equal(t, expectedVMSize, string(thisVm.HardwareProfile.VMSize)) + + // Check for the VM. + thisVM := (*vmsByRef)[expectedVMName] + assert.Equal(t, expectedVMSize, thisVM.HardwareProfile.VMSize) + + // Check for the VM negative test. + fakeVM := fmt.Sprintf("terratest-vm-%s", random.UniqueId()) + assert.Nil(t, (*vmsByRef)[fakeVM].VMID) }) t.Run("Information", func(t *testing.T) { - // Check the Virtual Machine exists + t.Parallel() + + // Check if the Virtual Machine exists. assert.True(t, azure.VirtualMachineExists(t, expectedVMName, resourceGroupName, subscriptionID)) - // Check the Admin User + // Check the Admin User of the VM. actualVmAdminUser := azure.GetVirtualMachineAdminUser(t, expectedVMName, resourceGroupName, subscriptionID) assert.Equal(t, expectedVmAdminUser, actualVmAdminUser) - // Check the Storage Image reference + // Check the Storage Image reference of the VM.. actualImage := azure.GetVirtualMachineImage(t, expectedVMName, resourceGroupName, subscriptionID) assert.Equal(t, expectedImageSKU, actualImage.SKU) assert.Equal(t, expectedImageVersion, actualImage.Version) }) t.Run("AvailabilitySet", func(t *testing.T) { - // Check the Availability Set + t.Parallel() + + // Check the Availability Set of the VM. + // The AVS ID returned from the VM is always CAPS so ignoring case in the assertion. actualexpectedAvsName := azure.GetVirtualMachineAvailabilitySetID(t, expectedVMName, resourceGroupName, subscriptionID) assert.True(t, strings.EqualFold(expectedAvsName, actualexpectedAvsName)) - // Check the Availability set fault domain counts - actualAvsFaultDomainCount := azure.GetAvailabilitySetFaultDomainCount(t, expectedAvsName, resourceGroupName, subscriptionID) - assert.Equal(t, expectedAvsFaultDomainCount, actualAvsFaultDomainCount) - + // Check AVS for multiple VMs at a time alternate example. actualVMsInAvs := azure.GetAvailabilitySetVMNamesInCaps(t, expectedAvsName, resourceGroupName, subscriptionID) assert.Contains(t, actualVMsInAvs, strings.ToUpper(expectedVMName)) }) t.Run("Disk", func(t *testing.T) { - // Check the OS Disk name + t.Parallel() + + // Check the OS Disk name of the VM. actualOSDiskName := azure.GetVirtualMachineOsDiskName(t, expectedVMName, resourceGroupName, subscriptionID) assert.Equal(t, expectedOSDiskName, actualOSDiskName) - // Check the Managed Disk count + // Check the Managed Disk count of the VM. actualManagedDiskCount := azure.GetVirtualMachineManagedDiskCount(t, expectedVMName, resourceGroupName, subscriptionID) assert.Equal(t, expectedManagedDiskCount, actualManagedDiskCount) - // Check the VM Managed Disk exists in the list of all VM Managed Disks + // Check the VM Managed Disk exists in the list of all VM Managed Disks. actualManagedDiskNames := azure.GetVirtualMachineManagedDisks(t, expectedVMName, resourceGroupName, subscriptionID) assert.Contains(t, actualManagedDiskNames, expectedDiskName) - // Check the Disk Type + // Check the Disk Type of the Managed Disk of the VM. actualDiskType := azure.GetDiskType(t, expectedDiskName, resourceGroupName, subscriptionID) assert.Equal(t, compute.DiskStorageAccountTypes(expectedDiskType), actualDiskType) }) + // See the Terratest Azure Network Example for other related tests. t.Run("NetworkInterface", func(t *testing.T) { - // Check the Network Interface count + t.Parallel() + + // Check the Network Interface count of the VM. actualNicCount := azure.GetVirtualMachineNicCount(t, expectedVMName, resourceGroupName, subscriptionID) assert.Equal(t, expectedNicCount, actualNicCount) - // Check the VM Network Interface exists in the list of all VM Network Interfaces + // Check the VM Network Interface exists in the list of all VM Network Interfaces. actualNics := azure.GetVirtualMachineNics(t, expectedVMName, resourceGroupName, subscriptionID) assert.Contains(t, actualNics, expectedNicName) - // Check the Private IP + // Check the Private IP for the NIC. actualNicIPs := azure.GetNetworkInterfacePrivateIPs(t, expectedNicName, resourceGroupName, subscriptionID) assert.Contains(t, actualNicIPs, expectedPrivateIPAddress) - // Check the Public IP exists + // Check the Public IP for the NIC. actualPublicIP := azure.GetPublicAddressIP(t, expectedPublicAddressName, resourceGroupName, subscriptionID) assert.NotNil(t, actualPublicIP) }) t.Run("Vnet&Subnet", func(t *testing.T) { - // Check the Subnet exists in the Virtual Network Subnets + t.Parallel() + + // Check the Subnet exists in the Virtual Network. actualVnetSubnets := azure.GetVirtualNetworkSubnets(t, expectedVNetName, resourceGroupName, subscriptionID) assert.NotNil(t, actualVnetSubnets[expectedVNetName]) - // Check the Private IP is in the Subnet Range + // Check the Private IP is in the Subnet Range. actualVMNicIPInSubnet := azure.CheckSubnetContainsIP(t, expectedPrivateIPAddress, expectedSubnetName, expectedVNetName, resourceGroupName, subscriptionID) assert.True(t, actualVMNicIPInSubnet) }) From 5c5c803a118b150a73036625f3bdaf208d9ced96 Mon Sep 17 00:00:00 2001 From: Dave Seepersad <“davesee@microsoft.com”> Date: Fri, 25 Sep 2020 18:20:57 -0400 Subject: [PATCH 10/56] Restored compute_test updates. --- modules/azure/compute_test.go | 170 +++++++++++++++++++++++++++++++++- 1 file changed, 168 insertions(+), 2 deletions(-) diff --git a/modules/azure/compute_test.go b/modules/azure/compute_test.go index b4c046d16..7b8914754 100644 --- a/modules/azure/compute_test.go +++ b/modules/azure/compute_test.go @@ -17,14 +17,60 @@ If/when CRUD methods are introduced for Azure Virtual Machines, these tests can (see AWS S3 tests for reference). */ -func TestGetTagsForVirtualMachineE(t *testing.T) { +func TestGetVirtualMachineE(t *testing.T) { t.Parallel() vmName := "" rgName := "" subID := "" - _, err := GetTagsForVirtualMachineE(t, vmName, rgName, subID) + _, err := GetVirtualMachineE(t, vmName, rgName, subID) + + require.Error(t, err) +} + +func TestGetVirtualMachineInstanceE(t *testing.T) { + t.Parallel() + + vmName := "" + rgName := "" + subID := "" + + _, err := GetVirtualMachineInstanceE(t, vmName, rgName, subID) + + require.Error(t, err) +} + +func TestGetResourceGroupVirtualMachinesObjectsE(t *testing.T) { + t.Parallel() + + rgName := "" + subID := "" + + _, err := GetResourceGroupVirtualMachinesObjectsE(t, rgName, subID) + + require.Error(t, err) +} + +func TestGetResourceGroupVirtualMachinesE(t *testing.T) { + t.Parallel() + + rgName := "" + subID := "" + + _, err := GetResourceGroupVirtualMachinesE(t, rgName, subID) + + require.Error(t, err) +} + +func TestGetVirtualMachineTagsE(t *testing.T) { + t.Parallel() + + vmName := "" + rgName := "" + subID := "" + + _, err := GetVirtualMachineTagsE(t, vmName, rgName, subID) require.Error(t, err) } @@ -40,3 +86,123 @@ func TestGetVirtualMachineSizeE(t *testing.T) { require.Error(t, err) } + +func TestGetVirtualMachineAdminUserE(t *testing.T) { + t.Parallel() + + vmName := "" + rgName := "" + subID := "" + + _, err := GetVirtualMachineAdminUserE(t, vmName, rgName, subID) + + require.Error(t, err) +} + +func TestGetVirtualMachineImageE(t *testing.T) { + t.Parallel() + + vmName := "" + rgName := "" + subID := "" + + _, err := GetVirtualMachineImageE(t, vmName, rgName, subID) + + require.Error(t, err) +} + +func TestGetVirtualMachineAvailabilitySetIDE(t *testing.T) { + t.Parallel() + + vmName := "" + rgName := "" + subID := "" + + _, err := GetVirtualMachineAvailabilitySetIDE(t, vmName, rgName, subID) + + require.Error(t, err) +} + +func TestGetVirtualMachineStateE(t *testing.T) { + t.Parallel() + + vmName := "" + rgName := "" + subID := "" + + _, err := GetVirtualMachineStateE(t, vmName, rgName, subID) + + require.Error(t, err) +} + +func TestGetVirtualMachineOsDiskNameE(t *testing.T) { + t.Parallel() + + vmName := "" + rgName := "" + subID := "" + + _, err := GetVirtualMachineOsDiskNameE(t, vmName, rgName, subID) + + require.Error(t, err) +} + +func TestGetVirtualMachineManagedDiskCountE(t *testing.T) { + t.Parallel() + + vmName := "" + rgName := "" + subID := "" + + _, err := GetVirtualMachineManagedDiskCountE(t, vmName, rgName, subID) + + require.Error(t, err) +} + +func TestGetVirtualMachineManagedDisksE(t *testing.T) { + t.Parallel() + + vmName := "" + rgName := "" + subID := "" + + _, err := GetVirtualMachineManagedDisksE(t, vmName, rgName, subID) + + require.Error(t, err) +} + +func TestGetVirtualMachineNicCountE(t *testing.T) { + t.Parallel() + + vmName := "" + rgName := "" + subID := "" + + _, err := GetVirtualMachineNicCountE(t, vmName, rgName, subID) + + require.Error(t, err) +} + +func TestGetVirtualMachineNicsE(t *testing.T) { + t.Parallel() + + vmName := "" + rgName := "" + subID := "" + + _, err := GetVirtualMachineNicsE(t, vmName, rgName, subID) + + require.Error(t, err) +} + +func TestVirtualMachineExistsE(t *testing.T) { + t.Parallel() + + vmName := "" + rgName := "" + subID := "" + + _, err := VirtualMachineExistsE(t, vmName, rgName, subID) + + require.Error(t, err) +} From 7a13c5ddc0e46345c675d3ba528803065dc7f5b5 Mon Sep 17 00:00:00 2001 From: Dave Seepersad <“davesee@microsoft.com”> Date: Fri, 25 Sep 2020 18:44:53 -0400 Subject: [PATCH 11/56] Removed subtest parallelism --- test/azure/terraform_azure_vm_example_test.go | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/test/azure/terraform_azure_vm_example_test.go b/test/azure/terraform_azure_vm_example_test.go index 59dee6186..58db86963 100644 --- a/test/azure/terraform_azure_vm_example_test.go +++ b/test/azure/terraform_azure_vm_example_test.go @@ -75,8 +75,6 @@ func TestTerraformAzureVmExample(t *testing.T) { // we have used in various scenarios to give some flexability in Terratest. t.Run("Strategies", func(t *testing.T) { - t.Parallel() - // Check the VM Size directly. actualVMSize := azure.GetVirtualMachineSize(t, expectedVMName, resourceGroupName, subscriptionID) assert.Equal(t, expectedVMSize, actualVMSize) @@ -97,8 +95,6 @@ func TestTerraformAzureVmExample(t *testing.T) { }) t.Run("MultipleVMs", func(t *testing.T) { - t.Parallel() - // Ths approach is beneficial when multiple VMs need to be tested at once. // Check against all VM names in a Resource Group. @@ -123,8 +119,6 @@ func TestTerraformAzureVmExample(t *testing.T) { }) t.Run("Information", func(t *testing.T) { - t.Parallel() - // Check if the Virtual Machine exists. assert.True(t, azure.VirtualMachineExists(t, expectedVMName, resourceGroupName, subscriptionID)) @@ -139,8 +133,6 @@ func TestTerraformAzureVmExample(t *testing.T) { }) t.Run("AvailabilitySet", func(t *testing.T) { - t.Parallel() - // Check the Availability Set of the VM. // The AVS ID returned from the VM is always CAPS so ignoring case in the assertion. actualexpectedAvsName := azure.GetVirtualMachineAvailabilitySetID(t, expectedVMName, resourceGroupName, subscriptionID) @@ -152,8 +144,6 @@ func TestTerraformAzureVmExample(t *testing.T) { }) t.Run("Disk", func(t *testing.T) { - t.Parallel() - // Check the OS Disk name of the VM. actualOSDiskName := azure.GetVirtualMachineOsDiskName(t, expectedVMName, resourceGroupName, subscriptionID) assert.Equal(t, expectedOSDiskName, actualOSDiskName) @@ -173,8 +163,6 @@ func TestTerraformAzureVmExample(t *testing.T) { // See the Terratest Azure Network Example for other related tests. t.Run("NetworkInterface", func(t *testing.T) { - t.Parallel() - // Check the Network Interface count of the VM. actualNicCount := azure.GetVirtualMachineNicCount(t, expectedVMName, resourceGroupName, subscriptionID) assert.Equal(t, expectedNicCount, actualNicCount) @@ -193,8 +181,6 @@ func TestTerraformAzureVmExample(t *testing.T) { }) t.Run("Vnet&Subnet", func(t *testing.T) { - t.Parallel() - // Check the Subnet exists in the Virtual Network. actualVnetSubnets := azure.GetVirtualNetworkSubnets(t, expectedVNetName, resourceGroupName, subscriptionID) assert.NotNil(t, actualVnetSubnets[expectedVNetName]) From 50011dcba4c8ac8c2d1774ef59e1d15d18221922 Mon Sep 17 00:00:00 2001 From: Dave Seepersad <“davesee@microsoft.com”> Date: Fri, 25 Sep 2020 21:05:33 -0400 Subject: [PATCH 12/56] Increased the frequency of numbers in UniqueId --- modules/random/random.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/random/random.go b/modules/random/random.go index 1db77b923..01c855476 100644 --- a/modules/random/random.go +++ b/modules/random/random.go @@ -24,7 +24,7 @@ func RandomString(elements []string) string { return elements[index] } -const base62chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +const base62chars = "0123456789012345678901234567890123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" const uniqueIDLength = 6 // Should be good for 62^6 = 56+ billion combinations // UniqueId returns a unique (ish) id we can attach to resources and tfstate files so they don't conflict with each other From 7d0164d397ba389d713a8bd3137789170478bacb Mon Sep 17 00:00:00 2001 From: Dave Seepersad <“davesee@microsoft.com”> Date: Mon, 28 Sep 2020 09:15:48 -0400 Subject: [PATCH 13/56] Name chenges --- test/azure/terraform_azure_vm_example_test.go | 53 +++++++++---------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/test/azure/terraform_azure_vm_example_test.go b/test/azure/terraform_azure_vm_example_test.go index 58db86963..34bebc2cd 100644 --- a/test/azure/terraform_azure_vm_example_test.go +++ b/test/azure/terraform_azure_vm_example_test.go @@ -24,7 +24,7 @@ func TestTerraformAzureVmExample(t *testing.T) { subscriptionID := "" uniquePostfix := random.UniqueId() expectedVmAdminUser := "testadmin" - vmSize := "Standard_B1s" + expectedVMSize := compute.VirtualMachineSizeTypes("Standard_B1s") expectedImageSKU := "2019-Datacenter-Core-smalldisk" expectedImageVersion := "latest" expectedDiskType := "Standard_LRS" @@ -42,7 +42,7 @@ func TestTerraformAzureVmExample(t *testing.T) { Vars: map[string]interface{}{ "postfix": uniquePostfix, "user_name": expectedVmAdminUser, - "vm_size": vmSize, + "vm_size": string(expectedVMSize), "vm_image_sku": expectedImageSKU, "vm_image_version": expectedImageVersion, "disk_type": expectedDiskType, @@ -59,7 +59,7 @@ func TestTerraformAzureVmExample(t *testing.T) { // Run `terraform output` to get the values of output variables. resourceGroupName := terraform.Output(t, terraformOptions, "resource_group_name") - expectedVMName := terraform.Output(t, terraformOptions, "vm_name") + virtualMachineName := terraform.Output(t, terraformOptions, "vm_name") expectedVNetName := terraform.Output(t, terraformOptions, "virtual_network_name") expectedSubnetName := terraform.Output(t, terraformOptions, "subnet_name") expectedPublicAddressName := terraform.Output(t, terraformOptions, "public_ip_name") @@ -67,29 +67,28 @@ func TestTerraformAzureVmExample(t *testing.T) { expectedAvsName := terraform.Output(t, terraformOptions, "availability_set_name") expectedOSDiskName := terraform.Output(t, terraformOptions, "os_disk_name") expectedDiskName := terraform.Output(t, terraformOptions, "managed_disk_name") - expectedVMSize := compute.VirtualMachineSizeTypes(vmSize) - // Comment to be removed: + // Comment for PR, to be removed: // Please let me know if there are too many tests or alternate examples, happy to reduce the // complexity and amount of code to be maintained. I tried to illustrate different approaches - // we have used in various scenarios to give some flexability in Terratest. + // we have used in various scenarios to illustrate the flexability of Terratest. t.Run("Strategies", func(t *testing.T) { // Check the VM Size directly. - actualVMSize := azure.GetVirtualMachineSize(t, expectedVMName, resourceGroupName, subscriptionID) + actualVMSize := azure.GetVirtualMachineSize(t, virtualMachineName, resourceGroupName, subscriptionID) assert.Equal(t, expectedVMSize, actualVMSize) // Check the VM Size by reference alternate example. // This strategy is beneficial when checking multiple properties by using one VM reference, avoiding // multiple SDK calls. - vmRef := azure.GetVirtualMachine(t, expectedVMName, resourceGroupName, subscriptionID) + vmRef := azure.GetVirtualMachine(t, virtualMachineName, resourceGroupName, subscriptionID) actualVMSize = vmRef.HardwareProfile.VMSize assert.Equal(t, expectedVMSize, actualVMSize) // Check the VM Size by instance alternate example. // This strategy is beneficial when checking multiple properties by using one VM instance and making // calls against it with the added benefit of module abstraction. - vmInstance := azure.GetVirtualMachineInstance(t, expectedVMName, resourceGroupName, subscriptionID) + vmInstance := azure.GetVirtualMachineInstance(t, virtualMachineName, resourceGroupName, subscriptionID) actualVMSize = vmInstance.GetVirtualMachineInstanceSize() assert.Equal(t, expectedVMSize, actualVMSize) }) @@ -100,7 +99,7 @@ func TestTerraformAzureVmExample(t *testing.T) { // Check against all VM names in a Resource Group. vmList := azure.GetResourceGroupVirtualMachines(t, resourceGroupName, subscriptionID) assert.True(t, len(vmList) > 0) - assert.Contains(t, vmList, expectedVMName) + assert.Contains(t, vmList, virtualMachineName) // Get all VMs in a Resource Group by reference alternate example. // This strategy is beneficial when checking multiple VMs & their properties by avoiding @@ -110,24 +109,24 @@ func TestTerraformAzureVmExample(t *testing.T) { assert.True(t, len(*vmsByRef) > 0) // Check for the VM. - thisVM := (*vmsByRef)[expectedVMName] + thisVM := (*vmsByRef)[virtualMachineName] assert.Equal(t, expectedVMSize, thisVM.HardwareProfile.VMSize) // Check for the VM negative test. - fakeVM := fmt.Sprintf("terratest-vm-%s", random.UniqueId()) + fakeVM := fmt.Sprintf("vm-%s", random.UniqueId()) assert.Nil(t, (*vmsByRef)[fakeVM].VMID) }) t.Run("Information", func(t *testing.T) { // Check if the Virtual Machine exists. - assert.True(t, azure.VirtualMachineExists(t, expectedVMName, resourceGroupName, subscriptionID)) + assert.True(t, azure.VirtualMachineExists(t, virtualMachineName, resourceGroupName, subscriptionID)) // Check the Admin User of the VM. - actualVmAdminUser := azure.GetVirtualMachineAdminUser(t, expectedVMName, resourceGroupName, subscriptionID) + actualVmAdminUser := azure.GetVirtualMachineAdminUser(t, virtualMachineName, resourceGroupName, subscriptionID) assert.Equal(t, expectedVmAdminUser, actualVmAdminUser) - // Check the Storage Image reference of the VM.. - actualImage := azure.GetVirtualMachineImage(t, expectedVMName, resourceGroupName, subscriptionID) + // Check the Storage Image properties of the VM. + actualImage := azure.GetVirtualMachineImage(t, virtualMachineName, resourceGroupName, subscriptionID) assert.Equal(t, expectedImageSKU, actualImage.SKU) assert.Equal(t, expectedImageVersion, actualImage.Version) }) @@ -135,25 +134,25 @@ func TestTerraformAzureVmExample(t *testing.T) { t.Run("AvailabilitySet", func(t *testing.T) { // Check the Availability Set of the VM. // The AVS ID returned from the VM is always CAPS so ignoring case in the assertion. - actualexpectedAvsName := azure.GetVirtualMachineAvailabilitySetID(t, expectedVMName, resourceGroupName, subscriptionID) + actualexpectedAvsName := azure.GetVirtualMachineAvailabilitySetID(t, virtualMachineName, resourceGroupName, subscriptionID) assert.True(t, strings.EqualFold(expectedAvsName, actualexpectedAvsName)) // Check AVS for multiple VMs at a time alternate example. actualVMsInAvs := azure.GetAvailabilitySetVMNamesInCaps(t, expectedAvsName, resourceGroupName, subscriptionID) - assert.Contains(t, actualVMsInAvs, strings.ToUpper(expectedVMName)) + assert.Contains(t, actualVMsInAvs, strings.ToUpper(virtualMachineName)) }) t.Run("Disk", func(t *testing.T) { // Check the OS Disk name of the VM. - actualOSDiskName := azure.GetVirtualMachineOsDiskName(t, expectedVMName, resourceGroupName, subscriptionID) + actualOSDiskName := azure.GetVirtualMachineOsDiskName(t, virtualMachineName, resourceGroupName, subscriptionID) assert.Equal(t, expectedOSDiskName, actualOSDiskName) // Check the Managed Disk count of the VM. - actualManagedDiskCount := azure.GetVirtualMachineManagedDiskCount(t, expectedVMName, resourceGroupName, subscriptionID) + actualManagedDiskCount := azure.GetVirtualMachineManagedDiskCount(t, virtualMachineName, resourceGroupName, subscriptionID) assert.Equal(t, expectedManagedDiskCount, actualManagedDiskCount) // Check the VM Managed Disk exists in the list of all VM Managed Disks. - actualManagedDiskNames := azure.GetVirtualMachineManagedDisks(t, expectedVMName, resourceGroupName, subscriptionID) + actualManagedDiskNames := azure.GetVirtualMachineManagedDisks(t, virtualMachineName, resourceGroupName, subscriptionID) assert.Contains(t, actualManagedDiskNames, expectedDiskName) // Check the Disk Type of the Managed Disk of the VM. @@ -164,18 +163,18 @@ func TestTerraformAzureVmExample(t *testing.T) { // See the Terratest Azure Network Example for other related tests. t.Run("NetworkInterface", func(t *testing.T) { // Check the Network Interface count of the VM. - actualNicCount := azure.GetVirtualMachineNicCount(t, expectedVMName, resourceGroupName, subscriptionID) + actualNicCount := azure.GetVirtualMachineNicCount(t, virtualMachineName, resourceGroupName, subscriptionID) assert.Equal(t, expectedNicCount, actualNicCount) // Check the VM Network Interface exists in the list of all VM Network Interfaces. - actualNics := azure.GetVirtualMachineNics(t, expectedVMName, resourceGroupName, subscriptionID) + actualNics := azure.GetVirtualMachineNics(t, virtualMachineName, resourceGroupName, subscriptionID) assert.Contains(t, actualNics, expectedNicName) - // Check the Private IP for the NIC. - actualNicIPs := azure.GetNetworkInterfacePrivateIPs(t, expectedNicName, resourceGroupName, subscriptionID) - assert.Contains(t, actualNicIPs, expectedPrivateIPAddress) + // Check for the Private IP in the NICs IP list. + actualPrivateIPAddress := azure.GetNetworkInterfacePrivateIPs(t, expectedNicName, resourceGroupName, subscriptionID) + assert.Contains(t, actualPrivateIPAddress, expectedPrivateIPAddress) - // Check the Public IP for the NIC. + // Check for the Public IP for the NIC. No expected value since it is assigned runtime. actualPublicIP := azure.GetPublicAddressIP(t, expectedPublicAddressName, resourceGroupName, subscriptionID) assert.NotNil(t, actualPublicIP) }) From 0b458637305c58a692c26e28609372e74801f76a Mon Sep 17 00:00:00 2001 From: Mike Yeaney Date: Mon, 14 Sep 2020 10:13:01 -0400 Subject: [PATCH 14/56] Working NSG module and sample test --- .../terraform-azure-nsg-example/README.md | 129 ++++++++++++ examples/terraform-azure-nsg-example/main.tf | 118 +++++++++++ .../terraform-azure-nsg-example/outputs.tf | 11 ++ .../terraform-azure-nsg-example/variables.tf | 44 +++++ modules/azure/nsg.go | 187 ++++++++++++++++++ .../azure/terraform_azure_nsg_example_test.go | 46 +++++ 6 files changed, 535 insertions(+) create mode 100644 examples/terraform-azure-nsg-example/README.md create mode 100644 examples/terraform-azure-nsg-example/main.tf create mode 100644 examples/terraform-azure-nsg-example/outputs.tf create mode 100644 examples/terraform-azure-nsg-example/variables.tf create mode 100644 modules/azure/nsg.go create mode 100644 test/azure/terraform_azure_nsg_example_test.go diff --git a/examples/terraform-azure-nsg-example/README.md b/examples/terraform-azure-nsg-example/README.md new file mode 100644 index 000000000..502a16fba --- /dev/null +++ b/examples/terraform-azure-nsg-example/README.md @@ -0,0 +1,129 @@ +# Terraform Azure @Module@ Example + +This folder contains a simple Terraform module that deploys resources in [Azure](https://azure.microsoft.com/) to demonstrate how you can use Terratest to write automated tests for your Azure Terraform code. This module deploys the following: + +* A [Virtual Machine](https://azure.microsoft.com/en-us/services/virtual-machines/) that gives the module the following: + * [Virtual Machine](https://docs.microsoft.com/en-us/azure/virtual-machines/) with the value specified in the `vm_name` variable. + * A [Network Security Group](https://docs.microsoft.com/en-us/azure/virtual-network/network-security-groups-overview) created with a single custom rule to allow SSH (port 22) with the nsg name specified in the `nsg_name` variable. + +Check out [test/azure/terraform_azure_nsg_example_test.go](/test/azure/terraform_azure_nsg_example_test.go) to see how you can write +automated tests for this module. + +Note that the resources deployed in this module don't actually do anything; it just runs the resources for +demonstration purposes. + +**WARNING**: This module and the automated tests for it deploy real resources into your Azure account which can cost you +money. The resources are all part of the [Azure Free Account](https://azure.microsoft.com/en-us/free/), so if you haven't used that up, +it should be free, but you are completely responsible for all Azure charges. + +## Running this module manually + +1. Sign up for [Azure](https://azure.microsoft.com/). +1. Configure your Azure credentials using one of the [supported methods for Azure CLI + tools](https://docs.microsoft.com/en-us/cli/azure/azure-cli-configuration?view=azure-cli-latest). +1. Install [Terraform](https://www.terraform.io/) and make sure it's on your `PATH`. +1. Run `terraform init`. +1. Run `terraform apply`. +1. When you're done, run `terraform destroy`. + +## Running automated tests against this module + +1. Sign up for [Azure](https://azure.microsoft.com/). +1. Configure your Azure credentials using one of the [supported methods for Azure CLI + tools](https://docs.microsoft.com/en-us/cli/azure/azure-cli-configuration?view=azure-cli-latest). +1. Install [Terraform](https://www.terraform.io/) and make sure it's on your `PATH`. +1. [Review environment variables](#review-environment-variables). +1. Install [Golang](https://golang.org/) and make sure this code is checked out into your `GOPATH`. +1. `cd test` +1. Make sure [the azure-sdk-for-go versions match](#check-go-dependencies) in [/test/go.mod](/test/go.mod) and in [test/azure/terraform_azure_@module@_test.go](/test/terraform_azure_nic_test.go). +1. `go build terraform_azure_@module@_test.go` +1. `go test -v -run TestTerraformAzure@Module@Example` + +## Module test APIs + +This module exposes the following top-level API methods: + +- GetDefaultNsgRulesClientE returns a rules client that retuns the _default_ NSG rules
+`func GetDefaultNsgRulesClientE(subscriptionID string) (network.DefaultSecurityRulesClient, error)` +- GetCustomNsgRulesClientE returns a rules client that returns the _custom_ NSG rules
+`func GetCustomNsgRulesClientE(subscriptionID string) (network.SecurityRulesClient, error)` +- GetAllNSGRulesE returns a slice containing all rules from the NSG (including defauls and custom)
+`func GetAllNSGRulesE(resourceGroupName, nsgName, subscriptionID string) (NsgRuleSummaryList, error)` + +Note that the `GetAllNsgRulesE` method returns an `NsgRuleSummaryList`, which is defined as follows: + +- NsgRuleSummaryList holds a colleciton of NsgRuleSummary structs
+` type NsgRuleSummaryList struct { SummarizedRules []NsgRuleSummary } ` + +This collection wrapper has the following method: + +- FindRuleByName looks for a matching rule name
+`func (summarizedRules *NsgRuleSummaryList) FindRuleByName(t *testing.T, name string) NsgRuleSummary` + +Finally, the inner collection is defined as an `NsgRuleSummary` struct, defined as follows: + +``` +type NsgRuleSummary struct { + Name string + Description string + Protocol string + SourcePortRange string + DestinationPortRange string + SourceAddressPrefix string + DestinationAddressPrefix string + Access string + Priority int32 + Direction string +} +``` + +This wrapper has the following methods available: + +- AllowsDestinationPort checks to see if the rule allows a specific destination port
+`func (summarizedRule *NsgRuleSummary) AllowsDestinationPort(port string) bool` +- AllowsSourcePort checks to see if the rule allows a specific source port
+`func (summarizedRule *NsgRuleSummary) AllowsSourcePort(port string) bool` + +## Check Go Dependencies + +Check that the `github.com/Azure/azure-sdk-for-go` version in your generated `go.mod` for this test matches the version in the terratest [go.mod](https://github.com/gruntwork-io/terratest/blob/master/go.mod) file. + +> This was tested with **go1.14.4**. + +### Check Azure-sdk-for-go version + +Let's make sure [go.mod](https://github.com/gruntwork-io/terratest/blob/master/go.mod) includes the appropriate [azure-sdk-for-go version](https://github.com/Azure/azure-sdk-for-go/releases/tag/v38.1.0): + +```go +require ( + ... + github.com/Azure/azure-sdk-for-go v38.1.0+incompatible + ... +) +``` + +If we make changes to either the **go.mod** or the **go test file**, we should make sure that the go build command works still. + +```powershell +go build terraform_azure_@module_test.go +``` + +## Review Environment Variables + +As part of configuring terraform for Azure, we'll want to check that we have set the appropriate [credentials](https://docs.microsoft.com/en-us/azure/terraform/terraform-install-configure?toc=https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fterraform%2Ftoc.json&bc=https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fbread%2Ftoc.json#set-up-terraform-access-to-azure) and also that we set the [environment variables](https://docs.microsoft.com/en-us/azure/terraform/terraform-install-configure?toc=https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fterraform%2Ftoc.json&bc=https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fbread%2Ftoc.json#configure-terraform-environment-variables) on the testing host. + +```bash +export ARM_CLIENT_ID=your_app_id +export ARM_CLIENT_SECRET=your_password +export ARM_SUBSCRIPTION_ID=your_subscription_id +export ARM_TENANT_ID=your_tenant_id +``` + +Note, in a Windows environment, these should be set as **system environment variables**. We can use a PowerShell console with administrative rights to update these environment variables: + +```powershell +[System.Environment]::SetEnvironmentVariable("ARM_CLIENT_ID",$your_app_id,[System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable("ARM_CLIENT_SECRET",$your_password,[System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable("ARM_SUBSCRIPTION_ID",$your_subscription_id,[System.EnvironmentVariableTarget]::Machine) +[System.Environment]::SetEnvironmentVariable("ARM_TENANT_ID",$your_tenant_id,[System.EnvironmentVariableTarget]::Machine) +``` diff --git a/examples/terraform-azure-nsg-example/main.tf b/examples/terraform-azure-nsg-example/main.tf new file mode 100644 index 000000000..89d2bd68f --- /dev/null +++ b/examples/terraform-azure-nsg-example/main.tf @@ -0,0 +1,118 @@ +provider "azurerm" { + version = "=2.20.0" + features {} +} + +# --------------------------------------------------------------------------------------------------------------------- +# PIN TERRAFORM VERSION TO >= 0.12 +# The examples have been upgraded to 0.12 syntax +# --------------------------------------------------------------------------------------------------------------------- + +terraform { + required_version = ">= 0.12" +} + +# --------------------------------------------------------------------------------------------------------------------- +# DEPLOY A RESOURCE GROUP +# See test/terraform_azure_example_test.go for how to write automated tests for this code. +# --------------------------------------------------------------------------------------------------------------------- + +resource "azurerm_resource_group" "main" { + name = "${var.prefix}-resources" + location = "East US" +} + +# --------------------------------------------------------------------------------------------------------------------- +# DEPLOY VIRTUAL NETWORK RESOURCES +# --------------------------------------------------------------------------------------------------------------------- + +resource "azurerm_virtual_network" "main" { + name = "${var.prefix}-network" + address_space = ["10.0.0.0/16"] + location = azurerm_resource_group.main.location + resource_group_name = azurerm_resource_group.main.name +} + +resource "azurerm_subnet" "internal" { + name = "internal" + resource_group_name = azurerm_resource_group.main.name + virtual_network_name = azurerm_virtual_network.main.name + address_prefixes = ["10.0.17.0/24"] +} + +resource "azurerm_network_interface" "main" { + name = "${var.prefix}-nic" + location = azurerm_resource_group.main.location + resource_group_name = azurerm_resource_group.main.name + + ip_configuration { + name = "terratestconfiguration1" + subnet_id = azurerm_subnet.internal.id + private_ip_address_allocation = "Dynamic" + } +} + +resource "azurerm_network_security_group" "main" { + name = "testSecurityGroup1" + location = azurerm_resource_group.main.location + resource_group_name = azurerm_resource_group.main.name +} + +resource "azurerm_network_interface_security_group_association" "main" { + network_interface_id = azurerm_network_interface.main.id + network_security_group_id = azurerm_network_security_group.main.id +} + +resource "azurerm_network_security_rule" "main" { + name = "allowSSH" + description = "allowSSH" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_port_range = "*" + destination_port_range = 22 + source_address_prefix = "*" + destination_address_prefix = "*" + resource_group_name = azurerm_resource_group.main.name + network_security_group_name = azurerm_network_security_group.main.name +} + +# --------------------------------------------------------------------------------------------------------------------- +# DEPLOY A VIRTUAL MACHINE RUNNING UBUNTU +# --------------------------------------------------------------------------------------------------------------------- + +resource "azurerm_virtual_machine" "main" { + name = "${var.prefix}-vm" + location = azurerm_resource_group.main.location + resource_group_name = azurerm_resource_group.main.name + network_interface_ids = [azurerm_network_interface.main.id] + vm_size = "Standard_B1s" + delete_os_disk_on_termination = true + delete_data_disks_on_termination = true + + storage_image_reference { + publisher = "Canonical" + offer = "UbuntuServer" + sku = "16.04-LTS" + version = "latest" + } + + storage_os_disk { + name = "terratestosdisk1" + caching = "ReadWrite" + create_option = "FromImage" + managed_disk_type = "Standard_LRS" + } + + os_profile { + computer_name = var.hostname + admin_username = var.username + admin_password = var.password + } + + os_profile_linux_config { + disable_password_authentication = false + } +} + diff --git a/examples/terraform-azure-nsg-example/outputs.tf b/examples/terraform-azure-nsg-example/outputs.tf new file mode 100644 index 000000000..ae8d7bcd0 --- /dev/null +++ b/examples/terraform-azure-nsg-example/outputs.tf @@ -0,0 +1,11 @@ +output "vm_name" { + value = azurerm_virtual_machine.main.name +} + +output "resource_group_name" { + value = azurerm_resource_group.main.name +} + +output "nsg_name" { + value = azurerm_network_security_group.main.name +} diff --git a/examples/terraform-azure-nsg-example/variables.tf b/examples/terraform-azure-nsg-example/variables.tf new file mode 100644 index 000000000..8c231c502 --- /dev/null +++ b/examples/terraform-azure-nsg-example/variables.tf @@ -0,0 +1,44 @@ +# --------------------------------------------------------------------------------------------------------------------- +# ENVIRONMENT VARIABLES +# Define these secrets as environment variables +# --------------------------------------------------------------------------------------------------------------------- + +# ARM_CLIENT_ID +# ARM_CLIENT_SECRET +# ARM_SUBSCRIPTION_ID +# ARM_TENANT_ID + +# --------------------------------------------------------------------------------------------------------------------- +# REQUIRED PARAMETERS +# You must provide a value for each of these parameters. +# --------------------------------------------------------------------------------------------------------------------- + +# --------------------------------------------------------------------------------------------------------------------- +# OPTIONAL PARAMETERS +# These parameters have reasonable defaults. +# --------------------------------------------------------------------------------------------------------------------- + +variable "hostname" { + description = "The hostname of the new VM to be configured" + type = string + default = "terratest-vm" +} + +variable "password" { + description = "The password to configure for SSH access" + type = string + default = "HorriblePassword1234!" +} + +variable "prefix" { + description = "The prefix that will be attached to all resources deployed" + type = string + default = "terratest-nsg-example" +} + +variable "username" { + description = "The username to be provisioned into your VM" + type = string + default = "testadmin" +} + diff --git a/modules/azure/nsg.go b/modules/azure/nsg.go new file mode 100644 index 000000000..6e201595c --- /dev/null +++ b/modules/azure/nsg.go @@ -0,0 +1,187 @@ +package azure + +import ( + "context" + "strconv" + "strings" + "testing" + + "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-09-01/network" +) + +// GetDefaultNsgRulesClientE returns a rules client +func GetDefaultNsgRulesClientE(subscriptionID string) (network.DefaultSecurityRulesClient, error) { + // Validate Azure subscription ID + subscriptionID, err := getTargetAzureSubscription(subscriptionID) + if err != nil { + return network.DefaultSecurityRulesClient{}, err + } + + nsgClient := network.NewDefaultSecurityRulesClient(subscriptionID) + + // Get an authorizer + auth, err := NewAuthorizer() + if err != nil { + return network.DefaultSecurityRulesClient{}, err + } + + nsgClient.Authorizer = *auth + return nsgClient, nil +} + +// GetCustomNsgRulesClientE returns a rules client +func GetCustomNsgRulesClientE(subscriptionID string) (network.SecurityRulesClient, error) { + // Validate Azure subscription ID + subscriptionID, err := getTargetAzureSubscription(subscriptionID) + if err != nil { + return network.SecurityRulesClient{}, err + } + + nsgClient := network.NewSecurityRulesClient(subscriptionID) + + // Get an authorizer + auth, err := NewAuthorizer() + if err != nil { + return network.SecurityRulesClient{}, err + } + + nsgClient.Authorizer = *auth + return nsgClient, nil +} + +// GetAllNSGRulesE returns a slice containing all rules from the NSG (including defauls and custom) +func GetAllNSGRulesE(resourceGroupName, nsgName, subscriptionID string) (NsgRuleSummaryList, error) { + defaultRulesClient, err := GetDefaultNsgRulesClientE(subscriptionID) + if err != nil { + return NsgRuleSummaryList{}, err + } + + customRulesClient, err := GetCustomNsgRulesClientE(subscriptionID) + if err != nil { + return NsgRuleSummaryList{}, err + } + + defaultRuleList, err := defaultRulesClient.ListComplete(context.Background(), resourceGroupName, nsgName) + if err != nil { + return NsgRuleSummaryList{}, err + } + + customRuleList, err := customRulesClient.ListComplete(context.Background(), resourceGroupName, nsgName) + if err != nil { + return NsgRuleSummaryList{}, err + } + + rules := make([]NsgRuleSummary, 0) + bindRuleList(&rules, defaultRuleList) + bindRuleList(&rules, customRuleList) + ruleList := NsgRuleSummaryList{} + ruleList.SummarizedRules = rules + return ruleList, nil +} + +// NsgRuleSummaryList holds a colleciton of NsgRuleSummary structs +type NsgRuleSummaryList struct { + SummarizedRules []NsgRuleSummary +} + +// NsgRuleSummary is a string-based summary of an NSG rule with methods attached. +type NsgRuleSummary struct { + Name string + Description string + Protocol string + SourcePortRange string + DestinationPortRange string + SourceAddressPrefix string + DestinationAddressPrefix string + Access string + Priority int32 + Direction string +} + +func bindRuleList(dest *[]NsgRuleSummary, source network.SecurityRuleListResultIterator) { + for source.NotDone() { + v := source.Value() + *dest = append(*dest, convertToNsgRuleSummary(v.Name, v.SecurityRulePropertiesFormat)) + source.NextWithContext(context.Background()) + } +} + +func convertToNsgRuleSummary(name *string, rule *network.SecurityRulePropertiesFormat) NsgRuleSummary { + summary := NsgRuleSummary{} + + if rule.Description != nil { + summary.Description = *rule.Description + } else { + summary.Description = "" + } + summary.Name = *name + summary.Protocol = string(rule.Protocol) + summary.SourcePortRange = *rule.SourcePortRange + summary.DestinationPortRange = *rule.DestinationPortRange + summary.SourceAddressPrefix = *rule.SourceAddressPrefix + summary.DestinationAddressPrefix = *rule.DestinationAddressPrefix + summary.Access = string(rule.Access) + summary.Priority = *rule.Priority + summary.Direction = string(rule.Direction) + return summary +} + +// FindRuleByName looks for a matching rule name +func (summarizedRules *NsgRuleSummaryList) FindRuleByName(t *testing.T, name string) NsgRuleSummary { + for _, r := range summarizedRules.SummarizedRules { + if r.Name == name { + return r + } + } + + t.Error("Supplied rule name not found") + return NsgRuleSummary{} +} + +// AllowsDestinationPort checks to see if the rule allows a specific destination port. +func (summarizedRule *NsgRuleSummary) AllowsDestinationPort(port string) bool { + if summarizedRule.DestinationPortRange == "*" { + return true + } + + low, high := parsePortRangeString(summarizedRule.DestinationPortRange) + portAsInt, _ := strconv.ParseInt(port, 10, 16) + + if (port == "*") && (low == 0) && (high == 65535) { + return true + } + + return ((low <= uint16(portAsInt)) && (uint16(portAsInt) <= high)) +} + +// AllowsSourcePort checks to see if the rule allows a specific source port. +func (summarizedRule *NsgRuleSummary) AllowsSourcePort(port string) bool { + if summarizedRule.SourcePortRange == "*" { + return true + } + + low, high := parsePortRangeString(summarizedRule.SourcePortRange) + portAsInt, _ := strconv.ParseInt(port, 10, 16) + + if (port == "*") && (low == 0) && (high == 65535) { + return true + } + + return ((low <= uint16(portAsInt)) && (uint16(portAsInt) <= high)) +} + +// parsePortRangeString decodes a range string ("2-100") or a single digit ("22") and returns +// a tuple in [low, hi] form. Note that if a single digit is supplied, both members of the +// return tuple will be the same value (e.g., "22" returns (22, 22)) +func parsePortRangeString(rangeString string) (low uint16, hight uint16) { + // Is this a range? + if strings.Index(rangeString, "-") == -1 { + val, _ := strconv.ParseInt(rangeString, 10, 16) + return uint16(val), uint16(val) + } + + parts := strings.Split(rangeString, "-") + lowVal, _ := strconv.ParseInt(parts[0], 10, 16) + highVal, _ := strconv.ParseInt(parts[1], 10, 16) + return uint16(lowVal), uint16(highVal) +} diff --git a/test/azure/terraform_azure_nsg_example_test.go b/test/azure/terraform_azure_nsg_example_test.go new file mode 100644 index 000000000..a1eedc564 --- /dev/null +++ b/test/azure/terraform_azure_nsg_example_test.go @@ -0,0 +1,46 @@ +// +build azure + +// NOTE: We use build tags to differentiate azure testing because we currently do not have azure access setup for +// CircleCI. + +package test + +import ( + "testing" + + "github.com/gruntwork-io/terratest/modules/azure" + "github.com/gruntwork-io/terratest/modules/terraform" + "github.com/stretchr/testify/assert" +) + +func TestTerraformAzureNsgExample(t *testing.T) { + t.Parallel() + + terraformOptions := &terraform.Options{ + // The path to where our Terraform code is located + TerraformDir: "../../examples/terraform-azure-nsg-example", + } + + defer terraform.Destroy(t, terraformOptions) + terraform.InitAndApply(t, terraformOptions) + + nsgName := terraform.Output(t, terraformOptions, "nsg_name") + resourceGroupName := terraform.Output(t, terraformOptions, "resource_group_name") + + // A default NSG has 6 rules, and we have one custom rule for a total of 7 + rules, err := azure.GetAllNSGRulesE(resourceGroupName, nsgName, "") + assert.NoError(t, err) + assert.Equal(t, 7, len(rules.SummarizedRules)) + + // We should have a rule named "allowSSH" + sshRule := rules.FindRuleByName(t, "allowSSH") + + // That rule should allow port 22 inbound + assert.True(t, sshRule.AllowsDestinationPort("22")) + + // But should not allow 80 inbound + assert.False(t, sshRule.AllowsDestinationPort("80")) + + // SSh is allowed from any port + assert.True(t, sshRule.AllowsSourcePort("*")) +} From 679a6525db287fb460b45531dd267c74541d5571 Mon Sep 17 00:00:00 2001 From: Mike Yeaney Date: Mon, 14 Sep 2020 10:33:48 -0400 Subject: [PATCH 15/56] Moved example folder to correct location --- examples/{ => azure}/terraform-azure-nsg-example/README.md | 0 examples/{ => azure}/terraform-azure-nsg-example/main.tf | 0 examples/{ => azure}/terraform-azure-nsg-example/outputs.tf | 0 examples/{ => azure}/terraform-azure-nsg-example/variables.tf | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename examples/{ => azure}/terraform-azure-nsg-example/README.md (100%) rename examples/{ => azure}/terraform-azure-nsg-example/main.tf (100%) rename examples/{ => azure}/terraform-azure-nsg-example/outputs.tf (100%) rename examples/{ => azure}/terraform-azure-nsg-example/variables.tf (100%) diff --git a/examples/terraform-azure-nsg-example/README.md b/examples/azure/terraform-azure-nsg-example/README.md similarity index 100% rename from examples/terraform-azure-nsg-example/README.md rename to examples/azure/terraform-azure-nsg-example/README.md diff --git a/examples/terraform-azure-nsg-example/main.tf b/examples/azure/terraform-azure-nsg-example/main.tf similarity index 100% rename from examples/terraform-azure-nsg-example/main.tf rename to examples/azure/terraform-azure-nsg-example/main.tf diff --git a/examples/terraform-azure-nsg-example/outputs.tf b/examples/azure/terraform-azure-nsg-example/outputs.tf similarity index 100% rename from examples/terraform-azure-nsg-example/outputs.tf rename to examples/azure/terraform-azure-nsg-example/outputs.tf diff --git a/examples/terraform-azure-nsg-example/variables.tf b/examples/azure/terraform-azure-nsg-example/variables.tf similarity index 100% rename from examples/terraform-azure-nsg-example/variables.tf rename to examples/azure/terraform-azure-nsg-example/variables.tf From 1cfa3e5a66d1edd4c5b7453515647ee3c59fd928 Mon Sep 17 00:00:00 2001 From: Mike Yeaney Date: Mon, 14 Sep 2020 15:21:14 -0400 Subject: [PATCH 16/56] Added unit tests for nsg module --- modules/azure/nsg.go | 18 +++++---- test/azure/nsg_test.go | 91 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 7 deletions(-) create mode 100644 test/azure/nsg_test.go diff --git a/modules/azure/nsg.go b/modules/azure/nsg.go index 6e201595c..32bad124d 100644 --- a/modules/azure/nsg.go +++ b/modules/azure/nsg.go @@ -4,7 +4,6 @@ import ( "context" "strconv" "strings" - "testing" "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-09-01/network" ) @@ -114,6 +113,7 @@ func convertToNsgRuleSummary(name *string, rule *network.SecurityRulePropertiesF } else { summary.Description = "" } + summary.Name = *name summary.Protocol = string(rule.Protocol) summary.SourcePortRange = *rule.SourcePortRange @@ -127,14 +127,13 @@ func convertToNsgRuleSummary(name *string, rule *network.SecurityRulePropertiesF } // FindRuleByName looks for a matching rule name -func (summarizedRules *NsgRuleSummaryList) FindRuleByName(t *testing.T, name string) NsgRuleSummary { +func (summarizedRules *NsgRuleSummaryList) FindRuleByName(name string) NsgRuleSummary { for _, r := range summarizedRules.SummarizedRules { if r.Name == name { return r } } - t.Error("Supplied rule name not found") return NsgRuleSummary{} } @@ -144,7 +143,7 @@ func (summarizedRule *NsgRuleSummary) AllowsDestinationPort(port string) bool { return true } - low, high := parsePortRangeString(summarizedRule.DestinationPortRange) + low, high := ParsePortRangeString(summarizedRule.DestinationPortRange) portAsInt, _ := strconv.ParseInt(port, 10, 16) if (port == "*") && (low == 0) && (high == 65535) { @@ -160,7 +159,7 @@ func (summarizedRule *NsgRuleSummary) AllowsSourcePort(port string) bool { return true } - low, high := parsePortRangeString(summarizedRule.SourcePortRange) + low, high := ParsePortRangeString(summarizedRule.SourcePortRange) portAsInt, _ := strconv.ParseInt(port, 10, 16) if (port == "*") && (low == 0) && (high == 65535) { @@ -170,10 +169,15 @@ func (summarizedRule *NsgRuleSummary) AllowsSourcePort(port string) bool { return ((low <= uint16(portAsInt)) && (uint16(portAsInt) <= high)) } -// parsePortRangeString decodes a range string ("2-100") or a single digit ("22") and returns +// ParsePortRangeString decodes a range string ("2-100") or a single digit ("22") and returns // a tuple in [low, hi] form. Note that if a single digit is supplied, both members of the // return tuple will be the same value (e.g., "22" returns (22, 22)) -func parsePortRangeString(rangeString string) (low uint16, hight uint16) { +func ParsePortRangeString(rangeString string) (low uint16, hight uint16) { + // Is this an asterisk? + if rangeString == "*" { + return uint16(0), uint16(65535) + } + // Is this a range? if strings.Index(rangeString, "-") == -1 { val, _ := strconv.ParseInt(rangeString, 10, 16) diff --git a/test/azure/nsg_test.go b/test/azure/nsg_test.go new file mode 100644 index 000000000..173a9fb7e --- /dev/null +++ b/test/azure/nsg_test.go @@ -0,0 +1,91 @@ +// +build azure + +// NOTE: We use build tags to differentiate azure testing because we currently do not have azure access setup for +// CircleCI. + +package test + +import ( + "fmt" + "math/rand" + "testing" + "time" + + "github.com/gruntwork-io/terratest/modules/azure" + "github.com/stretchr/testify/assert" +) + +func TestRangeParsingSinglePort(t *testing.T) { + lo, hi := azure.ParsePortRangeString("22") + assert.Equal(t, uint16(22), lo) + assert.Equal(t, uint16(22), hi) +} + +func TestRangeParsingPortRange(t *testing.T) { + lo, hi := azure.ParsePortRangeString("22-80") + assert.Equal(t, uint16(22), lo) + assert.Equal(t, uint16(80), hi) +} + +func TestRangeParsingAsterisk(t *testing.T) { + lo, hi := azure.ParsePortRangeString("*") + assert.Equal(t, uint16(0), lo) + assert.Equal(t, uint16(65535), hi) +} + +func TestRuleSummaryAllowSourcePort(t *testing.T) { + summary := azure.NsgRuleSummary{} + summary.SourcePortRange = "22" + + result := summary.AllowsSourcePort("22") + assert.True(t, result) +} + +func TestRuleSummaryAllowSourcePortAsterisk(t *testing.T) { + summary := azure.NsgRuleSummary{} + summary.SourcePortRange = "*" + + rand := rand.New(rand.NewSource(time.Now().UnixNano())) + + result := summary.AllowsSourcePort(string(uint16(rand.Int()))) + assert.True(t, result) +} + +func TestRuleSummaryAllowDestinationPort(t *testing.T) { + summary := azure.NsgRuleSummary{} + summary.DestinationPortRange = "80" + + result := summary.AllowsDestinationPort("80") + assert.True(t, result) +} + +func TestRuleSummaryAllowDestinationPortAsterisk(t *testing.T) { + summary := azure.NsgRuleSummary{} + summary.DestinationPortRange = "*" + + rand := rand.New(rand.NewSource(time.Now().UnixNano())) + + result := summary.AllowsDestinationPort(string(uint16(rand.Int()))) + assert.True(t, result) +} + +func TestFindSummarizedRule(t *testing.T) { + ruleList := azure.NsgRuleSummaryList{} + rules := make([]azure.NsgRuleSummary, 0) + + // Create some rules + for i := 1; i <= 10; i++ { + rule := azure.NsgRuleSummary{} + rule.Name = fmt.Sprintf("rule_%d", i) + rules = append(rules, rule) + } + ruleList.SummarizedRules = rules + + // Look for a rule that exists + match1 := ruleList.FindRuleByName("rule_5") + assert.Equal(t, "rule_5", match1.Name) + + // Look for a rule that doesn't exist + match2 := ruleList.FindRuleByName("foo") + assert.Equal(t, azure.NsgRuleSummary{}, match2) +} From 6974b716d5a93458e70b28b59a8b629472194adb Mon Sep 17 00:00:00 2001 From: Mike Yeaney Date: Mon, 14 Sep 2020 15:27:11 -0400 Subject: [PATCH 17/56] Moving TF config into variables to help config updates --- examples/azure/terraform-azure-nsg-example/main.tf | 6 +++--- .../azure/terraform-azure-nsg-example/variables.tf | 12 ++++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/examples/azure/terraform-azure-nsg-example/main.tf b/examples/azure/terraform-azure-nsg-example/main.tf index 89d2bd68f..dc87b88b8 100644 --- a/examples/azure/terraform-azure-nsg-example/main.tf +++ b/examples/azure/terraform-azure-nsg-example/main.tf @@ -1,5 +1,5 @@ provider "azurerm" { - version = "=2.20.0" + version = "~>2.20" features {} } @@ -19,7 +19,7 @@ terraform { resource "azurerm_resource_group" "main" { name = "${var.prefix}-resources" - location = "East US" + location = var.location } # --------------------------------------------------------------------------------------------------------------------- @@ -87,7 +87,7 @@ resource "azurerm_virtual_machine" "main" { location = azurerm_resource_group.main.location resource_group_name = azurerm_resource_group.main.name network_interface_ids = [azurerm_network_interface.main.id] - vm_size = "Standard_B1s" + vm_size = var.vm_size delete_os_disk_on_termination = true delete_data_disks_on_termination = true diff --git a/examples/azure/terraform-azure-nsg-example/variables.tf b/examples/azure/terraform-azure-nsg-example/variables.tf index 8c231c502..ce58a7881 100644 --- a/examples/azure/terraform-azure-nsg-example/variables.tf +++ b/examples/azure/terraform-azure-nsg-example/variables.tf @@ -18,6 +18,12 @@ # These parameters have reasonable defaults. # --------------------------------------------------------------------------------------------------------------------- +variable "location" { + description = "The Azure region in which to deploy this sample" + type = string + default = "East US" +} + variable "hostname" { description = "The hostname of the new VM to be configured" type = string @@ -42,3 +48,9 @@ variable "username" { default = "testadmin" } +variable "vm_size" { + description = "The size of the VM to deploy" + type = string + default = "Standard_B1s" +} + From f12255a6019892b0c975face88533c549a2fe007 Mon Sep 17 00:00:00 2001 From: Mike Yeaney Date: Mon, 14 Sep 2020 15:56:24 -0400 Subject: [PATCH 18/56] Updated README fixes; fixed func signature update --- .../azure/terraform-azure-nsg-example/README.md | 14 +++++++------- test/azure/terraform_azure_nsg_example_test.go | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/azure/terraform-azure-nsg-example/README.md b/examples/azure/terraform-azure-nsg-example/README.md index 502a16fba..476d64f1e 100644 --- a/examples/azure/terraform-azure-nsg-example/README.md +++ b/examples/azure/terraform-azure-nsg-example/README.md @@ -1,4 +1,4 @@ -# Terraform Azure @Module@ Example +# Terraform Azure NSG Example This folder contains a simple Terraform module that deploys resources in [Azure](https://azure.microsoft.com/) to demonstrate how you can use Terratest to write automated tests for your Azure Terraform code. This module deploys the following: @@ -35,9 +35,9 @@ it should be free, but you are completely responsible for all Azure charges. 1. [Review environment variables](#review-environment-variables). 1. Install [Golang](https://golang.org/) and make sure this code is checked out into your `GOPATH`. 1. `cd test` -1. Make sure [the azure-sdk-for-go versions match](#check-go-dependencies) in [/test/go.mod](/test/go.mod) and in [test/azure/terraform_azure_@module@_test.go](/test/terraform_azure_nic_test.go). -1. `go build terraform_azure_@module@_test.go` -1. `go test -v -run TestTerraformAzure@Module@Example` +1. Make sure [the azure-sdk-for-go versions match](#check-go-dependencies) in [/test/go.mod](/test/go.mod) and in [test/azure/terraform_azure_nsg_example_test.go](/test/terraform_azure_nsg_example_test.go). +1. `go build terraform_azure_nsg_example_test.go` +1. `go test -v -run TestTerraformAzureNsgExample` ## Module test APIs @@ -92,12 +92,12 @@ Check that the `github.com/Azure/azure-sdk-for-go` version in your generated `go ### Check Azure-sdk-for-go version -Let's make sure [go.mod](https://github.com/gruntwork-io/terratest/blob/master/go.mod) includes the appropriate [azure-sdk-for-go version](https://github.com/Azure/azure-sdk-for-go/releases/tag/v38.1.0): +Let's make sure [go.mod](https://github.com/gruntwork-io/terratest/blob/master/go.mod) includes the appropriate [azure-sdk-for-go version](https://github.com/Azure/azure-sdk-for-go/releases/tag/v46.0.0): ```go require ( ... - github.com/Azure/azure-sdk-for-go v38.1.0+incompatible + github.com/Azure/azure-sdk-for-go v46.0.0+incompatible ... ) ``` @@ -105,7 +105,7 @@ require ( If we make changes to either the **go.mod** or the **go test file**, we should make sure that the go build command works still. ```powershell -go build terraform_azure_@module_test.go +go build terraform_azure_nsg_example_test.go ``` ## Review Environment Variables diff --git a/test/azure/terraform_azure_nsg_example_test.go b/test/azure/terraform_azure_nsg_example_test.go index a1eedc564..43a61471e 100644 --- a/test/azure/terraform_azure_nsg_example_test.go +++ b/test/azure/terraform_azure_nsg_example_test.go @@ -33,7 +33,7 @@ func TestTerraformAzureNsgExample(t *testing.T) { assert.Equal(t, 7, len(rules.SummarizedRules)) // We should have a rule named "allowSSH" - sshRule := rules.FindRuleByName(t, "allowSSH") + sshRule := rules.FindRuleByName("allowSSH") // That rule should allow port 22 inbound assert.True(t, sshRule.AllowsDestinationPort("22")) From efc0d058c4a1102321f53dc8f220ac187697cd11 Mon Sep 17 00:00:00 2001 From: Mike Yeaney Date: Mon, 14 Sep 2020 16:09:48 -0400 Subject: [PATCH 19/56] Fixed bad terraform example path --- test/azure/terraform_azure_nsg_example_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/azure/terraform_azure_nsg_example_test.go b/test/azure/terraform_azure_nsg_example_test.go index 43a61471e..d57c2e397 100644 --- a/test/azure/terraform_azure_nsg_example_test.go +++ b/test/azure/terraform_azure_nsg_example_test.go @@ -18,7 +18,7 @@ func TestTerraformAzureNsgExample(t *testing.T) { terraformOptions := &terraform.Options{ // The path to where our Terraform code is located - TerraformDir: "../../examples/terraform-azure-nsg-example", + TerraformDir: "../../examples/azure/terraform-azure-nsg-example", } defer terraform.Destroy(t, terraformOptions) From 18c96e0bc2a71a014f38fa0dc288c00ac107e8f7 Mon Sep 17 00:00:00 2001 From: Mike Yeaney Date: Mon, 14 Sep 2020 19:34:18 -0400 Subject: [PATCH 20/56] Fix linter errors --- modules/azure/nsg.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/azure/nsg.go b/modules/azure/nsg.go index 32bad124d..2e6e8a054 100644 --- a/modules/azure/nsg.go +++ b/modules/azure/nsg.go @@ -179,7 +179,7 @@ func ParsePortRangeString(rangeString string) (low uint16, hight uint16) { } // Is this a range? - if strings.Index(rangeString, "-") == -1 { + if !strings.Contains(rangeString, "-") { val, _ := strconv.ParseInt(rangeString, 10, 16) return uint16(val), uint16(val) } From 5ce77f18354ef9b514680a6c179f877d17368b42 Mon Sep 17 00:00:00 2001 From: Mike Yeaney Date: Mon, 14 Sep 2020 20:09:08 -0400 Subject: [PATCH 21/56] Updated linter and fixed remaining issues --- modules/azure/nsg.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/azure/nsg.go b/modules/azure/nsg.go index 2e6e8a054..b1be3f0f5 100644 --- a/modules/azure/nsg.go +++ b/modules/azure/nsg.go @@ -101,7 +101,10 @@ func bindRuleList(dest *[]NsgRuleSummary, source network.SecurityRuleListResultI for source.NotDone() { v := source.Value() *dest = append(*dest, convertToNsgRuleSummary(v.Name, v.SecurityRulePropertiesFormat)) - source.NextWithContext(context.Background()) + err := source.NextWithContext(context.Background()) + if err != nil { + panic(err) + } } } From bf2140bdec8415c4ee1dcf963e383d23522b15b4 Mon Sep 17 00:00:00 2001 From: Mike Yeaney Date: Fri, 18 Sep 2020 14:58:49 -0400 Subject: [PATCH 22/56] Working through PR feedback items --- .../terraform-azure-nsg-example/README.md | 91 +----------- modules/azure/nsg.go | 137 ++++++++++++------ {test => modules}/azure/nsg_test.go | 35 ++--- 3 files changed, 111 insertions(+), 152 deletions(-) rename {test => modules}/azure/nsg_test.go (67%) diff --git a/examples/azure/terraform-azure-nsg-example/README.md b/examples/azure/terraform-azure-nsg-example/README.md index 476d64f1e..6418200ef 100644 --- a/examples/azure/terraform-azure-nsg-example/README.md +++ b/examples/azure/terraform-azure-nsg-example/README.md @@ -37,93 +37,4 @@ it should be free, but you are completely responsible for all Azure charges. 1. `cd test` 1. Make sure [the azure-sdk-for-go versions match](#check-go-dependencies) in [/test/go.mod](/test/go.mod) and in [test/azure/terraform_azure_nsg_example_test.go](/test/terraform_azure_nsg_example_test.go). 1. `go build terraform_azure_nsg_example_test.go` -1. `go test -v -run TestTerraformAzureNsgExample` - -## Module test APIs - -This module exposes the following top-level API methods: - -- GetDefaultNsgRulesClientE returns a rules client that retuns the _default_ NSG rules
-`func GetDefaultNsgRulesClientE(subscriptionID string) (network.DefaultSecurityRulesClient, error)` -- GetCustomNsgRulesClientE returns a rules client that returns the _custom_ NSG rules
-`func GetCustomNsgRulesClientE(subscriptionID string) (network.SecurityRulesClient, error)` -- GetAllNSGRulesE returns a slice containing all rules from the NSG (including defauls and custom)
-`func GetAllNSGRulesE(resourceGroupName, nsgName, subscriptionID string) (NsgRuleSummaryList, error)` - -Note that the `GetAllNsgRulesE` method returns an `NsgRuleSummaryList`, which is defined as follows: - -- NsgRuleSummaryList holds a colleciton of NsgRuleSummary structs
-` type NsgRuleSummaryList struct { SummarizedRules []NsgRuleSummary } ` - -This collection wrapper has the following method: - -- FindRuleByName looks for a matching rule name
-`func (summarizedRules *NsgRuleSummaryList) FindRuleByName(t *testing.T, name string) NsgRuleSummary` - -Finally, the inner collection is defined as an `NsgRuleSummary` struct, defined as follows: - -``` -type NsgRuleSummary struct { - Name string - Description string - Protocol string - SourcePortRange string - DestinationPortRange string - SourceAddressPrefix string - DestinationAddressPrefix string - Access string - Priority int32 - Direction string -} -``` - -This wrapper has the following methods available: - -- AllowsDestinationPort checks to see if the rule allows a specific destination port
-`func (summarizedRule *NsgRuleSummary) AllowsDestinationPort(port string) bool` -- AllowsSourcePort checks to see if the rule allows a specific source port
-`func (summarizedRule *NsgRuleSummary) AllowsSourcePort(port string) bool` - -## Check Go Dependencies - -Check that the `github.com/Azure/azure-sdk-for-go` version in your generated `go.mod` for this test matches the version in the terratest [go.mod](https://github.com/gruntwork-io/terratest/blob/master/go.mod) file. - -> This was tested with **go1.14.4**. - -### Check Azure-sdk-for-go version - -Let's make sure [go.mod](https://github.com/gruntwork-io/terratest/blob/master/go.mod) includes the appropriate [azure-sdk-for-go version](https://github.com/Azure/azure-sdk-for-go/releases/tag/v46.0.0): - -```go -require ( - ... - github.com/Azure/azure-sdk-for-go v46.0.0+incompatible - ... -) -``` - -If we make changes to either the **go.mod** or the **go test file**, we should make sure that the go build command works still. - -```powershell -go build terraform_azure_nsg_example_test.go -``` - -## Review Environment Variables - -As part of configuring terraform for Azure, we'll want to check that we have set the appropriate [credentials](https://docs.microsoft.com/en-us/azure/terraform/terraform-install-configure?toc=https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fterraform%2Ftoc.json&bc=https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fbread%2Ftoc.json#set-up-terraform-access-to-azure) and also that we set the [environment variables](https://docs.microsoft.com/en-us/azure/terraform/terraform-install-configure?toc=https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fterraform%2Ftoc.json&bc=https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fbread%2Ftoc.json#configure-terraform-environment-variables) on the testing host. - -```bash -export ARM_CLIENT_ID=your_app_id -export ARM_CLIENT_SECRET=your_password -export ARM_SUBSCRIPTION_ID=your_subscription_id -export ARM_TENANT_ID=your_tenant_id -``` - -Note, in a Windows environment, these should be set as **system environment variables**. We can use a PowerShell console with administrative rights to update these environment variables: - -```powershell -[System.Environment]::SetEnvironmentVariable("ARM_CLIENT_ID",$your_app_id,[System.EnvironmentVariableTarget]::Machine) -[System.Environment]::SetEnvironmentVariable("ARM_CLIENT_SECRET",$your_password,[System.EnvironmentVariableTarget]::Machine) -[System.Environment]::SetEnvironmentVariable("ARM_SUBSCRIPTION_ID",$your_subscription_id,[System.EnvironmentVariableTarget]::Machine) -[System.Environment]::SetEnvironmentVariable("ARM_TENANT_ID",$your_tenant_id,[System.EnvironmentVariableTarget]::Machine) -``` +1. `go test -v -run TestTerraformAzureNsgExample` \ No newline at end of file diff --git a/modules/azure/nsg.go b/modules/azure/nsg.go index b1be3f0f5..cf307c397 100644 --- a/modules/azure/nsg.go +++ b/modules/azure/nsg.go @@ -2,13 +2,18 @@ package azure import ( "context" + "fmt" "strconv" "strings" + "testing" "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-09-01/network" + "github.com/stretchr/testify/assert" ) -// GetDefaultNsgRulesClientE returns a rules client +// GetDefaultNsgRulesClientE returns a rules client which can be used to read the list of *default* security rules +// defined on an network security group. Note that the "default" rules are those provided implicitly +// by the Azure platform. func GetDefaultNsgRulesClientE(subscriptionID string) (network.DefaultSecurityRulesClient, error) { // Validate Azure subscription ID subscriptionID, err := getTargetAzureSubscription(subscriptionID) @@ -28,7 +33,9 @@ func GetDefaultNsgRulesClientE(subscriptionID string) (network.DefaultSecurityRu return nsgClient, nil } -// GetCustomNsgRulesClientE returns a rules client +// GetCustomNsgRulesClientE returns a rules client which can be used to read the list of *custom* security rules +// defined on an network security group. Note that the "custom" rules are those defined by +// end users. func GetCustomNsgRulesClientE(subscriptionID string) (network.SecurityRulesClient, error) { // Validate Azure subscription ID subscriptionID, err := getTargetAzureSubscription(subscriptionID) @@ -48,42 +55,58 @@ func GetCustomNsgRulesClientE(subscriptionID string) (network.SecurityRulesClien return nsgClient, nil } -// GetAllNSGRulesE returns a slice containing all rules from the NSG (including defauls and custom) +// GetAllNSGRulesE returns a slice containing the combined "default" and "custom" rules from a network +// security group. func GetAllNSGRulesE(resourceGroupName, nsgName, subscriptionID string) (NsgRuleSummaryList, error) { defaultRulesClient, err := GetDefaultNsgRulesClientE(subscriptionID) if err != nil { return NsgRuleSummaryList{}, err } + // Get a client instance customRulesClient, err := GetCustomNsgRulesClientE(subscriptionID) if err != nil { return NsgRuleSummaryList{}, err } + // Read all default (platform) rules. defaultRuleList, err := defaultRulesClient.ListComplete(context.Background(), resourceGroupName, nsgName) if err != nil { return NsgRuleSummaryList{}, err } + // Read any custom (user provided) rules customRuleList, err := customRulesClient.ListComplete(context.Background(), resourceGroupName, nsgName) if err != nil { return NsgRuleSummaryList{}, err } - rules := make([]NsgRuleSummary, 0) - bindRuleList(&rules, defaultRuleList) - bindRuleList(&rules, customRuleList) + // Convert the default list to our summary type + boundDefaultRules, err := bindRuleList(defaultRuleList) + if err != nil { + return NsgRuleSummaryList{}, err + } + + // Convert the custom list to our summary type + boundCustomRules, err := bindRuleList(customRuleList) + if err != nil { + return NsgRuleSummaryList{}, err + } + + // Join the summarized lists and wrap in NsgRuleSummaryList struct + allRules := append(boundDefaultRules, boundCustomRules...) ruleList := NsgRuleSummaryList{} - ruleList.SummarizedRules = rules + ruleList.SummarizedRules = allRules return ruleList, nil } -// NsgRuleSummaryList holds a colleciton of NsgRuleSummary structs +// NsgRuleSummaryList holds a colleciton of NsgRuleSummary rules type NsgRuleSummaryList struct { SummarizedRules []NsgRuleSummary } -// NsgRuleSummary is a string-based summary of an NSG rule with methods attached. +// NsgRuleSummary is a string-based (non-pointer) summary of an NSG rule with several helper methods attached +// to help with verification of rule configuratoin. type NsgRuleSummary struct { Name string Description string @@ -97,24 +120,28 @@ type NsgRuleSummary struct { Direction string } -func bindRuleList(dest *[]NsgRuleSummary, source network.SecurityRuleListResultIterator) { +// bindRuleList takes a raw list of security rules from the SDK and converts them into a string-based +// summary struct. +func bindRuleList(source network.SecurityRuleListResultIterator) ([]NsgRuleSummary, error) { + rules := make([]NsgRuleSummary, 0) for source.NotDone() { v := source.Value() - *dest = append(*dest, convertToNsgRuleSummary(v.Name, v.SecurityRulePropertiesFormat)) + rules = append(rules, convertToNsgRuleSummary(v.Name, v.SecurityRulePropertiesFormat)) err := source.NextWithContext(context.Background()) if err != nil { - panic(err) + return []NsgRuleSummary{}, err } } + return rules, nil } +// convertToNsgRuleSummary converst the raw SDK security rule type into a summarized struct, flattening the +// rules properties and name into a single, string-based struct. func convertToNsgRuleSummary(name *string, rule *network.SecurityRulePropertiesFormat) NsgRuleSummary { summary := NsgRuleSummary{} if rule.Description != nil { summary.Description = *rule.Description - } else { - summary.Description = "" } summary.Name = *name @@ -129,7 +156,7 @@ func convertToNsgRuleSummary(name *string, rule *network.SecurityRulePropertiesF return summary } -// FindRuleByName looks for a matching rule name +// FindRuleByName looks for a matching rule by name within a collection of rules. func (summarizedRules *NsgRuleSummaryList) FindRuleByName(name string) NsgRuleSummary { for _, r := range summarizedRules.SummarizedRules { if r.Name == name { @@ -140,55 +167,79 @@ func (summarizedRules *NsgRuleSummaryList) FindRuleByName(name string) NsgRuleSu return NsgRuleSummary{} } -// AllowsDestinationPort checks to see if the rule allows a specific destination port. -func (summarizedRule *NsgRuleSummary) AllowsDestinationPort(port string) bool { - if summarizedRule.DestinationPortRange == "*" { - return true - } +// AllowsDestinationPort checks to see if the rule allows a specific destination port. This is helpful when verifying +// that a given rule is configured properly for a given port. +func (summarizedRule *NsgRuleSummary) AllowsDestinationPort(t *testing.T, port string) bool { + allowed, err := portRangeAllowsPort(summarizedRule.DestinationPortRange, port) + assert.NoError(t, err) + return allowed +} - low, high := ParsePortRangeString(summarizedRule.DestinationPortRange) - portAsInt, _ := strconv.ParseInt(port, 10, 16) +// AllowsSourcePort checks to see if the rule allows a specific source port. This is helpful when verifying +// that a given rule is configured properly for a given port. +func (summarizedRule *NsgRuleSummary) AllowsSourcePort(t *testing.T, port string) bool { + allowed, err := portRangeAllowsPort(summarizedRule.SourcePortRange, port) + assert.NoError(t, err) + return allowed +} - if (port == "*") && (low == 0) && (high == 65535) { - return true +// portRangeAllowsPort is the internal impelmentation of AllowsSourcePort and AllowsDestinationPort. +func portRangeAllowsPort(portRange string, port string) (bool, error) { + if portRange == "*" { + return true, nil } - return ((low <= uint16(portAsInt)) && (uint16(portAsInt) <= high)) -} - -// AllowsSourcePort checks to see if the rule allows a specific source port. -func (summarizedRule *NsgRuleSummary) AllowsSourcePort(port string) bool { - if summarizedRule.SourcePortRange == "*" { - return true + // Decode the provided port range + low, high, parseErr := parsePortRangeString(portRange) + if parseErr != nil { + return false, parseErr } - low, high := ParsePortRangeString(summarizedRule.SourcePortRange) - portAsInt, _ := strconv.ParseInt(port, 10, 16) + // Decode user-provided port + portAsInt, parseErr := strconv.ParseInt(port, 10, 16) + if (parseErr != nil) && (port != "*") { + return false, parseErr + } if (port == "*") && (low == 0) && (high == 65535) { - return true + return true, nil } - return ((low <= uint16(portAsInt)) && (uint16(portAsInt) <= high)) + return ((uint16(portAsInt) >= low) && (uint16(portAsInt) <= high)), nil } -// ParsePortRangeString decodes a range string ("2-100") or a single digit ("22") and returns +// parsePortRangeString decodes a range string ("2-100") or a single digit ("22") and returns // a tuple in [low, hi] form. Note that if a single digit is supplied, both members of the // return tuple will be the same value (e.g., "22" returns (22, 22)) -func ParsePortRangeString(rangeString string) (low uint16, hight uint16) { +func parsePortRangeString(rangeString string) (uint16, uint16, error) { // Is this an asterisk? if rangeString == "*" { - return uint16(0), uint16(65535) + return uint16(0), uint16(65535), nil } // Is this a range? if !strings.Contains(rangeString, "-") { - val, _ := strconv.ParseInt(rangeString, 10, 16) - return uint16(val), uint16(val) + val, parseErr := strconv.ParseInt(rangeString, 10, 16) + if parseErr != nil { + return 0, 0, parseErr + } + return uint16(val), uint16(val), nil } parts := strings.Split(rangeString, "-") - lowVal, _ := strconv.ParseInt(parts[0], 10, 16) - highVal, _ := strconv.ParseInt(parts[1], 10, 16) - return uint16(lowVal), uint16(highVal) + if len(parts) != 2 { + return 0, 0, fmt.Errorf("Invalid port range specified; must be of the format '{low port}-{high port}'") + } + + lowVal, parseErr := strconv.ParseInt(parts[0], 10, 16) + if parseErr != nil { + return 0, 0, parseErr + } + + highVal, parseErr := strconv.ParseInt(parts[1], 10, 16) + if parseErr != nil { + return 0, 0, parseErr + } + + return uint16(lowVal), uint16(highVal), nil } diff --git a/test/azure/nsg_test.go b/modules/azure/nsg_test.go similarity index 67% rename from test/azure/nsg_test.go rename to modules/azure/nsg_test.go index 173a9fb7e..d8edaba7b 100644 --- a/test/azure/nsg_test.go +++ b/modules/azure/nsg_test.go @@ -1,9 +1,7 @@ -// +build azure - // NOTE: We use build tags to differentiate azure testing because we currently do not have azure access setup for // CircleCI. -package test +package azure import ( "fmt" @@ -11,71 +9,70 @@ import ( "testing" "time" - "github.com/gruntwork-io/terratest/modules/azure" "github.com/stretchr/testify/assert" ) func TestRangeParsingSinglePort(t *testing.T) { - lo, hi := azure.ParsePortRangeString("22") + lo, hi, _ := parsePortRangeString("22") assert.Equal(t, uint16(22), lo) assert.Equal(t, uint16(22), hi) } func TestRangeParsingPortRange(t *testing.T) { - lo, hi := azure.ParsePortRangeString("22-80") + lo, hi, _ := parsePortRangeString("22-80") assert.Equal(t, uint16(22), lo) assert.Equal(t, uint16(80), hi) } func TestRangeParsingAsterisk(t *testing.T) { - lo, hi := azure.ParsePortRangeString("*") + lo, hi, _ := parsePortRangeString("*") assert.Equal(t, uint16(0), lo) assert.Equal(t, uint16(65535), hi) } func TestRuleSummaryAllowSourcePort(t *testing.T) { - summary := azure.NsgRuleSummary{} + summary := NsgRuleSummary{} summary.SourcePortRange = "22" - result := summary.AllowsSourcePort("22") + result := summary.AllowsSourcePort(t, "22") assert.True(t, result) } func TestRuleSummaryAllowSourcePortAsterisk(t *testing.T) { - summary := azure.NsgRuleSummary{} + summary := NsgRuleSummary{} summary.SourcePortRange = "*" rand := rand.New(rand.NewSource(time.Now().UnixNano())) - result := summary.AllowsSourcePort(string(uint16(rand.Int()))) + result := summary.AllowsSourcePort(t, string(uint16(rand.Int()))) assert.True(t, result) } func TestRuleSummaryAllowDestinationPort(t *testing.T) { - summary := azure.NsgRuleSummary{} + summary := NsgRuleSummary{} summary.DestinationPortRange = "80" - result := summary.AllowsDestinationPort("80") + result := summary.AllowsDestinationPort(t, "80") assert.True(t, result) } func TestRuleSummaryAllowDestinationPortAsterisk(t *testing.T) { - summary := azure.NsgRuleSummary{} + summary := NsgRuleSummary{} summary.DestinationPortRange = "*" rand := rand.New(rand.NewSource(time.Now().UnixNano())) - result := summary.AllowsDestinationPort(string(uint16(rand.Int()))) + result := summary.AllowsDestinationPort(t, string(uint16(rand.Int()))) assert.True(t, result) } func TestFindSummarizedRule(t *testing.T) { - ruleList := azure.NsgRuleSummaryList{} - rules := make([]azure.NsgRuleSummary, 0) + ruleList := NsgRuleSummaryList{} + rules := make([]NsgRuleSummary, 0) // Create some rules for i := 1; i <= 10; i++ { - rule := azure.NsgRuleSummary{} + rule := NsgRuleSummary{} rule.Name = fmt.Sprintf("rule_%d", i) rules = append(rules, rule) } @@ -87,5 +84,5 @@ func TestFindSummarizedRule(t *testing.T) { // Look for a rule that doesn't exist match2 := ruleList.FindRuleByName("foo") - assert.Equal(t, azure.NsgRuleSummary{}, match2) + assert.Equal(t, NsgRuleSummary{}, match2) } From d9fdedd7ade3b9dad3386714bfc4e37ae3f7df43 Mon Sep 17 00:00:00 2001 From: Mike Yeaney Date: Mon, 21 Sep 2020 10:35:49 -0400 Subject: [PATCH 23/56] Switch tests over to table-based testing --- modules/azure/nsg.go | 85 +++++++++++++--------- modules/azure/nsg_test.go | 147 +++++++++++++++++++++++--------------- 2 files changed, 143 insertions(+), 89 deletions(-) diff --git a/modules/azure/nsg.go b/modules/azure/nsg.go index cf307c397..1697defd3 100644 --- a/modules/azure/nsg.go +++ b/modules/azure/nsg.go @@ -11,6 +11,26 @@ import ( "github.com/stretchr/testify/assert" ) +// NsgRuleSummaryList holds a colleciton of NsgRuleSummary rules +type NsgRuleSummaryList struct { + SummarizedRules []NsgRuleSummary +} + +// NsgRuleSummary is a string-based (non-pointer) summary of an NSG rule with several helper methods attached +// to help with verification of rule configuratoin. +type NsgRuleSummary struct { + Name string + Description string + Protocol string + SourcePortRange string + DestinationPortRange string + SourceAddressPrefix string + DestinationAddressPrefix string + Access string + Priority int32 + Direction string +} + // GetDefaultNsgRulesClientE returns a rules client which can be used to read the list of *default* security rules // defined on an network security group. Note that the "default" rules are those provided implicitly // by the Azure platform. @@ -55,7 +75,7 @@ func GetCustomNsgRulesClientE(subscriptionID string) (network.SecurityRulesClien return nsgClient, nil } -// GetAllNSGRulesE returns a slice containing the combined "default" and "custom" rules from a network +// GetAllNSGRulesE returns an NsgRuleSummaryList instance containing the combined "default" and "custom" rules from a network // security group. func GetAllNSGRulesE(resourceGroupName, nsgName, subscriptionID string) (NsgRuleSummaryList, error) { defaultRulesClient, err := GetDefaultNsgRulesClientE(subscriptionID) @@ -100,26 +120,6 @@ func GetAllNSGRulesE(resourceGroupName, nsgName, subscriptionID string) (NsgRule return ruleList, nil } -// NsgRuleSummaryList holds a colleciton of NsgRuleSummary rules -type NsgRuleSummaryList struct { - SummarizedRules []NsgRuleSummary -} - -// NsgRuleSummary is a string-based (non-pointer) summary of an NSG rule with several helper methods attached -// to help with verification of rule configuratoin. -type NsgRuleSummary struct { - Name string - Description string - Protocol string - SourcePortRange string - DestinationPortRange string - SourceAddressPrefix string - DestinationAddressPrefix string - Access string - Priority int32 - Direction string -} - // bindRuleList takes a raw list of security rules from the SDK and converts them into a string-based // summary struct. func bindRuleList(source network.SecurityRuleListResultIterator) ([]NsgRuleSummary, error) { @@ -139,24 +139,36 @@ func bindRuleList(source network.SecurityRuleListResultIterator) ([]NsgRuleSumma // rules properties and name into a single, string-based struct. func convertToNsgRuleSummary(name *string, rule *network.SecurityRulePropertiesFormat) NsgRuleSummary { summary := NsgRuleSummary{} - - if rule.Description != nil { - summary.Description = *rule.Description - } - - summary.Name = *name + summary.Description = safePtrToString(rule.Description) + summary.Name = safePtrToString(name) summary.Protocol = string(rule.Protocol) - summary.SourcePortRange = *rule.SourcePortRange - summary.DestinationPortRange = *rule.DestinationPortRange - summary.SourceAddressPrefix = *rule.SourceAddressPrefix - summary.DestinationAddressPrefix = *rule.DestinationAddressPrefix + summary.SourcePortRange = safePtrToString(rule.SourcePortRange) + summary.DestinationPortRange = safePtrToString(rule.DestinationPortRange) + summary.SourceAddressPrefix = safePtrToString(rule.SourceAddressPrefix) + summary.DestinationAddressPrefix = safePtrToString(rule.DestinationAddressPrefix) summary.Access = string(rule.Access) - summary.Priority = *rule.Priority + summary.Priority = safePtrToInt32(rule.Priority) summary.Direction = string(rule.Direction) return summary } -// FindRuleByName looks for a matching rule by name within a collection of rules. +// safePtrToString converts a string pointer to a non-pointer string value, or to "" if the pointer is nil. +func safePtrToString(raw *string) string { + if raw == nil { + return "" + } + return *raw +} + +// safePtrToInt32 converts a int32 pointer to a non-pointer int32 value, or to 0 if the pointer is nil. +func safePtrToInt32(raw *int32) int32 { + if raw == nil { + return 0 + } + return *raw +} + +// FindRuleByName looks for a matching rule by name within the current collection of rules. func (summarizedRules *NsgRuleSummaryList) FindRuleByName(name string) NsgRuleSummary { for _, r := range summarizedRules.SummarizedRules { if r.Name == name { @@ -241,5 +253,12 @@ func parsePortRangeString(rangeString string) (uint16, uint16, error) { return 0, 0, parseErr } + // Normalize ordering + if lowVal > highVal { + temp := lowVal + lowVal = highVal + highVal = temp + } + return uint16(lowVal), uint16(highVal), nil } diff --git a/modules/azure/nsg_test.go b/modules/azure/nsg_test.go index d8edaba7b..df4816477 100644 --- a/modules/azure/nsg_test.go +++ b/modules/azure/nsg_test.go @@ -5,84 +5,119 @@ package azure import ( "fmt" - "math/rand" "testing" - "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -func TestRangeParsingSinglePort(t *testing.T) { - lo, hi, _ := parsePortRangeString("22") - assert.Equal(t, uint16(22), lo) - assert.Equal(t, uint16(22), hi) -} - -func TestRangeParsingPortRange(t *testing.T) { - lo, hi, _ := parsePortRangeString("22-80") - assert.Equal(t, uint16(22), lo) - assert.Equal(t, uint16(80), hi) -} - -func TestRangeParsingAsterisk(t *testing.T) { - lo, hi, _ := parsePortRangeString("*") - assert.Equal(t, uint16(0), lo) - assert.Equal(t, uint16(65535), hi) -} - -func TestRuleSummaryAllowSourcePort(t *testing.T) { - summary := NsgRuleSummary{} - summary.SourcePortRange = "22" - - result := summary.AllowsSourcePort(t, "22") - assert.True(t, result) -} - -func TestRuleSummaryAllowSourcePortAsterisk(t *testing.T) { - summary := NsgRuleSummary{} - summary.SourcePortRange = "*" - - rand := rand.New(rand.NewSource(time.Now().UnixNano())) +func TestPortRangeParsing(t *testing.T) { + var cases = []struct { + portRange string + expectedLo int + expectedHi int + expectsError bool + }{ + {"22", 22, 22, false}, + {"22-80", 22, 80, false}, + {"*", 0, 65535, false}, + {"*-*", 0, 0, true}, + {"22-", 0, 0, true}, + {"-80", 0, 0, true}, + {"-", 0, 0, true}, + {"80-22", 22, 80, false}, + } - result := summary.AllowsSourcePort(t, string(uint16(rand.Int()))) - assert.True(t, result) + for _, tt := range cases { + t.Run(tt.portRange, func(t *testing.T) { + lo, hi, err := parsePortRangeString(tt.portRange) + if !tt.expectsError { + require.NoError(t, err) + } + assert.Equal(t, tt.expectedLo, int(lo)) + assert.Equal(t, tt.expectedHi, int(hi)) + }) + } } -func TestRuleSummaryAllowDestinationPort(t *testing.T) { - summary := NsgRuleSummary{} - summary.DestinationPortRange = "80" +func TestAllowSourcePort(t *testing.T) { + var cases = []struct { + CaseName string + SourcePortRange string + TestPort string + Result bool + }{ + {"22 allows 22", "22", "22", true}, + {"22 doesn't allow 80", "22", "80", false}, + {"Any allows any", "*", "*", true}, + } - result := summary.AllowsDestinationPort(t, "80") - assert.True(t, result) + for _, tt := range cases { + t.Run(tt.CaseName, func(t *testing.T) { + summary := NsgRuleSummary{} + summary.SourcePortRange = tt.SourcePortRange + result := summary.AllowsSourcePort(t, tt.TestPort) + assert.Equal(t, result, tt.Result) + }) + } } -func TestRuleSummaryAllowDestinationPortAsterisk(t *testing.T) { - summary := NsgRuleSummary{} - summary.DestinationPortRange = "*" - - rand := rand.New(rand.NewSource(time.Now().UnixNano())) +func TestAllowDestinationPort(t *testing.T) { + var cases = []struct { + CaseName string + SourcePortRange string + TestPort string + Result bool + }{ + {"22 allows 22", "22", "22", true}, + {"22 doesn't allow 80", "22", "80", false}, + {"Any allows any", "*", "*", true}, + } - result := summary.AllowsDestinationPort(t, string(uint16(rand.Int()))) - assert.True(t, result) + for _, tt := range cases { + t.Run(tt.CaseName, func(t *testing.T) { + summary := NsgRuleSummary{} + summary.DestinationPortRange = tt.SourcePortRange + result := summary.AllowsDestinationPort(t, tt.TestPort) + assert.Equal(t, result, tt.Result) + }) + } } func TestFindSummarizedRule(t *testing.T) { + var cases = []struct { + SearchString string + Result bool + }{ + {"rule_1", true}, + {"rule_2", true}, + {"rule_3", true}, + {"rule_4", true}, + {"rule_5", true}, + {"rule_6", false}, + {"", false}, + {"foo", false}, + } + ruleList := NsgRuleSummaryList{} rules := make([]NsgRuleSummary, 0) - // Create some rules - for i := 1; i <= 10; i++ { + // Create some base rules + for i := 1; i <= 5; i++ { rule := NsgRuleSummary{} rule.Name = fmt.Sprintf("rule_%d", i) rules = append(rules, rule) } ruleList.SummarizedRules = rules - // Look for a rule that exists - match1 := ruleList.FindRuleByName("rule_5") - assert.Equal(t, "rule_5", match1.Name) - - // Look for a rule that doesn't exist - match2 := ruleList.FindRuleByName("foo") - assert.Equal(t, NsgRuleSummary{}, match2) + for _, tt := range cases { + t.Run(tt.SearchString, func(t *testing.T) { + match := ruleList.FindRuleByName(tt.SearchString) + if tt.Result { + assert.Equal(t, tt.SearchString, match.Name) + } else { + assert.Equal(t, match, NsgRuleSummary{}) + } + }) + } } From 29255e149eb04d67c6924934613d100cbd3f7ed0 Mon Sep 17 00:00:00 2001 From: Mike Yeaney Date: Mon, 21 Sep 2020 11:05:55 -0400 Subject: [PATCH 24/56] Fixing up some additional cases; considering the Allow/Deny state of the rules now --- modules/azure/nsg.go | 18 +++++++++++---- modules/azure/nsg_test.go | 48 ++++++++++++++++++++++++++++++++------- 2 files changed, 53 insertions(+), 13 deletions(-) diff --git a/modules/azure/nsg.go b/modules/azure/nsg.go index 1697defd3..aba925638 100644 --- a/modules/azure/nsg.go +++ b/modules/azure/nsg.go @@ -184,7 +184,7 @@ func (summarizedRules *NsgRuleSummaryList) FindRuleByName(name string) NsgRuleSu func (summarizedRule *NsgRuleSummary) AllowsDestinationPort(t *testing.T, port string) bool { allowed, err := portRangeAllowsPort(summarizedRule.DestinationPortRange, port) assert.NoError(t, err) - return allowed + return allowed && (summarizedRule.Access == "Allow") } // AllowsSourcePort checks to see if the rule allows a specific source port. This is helpful when verifying @@ -192,7 +192,7 @@ func (summarizedRule *NsgRuleSummary) AllowsDestinationPort(t *testing.T, port s func (summarizedRule *NsgRuleSummary) AllowsSourcePort(t *testing.T, port string) bool { allowed, err := portRangeAllowsPort(summarizedRule.SourcePortRange, port) assert.NoError(t, err) - return allowed + return allowed && (summarizedRule.Access == "Allow") } // portRangeAllowsPort is the internal impelmentation of AllowsSourcePort and AllowsDestinationPort. @@ -213,10 +213,12 @@ func portRangeAllowsPort(portRange string, port string) (bool, error) { return false, parseErr } + // If the user wants to check "all", make sure we parsed input range to include all ports. if (port == "*") && (low == 0) && (high == 65535) { return true, nil } + // Evaluate and return return ((uint16(portAsInt) >= low) && (uint16(portAsInt) <= high)), nil } @@ -224,12 +226,12 @@ func portRangeAllowsPort(portRange string, port string) (bool, error) { // a tuple in [low, hi] form. Note that if a single digit is supplied, both members of the // return tuple will be the same value (e.g., "22" returns (22, 22)) func parsePortRangeString(rangeString string) (uint16, uint16, error) { - // Is this an asterisk? + // An asterisk means all ports if rangeString == "*" { return uint16(0), uint16(65535), nil } - // Is this a range? + // Check for range string that contains hyphen separator if !strings.Contains(rangeString, "-") { val, parseErr := strconv.ParseInt(rangeString, 10, 16) if parseErr != nil { @@ -238,27 +240,33 @@ func parsePortRangeString(rangeString string) (uint16, uint16, error) { return uint16(val), uint16(val), nil } + // Split the rang into parts and validate parts := strings.Split(rangeString, "-") if len(parts) != 2 { return 0, 0, fmt.Errorf("Invalid port range specified; must be of the format '{low port}-{high port}'") } + // Assume the low port is listed first; parse it lowVal, parseErr := strconv.ParseInt(parts[0], 10, 16) if parseErr != nil { return 0, 0, parseErr } + // Assume the hi port is listed first; parse it highVal, parseErr := strconv.ParseInt(parts[1], 10, 16) if parseErr != nil { return 0, 0, parseErr } - // Normalize ordering + // Normalize ordering in the case that low and hi were reversed. + // This should _never_ happen, as the Azure API's won't allow it, but + // we shouldn't fail if it's the case. if lowVal > highVal { temp := lowVal lowVal = highVal highVal = temp } + // Return values return uint16(lowVal), uint16(highVal), nil } diff --git a/modules/azure/nsg_test.go b/modules/azure/nsg_test.go index df4816477..f03360580 100644 --- a/modules/azure/nsg_test.go +++ b/modules/azure/nsg_test.go @@ -9,6 +9,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-09-01/network" ) func TestPortRangeParsing(t *testing.T) { @@ -40,24 +42,51 @@ func TestPortRangeParsing(t *testing.T) { } } +func TestNsgRuleSummaryConverstion(t *testing.T) { + // Quick test to make sure the safe nil handling is working + var name = "test name" + var sdkStruct = network.SecurityRulePropertiesFormat{} + sdkStruct.Description = nil + sdkStruct.Protocol = network.SecurityRuleProtocolTCP + sdkStruct.SourcePortRange = nil + sdkStruct.DestinationPortRange = nil + sdkStruct.SourceAddressPrefix = nil + sdkStruct.DestinationAddressPrefix = nil + sdkStruct.Access = network.SecurityRuleAccessAllow + sdkStruct.Priority = nil + sdkStruct.Direction = network.SecurityRuleDirectionInbound + + // Verify the nil values were correctly defaulted to "" without a panic + result := convertToNsgRuleSummary(&name, &sdkStruct) + assert.Equal(t, "", result.Description) + assert.Equal(t, "", result.SourcePortRange) + assert.Equal(t, "", result.DestinationPortRange) + assert.Equal(t, "", result.SourceAddressPrefix) + assert.Equal(t, "", result.DestinationAddressPrefix) + assert.Equal(t, int32(0), result.Priority) +} + func TestAllowSourcePort(t *testing.T) { var cases = []struct { CaseName string SourcePortRange string + Access string TestPort string Result bool }{ - {"22 allows 22", "22", "22", true}, - {"22 doesn't allow 80", "22", "80", false}, - {"Any allows any", "*", "*", true}, + {"22 allowed", "22", "Allow", "22", true}, + {"22 denied", "22", "Deny", "22", false}, + {"22 doesn't allow 80", "22", "Allow", "80", false}, + {"Any allows any", "*", "Allow", "*", true}, } for _, tt := range cases { t.Run(tt.CaseName, func(t *testing.T) { summary := NsgRuleSummary{} summary.SourcePortRange = tt.SourcePortRange + summary.Access = tt.Access result := summary.AllowsSourcePort(t, tt.TestPort) - assert.Equal(t, result, tt.Result) + assert.Equal(t, tt.Result, result) }) } } @@ -66,20 +95,23 @@ func TestAllowDestinationPort(t *testing.T) { var cases = []struct { CaseName string SourcePortRange string + Access string TestPort string Result bool }{ - {"22 allows 22", "22", "22", true}, - {"22 doesn't allow 80", "22", "80", false}, - {"Any allows any", "*", "*", true}, + {"22 allowed", "22", "Allow", "22", true}, + {"22 denied", "22", "Deny", "22", false}, + {"22 doesn't allow 80", "22", "Allow", "80", false}, + {"Any allows any", "*", "Allow", "*", true}, } for _, tt := range cases { t.Run(tt.CaseName, func(t *testing.T) { summary := NsgRuleSummary{} summary.DestinationPortRange = tt.SourcePortRange + summary.Access = tt.Access result := summary.AllowsDestinationPort(t, tt.TestPort) - assert.Equal(t, result, tt.Result) + assert.Equal(t, tt.Result, result) }) } } From 25d7d2b2ede1b0ca87d913fce4dac65ada40e734 Mon Sep 17 00:00:00 2001 From: Mike Yeaney Date: Mon, 21 Sep 2020 11:08:38 -0400 Subject: [PATCH 25/56] Fixing doc links --- examples/azure/terraform-azure-nsg-example/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/azure/terraform-azure-nsg-example/README.md b/examples/azure/terraform-azure-nsg-example/README.md index 6418200ef..ba842947b 100644 --- a/examples/azure/terraform-azure-nsg-example/README.md +++ b/examples/azure/terraform-azure-nsg-example/README.md @@ -34,7 +34,7 @@ it should be free, but you are completely responsible for all Azure charges. 1. Install [Terraform](https://www.terraform.io/) and make sure it's on your `PATH`. 1. [Review environment variables](#review-environment-variables). 1. Install [Golang](https://golang.org/) and make sure this code is checked out into your `GOPATH`. -1. `cd test` +1. `cd test/azure` 1. Make sure [the azure-sdk-for-go versions match](#check-go-dependencies) in [/test/go.mod](/test/go.mod) and in [test/azure/terraform_azure_nsg_example_test.go](/test/terraform_azure_nsg_example_test.go). 1. `go build terraform_azure_nsg_example_test.go` 1. `go test -v -run TestTerraformAzureNsgExample` \ No newline at end of file From f29c781b1ab43b40491d4bee44e13608c5df0da2 Mon Sep 17 00:00:00 2001 From: Mike Yeaney Date: Mon, 21 Sep 2020 20:36:06 -0400 Subject: [PATCH 26/56] Working on variable uniqueness --- .../azure/terraform-azure-nsg-example/main.tf | 21 +++--- .../terraform-azure-nsg-example/variables.tf | 71 +++++++++++++++---- .../azure/terraform_azure_nsg_example_test.go | 53 +++++++++++++- 3 files changed, 120 insertions(+), 25 deletions(-) diff --git a/examples/azure/terraform-azure-nsg-example/main.tf b/examples/azure/terraform-azure-nsg-example/main.tf index dc87b88b8..a06316a09 100644 --- a/examples/azure/terraform-azure-nsg-example/main.tf +++ b/examples/azure/terraform-azure-nsg-example/main.tf @@ -18,7 +18,7 @@ terraform { # --------------------------------------------------------------------------------------------------------------------- resource "azurerm_resource_group" "main" { - name = "${var.prefix}-resources" + name = var.resource_group_name location = var.location } @@ -27,33 +27,33 @@ resource "azurerm_resource_group" "main" { # --------------------------------------------------------------------------------------------------------------------- resource "azurerm_virtual_network" "main" { - name = "${var.prefix}-network" + name = var.vnet_name address_space = ["10.0.0.0/16"] location = azurerm_resource_group.main.location resource_group_name = azurerm_resource_group.main.name } resource "azurerm_subnet" "internal" { - name = "internal" + name = var.subnet_name resource_group_name = azurerm_resource_group.main.name virtual_network_name = azurerm_virtual_network.main.name address_prefixes = ["10.0.17.0/24"] } resource "azurerm_network_interface" "main" { - name = "${var.prefix}-nic" + name = var.vm_nic_name location = azurerm_resource_group.main.location resource_group_name = azurerm_resource_group.main.name ip_configuration { - name = "terratestconfiguration1" + name = var.vm_nic_ip_config_name subnet_id = azurerm_subnet.internal.id private_ip_address_allocation = "Dynamic" } } resource "azurerm_network_security_group" "main" { - name = "testSecurityGroup1" + name = var.nsg_name location = azurerm_resource_group.main.location resource_group_name = azurerm_resource_group.main.name } @@ -64,8 +64,8 @@ resource "azurerm_network_interface_security_group_association" "main" { } resource "azurerm_network_security_rule" "main" { - name = "allowSSH" - description = "allowSSH" + name = var.nsg_rule_name + description = var.nsg_rule_name priority = 100 direction = "Inbound" access = "Allow" @@ -80,10 +80,11 @@ resource "azurerm_network_security_rule" "main" { # --------------------------------------------------------------------------------------------------------------------- # DEPLOY A VIRTUAL MACHINE RUNNING UBUNTU +# This VM does not actually do anything and is the smallest size VM available with an Ubuntu image # --------------------------------------------------------------------------------------------------------------------- resource "azurerm_virtual_machine" "main" { - name = "${var.prefix}-vm" + name = var.vm_name location = azurerm_resource_group.main.location resource_group_name = azurerm_resource_group.main.name network_interface_ids = [azurerm_network_interface.main.id] @@ -99,7 +100,7 @@ resource "azurerm_virtual_machine" "main" { } storage_os_disk { - name = "terratestosdisk1" + name = var.os_disk_name caching = "ReadWrite" create_option = "FromImage" managed_disk_type = "Standard_LRS" diff --git a/examples/azure/terraform-azure-nsg-example/variables.tf b/examples/azure/terraform-azure-nsg-example/variables.tf index ce58a7881..f3a5140c4 100644 --- a/examples/azure/terraform-azure-nsg-example/variables.tf +++ b/examples/azure/terraform-azure-nsg-example/variables.tf @@ -18,34 +18,58 @@ # These parameters have reasonable defaults. # --------------------------------------------------------------------------------------------------------------------- +variable "resource_group_name" { + description = "Name for the resource group holding resources for this example" + type = string + default = "terratest-nsg-example" +} + variable "location" { description = "The Azure region in which to deploy this sample" type = string default = "East US" } -variable "hostname" { - description = "The hostname of the new VM to be configured" +variable "vnet_name" { + description = "Name for the example virtual network" type = string - default = "terratest-vm" + default = "terratest-nsg-example-network" } -variable "password" { - description = "The password to configure for SSH access" +variable "subnet_name" { + description = "Name for the example virtual network default subnet" type = string - default = "HorriblePassword1234!" + default = "terratest-nsg-example-subnet" } -variable "prefix" { - description = "The prefix that will be attached to all resources deployed" +variable "vm_nic_name" { + description = "Name for the NIC attached to our example VM." type = string - default = "terratest-nsg-example" + default = "terratest-nsg-example-nic" } -variable "username" { - description = "The username to be provisioned into your VM" +variable "vm_nic_ip_config_name" { + description = "Name for the NIC IP configuration attached to our example VM." type = string - default = "testadmin" + default = "terratest-nsg-example-nic-ip-config" +} + +variable "nsg_name" { + description = "Name for the example NSG." + type = string + default = "terratest-nsg-example-nsg" +} + +variable "nsg_rule_name" { + description = "Name for the example NSG rule used in this example." + type = string + default = "terratest-nsg-example-nsg-rule" +} + +variable "vm_name" { + description = "The name of the VM used in this example" + type = string + default = "terratest-nsg-example-vm" } variable "vm_size" { @@ -54,3 +78,26 @@ variable "vm_size" { default = "Standard_B1s" } +variable "hostname" { + description = "The hostname of the new VM to be configured" + type = string + default = "terratest-nsg-example-vm" +} + +variable "os_disk_name" { + description = "The of the OS disk to use on our example VM." + type = string + default = "terratest-nsg-example-os-disk" +} + +variable "password" { + description = "The password to configure for SSH access" + type = string + default = "HorriblePassword1234!" +} + +variable "username" { + description = "The username to be provisioned into your VM" + type = string + default = "testadmin" +} diff --git a/test/azure/terraform_azure_nsg_example_test.go b/test/azure/terraform_azure_nsg_example_test.go index d57c2e397..518e3306d 100644 --- a/test/azure/terraform_azure_nsg_example_test.go +++ b/test/azure/terraform_azure_nsg_example_test.go @@ -6,9 +6,11 @@ package test import ( + "fmt" "testing" "github.com/gruntwork-io/terratest/modules/azure" + "github.com/gruntwork-io/terratest/modules/random" "github.com/gruntwork-io/terratest/modules/terraform" "github.com/stretchr/testify/assert" ) @@ -16,9 +18,54 @@ import ( func TestTerraformAzureNsgExample(t *testing.T) { t.Parallel() + // + // Setup our variables to be unique per test-run: + // + + // "resource_group_name" + expectedResourceGroupName := fmt.Sprintf("terratest-nsg-example-%s", random.UniqueId()) + + // "vnet_name" + // "subnet_name" + expectedVnetName := fmt.Sprintf("vnet_name_%s", random.UniqueId()) + expectedSubnetName := fmt.Sprintf("subnet_name_%s", random.UniqueId()) + + // "vm_nic_name" + // "vm_nic_ip_config_name" + expectedNICName := fmt.Sprintf("vm_nic_name_%s", random.UniqueId()) + expectedIPConfigName := fmt.Sprintf("vm_nic_ip_config_name_%s", random.UniqueId()) + + // "nsg_name" + // "nsg_rule_name" + expectedNSGName := fmt.Sprintf("nsg_name_%s", random.UniqueId()) + expectedNSGRuleName := fmt.Sprintf("nsg_rule_name_%s", random.UniqueId()) + + // "vm_name" + // "hostname" + // "os_disk_name" + expectedVMName := fmt.Sprintf("vm_name_%s", random.UniqueId()) + expectedHostName := fmt.Sprintf("hostname_%s", random.UniqueId()) + expectedOSDiskName := fmt.Sprintf("os_disk_name_%s", random.UniqueId()) + + // "password" + // "username" + + // Construct options for TF apply terraformOptions := &terraform.Options{ // The path to where our Terraform code is located TerraformDir: "../../examples/azure/terraform-azure-nsg-example", + Vars: map[string]interface{}{ + "resource_group_name": expectedResourceGroupName, + "vnet_name": expectedVnetName, + "subnet_name": expectedSubnetName, + "vm_nic_name": expectedNICName, + "vm_nic_ip_config_name": expectedIPConfigName, + "nsg_name": expectedNSGName, + "nsg_rule_name": expectedNSGRuleName, + "vm_name": expectedVMName, + "hostname": expectedHostName, + "os_disk_name": expectedOSDiskName, + }, } defer terraform.Destroy(t, terraformOptions) @@ -36,11 +83,11 @@ func TestTerraformAzureNsgExample(t *testing.T) { sshRule := rules.FindRuleByName("allowSSH") // That rule should allow port 22 inbound - assert.True(t, sshRule.AllowsDestinationPort("22")) + assert.True(t, sshRule.AllowsDestinationPort(t, "22")) // But should not allow 80 inbound - assert.False(t, sshRule.AllowsDestinationPort("80")) + assert.False(t, sshRule.AllowsDestinationPort(t, "80")) // SSh is allowed from any port - assert.True(t, sshRule.AllowsSourcePort("*")) + assert.True(t, sshRule.AllowsSourcePort(t, "*")) } From c500c1acd87c9a90e06af9f39e28c3ca757c431b Mon Sep 17 00:00:00 2001 From: Mike Yeaney Date: Thu, 24 Sep 2020 09:48:09 -0400 Subject: [PATCH 27/56] Fixed up unique naming constraints in terraform files; --- .../azure/terraform-azure-nsg-example/main.tf | 37 +++++++---- .../terraform-azure-nsg-example/outputs.tf | 8 +++ .../terraform-azure-nsg-example/variables.tf | 47 +++++++++----- modules/azure/nsg_test.go | 9 --- .../azure/terraform_azure_nsg_example_test.go | 63 +++++-------------- 5 files changed, 81 insertions(+), 83 deletions(-) diff --git a/examples/azure/terraform-azure-nsg-example/main.tf b/examples/azure/terraform-azure-nsg-example/main.tf index a06316a09..62f6b8431 100644 --- a/examples/azure/terraform-azure-nsg-example/main.tf +++ b/examples/azure/terraform-azure-nsg-example/main.tf @@ -18,7 +18,7 @@ terraform { # --------------------------------------------------------------------------------------------------------------------- resource "azurerm_resource_group" "main" { - name = var.resource_group_name + name = "${var.resource_group_name}-${var.postfix}" location = var.location } @@ -27,33 +27,33 @@ resource "azurerm_resource_group" "main" { # --------------------------------------------------------------------------------------------------------------------- resource "azurerm_virtual_network" "main" { - name = var.vnet_name + name = "${var.vnet_name}-${var.postfix}" address_space = ["10.0.0.0/16"] location = azurerm_resource_group.main.location resource_group_name = azurerm_resource_group.main.name } resource "azurerm_subnet" "internal" { - name = var.subnet_name + name = "${var.subnet_name}-${var.postfix}" resource_group_name = azurerm_resource_group.main.name virtual_network_name = azurerm_virtual_network.main.name address_prefixes = ["10.0.17.0/24"] } resource "azurerm_network_interface" "main" { - name = var.vm_nic_name + name = "${var.vm_nic_name}-${var.postfix}" location = azurerm_resource_group.main.location resource_group_name = azurerm_resource_group.main.name ip_configuration { - name = var.vm_nic_ip_config_name + name = "${var.vm_nic_ip_config_name}-${var.postfix}" subnet_id = azurerm_subnet.internal.id private_ip_address_allocation = "Dynamic" } } resource "azurerm_network_security_group" "main" { - name = var.nsg_name + name = "${var.nsg_name}-${var.postfix}" location = azurerm_resource_group.main.location resource_group_name = azurerm_resource_group.main.name } @@ -63,9 +63,9 @@ resource "azurerm_network_interface_security_group_association" "main" { network_security_group_id = azurerm_network_security_group.main.id } -resource "azurerm_network_security_rule" "main" { - name = var.nsg_rule_name - description = var.nsg_rule_name +resource "azurerm_network_security_rule" "allowSSH" { + name = "${var.nsg_ssh_rule_name}-${var.postfix}" + description = "${var.nsg_ssh_rule_name}-${var.postfix}" priority = 100 direction = "Inbound" access = "Allow" @@ -78,13 +78,28 @@ resource "azurerm_network_security_rule" "main" { network_security_group_name = azurerm_network_security_group.main.name } +resource "azurerm_network_security_rule" "blockHTTP" { + name = "${var.nsg_http_rule_name}-${var.postfix}" + description = "${var.nsg_http_rule_name}-${var.postfix}" + priority = 200 + direction = "Inbound" + access = "Deny" + protocol = "Tcp" + source_port_range = "*" + destination_port_range = 80 + source_address_prefix = "*" + destination_address_prefix = "*" + resource_group_name = azurerm_resource_group.main.name + network_security_group_name = azurerm_network_security_group.main.name +} + # --------------------------------------------------------------------------------------------------------------------- # DEPLOY A VIRTUAL MACHINE RUNNING UBUNTU # This VM does not actually do anything and is the smallest size VM available with an Ubuntu image # --------------------------------------------------------------------------------------------------------------------- resource "azurerm_virtual_machine" "main" { - name = var.vm_name + name = "${var.vm_name}-${var.postfix}" location = azurerm_resource_group.main.location resource_group_name = azurerm_resource_group.main.name network_interface_ids = [azurerm_network_interface.main.id] @@ -100,7 +115,7 @@ resource "azurerm_virtual_machine" "main" { } storage_os_disk { - name = var.os_disk_name + name = "${var.os_disk_name}-${var.postfix}" caching = "ReadWrite" create_option = "FromImage" managed_disk_type = "Standard_LRS" diff --git a/examples/azure/terraform-azure-nsg-example/outputs.tf b/examples/azure/terraform-azure-nsg-example/outputs.tf index ae8d7bcd0..dd60ae037 100644 --- a/examples/azure/terraform-azure-nsg-example/outputs.tf +++ b/examples/azure/terraform-azure-nsg-example/outputs.tf @@ -9,3 +9,11 @@ output "resource_group_name" { output "nsg_name" { value = azurerm_network_security_group.main.name } + +output "ssh_rule_name" { + value = azurerm_network_security_rule.allowSSH.name +} + +output "http_rule_name" { + value = azurerm_network_security_rule.blockHTTP.name +} diff --git a/examples/azure/terraform-azure-nsg-example/variables.tf b/examples/azure/terraform-azure-nsg-example/variables.tf index f3a5140c4..67b4f2f36 100644 --- a/examples/azure/terraform-azure-nsg-example/variables.tf +++ b/examples/azure/terraform-azure-nsg-example/variables.tf @@ -18,6 +18,13 @@ # These parameters have reasonable defaults. # --------------------------------------------------------------------------------------------------------------------- +variable "postfix" { + description = "Random postfix string used for each test run; set from the test file at runtime." + type = string + default = "qwefgt" +} + + variable "resource_group_name" { description = "Name for the resource group holding resources for this example" type = string @@ -33,43 +40,49 @@ variable "location" { variable "vnet_name" { description = "Name for the example virtual network" type = string - default = "terratest-nsg-example-network" + default = "vnet01" } variable "subnet_name" { description = "Name for the example virtual network default subnet" type = string - default = "terratest-nsg-example-subnet" + default = "subnet01" } variable "vm_nic_name" { description = "Name for the NIC attached to our example VM." type = string - default = "terratest-nsg-example-nic" + default = "nic01" } variable "vm_nic_ip_config_name" { description = "Name for the NIC IP configuration attached to our example VM." type = string - default = "terratest-nsg-example-nic-ip-config" + default = "nic_ipconfig01" } variable "nsg_name" { description = "Name for the example NSG." type = string - default = "terratest-nsg-example-nsg" + default = "nsg01" } -variable "nsg_rule_name" { - description = "Name for the example NSG rule used in this example." +variable "nsg_ssh_rule_name" { + description = "Name for the example SSH NSG rule used in this example." type = string - default = "terratest-nsg-example-nsg-rule" + default = "nsgrule01" +} + +variable "nsg_http_rule_name" { + description = "Name for the example HTTP NSG rule used in this example." + type = string + default = "nsgrule02" } variable "vm_name" { description = "The name of the VM used in this example" type = string - default = "terratest-nsg-example-vm" + default = "vm01" } variable "vm_size" { @@ -81,19 +94,13 @@ variable "vm_size" { variable "hostname" { description = "The hostname of the new VM to be configured" type = string - default = "terratest-nsg-example-vm" + default = "vm01" } variable "os_disk_name" { description = "The of the OS disk to use on our example VM." type = string - default = "terratest-nsg-example-os-disk" -} - -variable "password" { - description = "The password to configure for SSH access" - type = string - default = "HorriblePassword1234!" + default = "osdisk01" } variable "username" { @@ -101,3 +108,9 @@ variable "username" { type = string default = "testadmin" } + +variable "password" { + description = "The password to configure for SSH access" + type = string + default = "!@#PasswordSetInCode!@#" +} diff --git a/modules/azure/nsg_test.go b/modules/azure/nsg_test.go index f03360580..268385b5b 100644 --- a/modules/azure/nsg_test.go +++ b/modules/azure/nsg_test.go @@ -46,15 +46,6 @@ func TestNsgRuleSummaryConverstion(t *testing.T) { // Quick test to make sure the safe nil handling is working var name = "test name" var sdkStruct = network.SecurityRulePropertiesFormat{} - sdkStruct.Description = nil - sdkStruct.Protocol = network.SecurityRuleProtocolTCP - sdkStruct.SourcePortRange = nil - sdkStruct.DestinationPortRange = nil - sdkStruct.SourceAddressPrefix = nil - sdkStruct.DestinationAddressPrefix = nil - sdkStruct.Access = network.SecurityRuleAccessAllow - sdkStruct.Priority = nil - sdkStruct.Direction = network.SecurityRuleDirectionInbound // Verify the nil values were correctly defaulted to "" without a panic result := convertToNsgRuleSummary(&name, &sdkStruct) diff --git a/test/azure/terraform_azure_nsg_example_test.go b/test/azure/terraform_azure_nsg_example_test.go index 518e3306d..e7fb02bc5 100644 --- a/test/azure/terraform_azure_nsg_example_test.go +++ b/test/azure/terraform_azure_nsg_example_test.go @@ -18,69 +18,34 @@ import ( func TestTerraformAzureNsgExample(t *testing.T) { t.Parallel() - // - // Setup our variables to be unique per test-run: - // - - // "resource_group_name" - expectedResourceGroupName := fmt.Sprintf("terratest-nsg-example-%s", random.UniqueId()) - - // "vnet_name" - // "subnet_name" - expectedVnetName := fmt.Sprintf("vnet_name_%s", random.UniqueId()) - expectedSubnetName := fmt.Sprintf("subnet_name_%s", random.UniqueId()) - - // "vm_nic_name" - // "vm_nic_ip_config_name" - expectedNICName := fmt.Sprintf("vm_nic_name_%s", random.UniqueId()) - expectedIPConfigName := fmt.Sprintf("vm_nic_ip_config_name_%s", random.UniqueId()) - - // "nsg_name" - // "nsg_rule_name" - expectedNSGName := fmt.Sprintf("nsg_name_%s", random.UniqueId()) - expectedNSGRuleName := fmt.Sprintf("nsg_rule_name_%s", random.UniqueId()) - - // "vm_name" - // "hostname" - // "os_disk_name" - expectedVMName := fmt.Sprintf("vm_name_%s", random.UniqueId()) - expectedHostName := fmt.Sprintf("hostname_%s", random.UniqueId()) - expectedOSDiskName := fmt.Sprintf("os_disk_name_%s", random.UniqueId()) - - // "password" - // "username" + randomPostfixValue := random.UniqueId() + vmPassword := fmt.Sprintf("%s@#$%s", random.UniqueId(), random.UniqueId()) // Construct options for TF apply terraformOptions := &terraform.Options{ // The path to where our Terraform code is located TerraformDir: "../../examples/azure/terraform-azure-nsg-example", Vars: map[string]interface{}{ - "resource_group_name": expectedResourceGroupName, - "vnet_name": expectedVnetName, - "subnet_name": expectedSubnetName, - "vm_nic_name": expectedNICName, - "vm_nic_ip_config_name": expectedIPConfigName, - "nsg_name": expectedNSGName, - "nsg_rule_name": expectedNSGRuleName, - "vm_name": expectedVMName, - "hostname": expectedHostName, - "os_disk_name": expectedOSDiskName, + "postfix": randomPostfixValue, + "password": vmPassword, }, } defer terraform.Destroy(t, terraformOptions) terraform.InitAndApply(t, terraformOptions) - nsgName := terraform.Output(t, terraformOptions, "nsg_name") resourceGroupName := terraform.Output(t, terraformOptions, "resource_group_name") + nsgName := terraform.Output(t, terraformOptions, "nsg_name") + sshRuleName := terraform.Output(t, terraformOptions, "ssh_rule_name") + httpRuleName := terraform.Output(t, terraformOptions, "http_rule_name") - // A default NSG has 6 rules, and we have one custom rule for a total of 7 + // A default NSG has 6 rules, and we have two custom rules for a total of 8 rules, err := azure.GetAllNSGRulesE(resourceGroupName, nsgName, "") assert.NoError(t, err) - assert.Equal(t, 7, len(rules.SummarizedRules)) + assert.Equal(t, 8, len(rules.SummarizedRules)) - // We should have a rule named "allowSSH" - sshRule := rules.FindRuleByName("allowSSH") + // We should have a rule for allowing ssh + sshRule := rules.FindRuleByName(sshRuleName) // That rule should allow port 22 inbound assert.True(t, sshRule.AllowsDestinationPort(t, "22")) @@ -90,4 +55,10 @@ func TestTerraformAzureNsgExample(t *testing.T) { // SSh is allowed from any port assert.True(t, sshRule.AllowsSourcePort(t, "*")) + + // We should have a rule for blocking HTTP + httpRule := rules.FindRuleByName(httpRuleName) + + // This rule should BLOCK port 80 inbound + assert.False(t, httpRule.AllowsDestinationPort(t, "80")) } From 99d4a8f53829be86330482a2ba382ac7ac95fe06 Mon Sep 17 00:00:00 2001 From: Mike Yeaney Date: Thu, 24 Sep 2020 09:51:01 -0400 Subject: [PATCH 28/56] Updated links to core Azure README --- .../terraform-azure-nsg-example/README.md | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/examples/azure/terraform-azure-nsg-example/README.md b/examples/azure/terraform-azure-nsg-example/README.md index ba842947b..86992110a 100644 --- a/examples/azure/terraform-azure-nsg-example/README.md +++ b/examples/azure/terraform-azure-nsg-example/README.md @@ -18,23 +18,22 @@ it should be free, but you are completely responsible for all Azure charges. ## Running this module manually -1. Sign up for [Azure](https://azure.microsoft.com/). +1. Sign up for [Azure](https://azure.microsoft.com/) 1. Configure your Azure credentials using one of the [supported methods for Azure CLI - tools](https://docs.microsoft.com/en-us/cli/azure/azure-cli-configuration?view=azure-cli-latest). -1. Install [Terraform](https://www.terraform.io/) and make sure it's on your `PATH`. -1. Run `terraform init`. -1. Run `terraform apply`. -1. When you're done, run `terraform destroy`. + tools](https://docs.microsoft.com/en-us/cli/azure/azure-cli-configuration?view=azure-cli-latest) +1. Install [Terraform](https://www.terraform.io/) and make sure it's on your `PATH` +1. Ensure [environment variables](../README.md#review-environment-variables) are available +1. Run `terraform init` +1. Run `terraform apply` +1. When you're done, run `terraform destroy` ## Running automated tests against this module -1. Sign up for [Azure](https://azure.microsoft.com/). +1. Sign up for [Azure](https://azure.microsoft.com/) 1. Configure your Azure credentials using one of the [supported methods for Azure CLI - tools](https://docs.microsoft.com/en-us/cli/azure/azure-cli-configuration?view=azure-cli-latest). -1. Install [Terraform](https://www.terraform.io/) and make sure it's on your `PATH`. -1. [Review environment variables](#review-environment-variables). -1. Install [Golang](https://golang.org/) and make sure this code is checked out into your `GOPATH`. + tools](https://docs.microsoft.com/en-us/cli/azure/azure-cli-configuration?view=azure-cli-latest) +1. Install [Terraform](https://www.terraform.io/) and make sure it's on your `PATH` +1. Configure your Terratest [Go test environment](../README.md) 1. `cd test/azure` -1. Make sure [the azure-sdk-for-go versions match](#check-go-dependencies) in [/test/go.mod](/test/go.mod) and in [test/azure/terraform_azure_nsg_example_test.go](/test/terraform_azure_nsg_example_test.go). 1. `go build terraform_azure_nsg_example_test.go` -1. `go test -v -run TestTerraformAzureNsgExample` \ No newline at end of file +1. `go test -v -run TestTerraformAzureNsgExample` From 27777d477cbb9d9d9c66d3d3d8272028b3e2d926 Mon Sep 17 00:00:00 2001 From: Mike Yeaney Date: Thu, 24 Sep 2020 10:38:55 -0400 Subject: [PATCH 29/56] Moved safePtrXXX methods to azure common.go file --- modules/azure/common.go | 16 ++++++++++++++++ modules/azure/nsg.go | 16 ---------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/modules/azure/common.go b/modules/azure/common.go index d73923cc5..e664d5a41 100644 --- a/modules/azure/common.go +++ b/modules/azure/common.go @@ -52,3 +52,19 @@ func getTargetAzureResourceGroupName(resourceGroupName string) (string, error) { return resourceGroupName, nil } + +// safePtrToString converts a string pointer to a non-pointer string value, or to "" if the pointer is nil. +func safePtrToString(raw *string) string { + if raw == nil { + return "" + } + return *raw +} + +// safePtrToInt32 converts a int32 pointer to a non-pointer int32 value, or to 0 if the pointer is nil. +func safePtrToInt32(raw *int32) int32 { + if raw == nil { + return 0 + } + return *raw +} diff --git a/modules/azure/nsg.go b/modules/azure/nsg.go index aba925638..3495a56b9 100644 --- a/modules/azure/nsg.go +++ b/modules/azure/nsg.go @@ -152,22 +152,6 @@ func convertToNsgRuleSummary(name *string, rule *network.SecurityRulePropertiesF return summary } -// safePtrToString converts a string pointer to a non-pointer string value, or to "" if the pointer is nil. -func safePtrToString(raw *string) string { - if raw == nil { - return "" - } - return *raw -} - -// safePtrToInt32 converts a int32 pointer to a non-pointer int32 value, or to 0 if the pointer is nil. -func safePtrToInt32(raw *int32) int32 { - if raw == nil { - return 0 - } - return *raw -} - // FindRuleByName looks for a matching rule by name within the current collection of rules. func (summarizedRules *NsgRuleSummaryList) FindRuleByName(name string) NsgRuleSummary { for _, r := range summarizedRules.SummarizedRules { From 2d6141e08cc6159c882e6812527d6d8e0dd4c993 Mon Sep 17 00:00:00 2001 From: Mike Yeaney Date: Thu, 24 Sep 2020 10:56:43 -0400 Subject: [PATCH 30/56] Added non-error returning methods to module --- modules/azure/nsg.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/modules/azure/nsg.go b/modules/azure/nsg.go index 3495a56b9..27cf08f97 100644 --- a/modules/azure/nsg.go +++ b/modules/azure/nsg.go @@ -9,6 +9,7 @@ import ( "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-09-01/network" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) // NsgRuleSummaryList holds a colleciton of NsgRuleSummary rules @@ -31,6 +32,15 @@ type NsgRuleSummary struct { Direction string } +// GetDefaultNsgRulesClient returns a rules client which can be used to read the list of *default* security rules +// defined on an network security group. Note that the "default" rules are those provided implicitly +// by the Azure platform. +func GetDefaultNsgRulesClient(t *testing.T, subscriptionID string) network.DefaultSecurityRulesClient { + client, err := GetDefaultNsgRulesClientE(subscriptionID) + require.NoError(t, err) + return client +} + // GetDefaultNsgRulesClientE returns a rules client which can be used to read the list of *default* security rules // defined on an network security group. Note that the "default" rules are those provided implicitly // by the Azure platform. @@ -53,6 +63,15 @@ func GetDefaultNsgRulesClientE(subscriptionID string) (network.DefaultSecurityRu return nsgClient, nil } +// GetCustomNsgRulesClient returns a rules client which can be used to read the list of *custom* security rules +// defined on an network security group. Note that the "custom" rules are those defined by +// end users. +func GetCustomNsgRulesClient(t *testing.T, subscriptionID string) network.SecurityRulesClient { + client, err := GetCustomNsgRulesClientE(subscriptionID) + require.NoError(t, err) + return client +} + // GetCustomNsgRulesClientE returns a rules client which can be used to read the list of *custom* security rules // defined on an network security group. Note that the "custom" rules are those defined by // end users. @@ -75,6 +94,14 @@ func GetCustomNsgRulesClientE(subscriptionID string) (network.SecurityRulesClien return nsgClient, nil } +// GetAllNSGRules returns an NsgRuleSummaryList instance containing the combined "default" and "custom" rules from a network +// security group. +func GetAllNSGRules(t *testing.T, resourceGroupName, nsgName, subscriptionID string) NsgRuleSummaryList { + results, err := GetAllNSGRulesE(resourceGroupName, nsgName, subscriptionID) + require.NoError(t, err) + return results +} + // GetAllNSGRulesE returns an NsgRuleSummaryList instance containing the combined "default" and "custom" rules from a network // security group. func GetAllNSGRulesE(resourceGroupName, nsgName, subscriptionID string) (NsgRuleSummaryList, error) { From 517114aa18e66ce4cd5f051f8e5c85b2af940184 Mon Sep 17 00:00:00 2001 From: Mike Yeaney Date: Thu, 24 Sep 2020 11:14:04 -0400 Subject: [PATCH 31/56] Added stand-alone helpers for safePtr methods --- modules/azure/common_test.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/modules/azure/common_test.go b/modules/azure/common_test.go index 403dd01bf..71bc609db 100644 --- a/modules/azure/common_test.go +++ b/modules/azure/common_test.go @@ -9,6 +9,7 @@ import ( "os" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -74,3 +75,27 @@ func TestGetTargetAzureResourceGroupName(t *testing.T) { }) } } + +func TestSafePtrToString(t *testing.T) { + // When given a nil, should always return an empty string + var nilPtr *string = nil + nilResult := safePtrToString(nilPtr) + assert.Equal(t, "", nilResult) + + // When given a string, should just de-ref and return + stringPtr := "Test" + stringResult := safePtrToString(&stringPtr) + assert.Equal(t, "Test", stringResult) +} + +func TestSafePtrToInt32(t *testing.T) { + // When given a nil, should always return an zero value int32 + var nilPtr *int32 = nil + nilResult := safePtrToInt32(nilPtr) + assert.Equal(t, int32(0), nilResult) + + // When given a string, should just de-ref and return + intPtr := int32(42) + intResult := safePtrToInt32(&intPtr) + assert.Equal(t, int32(42), intResult) +} From 14496a9665bd96cf93ad6f49f4b5c205076c5957 Mon Sep 17 00:00:00 2001 From: Mike Yeaney Date: Thu, 24 Sep 2020 11:32:10 -0400 Subject: [PATCH 32/56] Updated terraform resource names; --- .../azure/terraform-azure-nsg-example/main.tf | 38 +++++++++---------- .../terraform-azure-nsg-example/outputs.tf | 6 +-- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/examples/azure/terraform-azure-nsg-example/main.tf b/examples/azure/terraform-azure-nsg-example/main.tf index 62f6b8431..ca84c1f9b 100644 --- a/examples/azure/terraform-azure-nsg-example/main.tf +++ b/examples/azure/terraform-azure-nsg-example/main.tf @@ -17,7 +17,7 @@ terraform { # See test/terraform_azure_example_test.go for how to write automated tests for this code. # --------------------------------------------------------------------------------------------------------------------- -resource "azurerm_resource_group" "main" { +resource "azurerm_resource_group" "nsg_rg" { name = "${var.resource_group_name}-${var.postfix}" location = var.location } @@ -26,24 +26,24 @@ resource "azurerm_resource_group" "main" { # DEPLOY VIRTUAL NETWORK RESOURCES # --------------------------------------------------------------------------------------------------------------------- -resource "azurerm_virtual_network" "main" { +resource "azurerm_virtual_network" "vnet" { name = "${var.vnet_name}-${var.postfix}" address_space = ["10.0.0.0/16"] - location = azurerm_resource_group.main.location - resource_group_name = azurerm_resource_group.main.name + location = azurerm_resource_group.nsg_rg.location + resource_group_name = azurerm_resource_group.nsg_rg.name } resource "azurerm_subnet" "internal" { name = "${var.subnet_name}-${var.postfix}" - resource_group_name = azurerm_resource_group.main.name - virtual_network_name = azurerm_virtual_network.main.name + resource_group_name = azurerm_resource_group.nsg_rg.name + virtual_network_name = azurerm_virtual_network.vnet.name address_prefixes = ["10.0.17.0/24"] } resource "azurerm_network_interface" "main" { name = "${var.vm_nic_name}-${var.postfix}" - location = azurerm_resource_group.main.location - resource_group_name = azurerm_resource_group.main.name + location = azurerm_resource_group.nsg_rg.location + resource_group_name = azurerm_resource_group.nsg_rg.name ip_configuration { name = "${var.vm_nic_ip_config_name}-${var.postfix}" @@ -52,15 +52,15 @@ resource "azurerm_network_interface" "main" { } } -resource "azurerm_network_security_group" "main" { +resource "azurerm_network_security_group" "nsg_example" { name = "${var.nsg_name}-${var.postfix}" - location = azurerm_resource_group.main.location - resource_group_name = azurerm_resource_group.main.name + location = azurerm_resource_group.nsg_rg.location + resource_group_name = azurerm_resource_group.nsg_rg.name } resource "azurerm_network_interface_security_group_association" "main" { network_interface_id = azurerm_network_interface.main.id - network_security_group_id = azurerm_network_security_group.main.id + network_security_group_id = azurerm_network_security_group.nsg_example.id } resource "azurerm_network_security_rule" "allowSSH" { @@ -74,8 +74,8 @@ resource "azurerm_network_security_rule" "allowSSH" { destination_port_range = 22 source_address_prefix = "*" destination_address_prefix = "*" - resource_group_name = azurerm_resource_group.main.name - network_security_group_name = azurerm_network_security_group.main.name + resource_group_name = azurerm_resource_group.nsg_rg.name + network_security_group_name = azurerm_network_security_group.nsg_example.name } resource "azurerm_network_security_rule" "blockHTTP" { @@ -89,8 +89,8 @@ resource "azurerm_network_security_rule" "blockHTTP" { destination_port_range = 80 source_address_prefix = "*" destination_address_prefix = "*" - resource_group_name = azurerm_resource_group.main.name - network_security_group_name = azurerm_network_security_group.main.name + resource_group_name = azurerm_resource_group.nsg_rg.name + network_security_group_name = azurerm_network_security_group.nsg_example.name } # --------------------------------------------------------------------------------------------------------------------- @@ -98,10 +98,10 @@ resource "azurerm_network_security_rule" "blockHTTP" { # This VM does not actually do anything and is the smallest size VM available with an Ubuntu image # --------------------------------------------------------------------------------------------------------------------- -resource "azurerm_virtual_machine" "main" { +resource "azurerm_virtual_machine" "vm_example" { name = "${var.vm_name}-${var.postfix}" - location = azurerm_resource_group.main.location - resource_group_name = azurerm_resource_group.main.name + location = azurerm_resource_group.nsg_rg.location + resource_group_name = azurerm_resource_group.nsg_rg.name network_interface_ids = [azurerm_network_interface.main.id] vm_size = var.vm_size delete_os_disk_on_termination = true diff --git a/examples/azure/terraform-azure-nsg-example/outputs.tf b/examples/azure/terraform-azure-nsg-example/outputs.tf index dd60ae037..d96abf0be 100644 --- a/examples/azure/terraform-azure-nsg-example/outputs.tf +++ b/examples/azure/terraform-azure-nsg-example/outputs.tf @@ -1,13 +1,13 @@ output "vm_name" { - value = azurerm_virtual_machine.main.name + value = azurerm_virtual_machine.vm_example.name } output "resource_group_name" { - value = azurerm_resource_group.main.name + value = azurerm_resource_group.nsg_rg.name } output "nsg_name" { - value = azurerm_network_security_group.main.name + value = azurerm_network_security_group.nsg_example.name } output "ssh_rule_name" { From 11dcc38e1755f7f1bb9f6317043d21a50e0f9ec5 Mon Sep 17 00:00:00 2001 From: Mike Yeaney Date: Thu, 24 Sep 2020 11:50:26 -0400 Subject: [PATCH 33/56] Added range testing cases to NsgRuleSummary type --- modules/azure/nsg_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/modules/azure/nsg_test.go b/modules/azure/nsg_test.go index 268385b5b..0e61f2e55 100644 --- a/modules/azure/nsg_test.go +++ b/modules/azure/nsg_test.go @@ -69,6 +69,12 @@ func TestAllowSourcePort(t *testing.T) { {"22 denied", "22", "Deny", "22", false}, {"22 doesn't allow 80", "22", "Allow", "80", false}, {"Any allows any", "*", "Allow", "*", true}, + {"Allows a range of ports", "80-90", "Allow", "80", true}, + {"Allows a range of ports", "80-90", "Allow", "85", true}, + {"Allows a range of ports", "80-90", "Allow", "90", true}, + {"Blocks a range of ports", "80-90", "Deny", "80", false}, + {"Blocks a range of ports", "80-90", "Deny", "85", false}, + {"Blocks a range of ports", "80-90", "Deny", "90", false}, } for _, tt := range cases { @@ -94,6 +100,12 @@ func TestAllowDestinationPort(t *testing.T) { {"22 denied", "22", "Deny", "22", false}, {"22 doesn't allow 80", "22", "Allow", "80", false}, {"Any allows any", "*", "Allow", "*", true}, + {"Allows a range of ports", "80-90", "Allow", "80", true}, + {"Allows a range of ports", "80-90", "Allow", "85", true}, + {"Allows a range of ports", "80-90", "Allow", "90", true}, + {"Blocks a range of ports", "80-90", "Deny", "80", false}, + {"Blocks a range of ports", "80-90", "Deny", "85", false}, + {"Blocks a range of ports", "80-90", "Deny", "90", false}, } for _, tt := range cases { From 533a39955bbb0f2f8cb38ec896a35d59c690beb5 Mon Sep 17 00:00:00 2001 From: Mike Yeaney Date: Thu, 24 Sep 2020 12:01:03 -0400 Subject: [PATCH 34/56] Updated resource group name per naming guidelines --- examples/azure/terraform-azure-nsg-example/variables.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/azure/terraform-azure-nsg-example/variables.tf b/examples/azure/terraform-azure-nsg-example/variables.tf index 67b4f2f36..f65383766 100644 --- a/examples/azure/terraform-azure-nsg-example/variables.tf +++ b/examples/azure/terraform-azure-nsg-example/variables.tf @@ -28,7 +28,7 @@ variable "postfix" { variable "resource_group_name" { description = "Name for the resource group holding resources for this example" type = string - default = "terratest-nsg-example" + default = "terratest-nsg-rg" } variable "location" { From 454fe4a8d4f182e1def1050926b019b950a6cdbb Mon Sep 17 00:00:00 2001 From: Mike Yeaney Date: Thu, 24 Sep 2020 12:42:56 -0400 Subject: [PATCH 35/56] Added comment indicated test failure for non-E suffix methods --- modules/azure/nsg.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/azure/nsg.go b/modules/azure/nsg.go index 27cf08f97..c0d6f06a2 100644 --- a/modules/azure/nsg.go +++ b/modules/azure/nsg.go @@ -35,6 +35,7 @@ type NsgRuleSummary struct { // GetDefaultNsgRulesClient returns a rules client which can be used to read the list of *default* security rules // defined on an network security group. Note that the "default" rules are those provided implicitly // by the Azure platform. +// This function would fail the test if there is an error. func GetDefaultNsgRulesClient(t *testing.T, subscriptionID string) network.DefaultSecurityRulesClient { client, err := GetDefaultNsgRulesClientE(subscriptionID) require.NoError(t, err) @@ -66,6 +67,7 @@ func GetDefaultNsgRulesClientE(subscriptionID string) (network.DefaultSecurityRu // GetCustomNsgRulesClient returns a rules client which can be used to read the list of *custom* security rules // defined on an network security group. Note that the "custom" rules are those defined by // end users. +// This function would fail the test if there is an error. func GetCustomNsgRulesClient(t *testing.T, subscriptionID string) network.SecurityRulesClient { client, err := GetCustomNsgRulesClientE(subscriptionID) require.NoError(t, err) @@ -96,6 +98,7 @@ func GetCustomNsgRulesClientE(subscriptionID string) (network.SecurityRulesClien // GetAllNSGRules returns an NsgRuleSummaryList instance containing the combined "default" and "custom" rules from a network // security group. +// This function would fail the test if there is an error. func GetAllNSGRules(t *testing.T, resourceGroupName, nsgName, subscriptionID string) NsgRuleSummaryList { results, err := GetAllNSGRulesE(resourceGroupName, nsgName, subscriptionID) require.NoError(t, err) From 50fa804c4cc6ea195e734f25a5bde4b23007dc6c Mon Sep 17 00:00:00 2001 From: Mike Yeaney Date: Thu, 24 Sep 2020 13:00:13 -0400 Subject: [PATCH 36/56] Fixing formatting issues with main.tf in example folder --- examples/azure/terraform-azure-nsg-example/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/azure/terraform-azure-nsg-example/main.tf b/examples/azure/terraform-azure-nsg-example/main.tf index ca84c1f9b..d420fec4d 100644 --- a/examples/azure/terraform-azure-nsg-example/main.tf +++ b/examples/azure/terraform-azure-nsg-example/main.tf @@ -37,7 +37,7 @@ resource "azurerm_subnet" "internal" { name = "${var.subnet_name}-${var.postfix}" resource_group_name = azurerm_resource_group.nsg_rg.name virtual_network_name = azurerm_virtual_network.vnet.name - address_prefixes = ["10.0.17.0/24"] + address_prefixes = ["10.0.17.0/24"] } resource "azurerm_network_interface" "main" { From 2add8bcb9cab3347ad27e6fd099fbceb563368f3 Mon Sep 17 00:00:00 2001 From: Mike Yeaney Date: Thu, 24 Sep 2020 15:24:51 -0400 Subject: [PATCH 37/56] Added manual depends-on to fix NSG security rule association issue --- examples/azure/terraform-azure-nsg-example/main.tf | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/azure/terraform-azure-nsg-example/main.tf b/examples/azure/terraform-azure-nsg-example/main.tf index d420fec4d..26b1cbf18 100644 --- a/examples/azure/terraform-azure-nsg-example/main.tf +++ b/examples/azure/terraform-azure-nsg-example/main.tf @@ -130,5 +130,10 @@ resource "azurerm_virtual_machine" "vm_example" { os_profile_linux_config { disable_password_authentication = false } + + # Correctly setup the dependencies to make sure resources are correctly destroyed. + depends_on = [ + azurerm_network_interface_security_group_association.main + ] } From 7f58fe67f6bc0b353221973594d326034ca37cdb Mon Sep 17 00:00:00 2001 From: Ben Whaley Date: Thu, 24 Sep 2020 18:58:47 -0700 Subject: [PATCH 38/56] Adds helpers to create, get, and delete ECR repositories --- modules/aws/ecr.go | 93 +++++++++++++++++++++++++++++++++++++++++ modules/aws/ecr_test.go | 23 ++++++++++ 2 files changed, 116 insertions(+) create mode 100644 modules/aws/ecr.go create mode 100644 modules/aws/ecr_test.go diff --git a/modules/aws/ecr.go b/modules/aws/ecr.go new file mode 100644 index 000000000..a73818b53 --- /dev/null +++ b/modules/aws/ecr.go @@ -0,0 +1,93 @@ +package aws + +import ( + "fmt" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ecr" + "github.com/gruntwork-io/terratest/modules/testing" + "github.com/stretchr/testify/require" +) + +// CreateECRRepo creates a new ECR Repository +func CreateECRRepo(t testing.TestingT, region string, name string) *ecr.Repository { + repo, err := CreateECRRepoE(t, region, name) + require.NoError(t, err) + return repo +} + +// CreateECRRepoE creates a new ECR Repository +func CreateECRRepoE(t testing.TestingT, region string, name string) (*ecr.Repository, error) { + client := NewECRClient(t, region) + resp, err := client.CreateRepository(&ecr.CreateRepositoryInput{RepositoryName: aws.String(name)}) + if err != nil { + return nil, err + } + return resp.Repository, nil +} + +// GetECRRepo gets an ECR repository by name +func GetECRRepo(t testing.TestingT, region string, name string) *ecr.Repository { + repo, err := GetECRRepoE(t, region, name) + require.NoError(t, err) + return repo +} + +// GetECRRepoE gets an ECR Repository by name +func GetECRRepoE(t testing.TestingT, region string, name string) (*ecr.Repository, error) { + client := NewECRClient(t, region) + repositoryNames := []*string{aws.String(name)} + resp, err := client.DescribeRepositories(&ecr.DescribeRepositoriesInput{RepositoryNames: repositoryNames}) + if err != nil { + return nil, err + } + if len(resp.Repositories) != 1 { + return nil, fmt.Errorf("There is no repository named %s", name) + } + return resp.Repositories[0], nil +} + +// DeleteECRRepo will force delete the ECR repo by deleting all images prior to deleting the ECR repository. +func DeleteECRRepo(t testing.TestingT, region string, repo *ecr.Repository) { + err := DeleteECRRepoE(t, region, repo) + require.NoError(t, err) +} + +// DeleteECRRepoE will force delete the ECR repo by deleting all images prior to deleting the ECR repository. +func DeleteECRRepoE(t testing.TestingT, region string, repo *ecr.Repository) error { + client := NewECRClient(t, region) + resp, err := client.ListImages(&ecr.ListImagesInput{RepositoryName: repo.RepositoryName}) + if err != nil { + return err + } + if len(resp.ImageIds) > 0 { + _, err = client.BatchDeleteImage(&ecr.BatchDeleteImageInput{ + RepositoryName: repo.RepositoryName, + ImageIds: resp.ImageIds, + }) + if err != nil { + return err + } + } + + _, err = client.DeleteRepository(&ecr.DeleteRepositoryInput{RepositoryName: repo.RepositoryName}) + if err != nil { + return err + } + return nil +} + +// NewECRClient returns a client for the Elastic Container Registry +func NewECRClient(t testing.TestingT, region string) *ecr.ECR { + sess, err := NewECRClientE(t, region) + require.NoError(t, err) + return sess +} + +func NewECRClientE(t testing.TestingT, region string) (*ecr.ECR, error) { + sess, err := NewAuthenticatedSession(region) + if err != nil { + return nil, err + } + return ecr.New(sess), nil +} diff --git a/modules/aws/ecr_test.go b/modules/aws/ecr_test.go new file mode 100644 index 000000000..3dba4c1bb --- /dev/null +++ b/modules/aws/ecr_test.go @@ -0,0 +1,23 @@ +package aws + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEcrRepo(t *testing.T) { + t.Parallel() + + region := GetRandomStableRegion(t, nil, nil) + repo1, err := CreateECRRepoE(t, region, "terratest") + defer DeleteECRRepo(t, region, repo1) + + assert.Nil(t, err) + assert.Equal(t, "terratest", *repo1.RepositoryName) + + repo2, err := GetECRRepoE(t, region, "terratest") + + assert.Nil(t, err) + assert.Equal(t, "terratest", *repo2.RepositoryName) +} From f239f64e00fb2d6c361655f69f47a33731c124f0 Mon Sep 17 00:00:00 2001 From: Ben Whaley Date: Fri, 25 Sep 2020 09:05:54 -0700 Subject: [PATCH 39/56] Incorporated review feedback --- modules/aws/ecr.go | 16 ++++++++++------ modules/aws/ecr_test.go | 10 ++++++---- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/modules/aws/ecr.go b/modules/aws/ecr.go index a73818b53..135c99ff5 100644 --- a/modules/aws/ecr.go +++ b/modules/aws/ecr.go @@ -5,18 +5,19 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ecr" + "github.com/gruntwork-io/gruntwork-cli/errors" "github.com/gruntwork-io/terratest/modules/testing" "github.com/stretchr/testify/require" ) -// CreateECRRepo creates a new ECR Repository +// CreateECRRepo creates a new ECR Repository. This will fail the test and stop execution if there is an error. func CreateECRRepo(t testing.TestingT, region string, name string) *ecr.Repository { repo, err := CreateECRRepoE(t, region, name) require.NoError(t, err) return repo } -// CreateECRRepoE creates a new ECR Repository +// CreateECRRepoE creates a new ECR Repository. func CreateECRRepoE(t testing.TestingT, region string, name string) (*ecr.Repository, error) { client := NewECRClient(t, region) resp, err := client.CreateRepository(&ecr.CreateRepositoryInput{RepositoryName: aws.String(name)}) @@ -26,14 +27,14 @@ func CreateECRRepoE(t testing.TestingT, region string, name string) (*ecr.Reposi return resp.Repository, nil } -// GetECRRepo gets an ECR repository by name +// GetECRRepo gets an ECR repository by name. This will fail the test and stop execution if there is an error. func GetECRRepo(t testing.TestingT, region string, name string) *ecr.Repository { repo, err := GetECRRepoE(t, region, name) require.NoError(t, err) return repo } -// GetECRRepoE gets an ECR Repository by name +// GetECRRepoE gets an ECR Repository by name. func GetECRRepoE(t testing.TestingT, region string, name string) (*ecr.Repository, error) { client := NewECRClient(t, region) repositoryNames := []*string{aws.String(name)} @@ -42,12 +43,13 @@ func GetECRRepoE(t testing.TestingT, region string, name string) (*ecr.Repositor return nil, err } if len(resp.Repositories) != 1 { - return nil, fmt.Errorf("There is no repository named %s", name) + return nil, errors.WithStackTrace(fmt.Errorf("Unexpectedly found %d repositories", len(resp.Repositories))) } return resp.Repositories[0], nil } // DeleteECRRepo will force delete the ECR repo by deleting all images prior to deleting the ECR repository. +// This will fail the test and stop execution if there is an error. func DeleteECRRepo(t testing.TestingT, region string, repo *ecr.Repository) { err := DeleteECRRepoE(t, region, repo) require.NoError(t, err) @@ -77,13 +79,15 @@ func DeleteECRRepoE(t testing.TestingT, region string, repo *ecr.Repository) err return nil } -// NewECRClient returns a client for the Elastic Container Registry +// NewECRClient returns a client for the Elastic Container Registry. This will fail the test and +// stop execution if there is an error. func NewECRClient(t testing.TestingT, region string) *ecr.ECR { sess, err := NewECRClientE(t, region) require.NoError(t, err) return sess } +// NewECRClient returns a client for the Elastic Container Registry. func NewECRClientE(t testing.TestingT, region string) (*ecr.ECR, error) { sess, err := NewAuthenticatedSession(region) if err != nil { diff --git a/modules/aws/ecr_test.go b/modules/aws/ecr_test.go index 3dba4c1bb..4ed559d66 100644 --- a/modules/aws/ecr_test.go +++ b/modules/aws/ecr_test.go @@ -3,7 +3,9 @@ package aws import ( "testing" + "github.com/aws/aws-sdk-go/aws" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestEcrRepo(t *testing.T) { @@ -13,11 +15,11 @@ func TestEcrRepo(t *testing.T) { repo1, err := CreateECRRepoE(t, region, "terratest") defer DeleteECRRepo(t, region, repo1) - assert.Nil(t, err) - assert.Equal(t, "terratest", *repo1.RepositoryName) + require.NoError(t, err) + assert.Equal(t, "terratest", aws.StringValue(repo1.RepositoryName)) repo2, err := GetECRRepoE(t, region, "terratest") - assert.Nil(t, err) - assert.Equal(t, "terratest", *repo2.RepositoryName) + require.NoError(t, err) + assert.Equal(t, "terratest", aws.StringValue(repo2.RepositoryName)) } From 7bd30da8abe2aa9c3f4e6e3c9d2945eeb0be095f Mon Sep 17 00:00:00 2001 From: Mike Yeaney Date: Fri, 25 Sep 2020 17:10:11 -0400 Subject: [PATCH 40/56] Fixes per PR feedback --- .../terraform-azure-nsg-example/README.md | 4 +-- .../azure/terraform-azure-nsg-example/main.tf | 25 ++++++++++++++++--- .../terraform-azure-nsg-example/outputs.tf | 4 +-- .../terraform-azure-nsg-example/variables.tf | 1 - modules/azure/nsg.go | 2 +- modules/azure/nsg_test.go | 6 ++--- .../azure/terraform_azure_nsg_example_test.go | 5 +--- 7 files changed, 30 insertions(+), 17 deletions(-) diff --git a/examples/azure/terraform-azure-nsg-example/README.md b/examples/azure/terraform-azure-nsg-example/README.md index 86992110a..1cee389c3 100644 --- a/examples/azure/terraform-azure-nsg-example/README.md +++ b/examples/azure/terraform-azure-nsg-example/README.md @@ -3,8 +3,8 @@ This folder contains a simple Terraform module that deploys resources in [Azure](https://azure.microsoft.com/) to demonstrate how you can use Terratest to write automated tests for your Azure Terraform code. This module deploys the following: * A [Virtual Machine](https://azure.microsoft.com/en-us/services/virtual-machines/) that gives the module the following: - * [Virtual Machine](https://docs.microsoft.com/en-us/azure/virtual-machines/) with the value specified in the `vm_name` variable. - * A [Network Security Group](https://docs.microsoft.com/en-us/azure/virtual-network/network-security-groups-overview) created with a single custom rule to allow SSH (port 22) with the nsg name specified in the `nsg_name` variable. + * [Virtual Machine](https://docs.microsoft.com/en-us/azure/virtual-machines/) with the value specified in the `vm_name` variable along with a random value for the `postfix` variable (set from test code). + * A [Network Security Group](https://docs.microsoft.com/en-us/azure/virtual-network/network-security-groups-overview) created with a single custom rule to allow SSH (port 22) with the nsg name specified in the `nsg_name` variable along with a random value for the `postfix` variable (set from test code). Check out [test/azure/terraform_azure_nsg_example_test.go](/test/azure/terraform_azure_nsg_example_test.go) to see how you can write automated tests for this module. diff --git a/examples/azure/terraform-azure-nsg-example/main.tf b/examples/azure/terraform-azure-nsg-example/main.tf index 26b1cbf18..04f39841d 100644 --- a/examples/azure/terraform-azure-nsg-example/main.tf +++ b/examples/azure/terraform-azure-nsg-example/main.tf @@ -1,3 +1,11 @@ +# --------------------------------------------------------------------------------------------------------------------- +# DEPLOY AN AZURE VM ALONG WITH AN EXAMPLE NETWORK SECURITY GROUP (NSG) +# This is an example of how to deploy an NSG along with the minimum networking resources +# to support a basic virtual machine. +# --------------------------------------------------------------------------------------------------------------------- +# See test/azure/terraform_azure_nsg_example_test.go for how to write automated tests for this code. +# --------------------------------------------------------------------------------------------------------------------- + provider "azurerm" { version = "~>2.20" features {} @@ -14,7 +22,7 @@ terraform { # --------------------------------------------------------------------------------------------------------------------- # DEPLOY A RESOURCE GROUP -# See test/terraform_azure_example_test.go for how to write automated tests for this code. +# See test/terraform_azure_nsg_example_test.go for how to write automated tests for this code. # --------------------------------------------------------------------------------------------------------------------- resource "azurerm_resource_group" "nsg_rg" { @@ -63,7 +71,7 @@ resource "azurerm_network_interface_security_group_association" "main" { network_security_group_id = azurerm_network_security_group.nsg_example.id } -resource "azurerm_network_security_rule" "allowSSH" { +resource "azurerm_network_security_rule" "allow_ssh" { name = "${var.nsg_ssh_rule_name}-${var.postfix}" description = "${var.nsg_ssh_rule_name}-${var.postfix}" priority = 100 @@ -78,7 +86,7 @@ resource "azurerm_network_security_rule" "allowSSH" { network_security_group_name = azurerm_network_security_group.nsg_example.name } -resource "azurerm_network_security_rule" "blockHTTP" { +resource "azurerm_network_security_rule" "block_http" { name = "${var.nsg_http_rule_name}-${var.postfix}" description = "${var.nsg_http_rule_name}-${var.postfix}" priority = 200 @@ -124,7 +132,7 @@ resource "azurerm_virtual_machine" "vm_example" { os_profile { computer_name = var.hostname admin_username = var.username - admin_password = var.password + admin_password = random_password.nsg.result } os_profile_linux_config { @@ -137,3 +145,12 @@ resource "azurerm_virtual_machine" "vm_example" { ] } +resource "random_password" "nsg" { + length = 16 + override_special = "-_%@" + min_upper = "1" + min_lower = "1" + min_numeric = "1" + min_special = "1" +} + diff --git a/examples/azure/terraform-azure-nsg-example/outputs.tf b/examples/azure/terraform-azure-nsg-example/outputs.tf index d96abf0be..f69279fea 100644 --- a/examples/azure/terraform-azure-nsg-example/outputs.tf +++ b/examples/azure/terraform-azure-nsg-example/outputs.tf @@ -11,9 +11,9 @@ output "nsg_name" { } output "ssh_rule_name" { - value = azurerm_network_security_rule.allowSSH.name + value = azurerm_network_security_rule.allow_ssh.name } output "http_rule_name" { - value = azurerm_network_security_rule.blockHTTP.name + value = azurerm_network_security_rule.block_http.name } diff --git a/examples/azure/terraform-azure-nsg-example/variables.tf b/examples/azure/terraform-azure-nsg-example/variables.tf index f65383766..1f52eb3ec 100644 --- a/examples/azure/terraform-azure-nsg-example/variables.tf +++ b/examples/azure/terraform-azure-nsg-example/variables.tf @@ -24,7 +24,6 @@ variable "postfix" { default = "qwefgt" } - variable "resource_group_name" { description = "Name for the resource group holding resources for this example" type = string diff --git a/modules/azure/nsg.go b/modules/azure/nsg.go index c0d6f06a2..934456337 100644 --- a/modules/azure/nsg.go +++ b/modules/azure/nsg.go @@ -165,7 +165,7 @@ func bindRuleList(source network.SecurityRuleListResultIterator) ([]NsgRuleSumma return rules, nil } -// convertToNsgRuleSummary converst the raw SDK security rule type into a summarized struct, flattening the +// convertToNsgRuleSummary converts the raw SDK security rule type into a summarized struct, flattening the // rules properties and name into a single, string-based struct. func convertToNsgRuleSummary(name *string, rule *network.SecurityRulePropertiesFormat) NsgRuleSummary { summary := NsgRuleSummary{} diff --git a/modules/azure/nsg_test.go b/modules/azure/nsg_test.go index 0e61f2e55..a65d7f065 100644 --- a/modules/azure/nsg_test.go +++ b/modules/azure/nsg_test.go @@ -42,10 +42,10 @@ func TestPortRangeParsing(t *testing.T) { } } -func TestNsgRuleSummaryConverstion(t *testing.T) { +func TestNsgRuleSummaryConversion(t *testing.T) { // Quick test to make sure the safe nil handling is working - var name = "test name" - var sdkStruct = network.SecurityRulePropertiesFormat{} + name := "test name" + sdkStruct := network.SecurityRulePropertiesFormat{} // Verify the nil values were correctly defaulted to "" without a panic result := convertToNsgRuleSummary(&name, &sdkStruct) diff --git a/test/azure/terraform_azure_nsg_example_test.go b/test/azure/terraform_azure_nsg_example_test.go index e7fb02bc5..cb641ba86 100644 --- a/test/azure/terraform_azure_nsg_example_test.go +++ b/test/azure/terraform_azure_nsg_example_test.go @@ -6,7 +6,6 @@ package test import ( - "fmt" "testing" "github.com/gruntwork-io/terratest/modules/azure" @@ -19,15 +18,13 @@ func TestTerraformAzureNsgExample(t *testing.T) { t.Parallel() randomPostfixValue := random.UniqueId() - vmPassword := fmt.Sprintf("%s@#$%s", random.UniqueId(), random.UniqueId()) // Construct options for TF apply terraformOptions := &terraform.Options{ // The path to where our Terraform code is located TerraformDir: "../../examples/azure/terraform-azure-nsg-example", Vars: map[string]interface{}{ - "postfix": randomPostfixValue, - "password": vmPassword, + "postfix": randomPostfixValue, }, } From e0f7bb922f0cf3d2813bd16ae02817c9fff339f9 Mon Sep 17 00:00:00 2001 From: Ben Whaley Date: Fri, 25 Sep 2020 22:30:59 -0700 Subject: [PATCH 41/56] Improved error handling --- modules/aws/ecr.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/aws/ecr.go b/modules/aws/ecr.go index 135c99ff5..0badc2b67 100644 --- a/modules/aws/ecr.go +++ b/modules/aws/ecr.go @@ -1,7 +1,7 @@ package aws import ( - "fmt" + goerrors "errors" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ecr" @@ -28,6 +28,7 @@ func CreateECRRepoE(t testing.TestingT, region string, name string) (*ecr.Reposi } // GetECRRepo gets an ECR repository by name. This will fail the test and stop execution if there is an error. +// An error occurs if a repository with the given name does not exist in the given region. func GetECRRepo(t testing.TestingT, region string, name string) *ecr.Repository { repo, err := GetECRRepoE(t, region, name) require.NoError(t, err) @@ -35,6 +36,7 @@ func GetECRRepo(t testing.TestingT, region string, name string) *ecr.Repository } // GetECRRepoE gets an ECR Repository by name. +// An error occurs if a repository with the given name does not exist in the given region. func GetECRRepoE(t testing.TestingT, region string, name string) (*ecr.Repository, error) { client := NewECRClient(t, region) repositoryNames := []*string{aws.String(name)} @@ -43,7 +45,7 @@ func GetECRRepoE(t testing.TestingT, region string, name string) (*ecr.Repositor return nil, err } if len(resp.Repositories) != 1 { - return nil, errors.WithStackTrace(fmt.Errorf("Unexpectedly found %d repositories", len(resp.Repositories))) + return nil, errors.WithStackTrace(goerrors.New(("An unexpected condition occurred. Please file an issue at github.com/gruntwork-io/terratest"))) } return resp.Repositories[0], nil } From 589ae6b36d546ae3581759c8d5b5acea2829ad98 Mon Sep 17 00:00:00 2001 From: Dave Seepersad <“davesee@microsoft.com”> Date: Wed, 30 Sep 2020 13:10:55 -0400 Subject: [PATCH 42/56] Comment update --- test/azure/terraform_azure_vm_example_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/azure/terraform_azure_vm_example_test.go b/test/azure/terraform_azure_vm_example_test.go index 34bebc2cd..5754be55c 100644 --- a/test/azure/terraform_azure_vm_example_test.go +++ b/test/azure/terraform_azure_vm_example_test.go @@ -104,7 +104,7 @@ func TestTerraformAzureVmExample(t *testing.T) { // Get all VMs in a Resource Group by reference alternate example. // This strategy is beneficial when checking multiple VMs & their properties by avoiding // multiple SDK calls. The penalty for this approach is introducing direct references - // which need to be checked for nil when not required. + // which need to be checked for nil for optional configurations. vmsByRef := azure.GetResourceGroupVirtualMachinesObjects(t, resourceGroupName, subscriptionID) assert.True(t, len(*vmsByRef) > 0) @@ -175,7 +175,7 @@ func TestTerraformAzureVmExample(t *testing.T) { assert.Contains(t, actualPrivateIPAddress, expectedPrivateIPAddress) // Check for the Public IP for the NIC. No expected value since it is assigned runtime. - actualPublicIP := azure.GetPublicAddressIP(t, expectedPublicAddressName, resourceGroupName, subscriptionID) + actualPublicIP := azure.GetIPOfPublicIPAddressByName(t, expectedPublicAddressName, resourceGroupName, subscriptionID) assert.NotNil(t, actualPublicIP) }) From 51a22a32ac3e0f13432c9226ac3e321c70478904 Mon Sep 17 00:00:00 2001 From: Dave Seepersad <“davesee@microsoft.com”> Date: Wed, 30 Sep 2020 18:43:10 -0400 Subject: [PATCH 43/56] Updated test negative checks --- modules/azure/compute.go | 126 ++++++++++-------- modules/azure/compute_test.go | 32 ++--- modules/azure/disk.go | 3 + modules/azure/errors.go | 8 +- test/azure/terraform_azure_vm_example_test.go | 6 +- 5 files changed, 96 insertions(+), 79 deletions(-) diff --git a/modules/azure/compute.go b/modules/azure/compute.go index b57acfb74..72ee5cf04 100644 --- a/modules/azure/compute.go +++ b/modules/azure/compute.go @@ -2,7 +2,6 @@ package azure import ( "context" - "errors" "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-07-01/compute" "github.com/gruntwork-io/terratest/modules/testing" @@ -12,16 +11,19 @@ import ( // VirtualMachineExists indicates whether the specifcied Azure Virtual Machine exists. // This function would fail the test if there is an error. func VirtualMachineExists(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) bool { - exists, err := VirtualMachineExistsE(t, vmName, resGroupName, subscriptionID) + exists, err := VirtualMachineExistsE(vmName, resGroupName, subscriptionID) require.NoError(t, err) return exists } // VirtualMachineExistsE indicates whether the specifcied Azure Virtual Machine exists. -func VirtualMachineExistsE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (bool, error) { +func VirtualMachineExistsE(vmName string, resGroupName string, subscriptionID string) (bool, error) { // Get VM Object - _, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) + _, err := GetVirtualMachineE(vmName, resGroupName, subscriptionID) if err != nil { + if ResourceNotFoundErrorExists(err) { + return false, nil + } return false, err } return true, nil @@ -30,26 +32,26 @@ func VirtualMachineExistsE(t testing.TestingT, vmName string, resGroupName strin // GetVirtualMachineNics gets a list of Network Interface names for a specifcied Azure Virtual Machine. // This function would fail the test if there is an error. func GetVirtualMachineNics(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) []string { - nicList, err := GetVirtualMachineNicsE(t, vmName, resGroupName, subscriptionID) + nicList, err := GetVirtualMachineNicsE(vmName, resGroupName, subscriptionID) require.NoError(t, err) return nicList } // GetVirtualMachineNicsE gets a list of Network Interface names for a specified Azure Virtual Machine. -func GetVirtualMachineNicsE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) ([]string, error) { - nics := []string{} +func GetVirtualMachineNicsE(vmName string, resGroupName string, subscriptionID string) ([]string, error) { + var nics []string // Get VM Object - vm, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) + vm, err := GetVirtualMachineE(vmName, resGroupName, subscriptionID) if err != nil { return nics, err } vmNICs := *vm.NetworkProfile.NetworkInterfaces if len(vmNICs) == 0 { - // No VM NICs attached is still valid but returning a meaningful error - return nics, errors.New("No network interface attached to this Virtual Machine") + // If the NIC is not present + return nics, NewNotFoundError("Network Interface", "Any", vmName) } // Get the attached NIC names @@ -62,18 +64,18 @@ func GetVirtualMachineNicsE(t testing.TestingT, vmName string, resGroupName stri // GetVirtualMachineNicCount gets the Network Interface count of the specified Azure Virtual Machine. // This function would fail the test if there is an error. func GetVirtualMachineNicCount(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) int { - nicCount, err := GetVirtualMachineNicCountE(t, vmName, resGroupName, subscriptionID) + nicCount, err := GetVirtualMachineNicCountE(vmName, resGroupName, subscriptionID) require.NoError(t, err) return nicCount } // GetVirtualMachineNicCountE gets the Network Interface count of the specified Azure Virtual Machine. -func GetVirtualMachineNicCountE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (int, error) { - nicCount := 0 +func GetVirtualMachineNicCountE(vmName string, resGroupName string, subscriptionID string) (int, error) { + var nicCount int // Get VM Object - vm, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) + vm, err := GetVirtualMachineE(vmName, resGroupName, subscriptionID) if err != nil { return nicCount, err } @@ -84,18 +86,18 @@ func GetVirtualMachineNicCountE(t testing.TestingT, vmName string, resGroupName // GetVirtualMachineManagedDisks gets the list of Managed Disk names of the specified Azure Virtual Machine. // This function would fail the test if there is an error. func GetVirtualMachineManagedDisks(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) []string { - diskNames, err := GetVirtualMachineManagedDisksE(t, vmName, resGroupName, subscriptionID) + diskNames, err := GetVirtualMachineManagedDisksE(vmName, resGroupName, subscriptionID) require.NoError(t, err) return diskNames } // GetVirtualMachineManagedDisksE gets the list of Managed Disk names of the specified Azure Virtual Machine. -func GetVirtualMachineManagedDisksE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) ([]string, error) { - diskNames := []string{} +func GetVirtualMachineManagedDisksE(vmName string, resGroupName string, subscriptionID string) ([]string, error) { + var diskNames []string // Get VM Object - vm, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) + vm, err := GetVirtualMachineE(vmName, resGroupName, subscriptionID) if err != nil { return diskNames, err } @@ -112,20 +114,18 @@ func GetVirtualMachineManagedDisksE(t testing.TestingT, vmName string, resGroupN // GetVirtualMachineManagedDiskCount gets the Managed Disk count of the specified Azure Virtual Machine. // This function would fail the test if there is an error. func GetVirtualMachineManagedDiskCount(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) int { - mngDiskCount, err := GetVirtualMachineManagedDiskCountE(t, vmName, resGroupName, subscriptionID) + mngDiskCount, err := GetVirtualMachineManagedDiskCountE(vmName, resGroupName, subscriptionID) require.NoError(t, err) return mngDiskCount } // GetVirtualMachineManagedDiskCountE gets the Managed Disk count of the specified Azure Virtual Machine. -func GetVirtualMachineManagedDiskCountE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (int, error) { - mngDiskCount := -1 - +func GetVirtualMachineManagedDiskCountE(vmName string, resGroupName string, subscriptionID string) (int, error) { // Get VM Object - vm, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) + vm, err := GetVirtualMachineE(vmName, resGroupName, subscriptionID) if err != nil { - return mngDiskCount, err + return 0, err } return len(*vm.StorageProfile.DataDisks), nil @@ -134,16 +134,16 @@ func GetVirtualMachineManagedDiskCountE(t testing.TestingT, vmName string, resGr // GetVirtualMachineOsDiskName gets the OS Disk name of the specified Azure Virtual Machine. // This function would fail the test if there is an error. func GetVirtualMachineOsDiskName(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) string { - osDiskName, err := GetVirtualMachineOsDiskNameE(t, vmName, resGroupName, subscriptionID) + osDiskName, err := GetVirtualMachineOsDiskNameE(vmName, resGroupName, subscriptionID) require.NoError(t, err) return osDiskName } // GetVirtualMachineOsDiskNameE gets the OS Disk name of the specified Azure Virtual Machine. -func GetVirtualMachineOsDiskNameE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (string, error) { +func GetVirtualMachineOsDiskNameE(vmName string, resGroupName string, subscriptionID string) (string, error) { // Get VM Object - vm, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) + vm, err := GetVirtualMachineE(vmName, resGroupName, subscriptionID) if err != nil { return "", err } @@ -154,15 +154,15 @@ func GetVirtualMachineOsDiskNameE(t testing.TestingT, vmName string, resGroupNam // GetVirtualMachineState gets the State of the specified Azure Virtual Machine. // This function would fail the test if there is an error. func GetVirtualMachineState(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) string { - vmState, err := GetVirtualMachineStateE(t, vmName, resGroupName, subscriptionID) + vmState, err := GetVirtualMachineStateE(vmName, resGroupName, subscriptionID) require.NoError(t, err) return vmState } // GetVirtualMachineStateE gets the State of the specified Azure Virtual Machine. -func GetVirtualMachineStateE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (string, error) { +func GetVirtualMachineStateE(vmName string, resGroupName string, subscriptionID string) (string, error) { // Get VM Object - vm, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) + vm, err := GetVirtualMachineE(vmName, resGroupName, subscriptionID) if err != nil { return "", err } @@ -173,20 +173,24 @@ func GetVirtualMachineStateE(t testing.TestingT, vmName string, resGroupName str // GetVirtualMachineAvailabilitySetID gets the Availability Set ID of the specified Azure Virtual Machine. // This function would fail the test if there is an error. func GetVirtualMachineAvailabilitySetID(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) string { - adminUser, err := GetVirtualMachineAvailabilitySetIDE(t, vmName, resGroupName, subscriptionID) + avsID, err := GetVirtualMachineAvailabilitySetIDE(vmName, resGroupName, subscriptionID) require.NoError(t, err) - return adminUser + return avsID } // GetVirtualMachineAvailabilitySetIDE gets the Availability Set ID of the specified Azure Virtual Machine. -func GetVirtualMachineAvailabilitySetIDE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (string, error) { +func GetVirtualMachineAvailabilitySetIDE(vmName string, resGroupName string, subscriptionID string) (string, error) { // Get VM Object - vm, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) + vm, err := GetVirtualMachineE(vmName, resGroupName, subscriptionID) if err != nil { return "", err } + if vm.AvailabilitySet == nil { + return "", NewNotFoundError("Availability Set", "", vmName) + } + return GetNameFromResourceID(*vm.AvailabilitySet.ID), nil } @@ -201,23 +205,27 @@ type VMImage struct { // GetVirtualMachineImage gets the Image of the specified Azure Virtual Machine. // This function would fail the test if there is an error. func GetVirtualMachineImage(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) VMImage { - adminUser, err := GetVirtualMachineImageE(t, vmName, resGroupName, subscriptionID) + adminUser, err := GetVirtualMachineImageE(vmName, resGroupName, subscriptionID) require.NoError(t, err) return adminUser } // GetVirtualMachineImageE gets the Image of the specified Azure Virtual Machine. -func GetVirtualMachineImageE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (VMImage, error) { - vmImage := VMImage{} +func GetVirtualMachineImageE(vmName string, resGroupName string, subscriptionID string) (VMImage, error) { + var vmImage VMImage // Get VM Object - vm, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) + vm, err := GetVirtualMachineE(vmName, resGroupName, subscriptionID) if err != nil { return vmImage, err } - // Populate VM Image + if vm.StorageProfile == nil { + return vmImage, NewNotFoundError("Image Reference", "Any", vmName) + } + + // Populate VM Image; values always present, no nil checks needed vmImage.Publisher = *vm.StorageProfile.ImageReference.Publisher vmImage.Offer = *vm.StorageProfile.ImageReference.Offer vmImage.SKU = *vm.StorageProfile.ImageReference.Sku @@ -229,15 +237,15 @@ func GetVirtualMachineImageE(t testing.TestingT, vmName string, resGroupName str // GetVirtualMachineAdminUser gets the Admin Username of the specified Azure Virtual Machine. // This function would fail the test if there is an error. func GetVirtualMachineAdminUser(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) string { - adminUser, err := GetVirtualMachineAdminUserE(t, vmName, resGroupName, subscriptionID) + adminUser, err := GetVirtualMachineAdminUserE(vmName, resGroupName, subscriptionID) require.NoError(t, err) return adminUser } // GetVirtualMachineAdminUserE gets the Admin Username of the specified Azure Virtual Machine. -func GetVirtualMachineAdminUserE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (string, error) { +func GetVirtualMachineAdminUserE(vmName string, resGroupName string, subscriptionID string) (string, error) { // Get VM Object - vm, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) + vm, err := GetVirtualMachineE(vmName, resGroupName, subscriptionID) if err != nil { return "", err } @@ -248,16 +256,16 @@ func GetVirtualMachineAdminUserE(t testing.TestingT, vmName string, resGroupName // GetVirtualMachineSize gets the Size Type of the specified Azure Virtual Machine. // This function would fail the test if there is an error. func GetVirtualMachineSize(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) compute.VirtualMachineSizeTypes { - size, err := GetVirtualMachineSizeE(t, vmName, resGroupName, subscriptionID) + size, err := GetVirtualMachineSizeE(vmName, resGroupName, subscriptionID) require.NoError(t, err) return size } // GetVirtualMachineSizeE gets the Size Type of the specified Azure Virtual Machine. -func GetVirtualMachineSizeE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (compute.VirtualMachineSizeTypes, error) { +func GetVirtualMachineSizeE(vmName string, resGroupName string, subscriptionID string) (compute.VirtualMachineSizeTypes, error) { // Get VM Object - vm, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) + vm, err := GetVirtualMachineE(vmName, resGroupName, subscriptionID) if err != nil { return "", err } @@ -268,19 +276,19 @@ func GetVirtualMachineSizeE(t testing.TestingT, vmName string, resGroupName stri // GetVirtualMachineTags gets the Tags of the specified Virtual Machine as a map. // This function would fail the test if there is an error. func GetVirtualMachineTags(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) map[string]string { - tags, err := GetVirtualMachineTagsE(t, vmName, resGroupName, subscriptionID) + tags, err := GetVirtualMachineTagsE(vmName, resGroupName, subscriptionID) require.NoError(t, err) return tags } // GetVirtualMachineTagsE gets the Tags of the specified Virtual Machine as a map. -func GetVirtualMachineTagsE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (map[string]string, error) { +func GetVirtualMachineTagsE(vmName string, resGroupName string, subscriptionID string) (map[string]string, error) { // Setup a blank map to populate and return - tags := make(map[string]string) + var tags map[string]string // Get VM Object - vm, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) + vm, err := GetVirtualMachineE(vmName, resGroupName, subscriptionID) if err != nil { return nil, err } @@ -300,14 +308,14 @@ func GetVirtualMachineTagsE(t testing.TestingT, vmName string, resGroupName stri // GetResourceGroupVirtualMachines gets a list of all Virtual Machine names in the specified Resource Group. // This function would fail the test if there is an error. func GetResourceGroupVirtualMachines(t testing.TestingT, resGroupName string, subscriptionID string) []string { - vms, err := GetResourceGroupVirtualMachinesE(t, resGroupName, subscriptionID) + vms, err := GetResourceGroupVirtualMachinesE(resGroupName, subscriptionID) require.NoError(t, err) return vms } // GetResourceGroupVirtualMachinesE gets a list of all Virtual Machine names in the specified Resource Group. -func GetResourceGroupVirtualMachinesE(t testing.TestingT, resourceGroupName string, subscriptionID string) ([]string, error) { - vmDetails := []string{} +func GetResourceGroupVirtualMachinesE(resourceGroupName string, subscriptionID string) ([]string, error) { + var vmDetails []string vmClient, err := GetVirtualMachineClientE(subscriptionID) if err != nil { @@ -328,13 +336,13 @@ func GetResourceGroupVirtualMachinesE(t testing.TestingT, resourceGroupName stri // GetResourceGroupVirtualMachinesObjects gets all Virtual Machine objects in the specified Resource Group. // This function would fail the test if there is an error. func GetResourceGroupVirtualMachinesObjects(t testing.TestingT, resGroupName string, subscriptionID string) *map[string]compute.VirtualMachineProperties { - vms, err := GetResourceGroupVirtualMachinesObjectsE(t, resGroupName, subscriptionID) + vms, err := GetResourceGroupVirtualMachinesObjectsE(resGroupName, subscriptionID) require.NoError(t, err) return vms } // GetResourceGroupVirtualMachinesObjectsE gets all Virtual Machine objects in the specified Resource Group. -func GetResourceGroupVirtualMachinesObjectsE(t testing.TestingT, resourceGroupName string, subscriptionID string) (*map[string]compute.VirtualMachineProperties, error) { +func GetResourceGroupVirtualMachinesObjectsE(resourceGroupName string, subscriptionID string) (*map[string]compute.VirtualMachineProperties, error) { vmClient, err := GetVirtualMachineClientE(subscriptionID) if err != nil { return nil, err @@ -366,15 +374,15 @@ type Instance struct { // GetVirtualMachineInstance gets a local Virtual Machine instance in the specified Resource Group. // This function would fail the test if there is an error. func GetVirtualMachineInstance(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) *Instance { - vm, err := GetVirtualMachineInstanceE(t, vmName, resGroupName, subscriptionID) + vm, err := GetVirtualMachineInstanceE(vmName, resGroupName, subscriptionID) require.NoError(t, err) return vm } // GetVirtualMachineInstanceE gets a local Virtual Machine instance in the specified Resource Group. -func GetVirtualMachineInstanceE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (*Instance, error) { +func GetVirtualMachineInstanceE(vmName string, resGroupName string, subscriptionID string) (*Instance, error) { // Get VM Object - vm, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) + vm, err := GetVirtualMachineE(vmName, resGroupName, subscriptionID) if err != nil { return nil, err } @@ -394,13 +402,13 @@ func (vm *Instance) GetVirtualMachineInstanceSize() compute.VirtualMachineSizeTy // GetVirtualMachine gets a Virtual Machine in the specified Azure Resource Group. // This function would fail the test if there is an error. func GetVirtualMachine(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) *compute.VirtualMachine { - vm, err := GetVirtualMachineE(t, vmName, resGroupName, subscriptionID) + vm, err := GetVirtualMachineE(vmName, resGroupName, subscriptionID) require.NoError(t, err) return vm } // GetVirtualMachineE gets a Virtual Machine in the specified Azure Resource Group. -func GetVirtualMachineE(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) (*compute.VirtualMachine, error) { +func GetVirtualMachineE(vmName string, resGroupName string, subscriptionID string) (*compute.VirtualMachine, error) { // Validate resource group name and subscription ID resGroupName, err := getTargetAzureResourceGroupName(resGroupName) if err != nil { diff --git a/modules/azure/compute_test.go b/modules/azure/compute_test.go index 7b8914754..dfac658a8 100644 --- a/modules/azure/compute_test.go +++ b/modules/azure/compute_test.go @@ -24,7 +24,7 @@ func TestGetVirtualMachineE(t *testing.T) { rgName := "" subID := "" - _, err := GetVirtualMachineE(t, vmName, rgName, subID) + _, err := GetVirtualMachineE(vmName, rgName, subID) require.Error(t, err) } @@ -36,7 +36,7 @@ func TestGetVirtualMachineInstanceE(t *testing.T) { rgName := "" subID := "" - _, err := GetVirtualMachineInstanceE(t, vmName, rgName, subID) + _, err := GetVirtualMachineInstanceE(vmName, rgName, subID) require.Error(t, err) } @@ -47,7 +47,7 @@ func TestGetResourceGroupVirtualMachinesObjectsE(t *testing.T) { rgName := "" subID := "" - _, err := GetResourceGroupVirtualMachinesObjectsE(t, rgName, subID) + _, err := GetResourceGroupVirtualMachinesObjectsE(rgName, subID) require.Error(t, err) } @@ -58,7 +58,7 @@ func TestGetResourceGroupVirtualMachinesE(t *testing.T) { rgName := "" subID := "" - _, err := GetResourceGroupVirtualMachinesE(t, rgName, subID) + _, err := GetResourceGroupVirtualMachinesE(rgName, subID) require.Error(t, err) } @@ -70,7 +70,7 @@ func TestGetVirtualMachineTagsE(t *testing.T) { rgName := "" subID := "" - _, err := GetVirtualMachineTagsE(t, vmName, rgName, subID) + _, err := GetVirtualMachineTagsE(vmName, rgName, subID) require.Error(t, err) } @@ -82,7 +82,7 @@ func TestGetVirtualMachineSizeE(t *testing.T) { rgName := "" subID := "" - _, err := GetVirtualMachineSizeE(t, vmName, rgName, subID) + _, err := GetVirtualMachineSizeE(vmName, rgName, subID) require.Error(t, err) } @@ -94,7 +94,7 @@ func TestGetVirtualMachineAdminUserE(t *testing.T) { rgName := "" subID := "" - _, err := GetVirtualMachineAdminUserE(t, vmName, rgName, subID) + _, err := GetVirtualMachineAdminUserE(vmName, rgName, subID) require.Error(t, err) } @@ -106,7 +106,7 @@ func TestGetVirtualMachineImageE(t *testing.T) { rgName := "" subID := "" - _, err := GetVirtualMachineImageE(t, vmName, rgName, subID) + _, err := GetVirtualMachineImageE(vmName, rgName, subID) require.Error(t, err) } @@ -118,7 +118,7 @@ func TestGetVirtualMachineAvailabilitySetIDE(t *testing.T) { rgName := "" subID := "" - _, err := GetVirtualMachineAvailabilitySetIDE(t, vmName, rgName, subID) + _, err := GetVirtualMachineAvailabilitySetIDE(vmName, rgName, subID) require.Error(t, err) } @@ -130,7 +130,7 @@ func TestGetVirtualMachineStateE(t *testing.T) { rgName := "" subID := "" - _, err := GetVirtualMachineStateE(t, vmName, rgName, subID) + _, err := GetVirtualMachineStateE(vmName, rgName, subID) require.Error(t, err) } @@ -142,7 +142,7 @@ func TestGetVirtualMachineOsDiskNameE(t *testing.T) { rgName := "" subID := "" - _, err := GetVirtualMachineOsDiskNameE(t, vmName, rgName, subID) + _, err := GetVirtualMachineOsDiskNameE(vmName, rgName, subID) require.Error(t, err) } @@ -154,7 +154,7 @@ func TestGetVirtualMachineManagedDiskCountE(t *testing.T) { rgName := "" subID := "" - _, err := GetVirtualMachineManagedDiskCountE(t, vmName, rgName, subID) + _, err := GetVirtualMachineManagedDiskCountE(vmName, rgName, subID) require.Error(t, err) } @@ -166,7 +166,7 @@ func TestGetVirtualMachineManagedDisksE(t *testing.T) { rgName := "" subID := "" - _, err := GetVirtualMachineManagedDisksE(t, vmName, rgName, subID) + _, err := GetVirtualMachineManagedDisksE(vmName, rgName, subID) require.Error(t, err) } @@ -178,7 +178,7 @@ func TestGetVirtualMachineNicCountE(t *testing.T) { rgName := "" subID := "" - _, err := GetVirtualMachineNicCountE(t, vmName, rgName, subID) + _, err := GetVirtualMachineNicCountE(vmName, rgName, subID) require.Error(t, err) } @@ -190,7 +190,7 @@ func TestGetVirtualMachineNicsE(t *testing.T) { rgName := "" subID := "" - _, err := GetVirtualMachineNicsE(t, vmName, rgName, subID) + _, err := GetVirtualMachineNicsE(vmName, rgName, subID) require.Error(t, err) } @@ -202,7 +202,7 @@ func TestVirtualMachineExistsE(t *testing.T) { rgName := "" subID := "" - _, err := VirtualMachineExistsE(t, vmName, rgName, subID) + _, err := VirtualMachineExistsE(vmName, rgName, subID) require.Error(t, err) } diff --git a/modules/azure/disk.go b/modules/azure/disk.go index 97a4459fd..dab3efa58 100644 --- a/modules/azure/disk.go +++ b/modules/azure/disk.go @@ -21,6 +21,9 @@ func DiskExistsE(t testing.TestingT, diskName string, resGroupName string, subsc // Get the Disk object _, err := GetDiskE(t, diskName, resGroupName, subscriptionID) if err != nil { + if ResourceNotFoundErrorExists(err) { + return false, nil + } return false, err } return true, nil diff --git a/modules/azure/errors.go b/modules/azure/errors.go index 36d74454d..c359fa28d 100644 --- a/modules/azure/errors.go +++ b/modules/azure/errors.go @@ -44,7 +44,13 @@ type NotFoundError struct { } func (err NotFoundError) Error() string { - return fmt.Sprintf("Object of type %s with id %s not found in %s", err.objectType, err.objectID, err.searchSpace) + var objIDMsg string + + if err.objectID != "Any" { + objIDMsg = fmt.Sprintf(" with id %s", err.objectID) + } + + return fmt.Sprintf("Object of type %s%s not found in %s", err.objectType, objIDMsg, err.searchSpace) } // NewNotFoundError creates a new not found error when an expected object is not found in the search space diff --git a/test/azure/terraform_azure_vm_example_test.go b/test/azure/terraform_azure_vm_example_test.go index 5754be55c..2e2c940f3 100644 --- a/test/azure/terraform_azure_vm_example_test.go +++ b/test/azure/terraform_azure_vm_example_test.go @@ -22,7 +22,7 @@ func TestTerraformAzureVmExample(t *testing.T) { // subscriptionID is overridden by the environment variable "ARM_SUBSCRIPTION_ID" subscriptionID := "" - uniquePostfix := random.UniqueId() + uniquePostfix := random.UniqueId() // "resource" // expectedVmAdminUser := "testadmin" expectedVMSize := compute.VirtualMachineSizeTypes("Standard_B1s") expectedImageSKU := "2019-Datacenter-Core-smalldisk" @@ -87,14 +87,14 @@ func TestTerraformAzureVmExample(t *testing.T) { // Check the VM Size by instance alternate example. // This strategy is beneficial when checking multiple properties by using one VM instance and making - // calls against it with the added benefit of module abstraction. + // calls against it with the added benefit of property checks and abstraction. vmInstance := azure.GetVirtualMachineInstance(t, virtualMachineName, resourceGroupName, subscriptionID) actualVMSize = vmInstance.GetVirtualMachineInstanceSize() assert.Equal(t, expectedVMSize, actualVMSize) }) t.Run("MultipleVMs", func(t *testing.T) { - // Ths approach is beneficial when multiple VMs need to be tested at once. + // This is beneficial when multiple VMs in a Resource Group need to be tested at once. // Check against all VM names in a Resource Group. vmList := azure.GetResourceGroupVirtualMachines(t, resourceGroupName, subscriptionID) From 07b5eaee622377f1556dfce6332818cd7a26f47a Mon Sep 17 00:00:00 2001 From: Dave Seepersad <“davesee@microsoft.com”> Date: Thu, 1 Oct 2020 13:16:39 -0400 Subject: [PATCH 44/56] Feedback updates --- .../azure/terraform-azure-vm-example/main.tf | 66 +++++++++---------- .../terraform-azure-vm-example/outputs.tf | 18 ++--- modules/azure/compute.go | 61 +++++++++-------- test/azure/terraform_azure_vm_example_test.go | 4 +- 4 files changed, 78 insertions(+), 71 deletions(-) diff --git a/examples/azure/terraform-azure-vm-example/main.tf b/examples/azure/terraform-azure-vm-example/main.tf index 14c805c06..8bafaceb7 100644 --- a/examples/azure/terraform-azure-vm-example/main.tf +++ b/examples/azure/terraform-azure-vm-example/main.tf @@ -7,7 +7,7 @@ # --------------------------------------------------------------------------------------------------------------------- provider "azurerm" { - version = "~> 2.20" + version = "~> 2.29" features {} } @@ -27,7 +27,7 @@ terraform { # DEPLOY A RESOURCE GROUP # --------------------------------------------------------------------------------------------------------------------- -resource "azurerm_resource_group" "vm" { +resource "azurerm_resource_group" "vm_rg" { name = "terratest-vm-rg-${var.postfix}" location = var.location } @@ -37,41 +37,41 @@ resource "azurerm_resource_group" "vm" { # This network includes a public address for integration tests # --------------------------------------------------------------------------------------------------------------------- -resource "azurerm_virtual_network" "vm" { +resource "azurerm_virtual_network" "vnet" { name = "vnet-${var.postfix}" address_space = ["10.0.0.0/16"] - location = azurerm_resource_group.vm.location - resource_group_name = azurerm_resource_group.vm.name + location = azurerm_resource_group.vm_rg.location + resource_group_name = azurerm_resource_group.vm_rg.name } -resource "azurerm_subnet" "vm" { +resource "azurerm_subnet" "subnet" { name = "subnet-${var.postfix}" - resource_group_name = azurerm_resource_group.vm.name - virtual_network_name = azurerm_virtual_network.vm.name + resource_group_name = azurerm_resource_group.vm_rg.name + virtual_network_name = azurerm_virtual_network.vnet.name address_prefixes = [var.subnet_prefix] } -resource "azurerm_public_ip" "vm" { +resource "azurerm_public_ip" "pip" { name = "pip-${var.postfix}" - resource_group_name = azurerm_resource_group.vm.name - location = azurerm_resource_group.vm.location + resource_group_name = azurerm_resource_group.vm_rg.name + location = azurerm_resource_group.vm_rg.location allocation_method = "Static" ip_version = "IPv4" sku = "Standard" idle_timeout_in_minutes = "4" } -resource "azurerm_network_interface" "vm" { +resource "azurerm_network_interface" "nic" { name = "nic-${var.postfix}" - location = azurerm_resource_group.vm.location - resource_group_name = azurerm_resource_group.vm.name + location = azurerm_resource_group.vm_rg.location + resource_group_name = azurerm_resource_group.vm_rg.name ip_configuration { name = "terratestconfiguration1" - subnet_id = azurerm_subnet.vm.id + subnet_id = azurerm_subnet.subnet.id private_ip_address_allocation = "Static" private_ip_address = var.private_ip - public_ip_address_id = azurerm_public_ip.vm.id + public_ip_address_id = azurerm_public_ip.pip.id } } @@ -79,10 +79,10 @@ resource "azurerm_network_interface" "vm" { # DEPLOY AN AVAILABILITY SET # --------------------------------------------------------------------------------------------------------------------- -resource "azurerm_availability_set" "vm" { +resource "azurerm_availability_set" "avs" { name = "avs-${var.postfix}" - location = azurerm_resource_group.vm.location - resource_group_name = azurerm_resource_group.vm.name + location = azurerm_resource_group.vm_rg.location + resource_group_name = azurerm_resource_group.vm_rg.name platform_fault_domain_count = 2 managed = true } @@ -92,12 +92,12 @@ resource "azurerm_availability_set" "vm" { # This VM does not actually do anything and is the smallest size VM available with a Windows image # --------------------------------------------------------------------------------------------------------------------- -resource "azurerm_virtual_machine" "vm" { +resource "azurerm_virtual_machine" "vm_example" { name = "vm-${var.postfix}" - location = azurerm_resource_group.vm.location - resource_group_name = azurerm_resource_group.vm.name - network_interface_ids = [azurerm_network_interface.vm.id] - availability_set_id = azurerm_availability_set.vm.id + location = azurerm_resource_group.vm_rg.location + resource_group_name = azurerm_resource_group.vm_rg.name + network_interface_ids = [azurerm_network_interface.nic.id] + availability_set_id = azurerm_availability_set.avs.id vm_size = var.vm_size license_type = var.vm_license_type delete_os_disk_on_termination = true @@ -120,16 +120,16 @@ resource "azurerm_virtual_machine" "vm" { os_profile { computer_name = "vm-${var.postfix}" admin_username = var.user_name - admin_password = random_password.vm.result + admin_password = random_password.rand.result } os_profile_windows_config { provision_vm_agent = true } - depends_on = [random_password.vm] + depends_on = [random_password.rand] } -resource "random_password" "vm" { +resource "random_password" "rand" { length = 16 override_special = "-_%@" min_upper = "1" @@ -142,18 +142,18 @@ resource "random_password" "vm" { # ATTACH A MANAGED DISK TO THE VIRTUAL MACHINE # --------------------------------------------------------------------------------------------------------------------- -resource "azurerm_managed_disk" "vm" { +resource "azurerm_managed_disk" "disk" { name = "disk-${var.postfix}" - location = azurerm_resource_group.vm.location - resource_group_name = azurerm_resource_group.vm.name + location = azurerm_resource_group.vm_rg.location + resource_group_name = azurerm_resource_group.vm_rg.name storage_account_type = var.disk_type create_option = "Empty" disk_size_gb = 10 } -resource "azurerm_virtual_machine_data_disk_attachment" "vm" { - managed_disk_id = azurerm_managed_disk.vm.id - virtual_machine_id = azurerm_virtual_machine.vm.id +resource "azurerm_virtual_machine_data_disk_attachment" "vm_disk" { + managed_disk_id = azurerm_managed_disk.disk.id + virtual_machine_id = azurerm_virtual_machine.vm_example.id caching = "ReadWrite" lun = 10 } diff --git a/examples/azure/terraform-azure-vm-example/outputs.tf b/examples/azure/terraform-azure-vm-example/outputs.tf index a89020f3b..06cfea8f0 100644 --- a/examples/azure/terraform-azure-vm-example/outputs.tf +++ b/examples/azure/terraform-azure-vm-example/outputs.tf @@ -1,36 +1,36 @@ output "availability_set_name" { - value = azurerm_availability_set.vm.name + value = azurerm_availability_set.avs.name } output "managed_disk_name" { - value = azurerm_managed_disk.vm.name + value = azurerm_managed_disk.disk.name } output "network_interface_name" { - value = azurerm_network_interface.vm.name + value = azurerm_network_interface.nic.name } output "os_disk_name" { - value = azurerm_virtual_machine.vm.storage_os_disk[0].name + value = azurerm_virtual_machine.vm_example.storage_os_disk[0].name } output "public_ip_name" { - value = azurerm_public_ip.vm.name + value = azurerm_public_ip.pip.name } output "resource_group_name" { - value = azurerm_resource_group.vm.name + value = azurerm_resource_group.vm_rg.name } output "subnet_name" { - value = azurerm_subnet.vm.name + value = azurerm_subnet.subnet.name } output "virtual_network_name" { - value = azurerm_virtual_network.vm.name + value = azurerm_virtual_network.vnet.name } output "vm_name" { - value = azurerm_virtual_machine.vm.name + value = azurerm_virtual_machine.vm_example.name } diff --git a/modules/azure/compute.go b/modules/azure/compute.go index 72ee5cf04..5e8bc9f42 100644 --- a/modules/azure/compute.go +++ b/modules/azure/compute.go @@ -40,23 +40,24 @@ func GetVirtualMachineNics(t testing.TestingT, vmName string, resGroupName strin // GetVirtualMachineNicsE gets a list of Network Interface names for a specified Azure Virtual Machine. func GetVirtualMachineNicsE(vmName string, resGroupName string, subscriptionID string) ([]string, error) { - var nics []string // Get VM Object vm, err := GetVirtualMachineE(vmName, resGroupName, subscriptionID) if err != nil { - return nics, err + return nil, err } vmNICs := *vm.NetworkProfile.NetworkInterfaces if len(vmNICs) == 0 { - // If the NIC is not present - return nics, NewNotFoundError("Network Interface", "Any", vmName) + // No NIC present + return nil, nil } - // Get the attached NIC names - for _, nic := range vmNICs { - nics = append(nics, GetNameFromResourceID(*nic.ID)) + // Get the Names of the attached NICs + nics := make([]string, len(vmNICs)) + + for i, nic := range vmNICs { + nics[i] = GetNameFromResourceID(*nic.ID) } return nics, nil } @@ -72,12 +73,10 @@ func GetVirtualMachineNicCount(t testing.TestingT, vmName string, resGroupName s // GetVirtualMachineNicCountE gets the Network Interface count of the specified Azure Virtual Machine. func GetVirtualMachineNicCountE(vmName string, resGroupName string, subscriptionID string) (int, error) { - var nicCount int - // Get VM Object vm, err := GetVirtualMachineE(vmName, resGroupName, subscriptionID) if err != nil { - return nicCount, err + return -1, err } return len(*vm.NetworkProfile.NetworkInterfaces), nil @@ -94,16 +93,23 @@ func GetVirtualMachineManagedDisks(t testing.TestingT, vmName string, resGroupNa // GetVirtualMachineManagedDisksE gets the list of Managed Disk names of the specified Azure Virtual Machine. func GetVirtualMachineManagedDisksE(vmName string, resGroupName string, subscriptionID string) ([]string, error) { - var diskNames []string // Get VM Object vm, err := GetVirtualMachineE(vmName, resGroupName, subscriptionID) if err != nil { - return diskNames, err + return nil, err } - // Get VM Attached Disks + // Get VM attached Disks vmDisks := *vm.StorageProfile.DataDisks + + // No Attached Disks present + if len(vmDisks) == 0 { + return nil, nil + } + + // Get the Names of the attached Managed Disks + diskNames := make([]string, len(vmDisks)) for _, v := range vmDisks { diskNames = append(diskNames, *v.Name) } @@ -125,7 +131,7 @@ func GetVirtualMachineManagedDiskCountE(vmName string, resGroupName string, subs // Get VM Object vm, err := GetVirtualMachineE(vmName, resGroupName, subscriptionID) if err != nil { - return 0, err + return -1, err } return len(*vm.StorageProfile.DataDisks), nil @@ -187,8 +193,9 @@ func GetVirtualMachineAvailabilitySetIDE(vmName string, resGroupName string, sub return "", err } + // Virtual Machine has no associated Availability Set if vm.AvailabilitySet == nil { - return "", NewNotFoundError("Availability Set", "", vmName) + return "", nil } return GetNameFromResourceID(*vm.AvailabilitySet.ID), nil @@ -205,10 +212,10 @@ type VMImage struct { // GetVirtualMachineImage gets the Image of the specified Azure Virtual Machine. // This function would fail the test if there is an error. func GetVirtualMachineImage(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) VMImage { - adminUser, err := GetVirtualMachineImageE(vmName, resGroupName, subscriptionID) + vmImage, err := GetVirtualMachineImageE(vmName, resGroupName, subscriptionID) require.NoError(t, err) - return adminUser + return vmImage } // GetVirtualMachineImageE gets the Image of the specified Azure Virtual Machine. @@ -305,16 +312,16 @@ func GetVirtualMachineTagsE(vmName string, resGroupName string, subscriptionID s // Get multiple Virtual Machines from a Resource Group // ***************************************************** // -// GetResourceGroupVirtualMachines gets a list of all Virtual Machine names in the specified Resource Group. +// ListVirtualMachinesForResourceGroup gets a list of all Virtual Machine names in the specified Resource Group. // This function would fail the test if there is an error. -func GetResourceGroupVirtualMachines(t testing.TestingT, resGroupName string, subscriptionID string) []string { - vms, err := GetResourceGroupVirtualMachinesE(resGroupName, subscriptionID) +func ListVirtualMachinesForResourceGroup(t testing.TestingT, resGroupName string, subscriptionID string) []string { + vms, err := ListVirtualMachinesForResourceGroupE(resGroupName, subscriptionID) require.NoError(t, err) return vms } -// GetResourceGroupVirtualMachinesE gets a list of all Virtual Machine names in the specified Resource Group. -func GetResourceGroupVirtualMachinesE(resourceGroupName string, subscriptionID string) ([]string, error) { +// ListVirtualMachinesForResourceGroupE gets a list of all Virtual Machine names in the specified Resource Group. +func ListVirtualMachinesForResourceGroupE(resourceGroupName string, subscriptionID string) ([]string, error) { var vmDetails []string vmClient, err := GetVirtualMachineClientE(subscriptionID) @@ -333,16 +340,16 @@ func GetResourceGroupVirtualMachinesE(resourceGroupName string, subscriptionID s return vmDetails, nil } -// GetResourceGroupVirtualMachinesObjects gets all Virtual Machine objects in the specified Resource Group. +// GetVirtualMachinesForResourceGroup gets all Virtual Machine objects in the specified Resource Group. // This function would fail the test if there is an error. -func GetResourceGroupVirtualMachinesObjects(t testing.TestingT, resGroupName string, subscriptionID string) *map[string]compute.VirtualMachineProperties { - vms, err := GetResourceGroupVirtualMachinesObjectsE(resGroupName, subscriptionID) +func GetVirtualMachinesForResourceGroup(t testing.TestingT, resGroupName string, subscriptionID string) *map[string]compute.VirtualMachineProperties { + vms, err := GetVirtualMachinesForResourceGroupE(resGroupName, subscriptionID) require.NoError(t, err) return vms } -// GetResourceGroupVirtualMachinesObjectsE gets all Virtual Machine objects in the specified Resource Group. -func GetResourceGroupVirtualMachinesObjectsE(resourceGroupName string, subscriptionID string) (*map[string]compute.VirtualMachineProperties, error) { +// GetVirtualMachinesForResourceGroupE gets all Virtual Machine objects in the specified Resource Group. +func GetVirtualMachinesForResourceGroupE(resourceGroupName string, subscriptionID string) (*map[string]compute.VirtualMachineProperties, error) { vmClient, err := GetVirtualMachineClientE(subscriptionID) if err != nil { return nil, err diff --git a/test/azure/terraform_azure_vm_example_test.go b/test/azure/terraform_azure_vm_example_test.go index 2e2c940f3..7f362e9b0 100644 --- a/test/azure/terraform_azure_vm_example_test.go +++ b/test/azure/terraform_azure_vm_example_test.go @@ -97,7 +97,7 @@ func TestTerraformAzureVmExample(t *testing.T) { // This is beneficial when multiple VMs in a Resource Group need to be tested at once. // Check against all VM names in a Resource Group. - vmList := azure.GetResourceGroupVirtualMachines(t, resourceGroupName, subscriptionID) + vmList := azure.ListVirtualMachinesForResourceGroup(t, resourceGroupName, subscriptionID) assert.True(t, len(vmList) > 0) assert.Contains(t, vmList, virtualMachineName) @@ -105,7 +105,7 @@ func TestTerraformAzureVmExample(t *testing.T) { // This strategy is beneficial when checking multiple VMs & their properties by avoiding // multiple SDK calls. The penalty for this approach is introducing direct references // which need to be checked for nil for optional configurations. - vmsByRef := azure.GetResourceGroupVirtualMachinesObjects(t, resourceGroupName, subscriptionID) + vmsByRef := azure.GetVirtualMachinesForResourceGroup(t, resourceGroupName, subscriptionID) assert.True(t, len(*vmsByRef) > 0) // Check for the VM. From 8ea10728727286b33b465e84d2a84c8a72acb83a Mon Sep 17 00:00:00 2001 From: Dave Seepersad <“davesee@microsoft.com”> Date: Thu, 1 Oct 2020 13:23:46 -0400 Subject: [PATCH 45/56] Update unit test names to match --- modules/azure/compute_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/azure/compute_test.go b/modules/azure/compute_test.go index dfac658a8..4476a044a 100644 --- a/modules/azure/compute_test.go +++ b/modules/azure/compute_test.go @@ -41,24 +41,24 @@ func TestGetVirtualMachineInstanceE(t *testing.T) { require.Error(t, err) } -func TestGetResourceGroupVirtualMachinesObjectsE(t *testing.T) { +func TestListVirtualMachinesForResourceGroupE(t *testing.T) { t.Parallel() rgName := "" subID := "" - _, err := GetResourceGroupVirtualMachinesObjectsE(rgName, subID) + _, err := ListVirtualMachinesForResourceGroupE(rgName, subID) require.Error(t, err) } -func TestGetResourceGroupVirtualMachinesE(t *testing.T) { +func TestGetVirtualMachinesForResourceGroupE(t *testing.T) { t.Parallel() rgName := "" subID := "" - _, err := GetResourceGroupVirtualMachinesE(rgName, subID) + _, err := GetVirtualMachinesForResourceGroupE(rgName, subID) require.Error(t, err) } From 507c1e135d24f443c4e8d460160b4fbbde26f21f Mon Sep 17 00:00:00 2001 From: Dave Seepersad <“davesee@microsoft.com”> Date: Thu, 1 Oct 2020 20:15:45 -0400 Subject: [PATCH 46/56] Updated original vm test --- examples/azure/README.md | 2 +- .../azure/terraform-azure-example/main.tf | 44 +++++++++---------- .../azure/terraform-azure-example/outputs.tf | 7 ++- .../terraform-azure-example/variables.tf | 18 +++----- .../terraform-azure-vm-example/README.md | 24 +++++----- .../azure/terraform-azure-vm-example/main.tf | 7 +-- test/azure/terraform_azure_example_test.go | 5 +-- 7 files changed, 49 insertions(+), 58 deletions(-) diff --git a/examples/azure/README.md b/examples/azure/README.md index b3ba255e3..8dbca3ec8 100644 --- a/examples/azure/README.md +++ b/examples/azure/README.md @@ -35,7 +35,7 @@ go build terraform_azure_*_test.go ## Review Environment Variables -As part of configuring terraform for Azure, we'll want to check that we have set the appropriate [credentials](https://docs.microsoft.com/en-us/azure/terraform/terraform-install-configure?toc=https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fterraform%2Ftoc.json&bc=https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fbread%2Ftoc.json#set-up-terraform-access-to-azure) and also that we set the [environment variables](https://docs.microsoft.com/en-us/azure/terraform/terraform-install-configure?toc=https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fterraform%2Ftoc.json&bc=https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fbread%2Ftoc.json#configure-terraform-environment-variables) on the testing host. +As part of configuring terraform for Azure, we'll want to check that we have set the appropriate [credentials](https://docs.microsoft.com/azure/terraform/terraform-install-configure?toc=https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fterraform%2Ftoc.json&bc=https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fbread%2Ftoc.json#set-up-terraform-access-to-azure) and also that we set the [environment variables](https://docs.microsoft.com/azure/terraform/terraform-install-configure?toc=https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fterraform%2Ftoc.json&bc=https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fbread%2Ftoc.json#configure-terraform-environment-variables) on the testing host. ```bash export ARM_CLIENT_ID=your_app_id diff --git a/examples/azure/terraform-azure-example/main.tf b/examples/azure/terraform-azure-example/main.tf index d858a9d67..18da47440 100644 --- a/examples/azure/terraform-azure-example/main.tf +++ b/examples/azure/terraform-azure-example/main.tf @@ -1,14 +1,16 @@ # --------------------------------------------------------------------------------------------------------------------- -# DEPLOY AN AZURE VIRTUAL MACHINE -# This is a basic example of how to deploy an Azure Virtual Machine with the minimum network resources. +# DEPLOY A SIMPLE AZURE VIRTUAL MACHINE +# This is a simple example of how to deploy an Azure Virtual Machine with the minimum network resources. # --------------------------------------------------------------------------------------------------------------------- # See test/azure/terraform_azure_example_test.go for how to write automated tests for this code. # --------------------------------------------------------------------------------------------------------------------- provider "azurerm" { + version = "~> 2.29" features {} } + # --------------------------------------------------------------------------------------------------------------------- # PIN TERRAFORM VERSION TO >= 0.12 # The examples have been upgraded to 0.12 syntax @@ -21,26 +23,12 @@ terraform { required_version = ">= 0.12.26" } -# --------------------------------------------------------------------------------------------------------------------- -# GENERATE RANDOMIZATION STRING -# This random password is used to improve test security -# --------------------------------------------------------------------------------------------------------------------- - -resource "random_password" "main" { - length = 16 - override_special = "-_%@" - min_upper = "1" - min_lower = "1" - min_numeric = "1" - min_special = "1" -} - # --------------------------------------------------------------------------------------------------------------------- # DEPLOY A RESOURCE GROUP # --------------------------------------------------------------------------------------------------------------------- resource "azurerm_resource_group" "main" { - name = "${var.prefix}-resources" + name = "terratest-rg-${var.postfix}" location = "East US" } @@ -49,21 +37,21 @@ resource "azurerm_resource_group" "main" { # --------------------------------------------------------------------------------------------------------------------- resource "azurerm_virtual_network" "main" { - name = "${var.prefix}-network" + name = "vnet-${var.postfix}" address_space = ["10.0.0.0/16"] location = azurerm_resource_group.main.location resource_group_name = azurerm_resource_group.main.name } resource "azurerm_subnet" "internal" { - name = "${var.prefix}-subnet" + name = "subnet-${var.postfix}" resource_group_name = azurerm_resource_group.main.name virtual_network_name = azurerm_virtual_network.main.name - address_prefix = "10.0.17.0/24" + address_prefixes = ["10.0.17.0/24"] } resource "azurerm_network_interface" "main" { - name = "${var.prefix}-nic" + name = "nic-${var.postfix}" location = azurerm_resource_group.main.location resource_group_name = azurerm_resource_group.main.name @@ -79,7 +67,7 @@ resource "azurerm_network_interface" "main" { # --------------------------------------------------------------------------------------------------------------------- resource "azurerm_virtual_machine" "main" { - name = "${var.prefix}-vm" + name = "vm-${var.postfix}" location = azurerm_resource_group.main.location resource_group_name = azurerm_resource_group.main.name network_interface_ids = [azurerm_network_interface.main.id] @@ -102,7 +90,7 @@ resource "azurerm_virtual_machine" "main" { } os_profile { - computer_name = var.hostname + computer_name = "vm-${var.postfix}" admin_username = var.username admin_password = random_password.main.result } @@ -110,5 +98,15 @@ resource "azurerm_virtual_machine" "main" { os_profile_linux_config { disable_password_authentication = false } + + depends_on = [random_password.main] } +resource "random_password" "main" { + length = 16 + override_special = "-_%@" + min_upper = "1" + min_lower = "1" + min_numeric = "1" + min_special = "1" +} diff --git a/examples/azure/terraform-azure-example/outputs.tf b/examples/azure/terraform-azure-example/outputs.tf index 8eca512f9..be48c5dce 100644 --- a/examples/azure/terraform-azure-example/outputs.tf +++ b/examples/azure/terraform-azure-example/outputs.tf @@ -1,8 +1,7 @@ -output "vm_name" { - value = azurerm_virtual_machine.main.name -} - output "resource_group_name" { value = azurerm_resource_group.main.name } +output "vm_name" { + value = azurerm_virtual_machine.main.name +} diff --git a/examples/azure/terraform-azure-example/variables.tf b/examples/azure/terraform-azure-example/variables.tf index f35f90c88..450bfae26 100644 --- a/examples/azure/terraform-azure-example/variables.tf +++ b/examples/azure/terraform-azure-example/variables.tf @@ -18,22 +18,16 @@ # These parameters have reasonable defaults. # --------------------------------------------------------------------------------------------------------------------- -variable "hostname" { - description = "The hostname of the new VM to be configured" +variable "location" { + description = "The Azure location where to deploy your resources too" type = string - default = "terratest-vm" + default = "East US" } -variable "password" { - description = "The password to configure for SSH access" +variable "postfix" { + description = "A postfix string to centrally mitigate resource name collisions" type = string - default = "HorriblePassword1234!" -} - -variable "prefix" { - description = "The prefix that will be attached to all resources deployed" - type = string - default = "terratest-example" + default = "resource" } variable "username" { diff --git a/examples/azure/terraform-azure-vm-example/README.md b/examples/azure/terraform-azure-vm-example/README.md index 8622ad06d..660c52889 100644 --- a/examples/azure/terraform-azure-vm-example/README.md +++ b/examples/azure/terraform-azure-vm-example/README.md @@ -3,15 +3,15 @@ This folder contains a complete Terraform VM module that deploys resources in [Azure](https://azure.microsoft.com/) to demonstrate how you can use Terratest to write automated tests for your Azure Virtual Machine Terraform code. This module deploys these resources: -- A [Virtual Machine](https://azure.microsoft.com/en-us/services/virtual-machines/) and gives that VM the following resources: - - [Virtual Machine](https://docs.microsoft.com/en-us/azure/virtual-machines/) with the name specified in the `vm_name` variable. - - [Managed Disk](https://docs.microsoft.com/en-us/azure/virtual-machines/managed-disks-overview) with the name specified in the `managed_disk_name` variable. - - [Availability Set](https://docs.microsoft.com/en-us/azure/virtual-machines/availability) with the name specified in the `availability_set_name` variable. -- A [Virtual Network](https://azure.microsoft.com/en-us/services/virtual-network/) module that contains the following resources: - - [Virtual Network](https://docs.microsoft.com/en-us/azure/virtual-network/) with the name specified in the `virtual_network_name` variable. - - [Subnet](https://docs.microsoft.com/en-us/rest/api/virtualnetwork/subnets) with the name specified in the `subnet_name` variable. - - [Public Address](https://docs.microsoft.com/en-us/azure/virtual-network/public-ip-addresses) with the name specified in the `public_ip_name` variable. - - [Network Interface](https://docs.microsoft.com/en-us/azure/virtual-network/virtual-network-network-interface) with the name specified in the `network_interface_name` variable. +- A [Virtual Machine](https://azure.microsoft.com/services/virtual-machines/) and gives that VM the following resources: + - [Virtual Machine](https://docs.microsoft.com/azure/virtual-machines/) with the name specified in the `vm_name` variable. + - [Managed Disk](https://docs.microsoft.com/azure/virtual-machines/managed-disks-overview) with the name specified in the `managed_disk_name` variable. + - [Availability Set](https://docs.microsoft.com/azure/virtual-machines/availability) with the name specified in the `availability_set_name` variable. +- A [Virtual Network](https://azure.microsoft.com/services/virtual-network/) module that contains the following resources: + - [Virtual Network](https://docs.microsoft.com/azure/virtual-network/) with the name specified in the `virtual_network_name` variable. + - [Subnet](https://docs.microsoft.com/rest/api/virtualnetwork/subnets) with the name specified in the `subnet_name` variable. + - [Public Address](https://docs.microsoft.com/azure/virtual-network/public-ip-addresses) with the name specified in the `public_ip_name` variable. + - [Network Interface](https://docs.microsoft.com/azure/virtual-network/virtual-network-network-interface) with the name specified in the `network_interface_name` variable. Check out [test/azure/terraform_azure_vm_test.go](/test/azure/terraform_azure_vm_test.go) to see how you can write automated tests for this module. @@ -19,14 +19,14 @@ automated tests for this module. Note that the Virtual Machine module creates a Microsoft Windows Server Image with a managed disk, availability set and network configuration for demonstration purposes. **WARNING**: This module and the automated tests for it deploy real resources into your Azure account which can cost you -money. The resources are all part of the [Azure Free Account](https://azure.microsoft.com/en-us/free/), so if you haven't used that up, +money. The resources are all part of the [Azure Free Account](https://azure.microsoft.com/free/), so if you haven't used that up, it should be free, but you are completely responsible for all Azure charges. ## Running this module manually 1. Sign up for [Azure](https://azure.microsoft.com/) 1. Configure your Azure credentials using one of the [supported methods for Azure CL - tools](https://docs.microsoft.com/en-us/cli/azure/azure-cli-configuration?view=azure-cli-latest) + tools](https://docs.microsoft.com/cli/azure/azure-cli-configuration?view=azure-cli-latest) 1. Install [Terraform](https://www.terraform.io/) and make sure it's on your `PATH` 1. Ensure [environment variables](../README.md#review-environment-variables) are available 1. Run `terraform init` @@ -37,7 +37,7 @@ it should be free, but you are completely responsible for all Azure charges. 1. Sign up for [Azure](https://azure.microsoft.com/) 1. Configure your Azure credentials using one of the [supported methods for Azure CLI - tools](https://docs.microsoft.com/en-us/cli/azure/azure-cli-configuration?view=azure-cli-latest) + tools](https://docs.microsoft.com/cli/azure/azure-cli-configuration?view=azure-cli-latest) 1. Install [Terraform](https://www.terraform.io/) and make sure it's on your `PATH` 1. Configure your Terratest [Go test environment](../README.md) 1. `cd test/azure` diff --git a/examples/azure/terraform-azure-vm-example/main.tf b/examples/azure/terraform-azure-vm-example/main.tf index 8bafaceb7..9a60d9932 100644 --- a/examples/azure/terraform-azure-vm-example/main.tf +++ b/examples/azure/terraform-azure-vm-example/main.tf @@ -1,7 +1,7 @@ # --------------------------------------------------------------------------------------------------------------------- -# DEPLOY A VIRTUAL MACHINE +# DEPLOY AN ADVANCED AZURE VIRTUAL MACHINE # This is an advanced example of how to deploy an Azure Virtual Machine in an availability set, managed disk -# and Networking with a Public IP. +# and networking with a public IP. # --------------------------------------------------------------------------------------------------------------------- # See test/azure/terraform_azure_vm_example_test.go for how to write automated tests for this code. # --------------------------------------------------------------------------------------------------------------------- @@ -34,7 +34,7 @@ resource "azurerm_resource_group" "vm_rg" { # --------------------------------------------------------------------------------------------------------------------- # DEPLOY NETWORK RESOURCES -# This network includes a public address for integration tests +# This network includes a public address for integration test demonstration purposes # --------------------------------------------------------------------------------------------------------------------- resource "azurerm_virtual_network" "vnet" { @@ -122,6 +122,7 @@ resource "azurerm_virtual_machine" "vm_example" { admin_username = var.user_name admin_password = random_password.rand.result } + os_profile_windows_config { provision_vm_agent = true } diff --git a/test/azure/terraform_azure_example_test.go b/test/azure/terraform_azure_example_test.go index dbd9e0ed7..79b3e6b69 100644 --- a/test/azure/terraform_azure_example_test.go +++ b/test/azure/terraform_azure_example_test.go @@ -6,7 +6,6 @@ package test import ( - "fmt" "testing" "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-07-01/compute" @@ -19,14 +18,14 @@ import ( func TestTerraformAzureExample(t *testing.T) { t.Parallel() - prefix := fmt.Sprintf("terratest-%s", random.UniqueId()) + uniquePostfix := random.UniqueId() // website::tag::1:: Configure Terraform setting up a path to Terraform code. terraformOptions := &terraform.Options{ // The path to where our Terraform code is located TerraformDir: "../../examples/azure/terraform-azure-example", Vars: map[string]interface{}{ - "prefix": prefix, + "postfix": uniquePostfix, }, } From 252bd6a6dd016cb551055311293da61727442041 Mon Sep 17 00:00:00 2001 From: richard guthrie Date: Fri, 2 Oct 2020 13:54:02 -0700 Subject: [PATCH 47/56] Fixing some issues to resolve PR comments. --- modules/azure/disk.go | 2 +- test/azure/terraform_azure_vm_example_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/azure/disk.go b/modules/azure/disk.go index dab3efa58..d022fca01 100644 --- a/modules/azure/disk.go +++ b/modules/azure/disk.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/require" ) -// DiskExists indicates whether the specified Azure Managed Disk exists +// DiskExists indicates whether the specified Azure Managed Disk exists. // This function would fail the test if there is an error. func DiskExists(t testing.TestingT, diskName string, resGroupName string, subscriptionID string) bool { exists, err := DiskExistsE(t, diskName, resGroupName, subscriptionID) diff --git a/test/azure/terraform_azure_vm_example_test.go b/test/azure/terraform_azure_vm_example_test.go index 7f362e9b0..de7394d97 100644 --- a/test/azure/terraform_azure_vm_example_test.go +++ b/test/azure/terraform_azure_vm_example_test.go @@ -78,7 +78,7 @@ func TestTerraformAzureVmExample(t *testing.T) { actualVMSize := azure.GetVirtualMachineSize(t, virtualMachineName, resourceGroupName, subscriptionID) assert.Equal(t, expectedVMSize, actualVMSize) - // Check the VM Size by reference alternate example. + // An alternate example for checking the VM size by reference. // This strategy is beneficial when checking multiple properties by using one VM reference, avoiding // multiple SDK calls. vmRef := azure.GetVirtualMachine(t, virtualMachineName, resourceGroupName, subscriptionID) @@ -106,7 +106,7 @@ func TestTerraformAzureVmExample(t *testing.T) { // multiple SDK calls. The penalty for this approach is introducing direct references // which need to be checked for nil for optional configurations. vmsByRef := azure.GetVirtualMachinesForResourceGroup(t, resourceGroupName, subscriptionID) - assert.True(t, len(*vmsByRef) > 0) + assert.Greater(t, len(*vmsByRef) > 0) // Check for the VM. thisVM := (*vmsByRef)[virtualMachineName] From a7208d300c59fee712d6b3bf8f5f123980da9925 Mon Sep 17 00:00:00 2001 From: Dave Seepersad <“davesee@microsoft.com”> Date: Fri, 2 Oct 2020 21:16:06 -0400 Subject: [PATCH 48/56] Feedback updates --- .../azure/terraform-azure-example/main.tf | 4 +- .../azure/terraform-azure-vm-example/main.tf | 1 + .../terraform-azure-vm-example/outputs.tf | 24 ++ modules/azure/compute.go | 101 ++----- modules/azure/compute_test.go | 40 +-- modules/azure/disk.go | 16 +- modules/random/random.go | 2 +- test/azure/terraform_azure_example_test.go | 2 +- test/azure/terraform_azure_vm_example_test.go | 261 ++++++++++-------- 9 files changed, 205 insertions(+), 246 deletions(-) diff --git a/examples/azure/terraform-azure-example/main.tf b/examples/azure/terraform-azure-example/main.tf index 18da47440..d3c4af5db 100644 --- a/examples/azure/terraform-azure-example/main.tf +++ b/examples/azure/terraform-azure-example/main.tf @@ -1,6 +1,6 @@ # --------------------------------------------------------------------------------------------------------------------- -# DEPLOY A SIMPLE AZURE VIRTUAL MACHINE -# This is a simple example of how to deploy an Azure Virtual Machine with the minimum network resources. +# DEPLOY AN AZURE VIRTUAL MACHINE +# This is an example of how to deploy an Azure Virtual Machine with the minimum network resources. # --------------------------------------------------------------------------------------------------------------------- # See test/azure/terraform_azure_example_test.go for how to write automated tests for this code. # --------------------------------------------------------------------------------------------------------------------- diff --git a/examples/azure/terraform-azure-vm-example/main.tf b/examples/azure/terraform-azure-vm-example/main.tf index 9a60d9932..b191edd0c 100644 --- a/examples/azure/terraform-azure-vm-example/main.tf +++ b/examples/azure/terraform-azure-vm-example/main.tf @@ -61,6 +61,7 @@ resource "azurerm_public_ip" "pip" { idle_timeout_in_minutes = "4" } +# Public and Private IPs assigned to one NIC for test demonstration purposes resource "azurerm_network_interface" "nic" { name = "nic-${var.postfix}" location = azurerm_resource_group.vm_rg.location diff --git a/examples/azure/terraform-azure-vm-example/outputs.tf b/examples/azure/terraform-azure-vm-example/outputs.tf index 06cfea8f0..4ac75c9b4 100644 --- a/examples/azure/terraform-azure-vm-example/outputs.tf +++ b/examples/azure/terraform-azure-vm-example/outputs.tf @@ -7,6 +7,10 @@ output "managed_disk_name" { value = azurerm_managed_disk.disk.name } +output "managed_disk_type" { + value = azurerm_managed_disk.disk.storage_account_type +} + output "network_interface_name" { value = azurerm_network_interface.nic.name } @@ -15,6 +19,10 @@ output "os_disk_name" { value = azurerm_virtual_machine.vm_example.storage_os_disk[0].name } +output "private_ip" { + value = azurerm_network_interface.nic.ip_configuration[0].private_ip_address +} + output "public_ip_name" { value = azurerm_public_ip.pip.name } @@ -31,6 +39,22 @@ output "virtual_network_name" { value = azurerm_virtual_network.vnet.name } +output "vm_admin_username" { + value = azurerm_virtual_machine.vm_example.os_profile[*].admin_username +} + +output "vm_image_sku" { + value = azurerm_virtual_machine.vm_example.storage_image_reference[*].sku +} + +output "vm_image_version" { + value = azurerm_virtual_machine.vm_example.storage_image_reference[*].version +} + output "vm_name" { value = azurerm_virtual_machine.vm_example.name } + +output "vm_size" { + value = azurerm_virtual_machine.vm_example.vm_size +} diff --git a/modules/azure/compute.go b/modules/azure/compute.go index 5e8bc9f42..8aeed9bb4 100644 --- a/modules/azure/compute.go +++ b/modules/azure/compute.go @@ -62,26 +62,6 @@ func GetVirtualMachineNicsE(vmName string, resGroupName string, subscriptionID s return nics, nil } -// GetVirtualMachineNicCount gets the Network Interface count of the specified Azure Virtual Machine. -// This function would fail the test if there is an error. -func GetVirtualMachineNicCount(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) int { - nicCount, err := GetVirtualMachineNicCountE(vmName, resGroupName, subscriptionID) - require.NoError(t, err) - - return nicCount -} - -// GetVirtualMachineNicCountE gets the Network Interface count of the specified Azure Virtual Machine. -func GetVirtualMachineNicCountE(vmName string, resGroupName string, subscriptionID string) (int, error) { - // Get VM Object - vm, err := GetVirtualMachineE(vmName, resGroupName, subscriptionID) - if err != nil { - return -1, err - } - - return len(*vm.NetworkProfile.NetworkInterfaces), nil -} - // GetVirtualMachineManagedDisks gets the list of Managed Disk names of the specified Azure Virtual Machine. // This function would fail the test if there is an error. func GetVirtualMachineManagedDisks(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) []string { @@ -110,44 +90,24 @@ func GetVirtualMachineManagedDisksE(vmName string, resGroupName string, subscrip // Get the Names of the attached Managed Disks diskNames := make([]string, len(vmDisks)) - for _, v := range vmDisks { - diskNames = append(diskNames, *v.Name) + for i, v := range vmDisks { + diskNames[i] = *v.Name } return diskNames, nil } -// GetVirtualMachineManagedDiskCount gets the Managed Disk count of the specified Azure Virtual Machine. +// GetVirtualMachineOSDiskName gets the OS Disk name of the specified Azure Virtual Machine. // This function would fail the test if there is an error. -func GetVirtualMachineManagedDiskCount(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) int { - mngDiskCount, err := GetVirtualMachineManagedDiskCountE(vmName, resGroupName, subscriptionID) - require.NoError(t, err) - - return mngDiskCount -} - -// GetVirtualMachineManagedDiskCountE gets the Managed Disk count of the specified Azure Virtual Machine. -func GetVirtualMachineManagedDiskCountE(vmName string, resGroupName string, subscriptionID string) (int, error) { - // Get VM Object - vm, err := GetVirtualMachineE(vmName, resGroupName, subscriptionID) - if err != nil { - return -1, err - } - - return len(*vm.StorageProfile.DataDisks), nil -} - -// GetVirtualMachineOsDiskName gets the OS Disk name of the specified Azure Virtual Machine. -// This function would fail the test if there is an error. -func GetVirtualMachineOsDiskName(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) string { - osDiskName, err := GetVirtualMachineOsDiskNameE(vmName, resGroupName, subscriptionID) +func GetVirtualMachineOSDiskName(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) string { + osDiskName, err := GetVirtualMachineOSDiskNameE(vmName, resGroupName, subscriptionID) require.NoError(t, err) return osDiskName } -// GetVirtualMachineOsDiskNameE gets the OS Disk name of the specified Azure Virtual Machine. -func GetVirtualMachineOsDiskNameE(vmName string, resGroupName string, subscriptionID string) (string, error) { +// GetVirtualMachineOSDiskNameE gets the OS Disk name of the specified Azure Virtual Machine. +func GetVirtualMachineOSDiskNameE(vmName string, resGroupName string, subscriptionID string) (string, error) { // Get VM Object vm, err := GetVirtualMachineE(vmName, resGroupName, subscriptionID) if err != nil { @@ -157,25 +117,6 @@ func GetVirtualMachineOsDiskNameE(vmName string, resGroupName string, subscripti return *vm.StorageProfile.OsDisk.Name, nil } -// GetVirtualMachineState gets the State of the specified Azure Virtual Machine. -// This function would fail the test if there is an error. -func GetVirtualMachineState(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) string { - vmState, err := GetVirtualMachineStateE(vmName, resGroupName, subscriptionID) - require.NoError(t, err) - return vmState -} - -// GetVirtualMachineStateE gets the State of the specified Azure Virtual Machine. -func GetVirtualMachineStateE(vmName string, resGroupName string, subscriptionID string) (string, error) { - // Get VM Object - vm, err := GetVirtualMachineE(vmName, resGroupName, subscriptionID) - if err != nil { - return "", err - } - - return vm.Status, nil -} - // GetVirtualMachineAvailabilitySetID gets the Availability Set ID of the specified Azure Virtual Machine. // This function would fail the test if there is an error. func GetVirtualMachineAvailabilitySetID(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) string { @@ -260,17 +201,17 @@ func GetVirtualMachineAdminUserE(vmName string, resGroupName string, subscriptio return string(*vm.OsProfile.AdminUsername), nil } -// GetVirtualMachineSize gets the Size Type of the specified Azure Virtual Machine. +// GetSizeOfVirtualMachine gets the Size Type of the specified Azure Virtual Machine. // This function would fail the test if there is an error. -func GetVirtualMachineSize(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) compute.VirtualMachineSizeTypes { - size, err := GetVirtualMachineSizeE(vmName, resGroupName, subscriptionID) +func GetSizeOfVirtualMachine(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) compute.VirtualMachineSizeTypes { + size, err := GetSizeOfVirtualMachineE(vmName, resGroupName, subscriptionID) require.NoError(t, err) return size } -// GetVirtualMachineSizeE gets the Size Type of the specified Azure Virtual Machine. -func GetVirtualMachineSizeE(vmName string, resGroupName string, subscriptionID string) (compute.VirtualMachineSizeTypes, error) { +// GetSizeOfVirtualMachineE gets the Size Type of the specified Azure Virtual Machine. +func GetSizeOfVirtualMachineE(vmName string, resGroupName string, subscriptionID string) (compute.VirtualMachineSizeTypes, error) { // Get VM Object vm, err := GetVirtualMachineE(vmName, resGroupName, subscriptionID) if err != nil { @@ -340,16 +281,18 @@ func ListVirtualMachinesForResourceGroupE(resourceGroupName string, subscription return vmDetails, nil } -// GetVirtualMachinesForResourceGroup gets all Virtual Machine objects in the specified Resource Group. +// GetVirtualMachinesForResourceGroup gets all Virtual Machine objects in the specified Resource Group. Each +// VM Object represents the entire set of VM compute properties accessible by using the VM name as the map key. // This function would fail the test if there is an error. -func GetVirtualMachinesForResourceGroup(t testing.TestingT, resGroupName string, subscriptionID string) *map[string]compute.VirtualMachineProperties { +func GetVirtualMachinesForResourceGroup(t testing.TestingT, resGroupName string, subscriptionID string) map[string]compute.VirtualMachineProperties { vms, err := GetVirtualMachinesForResourceGroupE(resGroupName, subscriptionID) require.NoError(t, err) return vms } -// GetVirtualMachinesForResourceGroupE gets all Virtual Machine objects in the specified Resource Group. -func GetVirtualMachinesForResourceGroupE(resourceGroupName string, subscriptionID string) (*map[string]compute.VirtualMachineProperties, error) { +// GetVirtualMachinesForResourceGroupE gets all Virtual Machine objects in the specified Resource Group. Each +// VM Object represents the entire set of VM compute properties accessible by using the VM name as the map key. +func GetVirtualMachinesForResourceGroupE(resourceGroupName string, subscriptionID string) (map[string]compute.VirtualMachineProperties, error) { vmClient, err := GetVirtualMachineClientE(subscriptionID) if err != nil { return nil, err @@ -360,13 +303,11 @@ func GetVirtualMachinesForResourceGroupE(resourceGroupName string, subscriptionI return nil, err } - vmDetails := make(map[string]compute.VirtualMachineProperties) + vmDetails := make(map[string]compute.VirtualMachineProperties, len(vms.Values())) for _, v := range vms.Values() { - machineName := v.Name - vmProperties := v.VirtualMachineProperties - vmDetails[string(*machineName)] = *vmProperties + vmDetails[string(*v.Name)] = *v.VirtualMachineProperties } - return &vmDetails, nil + return vmDetails, nil } // ******************************************************************** // diff --git a/modules/azure/compute_test.go b/modules/azure/compute_test.go index 4476a044a..05dec4aa4 100644 --- a/modules/azure/compute_test.go +++ b/modules/azure/compute_test.go @@ -123,38 +123,14 @@ func TestGetVirtualMachineAvailabilitySetIDE(t *testing.T) { require.Error(t, err) } -func TestGetVirtualMachineStateE(t *testing.T) { +func TestGetVirtualMachineOSDiskNameE(t *testing.T) { t.Parallel() vmName := "" rgName := "" subID := "" - _, err := GetVirtualMachineStateE(vmName, rgName, subID) - - require.Error(t, err) -} - -func TestGetVirtualMachineOsDiskNameE(t *testing.T) { - t.Parallel() - - vmName := "" - rgName := "" - subID := "" - - _, err := GetVirtualMachineOsDiskNameE(vmName, rgName, subID) - - require.Error(t, err) -} - -func TestGetVirtualMachineManagedDiskCountE(t *testing.T) { - t.Parallel() - - vmName := "" - rgName := "" - subID := "" - - _, err := GetVirtualMachineManagedDiskCountE(vmName, rgName, subID) + _, err := GetVirtualMachineOSDiskNameE(vmName, rgName, subID) require.Error(t, err) } @@ -171,18 +147,6 @@ func TestGetVirtualMachineManagedDisksE(t *testing.T) { require.Error(t, err) } -func TestGetVirtualMachineNicCountE(t *testing.T) { - t.Parallel() - - vmName := "" - rgName := "" - subID := "" - - _, err := GetVirtualMachineNicCountE(vmName, rgName, subID) - - require.Error(t, err) -} - func TestGetVirtualMachineNicsE(t *testing.T) { t.Parallel() diff --git a/modules/azure/disk.go b/modules/azure/disk.go index d022fca01..80b1c817f 100644 --- a/modules/azure/disk.go +++ b/modules/azure/disk.go @@ -16,7 +16,7 @@ func DiskExists(t testing.TestingT, diskName string, resGroupName string, subscr return exists } -// DiskExistsE indicates whether the specified Azure Managed Disk exists +// DiskExistsE indicates whether the specified Azure Managed Disk exists. func DiskExistsE(t testing.TestingT, diskName string, resGroupName string, subscriptionID string) (bool, error) { // Get the Disk object _, err := GetDiskE(t, diskName, resGroupName, subscriptionID) @@ -29,8 +29,8 @@ func DiskExistsE(t testing.TestingT, diskName string, resGroupName string, subsc return true, nil } -// GetDiskType returns the Disk Storage Account Type of the Azure Managed Disk -// This property also accessible from the VM client disk storage object but only works +// GetDiskType returns the Disk Storage Account Type of the Azure Managed Disk. +// This property is also accessible from the VM client disk storage object but only works // when the VM is online, while this direct call to GetDiskType always works. // This function would fail the test if there is an error. func GetDiskType(t testing.TestingT, diskName string, resGroupName string, subscriptionID string) compute.DiskStorageAccountTypes { @@ -39,7 +39,9 @@ func GetDiskType(t testing.TestingT, diskName string, resGroupName string, subsc return diskType } -// GetDiskTypeE returns the Disk Storage Account Type of the Azure Managed Disk +// GetDiskTypeE returns the Disk Storage Account Type of the Azure Managed Disk. +// This property is also accessible from the VM client disk storage object but only works +// when the VM is online, while this direct call to GetDiskType always works. func GetDiskTypeE(t testing.TestingT, diskName string, resGroupName string, subscriptionID string) (compute.DiskStorageAccountTypes, error) { // Get the Disk object disk, err := GetDiskE(t, diskName, resGroupName, subscriptionID) @@ -50,7 +52,7 @@ func GetDiskTypeE(t testing.TestingT, diskName string, resGroupName string, subs return disk.Sku.Name, nil } -// GetDisk returns a Disk in the specified Azure Resource Group +// GetDisk returns a Disk in the specified Azure Resource Group. // This function would fail the test if there is an error. func GetDisk(t testing.TestingT, diskName string, resGroupName string, subscriptionID string) *compute.Disk { disk, err := GetDiskE(t, diskName, resGroupName, subscriptionID) @@ -58,7 +60,7 @@ func GetDisk(t testing.TestingT, diskName string, resGroupName string, subscript return disk } -// GetDiskE returns a Disk in the specified Azure Resource Group +// GetDiskE returns a Disk in the specified Azure Resource Group. func GetDiskE(t testing.TestingT, diskName string, resGroupName string, subscriptionID string) (*compute.Disk, error) { // Validate resource group name and subscription ID resGroupName, err := getTargetAzureResourceGroupName(resGroupName) @@ -81,7 +83,7 @@ func GetDiskE(t testing.TestingT, diskName string, resGroupName string, subscrip return &disk, nil } -// GetDiskClientE returns a new Disk client in the specified Azure Subscription +// GetDiskClientE returns a new Disk client in the specified Azure Subscription. func GetDiskClientE(subscriptionID string) (*compute.DisksClient, error) { // Validate Azure subscription ID subscriptionID, err := getTargetAzureSubscription(subscriptionID) diff --git a/modules/random/random.go b/modules/random/random.go index 01c855476..1db77b923 100644 --- a/modules/random/random.go +++ b/modules/random/random.go @@ -24,7 +24,7 @@ func RandomString(elements []string) string { return elements[index] } -const base62chars = "0123456789012345678901234567890123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +const base62chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" const uniqueIDLength = 6 // Should be good for 62^6 = 56+ billion combinations // UniqueId returns a unique (ish) id we can attach to resources and tfstate files so they don't conflict with each other diff --git a/test/azure/terraform_azure_example_test.go b/test/azure/terraform_azure_example_test.go index 79b3e6b69..4609cdc84 100644 --- a/test/azure/terraform_azure_example_test.go +++ b/test/azure/terraform_azure_example_test.go @@ -40,7 +40,7 @@ func TestTerraformAzureExample(t *testing.T) { resourceGroupName := terraform.Output(t, terraformOptions, "resource_group_name") // website::tag::4:: Look up the size of the given Virtual Machine and ensure it matches the output. - actualVMSize := azure.GetVirtualMachineSize(t, vmName, resourceGroupName, "") + actualVMSize := azure.GetSizeOfVirtualMachine(t, vmName, resourceGroupName, "") expectedVMSize := compute.VirtualMachineSizeTypes("Standard_B1s") assert.Equal(t, expectedVMSize, actualVMSize) } diff --git a/test/azure/terraform_azure_vm_example_test.go b/test/azure/terraform_azure_vm_example_test.go index de7394d97..1bb17c16d 100644 --- a/test/azure/terraform_azure_vm_example_test.go +++ b/test/azure/terraform_azure_vm_example_test.go @@ -20,18 +20,8 @@ import ( func TestTerraformAzureVmExample(t *testing.T) { t.Parallel() - // subscriptionID is overridden by the environment variable "ARM_SUBSCRIPTION_ID" subscriptionID := "" - uniquePostfix := random.UniqueId() // "resource" // - expectedVmAdminUser := "testadmin" - expectedVMSize := compute.VirtualMachineSizeTypes("Standard_B1s") - expectedImageSKU := "2019-Datacenter-Core-smalldisk" - expectedImageVersion := "latest" - expectedDiskType := "Standard_LRS" - expectedSubnetAddressRange := "10.0.17.0/24" - expectedPrivateIPAddress := "10.0.17.4" - expectedManagedDiskCount := 1 - expectedNicCount := 1 + uniquePostfix := random.UniqueId() // Configure Terraform setting up a path to Terraform code. terraformOptions := &terraform.Options{ @@ -40,14 +30,7 @@ func TestTerraformAzureVmExample(t *testing.T) { // Variables to pass to our Terraform code using -var options. Vars: map[string]interface{}{ - "postfix": uniquePostfix, - "user_name": expectedVmAdminUser, - "vm_size": string(expectedVMSize), - "vm_image_sku": expectedImageSKU, - "vm_image_version": expectedImageVersion, - "disk_type": expectedDiskType, - "private_ip": expectedPrivateIPAddress, - "subnet_prefix": expectedSubnetAddressRange, + "postfix": uniquePostfix, }, } @@ -57,119 +40,173 @@ func TestTerraformAzureVmExample(t *testing.T) { // Run `terraform init` and `terraform apply`. Fail the test if there are any errors. terraform.InitAndApply(t, terraformOptions) + // Run tests for the Virtual Machine. + testStrategiesForVMs(t, terraformOptions, subscriptionID) + testMultipleVMs(t, terraformOptions, subscriptionID) + testInformationOfVM(t, terraformOptions, subscriptionID) + testDisksOfVM(t, terraformOptions, subscriptionID) + testNetworkOfVM(t, terraformOptions, subscriptionID) +} + +func testStrategiesForVMs(t *testing.T, terraformOptions *terraform.Options, subscriptionID string) { + // These 3 tests check for the same property but illustrate different testing strategies for + // retriving this data. The first strategy is used in the other tests of this module while + // the other two can be extended by the user as needed. + // Run `terraform output` to get the values of output variables. resourceGroupName := terraform.Output(t, terraformOptions, "resource_group_name") virtualMachineName := terraform.Output(t, terraformOptions, "vm_name") - expectedVNetName := terraform.Output(t, terraformOptions, "virtual_network_name") - expectedSubnetName := terraform.Output(t, terraformOptions, "subnet_name") - expectedPublicAddressName := terraform.Output(t, terraformOptions, "public_ip_name") - expectedNicName := terraform.Output(t, terraformOptions, "network_interface_name") + expectedVMSize := compute.VirtualMachineSizeTypes(terraform.Output(t, terraformOptions, "vm_size")) + + // 1. Check the VM Size directly. + // This strategy gets one specific property of the VM per method. + actualVMSize := azure.GetSizeOfVirtualMachine(t, virtualMachineName, resourceGroupName, subscriptionID) + assert.Equal(t, expectedVMSize, actualVMSize) + + // 2. Check the VM size by reference. + // This strategy is beneficial when checking multiple properties by using one VM reference, avoiding + // multiple SDK calls. + vmByRef := azure.GetVirtualMachine(t, virtualMachineName, resourceGroupName, subscriptionID) + actualVMSize = vmByRef.HardwareProfile.VMSize + assert.Equal(t, expectedVMSize, actualVMSize) + + // 3. Check the VM size by instance. + // This strategy is beneficial when checking multiple properties by using one VM instance and making + // calls against it with the added benefit of property checks and abstraction. + vmInstance := azure.GetVirtualMachineInstance(t, virtualMachineName, resourceGroupName, subscriptionID) + actualVMSize = vmInstance.GetVirtualMachineInstanceSize() + assert.Equal(t, expectedVMSize, actualVMSize) +} + +func testMultipleVMs(t *testing.T, terraformOptions *terraform.Options, subscriptionID string) { + // These tests check for the multiple Virtual Machines in a Resource Group. + + // Run `terraform output` to get the values of output variables. + resourceGroupName := terraform.Output(t, terraformOptions, "resource_group_name") + expectedVMName := terraform.Output(t, terraformOptions, "vm_name") + expectedVMSize := compute.VirtualMachineSizeTypes(terraform.Output(t, terraformOptions, "vm_size")) expectedAvsName := terraform.Output(t, terraformOptions, "availability_set_name") - expectedOSDiskName := terraform.Output(t, terraformOptions, "os_disk_name") - expectedDiskName := terraform.Output(t, terraformOptions, "managed_disk_name") - // Comment for PR, to be removed: - // Please let me know if there are too many tests or alternate examples, happy to reduce the - // complexity and amount of code to be maintained. I tried to illustrate different approaches - // we have used in various scenarios to illustrate the flexability of Terratest. - - t.Run("Strategies", func(t *testing.T) { - // Check the VM Size directly. - actualVMSize := azure.GetVirtualMachineSize(t, virtualMachineName, resourceGroupName, subscriptionID) - assert.Equal(t, expectedVMSize, actualVMSize) - - // An alternate example for checking the VM size by reference. - // This strategy is beneficial when checking multiple properties by using one VM reference, avoiding - // multiple SDK calls. - vmRef := azure.GetVirtualMachine(t, virtualMachineName, resourceGroupName, subscriptionID) - actualVMSize = vmRef.HardwareProfile.VMSize - assert.Equal(t, expectedVMSize, actualVMSize) - - // Check the VM Size by instance alternate example. - // This strategy is beneficial when checking multiple properties by using one VM instance and making - // calls against it with the added benefit of property checks and abstraction. - vmInstance := azure.GetVirtualMachineInstance(t, virtualMachineName, resourceGroupName, subscriptionID) - actualVMSize = vmInstance.GetVirtualMachineInstanceSize() - assert.Equal(t, expectedVMSize, actualVMSize) - }) + // Check against all VM names in a Resource Group. + vmList := azure.ListVirtualMachinesForResourceGroup(t, resourceGroupName, subscriptionID) + expectedVMCount := 1 + assert.Equal(t, expectedVMCount, len(vmList)) + assert.Contains(t, vmList, expectedVMName) + + // Check Availability Set for multiple VMs. + actualVMsInAvs := azure.GetAvailabilitySetVMNamesInCaps(t, expectedAvsName, resourceGroupName, subscriptionID) + assert.Contains(t, actualVMsInAvs, strings.ToUpper(expectedVMName)) + + // Get all VMs in a Resource Group, including their properties, therefore avoiding + // multiple SDK calls. The penalty for this approach is introducing direct references + // which need to be checked for nil for optional configurations. + vmsByRef := azure.GetVirtualMachinesForResourceGroup(t, resourceGroupName, subscriptionID) + thisVM := (vmsByRef)[expectedVMName] + assert.Equal(t, expectedVMSize, thisVM.HardwareProfile.VMSize) + + // Check for the VM negative test. + fakeVM := fmt.Sprintf("vm-%s", random.UniqueId()) + assert.Nil(t, (vmsByRef)[fakeVM].VMID) +} - t.Run("MultipleVMs", func(t *testing.T) { - // This is beneficial when multiple VMs in a Resource Group need to be tested at once. +func testInformationOfVM(t *testing.T, terraformOptions *terraform.Options, subscriptionID string) { + // These tests check information directly related to the Virtual Machine resource. - // Check against all VM names in a Resource Group. - vmList := azure.ListVirtualMachinesForResourceGroup(t, resourceGroupName, subscriptionID) - assert.True(t, len(vmList) > 0) - assert.Contains(t, vmList, virtualMachineName) + // Run `terraform output` to get the values of output variables. + resourceGroupName := terraform.Output(t, terraformOptions, "resource_group_name") + virtualMachineName := terraform.Output(t, terraformOptions, "vm_name") + expectedAvsName := terraform.Output(t, terraformOptions, "availability_set_name") - // Get all VMs in a Resource Group by reference alternate example. - // This strategy is beneficial when checking multiple VMs & their properties by avoiding - // multiple SDK calls. The penalty for this approach is introducing direct references - // which need to be checked for nil for optional configurations. - vmsByRef := azure.GetVirtualMachinesForResourceGroup(t, resourceGroupName, subscriptionID) - assert.Greater(t, len(*vmsByRef) > 0) + // Check if the Virtual Machine exists. + assert.True(t, azure.VirtualMachineExists(t, virtualMachineName, resourceGroupName, subscriptionID)) - // Check for the VM. - thisVM := (*vmsByRef)[virtualMachineName] - assert.Equal(t, expectedVMSize, thisVM.HardwareProfile.VMSize) + // Check the Admin User of the VM. + actualVmAdminUser := azure.GetVirtualMachineAdminUser(t, virtualMachineName, resourceGroupName, subscriptionID) + expectedVmAdminUser := "testadmin" + assert.Equal(t, expectedVmAdminUser, actualVmAdminUser) - // Check for the VM negative test. - fakeVM := fmt.Sprintf("vm-%s", random.UniqueId()) - assert.Nil(t, (*vmsByRef)[fakeVM].VMID) - }) + // Check the Storage Image properties of the VM. + actualImage := azure.GetVirtualMachineImage(t, virtualMachineName, resourceGroupName, subscriptionID) + expectedImageSKU := "2019-Datacenter-Core-smalldisk" + expectedImageVersion := "latest" + assert.Contains(t, expectedImageSKU, actualImage.SKU) + assert.Contains(t, expectedImageVersion, actualImage.Version) - t.Run("Information", func(t *testing.T) { - // Check if the Virtual Machine exists. - assert.True(t, azure.VirtualMachineExists(t, virtualMachineName, resourceGroupName, subscriptionID)) + // Check the Availability Set of the VM. + // The AVS ID returned from the VM is always CAPS so ignoring case in the assertion. + actualexpectedAvsName := azure.GetVirtualMachineAvailabilitySetID(t, virtualMachineName, resourceGroupName, subscriptionID) + assert.True(t, strings.EqualFold(expectedAvsName, actualexpectedAvsName)) +} - // Check the Admin User of the VM. - actualVmAdminUser := azure.GetVirtualMachineAdminUser(t, virtualMachineName, resourceGroupName, subscriptionID) - assert.Equal(t, expectedVmAdminUser, actualVmAdminUser) +func testDisksOfVM(t *testing.T, terraformOptions *terraform.Options, subscriptionID string) { + // These tests check the OS Disk and Attached Managed Disks for the Virtual Machine. + // The following Azure modules are utilized: + // - compute + // - disk + // See the terraform_azure_disk_example_test.go for other related tests. - // Check the Storage Image properties of the VM. - actualImage := azure.GetVirtualMachineImage(t, virtualMachineName, resourceGroupName, subscriptionID) - assert.Equal(t, expectedImageSKU, actualImage.SKU) - assert.Equal(t, expectedImageVersion, actualImage.Version) - }) + // Run `terraform output` to get the values of output variables. + resourceGroupName := terraform.Output(t, terraformOptions, "resource_group_name") + virtualMachineName := terraform.Output(t, terraformOptions, "vm_name") + expectedOSDiskName := terraform.Output(t, terraformOptions, "os_disk_name") + expectedDiskName := terraform.Output(t, terraformOptions, "managed_disk_name") + expectedDiskType := terraform.Output(t, terraformOptions, "managed_disk_type") - t.Run("AvailabilitySet", func(t *testing.T) { - // Check the Availability Set of the VM. - // The AVS ID returned from the VM is always CAPS so ignoring case in the assertion. - actualexpectedAvsName := azure.GetVirtualMachineAvailabilitySetID(t, virtualMachineName, resourceGroupName, subscriptionID) - assert.True(t, strings.EqualFold(expectedAvsName, actualexpectedAvsName)) + // Check the OS Disk name of the VM. + actualOSDiskName := azure.GetVirtualMachineOSDiskName(t, virtualMachineName, resourceGroupName, subscriptionID) + assert.Equal(t, expectedOSDiskName, actualOSDiskName) - // Check AVS for multiple VMs at a time alternate example. - actualVMsInAvs := azure.GetAvailabilitySetVMNamesInCaps(t, expectedAvsName, resourceGroupName, subscriptionID) - assert.Contains(t, actualVMsInAvs, strings.ToUpper(virtualMachineName)) - }) + // Check the VM Managed Disk exists in the list of all VM Managed Disks. + actualManagedDiskNames := azure.GetVirtualMachineManagedDisks(t, virtualMachineName, resourceGroupName, subscriptionID) + assert.Contains(t, actualManagedDiskNames, expectedDiskName) - t.Run("Disk", func(t *testing.T) { - // Check the OS Disk name of the VM. - actualOSDiskName := azure.GetVirtualMachineOsDiskName(t, virtualMachineName, resourceGroupName, subscriptionID) - assert.Equal(t, expectedOSDiskName, actualOSDiskName) + // Check the Managed Disk count of the VM. + expectedManagedDiskCount := 1 + assert.Equal(t, expectedManagedDiskCount, len(actualManagedDiskNames)) - // Check the Managed Disk count of the VM. - actualManagedDiskCount := azure.GetVirtualMachineManagedDiskCount(t, virtualMachineName, resourceGroupName, subscriptionID) - assert.Equal(t, expectedManagedDiskCount, actualManagedDiskCount) + // Check the Disk Type of the Managed Disk of the VM. + actualDiskType := azure.GetDiskType(t, expectedDiskName, resourceGroupName, subscriptionID) + assert.Equal(t, compute.DiskStorageAccountTypes(expectedDiskType), actualDiskType) +} - // Check the VM Managed Disk exists in the list of all VM Managed Disks. - actualManagedDiskNames := azure.GetVirtualMachineManagedDisks(t, virtualMachineName, resourceGroupName, subscriptionID) - assert.Contains(t, actualManagedDiskNames, expectedDiskName) +func testNetworkOfVM(t *testing.T, terraformOptions *terraform.Options, subscriptionID string) { + // These tests check the underlying virtual network and subnet as well as the network + // interface and associated public IP address resources attached to the VM. + // The following Azure modules are utilized: + // - compute + // - networkinterface + // - publicaddress + // - virtualnetwork + // See the terraform_azure_network_example_test.go for other related tests. - // Check the Disk Type of the Managed Disk of the VM. - actualDiskType := azure.GetDiskType(t, expectedDiskName, resourceGroupName, subscriptionID) - assert.Equal(t, compute.DiskStorageAccountTypes(expectedDiskType), actualDiskType) - }) + // Run `terraform output` to get the values of output variables. + resourceGroupName := terraform.Output(t, terraformOptions, "resource_group_name") + virtualMachineName := terraform.Output(t, terraformOptions, "vm_name") + expectedVNetName := terraform.Output(t, terraformOptions, "virtual_network_name") + expectedSubnetName := terraform.Output(t, terraformOptions, "subnet_name") + expectedPublicAddressName := terraform.Output(t, terraformOptions, "public_ip_name") + expectedNicName := terraform.Output(t, terraformOptions, "network_interface_name") + expectedPrivateIPAddress := terraform.Output(t, terraformOptions, "private_ip") - // See the Terratest Azure Network Example for other related tests. - t.Run("NetworkInterface", func(t *testing.T) { - // Check the Network Interface count of the VM. - actualNicCount := azure.GetVirtualMachineNicCount(t, virtualMachineName, resourceGroupName, subscriptionID) - assert.Equal(t, expectedNicCount, actualNicCount) + t.Run("VirtualNetwork_Subnet", func(t *testing.T) { + // Check the Subnet exists in the Virtual Network. + actualVnetSubnets := azure.GetVirtualNetworkSubnets(t, expectedVNetName, resourceGroupName, subscriptionID) + assert.NotNil(t, actualVnetSubnets[expectedVNetName]) + // Check the Private IP is in the Subnet Range. + actualVMNicIPInSubnet := azure.CheckSubnetContainsIP(t, expectedPrivateIPAddress, expectedSubnetName, expectedVNetName, resourceGroupName, subscriptionID) + assert.True(t, actualVMNicIPInSubnet) + }) + + t.Run("NetworkInterfaceCard", func(t *testing.T) { // Check the VM Network Interface exists in the list of all VM Network Interfaces. actualNics := azure.GetVirtualMachineNics(t, virtualMachineName, resourceGroupName, subscriptionID) assert.Contains(t, actualNics, expectedNicName) + // Check the Network Interface count of the VM. + expectedNICCount := 1 + assert.Equal(t, expectedNICCount, len(actualNics)) + // Check for the Private IP in the NICs IP list. actualPrivateIPAddress := azure.GetNetworkInterfacePrivateIPs(t, expectedNicName, resourceGroupName, subscriptionID) assert.Contains(t, actualPrivateIPAddress, expectedPrivateIPAddress) @@ -178,14 +215,4 @@ func TestTerraformAzureVmExample(t *testing.T) { actualPublicIP := azure.GetIPOfPublicIPAddressByName(t, expectedPublicAddressName, resourceGroupName, subscriptionID) assert.NotNil(t, actualPublicIP) }) - - t.Run("Vnet&Subnet", func(t *testing.T) { - // Check the Subnet exists in the Virtual Network. - actualVnetSubnets := azure.GetVirtualNetworkSubnets(t, expectedVNetName, resourceGroupName, subscriptionID) - assert.NotNil(t, actualVnetSubnets[expectedVNetName]) - - // Check the Private IP is in the Subnet Range. - actualVMNicIPInSubnet := azure.CheckSubnetContainsIP(t, expectedPrivateIPAddress, expectedSubnetName, expectedVNetName, resourceGroupName, subscriptionID) - assert.True(t, actualVMNicIPInSubnet) - }) } From 344a4622325156ee79e069520faf9cb85242f18a Mon Sep 17 00:00:00 2001 From: Dave Seepersad <“davesee@microsoft.com”> Date: Sat, 3 Oct 2020 10:31:57 -0400 Subject: [PATCH 49/56] Unit test updated --- modules/azure/compute_test.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/azure/compute_test.go b/modules/azure/compute_test.go index 05dec4aa4..4fd2659d3 100644 --- a/modules/azure/compute_test.go +++ b/modules/azure/compute_test.go @@ -13,8 +13,7 @@ import ( /* The below tests are currently stubbed out, with the expectation that they will throw errors. -If/when CRUD methods are introduced for Azure Virtual Machines, these tests can be extended -(see AWS S3 tests for reference). +If/when CRUD methods are introduced for Azure Virtual Machines, these tests can be extended. */ func TestGetVirtualMachineE(t *testing.T) { @@ -75,14 +74,14 @@ func TestGetVirtualMachineTagsE(t *testing.T) { require.Error(t, err) } -func TestGetVirtualMachineSizeE(t *testing.T) { +func TestGetSizeOfVirtualMachineE(t *testing.T) { t.Parallel() vmName := "" rgName := "" subID := "" - _, err := GetVirtualMachineSizeE(vmName, rgName, subID) + _, err := GetSizeOfVirtualMachineE(vmName, rgName, subID) require.Error(t, err) } From 0df037bd0601dca868b74fe2ef2485396447c9ef Mon Sep 17 00:00:00 2001 From: Dave Seepersad <“davesee@microsoft.com”> Date: Sat, 3 Oct 2020 11:22:13 -0400 Subject: [PATCH 50/56] Added OutputList --- test/azure/terraform_azure_vm_example_test.go | 117 ++++++++---------- 1 file changed, 53 insertions(+), 64 deletions(-) diff --git a/test/azure/terraform_azure_vm_example_test.go b/test/azure/terraform_azure_vm_example_test.go index 1bb17c16d..655937700 100644 --- a/test/azure/terraform_azure_vm_example_test.go +++ b/test/azure/terraform_azure_vm_example_test.go @@ -48,39 +48,34 @@ func TestTerraformAzureVmExample(t *testing.T) { testNetworkOfVM(t, terraformOptions, subscriptionID) } +// These 3 tests check for the same property but illustrate different testing strategies for +// retriving the data. The first strategy is used in the other tests of this module while +// the other two can be extended by the user as needed. func testStrategiesForVMs(t *testing.T, terraformOptions *terraform.Options, subscriptionID string) { - // These 3 tests check for the same property but illustrate different testing strategies for - // retriving this data. The first strategy is used in the other tests of this module while - // the other two can be extended by the user as needed. - // Run `terraform output` to get the values of output variables. resourceGroupName := terraform.Output(t, terraformOptions, "resource_group_name") virtualMachineName := terraform.Output(t, terraformOptions, "vm_name") expectedVMSize := compute.VirtualMachineSizeTypes(terraform.Output(t, terraformOptions, "vm_size")) - // 1. Check the VM Size directly. - // This strategy gets one specific property of the VM per method. + // 1. Check the VM Size directly. This strategy gets one specific property of the VM per method. actualVMSize := azure.GetSizeOfVirtualMachine(t, virtualMachineName, resourceGroupName, subscriptionID) assert.Equal(t, expectedVMSize, actualVMSize) - // 2. Check the VM size by reference. - // This strategy is beneficial when checking multiple properties by using one VM reference, avoiding - // multiple SDK calls. + // 2. Check the VM size by reference. This strategy is beneficial when checking multiple properties + // by using one VM reference. Optional parameters have to be checked first to avoid nil panics. vmByRef := azure.GetVirtualMachine(t, virtualMachineName, resourceGroupName, subscriptionID) actualVMSize = vmByRef.HardwareProfile.VMSize assert.Equal(t, expectedVMSize, actualVMSize) - // 3. Check the VM size by instance. - // This strategy is beneficial when checking multiple properties by using one VM instance and making - // calls against it with the added benefit of property checks and abstraction. + // 3. Check the VM size by instance. This strategy is beneficial when checking multiple properties + // by using one VM instance and making calls against it with the added benefit of property check abstraction. vmInstance := azure.GetVirtualMachineInstance(t, virtualMachineName, resourceGroupName, subscriptionID) actualVMSize = vmInstance.GetVirtualMachineInstanceSize() assert.Equal(t, expectedVMSize, actualVMSize) } +// These tests check for the multiple Virtual Machines in a Resource Group. func testMultipleVMs(t *testing.T, terraformOptions *terraform.Options, subscriptionID string) { - // These tests check for the multiple Virtual Machines in a Resource Group. - // Run `terraform output` to get the values of output variables. resourceGroupName := terraform.Output(t, terraformOptions, "resource_group_name") expectedVMName := terraform.Output(t, terraformOptions, "vm_name") @@ -109,12 +104,14 @@ func testMultipleVMs(t *testing.T, terraformOptions *terraform.Options, subscrip assert.Nil(t, (vmsByRef)[fakeVM].VMID) } +// These tests check information directly related to the specified Azure Virtual Machine. func testInformationOfVM(t *testing.T, terraformOptions *terraform.Options, subscriptionID string) { - // These tests check information directly related to the Virtual Machine resource. - // Run `terraform output` to get the values of output variables. resourceGroupName := terraform.Output(t, terraformOptions, "resource_group_name") virtualMachineName := terraform.Output(t, terraformOptions, "vm_name") + expectedVmAdminUser := terraform.OutputList(t, terraformOptions, "vm_admin_username") + expectedImageSKU := terraform.OutputList(t, terraformOptions, "vm_image_sku") + expectedImageVersion := terraform.OutputList(t, terraformOptions, "vm_image_version") expectedAvsName := terraform.Output(t, terraformOptions, "availability_set_name") // Check if the Virtual Machine exists. @@ -122,15 +119,12 @@ func testInformationOfVM(t *testing.T, terraformOptions *terraform.Options, subs // Check the Admin User of the VM. actualVmAdminUser := azure.GetVirtualMachineAdminUser(t, virtualMachineName, resourceGroupName, subscriptionID) - expectedVmAdminUser := "testadmin" - assert.Equal(t, expectedVmAdminUser, actualVmAdminUser) + assert.Equal(t, expectedVmAdminUser[0], actualVmAdminUser) // Check the Storage Image properties of the VM. actualImage := azure.GetVirtualMachineImage(t, virtualMachineName, resourceGroupName, subscriptionID) - expectedImageSKU := "2019-Datacenter-Core-smalldisk" - expectedImageVersion := "latest" - assert.Contains(t, expectedImageSKU, actualImage.SKU) - assert.Contains(t, expectedImageVersion, actualImage.Version) + assert.Contains(t, expectedImageSKU[0], actualImage.SKU) + assert.Contains(t, expectedImageVersion[0], actualImage.Version) // Check the Availability Set of the VM. // The AVS ID returned from the VM is always CAPS so ignoring case in the assertion. @@ -138,13 +132,11 @@ func testInformationOfVM(t *testing.T, terraformOptions *terraform.Options, subs assert.True(t, strings.EqualFold(expectedAvsName, actualexpectedAvsName)) } +// These tests check the OS Disk and Attached Managed Disks for the Azure Virtual Machine. +// The following Terratest Azure module is utilized in addition to the compute module: +// - disk +// See the terraform_azure_disk_example_test.go for other related tests. func testDisksOfVM(t *testing.T, terraformOptions *terraform.Options, subscriptionID string) { - // These tests check the OS Disk and Attached Managed Disks for the Virtual Machine. - // The following Azure modules are utilized: - // - compute - // - disk - // See the terraform_azure_disk_example_test.go for other related tests. - // Run `terraform output` to get the values of output variables. resourceGroupName := terraform.Output(t, terraformOptions, "resource_group_name") virtualMachineName := terraform.Output(t, terraformOptions, "vm_name") @@ -169,16 +161,13 @@ func testDisksOfVM(t *testing.T, terraformOptions *terraform.Options, subscripti assert.Equal(t, compute.DiskStorageAccountTypes(expectedDiskType), actualDiskType) } +// These tests check the underlying Virtual Network, Network Interface and associated Public IP Address. +// The following Terratest Azure modules are utilized in addition to the compute module: +// - networkinterface +// - publicaddress +// - virtualnetwork +// See the terraform_azure_network_example_test.go for other related tests. func testNetworkOfVM(t *testing.T, terraformOptions *terraform.Options, subscriptionID string) { - // These tests check the underlying virtual network and subnet as well as the network - // interface and associated public IP address resources attached to the VM. - // The following Azure modules are utilized: - // - compute - // - networkinterface - // - publicaddress - // - virtualnetwork - // See the terraform_azure_network_example_test.go for other related tests. - // Run `terraform output` to get the values of output variables. resourceGroupName := terraform.Output(t, terraformOptions, "resource_group_name") virtualMachineName := terraform.Output(t, terraformOptions, "vm_name") @@ -188,31 +177,31 @@ func testNetworkOfVM(t *testing.T, terraformOptions *terraform.Options, subscrip expectedNicName := terraform.Output(t, terraformOptions, "network_interface_name") expectedPrivateIPAddress := terraform.Output(t, terraformOptions, "private_ip") - t.Run("VirtualNetwork_Subnet", func(t *testing.T) { - // Check the Subnet exists in the Virtual Network. - actualVnetSubnets := azure.GetVirtualNetworkSubnets(t, expectedVNetName, resourceGroupName, subscriptionID) - assert.NotNil(t, actualVnetSubnets[expectedVNetName]) - - // Check the Private IP is in the Subnet Range. - actualVMNicIPInSubnet := azure.CheckSubnetContainsIP(t, expectedPrivateIPAddress, expectedSubnetName, expectedVNetName, resourceGroupName, subscriptionID) - assert.True(t, actualVMNicIPInSubnet) - }) - - t.Run("NetworkInterfaceCard", func(t *testing.T) { - // Check the VM Network Interface exists in the list of all VM Network Interfaces. - actualNics := azure.GetVirtualMachineNics(t, virtualMachineName, resourceGroupName, subscriptionID) - assert.Contains(t, actualNics, expectedNicName) - - // Check the Network Interface count of the VM. - expectedNICCount := 1 - assert.Equal(t, expectedNICCount, len(actualNics)) - - // Check for the Private IP in the NICs IP list. - actualPrivateIPAddress := azure.GetNetworkInterfacePrivateIPs(t, expectedNicName, resourceGroupName, subscriptionID) - assert.Contains(t, actualPrivateIPAddress, expectedPrivateIPAddress) - - // Check for the Public IP for the NIC. No expected value since it is assigned runtime. - actualPublicIP := azure.GetIPOfPublicIPAddressByName(t, expectedPublicAddressName, resourceGroupName, subscriptionID) - assert.NotNil(t, actualPublicIP) - }) + // VirtualNetwork and Subnet tests + // Check the Subnet exists in the Virtual Network. + actualVnetSubnets := azure.GetVirtualNetworkSubnets(t, expectedVNetName, resourceGroupName, subscriptionID) + assert.NotNil(t, actualVnetSubnets[expectedVNetName]) + + // Check the Private IP is in the Subnet Range. + actualVMNicIPInSubnet := azure.CheckSubnetContainsIP(t, expectedPrivateIPAddress, expectedSubnetName, expectedVNetName, resourceGroupName, subscriptionID) + assert.True(t, actualVMNicIPInSubnet) + + // Network Interface Card tests + // Check the VM Network Interface exists in the list of all VM Network Interfaces. + actualNics := azure.GetVirtualMachineNics(t, virtualMachineName, resourceGroupName, subscriptionID) + assert.Contains(t, actualNics, expectedNicName) + + // Check the Network Interface count of the VM. + expectedNICCount := 1 + assert.Equal(t, expectedNICCount, len(actualNics)) + + // Check for the Private IP in the NICs IP list. + actualPrivateIPAddress := azure.GetNetworkInterfacePrivateIPs(t, expectedNicName, resourceGroupName, subscriptionID) + assert.Contains(t, actualPrivateIPAddress, expectedPrivateIPAddress) + + // Public IP Address test + // Check for the Public IP for the NIC. No expected value since it is assigned runtime. + actualPublicIP := azure.GetIPOfPublicIPAddressByName(t, expectedPublicAddressName, resourceGroupName, subscriptionID) + assert.NotNil(t, actualPublicIP) + } From 3b5f0e3f528a935f4713a2354f3d99ba26133fa0 Mon Sep 17 00:00:00 2001 From: Dave Seepersad <“davesee@microsoft.com”> Date: Sat, 3 Oct 2020 12:32:11 -0400 Subject: [PATCH 51/56] Updated comments to match disk PR --- modules/azure/disk.go | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/modules/azure/disk.go b/modules/azure/disk.go index 80b1c817f..6cb1cc1c1 100644 --- a/modules/azure/disk.go +++ b/modules/azure/disk.go @@ -11,15 +11,15 @@ import ( // DiskExists indicates whether the specified Azure Managed Disk exists. // This function would fail the test if there is an error. func DiskExists(t testing.TestingT, diskName string, resGroupName string, subscriptionID string) bool { - exists, err := DiskExistsE(t, diskName, resGroupName, subscriptionID) + exists, err := DiskExistsE(diskName, resGroupName, subscriptionID) require.NoError(t, err) return exists } -// DiskExistsE indicates whether the specified Azure Managed Disk exists. -func DiskExistsE(t testing.TestingT, diskName string, resGroupName string, subscriptionID string) (bool, error) { +// DiskExistsE indicates whether the specified Azure Managed Disk exists +func DiskExistsE(diskName string, resGroupName string, subscriptionID string) (bool, error) { // Get the Disk object - _, err := GetDiskE(t, diskName, resGroupName, subscriptionID) + _, err := GetDiskE(diskName, resGroupName, subscriptionID) if err != nil { if ResourceNotFoundErrorExists(err) { return false, nil @@ -30,21 +30,17 @@ func DiskExistsE(t testing.TestingT, diskName string, resGroupName string, subsc } // GetDiskType returns the Disk Storage Account Type of the Azure Managed Disk. -// This property is also accessible from the VM client disk storage object but only works -// when the VM is online, while this direct call to GetDiskType always works. // This function would fail the test if there is an error. func GetDiskType(t testing.TestingT, diskName string, resGroupName string, subscriptionID string) compute.DiskStorageAccountTypes { - diskType, err := GetDiskTypeE(t, diskName, resGroupName, subscriptionID) + diskType, err := GetDiskTypeE(diskName, resGroupName, subscriptionID) require.NoError(t, err) return diskType } // GetDiskTypeE returns the Disk Storage Account Type of the Azure Managed Disk. -// This property is also accessible from the VM client disk storage object but only works -// when the VM is online, while this direct call to GetDiskType always works. -func GetDiskTypeE(t testing.TestingT, diskName string, resGroupName string, subscriptionID string) (compute.DiskStorageAccountTypes, error) { +func GetDiskTypeE(diskName string, resGroupName string, subscriptionID string) (compute.DiskStorageAccountTypes, error) { // Get the Disk object - disk, err := GetDiskE(t, diskName, resGroupName, subscriptionID) + disk, err := GetDiskE(diskName, resGroupName, subscriptionID) if err != nil { return "", err } @@ -55,13 +51,13 @@ func GetDiskTypeE(t testing.TestingT, diskName string, resGroupName string, subs // GetDisk returns a Disk in the specified Azure Resource Group. // This function would fail the test if there is an error. func GetDisk(t testing.TestingT, diskName string, resGroupName string, subscriptionID string) *compute.Disk { - disk, err := GetDiskE(t, diskName, resGroupName, subscriptionID) + disk, err := GetDiskE(diskName, resGroupName, subscriptionID) require.NoError(t, err) return disk } // GetDiskE returns a Disk in the specified Azure Resource Group. -func GetDiskE(t testing.TestingT, diskName string, resGroupName string, subscriptionID string) (*compute.Disk, error) { +func GetDiskE(diskName string, resGroupName string, subscriptionID string) (*compute.Disk, error) { // Validate resource group name and subscription ID resGroupName, err := getTargetAzureResourceGroupName(resGroupName) if err != nil { From 2115a7a42c6073c86ec948c985f9ae788abcfbef Mon Sep 17 00:00:00 2001 From: Dave Seepersad <“davesee@microsoft.com”> Date: Tue, 13 Oct 2020 15:59:29 -0400 Subject: [PATCH 52/56] Feedback updates --- .../azure/terraform-azure-example/main.tf | 2 + .../azure/terraform-azure-vm-example/main.tf | 5 +- modules/azure/compute.go | 77 +++++-------------- modules/azure/compute_test.go | 24 ------ modules/azure/resourceid.go | 14 +++- test/azure/terraform_azure_vm_example_test.go | 8 +- 6 files changed, 42 insertions(+), 88 deletions(-) diff --git a/examples/azure/terraform-azure-example/main.tf b/examples/azure/terraform-azure-example/main.tf index d3c4af5db..e9bb1ad10 100644 --- a/examples/azure/terraform-azure-example/main.tf +++ b/examples/azure/terraform-azure-example/main.tf @@ -102,6 +102,8 @@ resource "azurerm_virtual_machine" "main" { depends_on = [random_password.main] } +# Random password is used as an example to simplify the deployment and improve the security of the remote VM. +# This is not as a production recommendation as the password is stored in the Terraform state file. resource "random_password" "main" { length = 16 override_special = "-_%@" diff --git a/examples/azure/terraform-azure-vm-example/main.tf b/examples/azure/terraform-azure-vm-example/main.tf index b191edd0c..37009fd7f 100644 --- a/examples/azure/terraform-azure-vm-example/main.tf +++ b/examples/azure/terraform-azure-vm-example/main.tf @@ -12,8 +12,7 @@ provider "azurerm" { } # --------------------------------------------------------------------------------------------------------------------- -# PIN TERRAFORM VERSION TO >= 0.12 -# The examples have been upgraded to 0.12 syntax +# PIN TERRAFORM VERSION # --------------------------------------------------------------------------------------------------------------------- terraform { @@ -131,6 +130,8 @@ resource "azurerm_virtual_machine" "vm_example" { depends_on = [random_password.rand] } +# Random password is used as an example to simplify the deployment and improve the security of the remote VM. +# This is not as a production recommendation as the password is stored in the Terraform state file. resource "random_password" "rand" { length = 16 override_special = "-_%@" diff --git a/modules/azure/compute.go b/modules/azure/compute.go index 8aeed9bb4..429fe4dfa 100644 --- a/modules/azure/compute.go +++ b/modules/azure/compute.go @@ -47,17 +47,16 @@ func GetVirtualMachineNicsE(vmName string, resGroupName string, subscriptionID s return nil, err } + // Get VM NIC(s); value always present, no nil checks needed. vmNICs := *vm.NetworkProfile.NetworkInterfaces - if len(vmNICs) == 0 { - // No NIC present - return nil, nil - } - // Get the Names of the attached NICs nics := make([]string, len(vmNICs)) - for i, nic := range vmNICs { - nics[i] = GetNameFromResourceID(*nic.ID) + // Get ID from resource string. + nicName, err := GetNameFromResourceIDE(*nic.ID) + if err == nil { + nics[i] = nicName + } } return nics, nil } @@ -80,17 +79,13 @@ func GetVirtualMachineManagedDisksE(vmName string, resGroupName string, subscrip return nil, err } - // Get VM attached Disks + // Get VM attached Disks; value always present even if no disks attached, no nil check needed. vmDisks := *vm.StorageProfile.DataDisks - // No Attached Disks present - if len(vmDisks) == 0 { - return nil, nil - } - // Get the Names of the attached Managed Disks diskNames := make([]string, len(vmDisks)) for i, v := range vmDisks { + // Disk names are required, no nil check needed. diskNames[i] = *v.Name } @@ -139,7 +134,13 @@ func GetVirtualMachineAvailabilitySetIDE(vmName string, resGroupName string, sub return "", nil } - return GetNameFromResourceID(*vm.AvailabilitySet.ID), nil + // Get ID from resource string + avs, err := GetNameFromResourceIDE(*vm.AvailabilitySet.ID) + if err != nil { + return "", err + } + + return avs, nil } // VMImage represents the storage image for the specified Azure Virtual Machine. @@ -169,10 +170,6 @@ func GetVirtualMachineImageE(vmName string, resGroupName string, subscriptionID return vmImage, err } - if vm.StorageProfile == nil { - return vmImage, NewNotFoundError("Image Reference", "Any", vmName) - } - // Populate VM Image; values always present, no nil checks needed vmImage.Publisher = *vm.StorageProfile.ImageReference.Publisher vmImage.Offer = *vm.StorageProfile.ImageReference.Offer @@ -182,25 +179,6 @@ func GetVirtualMachineImageE(vmName string, resGroupName string, subscriptionID return vmImage, nil } -// GetVirtualMachineAdminUser gets the Admin Username of the specified Azure Virtual Machine. -// This function would fail the test if there is an error. -func GetVirtualMachineAdminUser(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) string { - adminUser, err := GetVirtualMachineAdminUserE(vmName, resGroupName, subscriptionID) - require.NoError(t, err) - return adminUser -} - -// GetVirtualMachineAdminUserE gets the Admin Username of the specified Azure Virtual Machine. -func GetVirtualMachineAdminUserE(vmName string, resGroupName string, subscriptionID string) (string, error) { - // Get VM Object - vm, err := GetVirtualMachineE(vmName, resGroupName, subscriptionID) - if err != nil { - return "", err - } - - return string(*vm.OsProfile.AdminUsername), nil -} - // GetSizeOfVirtualMachine gets the Size Type of the specified Azure Virtual Machine. // This function would fail the test if there is an error. func GetSizeOfVirtualMachine(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) compute.VirtualMachineSizeTypes { @@ -293,19 +271,23 @@ func GetVirtualMachinesForResourceGroup(t testing.TestingT, resGroupName string, // GetVirtualMachinesForResourceGroupE gets all Virtual Machine objects in the specified Resource Group. Each // VM Object represents the entire set of VM compute properties accessible by using the VM name as the map key. func GetVirtualMachinesForResourceGroupE(resourceGroupName string, subscriptionID string) (map[string]compute.VirtualMachineProperties, error) { + // Create VM Client vmClient, err := GetVirtualMachineClientE(subscriptionID) if err != nil { return nil, err } + // Get the list of VMs in the Resource Group vms, err := vmClient.List(context.Background(), resourceGroupName) if err != nil { return nil, err } + // Get the VMs in the Resource Group. vmDetails := make(map[string]compute.VirtualMachineProperties, len(vms.Values())) for _, v := range vms.Values() { - vmDetails[string(*v.Name)] = *v.VirtualMachineProperties + // VM name and machine properties are required for each VM, no nill check required. + vmDetails[*v.Name] = *v.VirtualMachineProperties } return vmDetails, nil } @@ -319,25 +301,6 @@ type Instance struct { *compute.VirtualMachine } -// GetVirtualMachineInstance gets a local Virtual Machine instance in the specified Resource Group. -// This function would fail the test if there is an error. -func GetVirtualMachineInstance(t testing.TestingT, vmName string, resGroupName string, subscriptionID string) *Instance { - vm, err := GetVirtualMachineInstanceE(vmName, resGroupName, subscriptionID) - require.NoError(t, err) - return vm -} - -// GetVirtualMachineInstanceE gets a local Virtual Machine instance in the specified Resource Group. -func GetVirtualMachineInstanceE(vmName string, resGroupName string, subscriptionID string) (*Instance, error) { - // Get VM Object - vm, err := GetVirtualMachineE(vmName, resGroupName, subscriptionID) - if err != nil { - return nil, err - } - - return &Instance{vm}, nil -} - // GetVirtualMachineInstanceSize gets the size of the Virtual Machine. func (vm *Instance) GetVirtualMachineInstanceSize() compute.VirtualMachineSizeTypes { return vm.VirtualMachineProperties.HardwareProfile.VMSize diff --git a/modules/azure/compute_test.go b/modules/azure/compute_test.go index 4fd2659d3..af31bc2ed 100644 --- a/modules/azure/compute_test.go +++ b/modules/azure/compute_test.go @@ -28,18 +28,6 @@ func TestGetVirtualMachineE(t *testing.T) { require.Error(t, err) } -func TestGetVirtualMachineInstanceE(t *testing.T) { - t.Parallel() - - vmName := "" - rgName := "" - subID := "" - - _, err := GetVirtualMachineInstanceE(vmName, rgName, subID) - - require.Error(t, err) -} - func TestListVirtualMachinesForResourceGroupE(t *testing.T) { t.Parallel() @@ -86,18 +74,6 @@ func TestGetSizeOfVirtualMachineE(t *testing.T) { require.Error(t, err) } -func TestGetVirtualMachineAdminUserE(t *testing.T) { - t.Parallel() - - vmName := "" - rgName := "" - subID := "" - - _, err := GetVirtualMachineAdminUserE(vmName, rgName, subID) - - require.Error(t, err) -} - func TestGetVirtualMachineImageE(t *testing.T) { t.Parallel() diff --git a/modules/azure/resourceid.go b/modules/azure/resourceid.go index 8d13ef51e..c2b3a4aec 100644 --- a/modules/azure/resourceid.go +++ b/modules/azure/resourceid.go @@ -2,11 +2,21 @@ package azure import "github.com/gruntwork-io/terratest/modules/collections" -// GetNameFromResourceID gets the Name from an Azure Resource ID +// GetNameFromResourceID gets the Name from an Azure Resource ID. func GetNameFromResourceID(resourceID string) string { - id, err := collections.GetSliceLastValueE(resourceID, "/") + id, err := GetNameFromResourceIDE(resourceID) if err != nil { return "" } return id } + +// GetNameFromResourceIDE gets the Name from an Azure Resource ID. +// This function would fail the test if there is an error. +func GetNameFromResourceIDE(resourceID string) (string, error) { + id, err := collections.GetSliceLastValueE(resourceID, "/") + if err != nil { + return "", err + } + return id, nil +} diff --git a/test/azure/terraform_azure_vm_example_test.go b/test/azure/terraform_azure_vm_example_test.go index 655937700..cce03e8df 100644 --- a/test/azure/terraform_azure_vm_example_test.go +++ b/test/azure/terraform_azure_vm_example_test.go @@ -69,7 +69,7 @@ func testStrategiesForVMs(t *testing.T, terraformOptions *terraform.Options, sub // 3. Check the VM size by instance. This strategy is beneficial when checking multiple properties // by using one VM instance and making calls against it with the added benefit of property check abstraction. - vmInstance := azure.GetVirtualMachineInstance(t, virtualMachineName, resourceGroupName, subscriptionID) + vmInstance := &azure.Instance{vmByRef} actualVMSize = vmInstance.GetVirtualMachineInstanceSize() assert.Equal(t, expectedVMSize, actualVMSize) } @@ -96,7 +96,7 @@ func testMultipleVMs(t *testing.T, terraformOptions *terraform.Options, subscrip // multiple SDK calls. The penalty for this approach is introducing direct references // which need to be checked for nil for optional configurations. vmsByRef := azure.GetVirtualMachinesForResourceGroup(t, resourceGroupName, subscriptionID) - thisVM := (vmsByRef)[expectedVMName] + thisVM := vmsByRef[expectedVMName] assert.Equal(t, expectedVMSize, thisVM.HardwareProfile.VMSize) // Check for the VM negative test. @@ -118,7 +118,8 @@ func testInformationOfVM(t *testing.T, terraformOptions *terraform.Options, subs assert.True(t, azure.VirtualMachineExists(t, virtualMachineName, resourceGroupName, subscriptionID)) // Check the Admin User of the VM. - actualVmAdminUser := azure.GetVirtualMachineAdminUser(t, virtualMachineName, resourceGroupName, subscriptionID) + actualVM := azure.GetVirtualMachine(t, virtualMachineName, resourceGroupName, subscriptionID) + actualVmAdminUser := *actualVM.OsProfile.AdminUsername assert.Equal(t, expectedVmAdminUser[0], actualVmAdminUser) // Check the Storage Image properties of the VM. @@ -157,6 +158,7 @@ func testDisksOfVM(t *testing.T, terraformOptions *terraform.Options, subscripti assert.Equal(t, expectedManagedDiskCount, len(actualManagedDiskNames)) // Check the Disk Type of the Managed Disk of the VM. + // This does not apply to VHD disks saved under a storage account. actualDiskType := azure.GetDiskType(t, expectedDiskName, resourceGroupName, subscriptionID) assert.Equal(t, compute.DiskStorageAccountTypes(expectedDiskType), actualDiskType) } From 684bcc0c96805401a36f4e48ab2388bd73927233 Mon Sep 17 00:00:00 2001 From: Dave Seepersad <“davesee@microsoft.com”> Date: Tue, 13 Oct 2020 16:23:05 -0400 Subject: [PATCH 53/56] Removed disk type test --- modules/azure/disk.go | 27 +++---------------- test/azure/terraform_azure_vm_example_test.go | 3 ++- 2 files changed, 6 insertions(+), 24 deletions(-) diff --git a/modules/azure/disk.go b/modules/azure/disk.go index 6cb1cc1c1..d78487e5f 100644 --- a/modules/azure/disk.go +++ b/modules/azure/disk.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/require" ) -// DiskExists indicates whether the specified Azure Managed Disk exists. +// DiskExists indicates whether the specified Azure Managed Disk exists // This function would fail the test if there is an error. func DiskExists(t testing.TestingT, diskName string, resGroupName string, subscriptionID string) bool { exists, err := DiskExistsE(diskName, resGroupName, subscriptionID) @@ -29,26 +29,7 @@ func DiskExistsE(diskName string, resGroupName string, subscriptionID string) (b return true, nil } -// GetDiskType returns the Disk Storage Account Type of the Azure Managed Disk. -// This function would fail the test if there is an error. -func GetDiskType(t testing.TestingT, diskName string, resGroupName string, subscriptionID string) compute.DiskStorageAccountTypes { - diskType, err := GetDiskTypeE(diskName, resGroupName, subscriptionID) - require.NoError(t, err) - return diskType -} - -// GetDiskTypeE returns the Disk Storage Account Type of the Azure Managed Disk. -func GetDiskTypeE(diskName string, resGroupName string, subscriptionID string) (compute.DiskStorageAccountTypes, error) { - // Get the Disk object - disk, err := GetDiskE(diskName, resGroupName, subscriptionID) - if err != nil { - return "", err - } - - return disk.Sku.Name, nil -} - -// GetDisk returns a Disk in the specified Azure Resource Group. +// GetDisk returns a Disk in the specified Azure Resource Group // This function would fail the test if there is an error. func GetDisk(t testing.TestingT, diskName string, resGroupName string, subscriptionID string) *compute.Disk { disk, err := GetDiskE(diskName, resGroupName, subscriptionID) @@ -56,7 +37,7 @@ func GetDisk(t testing.TestingT, diskName string, resGroupName string, subscript return disk } -// GetDiskE returns a Disk in the specified Azure Resource Group. +// GetDiskE returns a Disk in the specified Azure Resource Group func GetDiskE(diskName string, resGroupName string, subscriptionID string) (*compute.Disk, error) { // Validate resource group name and subscription ID resGroupName, err := getTargetAzureResourceGroupName(resGroupName) @@ -79,7 +60,7 @@ func GetDiskE(diskName string, resGroupName string, subscriptionID string) (*com return &disk, nil } -// GetDiskClientE returns a new Disk client in the specified Azure Subscription. +// GetDiskClientE returns a new Disk client in the specified Azure Subscription func GetDiskClientE(subscriptionID string) (*compute.DisksClient, error) { // Validate Azure subscription ID subscriptionID, err := getTargetAzureSubscription(subscriptionID) diff --git a/test/azure/terraform_azure_vm_example_test.go b/test/azure/terraform_azure_vm_example_test.go index cce03e8df..8af977275 100644 --- a/test/azure/terraform_azure_vm_example_test.go +++ b/test/azure/terraform_azure_vm_example_test.go @@ -159,7 +159,8 @@ func testDisksOfVM(t *testing.T, terraformOptions *terraform.Options, subscripti // Check the Disk Type of the Managed Disk of the VM. // This does not apply to VHD disks saved under a storage account. - actualDiskType := azure.GetDiskType(t, expectedDiskName, resourceGroupName, subscriptionID) + actualDisk := azure.GetDisk(t, expectedDiskName, resourceGroupName, subscriptionID) + actualDiskType := actualDisk.Sku.Name assert.Equal(t, compute.DiskStorageAccountTypes(expectedDiskType), actualDiskType) } From eecf3d8dc095ac85fe2ad4a84c3463233a435c51 Mon Sep 17 00:00:00 2001 From: Dave Seepersad <“davesee@microsoft.com”> Date: Tue, 13 Oct 2020 16:30:51 -0400 Subject: [PATCH 54/56] Lint fix --- test/azure/terraform_azure_vm_example_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/azure/terraform_azure_vm_example_test.go b/test/azure/terraform_azure_vm_example_test.go index 8af977275..73b5cd682 100644 --- a/test/azure/terraform_azure_vm_example_test.go +++ b/test/azure/terraform_azure_vm_example_test.go @@ -69,7 +69,7 @@ func testStrategiesForVMs(t *testing.T, terraformOptions *terraform.Options, sub // 3. Check the VM size by instance. This strategy is beneficial when checking multiple properties // by using one VM instance and making calls against it with the added benefit of property check abstraction. - vmInstance := &azure.Instance{vmByRef} + vmInstance := &(azure.Instance){vmByRef} actualVMSize = vmInstance.GetVirtualMachineInstanceSize() assert.Equal(t, expectedVMSize, actualVMSize) } @@ -101,7 +101,7 @@ func testMultipleVMs(t *testing.T, terraformOptions *terraform.Options, subscrip // Check for the VM negative test. fakeVM := fmt.Sprintf("vm-%s", random.UniqueId()) - assert.Nil(t, (vmsByRef)[fakeVM].VMID) + assert.Nil(t, vmsByRef[fakeVM].VMID) } // These tests check information directly related to the specified Azure Virtual Machine. From 608e8465b1ab7fbce121bc2ecf659d90aa69fe74 Mon Sep 17 00:00:00 2001 From: Dave Seepersad <“davesee@microsoft.com”> Date: Tue, 13 Oct 2020 17:31:55 -0400 Subject: [PATCH 55/56] Lint fix --- test/azure/terraform_azure_vm_example_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/azure/terraform_azure_vm_example_test.go b/test/azure/terraform_azure_vm_example_test.go index 73b5cd682..536270ed3 100644 --- a/test/azure/terraform_azure_vm_example_test.go +++ b/test/azure/terraform_azure_vm_example_test.go @@ -69,7 +69,7 @@ func testStrategiesForVMs(t *testing.T, terraformOptions *terraform.Options, sub // 3. Check the VM size by instance. This strategy is beneficial when checking multiple properties // by using one VM instance and making calls against it with the added benefit of property check abstraction. - vmInstance := &(azure.Instance){vmByRef} + vmInstance := azure.Instance{vmByRef} actualVMSize = vmInstance.GetVirtualMachineInstanceSize() assert.Equal(t, expectedVMSize, actualVMSize) } From 12816112c731764ba447d2d0c93c78c737beffd2 Mon Sep 17 00:00:00 2001 From: Dave Seepersad <“davesee@microsoft.com”> Date: Tue, 13 Oct 2020 18:12:00 -0400 Subject: [PATCH 56/56] LInt fix --- test/azure/terraform_azure_vm_example_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/azure/terraform_azure_vm_example_test.go b/test/azure/terraform_azure_vm_example_test.go index 536270ed3..f74e00265 100644 --- a/test/azure/terraform_azure_vm_example_test.go +++ b/test/azure/terraform_azure_vm_example_test.go @@ -69,7 +69,7 @@ func testStrategiesForVMs(t *testing.T, terraformOptions *terraform.Options, sub // 3. Check the VM size by instance. This strategy is beneficial when checking multiple properties // by using one VM instance and making calls against it with the added benefit of property check abstraction. - vmInstance := azure.Instance{vmByRef} + vmInstance := azure.Instance{VirtualMachine: vmByRef} actualVMSize = vmInstance.GetVirtualMachineInstanceSize() assert.Equal(t, expectedVMSize, actualVMSize) }