View
@@ -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.

This comment has been minimized.

@phinze

phinze Jun 2, 2015

Member

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.

@phinze

phinze Jun 2, 2015

Member

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.

This comment has been minimized.

@svanharmelen

svanharmelen Jun 2, 2015

Collaborator

Yeah, I understand what you mean but indeed this follows the API... Maybe we can/should add some more attributes to make it more conscious choices/behaviour?

But I also think it's good for now, so I'll leave this as is for the time being.

@svanharmelen

svanharmelen Jun 2, 2015

Collaborator

Yeah, I understand what you mean but indeed this follows the API... Maybe we can/should add some more attributes to make it more conscious choices/behaviour?

But I also think it's good for now, so I'll leave this as is for the time being.

_, 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
}
View
@@ -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"))
View

Large diffs are not rendered by default.

Oops, something went wrong.
View

Large diffs are not rendered by default.

Oops, something went wrong.
View
@@ -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
}
View
@@ -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"
}
}`
View
@@ -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
}
View
@@ -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 {

This comment has been minimized.

@aznashwan

aznashwan Jun 3, 2015

Contributor

Shouldn't this function just check if the resource still exists instead of actually deleting it itself?

@aznashwan

aznashwan Jun 3, 2015

Contributor

Shouldn't this function just check if the resource still exists instead of actually deleting it itself?

This comment has been minimized.

@phinze

phinze Jun 3, 2015

Member

Yep that's correct. Good catch!

@phinze

phinze Jun 3, 2015

Member

Yep that's correct. Good catch!

This comment has been minimized.

@svanharmelen

svanharmelen Jun 4, 2015

Collaborator

Indeed a good catch! I think I might have used this (wrong) approach in the CloudStack provider as well. Will walk through both providers and update the tests where applicable...

@svanharmelen

svanharmelen Jun 4, 2015

Collaborator

Indeed a good catch! I think I might have used this (wrong) approach in the CloudStack provider as well. Will walk through both providers and update the tests where applicable...

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}"
}
}`
View
@@ -0,0 +1 @@
package azure
View
@@ -7,22 +7,23 @@ body.page-sub{
}
body.layout-atlas,
body.layout-aws,
body.layout-azure,
body.layout-cloudflare,
body.layout-cloudstack,
body.layout-consul,
body.layout-dnsimple,
body.layout-digitalocean,
body.layout-dme,
body.layout-dnsimple,
body.layout-docker,
body.layout-cloudflare,
body.layout-cloudstack,
body.layout-google,
body.layout-heroku,
body.layout-mailgun,
body.layout-openstack,
body.layout-template,
body.layout-digitalocean,
body.layout-aws,
body.layout-docs,
body.layout-inner,
body.layout-downloads,
body.layout-inner,
body.layout-intro{
background: $light-black image-url('sidebar-wire.png') left 62px no-repeat;
@@ -287,4 +288,3 @@ body.layout-intro{
}
}
}
View
@@ -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.
View
@@ -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.
View
@@ -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.
View
@@ -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.
View
@@ -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.
View
@@ -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">&laquo; 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 %>
View
@@ -119,19 +119,23 @@
<ul class="nav">
<li<%= sidebar_current("docs-providers-atlas") %>>
<a href="/docs/providers/atlas/index.html">Atlas</a>
</li>
</li>
<li<%= sidebar_current("docs-providers-aws") %>>
<a href="/docs/providers/aws/index.html">AWS</a>
</li>
<li<%= sidebar_current("docs-providers-azure") %>>
<a href="/docs/providers/azure/index.html">Azure</a>
</li>
<li<%= sidebar_current("docs-providers-cloudflare") %>>
<a href="/docs/providers/cloudflare/index.html">CloudFlare</a>
</li>
</li>
<li<%= sidebar_current("docs-providers-cloudstack") %>>
<a href="/docs/providers/cloudstack/index.html">CloudStack</a>
</li>
</li>
<li<%= sidebar_current("docs-providers-consul") %>>
<a href="/docs/providers/consul/index.html">Consul</a>