From a2aeb9f79d2572961fc554c9c0b2725c5cc11abd Mon Sep 17 00:00:00 2001 From: Sander van Harmelen Date: Thu, 28 May 2015 00:50:45 +0200 Subject: [PATCH] Adding acceptance tests together with a few minor tweaks --- builtin/providers/azure/config.go | 30 +- builtin/providers/azure/provider.go | 16 +- builtin/providers/azure/provider_test.go | 12 +- ...re_disk.go => resource_azure_data_disk.go} | 85 +++- .../azure/resource_azure_data_disk_test.go | 238 +++++++++ .../azure/resource_azure_disk_test.go | 1 - .../azure/resource_azure_instance.go | 43 +- .../azure/resource_azure_instance_test.go | 456 ++++++++++++++++++ .../azure/resource_azure_security_group.go | 5 - .../resource_azure_security_group_test.go | 272 +++++++++++ .../azure/resource_azure_virtual_network.go | 148 +++++- .../resource_azure_virtual_network_test.go | 295 +++++++++++ 12 files changed, 1531 insertions(+), 70 deletions(-) rename builtin/providers/azure/{resource_azure_disk.go => resource_azure_data_disk.go} (81%) create mode 100644 builtin/providers/azure/resource_azure_data_disk_test.go delete mode 100644 builtin/providers/azure/resource_azure_disk_test.go diff --git a/builtin/providers/azure/config.go b/builtin/providers/azure/config.go index f9c7cea42..b62ab435e 100644 --- a/builtin/providers/azure/config.go +++ b/builtin/providers/azure/config.go @@ -12,18 +12,34 @@ import ( type Config struct { SettingsFile string SubscriptionID string + Certificate []byte + ManagementURL string } -// NewClient returns a new Azure management client +// NewClient returns a new Azure management client which is created +// using different functions depending on the supplied settings func (c *Config) NewClient() (management.Client, error) { - if _, err := os.Stat(c.SettingsFile); os.IsNotExist(err) { - return nil, fmt.Errorf("Publish Settings file %q does not exist!", c.SettingsFile) + if c.SettingsFile != "" { + if _, err := os.Stat(c.SettingsFile); os.IsNotExist(err) { + return nil, fmt.Errorf("Publish Settings file %q does not exist!", c.SettingsFile) + } + + return management.ClientFromPublishSettingsFile(c.SettingsFile, c.SubscriptionID) } - mc, err := management.ClientFromPublishSettingsFile(c.SettingsFile, c.SubscriptionID) - if err != nil { - return nil, fmt.Errorf("Error creating management client: %s", err) + if c.ManagementURL != "" { + return management.NewClientFromConfig( + c.SubscriptionID, + c.Certificate, + management.ClientConfig{ManagementURL: c.ManagementURL}, + ) } - return mc, nil + if c.SubscriptionID != "" && len(c.Certificate) > 0 { + return management.NewClient(c.SubscriptionID, c.Certificate) + } + + return nil, fmt.Errorf( + "Insufficient configuration data. Please specify either a 'settings_file'\n" + + "or both a 'subscription_id' and 'certificate' with an optional 'management_url'.") } diff --git a/builtin/providers/azure/provider.go b/builtin/providers/azure/provider.go index 8fec6f012..1bf8233fa 100644 --- a/builtin/providers/azure/provider.go +++ b/builtin/providers/azure/provider.go @@ -11,7 +11,7 @@ func Provider() terraform.ResourceProvider { Schema: map[string]*schema.Schema{ "settings_file": &schema.Schema{ Type: schema.TypeString, - Required: true, + Optional: true, DefaultFunc: schema.EnvDefaultFunc("AZURE_SETTINGS_FILE", nil), }, @@ -20,6 +20,18 @@ func Provider() terraform.ResourceProvider { Optional: true, DefaultFunc: schema.EnvDefaultFunc("AZURE_SUBSCRIPTION_ID", ""), }, + + "certificate": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("AZURE_CERTIFICATE", ""), + }, + + "management_url": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("AZURE_MANAGEMENT_URL", ""), + }, }, ResourcesMap: map[string]*schema.Resource{ @@ -37,6 +49,8 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) { config := Config{ SettingsFile: d.Get("settings_file").(string), SubscriptionID: d.Get("subscription_id").(string), + Certificate: []byte(d.Get("certificate").(string)), + ManagementURL: d.Get("management_url").(string), } return config.NewClient() diff --git a/builtin/providers/azure/provider_test.go b/builtin/providers/azure/provider_test.go index 5606dcdad..5403df3ef 100644 --- a/builtin/providers/azure/provider_test.go +++ b/builtin/providers/azure/provider_test.go @@ -30,6 +30,16 @@ func TestProvider_impl(t *testing.T) { func testAccPreCheck(t *testing.T) { if v := os.Getenv("AZURE_SETTINGS_FILE"); v == "" { - t.Fatal("AZURE_SETTINGS_FILE must be set for acceptance tests") + subscriptionID := os.Getenv("AZURE_SUBSCRIPTION_ID") + certificate := os.Getenv("AZURE_CERTIFICATE") + + if subscriptionID == "" || certificate == "" { + t.Fatal("either AZURE_SETTINGS_FILE, or AZURE_SUBSCRIPTION_ID " + + "and AZURE_CERTIFICATE must be set for acceptance tests") + } + } + + if v := os.Getenv("AZURE_STORAGE"); v == "" { + t.Fatal("AZURE_STORAGE must be set for acceptance tests") } } diff --git a/builtin/providers/azure/resource_azure_disk.go b/builtin/providers/azure/resource_azure_data_disk.go similarity index 81% rename from builtin/providers/azure/resource_azure_disk.go rename to builtin/providers/azure/resource_azure_data_disk.go index 8f9e1b4c9..a306e96f0 100644 --- a/builtin/providers/azure/resource_azure_disk.go +++ b/builtin/providers/azure/resource_azure_data_disk.go @@ -3,6 +3,7 @@ package azure import ( "fmt" "log" + "time" "github.com/hashicorp/terraform/helper/schema" "github.com/svanharmelen/azure-sdk-for-go/management" @@ -26,6 +27,13 @@ func resourceAzureDataDisk() *schema.Resource { ForceNew: true, }, + "label": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "lun": &schema.Schema{ Type: schema.TypeInt, Required: true, @@ -79,7 +87,13 @@ func resourceAzureDataDiskCreate(d *schema.ResourceData, meta interface{}) error lun := d.Get("lun").(int) vm := d.Get("virtual_machine").(string) + label := d.Get("label").(string) + if label == "" { + label = fmt.Sprintf("%s-%d", vm, lun) + } + p := virtualmachinedisk.CreateDataDiskParameters{ + DiskLabel: label, Lun: lun, LogicalDiskSizeInGB: d.Get("size").(int), HostCaching: hostCaching(d), @@ -123,10 +137,15 @@ func resourceAzureDataDiskRead(d *schema.ResourceData, meta interface{}) error { log.Printf("[DEBUG] Retrieving data disk: %s", d.Id()) datadisk, err := virtualmachinedisk.NewClient(mc).GetDataDisk(vm, vm, vm, lun) if err != nil { + if management.IsResourceNotFoundError(err) { + d.SetId("") + return nil + } return fmt.Errorf("Error retrieving data disk %s: %s", d.Id(), err) } d.Set("name", datadisk.DiskName) + d.Set("label", datadisk.DiskLabel) d.Set("lun", datadisk.Lun) d.Set("size", datadisk.LogicalDiskSizeInGB) d.Set("caching", datadisk.HostCaching) @@ -148,31 +167,13 @@ func resourceAzureDataDiskUpdate(d *schema.ResourceData, meta interface{}) error lun := d.Get("lun").(int) vm := d.Get("virtual_machine").(string) - if d.HasChange("size") { - p := virtualmachinedisk.UpdateDiskParameters{ - Name: d.Id(), - } + if d.HasChange("lun") || d.HasChange("size") || d.HasChange("virtual_machine") { + olun, _ := d.GetChange("lun") + ovm, _ := d.GetChange("virtual_machine") - if d.HasChange("size") { - p.ResizedSizeInGB = d.Get("size").(int) - } - - log.Printf("[DEBUG] Updating disk: %s", d.Id()) - req, err := virtualmachinedisk.NewClient(mc).UpdateDisk(d.Id(), p) - if err != nil { - return fmt.Errorf("Error updating disk %s: %s", d.Id(), err) - } - - // Wait until the disk is updated - if err := mc.WaitForOperation(req, nil); err != nil { - return fmt.Errorf( - "Error waiting for disk %s to be updated: %s", d.Id(), err) - } - } - - if d.HasChange("virtual_machine") { log.Printf("[DEBUG] Detaching data disk: %s", d.Id()) - req, err := virtualmachinedisk.NewClient(mc).DeleteDataDisk(vm, vm, vm, lun, false) + req, err := virtualmachinedisk.NewClient(mc). + DeleteDataDisk(ovm.(string), ovm.(string), ovm.(string), olun.(int), false) if err != nil { return fmt.Errorf("Error detaching data disk %s: %s", d.Id(), err) } @@ -183,6 +184,42 @@ func resourceAzureDataDiskUpdate(d *schema.ResourceData, meta interface{}) error "Error waiting for data disk %s to be detached: %s", d.Id(), err) } + log.Printf("[DEBUG] Verifying data disk %s is properly detached...", d.Id()) + for i := 0; i < 6; i++ { + disk, err := virtualmachinedisk.NewClient(mc).GetDisk(d.Id()) + if err != nil { + return fmt.Errorf("Error retrieving disk %s: %s", d.Id(), err) + } + + // Check if the disk is really detached + if disk.AttachedTo.RoleName == "" { + break + } + + // If not, wait 30 seconds and try it again... + time.Sleep(time.Duration(30 * time.Second)) + } + + if d.HasChange("size") { + p := virtualmachinedisk.UpdateDiskParameters{ + DiskName: d.Id(), + Label: d.Get("label").(string), + ResizedSizeInGB: d.Get("size").(int), + } + + log.Printf("[DEBUG] Updating disk: %s", d.Id()) + req, err := virtualmachinedisk.NewClient(mc).UpdateDisk(d.Id(), p) + if err != nil { + return fmt.Errorf("Error updating disk %s: %s", d.Id(), err) + } + + // Wait until the disk is updated + if err := mc.WaitForOperation(req, nil); err != nil { + return fmt.Errorf( + "Error waiting for disk %s to be updated: %s", d.Id(), err) + } + } + p := virtualmachinedisk.CreateDataDiskParameters{ DiskName: d.Id(), Lun: lun, @@ -207,7 +244,7 @@ func resourceAzureDataDiskUpdate(d *schema.ResourceData, meta interface{}) error return nil } - if d.HasChange("caching") || d.HasChange("lun") { + if d.HasChange("caching") { p := virtualmachinedisk.UpdateDataDiskParameters{ DiskName: d.Id(), Lun: lun, diff --git a/builtin/providers/azure/resource_azure_data_disk_test.go b/builtin/providers/azure/resource_azure_data_disk_test.go new file mode 100644 index 000000000..1e1fb3132 --- /dev/null +++ b/builtin/providers/azure/resource_azure_data_disk_test.go @@ -0,0 +1,238 @@ +package azure + +import ( + "fmt" + "os" + "strconv" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/svanharmelen/azure-sdk-for-go/management" + "github.com/svanharmelen/azure-sdk-for-go/management/virtualmachinedisk" +) + +func TestAccAzureDataDisk_basic(t *testing.T) { + var disk virtualmachinedisk.DataDiskResponse + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAzureDataDiskDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAzureDataDisk_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckAzureDataDiskExists( + "azure_data_disk.foo", &disk), + testAccCheckAzureDataDiskAttributes(&disk), + resource.TestCheckResourceAttr( + "azure_data_disk.foo", "label", "terraform-test-0"), + resource.TestCheckResourceAttr( + "azure_data_disk.foo", "size", "10"), + ), + }, + }, + }) +} + +func TestAccAzureDataDisk_update(t *testing.T) { + var disk virtualmachinedisk.DataDiskResponse + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAzureDataDiskDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAzureDataDisk_advanced, + Check: resource.ComposeTestCheckFunc( + testAccCheckAzureDataDiskExists( + "azure_data_disk.foo", &disk), + resource.TestCheckResourceAttr( + "azure_data_disk.foo", "label", "terraform-test1-1"), + resource.TestCheckResourceAttr( + "azure_data_disk.foo", "lun", "1"), + resource.TestCheckResourceAttr( + "azure_data_disk.foo", "size", "10"), + resource.TestCheckResourceAttr( + "azure_data_disk.foo", "caching", "ReadOnly"), + resource.TestCheckResourceAttr( + "azure_data_disk.foo", "virtual_machine", "terraform-test1"), + ), + }, + + resource.TestStep{ + Config: testAccAzureDataDisk_update, + Check: resource.ComposeTestCheckFunc( + testAccCheckAzureDataDiskExists( + "azure_data_disk.foo", &disk), + resource.TestCheckResourceAttr( + "azure_data_disk.foo", "label", "terraform-test1-1"), + resource.TestCheckResourceAttr( + "azure_data_disk.foo", "lun", "2"), + resource.TestCheckResourceAttr( + "azure_data_disk.foo", "size", "20"), + resource.TestCheckResourceAttr( + "azure_data_disk.foo", "caching", "ReadWrite"), + resource.TestCheckResourceAttr( + "azure_data_disk.foo", "virtual_machine", "terraform-test2"), + ), + }, + }, + }) +} + +func testAccCheckAzureDataDiskExists( + n string, + disk *virtualmachinedisk.DataDiskResponse) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Data Disk ID is set") + } + + vm := rs.Primary.Attributes["virtual_machine"] + lun, err := strconv.Atoi(rs.Primary.Attributes["lun"]) + if err != nil { + return err + } + + mc := testAccProvider.Meta().(management.Client) + d, err := virtualmachinedisk.NewClient(mc).GetDataDisk(vm, vm, vm, lun) + if err != nil { + return err + } + + if d.DiskName != rs.Primary.ID { + return fmt.Errorf("Data Disk not found") + } + + *disk = d + + return nil + } +} + +func testAccCheckAzureDataDiskAttributes( + disk *virtualmachinedisk.DataDiskResponse) resource.TestCheckFunc { + return func(s *terraform.State) error { + + if disk.Lun != 0 { + return fmt.Errorf("Bad lun: %d", disk.Lun) + } + + if disk.LogicalDiskSizeInGB != 10 { + return fmt.Errorf("Bad size: %d", disk.LogicalDiskSizeInGB) + } + + if disk.HostCaching != "None" { + return fmt.Errorf("Bad caching: %s", disk.HostCaching) + } + + return nil + } +} + +func testAccCheckAzureDataDiskDestroy(s *terraform.State) error { + mc := testAccProvider.Meta().(management.Client) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azure_data_disk" { + continue + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Disk ID is set") + } + + vm := rs.Primary.Attributes["virtual_machine"] + lun, err := strconv.Atoi(rs.Primary.Attributes["lun"]) + if err != nil { + return err + } + + req, err := virtualmachinedisk.NewClient(mc).DeleteDataDisk(vm, vm, vm, lun, true) + if err != nil { + return fmt.Errorf("Error deleting Data Disk (%s): %s", rs.Primary.ID, err) + } + + // Wait until the data disk is deleted + if err := mc.WaitForOperation(req, nil); err != nil { + return fmt.Errorf( + "Error deleting Data Disk (%s): %s", rs.Primary.ID, err) + } + } + + return nil +} + +var testAccAzureDataDisk_basic = fmt.Sprintf(` +resource "azure_instance" "foo" { + name = "terraform-test" + image = "Ubuntu Server 14.04 LTS" + size = "Basic_A1" + storage = "%s" + location = "West US" + username = "terraform" + password = "Pass!admin123" +} + +resource "azure_data_disk" "foo" { + lun = 0 + size = 10 + storage = "${azure_instance.foo.storage}" + virtual_machine = "${azure_instance.foo.id}" +}`, os.Getenv("AZURE_STORAGE")) + +var testAccAzureDataDisk_advanced = fmt.Sprintf(` +resource "azure_instance" "foo" { + name = "terraform-test1" + image = "Ubuntu Server 14.04 LTS" + size = "Basic_A1" + storage = "%s" + location = "West US" + username = "terraform" + password = "Pass!admin123" +} + +resource "azure_data_disk" "foo" { + lun = 1 + size = 10 + caching = "ReadOnly" + storage = "${azure_instance.foo.storage}" + virtual_machine = "${azure_instance.foo.id}" +}`, os.Getenv("AZURE_STORAGE")) + +var testAccAzureDataDisk_update = fmt.Sprintf(` +resource "azure_instance" "foo" { + name = "terraform-test1" + image = "Ubuntu Server 14.04 LTS" + size = "Basic_A1" + storage = "%s" + location = "West US" + username = "terraform" + password = "Pass!admin123" +} + +resource "azure_instance" "bar" { + name = "terraform-test2" + image = "Ubuntu Server 14.04 LTS" + size = "Basic_A1" + storage = "${azure_instance.foo.storage}" + location = "West US" + username = "terraform" + password = "Pass!admin123" +} + +resource "azure_data_disk" "foo" { + lun = 2 + size = 20 + caching = "ReadWrite" + storage = "${azure_instance.bar.storage}" + virtual_machine = "${azure_instance.bar.id}" +}`, os.Getenv("AZURE_STORAGE")) diff --git a/builtin/providers/azure/resource_azure_disk_test.go b/builtin/providers/azure/resource_azure_disk_test.go deleted file mode 100644 index 6512f735e..000000000 --- a/builtin/providers/azure/resource_azure_disk_test.go +++ /dev/null @@ -1 +0,0 @@ -package azure diff --git a/builtin/providers/azure/resource_azure_instance.go b/builtin/providers/azure/resource_azure_instance.go index 814144ea4..ed34d9fc2 100644 --- a/builtin/providers/azure/resource_azure_instance.go +++ b/builtin/providers/azure/resource_azure_instance.go @@ -5,6 +5,7 @@ import ( "encoding/base64" "fmt" "log" + "strings" "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" @@ -179,6 +180,7 @@ func resourceAzureInstanceCreate(d *schema.ResourceData, meta interface{}) (err configureForImage, osType, err := retrieveImageDetails( mc, d.Get("image").(string), + name, d.Get("storage").(string), ) if err != nil { @@ -521,32 +523,35 @@ func resourceAzureEndpointHash(v interface{}) int { func retrieveImageDetails( mc management.Client, label string, + name string, storage string) (func(*virtualmachine.Role) error, string, error) { - configureForImage, osType, err := retrieveVMImageDetails(mc, label) + configureForImage, osType, VMLabels, err := retrieveVMImageDetails(mc, label) if err == nil { return configureForImage, osType, nil } - configureForImage, osType, err = retrieveOSImageDetails(mc, label, storage) + configureForImage, osType, OSLabels, err := retrieveOSImageDetails(mc, label, name, storage) if err == nil { return configureForImage, osType, nil } - return nil, "", fmt.Errorf("Could not find image with label '%s'", label) + return nil, "", fmt.Errorf("Could not find image with label '%s'. Available images are: %s", + label, strings.Join(append(VMLabels, OSLabels...), ", ")) } func retrieveVMImageDetails( mc management.Client, - label string) (func(*virtualmachine.Role) error, string, error) { + label string) (func(*virtualmachine.Role) error, string, []string, error) { imgs, err := virtualmachineimage.NewClient(mc).ListVirtualMachineImages() if err != nil { - return nil, "", fmt.Errorf("Error retrieving image details: %s", err) + return nil, "", nil, fmt.Errorf("Error retrieving image details: %s", err) } + var labels []string for _, img := range imgs.VMImages { if img.Label == label { if img.OSDiskConfiguration.OS != linux && img.OSDiskConfiguration.OS != windows { - return nil, "", fmt.Errorf("Unsupported image OS: %s", img.OSDiskConfiguration.OS) + return nil, "", nil, fmt.Errorf("Unsupported image OS: %s", img.OSDiskConfiguration.OS) } configureForImage := func(role *virtualmachine.Role) error { @@ -558,33 +563,37 @@ func retrieveVMImageDetails( ) } - return configureForImage, img.OSDiskConfiguration.OS, nil + return configureForImage, img.OSDiskConfiguration.OS, nil, nil } + + labels = append(labels, img.Label) } - return nil, "", fmt.Errorf("Could not find image with label '%s'", label) + return nil, "", labels, fmt.Errorf("Could not find image with label '%s'", label) } func retrieveOSImageDetails( mc management.Client, - label, - storage string) (func(*virtualmachine.Role) error, string, error) { + label string, + name string, + storage string) (func(*virtualmachine.Role) error, string, []string, error) { imgs, err := osimage.NewClient(mc).ListOSImages() if err != nil { - return nil, "", fmt.Errorf("Error retrieving image details: %s", err) + return nil, "", nil, fmt.Errorf("Error retrieving image details: %s", err) } + var labels []string for _, img := range imgs.OSImages { if img.Label == label { if img.OS != linux && img.OS != windows { - return nil, "", fmt.Errorf("Unsupported image OS: %s", img.OS) + return nil, "", nil, fmt.Errorf("Unsupported image OS: %s", img.OS) } if img.MediaLink == "" { if storage == "" { - return nil, "", + return nil, "", nil, fmt.Errorf("When using a platform image, the 'storage' parameter is required") } - img.MediaLink = fmt.Sprintf(osDiskBlobStorageURL, storage, label) + img.MediaLink = fmt.Sprintf(osDiskBlobStorageURL, storage, name) } configureForImage := func(role *virtualmachine.Role) error { @@ -596,11 +605,13 @@ func retrieveOSImageDetails( ) } - return configureForImage, img.OS, nil + return configureForImage, img.OS, nil, nil } + + labels = append(labels, img.Label) } - return nil, "", fmt.Errorf("Could not find image with label '%s'", label) + return nil, "", labels, fmt.Errorf("Could not find image with label '%s'", label) } func endpointProtocol(p string) virtualmachine.InputEndpointProtocol { diff --git a/builtin/providers/azure/resource_azure_instance_test.go b/builtin/providers/azure/resource_azure_instance_test.go index 6512f735e..0a272baa2 100644 --- a/builtin/providers/azure/resource_azure_instance_test.go +++ b/builtin/providers/azure/resource_azure_instance_test.go @@ -1 +1,457 @@ package azure + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/svanharmelen/azure-sdk-for-go/management" + "github.com/svanharmelen/azure-sdk-for-go/management/hostedservice" + "github.com/svanharmelen/azure-sdk-for-go/management/virtualmachine" +) + +func TestAccAzureInstance_basic(t *testing.T) { + var dpmt virtualmachine.DeploymentResponse + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAzureInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAzureInstance_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckAzureInstanceExists( + "azure_instance.foo", &dpmt), + testAccCheckAzureInstanceBasicAttributes(&dpmt), + resource.TestCheckResourceAttr( + "azure_instance.foo", "name", "terraform-test"), + resource.TestCheckResourceAttr( + "azure_instance.foo", "location", "West US"), + resource.TestCheckResourceAttr( + "azure_instance.foo", "endpoint.2462817782.public_port", "22"), + ), + }, + }, + }) +} + +func TestAccAzureInstance_advanced(t *testing.T) { + var dpmt virtualmachine.DeploymentResponse + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAzureInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAzureInstance_advanced, + Check: resource.ComposeTestCheckFunc( + testAccCheckAzureInstanceExists( + "azure_instance.foo", &dpmt), + testAccCheckAzureInstanceAdvancedAttributes(&dpmt), + resource.TestCheckResourceAttr( + "azure_instance.foo", "name", "terraform-test1"), + resource.TestCheckResourceAttr( + "azure_instance.foo", "size", "Basic_A1"), + resource.TestCheckResourceAttr( + "azure_instance.foo", "subnet", "subnet1"), + resource.TestCheckResourceAttr( + "azure_instance.foo", "virtual_network", "terraform-vnet"), + resource.TestCheckResourceAttr( + "azure_instance.foo", "security_group", "terraform-security-group1"), + resource.TestCheckResourceAttr( + "azure_instance.foo", "endpoint.1814039778.public_port", "3389"), + ), + }, + }, + }) +} + +func TestAccAzureInstance_update(t *testing.T) { + var dpmt virtualmachine.DeploymentResponse + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAzureInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAzureInstance_advanced, + Check: resource.ComposeTestCheckFunc( + testAccCheckAzureInstanceExists( + "azure_instance.foo", &dpmt), + testAccCheckAzureInstanceAdvancedAttributes(&dpmt), + resource.TestCheckResourceAttr( + "azure_instance.foo", "name", "terraform-test1"), + resource.TestCheckResourceAttr( + "azure_instance.foo", "size", "Basic_A1"), + resource.TestCheckResourceAttr( + "azure_instance.foo", "subnet", "subnet1"), + resource.TestCheckResourceAttr( + "azure_instance.foo", "virtual_network", "terraform-vnet"), + resource.TestCheckResourceAttr( + "azure_instance.foo", "security_group", "terraform-security-group1"), + resource.TestCheckResourceAttr( + "azure_instance.foo", "endpoint.1814039778.public_port", "3389"), + ), + }, + + resource.TestStep{ + Config: testAccAzureInstance_update, + Check: resource.ComposeTestCheckFunc( + testAccCheckAzureInstanceExists( + "azure_instance.foo", &dpmt), + testAccCheckAzureInstanceUpdatedAttributes(&dpmt), + resource.TestCheckResourceAttr( + "azure_instance.foo", "size", "Basic_A2"), + resource.TestCheckResourceAttr( + "azure_instance.foo", "security_group", "terraform-security-group2"), + resource.TestCheckResourceAttr( + "azure_instance.foo", "endpoint.1814039778.public_port", "3389"), + resource.TestCheckResourceAttr( + "azure_instance.foo", "endpoint.3713350066.public_port", "5985"), + ), + }, + }, + }) +} + +func testAccCheckAzureInstanceExists( + n string, + dpmt *virtualmachine.DeploymentResponse) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No instance ID is set") + } + + mc := testAccProvider.Meta().(management.Client) + vm, err := virtualmachine.NewClient(mc).GetDeployment(rs.Primary.ID, rs.Primary.ID) + if err != nil { + return err + } + + if vm.Name != rs.Primary.ID { + return fmt.Errorf("Instance not found") + } + + *dpmt = vm + + return nil + } +} + +func testAccCheckAzureInstanceBasicAttributes( + dpmt *virtualmachine.DeploymentResponse) resource.TestCheckFunc { + return func(s *terraform.State) error { + + if dpmt.Name != "terraform-test" { + return fmt.Errorf("Bad name: %s", dpmt.Name) + } + + if len(dpmt.RoleList) != 1 { + return fmt.Errorf( + "Instance %s has an unexpected number of roles: %d", dpmt.Name, len(dpmt.RoleList)) + } + + if dpmt.RoleList[0].RoleSize != "Basic_A1" { + return fmt.Errorf("Bad size: %s", dpmt.RoleList[0].RoleSize) + } + + return nil + } +} + +func testAccCheckAzureInstanceAdvancedAttributes( + dpmt *virtualmachine.DeploymentResponse) resource.TestCheckFunc { + return func(s *terraform.State) error { + + if dpmt.Name != "terraform-test1" { + return fmt.Errorf("Bad name: %s", dpmt.Name) + } + + if dpmt.VirtualNetworkName != "terraform-vnet" { + return fmt.Errorf("Bad virtual network: %s", dpmt.VirtualNetworkName) + } + + if len(dpmt.RoleList) != 1 { + return fmt.Errorf( + "Instance %s has an unexpected number of roles: %d", dpmt.Name, len(dpmt.RoleList)) + } + + if dpmt.RoleList[0].RoleSize != "Basic_A1" { + return fmt.Errorf("Bad size: %s", dpmt.RoleList[0].RoleSize) + } + + for _, c := range dpmt.RoleList[0].ConfigurationSets { + if c.ConfigurationSetType == virtualmachine.ConfigurationSetTypeNetwork { + if len(c.InputEndpoints) != 1 { + return fmt.Errorf( + "Instance %s has an unexpected number of endpoints %d", + dpmt.Name, len(c.InputEndpoints)) + } + + if c.InputEndpoints[0].Name != "RDP" { + return fmt.Errorf("Bad endpoint name: %s", c.InputEndpoints[0].Name) + } + + if c.InputEndpoints[0].Port != 3389 { + return fmt.Errorf("Bad endpoint port: %d", c.InputEndpoints[0].Port) + } + + if len(c.SubnetNames) != 1 { + return fmt.Errorf( + "Instance %s has an unexpected number of associated subnets %d", + dpmt.Name, len(c.SubnetNames)) + } + + if c.SubnetNames[0] != "subnet1" { + return fmt.Errorf("Bad subnet: %s", c.SubnetNames[0]) + } + + if c.NetworkSecurityGroup != "terraform-security-group1" { + return fmt.Errorf("Bad security group: %s", c.NetworkSecurityGroup) + } + } + } + + return nil + } +} + +func testAccCheckAzureInstanceUpdatedAttributes( + dpmt *virtualmachine.DeploymentResponse) resource.TestCheckFunc { + return func(s *terraform.State) error { + + if dpmt.Name != "terraform-test1" { + return fmt.Errorf("Bad name: %s", dpmt.Name) + } + + if dpmt.VirtualNetworkName != "terraform-vnet" { + return fmt.Errorf("Bad virtual network: %s", dpmt.VirtualNetworkName) + } + + if len(dpmt.RoleList) != 1 { + return fmt.Errorf( + "Instance %s has an unexpected number of roles: %d", dpmt.Name, len(dpmt.RoleList)) + } + + if dpmt.RoleList[0].RoleSize != "Basic_A2" { + return fmt.Errorf("Bad size: %s", dpmt.RoleList[0].RoleSize) + } + + for _, c := range dpmt.RoleList[0].ConfigurationSets { + if c.ConfigurationSetType == virtualmachine.ConfigurationSetTypeNetwork { + if len(c.InputEndpoints) != 2 { + return fmt.Errorf( + "Instance %s has an unexpected number of endpoints %d", + dpmt.Name, len(c.InputEndpoints)) + } + + if c.InputEndpoints[1].Name != "WINRM" { + return fmt.Errorf("Bad endpoint name: %s", c.InputEndpoints[1].Name) + } + + if c.InputEndpoints[1].Port != 5985 { + return fmt.Errorf("Bad endpoint port: %d", c.InputEndpoints[1].Port) + } + + if len(c.SubnetNames) != 1 { + return fmt.Errorf( + "Instance %s has an unexpected number of associated subnets %d", + dpmt.Name, len(c.SubnetNames)) + } + + if c.SubnetNames[0] != "subnet1" { + return fmt.Errorf("Bad subnet: %s", c.SubnetNames[0]) + } + + if c.NetworkSecurityGroup != "terraform-security-group2" { + return fmt.Errorf("Bad security group: %s", c.NetworkSecurityGroup) + } + } + } + + return nil + } +} + +func testAccCheckAzureInstanceDestroy(s *terraform.State) error { + mc := testAccProvider.Meta().(management.Client) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azure_instance" { + continue + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No instance ID is set") + } + + req, err := hostedservice.NewClient(mc).DeleteHostedService(rs.Primary.ID, true) + if err != nil { + return fmt.Errorf("Error deleting instance (%s): %s", rs.Primary.ID, err) + } + + // Wait until the instance is deleted + if err := mc.WaitForOperation(req, nil); err != nil { + return fmt.Errorf( + "Error deleting instance (%s): %s", rs.Primary.ID, err) + } + } + + return nil +} + +var testAccAzureInstance_basic = fmt.Sprintf(` +resource "azure_instance" "foo" { + name = "terraform-test" + image = "Ubuntu Server 14.04 LTS" + size = "Basic_A1" + storage = "%s" + location = "West US" + username = "terraform" + password = "Pass!admin123" + + endpoint { + name = "SSH" + protocol = "tcp" + public_port = 22 + private_port = 22 + } +}`, os.Getenv("AZURE_STORAGE")) + +var testAccAzureInstance_advanced = fmt.Sprintf(` +resource "azure_virtual_network" "foo" { + name = "terraform-vnet" + address_space = ["10.1.2.0/24"] + location = "West US" + + subnet { + name = "subnet1" + address_prefix = "10.1.2.0/25" + } + + subnet { + name = "subnet2" + address_prefix = "10.1.2.128/25" + } +} + +resource "azure_security_group" "foo" { + name = "terraform-security-group1" + location = "West US" + + rule { + name = "rdp" + priority = 101 + source_cidr = "*" + source_port = "*" + destination_cidr = "*" + destination_port = 3389 + protocol = "TCP" + } +} + +resource "azure_instance" "foo" { + name = "terraform-test1" + image = "Windows Server 2012 R2 Datacenter, April 2015" + size = "Basic_A1" + storage = "%s" + location = "West US" + time_zone = "America/Los_Angeles" + subnet = "subnet1" + virtual_network = "${azure_virtual_network.foo.name}" + security_group = "${azure_security_group.foo.name}" + username = "terraform" + password = "Pass!admin123" + + endpoint { + name = "RDP" + protocol = "tcp" + public_port = 3389 + private_port = 3389 + } +}`, os.Getenv("AZURE_STORAGE")) + +var testAccAzureInstance_update = fmt.Sprintf(` +resource "azure_virtual_network" "foo" { + name = "terraform-vnet" + address_space = ["10.1.2.0/24"] + location = "West US" + + subnet { + name = "subnet1" + address_prefix = "10.1.2.0/25" + } + + subnet { + name = "subnet2" + address_prefix = "10.1.2.128/25" + } +} + +resource "azure_security_group" "foo" { + name = "terraform-security-group1" + location = "West US" + + rule { + name = "rdp" + priority = 101 + source_cidr = "*" + source_port = "*" + destination_cidr = "*" + destination_port = 3389 + protocol = "TCP" + } +} + +resource "azure_security_group" "bar" { + name = "terraform-security-group2" + location = "West US" + + rule { + name = "rdp" + priority = 101 + source_cidr = "192.168.0.0/24" + source_port = "*" + destination_cidr = "*" + destination_port = 3389 + protocol = "TCP" + } +} + +resource "azure_instance" "foo" { + name = "terraform-test1" + image = "Windows Server 2012 R2 Datacenter, April 2015" + size = "Basic_A2" + storage = "%s" + location = "West US" + time_zone = "America/Los_Angeles" + subnet = "subnet1" + virtual_network = "${azure_virtual_network.foo.name}" + security_group = "${azure_security_group.bar.name}" + username = "terraform" + password = "Pass!admin123" + + endpoint { + name = "RDP" + protocol = "tcp" + public_port = 3389 + private_port = 3389 + } + + endpoint { + name = "WINRM" + protocol = "tcp" + public_port = 5985 + private_port = 5985 + } +}`, os.Getenv("AZURE_STORAGE")) diff --git a/builtin/providers/azure/resource_azure_security_group.go b/builtin/providers/azure/resource_azure_security_group.go index e5cca0f88..98d79bad0 100644 --- a/builtin/providers/azure/resource_azure_security_group.go +++ b/builtin/providers/azure/resource_azure_security_group.go @@ -33,11 +33,6 @@ func resourceAzureSecurityGroup() *schema.Resource { ForceNew: true, }, - "subnet": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - }, - "location": &schema.Schema{ Type: schema.TypeString, Required: true, diff --git a/builtin/providers/azure/resource_azure_security_group_test.go b/builtin/providers/azure/resource_azure_security_group_test.go index 6512f735e..1e48faad8 100644 --- a/builtin/providers/azure/resource_azure_security_group_test.go +++ b/builtin/providers/azure/resource_azure_security_group_test.go @@ -1 +1,273 @@ package azure + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/svanharmelen/azure-sdk-for-go/management" + "github.com/svanharmelen/azure-sdk-for-go/management/networksecuritygroup" +) + +func TestAccAzureSecurityGroup_basic(t *testing.T) { + var group networksecuritygroup.SecurityGroupResponse + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAzureSecurityGroupDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAzureSecurityGroup_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckAzureSecurityGroupExists( + "azure_security_group.foo", &group), + testAccCheckAzureSecurityGroupBasicAttributes(&group), + resource.TestCheckResourceAttr( + "azure_security_group.foo", "name", "terraform-security-group"), + resource.TestCheckResourceAttr( + "azure_security_group.foo", "location", "West US"), + resource.TestCheckResourceAttr( + "azure_security_group.foo", "rule.936204579.name", "RDP"), + resource.TestCheckResourceAttr( + "azure_security_group.foo", "rule.936204579.source_port", "*"), + resource.TestCheckResourceAttr( + "azure_security_group.foo", "rule.936204579.destination_port", "3389"), + ), + }, + }, + }) +} + +func TestAccAzureSecurityGroup_update(t *testing.T) { + var group networksecuritygroup.SecurityGroupResponse + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAzureSecurityGroupDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAzureSecurityGroup_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckAzureSecurityGroupExists( + "azure_security_group.foo", &group), + testAccCheckAzureSecurityGroupBasicAttributes(&group), + resource.TestCheckResourceAttr( + "azure_security_group.foo", "name", "terraform-security-group"), + resource.TestCheckResourceAttr( + "azure_security_group.foo", "location", "West US"), + resource.TestCheckResourceAttr( + "azure_security_group.foo", "rule.936204579.name", "RDP"), + resource.TestCheckResourceAttr( + "azure_security_group.foo", "rule.936204579.source_cidr", "*"), + resource.TestCheckResourceAttr( + "azure_security_group.foo", "rule.936204579.destination_port", "3389"), + ), + }, + + resource.TestStep{ + Config: testAccAzureSecurityGroup_update, + Check: resource.ComposeTestCheckFunc( + testAccCheckAzureSecurityGroupExists( + "azure_security_group.foo", &group), + testAccCheckAzureSecurityGroupUpdatedAttributes(&group), + resource.TestCheckResourceAttr( + "azure_security_group.foo", "rule.3322523298.name", "RDP"), + resource.TestCheckResourceAttr( + "azure_security_group.foo", "rule.3322523298.source_cidr", "192.168.0.0/24"), + resource.TestCheckResourceAttr( + "azure_security_group.foo", "rule.3322523298.destination_port", "3389"), + resource.TestCheckResourceAttr( + "azure_security_group.foo", "rule.3929353075.name", "WINRM"), + resource.TestCheckResourceAttr( + "azure_security_group.foo", "rule.3929353075.source_cidr", "192.168.0.0/24"), + resource.TestCheckResourceAttr( + "azure_security_group.foo", "rule.3929353075.destination_port", "5985"), + ), + }, + }, + }) +} + +func testAccCheckAzureSecurityGroupExists( + n string, + group *networksecuritygroup.SecurityGroupResponse) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Network Security Group ID is set") + } + + mc := testAccProvider.Meta().(management.Client) + sg, err := networksecuritygroup.NewClient(mc).GetNetworkSecurityGroup(rs.Primary.ID) + if err != nil { + return err + } + + if sg.Name != rs.Primary.ID { + return fmt.Errorf("Security Group not found") + } + + *group = sg + + return nil + } +} + +func testAccCheckAzureSecurityGroupBasicAttributes( + group *networksecuritygroup.SecurityGroupResponse) resource.TestCheckFunc { + return func(s *terraform.State) error { + + if group.Name != "terraform-security-group" { + return fmt.Errorf("Bad name: %s", group.Name) + } + + for _, r := range group.Rules { + if !r.IsDefault { + if r.Name != "RDP" { + return fmt.Errorf("Bad rule name: %s", r.Name) + } + if r.Priority != 101 { + return fmt.Errorf("Bad rule priority: %d", r.Priority) + } + if r.SourceAddressPrefix != "*" { + return fmt.Errorf("Bad source CIDR: %s", r.SourceAddressPrefix) + } + if r.DestinationAddressPrefix != "*" { + return fmt.Errorf("Bad destination CIDR: %s", r.DestinationAddressPrefix) + } + if r.DestinationPortRange != "3389" { + return fmt.Errorf("Bad destination port: %s", r.DestinationPortRange) + } + } + } + + return nil + } +} + +func testAccCheckAzureSecurityGroupUpdatedAttributes( + group *networksecuritygroup.SecurityGroupResponse) resource.TestCheckFunc { + return func(s *terraform.State) error { + + if group.Name != "terraform-security-group" { + return fmt.Errorf("Bad name: %s", group.Name) + } + + foundRDP := false + foundWINRM := false + for _, r := range group.Rules { + if !r.IsDefault { + if r.Name == "RDP" { + if r.SourceAddressPrefix != "192.168.0.0/24" { + return fmt.Errorf("Bad source CIDR: %s", r.SourceAddressPrefix) + } + + foundRDP = true + } + + if r.Name == "WINRM" { + if r.Priority != 102 { + return fmt.Errorf("Bad rule priority: %d", r.Priority) + } + if r.SourceAddressPrefix != "192.168.0.0/24" { + return fmt.Errorf("Bad source CIDR: %s", r.SourceAddressPrefix) + } + if r.DestinationAddressPrefix != "*" { + return fmt.Errorf("Bad destination CIDR: %s", r.DestinationAddressPrefix) + } + if r.DestinationPortRange != "5985" { + return fmt.Errorf("Bad destination port: %s", r.DestinationPortRange) + } + + foundWINRM = true + } + } + } + + if !foundRDP { + return fmt.Errorf("RDP rule not found") + } + + if !foundWINRM { + return fmt.Errorf("WINRM rule not found") + } + + return nil + } +} + +func testAccCheckAzureSecurityGroupDestroy(s *terraform.State) error { + mc := testAccProvider.Meta().(management.Client) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azure_security_group" { + continue + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Network Security Group ID is set") + } + + req, err := networksecuritygroup.NewClient(mc).DeleteNetworkSecurityGroup(rs.Primary.ID) + if err != nil { + return fmt.Errorf("Error deleting Network Security Group (%s): %s", rs.Primary.ID, err) + } + + // Wait until the instance is deleted + if err := mc.WaitForOperation(req, nil); err != nil { + return fmt.Errorf( + "Error deleting Network Security Group (%s): %s", rs.Primary.ID, err) + } + } + + return nil +} + +const testAccAzureSecurityGroup_basic = ` +resource "azure_security_group" "foo" { + name = "terraform-security-group" + location = "West US" + + rule { + name = "RDP" + priority = 101 + source_cidr = "*" + source_port = "*" + destination_cidr = "*" + destination_port = "3389" + protocol = "TCP" + } +}` + +const testAccAzureSecurityGroup_update = ` +resource "azure_security_group" "foo" { + name = "terraform-security-group" + location = "West US" + + rule { + name = "RDP" + priority = 101 + source_cidr = "192.168.0.0/24" + source_port = "*" + destination_cidr = "*" + destination_port = "3389" + protocol = "TCP" + } + + rule { + name = "WINRM" + priority = 102 + source_cidr = "192.168.0.0/24" + source_port = "*" + destination_cidr = "*" + destination_port = "5985" + protocol = "TCP" + } +}` diff --git a/builtin/providers/azure/resource_azure_virtual_network.go b/builtin/providers/azure/resource_azure_virtual_network.go index de451fe8d..7125f24a0 100644 --- a/builtin/providers/azure/resource_azure_virtual_network.go +++ b/builtin/providers/azure/resource_azure_virtual_network.go @@ -5,9 +5,11 @@ import ( "log" "strings" + "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" "github.com/mitchellh/mapstructure" "github.com/svanharmelen/azure-sdk-for-go/management" + "github.com/svanharmelen/azure-sdk-for-go/management/networksecuritygroup" "github.com/svanharmelen/azure-sdk-for-go/management/virtualnetwork" ) @@ -36,8 +38,25 @@ func resourceAzureVirtualNetwork() *schema.Resource { }, "subnet": &schema.Schema{ - Type: schema.TypeMap, + Type: schema.TypeSet, Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "address_prefix": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "security_group": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + }, + Set: resourceAzureSubnetHash, }, "location": &schema.Schema{ @@ -83,12 +102,15 @@ func resourceAzureVirtualNetworkCreate(d *schema.ResourceData, meta interface{}) // Wait until the virtual network is created if err := mc.WaitForOperation(req, nil); err != nil { - log.Printf( - "[DEBUG] Error waiting for Virtual Network %s to be created: %s", name, err) + return fmt.Errorf("Error waiting for Virtual Network %s to be created: %s", name, err) } d.SetId(name) + if err := associateSecurityGroups(d, meta); err != nil { + return err + } + return resourceAzureVirtualNetworkRead(d, meta) } @@ -105,9 +127,29 @@ func resourceAzureVirtualNetworkRead(d *schema.ResourceData, meta interface{}) e d.Set("address_space", n.AddressSpace.AddressPrefix) d.Set("location", n.Location) - subnets := map[string]interface{}{} + // Create a new set to hold all configured subnets + subnets := &schema.Set{ + F: resourceAzureSubnetHash, + } + + // Loop through all endpoints for _, s := range n.Subnets { - subnets[s.Name] = s.AddressPrefix + subnet := map[string]interface{}{} + + // Get the associated (if any) security group + sg, err := networksecuritygroup.NewClient(mc). + GetNetworkSecurityGroupForSubnet(s.Name, d.Id()) + if err != nil && !management.IsResourceNotFoundError(err) { + return fmt.Errorf( + "Error retrieving Network Security Group associations of subnet %s: %s", s.Name, err) + } + + // Update the values + subnet["name"] = s.Name + subnet["address_prefix"] = s.AddressPrefix + subnet["security_group"] = sg.Name + + subnets.Add(subnet) } d.Set("subnet", subnets) @@ -155,8 +197,11 @@ func resourceAzureVirtualNetworkUpdate(d *schema.ResourceData, meta interface{}) // Wait until the virtual network is updated if err := mc.WaitForOperation(req, nil); err != nil { - log.Printf( - "[DEBUG] Error waiting for Virtual Network %s to be updated: %s", d.Id(), err) + return fmt.Errorf("Error waiting for Virtual Network %s to be updated: %s", d.Id(), err) + } + + if err := associateSecurityGroups(d, meta); err != nil { + return err } return resourceAzureVirtualNetworkRead(d, meta) @@ -186,8 +231,7 @@ func resourceAzureVirtualNetworkDelete(d *schema.ResourceData, meta interface{}) // Wait until the virtual network is deleted if err := mc.WaitForOperation(req, nil); err != nil { - log.Printf( - "[DEBUG] Error waiting for Virtual Network %s to be deleted: %s", d.Id(), err) + return fmt.Errorf("Error waiting for Virtual Network %s to be deleted: %s", d.Id(), err) } d.SetId("") @@ -195,6 +239,12 @@ func resourceAzureVirtualNetworkDelete(d *schema.ResourceData, meta interface{}) return nil } +func resourceAzureSubnetHash(v interface{}) int { + m := v.(map[string]interface{}) + subnet := m["name"].(string) + m["address_prefix"].(string) + m["security_group"].(string) + return hashcode.String(subnet) +} + func createVirtualNetwork(d *schema.ResourceData) (virtualnetwork.VirtualNetworkSite, error) { var addressPrefix []string err := mapstructure.WeakDecode(d.Get("address_space"), &addressPrefix) @@ -206,12 +256,16 @@ func createVirtualNetwork(d *schema.ResourceData) (virtualnetwork.VirtualNetwork AddressPrefix: addressPrefix, } - subnets := []virtualnetwork.Subnet{} - for n, p := range d.Get("subnet").(map[string]interface{}) { - subnets = append(subnets, virtualnetwork.Subnet{ - Name: n, - AddressPrefix: p.(string), - }) + // Add all subnets that are configured + var subnets []virtualnetwork.Subnet + if rs := d.Get("subnet").(*schema.Set); rs.Len() > 0 { + for _, subnet := range rs.List() { + subnet := subnet.(map[string]interface{}) + subnets = append(subnets, virtualnetwork.Subnet{ + Name: subnet["name"].(string), + AddressPrefix: subnet["address_prefix"].(string), + }) + } } return virtualnetwork.VirtualNetworkSite{ @@ -221,3 +275,67 @@ func createVirtualNetwork(d *schema.ResourceData) (virtualnetwork.VirtualNetwork Subnets: subnets, }, nil } + +func associateSecurityGroups(d *schema.ResourceData, meta interface{}) error { + mc := meta.(management.Client) + + virtualNetwork := d.Get("name").(string) + + if rs := d.Get("subnet").(*schema.Set); rs.Len() > 0 { + for _, subnet := range rs.List() { + subnet := subnet.(map[string]interface{}) + securityGroup := subnet["security_group"].(string) + subnetName := subnet["name"].(string) + + // Get the associated (if any) security group + sg, err := networksecuritygroup.NewClient(mc). + GetNetworkSecurityGroupForSubnet(subnetName, d.Id()) + if err != nil && !management.IsResourceNotFoundError(err) { + return fmt.Errorf( + "Error retrieving Network Security Group associations of subnet %s: %s", subnetName, err) + } + + // If the desired and actual security group are the same, were done so can just continue + if sg.Name == securityGroup { + continue + } + + // If there is an associated security group, make sure we first remove it from the subnet + if sg.Name != "" { + req, err := networksecuritygroup.NewClient(mc). + RemoveNetworkSecurityGroupFromSubnet(sg.Name, subnetName, virtualNetwork) + if err != nil { + return fmt.Errorf("Error removing Network Security Group %s from subnet %s: %s", + securityGroup, subnetName, err) + } + + // Wait until the security group is associated + if err := mc.WaitForOperation(req, nil); err != nil { + return fmt.Errorf( + "Error waiting for Network Security Group %s to be removed from subnet %s: %s", + securityGroup, subnetName, err) + } + } + + // If the desired security group is not empty, assign the security group to the subnet + if securityGroup != "" { + req, err := networksecuritygroup.NewClient(mc). + AddNetworkSecurityToSubnet(securityGroup, subnetName, virtualNetwork) + if err != nil { + return fmt.Errorf("Error associating Network Security Group %s to subnet %s: %s", + securityGroup, subnetName, err) + } + + // Wait until the security group is associated + if err := mc.WaitForOperation(req, nil); err != nil { + return fmt.Errorf( + "Error waiting for Network Security Group %s to be associated with subnet %s: %s", + securityGroup, subnetName, err) + } + } + + } + } + + return nil +} diff --git a/builtin/providers/azure/resource_azure_virtual_network_test.go b/builtin/providers/azure/resource_azure_virtual_network_test.go index 6512f735e..2ade3de00 100644 --- a/builtin/providers/azure/resource_azure_virtual_network_test.go +++ b/builtin/providers/azure/resource_azure_virtual_network_test.go @@ -1 +1,296 @@ package azure + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/svanharmelen/azure-sdk-for-go/management" + "github.com/svanharmelen/azure-sdk-for-go/management/virtualnetwork" +) + +func TestAccAzureVirtualNetwork_basic(t *testing.T) { + var network virtualnetwork.VirtualNetworkSite + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAzureVirtualNetworkDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAzureVirtualNetwork_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckAzureVirtualNetworkExists( + "azure_virtual_network.foo", &network), + testAccCheckAzureVirtualNetworkAttributes(&network), + resource.TestCheckResourceAttr( + "azure_virtual_network.foo", "name", "terraform-vnet"), + resource.TestCheckResourceAttr( + "azure_virtual_network.foo", "location", "West US"), + resource.TestCheckResourceAttr( + "azure_virtual_network.foo", "address_space.0", "10.1.2.0/24"), + resource.TestCheckResourceAttr( + "azure_virtual_network.foo", "subnet.1787288781.name", "subnet1"), + resource.TestCheckResourceAttr( + "azure_virtual_network.foo", "subnet.1787288781.address_prefix", "10.1.2.0/25"), + ), + }, + }, + }) +} + +func TestAccAzureVirtualNetwork_advanced(t *testing.T) { + var network virtualnetwork.VirtualNetworkSite + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAzureVirtualNetworkDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAzureVirtualNetwork_advanced, + Check: resource.ComposeTestCheckFunc( + testAccCheckAzureVirtualNetworkExists( + "azure_virtual_network.foo", &network), + testAccCheckAzureVirtualNetworkAttributes(&network), + resource.TestCheckResourceAttr( + "azure_virtual_network.foo", "name", "terraform-vnet"), + resource.TestCheckResourceAttr( + "azure_virtual_network.foo", "location", "West US"), + resource.TestCheckResourceAttr( + "azure_virtual_network.foo", "address_space.0", "10.1.2.0/24"), + resource.TestCheckResourceAttr( + "azure_virtual_network.foo", "subnet.33778499.name", "subnet1"), + resource.TestCheckResourceAttr( + "azure_virtual_network.foo", "subnet.33778499.address_prefix", "10.1.2.0/25"), + resource.TestCheckResourceAttr( + "azure_virtual_network.foo", "subnet.33778499.security_group", "terraform-security-group1"), + ), + }, + }, + }) +} + +func TestAccAzureVirtualNetwork_update(t *testing.T) { + var network virtualnetwork.VirtualNetworkSite + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAzureVirtualNetworkDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAzureVirtualNetwork_advanced, + Check: resource.ComposeTestCheckFunc( + testAccCheckAzureVirtualNetworkExists( + "azure_virtual_network.foo", &network), + testAccCheckAzureVirtualNetworkAttributes(&network), + resource.TestCheckResourceAttr( + "azure_virtual_network.foo", "name", "terraform-vnet"), + resource.TestCheckResourceAttr( + "azure_virtual_network.foo", "location", "West US"), + resource.TestCheckResourceAttr( + "azure_virtual_network.foo", "address_space.0", "10.1.2.0/24"), + resource.TestCheckResourceAttr( + "azure_virtual_network.foo", "subnet.33778499.name", "subnet1"), + resource.TestCheckResourceAttr( + "azure_virtual_network.foo", "subnet.33778499.address_prefix", "10.1.2.0/25"), + resource.TestCheckResourceAttr( + "azure_virtual_network.foo", "subnet.33778499.security_group", "terraform-security-group1"), + ), + }, + + resource.TestStep{ + Config: testAccAzureVirtualNetwork_update, + Check: resource.ComposeTestCheckFunc( + testAccCheckAzureVirtualNetworkExists( + "azure_virtual_network.foo", &network), + testAccCheckAzureVirtualNetworkAttributes(&network), + resource.TestCheckResourceAttr( + "azure_virtual_network.foo", "name", "terraform-vnet"), + resource.TestCheckResourceAttr( + "azure_virtual_network.foo", "location", "West US"), + resource.TestCheckResourceAttr( + "azure_virtual_network.foo", "address_space.0", "10.1.3.0/24"), + resource.TestCheckResourceAttr( + "azure_virtual_network.foo", "subnet.514595123.name", "subnet1"), + resource.TestCheckResourceAttr( + "azure_virtual_network.foo", "subnet.514595123.address_prefix", "10.1.3.128/25"), + resource.TestCheckResourceAttr( + "azure_virtual_network.foo", "subnet.514595123.security_group", "terraform-security-group2"), + ), + }, + }, + }) +} + +func testAccCheckAzureVirtualNetworkExists( + n string, + network *virtualnetwork.VirtualNetworkSite) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Virtual Network ID is set") + } + + mc := testAccProvider.Meta().(management.Client) + nc, err := virtualnetwork.NewClient(mc).GetVirtualNetworkConfiguration() + if err != nil { + return err + } + + for _, n := range nc.Configuration.VirtualNetworkSites { + if n.Name == rs.Primary.ID { + *network = n + + return nil + } + } + + return fmt.Errorf("Virtual Network not found") + } +} + +func testAccCheckAzureVirtualNetworkAttributes( + network *virtualnetwork.VirtualNetworkSite) resource.TestCheckFunc { + return func(s *terraform.State) error { + + if network.Name != "terraform-vnet" { + return fmt.Errorf("Bad name: %s", network.Name) + } + + if network.Location != "West US" { + return fmt.Errorf("Bad location: %s", network.Location) + } + + return nil + } +} + +func testAccCheckAzureVirtualNetworkDestroy(s *terraform.State) error { + mc := testAccProvider.Meta().(management.Client) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azure_virtual_network" { + continue + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Virtual Network ID is set") + } + + nc, err := virtualnetwork.NewClient(mc).GetVirtualNetworkConfiguration() + if err != nil { + return fmt.Errorf("Error retrieving Virtual Network Configuration: %s", err) + } + + filtered := nc.Configuration.VirtualNetworkSites[:0] + for _, n := range nc.Configuration.VirtualNetworkSites { + if n.Name != rs.Primary.ID { + filtered = append(filtered, n) + } + } + + nc.Configuration.VirtualNetworkSites = filtered + + req, err := virtualnetwork.NewClient(mc).SetVirtualNetworkConfiguration(nc) + if err != nil { + return fmt.Errorf("Error deleting Virtual Network %s: %s", rs.Primary.ID, err) + } + + // Wait until the virtual network is deleted + if err := mc.WaitForOperation(req, nil); err != nil { + return fmt.Errorf("Error waiting for Virtual Network %s to be deleted: %s", rs.Primary.ID, err) + } + } + + return nil +} + +const testAccAzureVirtualNetwork_basic = ` +resource "azure_virtual_network" "foo" { + name = "terraform-vnet" + address_space = ["10.1.2.0/24"] + location = "West US" + + subnet { + name = "subnet1" + address_prefix = "10.1.2.0/25" + } +}` + +const testAccAzureVirtualNetwork_advanced = ` +resource "azure_security_group" "foo" { + name = "terraform-security-group1" + location = "West US" + + rule { + name = "RDP" + priority = 101 + source_cidr = "*" + source_port = "*" + destination_cidr = "*" + destination_port = "3389" + protocol = "TCP" + } +} + +resource "azure_virtual_network" "foo" { + name = "terraform-vnet" + address_space = ["10.1.2.0/24"] + location = "West US" + + subnet { + name = "subnet1" + address_prefix = "10.1.2.0/25" + security_group = "${azure_security_group.foo.name}" + } +}` + +const testAccAzureVirtualNetwork_update = ` +resource "azure_security_group" "foo" { + name = "terraform-security-group1" + location = "West US" + + rule { + name = "RDP" + priority = 101 + source_cidr = "*" + source_port = "*" + destination_cidr = "*" + destination_port = "3389" + protocol = "TCP" + } +} + +resource "azure_security_group" "bar" { + name = "terraform-security-group2" + location = "West US" + + rule { + name = "SSH" + priority = 101 + source_cidr = "*" + source_port = "*" + destination_cidr = "*" + destination_port = "22" + protocol = "TCP" + } +} + +resource "azure_virtual_network" "foo" { + name = "terraform-vnet" + address_space = ["10.1.3.0/24"] + location = "West US" + + subnet { + name = "subnet1" + address_prefix = "10.1.3.128/25" + security_group = "${azure_security_group.bar.name}" + } +}`