| @@ -0,0 +1,341 @@ | ||
| package azure | ||
| import ( | ||
| "fmt" | ||
| "log" | ||
| "time" | ||
| "github.com/hashicorp/terraform/helper/schema" | ||
| "github.com/svanharmelen/azure-sdk-for-go/management" | ||
| "github.com/svanharmelen/azure-sdk-for-go/management/virtualmachinedisk" | ||
| ) | ||
| const dataDiskBlobStorageURL = "http://%s.blob.core.windows.net/disks/%s.vhd" | ||
| func resourceAzureDataDisk() *schema.Resource { | ||
| return &schema.Resource{ | ||
| Create: resourceAzureDataDiskCreate, | ||
| Read: resourceAzureDataDiskRead, | ||
| Update: resourceAzureDataDiskUpdate, | ||
| Delete: resourceAzureDataDiskDelete, | ||
| Schema: map[string]*schema.Schema{ | ||
| "name": &schema.Schema{ | ||
| Type: schema.TypeString, | ||
| Optional: true, | ||
| Computed: true, | ||
| ForceNew: true, | ||
| }, | ||
| "label": &schema.Schema{ | ||
| Type: schema.TypeString, | ||
| Optional: true, | ||
| Computed: true, | ||
| ForceNew: true, | ||
| }, | ||
| "lun": &schema.Schema{ | ||
| Type: schema.TypeInt, | ||
| Required: true, | ||
| }, | ||
| "size": &schema.Schema{ | ||
| Type: schema.TypeInt, | ||
| Optional: true, | ||
| }, | ||
| "caching": &schema.Schema{ | ||
| Type: schema.TypeString, | ||
| Optional: true, | ||
| Default: "None", | ||
| }, | ||
| "storage": &schema.Schema{ | ||
| Type: schema.TypeString, | ||
| Optional: true, | ||
| ForceNew: true, | ||
| }, | ||
| "media_link": &schema.Schema{ | ||
| Type: schema.TypeString, | ||
| Optional: true, | ||
| Computed: true, | ||
| ForceNew: true, | ||
| }, | ||
| "source_media_link": &schema.Schema{ | ||
| Type: schema.TypeString, | ||
| Optional: true, | ||
| ForceNew: true, | ||
| }, | ||
| "virtual_machine": &schema.Schema{ | ||
| Type: schema.TypeString, | ||
| Required: true, | ||
| }, | ||
| }, | ||
| } | ||
| } | ||
| func resourceAzureDataDiskCreate(d *schema.ResourceData, meta interface{}) error { | ||
| mc := meta.(*Client).mgmtClient | ||
| if err := verifyDataDiskParameters(d); err != nil { | ||
| return err | ||
| } | ||
| 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), | ||
| MediaLink: mediaLink(d), | ||
| SourceMediaLink: d.Get("source_media_link").(string), | ||
| } | ||
| if name, ok := d.GetOk("name"); ok { | ||
| p.DiskName = name.(string) | ||
| } | ||
| log.Printf("[DEBUG] Adding data disk %d to instance: %s", lun, vm) | ||
| req, err := virtualmachinedisk.NewClient(mc).AddDataDisk(vm, vm, vm, p) | ||
| if err != nil { | ||
| return fmt.Errorf("Error adding data disk %d to instance %s: %s", lun, vm, err) | ||
| } | ||
| // Wait until the data disk is added | ||
| if err := mc.WaitForOperation(req, nil); err != nil { | ||
| return fmt.Errorf( | ||
| "Error waiting for data disk %d to be added to instance %s: %s", lun, vm, err) | ||
| } | ||
| log.Printf("[DEBUG] Retrieving data disk %d from instance %s", lun, vm) | ||
| disk, err := virtualmachinedisk.NewClient(mc).GetDataDisk(vm, vm, vm, lun) | ||
| if err != nil { | ||
| return fmt.Errorf("Error retrieving data disk %d from instance %s: %s", lun, vm, err) | ||
| } | ||
| d.SetId(disk.DiskName) | ||
| return resourceAzureDataDiskRead(d, meta) | ||
| } | ||
| func resourceAzureDataDiskRead(d *schema.ResourceData, meta interface{}) error { | ||
| mc := meta.(*Client).mgmtClient | ||
| lun := d.Get("lun").(int) | ||
| vm := d.Get("virtual_machine").(string) | ||
| 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) | ||
| d.Set("media_link", datadisk.MediaLink) | ||
| log.Printf("[DEBUG] Retrieving disk: %s", d.Id()) | ||
| disk, err := virtualmachinedisk.NewClient(mc).GetDisk(d.Id()) | ||
| if err != nil { | ||
| return fmt.Errorf("Error retrieving disk %s: %s", d.Id(), err) | ||
| } | ||
| d.Set("virtual_machine", disk.AttachedTo.RoleName) | ||
| return nil | ||
| } | ||
| func resourceAzureDataDiskUpdate(d *schema.ResourceData, meta interface{}) error { | ||
| mc := meta.(*Client).mgmtClient | ||
| diskClient := virtualmachinedisk.NewClient(mc) | ||
| lun := d.Get("lun").(int) | ||
| vm := d.Get("virtual_machine").(string) | ||
| if d.HasChange("lun") || d.HasChange("size") || d.HasChange("virtual_machine") { | ||
| olun, _ := d.GetChange("lun") | ||
| ovm, _ := d.GetChange("virtual_machine") | ||
| log.Printf("[DEBUG] Detaching data disk: %s", d.Id()) | ||
| req, err := diskClient. | ||
| 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) | ||
| } | ||
| // Wait until the data disk is detached | ||
| if err := mc.WaitForOperation(req, nil); err != nil { | ||
| return fmt.Errorf( | ||
| "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 := diskClient.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{ | ||
| Name: d.Id(), | ||
| Label: d.Get("label").(string), | ||
| ResizedSizeInGB: d.Get("size").(int), | ||
| } | ||
| log.Printf("[DEBUG] Updating disk: %s", d.Id()) | ||
| req, err := diskClient.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, | ||
| HostCaching: hostCaching(d), | ||
| MediaLink: mediaLink(d), | ||
| } | ||
| log.Printf("[DEBUG] Attaching data disk: %s", d.Id()) | ||
| req, err = diskClient.AddDataDisk(vm, vm, vm, p) | ||
| if err != nil { | ||
| return fmt.Errorf("Error attaching data disk %s to instance %s: %s", d.Id(), vm, err) | ||
| } | ||
| // Wait until the data disk is attached | ||
| if err := mc.WaitForOperation(req, nil); err != nil { | ||
| return fmt.Errorf( | ||
| "Error waiting for data disk %s to be attached to instance %s: %s", d.Id(), vm, err) | ||
| } | ||
| // Make sure we return here since all possible changes are | ||
| // already updated if we reach this point | ||
| return nil | ||
| } | ||
| if d.HasChange("caching") { | ||
| p := virtualmachinedisk.UpdateDataDiskParameters{ | ||
| DiskName: d.Id(), | ||
| Lun: lun, | ||
| HostCaching: hostCaching(d), | ||
| MediaLink: mediaLink(d), | ||
| } | ||
| log.Printf("[DEBUG] Updating data disk: %s", d.Id()) | ||
| req, err := diskClient.UpdateDataDisk(vm, vm, vm, lun, p) | ||
| if err != nil { | ||
| return fmt.Errorf("Error updating data disk %s: %s", d.Id(), err) | ||
| } | ||
| // Wait until the data disk is updated | ||
| if err := mc.WaitForOperation(req, nil); err != nil { | ||
| return fmt.Errorf( | ||
| "Error waiting for data disk %s to be updated: %s", d.Id(), err) | ||
| } | ||
| } | ||
| return resourceAzureDataDiskRead(d, meta) | ||
| } | ||
| func resourceAzureDataDiskDelete(d *schema.ResourceData, meta interface{}) error { | ||
| mc := meta.(*Client).mgmtClient | ||
| lun := d.Get("lun").(int) | ||
| vm := d.Get("virtual_machine").(string) | ||
| // If a name was not supplied, it means we created a new emtpy disk and we now want to | ||
| // delete that disk again. Otherwise we only want to detach the disk and keep the blob. | ||
|
||
| _, removeBlob := d.GetOk("name") | ||
| log.Printf("[DEBUG] Detaching data disk %s with removeBlob = %t", d.Id(), removeBlob) | ||
| req, err := virtualmachinedisk.NewClient(mc).DeleteDataDisk(vm, vm, vm, lun, removeBlob) | ||
| if err != nil { | ||
| return fmt.Errorf( | ||
| "Error detaching data disk %s with removeBlob = %t: %s", d.Id(), removeBlob, err) | ||
| } | ||
| // Wait until the data disk is detached and optionally deleted | ||
| if err := mc.WaitForOperation(req, nil); err != nil { | ||
| return fmt.Errorf( | ||
| "Error waiting for data disk %s to be detached with removeBlob = %t: %s", | ||
| d.Id(), removeBlob, err) | ||
| } | ||
| d.SetId("") | ||
| return nil | ||
| } | ||
| func hostCaching(d *schema.ResourceData) virtualmachinedisk.HostCachingType { | ||
| switch d.Get("caching").(string) { | ||
| case "ReadOnly": | ||
| return virtualmachinedisk.HostCachingTypeReadOnly | ||
| case "ReadWrite": | ||
| return virtualmachinedisk.HostCachingTypeReadWrite | ||
| default: | ||
| return virtualmachinedisk.HostCachingTypeNone | ||
| } | ||
| } | ||
| func mediaLink(d *schema.ResourceData) string { | ||
| mediaLink, ok := d.GetOk("media_link") | ||
| if ok { | ||
| return mediaLink.(string) | ||
| } | ||
| name, ok := d.GetOk("name") | ||
| if !ok { | ||
| name = fmt.Sprintf("%s-%d", d.Get("virtual_machine").(string), d.Get("lun").(int)) | ||
| } | ||
| return fmt.Sprintf(dataDiskBlobStorageURL, d.Get("storage").(string), name.(string)) | ||
| } | ||
| func verifyDataDiskParameters(d *schema.ResourceData) error { | ||
| caching := d.Get("caching").(string) | ||
| if caching != "None" && caching != "ReadOnly" && caching != "ReadWrite" { | ||
| return fmt.Errorf( | ||
| "Invalid caching type %s! Valid options are 'None', 'ReadOnly' and 'ReadWrite'.", caching) | ||
| } | ||
| if _, ok := d.GetOk("media_link"); !ok { | ||
| if _, ok := d.GetOk("storage"); !ok { | ||
| return fmt.Errorf("If not supplying 'media_link', you must supply 'storage'.") | ||
| } | ||
| } | ||
| return nil | ||
| } | ||
| @@ -0,0 +1,237 @@ | ||
| 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/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().(*Client).mgmtClient | ||
| 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().(*Client).mgmtClient | ||
| 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")) |
| @@ -0,0 +1,353 @@ | ||
| package azure | ||
| import ( | ||
| "bytes" | ||
| "fmt" | ||
| "log" | ||
| "strconv" | ||
| "github.com/hashicorp/terraform/helper/hashcode" | ||
| "github.com/hashicorp/terraform/helper/schema" | ||
| "github.com/svanharmelen/azure-sdk-for-go/management" | ||
| "github.com/svanharmelen/azure-sdk-for-go/management/networksecuritygroup" | ||
| ) | ||
| func resourceAzureSecurityGroup() *schema.Resource { | ||
| return &schema.Resource{ | ||
| Create: resourceAzureSecurityGroupCreate, | ||
| Read: resourceAzureSecurityGroupRead, | ||
| Update: resourceAzureSecurityGroupUpdate, | ||
| Delete: resourceAzureSecurityGroupDelete, | ||
| Schema: map[string]*schema.Schema{ | ||
| "name": &schema.Schema{ | ||
| Type: schema.TypeString, | ||
| Required: true, | ||
| ForceNew: true, | ||
| }, | ||
| "label": &schema.Schema{ | ||
| Type: schema.TypeString, | ||
| Optional: true, | ||
| Computed: true, | ||
| ForceNew: true, | ||
| }, | ||
| "location": &schema.Schema{ | ||
| Type: schema.TypeString, | ||
| Required: true, | ||
| ForceNew: true, | ||
| }, | ||
| "rule": &schema.Schema{ | ||
| Type: schema.TypeSet, | ||
| Required: true, | ||
| Elem: &schema.Resource{ | ||
| Schema: map[string]*schema.Schema{ | ||
| "name": &schema.Schema{ | ||
| Type: schema.TypeString, | ||
| Required: true, | ||
| }, | ||
| "type": &schema.Schema{ | ||
| Type: schema.TypeString, | ||
| Optional: true, | ||
| Default: "Inbound", | ||
| }, | ||
| "priority": &schema.Schema{ | ||
| Type: schema.TypeInt, | ||
| Required: true, | ||
| }, | ||
| "action": &schema.Schema{ | ||
| Type: schema.TypeString, | ||
| Optional: true, | ||
| Default: "Allow", | ||
| }, | ||
| "source_cidr": &schema.Schema{ | ||
| Type: schema.TypeString, | ||
| Required: true, | ||
| }, | ||
| "source_port": &schema.Schema{ | ||
| Type: schema.TypeString, | ||
| Required: true, | ||
| }, | ||
| "destination_cidr": &schema.Schema{ | ||
| Type: schema.TypeString, | ||
| Required: true, | ||
| }, | ||
| "destination_port": &schema.Schema{ | ||
| Type: schema.TypeString, | ||
| Required: true, | ||
| }, | ||
| "protocol": &schema.Schema{ | ||
| Type: schema.TypeString, | ||
| Optional: true, | ||
| Default: "TCP", | ||
| }, | ||
| }, | ||
| }, | ||
| Set: resourceAzureSecurityGroupRuleHash, | ||
| }, | ||
| }, | ||
| } | ||
| } | ||
| func resourceAzureSecurityGroupCreate(d *schema.ResourceData, meta interface{}) (err error) { | ||
| mc := meta.(*Client).mgmtClient | ||
| name := d.Get("name").(string) | ||
| // Compute/set the label | ||
| label := d.Get("label").(string) | ||
| if label == "" { | ||
| label = name | ||
| } | ||
| req, err := networksecuritygroup.NewClient(mc).CreateNetworkSecurityGroup( | ||
| name, | ||
| label, | ||
| d.Get("location").(string), | ||
| ) | ||
| if err != nil { | ||
| return fmt.Errorf("Error creating Network Security Group %s: %s", name, err) | ||
| } | ||
| if err := mc.WaitForOperation(req, nil); err != nil { | ||
| return fmt.Errorf( | ||
| "Error waiting for Network Security Group %s to be created: %s", name, err) | ||
| } | ||
| d.SetId(name) | ||
| // Create all rules that are configured | ||
| if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 { | ||
| // Create an empty schema.Set to hold all rules | ||
| rules := &schema.Set{ | ||
| F: resourceAzureSecurityGroupRuleHash, | ||
| } | ||
| for _, rule := range rs.List() { | ||
| // Create a single rule | ||
| err := resourceAzureSecurityGroupRuleCreate(d, meta, rule.(map[string]interface{})) | ||
| // We need to update this first to preserve the correct state | ||
| rules.Add(rule) | ||
| d.Set("rule", rules) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } | ||
| } | ||
| return resourceAzureSecurityGroupRead(d, meta) | ||
| } | ||
| func resourceAzureSecurityGroupRuleCreate( | ||
| d *schema.ResourceData, | ||
| meta interface{}, | ||
| rule map[string]interface{}) error { | ||
| mc := meta.(*Client).mgmtClient | ||
| // Make sure all required parameters are there | ||
| if err := verifySecurityGroupRuleParams(rule); err != nil { | ||
| return err | ||
| } | ||
| name := rule["name"].(string) | ||
| // Create the rule | ||
| req, err := networksecuritygroup.NewClient(mc).SetNetworkSecurityGroupRule(d.Id(), | ||
| networksecuritygroup.RuleRequest{ | ||
| Name: name, | ||
| Type: networksecuritygroup.RuleType(rule["type"].(string)), | ||
| Priority: rule["priority"].(int), | ||
| Action: networksecuritygroup.RuleAction(rule["action"].(string)), | ||
| SourceAddressPrefix: rule["source_cidr"].(string), | ||
| SourcePortRange: rule["source_port"].(string), | ||
| DestinationAddressPrefix: rule["destination_cidr"].(string), | ||
| DestinationPortRange: rule["destination_port"].(string), | ||
| Protocol: networksecuritygroup.RuleProtocol(rule["protocol"].(string)), | ||
| }, | ||
| ) | ||
| if err != nil { | ||
| return fmt.Errorf("Error creating Network Security Group rule %s: %s", name, err) | ||
| } | ||
| if err := mc.WaitForOperation(req, nil); err != nil { | ||
| return fmt.Errorf( | ||
| "Error waiting for Network Security Group rule %s to be created: %s", name, err) | ||
| } | ||
| return nil | ||
| } | ||
| func resourceAzureSecurityGroupRead(d *schema.ResourceData, meta interface{}) error { | ||
| mc := meta.(*Client).mgmtClient | ||
| sg, err := networksecuritygroup.NewClient(mc).GetNetworkSecurityGroup(d.Id()) | ||
| if err != nil { | ||
| if management.IsResourceNotFoundError(err) { | ||
| d.SetId("") | ||
| return nil | ||
| } | ||
| return fmt.Errorf("Error retrieving Network Security Group %s: %s", d.Id(), err) | ||
| } | ||
| d.Set("label", sg.Label) | ||
| d.Set("location", sg.Location) | ||
| // Create an empty schema.Set to hold all rules | ||
| rules := &schema.Set{ | ||
| F: resourceAzureSecurityGroupRuleHash, | ||
| } | ||
| for _, r := range sg.Rules { | ||
| if !r.IsDefault { | ||
| rule := map[string]interface{}{ | ||
| "name": r.Name, | ||
| "type": string(r.Type), | ||
| "priority": r.Priority, | ||
| "action": string(r.Action), | ||
| "source_cidr": r.SourceAddressPrefix, | ||
| "source_port": r.SourcePortRange, | ||
| "destination_cidr": r.DestinationAddressPrefix, | ||
| "destination_port": r.DestinationPortRange, | ||
| "protocol": string(r.Protocol), | ||
| } | ||
| rules.Add(rule) | ||
| } | ||
| } | ||
| d.Set("rule", rules) | ||
| return nil | ||
| } | ||
| func resourceAzureSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error { | ||
| // Check if the rule set as a whole has changed | ||
| if d.HasChange("rule") { | ||
| o, n := d.GetChange("rule") | ||
| ors := o.(*schema.Set).Difference(n.(*schema.Set)) | ||
| nrs := n.(*schema.Set).Difference(o.(*schema.Set)) | ||
| // Now first loop through all the old rules and delete any obsolete ones | ||
| for _, rule := range ors.List() { | ||
| // Delete the rule as it no longer exists in the config | ||
| err := resourceAzureSecurityGroupRuleDelete(d, meta, rule.(map[string]interface{})) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } | ||
| // Make sure we save the state of the currently configured rules | ||
| rules := o.(*schema.Set).Intersection(n.(*schema.Set)) | ||
| d.Set("rule", rules) | ||
| // Then loop through al the currently configured rules and create the new ones | ||
| for _, rule := range nrs.List() { | ||
| err := resourceAzureSecurityGroupRuleCreate(d, meta, rule.(map[string]interface{})) | ||
| // We need to update this first to preserve the correct state | ||
| rules.Add(rule) | ||
| d.Set("rule", rules) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } | ||
| } | ||
| return resourceAzureSecurityGroupRead(d, meta) | ||
| } | ||
| func resourceAzureSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error { | ||
| mc := meta.(*Client).mgmtClient | ||
| log.Printf("[DEBUG] Deleting Network Security Group: %s", d.Id()) | ||
| req, err := networksecuritygroup.NewClient(mc).DeleteNetworkSecurityGroup(d.Id()) | ||
| if err != nil { | ||
| return fmt.Errorf("Error deleting Network Security Group %s: %s", d.Id(), err) | ||
| } | ||
| // Wait until the network security group is deleted | ||
| if err := mc.WaitForOperation(req, nil); err != nil { | ||
| return fmt.Errorf( | ||
| "Error waiting for Network Security Group %s to be deleted: %s", d.Id(), err) | ||
| } | ||
| d.SetId("") | ||
| return nil | ||
| } | ||
| func resourceAzureSecurityGroupRuleDelete( | ||
| d *schema.ResourceData, meta interface{}, rule map[string]interface{}) error { | ||
| mc := meta.(*Client).mgmtClient | ||
| name := rule["name"].(string) | ||
| // Delete the rule | ||
| req, err := networksecuritygroup.NewClient(mc).DeleteNetworkSecurityGroupRule(d.Id(), name) | ||
| if err != nil { | ||
| if management.IsResourceNotFoundError(err) { | ||
| return nil | ||
| } | ||
| return fmt.Errorf("Error deleting Network Security Group rule %s: %s", name, err) | ||
| } | ||
| if err := mc.WaitForOperation(req, nil); err != nil { | ||
| return fmt.Errorf( | ||
| "Error waiting for Network Security Group rule %s to be deleted: %s", name, err) | ||
| } | ||
| return nil | ||
| } | ||
| func resourceAzureSecurityGroupRuleHash(v interface{}) int { | ||
| var buf bytes.Buffer | ||
| m := v.(map[string]interface{}) | ||
| buf.WriteString(fmt.Sprintf( | ||
| "%s-%d-%s-%s-%s-%s-%s-%s", | ||
| m["type"].(string), | ||
| m["priority"].(int), | ||
| m["action"].(string), | ||
| m["source_cidr"].(string), | ||
| m["source_port"].(string), | ||
| m["destination_cidr"].(string), | ||
| m["destination_port"].(string), | ||
| m["protocol"].(string))) | ||
| return hashcode.String(buf.String()) | ||
| } | ||
| func verifySecurityGroupRuleParams(rule map[string]interface{}) error { | ||
| typ := rule["type"].(string) | ||
| if typ != "Inbound" && typ != "Outbound" { | ||
| return fmt.Errorf("Parameter type only accepts 'Inbound' or 'Outbound' as values") | ||
| } | ||
| action := rule["action"].(string) | ||
| if action != "Allow" && action != "Deny" { | ||
| return fmt.Errorf("Parameter action only accepts 'Allow' or 'Deny' as values") | ||
| } | ||
| protocol := rule["protocol"].(string) | ||
| if protocol != "TCP" && protocol != "UDP" && protocol != "*" { | ||
| _, err := strconv.ParseInt(protocol, 0, 0) | ||
| if err != nil { | ||
| return fmt.Errorf( | ||
| "Parameter type only accepts 'TCP', 'UDP' or '*' as values") | ||
| } | ||
| } | ||
| return nil | ||
| } |
| @@ -0,0 +1,272 @@ | ||
| package azure | ||
| import ( | ||
| "fmt" | ||
| "testing" | ||
| "github.com/hashicorp/terraform/helper/resource" | ||
| "github.com/hashicorp/terraform/terraform" | ||
| "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().(*Client).mgmtClient | ||
| 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().(*Client).mgmtClient | ||
| 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" | ||
| } | ||
| }` |
| @@ -0,0 +1,357 @@ | ||
| package azure | ||
| import ( | ||
| "fmt" | ||
| "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" | ||
| ) | ||
| const ( | ||
| virtualNetworkRetrievalError = "Error retrieving Virtual Network Configuration: %s" | ||
| ) | ||
| func resourceAzureVirtualNetwork() *schema.Resource { | ||
| return &schema.Resource{ | ||
| Create: resourceAzureVirtualNetworkCreate, | ||
| Read: resourceAzureVirtualNetworkRead, | ||
| Update: resourceAzureVirtualNetworkUpdate, | ||
| Delete: resourceAzureVirtualNetworkDelete, | ||
| Schema: map[string]*schema.Schema{ | ||
| "name": &schema.Schema{ | ||
| Type: schema.TypeString, | ||
| Required: true, | ||
| ForceNew: true, | ||
| }, | ||
| "address_space": &schema.Schema{ | ||
| Type: schema.TypeList, | ||
| Required: true, | ||
| Elem: &schema.Schema{Type: schema.TypeString}, | ||
| }, | ||
| "subnet": &schema.Schema{ | ||
| 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{ | ||
| Type: schema.TypeString, | ||
| Required: true, | ||
| ForceNew: true, | ||
| }, | ||
| }, | ||
| } | ||
| } | ||
| func resourceAzureVirtualNetworkCreate(d *schema.ResourceData, meta interface{}) error { | ||
| ac := meta.(*Client) | ||
| mc := ac.mgmtClient | ||
| name := d.Get("name").(string) | ||
| // Lock the client just before we get the virtual network configuration and immediately | ||
| // set an defer to unlock the client again whenever this function exits | ||
| ac.mutex.Lock() | ||
| defer ac.mutex.Unlock() | ||
| nc, err := virtualnetwork.NewClient(mc).GetVirtualNetworkConfiguration() | ||
| if err != nil { | ||
| if strings.Contains(err.Error(), "ResourceNotFound") { | ||
| nc = virtualnetwork.NetworkConfiguration{} | ||
| } else { | ||
| return fmt.Errorf(virtualNetworkRetrievalError, err) | ||
| } | ||
| } | ||
| for _, n := range nc.Configuration.VirtualNetworkSites { | ||
| if n.Name == name { | ||
| return fmt.Errorf("Virtual Network %s already exists!", name) | ||
| } | ||
| } | ||
| network, err := createVirtualNetwork(d) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| nc.Configuration.VirtualNetworkSites = append(nc.Configuration.VirtualNetworkSites, network) | ||
| req, err := virtualnetwork.NewClient(mc).SetVirtualNetworkConfiguration(nc) | ||
| if err != nil { | ||
| return fmt.Errorf("Error creating Virtual Network %s: %s", name, err) | ||
| } | ||
| // Wait until the virtual network is created | ||
| if err := mc.WaitForOperation(req, nil); err != nil { | ||
| 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) | ||
| } | ||
| func resourceAzureVirtualNetworkRead(d *schema.ResourceData, meta interface{}) error { | ||
| mc := meta.(*Client).mgmtClient | ||
| nc, err := virtualnetwork.NewClient(mc).GetVirtualNetworkConfiguration() | ||
| if err != nil { | ||
| return fmt.Errorf(virtualNetworkRetrievalError, err) | ||
| } | ||
| for _, n := range nc.Configuration.VirtualNetworkSites { | ||
| if n.Name == d.Id() { | ||
| d.Set("address_space", n.AddressSpace.AddressPrefix) | ||
| d.Set("location", n.Location) | ||
| // Create a new set to hold all configured subnets | ||
| subnets := &schema.Set{ | ||
| F: resourceAzureSubnetHash, | ||
| } | ||
| // Loop through all endpoints | ||
| for _, s := range n.Subnets { | ||
| 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) | ||
| return nil | ||
| } | ||
| } | ||
| log.Printf("[DEBUG] Virtual Network %s does no longer exist", d.Id()) | ||
| d.SetId("") | ||
| return nil | ||
| } | ||
| func resourceAzureVirtualNetworkUpdate(d *schema.ResourceData, meta interface{}) error { | ||
| ac := meta.(*Client) | ||
| mc := ac.mgmtClient | ||
| // Lock the client just before we get the virtual network configuration and immediately | ||
| // set an defer to unlock the client again whenever this function exits | ||
| ac.mutex.Lock() | ||
| defer ac.mutex.Unlock() | ||
| nc, err := virtualnetwork.NewClient(mc).GetVirtualNetworkConfiguration() | ||
| if err != nil { | ||
| return fmt.Errorf(virtualNetworkRetrievalError, err) | ||
| } | ||
| found := false | ||
| for i, n := range nc.Configuration.VirtualNetworkSites { | ||
| if n.Name == d.Id() { | ||
| network, err := createVirtualNetwork(d) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| nc.Configuration.VirtualNetworkSites[i] = network | ||
| found = true | ||
| } | ||
| } | ||
| if !found { | ||
| return fmt.Errorf("Virtual Network %s does not exists!", d.Id()) | ||
| } | ||
| req, err := virtualnetwork.NewClient(mc).SetVirtualNetworkConfiguration(nc) | ||
| if err != nil { | ||
| return fmt.Errorf("Error updating Virtual Network %s: %s", d.Id(), err) | ||
| } | ||
| // Wait until the virtual network is updated | ||
| if err := mc.WaitForOperation(req, nil); err != nil { | ||
| 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) | ||
| } | ||
| func resourceAzureVirtualNetworkDelete(d *schema.ResourceData, meta interface{}) error { | ||
| ac := meta.(*Client) | ||
| mc := ac.mgmtClient | ||
| // Lock the client just before we get the virtual network configuration and immediately | ||
| // set an defer to unlock the client again whenever this function exits | ||
| ac.mutex.Lock() | ||
| defer ac.mutex.Unlock() | ||
| nc, err := virtualnetwork.NewClient(mc).GetVirtualNetworkConfiguration() | ||
| if err != nil { | ||
| return fmt.Errorf(virtualNetworkRetrievalError, err) | ||
| } | ||
| filtered := nc.Configuration.VirtualNetworkSites[:0] | ||
| for _, n := range nc.Configuration.VirtualNetworkSites { | ||
| if n.Name != d.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", d.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", d.Id(), err) | ||
| } | ||
| d.SetId("") | ||
| 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) | ||
| if err != nil { | ||
| return virtualnetwork.VirtualNetworkSite{}, fmt.Errorf("Error decoding address_space: %s", err) | ||
| } | ||
| addressSpace := virtualnetwork.AddressSpace{ | ||
| AddressPrefix: addressPrefix, | ||
| } | ||
| // 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{ | ||
| Name: d.Get("name").(string), | ||
| Location: d.Get("location").(string), | ||
| AddressSpace: addressSpace, | ||
| Subnets: subnets, | ||
| }, nil | ||
| } | ||
| func associateSecurityGroups(d *schema.ResourceData, meta interface{}) error { | ||
| mc := meta.(*Client).mgmtClient | ||
| nsgClient := networksecuritygroup.NewClient(mc) | ||
| 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 := nsgClient.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 := nsgClient.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 := nsgClient.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 | ||
| } |
| @@ -0,0 +1,295 @@ | ||
| package azure | ||
| import ( | ||
| "fmt" | ||
| "testing" | ||
| "github.com/hashicorp/terraform/helper/resource" | ||
| "github.com/hashicorp/terraform/terraform" | ||
| "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().(*Client).mgmtClient | ||
| 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().(*Client).mgmtClient | ||
| 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}" | ||
| } | ||
| }` | ||
| @@ -0,0 +1 @@ | ||
| package azure |
| @@ -0,0 +1,48 @@ | ||
| --- | ||
| layout: "azure" | ||
| page_title: "Provider: Azure" | ||
| sidebar_current: "docs-azure-index" | ||
| description: |- | ||
| The Azure provider is used to interact with the many resources supported by Azure. The provider needs to be configured with a publish settings file and optionally a subscription ID before it can be used. | ||
| --- | ||
| # Azure Provider | ||
| The Azure provider is used to interact with the many resources supported | ||
| by Azure. The provider needs to be configured with a [publish settings | ||
| file](https://manage.windowsazure.com/publishsettings) and optionally a | ||
| subscription ID before it can be used. | ||
| Use the navigation to the left to read about the available resources. | ||
| ## Example Usage | ||
| ``` | ||
| # Configure the Azure Provider | ||
| provider "azure" { | ||
| settings_file = "${var.azure_settings_file}" | ||
| } | ||
| # Create a web server | ||
| resource "azure_instance" "web" { | ||
| ... | ||
| } | ||
| ``` | ||
| ## Argument Reference | ||
| The following arguments are supported: | ||
| * `settings_file` - (Optional) The path to a publish settings file used to | ||
| authenticate with the Azure API. You can download the settings file here: | ||
| https://manage.windowsazure.com/publishsettings. You must either provide | ||
| (or source from the `AZURE_SETTINGS_FILE` environment variable) a settings | ||
| file or both a `subscription_id` and `certificate`. | ||
| * `subscription_id` - (Optional) The subscription ID to use. If a | ||
| `settings_file` is not provided `subscription_id` is required. It can also | ||
| be sourced from the `AZURE_SUBSCRIPTION_ID` environment variable. | ||
| * `certificate` - (Optional) The certificate used to authenticate with the | ||
| Azure API. If a `settings_file` is not provided `certificate` is required. | ||
| It can also be sourced from the `AZURE_CERTIFICATE` environment variable. |
| @@ -0,0 +1,70 @@ | ||
| --- | ||
| layout: "azure" | ||
| page_title: "Azure: azure_data_disk" | ||
| sidebar_current: "docs-azure-resource-data-disk" | ||
| description: |- | ||
| Adds a data disk to a virtual machine. If the name of an existing disk is given, it will attach that disk. Otherwise it will create and attach a new empty disk. | ||
| --- | ||
| # azure\_data\_disk | ||
| Adds a data disk to a virtual machine. If the name of an existing disk is given, | ||
| it will attach that disk. Otherwise it will create and attach a new empty disk. | ||
| ## Example Usage | ||
| ``` | ||
| resource "azure_data_disk" "data" { | ||
| lun = 0 | ||
| size = 10 | ||
| storage = "yourstorage" | ||
| virtual_machine = "server1" | ||
| } | ||
| ``` | ||
| ## Argument Reference | ||
| The following arguments are supported: | ||
| * `name` - (Optional) The name of an existing registered disk to attach to the | ||
| virtual machine. If left empty, a new empty disk will be created and | ||
| attached instead. Changing this forces a new resource to be created. | ||
| * `label` - (Optional) The identifier of the data disk. Changing this forces a | ||
| new resource to be created (defaults to "virtual_machine-lun") | ||
| * `lun` - (Required) The Logical Unit Number (LUN) for the disk. The LUN | ||
| specifies the slot in which the data drive appears when mounted for usage | ||
| by the virtual machine. Valid LUN values are 0 through 31. | ||
| * `size` - (Optional) The size, in GB, of an empty disk to be attached to the | ||
| virtual machine. Required when creating a new disk, not used otherwise. | ||
| * `caching` - (Optional) The caching behavior of data disk. Valid options are: | ||
| `None`, `ReadOnly` and `ReadWrite` (defaults `None`) | ||
| * `storage ` - (Optional) The name of an existing storage account within the | ||
| subscription which will be used to store the VHD of this disk. Required | ||
| if no value is supplied for `media_link`. Changing this forces a new | ||
| resource to be created. | ||
| * `media_link` - (Optional) The location of the blob in storage where the VHD | ||
| of this disk will be created. The storage account where must be associated | ||
| with the subscription. Changing this forces a new resource to be created. | ||
| * `source_media_link` - (Optional) The location of a blob in storage where a | ||
| VHD file is located that is imported and registered as a disk. If a value | ||
| is supplied, `media_link` will not be used. | ||
| * `virtual_machine` - (Required) The name of the virtual machine the disk will | ||
| be attached to. | ||
| ## Attributes Reference | ||
| The following attributes are exported: | ||
| * `id` - The security group ID. | ||
| * `name` - The name of the disk. | ||
| * `label` - The identifier for the disk. | ||
| * `media_link` - The location of the blob in storage where the VHD of this disk | ||
| is created. |
| @@ -0,0 +1,119 @@ | ||
| --- | ||
| layout: "azure" | ||
| page_title: "Azure: azure_instance" | ||
| sidebar_current: "docs-azure-resource-instance" | ||
| description: |- | ||
| Creates a hosted service, role and deployment and then creates a virtual machine in the deployment based on the specified configuration. | ||
| --- | ||
| # azure\_instance | ||
| Creates a hosted service, role and deployment and then creates a virtual | ||
| machine in the deployment based on the specified configuration. | ||
| ## Example Usage | ||
| ``` | ||
| resource "azure_instance" "web" { | ||
| name = "terraform-test" | ||
| image = "Ubuntu Server 14.04 LTS" | ||
| size = "Basic_A1" | ||
| storage = "yourstorage" | ||
| location = "West US" | ||
| username = "terraform" | ||
| password = "Pass!admin123" | ||
| endpoint { | ||
| name = "SSH" | ||
| protocol = "tcp" | ||
| public_port = 22 | ||
| private_port = 22 | ||
| } | ||
| } | ||
| ``` | ||
| ## Argument Reference | ||
| The following arguments are supported: | ||
| * `name` - (Required) The name of the instance. Changing this forces a new | ||
| resource to be created. | ||
| * `description` - (Optional) The description for the associated hosted service. | ||
| Changing this forces a new resource to be created (defaults to the instance | ||
| name). | ||
| * `image` - (Required) The name of an existing VM or OS image to use for this | ||
| instance. Changing this forces a new resource to be created. | ||
| * `size` - (Required) The size of the instance. | ||
| * `subnet` - (Optional) The name of the subnet to connect this instance to. If | ||
| a value is supplied `virtual_network` is required. Changing this forces a | ||
| new resource to be created. | ||
| * `virtual_network` - (Optional) The name of the virtual network the `subnet` | ||
| belongs to. If a value is supplied `subnet` is required. Changing this | ||
| forces a new resource to be created. | ||
| * `storage` - (Optional) The name of an existing storage account within the | ||
| subscription which will be used to store the VHDs of this instance. | ||
| Changing this forces a new resource to be created. | ||
| * `reverse_dns` - (Optional) The DNS address to which the IP address of the | ||
| hosted service resolves when queried using a reverse DNS query. Changing | ||
| this forces a new resource to be created. | ||
| * `location` - (Required) The location/region where the cloud service is | ||
| created. Changing this forces a new resource to be created. | ||
| * `automatic_updates` - (Optional) If true this will enable automatic updates. | ||
| This attribute is only used when creating a Windows instance. Changing this | ||
| forces a new resource to be created (defaults false) | ||
| * `time_zone` - (Optional) The appropriate time zone for this instance in the | ||
| format 'America/Los_Angeles'. This attribute is only used when creating a | ||
| Windows instance. Changing this forces a new resource to be created | ||
| (defaults false) | ||
| * `username` - (Required) The username of a new user that will be created while | ||
| creating the instance. Changing this forces a new resource to be created. | ||
| * `password` - (Optional) The password of the new user that will be created | ||
| while creating the instance. Required when creating a Windows instance or | ||
| when not supplying an `ssh_key_thumbprint` while creating a Linux instance. | ||
| Changing this forces a new resource to be created. | ||
| * `ssh_key_thumbprint` - (Optional) The SSH thumbprint of an existing SSH key | ||
| within the subscription. This attribute is only used when creating a Linux | ||
| instance. Changing this forces a new resource to be created. | ||
| * `security_group` - (Optional) The Network Security Group to associate with | ||
| this instance. | ||
| * `endpoint` - (Optional) Can be specified multiple times to define multiple | ||
| endpoints. Each `endpoint` block supports fields documented below. | ||
| The `endpoint` block supports: | ||
| * `name` - (Required) The name of the external endpoint. | ||
| * `protocol` - (Optional) The transport protocol for the endpoint. Valid | ||
| options are: `tcp` and `udp` (defaults `tcp`) | ||
| * `public_port` - (Required) The external port to use for the endpoint. | ||
| * `private_port` - (Required) The private port on which the instance is | ||
| listening. | ||
| ## Attributes Reference | ||
| The following attributes are exported: | ||
| * `id` - The instance ID. | ||
| * `description` - The description for the associated hosted service. | ||
| * `subnet` - The subnet the instance is connected to. | ||
| * `endpoint` - The complete set of configured endpoints. | ||
| * `security_group` - The associated Network Security Group. | ||
| * `ip_address` - The private IP address assigned to the instance. | ||
| * `vip_address` - The public IP address assigned to the instance. |
| @@ -0,0 +1,84 @@ | ||
| --- | ||
| layout: "azure" | ||
| page_title: "Azure: azure_security_group" | ||
| sidebar_current: "docs-azure-resource-security-group" | ||
| description: |- | ||
| Creates a new network security group within the context of the specified subscription. | ||
| --- | ||
| # azure\_security\_group | ||
| Creates a new network security group within the context of the specified | ||
| subscription. | ||
| ## Example Usage | ||
| ``` | ||
| resource "azure_security_group" "web" { | ||
| name = "webservers" | ||
| location = "West US" | ||
| rule { | ||
| name = "HTTPS" | ||
| priority = 101 | ||
| source_cidr = "*" | ||
| source_port = "*" | ||
| destination_cidr = "*" | ||
| destination_port = "443" | ||
| protocol = "TCP" | ||
| } | ||
| } | ||
| ``` | ||
| ## Argument Reference | ||
| The following arguments are supported: | ||
| * `name` - (Required) The name of the security group. Changing this forces a | ||
| new resource to be created. | ||
| * `label` - (Optional) The identifier for the security group. The label can be | ||
| up to 1024 characters long. Changing this forces a new resource to be | ||
| created (defaults to the security group name) | ||
| * `location` - (Required) The location/region where the security group is | ||
| created. Changing this forces a new resource to be created. | ||
| * `rule` - (Required) Can be specified multiple times to define multiple | ||
| rules. Each `rule` block supports fields documented below. | ||
| The `rule` block supports: | ||
| * `name` - (Required) The name of the security rule. | ||
| * `type ` - (Optional) The type of the security rule. Valid options are: | ||
| `Inbound` and `Outbound` (defaults `Inbound`) | ||
| * `priority` - (Required) The priority of the network security rule. Rules with | ||
| lower priority are evaluated first. This value can be between 100 and 4096. | ||
| * `action` - (Optional) The action that is performed when the security rule is | ||
| matched. Valid options are: `Allow` and `Deny` (defaults `Allow`) | ||
| * `source_cidr` - (Required) The CIDR or source IP range. An asterisk (\*) can | ||
| also be used to match all source IPs. | ||
| * `source_port` - (Required) The source port or range. This value can be | ||
| between 0 and 65535. An asterisk (\*) can also be used to match all ports. | ||
| * `destination_cidr` - (Required) The CIDR or destination IP range. An asterisk | ||
| (\*) can also be used to match all destination IPs. | ||
| * `destination_port` - (Required) The destination port or range. This value can | ||
| be between 0 and 65535. An asterisk (\*) can also be used to match all | ||
| ports. | ||
| * `protocol` - (Optional) The protocol of the security rule. Valid options are: | ||
| `TCP`, `UDP` and `*` (defaults `TCP`) | ||
| ## Attributes Reference | ||
| The following attributes are exported: | ||
| * `id` - The security group ID. | ||
| * `label` - The identifier for the security group. |
| @@ -0,0 +1,59 @@ | ||
| --- | ||
| layout: "azure" | ||
| page_title: "Azure: azure_virtual_network" | ||
| sidebar_current: "docs-azure-resource-virtual-network" | ||
| description: |- | ||
| Creates a new virtual network including any configured subnets. Each subnet can optionally be configured with a security group to be associated with the subnet. | ||
| --- | ||
| # azure\_virtual\_network | ||
| Creates a new virtual network including any configured subnets. Each subnet can | ||
| optionally be configured with a security group to be associated with the subnet. | ||
| ## Example Usage | ||
| ``` | ||
| resource "azure_virtual_network" "default" { | ||
| name = "test-network" | ||
| address_space = ["10.1.2.0/24"] | ||
| location = "West US" | ||
| subnet { | ||
| name = "subnet1" | ||
| address_prefix = "10.1.2.0/25" | ||
| } | ||
| } | ||
| ``` | ||
| ## Argument Reference | ||
| The following arguments are supported: | ||
| * `name` - (Required) The name of the virtual network. Changing this forces a | ||
| new resource to be created. | ||
| * `address_space` - (Required) The address space that is used the virtual | ||
| network. You can supply more than one address space. Changing this forces | ||
| a new resource to be created. | ||
| * `location` - (Required) The location/region where the virtual network is | ||
| created. Changing this forces a new resource to be created. | ||
| * `subnet` - (Required) Can be specified multiple times to define multiple | ||
| subnets. Each `subnet` block supports fields documented below. | ||
| The `subnet` block supports: | ||
| * `name` - (Required) The name of the subnet. | ||
| * `address_prefix` - (Required) The address prefix to use for the subnet. | ||
| * `security_group` - (Optional) The Network Security Group to associate with | ||
| the subnet. | ||
| ## Attributes Reference | ||
| The following attributes are exported: | ||
| * `id` - The virtual NetworkConfiguration ID. |
| @@ -0,0 +1,38 @@ | ||
| <% wrap_layout :inner do %> | ||
| <% content_for :sidebar do %> | ||
| <div class="docs-sidebar hidden-print affix-top" role="complementary"> | ||
| <ul class="nav docs-sidenav"> | ||
| <li<%= sidebar_current("docs-home") %>> | ||
| <a href="/docs/providers/index.html">« Documentation Home</a> | ||
| </li> | ||
| <li<%= sidebar_current("docs-azure-index") %>> | ||
| <a href="/docs/providers/azure/index.html">Azure Provider</a> | ||
| </li> | ||
| <li<%= sidebar_current(/^docs-azure-resource/) %>> | ||
| <a href="#">Resources</a> | ||
| <ul class="nav nav-visible"> | ||
| <li<%= sidebar_current("docs-azure-resource-data-disk") %>> | ||
| <a href="/docs/providers/azure/r/data_disk.html">azure_data_disk</a> | ||
| </li> | ||
| <li<%= sidebar_current("docs-azure-resource-instance") %>> | ||
| <a href="/docs/providers/azure/r/instance.html">azure_instance</a> | ||
| </li> | ||
| <li<%= sidebar_current("docs-azure-resource-security-group") %>> | ||
| <a href="/docs/providers/azure/r/security_group.html">azure_security_group</a> | ||
| </li> | ||
| <li<%= sidebar_current("docs-azure-resource-virtual-network") %>> | ||
| <a href="/docs/providers/azure/r/virtual_network.html">azure_virtual_network</a> | ||
| </li> | ||
| </ul> | ||
| </li> | ||
| </ul> | ||
| </div> | ||
| <% end %> | ||
| <%= yield %> | ||
| <% end %> |
This "sometimes a disk, sometimes just an attachment" modeling feels a bit muddy to me, but it matches the upstream's API, so this seems good for now.