From 0b7c2fc4bac64edc30b3ca085309c3ed60288beb Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Thu, 20 Aug 2015 20:52:30 +0100 Subject: [PATCH] google: Add new resource - google_compute_backend_service --- provider.go | 1 + resource_compute_backend_service.go | 410 ++++++++++++++++++++++++++++ 2 files changed, 411 insertions(+) create mode 100644 resource_compute_backend_service.go diff --git a/provider.go b/provider.go index d7e29330..a7438995 100644 --- a/provider.go +++ b/provider.go @@ -36,6 +36,7 @@ func Provider() terraform.ResourceProvider { ResourcesMap: map[string]*schema.Resource{ "google_compute_autoscaler": resourceComputeAutoscaler(), "google_compute_address": resourceComputeAddress(), + "google_compute_backend_service": resourceComputeBackendService(), "google_compute_disk": resourceComputeDisk(), "google_compute_firewall": resourceComputeFirewall(), "google_compute_forwarding_rule": resourceComputeForwardingRule(), diff --git a/resource_compute_backend_service.go b/resource_compute_backend_service.go new file mode 100644 index 00000000..133af1b0 --- /dev/null +++ b/resource_compute_backend_service.go @@ -0,0 +1,410 @@ +package google + +import ( + "bytes" + "fmt" + "log" + "regexp" + "time" + + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + "google.golang.org/api/compute/v1" + "google.golang.org/api/googleapi" +) + +func resourceComputeBackendService() *schema.Resource { + return &schema.Resource{ + Create: resourceComputeBackendServiceCreate, + Read: resourceComputeBackendServiceRead, + Update: resourceComputeBackendServiceUpdate, + Delete: resourceComputeBackendServiceDelete, + + Schema: map[string]*schema.Schema{ + "backend": &schema.Schema{ + Type: schema.TypeSet, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "balancing_mode": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "UTILIZATION", + }, + "capacity_scaler": &schema.Schema{ + Type: schema.TypeFloat, + Optional: true, + Default: 1, + }, + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "group": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "max_rate": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + }, + "max_rate_per_instance": &schema.Schema{ + Type: schema.TypeFloat, + Optional: true, + }, + "max_utilization": &schema.Schema{ + Type: schema.TypeFloat, + Optional: true, + Default: 0.8, + }, + }, + }, + Optional: true, + Set: resourceGoogleComputeBackendServiceBackendHash, + }, + + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + + "health_checks": &schema.Schema{ + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Required: true, + Set: schema.HashString, + }, + + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + re := `^(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?)$` + if !regexp.MustCompile(re).MatchString(value) { + errors = append(errors, fmt.Errorf( + "%q (%q) doesn't match regexp %q", k, value, re)) + } + return + }, + }, + + "port_name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "protocol": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "timeout_sec": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + + "fingerprint": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "self_link": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceComputeBackendServiceCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + hc := d.Get("health_checks").(*schema.Set).List() + healthChecks := make([]string, 0, len(hc)) + for _, v := range hc { + healthChecks = append(healthChecks, v.(string)) + } + + service := compute.BackendService{ + Name: d.Get("name").(string), + Fingerprint: resource.PrefixedUniqueId("tf-gce-bs-"), + HealthChecks: healthChecks, + } + + if v, ok := d.GetOk("backend"); ok { + service.Backends = expandBackends(v.(*schema.Set).List()) + } + + if v, ok := d.GetOk("description"); ok { + service.Description = v.(string) + } + + if v, ok := d.GetOk("port_name"); ok { + service.PortName = v.(string) + } + + if v, ok := d.GetOk("protocol"); ok { + service.Protocol = v.(string) + } + + if v, ok := d.GetOk("timeout_sec"); ok { + service.TimeoutSec = int64(v.(int)) + } + + log.Printf("[DEBUG] Creating new Backend Service: %#v", service) + op, err := config.clientCompute.BackendServices.Insert( + config.Project, &service).Do() + if err != nil { + return fmt.Errorf("Error creating backend service: %s", err) + } + + log.Printf("[DEBUG] Waiting for new backend service, operation: %#v", op) + + d.SetId(service.Name) + + // Wait for the operation to complete + w := &OperationWaiter{ + Service: config.clientCompute, + Op: op, + Project: config.Project, + Region: config.Region, + Type: OperationWaitGlobal, + } + state := w.Conf() + state.Timeout = 2 * time.Minute + state.MinTimeout = 1 * time.Second + opRaw, err := state.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for backend service to create: %s", err) + } + op = opRaw.(*compute.Operation) + if op.Error != nil { + // The resource didn't actually create + d.SetId("") + + // Return the error + return OperationError(*op.Error) + } + + return resourceComputeBackendServiceRead(d, meta) +} + +func resourceComputeBackendServiceRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + service, err := config.clientCompute.BackendServices.Get( + config.Project, d.Id()).Do() + if err != nil { + if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { + // The resource doesn't exist anymore + d.SetId("") + + return nil + } + + return fmt.Errorf("Error reading service: %s", err) + } + + d.Set("description", service.Description) + d.Set("port_name", service.PortName) + d.Set("protocol", service.Protocol) + d.Set("timeout_sec", service.TimeoutSec) + d.Set("fingerprint", service.Fingerprint) + d.Set("self_link", service.SelfLink) + + d.Set("backend", flattenBackends(service.Backends)) + d.Set("health_checks", service.HealthChecks) + + return nil +} + +func resourceComputeBackendServiceUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + hc := d.Get("health_checks").(*schema.Set).List() + healthChecks := make([]string, 0, len(hc)) + for _, v := range hc { + healthChecks = append(healthChecks, v.(string)) + } + + service := compute.BackendService{ + Name: d.Get("name").(string), + Fingerprint: d.Get("fingerprint").(string), + HealthChecks: healthChecks, + } + + if d.HasChange("backend") { + service.Backends = expandBackends(d.Get("backend").(*schema.Set).List()) + } + if d.HasChange("description") { + service.Description = d.Get("description").(string) + } + if d.HasChange("port_name") { + service.PortName = d.Get("port_name").(string) + } + if d.HasChange("protocol") { + service.Protocol = d.Get("protocol").(string) + } + if d.HasChange("timeout_sec") { + service.TimeoutSec = int64(d.Get("timeout_sec").(int)) + } + + log.Printf("[DEBUG] Updating existing Backend Service %q: %#v", d.Id(), service) + op, err := config.clientCompute.BackendServices.Update( + config.Project, d.Id(), &service).Do() + if err != nil { + return fmt.Errorf("Error updating backend service: %s", err) + } + + d.SetId(service.Name) + + // Wait for the operation to complete + w := &OperationWaiter{ + Service: config.clientCompute, + Op: op, + Project: config.Project, + Region: config.Region, + Type: OperationWaitGlobal, + } + state := w.Conf() + state.Timeout = 2 * time.Minute + state.MinTimeout = 1 * time.Second + opRaw, err := state.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for backend service to update: %s", err) + } + op = opRaw.(*compute.Operation) + if op.Error != nil { + // Return the error + return OperationError(*op.Error) + } + + return resourceComputeBackendServiceRead(d, meta) +} + +func resourceComputeBackendServiceDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + log.Printf("[DEBUG] Deleting backend service %s", d.Id()) + op, err := config.clientCompute.BackendServices.Delete( + config.Project, d.Id()).Do() + if err != nil { + return fmt.Errorf("Error deleting backend service: %s", err) + } + + // Wait for the operation to complete + w := &OperationWaiter{ + Service: config.clientCompute, + Op: op, + Project: config.Project, + Region: config.Region, + Type: OperationWaitGlobal, + } + state := w.Conf() + state.Timeout = 2 * time.Minute + state.MinTimeout = 1 * time.Second + opRaw, err := state.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for backend service to delete: %s", err) + } + op = opRaw.(*compute.Operation) + if op.Error != nil { + // Return the error + return OperationError(*op.Error) + } + + d.SetId("") + return nil +} + +func expandBackends(configured []interface{}) []*compute.Backend { + backends := make([]*compute.Backend, 0, len(configured)) + + for _, raw := range configured { + data := raw.(map[string]interface{}) + + b := compute.Backend{ + Group: data["group"].(string), + } + + if v, ok := data["balancing_mode"]; ok { + b.BalancingMode = v.(string) + } + if v, ok := data["capacity_scaler"]; ok { + b.CapacityScaler = v.(float64) + } + if v, ok := data["description"]; ok { + b.Description = v.(string) + } + if v, ok := data["max_rate"]; ok { + b.MaxRate = int64(v.(int)) + } + if v, ok := data["max_rate_per_instance"]; ok { + b.MaxRatePerInstance = v.(float64) + } + if v, ok := data["max_rate_per_instance"]; ok { + b.MaxUtilization = v.(float64) + } + + backends = append(backends, &b) + } + + return backends +} + +func flattenBackends(backends []*compute.Backend) []map[string]interface{} { + result := make([]map[string]interface{}, 0, len(backends)) + + for _, b := range backends { + data := make(map[string]interface{}) + + data["balancing_mode"] = b.BalancingMode + data["capacity_scaler"] = b.CapacityScaler + data["description"] = b.Description + data["group"] = b.Group + data["max_rate"] = b.MaxRate + data["max_rate_per_instance"] = b.MaxRatePerInstance + data["max_utilization"] = b.MaxUtilization + + result = append(result, data) + } + + return result +} + +func resourceGoogleComputeBackendServiceBackendHash(v interface{}) int { + if v == nil { + return 0 + } + + var buf bytes.Buffer + m := v.(map[string]interface{}) + + buf.WriteString(fmt.Sprintf("%s-", m["group"].(string))) + + if v, ok := m["balancing_mode"]; ok { + buf.WriteString(fmt.Sprintf("%s-", v.(string))) + } + if v, ok := m["capacity_scaler"]; ok { + buf.WriteString(fmt.Sprintf("%f-", v.(float64))) + } + if v, ok := m["description"]; ok { + buf.WriteString(fmt.Sprintf("%s-", v.(string))) + } + if v, ok := m["max_rate"]; ok { + buf.WriteString(fmt.Sprintf("%d-", int64(v.(int)))) + } + if v, ok := m["max_rate_per_instance"]; ok { + buf.WriteString(fmt.Sprintf("%f-", v.(float64))) + } + if v, ok := m["max_rate_per_instance"]; ok { + buf.WriteString(fmt.Sprintf("%f-", v.(float64))) + } + + return hashcode.String(buf.String()) +}