diff --git a/azurerm/config.go b/azurerm/config.go index 5c50929f6055..84b14691ba7a 100644 --- a/azurerm/config.go +++ b/azurerm/config.go @@ -154,6 +154,7 @@ type ArmClient struct { // DevTestLabs devTestLabsClient dtl.LabsClient + devTestVirtualMachinesClient dtl.VirtualMachinesClient devTestVirtualNetworksClient dtl.VirtualNetworksClient // Databases @@ -773,6 +774,10 @@ func (c *ArmClient) registerDevTestClients(endpoint, subscriptionId string, auth c.configureClient(&labsClient.Client, auth) c.devTestLabsClient = labsClient + devTestVirtualMachinesClient := dtl.NewVirtualMachinesClientWithBaseURI(endpoint, subscriptionId) + c.configureClient(&devTestVirtualMachinesClient.Client, auth) + c.devTestVirtualMachinesClient = devTestVirtualMachinesClient + devTestVirtualNetworksClient := dtl.NewVirtualNetworksClientWithBaseURI(endpoint, subscriptionId) c.configureClient(&devTestVirtualNetworksClient.Client, auth) c.devTestVirtualNetworksClient = devTestVirtualNetworksClient diff --git a/azurerm/data_source_dev_test_lab.go b/azurerm/data_source_dev_test_lab.go index 8a1c6c78fa01..2e9b4f4551d0 100644 --- a/azurerm/data_source_dev_test_lab.go +++ b/azurerm/data_source_dev_test_lab.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/hashicorp/terraform/helper/schema" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -16,7 +16,7 @@ func dataSourceArmDevTestLab() *schema.Resource { "name": { Type: schema.TypeString, Required: true, - ValidateFunc: azure.ValidateDevTestLabName(), + ValidateFunc: validate.DevTestLabName(), }, "location": locationForDataSourceSchema(), diff --git a/azurerm/helpers/azure/devtest.go b/azurerm/helpers/azure/devtest.go index 3a18c96af15d..28b5f54076f2 100644 --- a/azurerm/helpers/azure/devtest.go +++ b/azurerm/helpers/azure/devtest.go @@ -1,14 +1,144 @@ package azure import ( - "regexp" - + "github.com/Azure/azure-sdk-for-go/services/devtestlabs/mgmt/2016-05-15/dtl" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) -func ValidateDevTestLabName() schema.SchemaValidateFunc { - return validation.StringMatch( - regexp.MustCompile("^[A-Za-z0-9_-]+$"), - "Lab Name can only include alphanumeric characters, underscores, hyphens.") +func SchemaDevTestVirtualMachineInboundNatRule() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + // since these aren't returned from the API + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "protocol": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(dtl.TCP), + string(dtl.UDP), + }, false), + }, + + "backend_port": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + ValidateFunc: validate.PortNumber, + }, + + "frontend_port": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + } +} + +func ExpandDevTestLabVirtualMachineNatRules(input *schema.Set) []dtl.InboundNatRule { + rules := make([]dtl.InboundNatRule, 0) + if input == nil { + return rules + } + + for _, val := range input.List() { + v := val.(map[string]interface{}) + backendPort := v["backend_port"].(int) + protocol := v["protocol"].(string) + + rule := dtl.InboundNatRule{ + TransportProtocol: dtl.TransportProtocol(protocol), + BackendPort: utils.Int32(int32(backendPort)), + } + + rules = append(rules, rule) + } + + return rules +} + +func ExpandDevTestLabVirtualMachineGalleryImageReference(input []interface{}, osType string) *dtl.GalleryImageReference { + if len(input) == 0 { + return nil + } + + v := input[0].(map[string]interface{}) + offer := v["offer"].(string) + publisher := v["publisher"].(string) + sku := v["sku"].(string) + version := v["version"].(string) + + return &dtl.GalleryImageReference{ + Offer: utils.String(offer), + OsType: utils.String(osType), + Publisher: utils.String(publisher), + Sku: utils.String(sku), + Version: utils.String(version), + } +} + +func SchemaDevTestVirtualMachineGalleryImageReference() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "offer": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "publisher": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "sku": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "version": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + }, + } +} + +func FlattenDevTestVirtualMachineGalleryImage(input *dtl.GalleryImageReference) []interface{} { + results := make([]interface{}, 0) + + if input != nil { + output := make(map[string]interface{}, 0) + + if input.Offer != nil { + output["offer"] = *input.Offer + } + + if input.Publisher != nil { + output["publisher"] = *input.Publisher + } + + if input.Sku != nil { + output["sku"] = *input.Sku + } + + if input.Version != nil { + output["version"] = *input.Version + } + + results = append(results, output) + } + + return results } diff --git a/azurerm/helpers/azure/devtest_test.go b/azurerm/helpers/azure/devtest_test.go deleted file mode 100644 index f5090b9d808c..000000000000 --- a/azurerm/helpers/azure/devtest_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package azure - -import "testing" - -func TestValidateDevTestLabName(t *testing.T) { - validNames := []string{ - "valid-name", - "valid02-name", - "validName1", - "-validname1", - "valid_name", - "double-hyphen--valid", - } - for _, v := range validNames { - _, errors := ValidateDevTestLabName()(v, "example") - if len(errors) != 0 { - t.Fatalf("%q should be a valid Dev Test Lab Name: %q", v, errors) - } - } - - invalidNames := []string{ - "invalid!", - "!@£", - } - for _, v := range invalidNames { - _, errors := ValidateDevTestLabName()(v, "name") - if len(errors) == 0 { - t.Fatalf("%q should be an invalid Dev Test Lab Name", v) - } - } -} diff --git a/azurerm/helpers/validate/devtest.go b/azurerm/helpers/validate/devtest.go new file mode 100644 index 000000000000..ea13e6c6f2f5 --- /dev/null +++ b/azurerm/helpers/validate/devtest.go @@ -0,0 +1,58 @@ +package validate + +import ( + "fmt" + "regexp" + + "github.com/Azure/azure-sdk-for-go/services/devtestlabs/mgmt/2016-05-15/dtl" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" +) + +func DevTestLabName() schema.SchemaValidateFunc { + return validation.StringMatch( + regexp.MustCompile("^[A-Za-z0-9_-]+$"), + "Lab Name can only include alphanumeric characters, underscores, hyphens.") +} + +func DevTestVirtualMachineName(maxLength int) schema.SchemaValidateFunc { + return func(i interface{}, k string) ([]string, []error) { + v, ok := i.(string) + if !ok { + return nil, []error{ + fmt.Errorf("expected type of %s to be string", k), + } + } + + errs := make([]error, 0) + if 1 <= len(v) && len(v) > maxLength { + errs = append(errs, fmt.Errorf("Expected %s to be between 1 and %d characters - got %d", k, maxLength, len(v))) + } + + matched, err := regexp.MatchString("^([a-zA-Z0-9]{1})([a-zA-Z0-9-]+)([a-zA-Z0-9]{1})$", v) + if err != nil { + errs = append(errs, fmt.Errorf("Error validating regex: %+v", err)) + } + if !matched { + errs = append(errs, fmt.Errorf("%s may contain letters, numbers, or '-', must begin and end with a letter or number, and cannot be all numbers.", k)) + } + + matched, err = regexp.MatchString("([a-zA-Z-]+)", v) + if err != nil { + errs = append(errs, fmt.Errorf("Error validating regex: %+v", err)) + } + if !matched { + errs = append(errs, fmt.Errorf("%s cannot be all numbers.", k)) + } + + return nil, errs + } +} + +func DevTestVirtualNetworkUsagePermissionType() schema.SchemaValidateFunc { + return validation.StringInSlice([]string{ + string(dtl.Allow), + string(dtl.Default), + string(dtl.Deny), + }, false) +} diff --git a/azurerm/helpers/validate/devtest_test.go b/azurerm/helpers/validate/devtest_test.go new file mode 100644 index 000000000000..07c58553478c --- /dev/null +++ b/azurerm/helpers/validate/devtest_test.go @@ -0,0 +1,68 @@ +package validate + +import "testing" + +func TestValidateDevTestLabName(t *testing.T) { + validNames := []string{ + "valid-name", + "valid02-name", + "validName1", + "-validname1", + "valid_name", + "double-hyphen--valid", + } + for _, v := range validNames { + _, errors := DevTestLabName()(v, "example") + if len(errors) != 0 { + t.Fatalf("%q should be a valid Dev Test Lab Name: %q", v, errors) + } + } + + invalidNames := []string{ + "invalid!", + "!@£", + } + for _, v := range invalidNames { + _, errors := DevTestLabName()(v, "name") + if len(errors) == 0 { + t.Fatalf("%q should be an invalid Dev Test Lab Name", v) + } + } +} + +func TestValidateDevTestVirtualMachineName(t *testing.T) { + validNames := []string{ + "valid-name", + "valid02-ne", + "validName1", + "1hello", + "hello1", + "1hello1", + "dbl--valid", + } + for _, v := range validNames { + _, errors := DevTestVirtualMachineName(10)(v, "example") + if len(errors) != 0 { + t.Fatalf("%q should be a valid Dev Test Virtual Machine Name: %q", v, errors) + } + } + + invalidNames := []string{ + "", + "-invalidname1", + "thisnameiswaytoolong", + "12345", + "in_valid", + "-hello", + "hello-", + "1hello-", + "invalid!", + "!@£", + } + for _, v := range invalidNames { + _, errors := DevTestVirtualMachineName(10)(v, "name") + if len(errors) == 0 { + t.Fatalf("%q should be an invalid Dev Test Virtual Machine Name", v) + } + } +} diff --git a/azurerm/provider.go b/azurerm/provider.go index e82f7e28590c..1444b21e3360 100644 --- a/azurerm/provider.go +++ b/azurerm/provider.go @@ -161,6 +161,8 @@ func Provider() terraform.ResourceProvider { "azurerm_data_lake_store_file": resourceArmDataLakeStoreFile(), "azurerm_data_lake_store_firewall_rule": resourceArmDataLakeStoreFirewallRule(), "azurerm_dev_test_lab": resourceArmDevTestLab(), + "azurerm_dev_test_linux_virtual_machine": resourceArmDevTestLinuxVirtualMachine(), + "azurerm_dev_test_windows_virtual_machine": resourceArmDevTestWindowsVirtualMachine(), "azurerm_dev_test_virtual_network": resourceArmDevTestVirtualNetwork(), "azurerm_dns_a_record": resourceArmDnsARecord(), "azurerm_dns_aaaa_record": resourceArmDnsAAAARecord(), diff --git a/azurerm/resource_arm_dev_test_lab.go b/azurerm/resource_arm_dev_test_lab.go index 2307727709a2..f08086c96453 100644 --- a/azurerm/resource_arm_dev_test_lab.go +++ b/azurerm/resource_arm_dev_test_lab.go @@ -7,7 +7,7 @@ import ( "github.com/Azure/azure-sdk-for-go/services/devtestlabs/mgmt/2016-05-15/dtl" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -26,7 +26,7 @@ func resourceArmDevTestLab() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, - ValidateFunc: azure.ValidateDevTestLabName(), + ValidateFunc: validate.DevTestLabName(), }, "location": locationSchema(), diff --git a/azurerm/resource_arm_dev_test_linux_virtual_machine.go b/azurerm/resource_arm_dev_test_linux_virtual_machine.go new file mode 100644 index 000000000000..bb4a120de718 --- /dev/null +++ b/azurerm/resource_arm_dev_test_linux_virtual_machine.go @@ -0,0 +1,306 @@ +package azurerm + +import ( + "fmt" + "log" + + "github.com/Azure/azure-sdk-for-go/services/devtestlabs/mgmt/2016-05-15/dtl" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmDevTestLinuxVirtualMachine() *schema.Resource { + return &schema.Resource{ + Create: resourceArmDevTestLinuxVirtualMachineCreateUpdate, + Read: resourceArmDevTestLinuxVirtualMachineRead, + Update: resourceArmDevTestLinuxVirtualMachineCreateUpdate, + Delete: resourceArmDevTestLinuxVirtualMachineDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.DevTestVirtualMachineName(62), + }, + + "lab_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.DevTestLabName(), + }, + + // There's a bug in the Azure API where this is returned in lower-case + // BUG: https://github.com/Azure/azure-rest-api-specs/issues/3964 + "resource_group_name": resourceGroupNameDiffSuppressSchema(), + + "location": locationSchema(), + + "size": { + Type: schema.TypeString, + Required: true, + // since this isn't returned from the API + ForceNew: true, + }, + + "username": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "storage_type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + "Standard", + "Premium", + }, false), + }, + + "lab_subnet_name": { + Type: schema.TypeString, + Required: true, + // since this isn't returned from the API + ForceNew: true, + }, + + "lab_virtual_network_id": { + Type: schema.TypeString, + Required: true, + // since this isn't returned from the API + ForceNew: true, + }, + + "allow_claim": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + + "disallow_public_ip_address": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + + "password": { + Type: schema.TypeString, + Optional: true, + // since this isn't returned from the API + ForceNew: true, + Sensitive: true, + }, + + "ssh_key": { + Type: schema.TypeString, + Optional: true, + // since this isn't returned from the API + ForceNew: true, + }, + + "gallery_image_reference": azure.SchemaDevTestVirtualMachineGalleryImageReference(), + + "inbound_nat_rule": azure.SchemaDevTestVirtualMachineInboundNatRule(), + + "notes": { + Type: schema.TypeString, + Optional: true, + }, + + "tags": tagsSchema(), + + "fqdn": { + Type: schema.TypeString, + Computed: true, + }, + + "unique_identifier": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceArmDevTestLinuxVirtualMachineCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).devTestVirtualMachinesClient + ctx := meta.(*ArmClient).StopContext + + log.Printf("[INFO] preparing arguments for DevTest Linux Virtual Machine creation") + + name := d.Get("name").(string) + labName := d.Get("lab_name").(string) + resourceGroup := d.Get("resource_group_name").(string) + tags := d.Get("tags").(map[string]interface{}) + + allowClaim := d.Get("allow_claim").(bool) + disallowPublicIPAddress := d.Get("disallow_public_ip_address").(bool) + labSubnetName := d.Get("lab_subnet_name").(string) + labVirtualNetworkId := d.Get("lab_virtual_network_id").(string) + location := azureRMNormalizeLocation(d.Get("location").(string)) + notes := d.Get("notes").(string) + password := d.Get("password").(string) + sshKey := d.Get("ssh_key").(string) + size := d.Get("size").(string) + storageType := d.Get("storage_type").(string) + username := d.Get("username").(string) + + galleryImageReferenceRaw := d.Get("gallery_image_reference").([]interface{}) + galleryImageReference := azure.ExpandDevTestLabVirtualMachineGalleryImageReference(galleryImageReferenceRaw, "Linux") + + natRulesRaw := d.Get("inbound_nat_rule").(*schema.Set) + natRules := azure.ExpandDevTestLabVirtualMachineNatRules(natRulesRaw) + + if len(natRules) > 0 && !disallowPublicIPAddress { + return fmt.Errorf("If `inbound_nat_rule` is specified then `disallow_public_ip_address` must be set to true.") + } + + nic := dtl.NetworkInterfaceProperties{} + if disallowPublicIPAddress { + nic.SharedPublicIPAddressConfiguration = &dtl.SharedPublicIPAddressConfiguration{ + InboundNatRules: &natRules, + } + } + + authenticateViaSsh := sshKey != "" + parameters := dtl.LabVirtualMachine{ + Location: utils.String(location), + LabVirtualMachineProperties: &dtl.LabVirtualMachineProperties{ + AllowClaim: utils.Bool(allowClaim), + IsAuthenticationWithSSHKey: utils.Bool(authenticateViaSsh), + DisallowPublicIPAddress: utils.Bool(disallowPublicIPAddress), + GalleryImageReference: galleryImageReference, + LabSubnetName: utils.String(labSubnetName), + LabVirtualNetworkID: utils.String(labVirtualNetworkId), + NetworkInterface: &nic, + OsType: utils.String("Linux"), + Notes: utils.String(notes), + Password: utils.String(password), + Size: utils.String(size), + SSHKey: utils.String(sshKey), + StorageType: utils.String(storageType), + UserName: utils.String(username), + }, + Tags: expandTags(tags), + } + + future, err := client.CreateOrUpdate(ctx, resourceGroup, labName, name, parameters) + if err != nil { + return fmt.Errorf("Error creating/updating DevTest Linux Virtual Machine %q (Lab %q / Resource Group %q): %+v", name, labName, resourceGroup, err) + } + + err = future.WaitForCompletionRef(ctx, client.Client) + if err != nil { + return fmt.Errorf("Error waiting for creation/update of DevTest Linux Virtual Machine %q (Lab %q / Resource Group %q): %+v", name, labName, resourceGroup, err) + } + + read, err := client.Get(ctx, resourceGroup, labName, name, "") + if err != nil { + return fmt.Errorf("Error retrieving DevTest Linux Virtual Machine %q (Lab %q / Resource Group %q): %+v", name, labName, resourceGroup, err) + } + + if read.ID == nil { + return fmt.Errorf("Cannot read DevTest Linux Virtual Machine %q (Lab %q / Resource Group %q) ID", name, labName, resourceGroup) + } + + d.SetId(*read.ID) + + return resourceArmDevTestLinuxVirtualMachineRead(d, meta) +} + +func resourceArmDevTestLinuxVirtualMachineRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).devTestVirtualMachinesClient + ctx := meta.(*ArmClient).StopContext + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + resourceGroup := id.ResourceGroup + labName := id.Path["labs"] + name := id.Path["virtualmachines"] + + read, err := client.Get(ctx, resourceGroup, labName, name, "") + if err != nil { + if utils.ResponseWasNotFound(read.Response) { + log.Printf("[DEBUG] DevTest Linux Virtual Machine %q was not found in Lab %q / Resource Group %q - removing from state!", name, labName, resourceGroup) + d.SetId("") + return nil + } + + return fmt.Errorf("Error making Read request on DevTest Linux Virtual Machine %q (Lab %q / Resource Group %q): %+v", name, labName, resourceGroup, err) + } + + d.Set("name", read.Name) + d.Set("lab_name", labName) + d.Set("resource_group_name", resourceGroup) + if location := read.Location; location != nil { + d.Set("location", azureRMNormalizeLocation(*location)) + } + + if props := read.LabVirtualMachineProperties; props != nil { + d.Set("allow_claim", props.AllowClaim) + d.Set("disallow_public_ip_address", props.DisallowPublicIPAddress) + d.Set("notes", props.Notes) + d.Set("size", props.Size) + d.Set("storage_type", props.StorageType) + d.Set("username", props.UserName) + + flattenedImage := azure.FlattenDevTestVirtualMachineGalleryImage(props.GalleryImageReference) + if err := d.Set("gallery_image_reference", flattenedImage); err != nil { + return fmt.Errorf("Error flattening `gallery_image_reference`: %+v", err) + } + + // Computed fields + d.Set("fqdn", props.Fqdn) + d.Set("unique_identifier", props.UniqueIdentifier) + } + + flattenAndSetTags(d, read.Tags) + + return nil +} + +func resourceArmDevTestLinuxVirtualMachineDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).devTestVirtualMachinesClient + ctx := meta.(*ArmClient).StopContext + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + resourceGroup := id.ResourceGroup + labName := id.Path["labs"] + name := id.Path["virtualmachines"] + + read, err := client.Get(ctx, resourceGroup, labName, name, "") + if err != nil { + if utils.ResponseWasNotFound(read.Response) { + // deleted outside of TF + log.Printf("[DEBUG] DevTest Linux Virtual Machine %q was not found in Lab %q / Resource Group %q - assuming removed!", name, labName, resourceGroup) + return nil + } + + return fmt.Errorf("Error retrieving DevTest Linux Virtual Machine %q (Lab %q / Resource Group %q): %+v", name, labName, resourceGroup, err) + } + + future, err := client.Delete(ctx, resourceGroup, labName, name) + if err != nil { + return fmt.Errorf("Error deleting DevTest Linux Virtual Machine %q (Lab %q / Resource Group %q): %+v", name, labName, resourceGroup, err) + } + + err = future.WaitForCompletionRef(ctx, client.Client) + if err != nil { + return fmt.Errorf("Error waiting for the deletion of DevTest Linux Virtual Machine %q (Lab %q / Resource Group %q): %+v", name, labName, resourceGroup, err) + } + + return err +} diff --git a/azurerm/resource_arm_dev_test_linux_virtual_machine_test.go b/azurerm/resource_arm_dev_test_linux_virtual_machine_test.go new file mode 100644 index 000000000000..ad099532ec48 --- /dev/null +++ b/azurerm/resource_arm_dev_test_linux_virtual_machine_test.go @@ -0,0 +1,351 @@ +package azurerm + +import ( + "fmt" + "net/http" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAzureRMDevTestLinuxVirtualMachine_basic(t *testing.T) { + resourceName := "azurerm_dev_test_linux_virtual_machine.test" + rInt := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMDevTestLinuxVirtualMachineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMDevTestLinuxVirtualMachine_basic(rInt, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMDevTestLinuxVirtualMachineExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "gallery_image_reference.0.publisher", "Canonical"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + // not returned from the API + "lab_subnet_name", + "lab_virtual_network_id", + "password", + }, + }, + }, + }) +} + +func TestAccAzureRMDevTestLinuxVirtualMachine_basicSSH(t *testing.T) { + resourceName := "azurerm_dev_test_linux_virtual_machine.test" + rInt := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMDevTestLinuxVirtualMachineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMDevTestLinuxVirtualMachine_basicSSH(rInt, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMDevTestLinuxVirtualMachineExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "gallery_image_reference.0.publisher", "Canonical"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + // not returned from the API + "lab_subnet_name", + "lab_virtual_network_id", + "ssh_key", + }, + }, + }, + }) +} + +func TestAccAzureRMDevTestLinuxVirtualMachine_inboundNatRules(t *testing.T) { + resourceName := "azurerm_dev_test_linux_virtual_machine.test" + rInt := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMDevTestLinuxVirtualMachineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMDevTestLinuxVirtualMachine_inboundNatRules(rInt, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMDevTestLinuxVirtualMachineExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "disallow_public_ip_address", "true"), + resource.TestCheckResourceAttr(resourceName, "gallery_image_reference.0.publisher", "Canonical"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.Acceptance", "Test"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + // not returned from the API + "inbound_nat_rule", + "lab_subnet_name", + "lab_virtual_network_id", + "password", + }, + }, + }, + }) +} + +func TestAccAzureRMDevTestLinuxVirtualMachine_updateStorage(t *testing.T) { + resourceName := "azurerm_dev_test_linux_virtual_machine.test" + rInt := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMDevTestLinuxVirtualMachineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMDevTestLinuxVirtualMachine_storage(rInt, location, "Standard"), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMDevTestLinuxVirtualMachineExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "gallery_image_reference.0.publisher", "Canonical"), + resource.TestCheckResourceAttr(resourceName, "storage_type", "Standard"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + { + Config: testAccAzureRMDevTestLinuxVirtualMachine_storage(rInt, location, "Premium"), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMDevTestLinuxVirtualMachineExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "gallery_image_reference.0.publisher", "Canonical"), + resource.TestCheckResourceAttr(resourceName, "storage_type", "Premium"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + }, + }) +} + +func testCheckAzureRMDevTestLinuxVirtualMachineExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // Ensure we have enough information in state to look up in API + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + virtualMachineName := rs.Primary.Attributes["name"] + labName := rs.Primary.Attributes["lab_name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + conn := testAccProvider.Meta().(*ArmClient).devTestVirtualMachinesClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + resp, err := conn.Get(ctx, resourceGroup, labName, virtualMachineName, "") + if err != nil { + return fmt.Errorf("Bad: Get devTestVirtualMachinesClient: %+v", err) + } + + if resp.StatusCode == http.StatusNotFound { + return fmt.Errorf("Bad: DevTest Linux Virtual Machine %q (Lab %q / Resource Group: %q) does not exist", virtualMachineName, labName, resourceGroup) + } + + return nil + } +} + +func testCheckAzureRMDevTestLinuxVirtualMachineDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*ArmClient).devTestVirtualMachinesClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_dev_test_linux_virtual_machine" { + continue + } + + virtualMachineName := rs.Primary.Attributes["name"] + labName := rs.Primary.Attributes["lab_name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + resp, err := conn.Get(ctx, resourceGroup, labName, virtualMachineName, "") + + if err != nil { + if resp.StatusCode == http.StatusNotFound { + return nil + } + + return err + } + + return fmt.Errorf("DevTest Linux Virtual Machine still exists:\n%#v", resp) + } + + return nil +} + +func testAccAzureRMDevTestLinuxVirtualMachine_basic(rInt int, location string) string { + template := testAccAzureRMDevTestLinuxVirtualMachine_template(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_dev_test_linux_virtual_machine" "test" { + name = "acctestvm-vm%d" + lab_name = "${azurerm_dev_test_lab.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + size = "Standard_F2" + username = "acct5stU5er" + password = "Pa$$w0rd1234!" + lab_virtual_network_id = "${azurerm_dev_test_virtual_network.test.id}" + lab_subnet_name = "${azurerm_dev_test_virtual_network.test.subnet.0.name}" + storage_type = "Standard" + + gallery_image_reference { + offer = "UbuntuServer" + publisher = "Canonical" + sku = "18.04-LTS" + version = "latest" + } +} +`, template, rInt) +} + +func testAccAzureRMDevTestLinuxVirtualMachine_basicSSH(rInt int, location string) string { + template := testAccAzureRMDevTestLinuxVirtualMachine_template(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_dev_test_linux_virtual_machine" "test" { + name = "acctestvm-vm%d" + lab_name = "${azurerm_dev_test_lab.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + size = "Standard_F2" + username = "acct5stU5er" + ssh_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDCsTcryUl51Q2VSEHqDRNmceUFo55ZtcIwxl2QITbN1RREti5ml/VTytC0yeBOvnZA4x4CFpdw/lCDPk0yrH9Ei5vVkXmOrExdTlT3qI7YaAzj1tUVlBd4S6LX1F7y6VLActvdHuDDuXZXzCDd/97420jrDfWZqJMlUK/EmCE5ParCeHIRIvmBxcEnGfFIsw8xQZl0HphxWOtJil8qsUWSdMyCiJYYQpMoMliO99X40AUc4/AlsyPyT5ddbKk08YrZ+rKDVHF7o29rh4vi5MmHkVgVQHKiKybWlHq+b71gIAUQk9wrJxD+dqt4igrmDSpIjfjwnd+l5UIn5fJSO5DYV4YT/4hwK7OKmuo7OFHD0WyY5YnkYEMtFgzemnRBdE8ulcT60DQpVgRMXFWHvhyCWy0L6sgj1QWDZlLpvsIvNfHsyhKFMG1frLnMt/nP0+YCcfg+v1JYeCKjeoJxB8DWcRBsjzItY0CGmzP8UYZiYKl/2u+2TgFS5r7NWH11bxoUzjKdaa1NLw+ieA8GlBFfCbfWe6YVB9ggUte4VtYFMZGxOjS2bAiYtfgTKFJv+XqORAwExG6+G2eDxIDyo80/OA9IG7Xv/jwQr7D6KDjDuULFcN/iTxuttoKrHeYz1hf5ZQlBdllwJHYx6fK2g8kha6r2JIQKocvsAXiiONqSfw== hello@world.com" + lab_virtual_network_id = "${azurerm_dev_test_virtual_network.test.id}" + lab_subnet_name = "${azurerm_dev_test_virtual_network.test.subnet.0.name}" + storage_type = "Standard" + + gallery_image_reference { + offer = "UbuntuServer" + publisher = "Canonical" + sku = "18.04-LTS" + version = "latest" + } +} +`, template, rInt) +} + +func testAccAzureRMDevTestLinuxVirtualMachine_inboundNatRules(rInt int, location string) string { + template := testAccAzureRMDevTestLinuxVirtualMachine_template(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_dev_test_linux_virtual_machine" "test" { + name = "acctestvm-vm%d" + lab_name = "${azurerm_dev_test_lab.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + size = "Standard_F2" + username = "acct5stU5er" + password = "Pa$$w0rd1234!" + disallow_public_ip_address = true + lab_virtual_network_id = "${azurerm_dev_test_virtual_network.test.id}" + lab_subnet_name = "${azurerm_dev_test_virtual_network.test.subnet.0.name}" + storage_type = "Standard" + + gallery_image_reference { + offer = "UbuntuServer" + publisher = "Canonical" + sku = "18.04-LTS" + version = "latest" + } + + inbound_nat_rule { + protocol = "Tcp" + backend_port = 22 + } + + inbound_nat_rule { + protocol = "Tcp" + backend_port = 3389 + } + + tags { + "Acceptance" = "Test" + } +} +`, template, rInt) +} + +func testAccAzureRMDevTestLinuxVirtualMachine_storage(rInt int, location, storageType string) string { + template := testAccAzureRMDevTestLinuxVirtualMachine_template(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_dev_test_linux_virtual_machine" "test" { + name = "acctestvm-vm%d" + lab_name = "${azurerm_dev_test_lab.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + size = "Standard_B1ms" + username = "acct5stU5er" + password = "Pa$$w0rd1234!" + lab_virtual_network_id = "${azurerm_dev_test_virtual_network.test.id}" + lab_subnet_name = "${azurerm_dev_test_virtual_network.test.subnet.0.name}" + storage_type = "%s" + + gallery_image_reference { + offer = "UbuntuServer" + publisher = "Canonical" + sku = "18.04-LTS" + version = "latest" + } +} +`, template, rInt, storageType) +} + +func testAccAzureRMDevTestLinuxVirtualMachine_template(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_dev_test_lab" "test" { + name = "acctestdtl%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" +} + +resource "azurerm_dev_test_virtual_network" "test" { + name = "acctestdtvn%d" + lab_name = "${azurerm_dev_test_lab.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + + subnet { + use_public_ip_address = "Allow" + use_in_virtual_machine_creation = "Allow" + } +} +`, rInt, location, rInt, rInt) +} diff --git a/azurerm/resource_arm_dev_test_virtual_network.go b/azurerm/resource_arm_dev_test_virtual_network.go index fc685a07113e..3cdf56975a0f 100644 --- a/azurerm/resource_arm_dev_test_virtual_network.go +++ b/azurerm/resource_arm_dev_test_virtual_network.go @@ -8,7 +8,7 @@ import ( "github.com/Azure/azure-sdk-for-go/services/devtestlabs/mgmt/2016-05-15/dtl" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -34,7 +34,7 @@ func resourceArmDevTestVirtualNetwork() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, - ValidateFunc: azure.ValidateDevTestLabName(), + ValidateFunc: validate.DevTestLabName(), }, // There's a bug in the Azure API where this is returned in lower-case @@ -63,14 +63,14 @@ func resourceArmDevTestVirtualNetwork() *schema.Resource { Type: schema.TypeString, Optional: true, Default: string(dtl.Allow), - ValidateFunc: validateDevTestVirtualNetworkUsagePermissionType(), + ValidateFunc: validate.DevTestVirtualNetworkUsagePermissionType(), }, "use_public_ip_address": { Type: schema.TypeString, Optional: true, Default: string(dtl.Allow), - ValidateFunc: validateDevTestVirtualNetworkUsagePermissionType(), + ValidateFunc: validate.DevTestVirtualNetworkUsagePermissionType(), }, }, }, @@ -100,7 +100,7 @@ func resourceArmDevTestVirtualNetworkCreateUpdate(d *schema.ResourceData, meta i subscriptionId := meta.(*ArmClient).subscriptionId subnetsRaw := d.Get("subnet").([]interface{}) - subnets := expandDevTestVirtualNetworkSubnets(d, subnetsRaw, subscriptionId, resourceGroup, labName, name) + subnets := expandDevTestVirtualNetworkSubnets(subnetsRaw, subscriptionId, resourceGroup, labName, name) parameters := dtl.VirtualNetwork{ Tags: expandTags(tags), @@ -220,7 +220,7 @@ func validateDevTestVirtualNetworkName() schema.SchemaValidateFunc { "Virtual Network Name can only include alphanumeric characters, underscores, hyphens.") } -func expandDevTestVirtualNetworkSubnets(d *schema.ResourceData, input []interface{}, subscriptionId, resourceGroupName, labName, virtualNetworkName string) *[]dtl.SubnetOverride { +func expandDevTestVirtualNetworkSubnets(input []interface{}, subscriptionId, resourceGroupName, labName, virtualNetworkName string) *[]dtl.SubnetOverride { results := make([]dtl.SubnetOverride, 0) // default found from the Portal name := fmt.Sprintf("%sSubnet", virtualNetworkName) @@ -273,11 +273,3 @@ func flattenDevTestVirtualNetworkSubnets(input *[]dtl.SubnetOverride) []interfac return outputs } - -func validateDevTestVirtualNetworkUsagePermissionType() schema.SchemaValidateFunc { - return validation.StringInSlice([]string{ - string(dtl.Allow), - string(dtl.Default), - string(dtl.Deny), - }, false) -} diff --git a/azurerm/resource_arm_dev_test_windows_virtual_machine.go b/azurerm/resource_arm_dev_test_windows_virtual_machine.go new file mode 100644 index 000000000000..9863cab7bc2a --- /dev/null +++ b/azurerm/resource_arm_dev_test_windows_virtual_machine.go @@ -0,0 +1,295 @@ +package azurerm + +import ( + "fmt" + "log" + + "github.com/Azure/azure-sdk-for-go/services/devtestlabs/mgmt/2016-05-15/dtl" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmDevTestWindowsVirtualMachine() *schema.Resource { + return &schema.Resource{ + Create: resourceArmDevTestWindowsVirtualMachineCreateUpdate, + Read: resourceArmDevTestWindowsVirtualMachineRead, + Update: resourceArmDevTestWindowsVirtualMachineCreateUpdate, + Delete: resourceArmDevTestWindowsVirtualMachineDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.DevTestVirtualMachineName(15), + }, + + "lab_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.DevTestLabName(), + }, + + // There's a bug in the Azure API where this is returned in lower-case + // BUG: https://github.com/Azure/azure-rest-api-specs/issues/3964 + "resource_group_name": resourceGroupNameDiffSuppressSchema(), + + "location": locationSchema(), + + "size": { + Type: schema.TypeString, + Required: true, + // since this isn't returned from the API + ForceNew: true, + }, + + "username": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "password": { + Type: schema.TypeString, + Required: true, + // since this isn't returned from the API + ForceNew: true, + }, + + "storage_type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + "Standard", + "Premium", + }, false), + }, + + "lab_subnet_name": { + Type: schema.TypeString, + Required: true, + // since this isn't returned from the API + ForceNew: true, + }, + + "lab_virtual_network_id": { + Type: schema.TypeString, + Required: true, + // since this isn't returned from the API + ForceNew: true, + }, + + "allow_claim": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + + "disallow_public_ip_address": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + + "gallery_image_reference": azure.SchemaDevTestVirtualMachineGalleryImageReference(), + + "inbound_nat_rule": azure.SchemaDevTestVirtualMachineInboundNatRule(), + + "notes": { + Type: schema.TypeString, + Optional: true, + }, + + "tags": tagsSchema(), + + "fqdn": { + Type: schema.TypeString, + Computed: true, + }, + + "unique_identifier": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceArmDevTestWindowsVirtualMachineCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).devTestVirtualMachinesClient + ctx := meta.(*ArmClient).StopContext + + log.Printf("[INFO] preparing arguments for DevTest Windows Virtual Machine creation") + + name := d.Get("name").(string) + labName := d.Get("lab_name").(string) + resourceGroup := d.Get("resource_group_name").(string) + tags := d.Get("tags").(map[string]interface{}) + + allowClaim := d.Get("allow_claim").(bool) + disallowPublicIPAddress := d.Get("disallow_public_ip_address").(bool) + labSubnetName := d.Get("lab_subnet_name").(string) + labVirtualNetworkId := d.Get("lab_virtual_network_id").(string) + location := azureRMNormalizeLocation(d.Get("location").(string)) + notes := d.Get("notes").(string) + password := d.Get("password").(string) + size := d.Get("size").(string) + storageType := d.Get("storage_type").(string) + username := d.Get("username").(string) + + galleryImageReferenceRaw := d.Get("gallery_image_reference").([]interface{}) + galleryImageReference := azure.ExpandDevTestLabVirtualMachineGalleryImageReference(galleryImageReferenceRaw, "Windows") + + natRulesRaw := d.Get("inbound_nat_rule").(*schema.Set) + natRules := azure.ExpandDevTestLabVirtualMachineNatRules(natRulesRaw) + + if len(natRules) > 0 && !disallowPublicIPAddress { + return fmt.Errorf("If `inbound_nat_rule` is specified then `disallow_public_ip_address` must be set to true.") + } + + nic := dtl.NetworkInterfaceProperties{} + if disallowPublicIPAddress { + nic.SharedPublicIPAddressConfiguration = &dtl.SharedPublicIPAddressConfiguration{ + InboundNatRules: &natRules, + } + } + + parameters := dtl.LabVirtualMachine{ + Location: utils.String(location), + LabVirtualMachineProperties: &dtl.LabVirtualMachineProperties{ + AllowClaim: utils.Bool(allowClaim), + IsAuthenticationWithSSHKey: utils.Bool(false), + DisallowPublicIPAddress: utils.Bool(disallowPublicIPAddress), + GalleryImageReference: galleryImageReference, + LabSubnetName: utils.String(labSubnetName), + LabVirtualNetworkID: utils.String(labVirtualNetworkId), + NetworkInterface: &nic, + OsType: utils.String("Windows"), + Notes: utils.String(notes), + Password: utils.String(password), + Size: utils.String(size), + StorageType: utils.String(storageType), + UserName: utils.String(username), + }, + Tags: expandTags(tags), + } + + future, err := client.CreateOrUpdate(ctx, resourceGroup, labName, name, parameters) + if err != nil { + return fmt.Errorf("Error creating/updating DevTest Windows Virtual Machine %q (Lab %q / Resource Group %q): %+v", name, labName, resourceGroup, err) + } + + err = future.WaitForCompletionRef(ctx, client.Client) + if err != nil { + return fmt.Errorf("Error waiting for creation/update of DevTest Windows Virtual Machine %q (Lab %q / Resource Group %q): %+v", name, labName, resourceGroup, err) + } + + read, err := client.Get(ctx, resourceGroup, labName, name, "") + if err != nil { + return fmt.Errorf("Error retrieving DevTest Windows Virtual Machine %q (Lab %q / Resource Group %q): %+v", name, labName, resourceGroup, err) + } + + if read.ID == nil { + return fmt.Errorf("Cannot read DevTest Windows Virtual Machine %q (Lab %q / Resource Group %q) ID", name, labName, resourceGroup) + } + + d.SetId(*read.ID) + + return resourceArmDevTestWindowsVirtualMachineRead(d, meta) +} + +func resourceArmDevTestWindowsVirtualMachineRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).devTestVirtualMachinesClient + ctx := meta.(*ArmClient).StopContext + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + resourceGroup := id.ResourceGroup + labName := id.Path["labs"] + name := id.Path["virtualmachines"] + + read, err := client.Get(ctx, resourceGroup, labName, name, "") + if err != nil { + if utils.ResponseWasNotFound(read.Response) { + log.Printf("[DEBUG] DevTest Windows Virtual Machine %q was not found in Lab %q / Resource Group %q - removing from state!", name, labName, resourceGroup) + d.SetId("") + return nil + } + + return fmt.Errorf("Error making Read request on DevTest Windows Virtual Machine %q (Lab %q / Resource Group %q): %+v", name, labName, resourceGroup, err) + } + + d.Set("name", read.Name) + d.Set("lab_name", labName) + d.Set("resource_group_name", resourceGroup) + if location := read.Location; location != nil { + d.Set("location", azureRMNormalizeLocation(*location)) + } + + if props := read.LabVirtualMachineProperties; props != nil { + d.Set("allow_claim", props.AllowClaim) + d.Set("disallow_public_ip_address", props.DisallowPublicIPAddress) + d.Set("notes", props.Notes) + d.Set("size", props.Size) + d.Set("storage_type", props.StorageType) + d.Set("username", props.UserName) + + flattenedImage := azure.FlattenDevTestVirtualMachineGalleryImage(props.GalleryImageReference) + if err := d.Set("gallery_image_reference", flattenedImage); err != nil { + return fmt.Errorf("Error flattening `gallery_image_reference`: %+v", err) + } + + // Computed fields + d.Set("fqdn", props.Fqdn) + d.Set("unique_identifier", props.UniqueIdentifier) + } + + flattenAndSetTags(d, read.Tags) + + return nil +} + +func resourceArmDevTestWindowsVirtualMachineDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).devTestVirtualMachinesClient + ctx := meta.(*ArmClient).StopContext + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + resourceGroup := id.ResourceGroup + labName := id.Path["labs"] + name := id.Path["virtualmachines"] + + read, err := client.Get(ctx, resourceGroup, labName, name, "") + if err != nil { + if utils.ResponseWasNotFound(read.Response) { + // deleted outside of TF + log.Printf("[DEBUG] DevTest Windows Virtual Machine %q was not found in Lab %q / Resource Group %q - assuming removed!", name, labName, resourceGroup) + return nil + } + + return fmt.Errorf("Error retrieving DevTest Windows Virtual Machine %q (Lab %q / Resource Group %q): %+v", name, labName, resourceGroup, err) + } + + future, err := client.Delete(ctx, resourceGroup, labName, name) + if err != nil { + return fmt.Errorf("Error deleting DevTest Windows Virtual Machine %q (Lab %q / Resource Group %q): %+v", name, labName, resourceGroup, err) + } + + err = future.WaitForCompletionRef(ctx, client.Client) + if err != nil { + return fmt.Errorf("Error waiting for the deletion of DevTest Windows Virtual Machine %q (Lab %q / Resource Group %q): %+v", name, labName, resourceGroup, err) + } + + return err +} diff --git a/azurerm/resource_arm_dev_test_windows_virtual_machine_test.go b/azurerm/resource_arm_dev_test_windows_virtual_machine_test.go new file mode 100644 index 000000000000..0f3170f9bb80 --- /dev/null +++ b/azurerm/resource_arm_dev_test_windows_virtual_machine_test.go @@ -0,0 +1,291 @@ +package azurerm + +import ( + "fmt" + "net/http" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAzureRMDevTestVirtualMachine_basic(t *testing.T) { + resourceName := "azurerm_dev_test_windows_virtual_machine.test" + rInt := acctest.RandIntRange(11111, 99999) + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMDevTestWindowsVirtualMachineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMDevTestWindowsVirtualMachine_basic(rInt, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMDevTestWindowsVirtualMachineExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "gallery_image_reference.0.publisher", "MicrosoftWindowsServer"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + // not returned from the API + "lab_subnet_name", + "lab_virtual_network_id", + "password", + }, + }, + }, + }) +} + +func TestAccAzureRMDevTestWindowsVirtualMachine_inboundNatRules(t *testing.T) { + resourceName := "azurerm_dev_test_windows_virtual_machine.test" + rInt := acctest.RandIntRange(11111, 99999) + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMDevTestWindowsVirtualMachineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMDevTestWindowsVirtualMachine_inboundNatRules(rInt, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMDevTestWindowsVirtualMachineExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "disallow_public_ip_address", "true"), + resource.TestCheckResourceAttr(resourceName, "gallery_image_reference.0.publisher", "MicrosoftWindowsServer"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.Acceptance", "Test"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + // not returned from the API + "inbound_nat_rule", + "lab_subnet_name", + "lab_virtual_network_id", + "password", + }, + }, + }, + }) +} + +func TestAccAzureRMDevTestWindowsVirtualMachine_updateStorage(t *testing.T) { + resourceName := "azurerm_dev_test_windows_virtual_machine.test" + rInt := acctest.RandIntRange(11111, 99999) + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMDevTestWindowsVirtualMachineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMDevTestWindowsVirtualMachine_storage(rInt, location, "Standard"), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMDevTestWindowsVirtualMachineExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "gallery_image_reference.0.publisher", "MicrosoftWindowsServer"), + resource.TestCheckResourceAttr(resourceName, "storage_type", "Standard"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + { + Config: testAccAzureRMDevTestWindowsVirtualMachine_storage(rInt, location, "Premium"), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMDevTestWindowsVirtualMachineExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "gallery_image_reference.0.publisher", "MicrosoftWindowsServer"), + resource.TestCheckResourceAttr(resourceName, "storage_type", "Premium"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + }, + }) +} + +func testCheckAzureRMDevTestWindowsVirtualMachineExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // Ensure we have enough information in state to look up in API + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + virtualMachineName := rs.Primary.Attributes["name"] + labName := rs.Primary.Attributes["lab_name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + conn := testAccProvider.Meta().(*ArmClient).devTestVirtualMachinesClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + resp, err := conn.Get(ctx, resourceGroup, labName, virtualMachineName, "") + if err != nil { + return fmt.Errorf("Bad: Get devTestVirtualMachinesClient: %+v", err) + } + + if resp.StatusCode == http.StatusNotFound { + return fmt.Errorf("Bad: DevTest Windows Virtual Machine %q (Lab %q / Resource Group: %q) does not exist", virtualMachineName, labName, resourceGroup) + } + + return nil + } +} + +func testCheckAzureRMDevTestWindowsVirtualMachineDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*ArmClient).devTestVirtualMachinesClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_dev_test_windows_virtual_machine" { + continue + } + + virtualMachineName := rs.Primary.Attributes["name"] + labName := rs.Primary.Attributes["lab_name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + resp, err := conn.Get(ctx, resourceGroup, labName, virtualMachineName, "") + + if err != nil { + if resp.StatusCode == http.StatusNotFound { + return nil + } + + return err + } + + return fmt.Errorf("DevTest Windows Virtual Machine still exists:\n%#v", resp) + } + + return nil +} + +func testAccAzureRMDevTestWindowsVirtualMachine_basic(rInt int, location string) string { + template := testAccAzureRMDevTestWindowsVirtualMachine_template(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_dev_test_windows_virtual_machine" "test" { + name = "acctestvm%d" + lab_name = "${azurerm_dev_test_lab.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + size = "Standard_F2" + username = "acct5stU5er" + password = "Pa$$w0rd1234!" + lab_virtual_network_id = "${azurerm_dev_test_virtual_network.test.id}" + lab_subnet_name = "${azurerm_dev_test_virtual_network.test.subnet.0.name}" + storage_type = "Standard" + + gallery_image_reference { + offer = "WindowsServer" + publisher = "MicrosoftWindowsServer" + sku = "2012-Datacenter" + version = "latest" + } +} +`, template, rInt) +} + +func testAccAzureRMDevTestWindowsVirtualMachine_inboundNatRules(rInt int, location string) string { + template := testAccAzureRMDevTestWindowsVirtualMachine_template(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_dev_test_windows_virtual_machine" "test" { + name = "acctestvm%d" + lab_name = "${azurerm_dev_test_lab.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + size = "Standard_F2" + username = "acct5stU5er" + password = "Pa$$w0rd1234!" + disallow_public_ip_address = true + lab_virtual_network_id = "${azurerm_dev_test_virtual_network.test.id}" + lab_subnet_name = "${azurerm_dev_test_virtual_network.test.subnet.0.name}" + storage_type = "Standard" + + gallery_image_reference { + offer = "WindowsServer" + publisher = "MicrosoftWindowsServer" + sku = "2012-Datacenter" + version = "latest" + } + + inbound_nat_rule { + protocol = "Tcp" + backend_port = 22 + } + + inbound_nat_rule { + protocol = "Tcp" + backend_port = 3389 + } + + tags { + "Acceptance" = "Test" + } +} +`, template, rInt) +} + +func testAccAzureRMDevTestWindowsVirtualMachine_storage(rInt int, location, storageType string) string { + template := testAccAzureRMDevTestWindowsVirtualMachine_template(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_dev_test_windows_virtual_machine" "test" { + name = "acctestvm%d" + lab_name = "${azurerm_dev_test_lab.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + size = "Standard_B1ms" + username = "acct5stU5er" + password = "Pa$$w0rd1234!" + lab_virtual_network_id = "${azurerm_dev_test_virtual_network.test.id}" + lab_subnet_name = "${azurerm_dev_test_virtual_network.test.subnet.0.name}" + storage_type = "%s" + + gallery_image_reference { + offer = "WindowsServer" + publisher = "MicrosoftWindowsServer" + sku = "2012-Datacenter" + version = "latest" + } +} +`, template, rInt, storageType) +} + +func testAccAzureRMDevTestWindowsVirtualMachine_template(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_dev_test_lab" "test" { + name = "acctestdtl%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" +} + +resource "azurerm_dev_test_virtual_network" "test" { + name = "acctestdtvn%d" + lab_name = "${azurerm_dev_test_lab.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + + subnet { + use_public_ip_address = "Allow" + use_in_virtual_machine_creation = "Allow" + } +} +`, rInt, location, rInt, rInt) +} diff --git a/website/azurerm.erb b/website/azurerm.erb index 01bcc347ec48..dc197c730ab7 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -553,10 +553,18 @@ azurerm_dev_test_lab + > + azurerm_dev_test_linux_virtual_machine + + > azurerm_dev_test_virtual_network + > + azurerm_dev_test_windows_virtual_machine + + diff --git a/website/docs/r/dev_test_linux_virtual_machine.html.markdown b/website/docs/r/dev_test_linux_virtual_machine.html.markdown new file mode 100644 index 000000000000..a0d05f773cc6 --- /dev/null +++ b/website/docs/r/dev_test_linux_virtual_machine.html.markdown @@ -0,0 +1,156 @@ +--- +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_dev_test_linux_virtual_machine" +sidebar_current: "docs-azurerm-resource-dev-test-linux-virtual-machine" +description: |- + Manages a Linux Virtual Machine within a Dev Test Lab. +--- + +# azurerm_dev_test_linux_virtual_machine + +Manages a Linux Virtual Machine within a Dev Test Lab. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "test" { + name = "example-resources" + location = "West US" +} + +resource "azurerm_dev_test_lab" "test" { + name = "example-devtestlab" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + + tags { + "Sydney" = "Australia" + } +} + + +resource "azurerm_dev_test_virtual_network" "test" { + name = "example-network" + lab_name = "${azurerm_dev_test_lab.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + + subnet { + use_public_ip_address = "Allow" + use_in_virtual_machine_creation = "Allow" + } +} + +resource "azurerm_dev_test_linux_virtual_machine" "test" { + name = "example-vm03" + lab_name = "${azurerm_dev_test_lab.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + size = "Standard_DS2" + username = "exampleuser99" + ssh_key = "${file("~/.ssh/id_rsa.pub")}" + lab_virtual_network_id = "${azurerm_dev_test_virtual_network.test.id}" + lab_subnet_name = "${azurerm_dev_test_virtual_network.test.subnet.0.name}" + storage_type = "Premium" + notes = "Some notes about this Virtual Machine." + + gallery_image_reference { + offer = "UbuntuServer" + publisher = "Canonical" + sku = "18.04-LTS" + version = "latest" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) Specifies the name of the Dev Test Machine. Changing this forces a new resource to be created. + +-> **NOTE:** The validation requirements for the Name change based on the `os_type` used in this Virtual Machine. For a Linux VM the name must be between 1-62 characters, and for a Windows VM the name must be between 1-15 characters. It must begin and end with a letter or number, and cannot be all numbers. + +* `lab_name` - (Required) Specifies the name of the Dev Test Lab in which the Virtual Machine should be created. Changing this forces a new resource to be created. + +* `resource_group_name` - (Required) The name of the resource group in which the Dev Test Lab resource exists. Changing this forces a new resource to be created. + +* `location` - (Required) Specifies the supported Azure location where the Dev Test Lab exists. Changing this forces a new resource to be created. + +* `gallery_image_reference` - (Required) A `gallery_image_reference` block as defined below. + +* `lab_subnet_name` - (Required) The name of a Subnet within the Dev Test Virtual Network where this machine should exist. Changing this forces a new resource to be created. + +* `lab_virtual_network_id` - (Required) The ID of the Dev Test Virtual Network where this Virtual Machine should be created. Changing this forces a new resource to be created. + +* `size` - (Required) The Machine Size to use for this Virtual Machine, such as `Standard_F2`. Changing this forces a new resource to be created. + +* `storage_type` - (Required) The type of Storage to use on this Virtual Machine. Possible values are `Standard` and `Premium`. + +* `username` - (Required) The Username associated with the local administrator on this Virtual Machine. Changing this forces a new resource to be created. + +--- + +* `allow_claim` - (Optional) Can this Virtual Machine be claimed by users? Defaults to `true`. + +* `disallow_public_ip_address` - (Optional) Should the Virtual Machine be created without a Public IP Address? Changing this forces a new resource to be created. + +* `inbound_nat_rule` - (Optional) One or more `inbound_nat_rule` blocks as defined below. Changing this forces a new resource to be created. + +-> **NOTE:** If any `inbound_nat_rule` blocks are specified then `disallow_public_ip_address` must be set to `true`. + +* `notes` - (Optional) Any notes about the Virtual Machine. + +* `password` - (Optional) The Password associated with the `username` used to login to this Virtual Machine. Changing this forces a new resource to be created. + +* `ssh_key` - (Optional) The SSH Key associated with the `username` used to login to this Virtual Machine. Changing this forces a new resource to be created. + +-> **NOTE:** One or either `password` or `ssh_key` must be specified. + +* `tags` - (Optional) A mapping of tags to assign to the resource. + +--- + +A `gallery_image_reference` block supports the following: + +* `offer` - (Required) The Offer of the Gallery Image. Changing this forces a new resource to be created. + +* `publisher` - (Required) The Publisher of the Gallery Image. Changing this forces a new resource to be created. + +* `sku` - (Required) The SKU of the Gallery Image. Changing this forces a new resource to be created. + +* `version` - (Required) The Version of the Gallery Image. Changing this forces a new resource to be created. + +--- + +A `inbound_nat_rule` block supports the following: + +* `protocol` - (Required) The Protocol used for this NAT Rule. Possible values are `Tcp` and `Udp`. Changing this forces a new resource to be created. + +* `backend_port` - (Required) The Backend Port associated with this NAT Rule. Changing this forces a new resource to be created. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the Virtual Machine. + +* `fqdn` - The FQDN of the Virtual Machine. + +* `inbound_nat_rule` - One or more `inbound_nat_rule` blocks as defined below. + +* `unique_identifier` - The unique immutable identifier of the Virtual Machine. + +--- + +A `inbound_nat_rule` block exports the following: + +* `frontend_port` - The frontend port associated with this Inbound NAT Rule. + +## Import + +Dev Test Linux Virtual Machines can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_dev_test_linux_virtual_machine.machine1 /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.DevTestLab/labs/lab1/virtualmachines/machine1 +``` diff --git a/website/docs/r/dev_test_virtual_network.html.markdown b/website/docs/r/dev_test_virtual_network.html.markdown index 632e6accf3b8..0c732df3ed7e 100644 --- a/website/docs/r/dev_test_virtual_network.html.markdown +++ b/website/docs/r/dev_test_virtual_network.html.markdown @@ -34,6 +34,11 @@ resource "azurerm_dev_test_virtual_network" "test" { lab_name = "${azurerm_dev_test_lab.test.name}" resource_group_name = "${azurerm_resource_group.test.name}" location = "${azurerm_resource_group.test.location}" + + subnet { + use_public_ip_address = "Allow" + use_in_virtual_machine_creation = "Allow" + } } ``` @@ -41,7 +46,7 @@ resource "azurerm_dev_test_virtual_network" "test" { The following arguments are supported: -* `name` - (Required) Specifies the name of the Dev Test Lab. Changing this forces a new resource to be created. +* `name` - (Required) Specifies the name of the Dev Test Virtual Network. Changing this forces a new resource to be created. * `lab_name` - (Required) Specifies the name of the Dev Test Lab in which the Virtual Network should be created. Changing this forces a new resource to be created. diff --git a/website/docs/r/dev_test_windows_virtual_machine.html.markdown b/website/docs/r/dev_test_windows_virtual_machine.html.markdown new file mode 100644 index 000000000000..48fce2eaa75c --- /dev/null +++ b/website/docs/r/dev_test_windows_virtual_machine.html.markdown @@ -0,0 +1,151 @@ +--- +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_dev_test_windows_virtual_machine" +sidebar_current: "docs-azurerm-resource-dev-test-windows-virtual-machine" +description: |- + Manages a Windows Virtual Machine within a Dev Test Lab. +--- + +# azurerm_dev_test_windows_virtual_machine + +Manages a Windows Virtual Machine within a Dev Test Lab. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "test" { + name = "example-resources" + location = "West US" +} + +resource "azurerm_dev_test_lab" "test" { + name = "example-devtestlab" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + + tags { + "Sydney" = "Australia" + } +} + +resource "azurerm_dev_test_virtual_network" "test" { + name = "example-network" + lab_name = "${azurerm_dev_test_lab.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + + subnet { + use_public_ip_address = "Allow" + use_in_virtual_machine_creation = "Allow" + } +} + +resource "azurerm_dev_test_windows_virtual_machine" "test" { + name = "example-vm03" + lab_name = "${azurerm_dev_test_lab.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + size = "Standard_DS2" + username = "exampleuser99" + password = "Pa$$w0rd1234!" + lab_virtual_network_id = "${azurerm_dev_test_virtual_network.test.id}" + lab_subnet_name = "${azurerm_dev_test_virtual_network.test.subnet.0.name}" + storage_type = "Premium" + notes = "Some notes about this Virtual Machine." + + gallery_image_reference { + offer = "UbuntuServer" + publisher = "Canonical" + sku = "18.04-LTS" + version = "latest" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) Specifies the name of the Dev Test Machine. Changing this forces a new resource to be created. + +-> **NOTE:** The validation requirements for the Name change based on the `os_type` used in this Virtual Machine. For a Linux VM the name must be between 1-62 characters, and for a Windows VM the name must be between 1-15 characters. It must begin and end with a letter or number, and cannot be all numbers. + +* `lab_name` - (Required) Specifies the name of the Dev Test Lab in which the Virtual Machine should be created. Changing this forces a new resource to be created. + +* `resource_group_name` - (Required) The name of the resource group in which the Dev Test Lab resource exists. Changing this forces a new resource to be created. + +* `location` - (Required) Specifies the supported Azure location where the Dev Test Lab exists. Changing this forces a new resource to be created. + +* `gallery_image_reference` - (Required) A `gallery_image_reference` block as defined below. + +* `lab_subnet_name` - (Required) The name of a Subnet within the Dev Test Virtual Network where this machine should exist. Changing this forces a new resource to be created. + +* `lab_virtual_network_id` - (Required) The ID of the Dev Test Virtual Network where this Virtual Machine should be created. Changing this forces a new resource to be created. + +* `password` - (Required) The Password associated with the `username` used to login to this Virtual Machine. Changing this forces a new resource to be created. + +* `size` - (Required) The Machine Size to use for this Virtual Machine, such as `Standard_F2`. Changing this forces a new resource to be created. + +* `storage_type` - (Required) The type of Storage to use on this Virtual Machine. Possible values are `Standard` and `Premium`. + +* `username` - (Required) The Username associated with the local administrator on this Virtual Machine. Changing this forces a new resource to be created. + +--- + +* `allow_claim` - (Optional) Can this Virtual Machine be claimed by users? Defaults to `true`. + +* `disallow_public_ip_address` - (Optional) Should the Virtual Machine be created without a Public IP Address? Changing this forces a new resource to be created. + +* `inbound_nat_rule` - (Optional) One or more `inbound_nat_rule` blocks as defined below. Changing this forces a new resource to be created. + +-> **NOTE:** If any `inbound_nat_rule` blocks are specified then `disallow_public_ip_address` must be set to `true`. + +* `notes` - (Optional) Any notes about the Virtual Machine. + +* `tags` - (Optional) A mapping of tags to assign to the resource. + +--- + +A `gallery_image_reference` block supports the following: + +* `offer` - (Required) The Offer of the Gallery Image. Changing this forces a new resource to be created. + +* `publisher` - (Required) The Publisher of the Gallery Image. Changing this forces a new resource to be created. + +* `sku` - (Required) The SKU of the Gallery Image. Changing this forces a new resource to be created. + +* `version` - (Required) The Version of the Gallery Image. Changing this forces a new resource to be created. + +--- + +A `inbound_nat_rule` block supports the following: + +* `protocol` - (Required) The Protocol used for this NAT Rule. Possible values are `Tcp` and `Udp`. Changing this forces a new resource to be created. + +* `backend_port` - (Required) The Backend Port associated with this NAT Rule. Changing this forces a new resource to be created. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the Virtual Machine. + +* `fqdn` - The FQDN of the Virtual Machine. + +* `inbound_nat_rule` - One or more `inbound_nat_rule` blocks as defined below. + +* `unique_identifier` - The unique immutable identifier of the Virtual Machine. + +--- + +A `inbound_nat_rule` block exports the following: + +* `frontend_port` - The frontend port associated with this Inbound NAT Rule. + +## Import + +Dev Test Windows Virtual Machines can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_dev_test_windows_virtual_machine.machine1 /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.DevTestLab/labs/lab1/virtualmachines/machine1 +```