Needs more testing and tests, but it's becoming a nice provider

This commit is contained in:
Sander van Harmelen 2015-04-30 10:58:45 +02:00
parent 84a870a255
commit 4e33d898e9
9 changed files with 791 additions and 258 deletions

View File

@ -23,9 +23,10 @@ func Provider() terraform.ResourceProvider {
}, },
ResourcesMap: map[string]*schema.Resource{ ResourcesMap: map[string]*schema.Resource{
"azure_disk": resourceAzureDisk(), "azure_disk": resourceAzureDisk(),
"azure_instance": resourceAzureInstance(), "azure_instance": resourceAzureInstance(),
"azure_network": resourceAzureNetwork(), "azure_security_group": resourceAzureSecurityGroup(),
"azure_virtual_network": resourceAzureVirtualNetwork(),
}, },
ConfigureFunc: providerConfigure, ConfigureFunc: providerConfigure,

View File

@ -4,10 +4,10 @@ import "github.com/hashicorp/terraform/helper/schema"
func resourceAzureDisk() *schema.Resource { func resourceAzureDisk() *schema.Resource {
return &schema.Resource{ return &schema.Resource{
Create: resourceAzureNetworkCreate, Create: resourceAzureDiskCreate,
Read: resourceAzureNetworkRead, Read: resourceAzureDiskRead,
Update: resourceAzureNetworkUpdate, Update: resourceAzureDiskUpdate,
Delete: resourceAzureNetworkDelete, Delete: resourceAzureDiskDelete,
Schema: map[string]*schema.Schema{ Schema: map[string]*schema.Schema{
"name": &schema.Schema{ "name": &schema.Schema{
@ -19,7 +19,7 @@ func resourceAzureDisk() *schema.Resource {
} }
} }
func resourceAzureDiskCreate(d *schema.ResourceData, meta interface{}) (err error) { func resourceAzureDiskCreate(d *schema.ResourceData, meta interface{}) error {
//mc := meta.(*management.Client) //mc := meta.(*management.Client)
return resourceAzureDiskRead(d, meta) return resourceAzureDiskRead(d, meta)

View File

@ -52,7 +52,13 @@ func resourceAzureInstance() *schema.Resource {
Required: true, Required: true,
}, },
"network": &schema.Schema{ "subnet": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"virtual_network": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
ForceNew: true, ForceNew: true,
@ -88,18 +94,6 @@ func resourceAzureInstance() *schema.Resource {
ForceNew: true, ForceNew: true,
}, },
"public_rdp": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
ForceNew: true,
},
"public_ssh": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
ForceNew: true,
},
"username": &schema.Schema{ "username": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Required: true,
@ -134,12 +128,12 @@ func resourceAzureInstance() *schema.Resource {
Required: true, Required: true,
}, },
"port": &schema.Schema{ "public_port": &schema.Schema{
Type: schema.TypeInt, Type: schema.TypeInt,
Required: true, Required: true,
}, },
"local_port": &schema.Schema{ "private_port": &schema.Schema{
Type: schema.TypeInt, Type: schema.TypeInt,
Required: true, Required: true,
}, },
@ -148,6 +142,16 @@ func resourceAzureInstance() *schema.Resource {
Set: resourceAzureEndpointHash, Set: resourceAzureEndpointHash,
}, },
"security_groups": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: func(v interface{}) int {
return hashcode.String(v.(string))
},
},
"ip_address": &schema.Schema{ "ip_address": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Computed: true, Computed: true,
@ -162,177 +166,165 @@ func resourceAzureInstance() *schema.Resource {
} }
func resourceAzureInstanceCreate(d *schema.ResourceData, meta interface{}) (err error) { func resourceAzureInstanceCreate(d *schema.ResourceData, meta interface{}) (err error) {
mc := meta.(*management.Client) /*
mc := meta.(*management.Client)
name := d.Get("name").(string) name := d.Get("name").(string)
// Compute/set the description // Compute/set the description
description := d.Get("description").(string) description := d.Get("description").(string)
if description == "" { if description == "" {
description = name description = name
}
// Retrieve the needed details of the image
imageName, imageURL, osType, err := retrieveImageDetails(mc, d.Get("image").(string))
if err != nil {
return err
}
if imageURL == "" {
storage, ok := d.GetOk("storage")
if !ok {
return fmt.Errorf("When using a platform image, the 'storage' parameter is required")
} }
imageURL = fmt.Sprintf("http://%s.blob.core.windows.net/vhds/%s.vhd", storage, name)
}
// Verify if we have all parameters required for the image OS type // Retrieve the needed details of the image
if err := verifyParameters(d, osType); err != nil { imageName, imageURL, osType, err := retrieveImageDetails(mc, d.Get("image").(string))
return err
}
log.Printf("[DEBUG] Creating Cloud Service for instance: %s", name)
req, err := hostedservice.NewClient(*mc).
CreateHostedService(
name,
d.Get("location").(string),
d.Get("reverse_dns").(string),
name,
fmt.Sprintf("Cloud Service created automatically for instance %s", name),
)
if err != nil {
return fmt.Errorf("Error creating Cloud Service for instance %s: %s", name, err)
}
// Wait until the Cloud Service is created
if err := mc.WaitAsyncOperation(req); err != nil {
return fmt.Errorf(
"Error waiting for Cloud Service of instance %s to be created: %s", name, err)
}
// Put in this defer here, so we are sure to cleanup already created parts
// when we exit with an error
defer func(mc *management.Client) {
if err != nil { if err != nil {
req, err := hostedservice.NewClient(*mc).DeleteHostedService(name, true) return err
if err != nil { }
log.Printf("[DEBUG] Error cleaning up Cloud Service of instance %s: %s", name, err)
if imageURL == "" {
storage, ok := d.GetOk("storage")
if !ok {
return fmt.Errorf("When using a platform image, the 'storage' parameter is required")
} }
imageURL = fmt.Sprintf("http://%s.blob.core.windows.net/vhds/%s.vhd", storage, name)
// Wait until the Cloud Service is deleted
if err := mc.WaitAsyncOperation(req); err != nil {
log.Printf(
"[DEBUG] Error waiting for Cloud Service of instance %s to be deleted : %s", name, err)
}
}
}(mc)
// Create a new role for the instance
role := vmutils.NewVmConfiguration(name, d.Get("size").(string))
log.Printf("[DEBUG] Configuring deployment from image...")
err = vmutils.ConfigureDeploymentFromPlatformImage(
&role,
imageName,
imageURL,
d.Get("image").(string),
)
if err != nil {
return fmt.Errorf("Error configuring the deployment for %s: %s", name, err)
}
if osType == linux {
// This is pretty ugly, but the Azure SDK leaves me no other choice...
if tp, ok := d.GetOk("ssh_key_thumbprint"); ok {
err = vmutils.ConfigureForLinux(
&role,
name,
d.Get("username").(string),
d.Get("password").(string),
tp.(string),
)
} else {
err = vmutils.ConfigureForLinux(
&role,
name,
d.Get("username").(string),
d.Get("password").(string),
)
}
if err != nil {
return fmt.Errorf("Error configuring %s for Linux: %s", name, err)
} }
if d.Get("public_ssh").(bool) { // Verify if we have all parameters required for the image OS type
if err := vmutils.ConfigureWithPublicSSH(&role); err != nil { if err := verifyParameters(d, osType); err != nil {
return fmt.Errorf("Error configuring %s for public SSH: %s", name, err) return err
}
} }
}
if osType == windows { log.Printf("[DEBUG] Creating Cloud Service for instance: %s", name)
err = vmutils.ConfigureForWindows( req, err := hostedservice.NewClient(*mc).
&role, CreateHostedService(
name, name,
d.Get("username").(string), d.Get("location").(string),
d.Get("password").(string), d.Get("reverse_dns").(string),
d.Get("automatic_updates").(bool), name,
d.Get("time_zone").(string), fmt.Sprintf("Cloud Service created automatically for instance %s", name),
) )
if err != nil { if err != nil {
return fmt.Errorf("Error configuring %s for Windows: %s", name, err) return fmt.Errorf("Error creating Cloud Service for instance %s: %s", name, err)
} }
if d.Get("public_rdp").(bool) { // Wait until the Cloud Service is created
if err := vmutils.ConfigureWithPublicRDP(&role); err != nil { if err := mc.WaitAsyncOperation(req); err != nil {
return fmt.Errorf("Error configuring %s for public RDP: %s", name, err) return fmt.Errorf(
} "Error waiting for Cloud Service of instance %s to be created: %s", name, err)
} }
}
log.Printf("[DEBUG] Creating the new instance...") // Put in this defer here, so we are sure to cleanup already created parts
req, err = virtualmachine.NewClient(*mc).CreateDeployment(role, name) // when we exit with an error
if err != nil { defer func(mc *management.Client) {
return fmt.Errorf("Error creating instance %s: %s", name, err) if err != nil {
} req, err := hostedservice.NewClient(*mc).DeleteHostedService(name, true)
if err != nil {
log.Printf("[DEBUG] Waiting for the new instance to be created...") log.Printf("[DEBUG] Error cleaning up Cloud Service of instance %s: %s", name, err)
if err := mc.WaitAsyncOperation(req); err != nil {
return fmt.Errorf(
"Error waiting for instance %s to be created: %s", name, err)
}
/*
if v := d.Get("endpoint").(*schema.Set); v.Len() > 0 {
log.Printf("[DEBUG] Adding Endpoints to the Azure Virtual Machine...")
endpoints := make([]vmClient.InputEndpoint, v.Len())
for i, v := range v.List() {
m := v.(map[string]interface{})
endpoint := vmClient.InputEndpoint{}
endpoint.Name = m["name"].(string)
endpoint.Protocol = m["protocol"].(string)
endpoint.Port = m["port"].(int)
endpoint.LocalPort = m["local_port"].(int)
endpoints[i] = endpoint
}
configSets := vmConfig.ConfigurationSets.ConfigurationSet
if len(configSets) == 0 {
return fmt.Errorf("Azure virtual machine does not have configuration sets")
}
for i := 0; i < len(configSets); i++ {
if configSets[i].ConfigurationSetType != "NetworkConfiguration" {
continue
} }
configSets[i].InputEndpoints.InputEndpoint =
append(configSets[i].InputEndpoints.InputEndpoint, endpoints...) // Wait until the Cloud Service is deleted
if err := mc.WaitAsyncOperation(req); err != nil {
log.Printf(
"[DEBUG] Error waiting for Cloud Service of instance %s to be deleted : %s", name, err)
}
}
}(mc)
// Create a new role for the instance
role := vmutils.NewVmConfiguration(name, d.Get("size").(string))
log.Printf("[DEBUG] Configuring deployment from image...")
err = vmutils.ConfigureDeploymentFromPlatformImage(
&role,
imageName,
imageURL,
d.Get("image").(string),
)
if err != nil {
return fmt.Errorf("Error configuring the deployment for %s: %s", name, err)
}
if osType == linux {
// This is pretty ugly, but the Azure SDK leaves me no other choice...
if tp, ok := d.GetOk("ssh_key_thumbprint"); ok {
err = vmutils.ConfigureForLinux(
&role,
name,
d.Get("username").(string),
d.Get("password").(string),
tp.(string),
)
} else {
err = vmutils.ConfigureForLinux(
&role,
name,
d.Get("username").(string),
d.Get("password").(string),
)
}
if err != nil {
return fmt.Errorf("Error configuring %s for Linux: %s", name, err)
} }
} }
if osType == windows {
err = vmutils.ConfigureForWindows(
&role,
name,
d.Get("username").(string),
d.Get("password").(string),
d.Get("automatic_updates").(bool),
d.Get("time_zone").(string),
)
if err != nil {
return fmt.Errorf("Error configuring %s for Windows: %s", name, err)
}
}
if s := d.Get("endpoint").(*schema.Set); s.Len() > 0 {
for _, v := range s.List() {
m := v.(map[string]interface{})
err := vmutils.ConfigureWithExternalPort(
&role,
m["name"].(string),
m["private_port"].(int),
m["public_port"].(int),
endpointProtocol(m["protocol"].(string)),
)
if err != nil {
return fmt.Errorf(
"Error adding endpoint %s for instance %s: %s", m["name"].(string), name, err)
}
}
}
err = vmutils.ConfigureForSubnet(&role, d.Get("subnet").(string))
if err != nil {
return fmt.Errorf(
"Error adding role to subnet %s for instance %s: %s", d.Get("subnet").(string), name, err)
}
options := &virtualmachine.CreateDeploymentOptions{
Subnet: d.Get("subnet").(string),
VirtualNetworkName: d.Get("virtual_network").(string),
}
log.Printf("[DEBUG] Creating the new instance...")
req, err = virtualmachine.NewClient(*mc).CreateDeployment(role, name, options)
if err != nil {
return fmt.Errorf("Error creating instance %s: %s", name, err)
}
log.Printf("[DEBUG] Waiting for the new instance to be created...")
if err := mc.WaitAsyncOperation(req); err != nil {
return fmt.Errorf(
"Error waiting for instance %s to be created: %s", name, err)
}
d.SetId(name)
*/ */
d.SetId(name)
return resourceAzureInstanceRead(d, meta) return resourceAzureInstanceRead(d, meta)
} }
@ -349,37 +341,59 @@ func resourceAzureInstanceRead(d *schema.ResourceData, meta interface{}) error {
d.Set("location", cs.Location) d.Set("location", cs.Location)
log.Printf("[DEBUG] Retrieving instance: %s", d.Id()) log.Printf("[DEBUG] Retrieving instance: %s", d.Id())
wi, err := virtualmachine.NewClient(*mc).GetDeployment(d.Id(), d.Id()) dpmt, err := virtualmachine.NewClient(*mc).GetDeployment(d.Id(), d.Id())
if err != nil { if err != nil {
return fmt.Errorf("Error retrieving instance %s: %s", d.Id(), err) return fmt.Errorf("Error retrieving instance %s: %s", d.Id(), err)
} }
if len(wi.RoleList) == 0 { if len(dpmt.RoleList) != 1 {
return fmt.Errorf("Instance %s does not have VIP addresses", d.Id()) return fmt.Errorf(
"Instance %s has an unexpected number of roles: %d", d.Id(), len(dpmt.RoleList))
} }
role := wi.RoleList[0] d.Set("size", dpmt.RoleList[0].RoleSize)
d.Set("size", role.RoleSize) if len(dpmt.RoleInstanceList) != 1 {
return fmt.Errorf(
if len(wi.RoleInstanceList) == 0 { "Instance %s has an unexpected number of role instances %d", d.Id(), len(dpmt.RoleInstanceList))
return fmt.Errorf("Instance %s does not have IP addresses", d.Id())
} }
d.Set("ip_address", wi.RoleInstanceList[0].IpAddress) d.Set("ip_address", dpmt.RoleInstanceList[0].IpAddress)
if len(wi.VirtualIPs) == 0 { if len(dpmt.RoleInstanceList[0].InstanceEndpoints) > 0 {
return fmt.Errorf("Instance %s does not have VIP addresses", d.Id()) d.Set("vip_address", dpmt.RoleInstanceList[0].InstanceEndpoints[0].Vip)
}
// Create a new set to hold all configured endpoints
endpoints := &schema.Set{
F: resourceAzureEndpointHash,
}
// Loop through all endpoints and add them to the set
for _, c := range *dpmt.RoleList[0].ConfigurationSets {
if c.ConfigurationSetType == virtualmachine.ConfigurationSetTypeNetwork {
for _, ep := range *c.InputEndpoints {
endpoint := map[string]interface{}{}
// Update the values
endpoint["name"] = ep.Name
endpoint["protocol"] = string(ep.Protocol)
endpoint["public_port"] = ep.Port
endpoint["private_port"] = ep.LocalPort
endpoints.Add(endpoint)
}
d.Set("endpoint", endpoints)
}
} }
d.Set("vip_address", wi.VirtualIPs[0].Address)
connType := "ssh" connType := "ssh"
if role.OSVirtualHardDisk.OS == windows { if dpmt.RoleList[0].OSVirtualHardDisk.OS == windows {
connType = windows connType = windows
} }
// Set the connection info for any configured provisioners // Set the connection info for any configured provisioners
d.SetConnInfo(map[string]string{ d.SetConnInfo(map[string]string{
"type": connType, "type": connType,
"host": wi.VirtualIPs[0].Address, "host": dpmt.VirtualIPs[0].Address,
"user": d.Get("username").(string), "user": d.Get("username").(string),
}) })
@ -389,27 +403,65 @@ func resourceAzureInstanceRead(d *schema.ResourceData, meta interface{}) error {
func resourceAzureInstanceUpdate(d *schema.ResourceData, meta interface{}) error { func resourceAzureInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
mc := meta.(*management.Client) mc := meta.(*management.Client)
// First check if anything we can update changed, and if not just return
if !d.HasChange("size") && !d.HasChange("endpoint") {
return nil
}
// Get the current role
role, err := virtualmachine.NewClient(*mc).GetRole(d.Id(), d.Id(), d.Id())
if err != nil {
return fmt.Errorf("Error retrieving role of instance %s: %s", d.Id(), err)
}
// Verify if we have all parameters required for the image OS type
if err := verifyParameters(d, role.OSVirtualHardDisk.OS); err != nil {
return err
}
if d.HasChange("size") { if d.HasChange("size") {
role, err := virtualmachine.NewClient(*mc).GetRole(d.Id(), d.Id(), d.Id())
if err != nil {
return fmt.Errorf("Error retrieving role of instance %s: %s", d.Id(), err)
}
role.RoleSize = d.Get("size").(string) role.RoleSize = d.Get("size").(string)
req, err := virtualmachine.NewClient(*mc).UpdateRole(d.Id(), d.Id(), d.Id(), *role)
if err != nil {
return fmt.Errorf("Error updating role of instance %s: %s", d.Id(), err)
}
if err := mc.WaitAsyncOperation(req); err != nil {
return fmt.Errorf(
"Error waiting for role of instance %s to be updated: %s", d.Id(), err)
}
} }
if d.HasChange("endpoint") { if d.HasChange("endpoint") {
_, n := d.GetChange("endpoint")
// Delete the existing endpoints
for i, c := range *role.ConfigurationSets {
if c.ConfigurationSetType == virtualmachine.ConfigurationSetTypeNetwork {
c.InputEndpoints = nil
(*role.ConfigurationSets)[i] = c
}
}
// And add the ones we still want
if s := n.(*schema.Set); s.Len() > 0 {
for _, v := range s.List() {
m := v.(map[string]interface{})
err := vmutils.ConfigureWithExternalPort(
role,
m["name"].(string),
m["private_port"].(int),
m["public_port"].(int),
endpointProtocol(m["protocol"].(string)),
)
if err != nil {
return fmt.Errorf(
"Error adding endpoint %s for instance %s: %s", m["name"].(string), d.Id(), err)
}
}
}
}
// Update the adjusted role
req, err := virtualmachine.NewClient(*mc).UpdateRole(d.Id(), d.Id(), d.Id(), *role)
if err != nil {
return fmt.Errorf("Error updating role of instance %s: %s", d.Id(), err)
}
if err := mc.WaitAsyncOperation(req); err != nil {
return fmt.Errorf(
"Error waiting for role of instance %s to be updated: %s", d.Id(), err)
} }
return resourceAzureInstanceRead(d, meta) return resourceAzureInstanceRead(d, meta)
@ -440,8 +492,8 @@ func resourceAzureEndpointHash(v interface{}) int {
m := v.(map[string]interface{}) m := v.(map[string]interface{})
buf.WriteString(fmt.Sprintf("%s-", m["name"].(string))) buf.WriteString(fmt.Sprintf("%s-", m["name"].(string)))
buf.WriteString(fmt.Sprintf("%s-", m["protocol"].(string))) buf.WriteString(fmt.Sprintf("%s-", m["protocol"].(string)))
buf.WriteString(fmt.Sprintf("%d-", m["port"].(int))) buf.WriteString(fmt.Sprintf("%d-", m["public_port"].(int)))
buf.WriteString(fmt.Sprintf("%d-", m["local_port"].(int))) buf.WriteString(fmt.Sprintf("%d-", m["private_port"].(int)))
return hashcode.String(buf.String()) return hashcode.String(buf.String())
} }
@ -468,6 +520,14 @@ func retrieveImageDetails(mc *management.Client, label string) (string, string,
label, strings.Join(labels, ",")) label, strings.Join(labels, ","))
} }
func endpointProtocol(p string) virtualmachine.InputEndpointProtocol {
if p == "tcp" {
return virtualmachine.InputEndpointProtocolTcp
}
return virtualmachine.InputEndpointProtocolUdp
}
func verifyParameters(d *schema.ResourceData, osType string) error { func verifyParameters(d *schema.ResourceData, osType string) error {
if osType == linux { if osType == linux {
_, pass := d.GetOk("password") _, pass := d.GetOk("password")
@ -477,10 +537,6 @@ func verifyParameters(d *schema.ResourceData, osType string) error {
return fmt.Errorf( return fmt.Errorf(
"You must supply a 'password' and/or a 'ssh_key_thumbprint' when using a Linux image") "You must supply a 'password' and/or a 'ssh_key_thumbprint' when using a Linux image")
} }
if key {
// check if it's a file of a string containing the key
}
} }
if osType == windows { if osType == windows {
@ -493,5 +549,22 @@ func verifyParameters(d *schema.ResourceData, osType string) error {
} }
} }
if _, ok := d.GetOk("subnet"); ok {
if _, ok := d.GetOk("virtual_network"); !ok {
return fmt.Errorf("You must also supply a 'virtual_network' when supplying a 'subnet'")
}
}
if s := d.Get("endpoint").(*schema.Set); s.Len() > 0 {
for _, v := range s.List() {
protocol := v.(map[string]interface{})["protocol"].(string)
if protocol != "tcp" && protocol != "udp" {
return fmt.Errorf(
"Invalid endpoint protocol %s! Valid options are 'tcp' and 'udp'.", protocol)
}
}
}
return nil return nil
} }

View File

@ -1,51 +0,0 @@
package azure
import "github.com/hashicorp/terraform/helper/schema"
func resourceAzureNetwork() *schema.Resource {
return &schema.Resource{
Create: resourceAzureNetworkCreate,
Read: resourceAzureNetworkRead,
Update: resourceAzureNetworkUpdate,
Delete: resourceAzureNetworkDelete,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"virtual": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: true,
ForceNew: true,
},
},
}
}
func resourceAzureNetworkCreate(d *schema.ResourceData, meta interface{}) (err error) {
//mc := meta.(*management.Client)
return resourceAzureNetworkRead(d, meta)
}
func resourceAzureNetworkRead(d *schema.ResourceData, meta interface{}) error {
//mc := meta.(*management.Client)
return nil
}
func resourceAzureNetworkUpdate(d *schema.ResourceData, meta interface{}) error {
//mc := meta.(*management.Client)
return resourceAzureNetworkRead(d, meta)
}
func resourceAzureNetworkDelete(d *schema.ResourceData, meta interface{}) error {
//mc := meta.(*management.Client)
return nil
}

View File

@ -0,0 +1,304 @@
package azure
import (
"fmt"
"log"
"strconv"
"github.com/MSOpenTech/azure-sdk-for-go/management"
"github.com/MSOpenTech/azure-sdk-for-go/management/networksecuritygroup"
"github.com/hashicorp/terraform/helper/schema"
)
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,
},
"subnet": &schema.Schema{
Type: schema.TypeString,
Optional: 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.(*management.Client)
name := d.Get("name").(string)
req, err := networksecuritygroup.NewClient(*mc).CreateNetworkSecurityGroup(
name,
d.Get("label").(string),
d.Get("location").(string),
)
if err != nil {
return fmt.Errorf("Error creating Network Security Group %s: %s", name, err)
}
if err := mc.WaitAsyncOperation(req); 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.(*management.Client)
// 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.Rule{
Name: name,
Type: rule["type"].(string),
Priority: rule["priority"].(int),
Action: rule["action"].(string),
SourceAddressPrefix: rule["source_cidr"].(string),
SourcePortRange: rule["source_port"].(string),
DestinationAddressPrefix: rule["destination_cidr"].(string),
DestinationPortRange: rule["destination_port"].(string),
Protocol: rule["protocol"].(string),
},
)
if err != nil {
return fmt.Errorf("Error creating Network Security Group rule %s: %s", name, err)
}
if err := mc.WaitAsyncOperation(req); 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.(*management.Client)
sg, err := networksecuritygroup.NewClient(*mc).GetNetworkSecurityGroup(d.Id())
if err != nil {
return fmt.Errorf("Error retrieving Network Security Group %s: %s", d.Id(), err)
}
d.Set("label", sg.Label)
d.Set("location", sg.Location)
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.(*management.Client)
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.WaitAsyncOperation(req); 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.(*management.Client)
name := rule["name"].(string)
// Delete the rule
req, err := networksecuritygroup.NewClient(*mc).DeleteNetworkSecurityGroupRule(d.Id(), name)
if err != nil {
return fmt.Errorf("Error deleting Network Security Group rule %s: %s", name, err)
}
if err := mc.WaitAsyncOperation(req); 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 {
return 0
}
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 File

@ -0,0 +1,205 @@
package azure
import (
"fmt"
"log"
"strings"
"github.com/MSOpenTech/azure-sdk-for-go/management"
"github.com/MSOpenTech/azure-sdk-for-go/management/virtualnetwork"
"github.com/hashicorp/terraform/helper/schema"
"github.com/mitchellh/mapstructure"
)
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.TypeMap,
Required: true,
},
"location": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}
func resourceAzureVirtualNetworkCreate(d *schema.ResourceData, meta interface{}) error {
mc := meta.(*management.Client)
name := d.Get("name").(string)
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)
err = virtualnetwork.NewClient(*mc).SetVirtualNetworkConfiguration(nc)
if err != nil {
return fmt.Errorf("Error creating Virtual Network %s: %s", name, err)
}
d.SetId(name)
return resourceAzureVirtualNetworkRead(d, meta)
}
func resourceAzureVirtualNetworkRead(d *schema.ResourceData, meta interface{}) error {
mc := meta.(*management.Client)
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)
subnets := map[string]interface{}{}
for _, s := range n.Subnets {
subnets[s.Name] = s.AddressPrefix
}
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 {
mc := meta.(*management.Client)
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())
}
err = virtualnetwork.NewClient(*mc).SetVirtualNetworkConfiguration(nc)
if err != nil {
return fmt.Errorf("Error updating Virtual Network %s: %s", d.Id(), err)
}
return resourceAzureVirtualNetworkRead(d, meta)
}
func resourceAzureVirtualNetworkDelete(d *schema.ResourceData, meta interface{}) error {
mc := meta.(*management.Client)
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
err = virtualnetwork.NewClient(*mc).SetVirtualNetworkConfiguration(nc)
if err != nil {
return fmt.Errorf("Error deleting Virtual Network %s: %s", d.Id(), err)
}
d.SetId("")
return nil
}
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,
}
subnets := []virtualnetwork.Subnet{}
for n, p := range d.Get("subnet").(map[string]interface{}) {
subnets = append(subnets, virtualnetwork.Subnet{
Name: n,
AddressPrefix: p.(string),
})
}
return virtualnetwork.VirtualNetworkSite{
Name: d.Get("name").(string),
Location: d.Get("location").(string),
AddressSpace: addressSpace,
Subnets: subnets,
}, nil
}

View File

@ -0,0 +1 @@
package azure