diff --git a/builtin/providers/azurerm/provider.go b/builtin/providers/azurerm/provider.go index 0dfdc07f7261..bc0608498349 100644 --- a/builtin/providers/azurerm/provider.go +++ b/builtin/providers/azurerm/provider.go @@ -80,6 +80,7 @@ func Provider() terraform.ResourceProvider { "azurerm_sql_database": resourceArmSqlDatabase(), "azurerm_sql_firewall_rule": resourceArmSqlFirewallRule(), "azurerm_sql_server": resourceArmSqlServer(), + "azurerm_simple_lb": resourceArmSimpleLb(), }, ConfigureFunc: providerConfigure, } diff --git a/builtin/providers/azurerm/resource_arm_network_interface_card.go b/builtin/providers/azurerm/resource_arm_network_interface_card.go index 8e6771541d01..371b99a7a253 100644 --- a/builtin/providers/azurerm/resource_arm_network_interface_card.go +++ b/builtin/providers/azurerm/resource_arm_network_interface_card.go @@ -328,6 +328,12 @@ func resourceArmNetworkInterfaceIpConfigurationHash(v interface{}) int { if m["public_ip_address_id"] != nil { buf.WriteString(fmt.Sprintf("%s-", m["public_ip_address_id"].(string))) } + if m["load_balancer_backend_address_pools_ids"] != nil { + buf.WriteString(fmt.Sprintf("%s-", m["load_balancer_backend_address_pools_ids"].(*schema.Set).GoString())) + } + if m["load_balancer_inbound_nat_rules_ids"] != nil { + buf.WriteString(fmt.Sprintf("%s-", m["load_balancer_inbound_nat_rules_ids"].(*schema.Set).GoString())) + } return hashcode.String(buf.String()) } diff --git a/builtin/providers/azurerm/resource_arm_simple_lb.go b/builtin/providers/azurerm/resource_arm_simple_lb.go new file mode 100644 index 000000000000..fb15fd3142a6 --- /dev/null +++ b/builtin/providers/azurerm/resource_arm_simple_lb.go @@ -0,0 +1,705 @@ +package azurerm + +import ( + "fmt" + "log" + "strings" + + "github.com/Azure/azure-sdk-for-go/arm/network" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" +) + +// resourceArmLoadBalancer returns the *schema.Resource +// associated to load balancer resources on ARM. +func resourceArmSimpleLb() *schema.Resource { + return &schema.Resource{ + Create: resourceArmSimpleLbCreate, + Read: resourceArmSimpleLbRead, + Update: resourceArmSimpleLbUpdate, + Delete: resourceArmSimpleLbDelete, + + Schema: map[string]*schema.Schema{ + "id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "backend_pool_id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "frontend_id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "type": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "resource_group_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "location": &schema.Schema{ + Type: schema.TypeString, + Required: true, + StateFunc: azureRMNormalizeLocation, + ForceNew: true, + }, + "frontend_private_ip_address": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + "frontend_allocation_method": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ValidateFunc: validateAllocationMethod, + ForceNew: true, + StateFunc: func(id interface{}) string { + return strings.ToLower(id.(string)) + }, + }, + "frontend_subnet": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "frontend_public_ip_address": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "probe": &schema.Schema{ + Type: schema.TypeSet, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "protocol": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ValidateFunc: validateProbeProtocolType, + }, + "request_path": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "port": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + }, + "interval": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + }, + "number_of_probes": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + }, + "probe_id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + }, + Set: resourceARMLoadBalancerProbeHash, + }, + + "rule": &schema.Schema{ + Type: schema.TypeSet, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "rule_id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "protocol": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ValidateFunc: validateProtocolType, + }, + "probe_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "load_distribution": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ValidateFunc: validateLoadDistribution, + }, + "frontend_port": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + }, + "backend_port": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + }, + }, + }, + Set: resourceARMLoadBalancerRuleHash, + }, + "tags": tagsSchema(), + }, + } +} + +func resourceARMLoadBalancerRuleHash(v interface{}) int { + m := v.(map[string]interface{}) + rule := fmt.Sprintf("%s-%s-%s-%s-%d-%d", m["name"].(string), m["protocol"].(string), m["probe_name"].(string), m["load_distribution"].(string), m["frontend_port"], m["backend_port"]) + return hashcode.String(strings.ToLower(rule)) +} + +func resourceARMLoadBalancerProbeHash(v interface{}) int { + m := v.(map[string]interface{}) + rule := fmt.Sprintf("%s-%s-%d-%d-%d", m["name"].(string), m["protocol"].(string), m["port"], m["number_of_probes"], m["interval"]) + if m["request_path"] != nil { + rule = rule + "-" + m["request_path"].(string) + } + return hashcode.String(strings.ToLower(rule)) +} + +func validateAllocationMethod(allocation interface{}, k string) (ws []string, errors []error) { + if !isOneOf([]string{"static", "dynamic"}, allocation) { + errors = append(errors, fmt.Errorf("Allocation method can only be Static or Dynamic")) + } + return +} + +func validateProtocolType(protocol interface{}, k string) (ws []string, errors []error) { + if !isOneOf([]string{"tcp", "udp"}, protocol) { + errors = append(errors, fmt.Errorf("Protocol can only be tcp or udp")) + } + return +} + +func validateProbeProtocolType(protocol interface{}, k string) (ws []string, errors []error) { + if !isOneOf([]string{"tcp", "http"}, protocol) { + errors = append(errors, fmt.Errorf("Protocol can only be tcp or http")) + } + return +} + +func validateLoadDistribution(distribution interface{}, k string) (ws []string, errors []error) { + if !isOneOf([]string{"default", "sourceip", "sourceipprotocol"}, distribution) { + errors = append(errors, fmt.Errorf("Load Distribution can only be default, sourceIp, or sourceIpProtocol")) + } + return +} + +func findRuleByName(ruleArray *[]network.LoadBalancingRule, ruleName string) (network.LoadBalancingRule, error) { + for _, rule := range *ruleArray { + if *rule.Name == ruleName { + log.Printf("[findRuleByName] found rule %v", rule) + return rule, nil + } + } + return network.LoadBalancingRule{}, fmt.Errorf("Error loading the rule named %s", ruleName) +} + +func findProbeByName(probeArray *[]network.Probe, probeName string) (network.Probe, error) { + for _, probe := range *probeArray { + if *probe.Name == probeName { + log.Printf("[findProbeByName] found probe %v", probe) + return probe, nil + } + } + + return network.Probe{}, fmt.Errorf("Error loading the probe named %s", probeName) +} + +func findProbeById(probeArray *[]network.Probe, probeId string) (network.Probe, error) { + for _, probe := range *probeArray { + if *probe.ID == probeId { + log.Printf("[findProbeById] found probe %v", probe) + return probe, nil + } + } + + return network.Probe{}, fmt.Errorf("Error finding the probe with ID %s", probeId) +} + +func pullOutLbRules(d *schema.ResourceData, loadBalancer network.LoadBalancer) (*[]network.LoadBalancingRule, error) { + log.Printf("[resourceArmSimpleLb] pullOutLbRules[enter]") + defer log.Printf("[resourceArmSimpleLb] pullOutLbRules[exit]") + + log.Printf("[resourceArmSimpleLb] pullOutLbRules %v", *loadBalancer.Properties.Probes) + outRules := []network.LoadBalancingRule{} + + backendPoolID := (*loadBalancer.Properties.BackendAddressPools)[0].ID + frontendIpID := (*loadBalancer.Properties.FrontendIPConfigurations)[0].ID + log.Printf("[resourceArmSimpleLb] pullOutLbRules will use frontend %s and backend %s", *frontendIpID, *backendPoolID) + + rules := d.Get("rule").(*schema.Set) + if rules.Len() == 0 { + log.Printf("[resourceArmSimpleLb] pullOutLbRules no rules found") + return &outRules, nil + } + + log.Printf("[resourceArmSimpleLb] pullOutLbRules found %d rules in plan", rules.Len()) + for _, rule := range rules.List() { + rule := rule.(map[string]interface{}) + ruleName := rule["name"].(string) + log.Printf("[resourceArmSimpleLb] pullOutLbRules %s", ruleName) + + existingRule, err := findRuleByName(loadBalancer.Properties.LoadBalancingRules, ruleName) + if err == nil { + log.Printf("[resourceArmSimpleLb] pullOutLbRules found the existing rule %s", ruleName) + } + + probe, err := findProbeByName(loadBalancer.Properties.Probes, rule["probe_name"].(string)) + if err != nil { + return nil, err + } + log.Printf("[resourceArmSimpleLb] pullOutLbRules rule %s is using probe %s", ruleName, *probe.ID) + + props := network.LoadBalancingRulePropertiesFormat{ + Protocol: network.TransportProtocol(rule["protocol"].(string)), + LoadDistribution: network.LoadDistribution(rule["load_distribution"].(string)), + FrontendPort: Int32(rule["frontend_port"]), + BackendPort: Int32(rule["backend_port"]), + Probe: &network.SubResource{ID: probe.ID}, + BackendAddressPool: &network.SubResource{ID: backendPoolID}, + FrontendIPConfiguration: &network.SubResource{ID: frontendIpID}, + } + + outRules = append(outRules, network.LoadBalancingRule{ + Name: &ruleName, + Properties: &props, + ID: existingRule.ID, + }) + } + + return &outRules, nil +} + +func pullOutProbes(d *schema.ResourceData) (*[]network.Probe, error) { + log.Printf("[resourceArmSimpleLb] pullOutProbes[enter]") + defer log.Printf("[resourceArmSimpleLb] pullOutProbes[exit]") + + outProbes := []network.Probe{} + if probes := d.Get("probe").(*schema.Set); probes.Len() > 0 { + for _, probe := range probes.List() { + probe := probe.(map[string]interface{}) + + probeProps := network.ProbePropertiesFormat{ + Protocol: network.ProbeProtocol(probe["protocol"].(string)), + Port: Int32(probe["port"]), + IntervalInSeconds: Int32(probe["interval"]), + NumberOfProbes: Int32(probe["number_of_probes"]), + } + if requestPath := probe["request_path"].(string); requestPath != "" { + probeProps.RequestPath = &requestPath + } + + outProbes = append(outProbes, network.Probe{ + Name: String(probe["name"]), + Properties: &probeProps, + }) + } + } + + return &outProbes, nil +} + +func pullOutFrontEndIps(d *schema.ResourceData) (*[]network.FrontendIPConfiguration, error) { + log.Printf("[resourceArmSimpleLb] pullOutFrontEndIps[enter]") + defer log.Printf("[resourceArmSimpleLb] pullOutFrontEndIps[exit]") + + returnRules := []network.FrontendIPConfiguration{} + + frontendIpName := fmt.Sprintf("%sfrontendip", d.Get("name").(string)) + frontendIpAllocationMethod := network.IPAllocationMethod(d.Get("frontend_allocation_method").(string)) + frontendIpSubnet := d.Get("frontend_subnet").(string) + frontendIpPublicIpAddress := d.Get("frontend_public_ip_address").(string) + frontendIpPrivateIpAddress := d.Get("frontend_private_ip_address").(string) + + if frontendIpSubnet == "" && frontendIpPublicIpAddress == "" { + var logMsg = fmt.Sprintf("[ERROR] Either a subnet of a public ip address must be provided") + log.Printf("[resourceArmSimpleLb] %s", logMsg) + return nil, fmt.Errorf(logMsg) + } + + if frontendIpPrivateIpAddress == "" && frontendIpAllocationMethod == network.Static { + var logMsg = fmt.Sprintf("An private IP address must be provided if static allocation is used.") + log.Printf("[resourceArmSimpleLb] %s", logMsg) + return nil, fmt.Errorf(logMsg) + } + + ipProps := network.FrontendIPConfigurationPropertiesFormat{ + PrivateIPAllocationMethod: frontendIpAllocationMethod} + + if frontendIpSubnet != "" { + subnet := network.Subnet{ID: &frontendIpSubnet} + ipProps.Subnet = &subnet + } + if frontendIpPublicIpAddress != "" { + pubIp := network.PublicIPAddress{ID: &frontendIpPublicIpAddress} + ipProps.PublicIPAddress = &pubIp + } + if frontendIpPrivateIpAddress != "" { + ipProps.PrivateIPAddress = &frontendIpPrivateIpAddress + } + + frontendIpConf := network.FrontendIPConfiguration{Name: &frontendIpName, Properties: &ipProps} + returnRules = append(returnRules, frontendIpConf) + return &returnRules, nil +} + +func resourceArmSimpleLbCreate(d *schema.ResourceData, meta interface{}) error { + log.Printf("[resourceArmSimpleLb] resourceArmSimpleLbCreate[enter]") + defer log.Printf("[resourceArmSimpleLb] resourceArmSimpleLbCreate[exit]") + + lbClient := meta.(*ArmClient).loadBalancerClient + + // first; fetch a bunch of fields: + typ := d.Get("type").(string) + name := d.Get("name").(string) + location := d.Get("location").(string) + resGrp := d.Get("resource_group_name").(string) + tags := d.Get("tags").(map[string]interface{}) + + loadBalancer := network.LoadBalancer{ + Name: &name, + Type: &typ, + Location: &location, + Properties: &network.LoadBalancerPropertiesFormat{}, + Tags: expandTags(tags), + } + + fipconfs, err := pullOutFrontEndIps(d) + if err != nil { + return err + } + loadBalancer.Properties.FrontendIPConfigurations = fipconfs + probes, err := pullOutProbes(d) + if err != nil { + return err + } + loadBalancer.Properties.Probes = probes + + newBackendPoolName := fmt.Sprintf("%sbackendpool", name) + backendPool := network.BackendAddressPool{Name: &newBackendPoolName} + backendPoolConfs := []network.BackendAddressPool{} + backendPoolConfs = append(backendPoolConfs, backendPool) + loadBalancer.Properties.BackendAddressPools = &backendPoolConfs + loadBalancer.Properties.LoadBalancingRules = &[]network.LoadBalancingRule{} + + cancelChannel := make(<-chan struct{}) + _, err = lbClient.CreateOrUpdate(resGrp, name, loadBalancer, cancelChannel) + if err != nil { + log.Printf("[resourceArmSimpleLb] ERROR LB got status %s", err.Error()) + return fmt.Errorf("Error issuing Azure ARM creation request for load balancer '%s': %s", name, err) + } + respLb, err := lbClient.Get(resGrp, name, "") + if err != nil { + log.Printf("[resourceArmSimpleLb] ERROR LB retrieving load balancer %s", err.Error()) + return fmt.Errorf("Error issuing Azure ARM get request for load balancer '%s': %s", name, err) + } + log.Printf("[resourceArmSimpleLb] Create LB got status %d. Provision State %s", respLb.StatusCode, *respLb.Properties.ProvisioningState) + + //Possible status values are Updating|Deleting|Failed|Succeeded + if *respLb.Properties.ProvisioningState != "Succeeded" { + log.Printf("[resourceArmSimpleLb] ERROR retrieving load balancer, provisioning state %s", *respLb.Properties.ProvisioningState) + return fmt.Errorf("ERROR retrieving load balancer, provisioning state %s", *respLb.Properties.ProvisioningState) + } + + log.Printf("[resourceArmSimpleLb] We have the IDs now updating to set rules") + loadBalancer.Properties.LoadBalancingRules, err = pullOutLbRules(d, respLb) + if err != nil { + return err + } + log.Printf("[resourceArmSimpleLb] created %d rules", len(*loadBalancer.Properties.LoadBalancingRules)) + + _, err = lbClient.CreateOrUpdate(resGrp, name, loadBalancer, cancelChannel) + if err != nil { + log.Printf("[resourceArmSimpleLb] ERROR When trying to set the rules. LB got status %s", err.Error()) + return fmt.Errorf("Error issuing Azure ARM creation request for load balancer '%s': %s", name, err) + } + respLb, err = lbClient.Get(resGrp, name, "") + if err != nil { + log.Printf("[resourceArmSimpleLb] ERROR LB retrieving load balancer %s", err.Error()) + return fmt.Errorf("Error issuing Azure ARM get request for load balancer '%s': %s", name, err) + } + log.Printf("XXXXXX 2 %s", respLb.Tags) + return flattenAllOfLb(respLb, d, meta) +} + +func flattenAzureRmFrontendIp(frontendIpArray []network.FrontendIPConfiguration, d *schema.ResourceData) error { + log.Printf("[resourceArmSimpleLb] flattenAzureRmFrontendIp[enter]") + defer log.Printf("[resourceArmSimpleLb] flattenAzureRmFrontendIp[exit]") + + if len(frontendIpArray) < 1 { + return nil + } + if len(frontendIpArray) > 1 { + log.Printf("[WARN] More than 1 frontend ip was found. The simpleLB resource will just use the first one.") + } + + frontendIp := frontendIpArray[0] + + if frontendIp.Properties.PrivateIPAddress != nil { + d.Set("frontend_private_ip_address", *frontendIp.Properties.PrivateIPAddress) + } + d.Set("frontend_allocation_method", strings.ToLower(string(frontendIp.Properties.PrivateIPAllocationMethod))) + if frontendIp.Properties.Subnet != nil { + d.Set("frontend_subnet", *frontendIp.Properties.Subnet.ID) + } + if frontendIp.Properties.PublicIPAddress != nil { + d.Set("frontend_public_ip_address", *frontendIp.Properties.PublicIPAddress.ID) + } + + return nil +} + +func flattenAzureRmLoadBalancerRules(loadBalancer network.LoadBalancer, d *schema.ResourceData) error { + log.Printf("[resourceArmSimpleLb] flattenAzureRmLoadBalancerRules[enter]") + defer log.Printf("[resourceArmSimpleLb] flattenAzureRmLoadBalancerRules[exit]") + + loadBalancingRuleArray := loadBalancer.Properties.LoadBalancingRules + + ruleSet := &schema.Set{ + F: resourceARMLoadBalancerProbeHash, + } + + log.Printf("[resourceArmSimpleLb] flattenAzureRmLoadBalancerRules found %d rules", len(*loadBalancingRuleArray)) + + for _, rule := range *loadBalancingRuleArray { + r := map[string]interface{}{} + + log.Printf("[resourceArmSimpleLb] Found LB RULE %s", *rule.Name) + r["name"] = *rule.Name + r["rule_id"] = *rule.ID + if rule.Properties != nil { + r["protocol"] = string(rule.Properties.Protocol) + r["load_distribution"] = string(rule.Properties.LoadDistribution) + r["frontend_port"] = *rule.Properties.FrontendPort + r["backend_port"] = *rule.Properties.BackendPort + + ruleProbeID := *rule.Properties.Probe.ID + conf, err := findProbeById(loadBalancer.Properties.Probes, ruleProbeID) + if err != nil { + log.Printf("[resourceArmSimpleLb] Error reading %s", *rule.Name) + return err + } + r["probe_name"] = *conf.Name + log.Printf("[resourceArmSimpleLb] Successfully read rule %s", *rule.Name) + } + ruleSet.Add(r) + } + d.Set("rule", ruleSet) + + return nil +} + +func flattenAzureRmProbe(probeArray *[]network.Probe, d *schema.ResourceData) error { + log.Printf("[resourceArmSimpleLb] flattenAzureRmProbe[enter]") + defer log.Printf("[resourceArmSimpleLb] flattenAzureRmProbe[exit]") + + probeSet := &schema.Set{ + F: resourceARMLoadBalancerProbeHash, + } + + for _, probe := range *probeArray { + p := map[string]interface{}{} + + p["name"] = *probe.Name + p["probe_id"] = *probe.ID + if probe.Properties != nil { + p["protocol"] = string(probe.Properties.Protocol) + p["port"] = *probe.Properties.Port + p["interval"] = *probe.Properties.IntervalInSeconds + p["number_of_probes"] = *probe.Properties.NumberOfProbes + if probe.Properties.RequestPath != nil { + p["request_path"] = *probe.Properties.RequestPath + } + } + + probeSet.Add(p) + } + d.Set("probe", probeSet) + + return nil +} + +func resourceArmSimpleLbUpdate(d *schema.ResourceData, meta interface{}) error { + log.Printf("[resourceArmSimpleLb] resourceArmSimpleLbUpdate[enter]") + defer log.Printf("[resourceArmSimpleLb] resourceArmSimpleLbUpdate[exit]") + + lbClient := meta.(*ArmClient).loadBalancerClient + + // first; fetch a bunch of fields: + name := d.Get("name").(string) + resGrp := d.Get("resource_group_name").(string) + tags := d.Get("tags").(map[string]interface{}) + + loadBalancer, err := lbClient.Get(resGrp, name, "") + if err != nil { + return fmt.Errorf("Error could not find the LB %s. %s", name, err) + } + + loadBalancer.Tags = expandTags(tags) + + probes, err := pullOutProbes(d) + if err != nil { + return err + } + loadBalancer.Properties.Probes = probes + + newBackendPoolName := fmt.Sprintf("%sbackendpool", name) + backendPool := network.BackendAddressPool{Name: &newBackendPoolName} + backendPoolConfs := []network.BackendAddressPool{} + backendPoolConfs = append(backendPoolConfs, backendPool) + loadBalancer.Properties.BackendAddressPools = &backendPoolConfs + loadBalancer.Properties.LoadBalancingRules = &[]network.LoadBalancingRule{} + + cancelChannel := make(<-chan struct{}) + resp, err := lbClient.CreateOrUpdate(resGrp, name, loadBalancer, cancelChannel) + if err != nil { + log.Printf("[resourceArmSimpleLb] ERROR Update LB got status %s", err.Error()) + return fmt.Errorf("Error issuing Azure ARM creation request for load balancer '%s': %s", name, err) + } + respLb, err := lbClient.Get(resGrp, name, "") + if err != nil { + log.Printf("[resourceArmSimpleLb] ERROR LB retrieving load balancer %s", err.Error()) + return fmt.Errorf("Error issuing Azure ARM get request for load balancer '%s': %s", name, err) + } + + log.Printf("[resourceArmSimpleLb] Update LB got status %d. Provision State %s", resp.StatusCode, *respLb.Properties.ProvisioningState) + + //Possible status values are Updating|Deleting|Failed|Succeeded + if *respLb.Properties.ProvisioningState != "Succeeded" { + return fmt.Errorf("The load balancer was not properly deployed. The provisioning state %s", *respLb.Properties.ProvisioningState) + } + + log.Printf("[resourceArmSimpleLb] We have the IDs now updating to set rules") + loadBalancer.Properties.LoadBalancingRules, err = pullOutLbRules(d, respLb) + if err != nil { + return err + } + log.Printf("[resourceArmSimpleLb] created %d rules", len(*loadBalancer.Properties.LoadBalancingRules)) + + resp, err = lbClient.CreateOrUpdate(resGrp, name, loadBalancer, cancelChannel) + if err != nil { + log.Printf("[resourceArmSimpleLb] ERROR When trying to set the rules. Update LB got status %s", err.Error()) + return fmt.Errorf("Error issuing Azure ARM creation request for load balancer '%s': %s", name, err) + } + log.Printf("[resourceArmSimpleLb] Set the rules on the LB. Provision State %s", *respLb.Properties.ProvisioningState) + + respLb, err = lbClient.Get(resGrp, name, "") + if err != nil { + log.Printf("[resourceArmSimpleLb] ERROR LB retrieving load balancer %s", err.Error()) + return fmt.Errorf("Error issuing Azure ARM get request for load balancer '%s': %s", name, err) + } + log.Printf("XXXXXX 3 %s", respLb.Tags) + return flattenAllOfLb(respLb, d, meta) +} + +func resourceArmSimpleLbDelete(d *schema.ResourceData, meta interface{}) error { + log.Printf("[resourceArmSimpleLb] resourceArmSimpleLbDelete[enter]") + defer log.Printf("[resourceArmSimpleLb] resourceArmSimpleLbDelete[exit]") + + lbClient := meta.(*ArmClient).loadBalancerClient + + name := d.Get("name").(string) + resGroup := d.Get("resource_group_name").(string) + + log.Printf("[INFO] Issuing deletion request to Azure ARM for load balancer '%s'.", name) + + cancelChannel := make(<-chan struct{}) + resp, err := lbClient.Delete(resGroup, name, cancelChannel) + if err != nil { + return fmt.Errorf("Error issuing Azure ARM delete request for load balancer '%s': %s", name, err) + } + + log.Printf("[resourceArmSimpleLb] delete response %d %s", resp.StatusCode, resp.Status) + + return nil +} + +// resourceArmLoadBalancerRead goes ahead and reads the state of the corresponding ARM load balancer. +func resourceArmSimpleLbRead(d *schema.ResourceData, meta interface{}) error { + log.Printf("[resourceArmSimpleLb] resourceArmSimpleLbRead[enter]") + defer log.Printf("[resourceArmSimpleLb] resourceArmSimpleLbRead[exit]") + + lbClient := meta.(*ArmClient).loadBalancerClient + + name := d.Get("name").(string) + resGrp := d.Get("resource_group_name").(string) + + log.Printf("[INFO] Issuing read request of load balancer '%s' off Azure.", name) + + loadBalancer, err := lbClient.Get(resGrp, name, "") + if err != nil { + if loadBalancer.StatusCode == 404 { + d.SetId("") + return nil + } + return fmt.Errorf("Error reading the state of the load balancer off Azure: %s", err) + } + log.Printf("XXXXXX 1 %s", loadBalancer.Tags) + return flattenAllOfLb(loadBalancer, d, meta) +} + +// resourceArmLoadBalancerRead goes ahead and reads the state of the corresponding ARM load balancer. +func flattenAllOfLb(loadBalancer network.LoadBalancer, d *schema.ResourceData, meta interface{}) error { + log.Printf("[resourceArmSimpleLb] flattenAllOfLb[enter]") + defer log.Printf("[resourceArmSimpleLb] flattenAllOfLb[exit]") + + log.Printf("[INFO] Succesfully retrieved details for load balancer '%s'.", *loadBalancer.Name) + + fip := loadBalancer.Properties.FrontendIPConfigurations + + d.Set("location", loadBalancer.Location) + d.Set("type", loadBalancer.Type) + + err := flattenAzureRmFrontendIp(*fip, d) + if err != nil { + return err + } + err = flattenAzureRmProbe(loadBalancer.Properties.Probes, d) + if err != nil { + return err + } + if loadBalancer.Properties.BackendAddressPools == nil || len(*loadBalancer.Properties.BackendAddressPools) != 1 { + return fmt.Errorf("There must be exactly 1 backend pool to use this resource") + } + d.Set("backend_pool_id", (*loadBalancer.Properties.BackendAddressPools)[0].ID) + if loadBalancer.Properties.FrontendIPConfigurations == nil || len(*loadBalancer.Properties.FrontendIPConfigurations) != 1 { + return fmt.Errorf("There must be exactly 1 frontend to use this resource") + } + d.Set("frontend_id", (*loadBalancer.Properties.FrontendIPConfigurations)[0].ID) + err = flattenAzureRmLoadBalancerRules(loadBalancer, d) + if err != nil { + return err + } + flattenAndSetTags(d, loadBalancer.Tags) + + d.SetId(*loadBalancer.ID) + + return nil +} diff --git a/builtin/providers/azurerm/resource_arm_simple_lb_test.go b/builtin/providers/azurerm/resource_arm_simple_lb_test.go new file mode 100644 index 000000000000..123b5f829517 --- /dev/null +++ b/builtin/providers/azurerm/resource_arm_simple_lb_test.go @@ -0,0 +1,450 @@ +package azurerm + +import ( + "fmt" + "net/http" + "testing" + + "regexp" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAzureRMSimpleLB_basic(t *testing.T) { + + ri := acctest.RandInt() + config := fmt.Sprintf(testAccAzureRMSimpleLB_basic, ri, ri, ri) + + justBeThere := regexp.MustCompile(".*") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSimpleRMLBDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSimpleLBExists("azurerm_simple_lb.test"), + resource.TestMatchResourceAttr("azurerm_simple_lb.test", "frontend_id", justBeThere), + resource.TestMatchResourceAttr("azurerm_simple_lb.test", "backend_pool_id", justBeThere), + ), + }, + }, + }) +} + +func TestAccAzureRMSimpleLB_updateTag(t *testing.T) { + + ri := acctest.RandInt() + preConfig := fmt.Sprintf(testAccAzureRMSimpleLB_tags, ri, ri, ri) + postConfig := fmt.Sprintf(testAccAzureRMSimpleLB_updateTags, ri, ri, ri) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSimpleRMLBDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: preConfig, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSimpleLBExists("azurerm_simple_lb.test"), + resource.TestCheckResourceAttr( + "azurerm_simple_lb.test", "tags.%", "2"), + resource.TestCheckResourceAttr( + "azurerm_simple_lb.test", "tags.environment", "Production"), + resource.TestCheckResourceAttr( + "azurerm_simple_lb.test", "tags.cost_center", "MSFT"), + ), + }, + + resource.TestStep{ + Config: postConfig, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSimpleLBExists("azurerm_simple_lb.test"), + resource.TestCheckResourceAttr( + "azurerm_simple_lb.test", "tags.%", "1"), + resource.TestCheckResourceAttr( + "azurerm_simple_lb.test", "tags.environment", "staging"), + ), + }, + }, + }) +} + +func TestAccAzureRMSimpleLB_updateProbe(t *testing.T) { + + ri := acctest.RandInt() + preConfig := fmt.Sprintf(testAccAzureRMSimpleLB_probe, ri, ri, ri) + postConfig := fmt.Sprintf(testAccAzureRMSimpleLB_probeUpdate, ri, ri, ri) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSimpleRMLBDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: preConfig, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSimpleLBExists("azurerm_simple_lb.test"), + resource.TestCheckResourceAttr( + "azurerm_simple_lb.test", "probe.#", "2"), + ), + }, + + resource.TestStep{ + Config: postConfig, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSimpleLBExists("azurerm_simple_lb.test"), + resource.TestCheckResourceAttr( + "azurerm_simple_lb.test", "probe.#", "1"), + ), + }, + }, + }) +} + +func TestAccAzureRMSimpleLB_dynamicFrontEndIPAddress(t *testing.T) { + ri := acctest.RandInt() + config := fmt.Sprintf(testAccAzureRMSimpleLB_dynamicFrontEndIPAddress, ri, ri, ri, ri) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSimpleRMLBDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: testCheckAzureRMSimpleLBExists("azurerm_simple_lb.test"), + }, + { + Config: config, + // left here to make it explicit that we expect an empty plan + ExpectNonEmptyPlan: false, + }, + }, + }) +} + +func testCheckAzureRMSimpleLBExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // Ensure we have enough information in state to look up in API + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + name := rs.Primary.Attributes["name"] + resourceGroup, hasResourceGroup := rs.Primary.Attributes["resource_group_name"] + if !hasResourceGroup { + return fmt.Errorf("Bad: no resource group found in state for cdn endpoint: %s", name) + } + + conn := testAccProvider.Meta().(*ArmClient).loadBalancerClient + + resp, err := conn.Get(resourceGroup, name, "") + if err != nil { + return fmt.Errorf("Bad: Get on loadBalancerClient: %s", err) + } + + if resp.StatusCode == http.StatusNotFound { + return fmt.Errorf("Bad: Load Balancer %q (resource group: %q) does not exist", name, resourceGroup) + } + + return nil + } +} + +func testCheckAzureRMSimpleRMLBDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*ArmClient).loadBalancerClient + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_load_balancer" { + continue + } + + name := rs.Primary.Attributes["name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + resp, err := conn.Get(resourceGroup, name, "") + + if err != nil { + return nil + } + + if resp.StatusCode != http.StatusNotFound { + return fmt.Errorf("Load balancer still exists:\n%#v", resp.Properties) + } + } + + return nil +} + +var testAccAzureRMSimpleLB_basic = ` +resource "azurerm_resource_group" "test" { + name = "acctestlbrg-%d" + location = "West US" +} + +resource "azurerm_public_ip" "test" { + name = "simplelbip%d" + location = "West US" + resource_group_name = "${azurerm_resource_group.test.name}" + public_ip_address_allocation = "static" +} + +resource "azurerm_simple_lb" "test" { + name = "acctestlb%d" + location = "West US" + type = "Microsoft.Network/loadBalancers" + resource_group_name = "${azurerm_resource_group.test.name}" + frontend_allocation_method = "Dynamic" + frontend_public_ip_address = "${azurerm_public_ip.test.id}" + + probe { + name = "testProbe1" + protocol = "Tcp" + port = 22 + interval = 5 + number_of_probes = 16 + } + rule { + protocol = "Tcp" + load_distribution = "Default" + frontend_port = 22 + backend_port = 22 + name = "rule1" + probe_name = "testProbe1" + } +} +` + +var testAccAzureRMSimpleLB_tags = ` +resource "azurerm_resource_group" "test" { + name = "acctestlbrg-%d" + location = "West US" +} + +resource "azurerm_public_ip" "test" { + name = "simplelbip%d" + location = "West US" + resource_group_name = "${azurerm_resource_group.test.name}" + public_ip_address_allocation = "static" +} + +resource "azurerm_simple_lb" "test" { + name = "acctestlb%d" + location = "West US" + type = "Microsoft.Network/loadBalancers" + resource_group_name = "${azurerm_resource_group.test.name}" + frontend_allocation_method = "Dynamic" + frontend_public_ip_address = "${azurerm_public_ip.test.id}" + + probe { + name = "testProbe1" + protocol = "Tcp" + port = 22 + interval = 5 + number_of_probes = 16 + } + rule { + protocol = "Tcp" + load_distribution = "Default" + frontend_port = 22 + backend_port = 22 + name = "rule1" + probe_name = "testProbe1" + } + + tags { + environment = "Production" + cost_center = "MSFT" + } +} +` + +var testAccAzureRMSimpleLB_updateTags = ` +resource "azurerm_resource_group" "test" { + name = "acctestlbrg-%d" + location = "West US" +} + +resource "azurerm_public_ip" "test" { + name = "simplelbip%d" + location = "West US" + resource_group_name = "${azurerm_resource_group.test.name}" + public_ip_address_allocation = "static" +} + +resource "azurerm_simple_lb" "test" { + name = "acctestlb%d" + location = "West US" + type = "Microsoft.Network/loadBalancers" + resource_group_name = "${azurerm_resource_group.test.name}" + frontend_allocation_method = "Dynamic" + frontend_public_ip_address = "${azurerm_public_ip.test.id}" + + probe { + name = "testProbe1" + protocol = "Tcp" + port = 22 + interval = 5 + number_of_probes = 16 + } + rule { + protocol = "Tcp" + load_distribution = "Default" + frontend_port = 22 + backend_port = 22 + name = "rule1" + probe_name = "testProbe1" + } + + tags { + environment = "staging" + } +} +` + +var testAccAzureRMSimpleLB_probe = ` +resource "azurerm_resource_group" "test" { + name = "acctestlbrg-%d" + location = "West US" +} + +resource "azurerm_public_ip" "test" { + name = "simplelbip%d" + location = "West US" + resource_group_name = "${azurerm_resource_group.test.name}" + public_ip_address_allocation = "static" +} + +resource "azurerm_simple_lb" "test" { + name = "acctestlb%d" + location = "West US" + type = "Microsoft.Network/loadBalancers" + resource_group_name = "${azurerm_resource_group.test.name}" + frontend_allocation_method = "Dynamic" + frontend_public_ip_address = "${azurerm_public_ip.test.id}" + + probe { + name = "testProbe1" + protocol = "Tcp" + port = 22 + interval = 5 + number_of_probes = 16 + } + rule { + protocol = "Tcp" + load_distribution = "Default" + frontend_port = 22 + backend_port = 22 + name = "rule1" + probe_name = "testProbe1" + } + + probe { + name = "testProbe2" + protocol = "Tcp" + port = 80 + interval = 5 + number_of_probes = 16 + } + rule { + protocol = "Tcp" + load_distribution = "Default" + frontend_port = 80 + backend_port = 80 + name = "rule2" + probe_name = "testProbe2" + } +} +` + +var testAccAzureRMSimpleLB_probeUpdate = ` +resource "azurerm_resource_group" "test" { + name = "acctestlbrg-%d" + location = "West US" +} + +resource "azurerm_public_ip" "test" { + name = "simplelbip%d" + location = "West US" + resource_group_name = "${azurerm_resource_group.test.name}" + public_ip_address_allocation = "static" +} + +resource "azurerm_simple_lb" "test" { + name = "acctestlb%d" + location = "West US" + type = "Microsoft.Network/loadBalancers" + resource_group_name = "${azurerm_resource_group.test.name}" + frontend_allocation_method = "Dynamic" + frontend_public_ip_address = "${azurerm_public_ip.test.id}" + + probe { + name = "testProbe1" + protocol = "Tcp" + port = 22 + interval = 5 + number_of_probes = 16 + } + rule { + protocol = "Tcp" + load_distribution = "Default" + frontend_port = 22 + backend_port = 22 + name = "rule1" + probe_name = "testProbe1" + } +} +` + +const testAccAzureRMSimpleLB_dynamicFrontEndIPAddress = ` +resource "azurerm_resource_group" "test" { + name = "acctestlbrg-%d" + location = "West US" +} + +resource "azurerm_virtual_network" "test" { + name = "acctestnet%d" + address_space = ["10.0.0.0/16"] + location = "West US" + resource_group_name = "${azurerm_resource_group.test.name}" +} + +resource "azurerm_subnet" "test" { + name = "acctestsubnet%d" + resource_group_name = "${azurerm_resource_group.test.name}" + virtual_network_name = "${azurerm_virtual_network.test.name}" + address_prefix = "10.0.0.0/24" +} + +resource "azurerm_simple_lb" "test" { + name = "acctestlb%d" + location = "West US" + type = "Microsoft.Network/loadBalancers" + resource_group_name = "${azurerm_resource_group.test.name}" + frontend_subnet = "${azurerm_subnet.test.id}" + frontend_allocation_method = "Dynamic" + + probe { + name = "testProbe1" + protocol = "Tcp" + port = 22 + interval = 5 + number_of_probes = 16 + } + + rule { + protocol = "Tcp" + load_distribution = "Default" + frontend_port = 22 + backend_port = 22 + name = "rule1" + probe_name = "testProbe1" + } +} +` diff --git a/builtin/providers/azurerm/util.go b/builtin/providers/azurerm/util.go new file mode 100644 index 000000000000..60506bffb09f --- /dev/null +++ b/builtin/providers/azurerm/util.go @@ -0,0 +1,30 @@ +package azurerm + +import "strings" + +func String(value interface{}) *string { + s := value.(string) + return &s +} + +func Int(value interface{}) *int { + i := value.(int) + return &i +} + +func Int32(value interface{}) *int32 { + i := value.(int) + i32 := int32(i) + return &i32 +} + +func isOneOf(haystack []string, needle interface{}) bool { + n := strings.ToLower(needle.(string)) + for _, h := range haystack { + if h == n { + return true + } + } + + return false +}