package google import ( "fmt" "log" "regexp" "strings" "github.com/hashicorp/terraform/helper/schema" "google.golang.org/api/compute/v1" "google.golang.org/api/googleapi" ) var instancesSelfLinkPattern = regexp.MustCompile(fmt.Sprintf(zonalLinkBasePattern, "instances")) func resourceComputeTargetPool() *schema.Resource { return &schema.Resource{ Create: resourceComputeTargetPoolCreate, Read: resourceComputeTargetPoolRead, Delete: resourceComputeTargetPoolDelete, Update: resourceComputeTargetPoolUpdate, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, Schema: map[string]*schema.Schema{ "name": { Type: schema.TypeString, Required: true, ForceNew: true, }, "backup_pool": { Type: schema.TypeString, Optional: true, ForceNew: false, }, "description": { Type: schema.TypeString, Optional: true, ForceNew: true, }, "failover_ratio": { Type: schema.TypeFloat, Optional: true, ForceNew: true, }, "health_checks": { Type: schema.TypeList, Optional: true, ForceNew: false, MaxItems: 1, Elem: &schema.Schema{ Type: schema.TypeString, DiffSuppressFunc: compareSelfLinkOrResourceName, }, }, "instances": { Type: schema.TypeSet, Optional: true, Computed: true, ForceNew: false, Elem: &schema.Schema{ Type: schema.TypeString, StateFunc: func(v interface{}) string { return canonicalizeInstanceRef(v.(string)) }, }, Set: func(v interface{}) int { return schema.HashString(canonicalizeInstanceRef(v.(string))) }, }, "project": { Type: schema.TypeString, Optional: true, ForceNew: true, Computed: true, }, "region": { Type: schema.TypeString, Optional: true, ForceNew: true, Computed: true, }, "self_link": { Type: schema.TypeString, Computed: true, }, "session_affinity": { Type: schema.TypeString, Optional: true, ForceNew: true, Default: "NONE", }, }, } } func canonicalizeInstanceRef(instanceRef string) string { // instances can also be specified in the config as a URL or / parts := instancesSelfLinkPattern.FindStringSubmatch(instanceRef) // parts[0] = full match // parts[1] = project // parts[2] = zone // parts[3] = instance name if len(parts) < 4 { return instanceRef } return fmt.Sprintf("%s/%s", parts[2], parts[3]) // return fmt.Sprintf("%s/%s/%s", parts[1], parts[2], parts[3]) } // Healthchecks need to exist before being referred to from the target pool. func convertHealthChecks(healthChecks []interface{}, d *schema.ResourceData, config *Config) ([]string, error) { if healthChecks == nil || len(healthChecks) == 0 { return []string{}, nil } hc, err := ParseHttpHealthCheckFieldValue(healthChecks[0].(string), d, config) if err != nil { return nil, err } return []string{hc.RelativeLink()}, nil } // Instances do not need to exist yet, so we simply generate URLs. // Instances can be full URLS or zone/name func convertInstancesToUrls(project string, names *schema.Set) ([]string, error) { urls := make([]string, len(names.List())) for i, nameI := range names.List() { name := nameI.(string) if strings.HasPrefix(name, "https://www.googleapis.com/compute/v1/") { urls[i] = name } else { splitName := strings.Split(name, "/") if len(splitName) != 2 { return nil, fmt.Errorf("Invalid instance name, require URL or zone/name: %s", name) } else { urls[i] = fmt.Sprintf( "https://www.googleapis.com/compute/v1/projects/%s/zones/%s/instances/%s", project, splitName[0], splitName[1]) } } } return urls, nil } func resourceComputeTargetPoolCreate(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) region, err := getRegion(d, config) if err != nil { return err } project, err := getProject(d, config) if err != nil { return err } hchkUrls, err := convertHealthChecks(d.Get("health_checks").([]interface{}), d, config) if err != nil { return err } instanceUrls, err := convertInstancesToUrls(project, d.Get("instances").(*schema.Set)) if err != nil { return err } // Build the parameter tpool := &compute.TargetPool{ BackupPool: d.Get("backup_pool").(string), Description: d.Get("description").(string), HealthChecks: hchkUrls, Instances: instanceUrls, Name: d.Get("name").(string), SessionAffinity: d.Get("session_affinity").(string), } if d.Get("failover_ratio") != nil { tpool.FailoverRatio = d.Get("failover_ratio").(float64) } log.Printf("[DEBUG] TargetPool insert request: %#v", tpool) op, err := config.clientCompute.TargetPools.Insert( project, region, tpool).Do() if err != nil { if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 && strings.Contains(gerr.Message, "httpHealthChecks") { return fmt.Errorf("Health check %s is not a valid HTTP health check", d.Get("health_checks").([]interface{})[0]) } return fmt.Errorf("Error creating TargetPool: %s", err) } // It probably maybe worked, so store the ID now d.SetId(tpool.Name) err = computeOperationWait(config.clientCompute, op, project, "Creating Target Pool") if err != nil { return err } return resourceComputeTargetPoolRead(d, meta) } func calcAddRemove(from []string, to []string) ([]string, []string) { add := make([]string, 0) remove := make([]string, 0) for _, u := range to { found := false for _, v := range from { if u == v { found = true break } } if !found { add = append(add, u) } } for _, u := range from { found := false for _, v := range to { if u == v { found = true break } } if !found { remove = append(remove, u) } } return add, remove } func resourceComputeTargetPoolUpdate(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) region, err := getRegion(d, config) if err != nil { return err } project, err := getProject(d, config) if err != nil { return err } d.Partial(true) if d.HasChange("health_checks") { from_, to_ := d.GetChange("health_checks") fromUrls, err := convertHealthChecks(from_.([]interface{}), d, config) if err != nil { return err } toUrls, err := convertHealthChecks(to_.([]interface{}), d, config) if err != nil { return err } add, remove := calcAddRemove(fromUrls, toUrls) removeReq := &compute.TargetPoolsRemoveHealthCheckRequest{ HealthChecks: make([]*compute.HealthCheckReference, len(remove)), } for i, v := range remove { removeReq.HealthChecks[i] = &compute.HealthCheckReference{HealthCheck: v} } op, err := config.clientCompute.TargetPools.RemoveHealthCheck( project, region, d.Id(), removeReq).Do() if err != nil { return fmt.Errorf("Error updating health_check: %s", err) } err = computeOperationWait(config.clientCompute, op, project, "Updating Target Pool") if err != nil { return err } addReq := &compute.TargetPoolsAddHealthCheckRequest{ HealthChecks: make([]*compute.HealthCheckReference, len(add)), } for i, v := range add { addReq.HealthChecks[i] = &compute.HealthCheckReference{HealthCheck: v} } op, err = config.clientCompute.TargetPools.AddHealthCheck( project, region, d.Id(), addReq).Do() if err != nil { return fmt.Errorf("Error updating health_check: %s", err) } err = computeOperationWait(config.clientCompute, op, project, "Updating Target Pool") if err != nil { return err } d.SetPartial("health_checks") } if d.HasChange("instances") { old_, new_ := d.GetChange("instances") old := old_.(*schema.Set) new := new_.(*schema.Set) addUrls, err := convertInstancesToUrls(project, new.Difference(old)) if err != nil { return err } removeUrls, err := convertInstancesToUrls(project, old.Difference(new)) if err != nil { return err } addReq := &compute.TargetPoolsAddInstanceRequest{ Instances: make([]*compute.InstanceReference, len(addUrls)), } for i, v := range addUrls { addReq.Instances[i] = &compute.InstanceReference{Instance: v} } op, err := config.clientCompute.TargetPools.AddInstance( project, region, d.Id(), addReq).Do() if err != nil { return fmt.Errorf("Error updating instances: %s", err) } err = computeOperationWait(config.clientCompute, op, project, "Updating Target Pool") if err != nil { return err } removeReq := &compute.TargetPoolsRemoveInstanceRequest{ Instances: make([]*compute.InstanceReference, len(removeUrls)), } for i, v := range removeUrls { removeReq.Instances[i] = &compute.InstanceReference{Instance: v} } op, err = config.clientCompute.TargetPools.RemoveInstance( project, region, d.Id(), removeReq).Do() if err != nil { return fmt.Errorf("Error updating instances: %s", err) } err = computeOperationWait(config.clientCompute, op, project, "Updating Target Pool") if err != nil { return err } d.SetPartial("instances") } if d.HasChange("backup_pool") { bpool_name := d.Get("backup_pool").(string) tref := &compute.TargetReference{ Target: bpool_name, } op, err := config.clientCompute.TargetPools.SetBackup( project, region, d.Id(), tref).Do() if err != nil { return fmt.Errorf("Error updating backup_pool: %s", err) } err = computeOperationWait(config.clientCompute, op, project, "Updating Target Pool") if err != nil { return err } d.SetPartial("backup_pool") } d.Partial(false) return resourceComputeTargetPoolRead(d, meta) } func convertInstancesFromUrls(urls []string) []string { result := make([]string, 0, len(urls)) for _, url := range urls { urlArray := strings.Split(url, "/") instance := fmt.Sprintf("%s/%s", urlArray[len(urlArray)-3], urlArray[len(urlArray)-1]) result = append(result, instance) } return result } func resourceComputeTargetPoolRead(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) region, err := getRegion(d, config) if err != nil { return err } project, err := getProject(d, config) if err != nil { return err } tpool, err := config.clientCompute.TargetPools.Get( project, region, d.Id()).Do() if err != nil { return handleNotFoundError(err, d, fmt.Sprintf("Target Pool %q", d.Get("name").(string))) } d.Set("self_link", tpool.SelfLink) d.Set("backup_pool", tpool.BackupPool) d.Set("description", tpool.Description) d.Set("failover_ratio", tpool.FailoverRatio) d.Set("health_checks", tpool.HealthChecks) if tpool.Instances != nil { d.Set("instances", convertInstancesFromUrls(tpool.Instances)) } else { d.Set("instances", nil) } d.Set("name", tpool.Name) d.Set("region", GetResourceNameFromSelfLink(tpool.Region)) d.Set("session_affinity", tpool.SessionAffinity) d.Set("project", project) return nil } func resourceComputeTargetPoolDelete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) region, err := getRegion(d, config) if err != nil { return err } project, err := getProject(d, config) if err != nil { return err } // Delete the TargetPool op, err := config.clientCompute.TargetPools.Delete( project, region, d.Id()).Do() if err != nil { return fmt.Errorf("Error deleting TargetPool: %s", err) } err = computeOperationWait(config.clientCompute, op, project, "Deleting Target Pool") if err != nil { return err } d.SetId("") return nil }