diff --git a/builtin/providers/azure/provider.go b/builtin/providers/azure/provider.go index 83e577a47..c4f117391 100644 --- a/builtin/providers/azure/provider.go +++ b/builtin/providers/azure/provider.go @@ -23,9 +23,10 @@ func Provider() terraform.ResourceProvider { }, ResourcesMap: map[string]*schema.Resource{ - "azure_disk": resourceAzureDisk(), - "azure_instance": resourceAzureInstance(), - "azure_network": resourceAzureNetwork(), + "azure_disk": resourceAzureDisk(), + "azure_instance": resourceAzureInstance(), + "azure_security_group": resourceAzureSecurityGroup(), + "azure_virtual_network": resourceAzureVirtualNetwork(), }, ConfigureFunc: providerConfigure, diff --git a/builtin/providers/azure/resource_disk.go b/builtin/providers/azure/resource_azure_disk.go similarity index 83% rename from builtin/providers/azure/resource_disk.go rename to builtin/providers/azure/resource_azure_disk.go index 8b0b09aae..3b67a39c7 100644 --- a/builtin/providers/azure/resource_disk.go +++ b/builtin/providers/azure/resource_azure_disk.go @@ -4,10 +4,10 @@ import "github.com/hashicorp/terraform/helper/schema" func resourceAzureDisk() *schema.Resource { return &schema.Resource{ - Create: resourceAzureNetworkCreate, - Read: resourceAzureNetworkRead, - Update: resourceAzureNetworkUpdate, - Delete: resourceAzureNetworkDelete, + Create: resourceAzureDiskCreate, + Read: resourceAzureDiskRead, + Update: resourceAzureDiskUpdate, + Delete: resourceAzureDiskDelete, Schema: map[string]*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) return resourceAzureDiskRead(d, meta) diff --git a/builtin/providers/azure/resource_azure_network_test.go b/builtin/providers/azure/resource_azure_disk_test.go similarity index 100% rename from builtin/providers/azure/resource_azure_network_test.go rename to builtin/providers/azure/resource_azure_disk_test.go diff --git a/builtin/providers/azure/resource_azure_instance.go b/builtin/providers/azure/resource_azure_instance.go index 9c2e461ff..9fe45eee1 100644 --- a/builtin/providers/azure/resource_azure_instance.go +++ b/builtin/providers/azure/resource_azure_instance.go @@ -52,7 +52,13 @@ func resourceAzureInstance() *schema.Resource { Required: true, }, - "network": &schema.Schema{ + "subnet": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "virtual_network": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: true, @@ -88,18 +94,6 @@ func resourceAzureInstance() *schema.Resource { 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{ Type: schema.TypeString, Required: true, @@ -134,12 +128,12 @@ func resourceAzureInstance() *schema.Resource { Required: true, }, - "port": &schema.Schema{ + "public_port": &schema.Schema{ Type: schema.TypeInt, Required: true, }, - "local_port": &schema.Schema{ + "private_port": &schema.Schema{ Type: schema.TypeInt, Required: true, }, @@ -148,6 +142,16 @@ func resourceAzureInstance() *schema.Resource { 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{ Type: schema.TypeString, Computed: true, @@ -162,177 +166,165 @@ func resourceAzureInstance() *schema.Resource { } 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 - description := d.Get("description").(string) - if description == "" { - 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") + // Compute/set the description + description := d.Get("description").(string) + if description == "" { + description = name } - 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 - if err := verifyParameters(d, osType); err != nil { - 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) { + // Retrieve the needed details of the image + imageName, imageURL, osType, err := retrieveImageDetails(mc, d.Get("image").(string)) if err != nil { - req, err := hostedservice.NewClient(*mc).DeleteHostedService(name, true) - if err != nil { - log.Printf("[DEBUG] Error cleaning up Cloud Service of instance %s: %s", name, err) + return err + } + + if imageURL == "" { + storage, ok := d.GetOk("storage") + if !ok { + return fmt.Errorf("When using a platform image, the 'storage' parameter is required") } - - // 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) + imageURL = fmt.Sprintf("http://%s.blob.core.windows.net/vhds/%s.vhd", storage, name) } - if d.Get("public_ssh").(bool) { - if err := vmutils.ConfigureWithPublicSSH(&role); err != nil { - return fmt.Errorf("Error configuring %s for public SSH: %s", name, err) - } + // Verify if we have all parameters required for the image OS type + if err := verifyParameters(d, osType); err != nil { + return err } - } - if osType == windows { - err = vmutils.ConfigureForWindows( - &role, + log.Printf("[DEBUG] Creating Cloud Service for instance: %s", name) + req, err := hostedservice.NewClient(*mc). + CreateHostedService( name, - d.Get("username").(string), - d.Get("password").(string), - d.Get("automatic_updates").(bool), - d.Get("time_zone").(string), + 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 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) { - if err := vmutils.ConfigureWithPublicRDP(&role); err != nil { - return fmt.Errorf("Error configuring %s for public RDP: %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) } - } - log.Printf("[DEBUG] Creating the new instance...") - req, err = virtualmachine.NewClient(*mc).CreateDeployment(role, name) - 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) - } - - /* - 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 + // 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 { + req, err := hostedservice.NewClient(*mc).DeleteHostedService(name, true) + if err != nil { + log.Printf("[DEBUG] Error cleaning up Cloud Service of instance %s: %s", name, err) } - 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) } @@ -349,37 +341,59 @@ func resourceAzureInstanceRead(d *schema.ResourceData, meta interface{}) error { d.Set("location", cs.Location) 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 { return fmt.Errorf("Error retrieving instance %s: %s", d.Id(), err) } - if len(wi.RoleList) == 0 { - return fmt.Errorf("Instance %s does not have VIP addresses", d.Id()) + if len(dpmt.RoleList) != 1 { + 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(wi.RoleInstanceList) == 0 { - return fmt.Errorf("Instance %s does not have IP addresses", d.Id()) + if len(dpmt.RoleInstanceList) != 1 { + return fmt.Errorf( + "Instance %s has an unexpected number of role instances %d", d.Id(), len(dpmt.RoleInstanceList)) } - d.Set("ip_address", wi.RoleInstanceList[0].IpAddress) + d.Set("ip_address", dpmt.RoleInstanceList[0].IpAddress) - if len(wi.VirtualIPs) == 0 { - return fmt.Errorf("Instance %s does not have VIP addresses", d.Id()) + if len(dpmt.RoleInstanceList[0].InstanceEndpoints) > 0 { + 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" - if role.OSVirtualHardDisk.OS == windows { + if dpmt.RoleList[0].OSVirtualHardDisk.OS == windows { connType = windows } // Set the connection info for any configured provisioners d.SetConnInfo(map[string]string{ "type": connType, - "host": wi.VirtualIPs[0].Address, + "host": dpmt.VirtualIPs[0].Address, "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 { 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") { - 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) - - 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") { + _, 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) @@ -440,8 +492,8 @@ func resourceAzureEndpointHash(v interface{}) int { m := v.(map[string]interface{}) buf.WriteString(fmt.Sprintf("%s-", m["name"].(string))) buf.WriteString(fmt.Sprintf("%s-", m["protocol"].(string))) - buf.WriteString(fmt.Sprintf("%d-", m["port"].(int))) - buf.WriteString(fmt.Sprintf("%d-", m["local_port"].(int))) + buf.WriteString(fmt.Sprintf("%d-", m["public_port"].(int))) + buf.WriteString(fmt.Sprintf("%d-", m["private_port"].(int))) return hashcode.String(buf.String()) } @@ -468,6 +520,14 @@ func retrieveImageDetails(mc *management.Client, label string) (string, string, 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 { if osType == linux { _, pass := d.GetOk("password") @@ -477,10 +537,6 @@ func verifyParameters(d *schema.ResourceData, osType string) error { return fmt.Errorf( "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 { @@ -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 } diff --git a/builtin/providers/azure/resource_azure_network.go b/builtin/providers/azure/resource_azure_network.go deleted file mode 100644 index 33d1d15a5..000000000 --- a/builtin/providers/azure/resource_azure_network.go +++ /dev/null @@ -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 -} diff --git a/builtin/providers/azure/resource_azure_security_group.go b/builtin/providers/azure/resource_azure_security_group.go new file mode 100644 index 000000000..f288fc0e5 --- /dev/null +++ b/builtin/providers/azure/resource_azure_security_group.go @@ -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 +} diff --git a/builtin/providers/azure/resource_disk_test.go b/builtin/providers/azure/resource_azure_security_group_test.go similarity index 100% rename from builtin/providers/azure/resource_disk_test.go rename to builtin/providers/azure/resource_azure_security_group_test.go diff --git a/builtin/providers/azure/resource_azure_virtual_network.go b/builtin/providers/azure/resource_azure_virtual_network.go new file mode 100644 index 000000000..0de456113 --- /dev/null +++ b/builtin/providers/azure/resource_azure_virtual_network.go @@ -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 +} diff --git a/builtin/providers/azure/resource_azure_virtual_network_test.go b/builtin/providers/azure/resource_azure_virtual_network_test.go new file mode 100644 index 000000000..6512f735e --- /dev/null +++ b/builtin/providers/azure/resource_azure_virtual_network_test.go @@ -0,0 +1 @@ +package azure