package openstack import ( "fmt" "log" "net/url" "time" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/l7policies" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools" ) func resourceL7PolicyV2() *schema.Resource { return &schema.Resource{ Create: resourceL7PolicyV2Create, Read: resourceL7PolicyV2Read, Update: resourceL7PolicyV2Update, Delete: resourceL7PolicyV2Delete, Importer: &schema.ResourceImporter{ State: resourceL7PolicyV2Import, }, Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(10 * time.Minute), Update: schema.DefaultTimeout(10 * time.Minute), Delete: schema.DefaultTimeout(10 * time.Minute), }, Schema: map[string]*schema.Schema{ "region": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, "tenant_id": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, "name": { Type: schema.TypeString, Optional: true, }, "description": { Type: schema.TypeString, Optional: true, }, "action": { Type: schema.TypeString, Required: true, ValidateFunc: validation.StringInSlice([]string{ "REDIRECT_TO_POOL", "REDIRECT_TO_URL", "REJECT", }, true), }, "listener_id": { Type: schema.TypeString, Required: true, ForceNew: true, }, "position": { Type: schema.TypeInt, Optional: true, Computed: true, }, "redirect_pool_id": { Type: schema.TypeString, ConflictsWith: []string{"redirect_url"}, Optional: true, }, "redirect_url": { Type: schema.TypeString, ConflictsWith: []string{"redirect_pool_id"}, Optional: true, ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { value := v.(string) _, err := url.ParseRequestURI(value) if err != nil { errors = append(errors, fmt.Errorf("URL is not valid: %s", err)) } return }, }, "admin_state_up": { Type: schema.TypeBool, Default: true, Optional: true, }, }, } } func resourceL7PolicyV2Create(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) lbClient, err := chooseLBV2Client(d, config) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } // Assign some required variables for use in creation. listenerID := d.Get("listener_id").(string) action := d.Get("action").(string) redirectPoolID := d.Get("redirect_pool_id").(string) redirectURL := d.Get("redirect_url").(string) // Ensure the right combination of options have been specified. err = checkL7PolicyAction(action, redirectURL, redirectPoolID) if err != nil { return fmt.Errorf("Unable to create L7 Policy: %s", err) } adminStateUp := d.Get("admin_state_up").(bool) createOpts := l7policies.CreateOpts{ TenantID: d.Get("tenant_id").(string), Name: d.Get("name").(string), Description: d.Get("description").(string), Action: l7policies.Action(action), ListenerID: listenerID, RedirectPoolID: redirectPoolID, RedirectURL: redirectURL, AdminStateUp: &adminStateUp, } if v, ok := d.GetOk("position"); ok { createOpts.Position = int32(v.(int)) } log.Printf("[DEBUG] Create Options: %#v", createOpts) timeout := d.Timeout(schema.TimeoutCreate) // Make sure the associated pool is active before proceeding. if redirectPoolID != "" { pool, err := pools.Get(lbClient, redirectPoolID).Extract() if err != nil { return fmt.Errorf("Unable to retrieve %s: %s", redirectPoolID, err) } err = waitForLBV2Pool(lbClient, pool, "ACTIVE", lbPendingStatuses, timeout) if err != nil { return err } } // Get a clean copy of the parent listener. parentListener, err := listeners.Get(lbClient, listenerID).Extract() if err != nil { return fmt.Errorf("Unable to retrieve listener %s: %s", listenerID, err) } // Wait for parent Listener to become active before continuing. err = waitForLBV2Listener(lbClient, parentListener, "ACTIVE", lbPendingStatuses, timeout) if err != nil { return err } log.Printf("[DEBUG] Attempting to create L7 Policy") var l7Policy *l7policies.L7Policy err = resource.Retry(timeout, func() *resource.RetryError { l7Policy, err = l7policies.Create(lbClient, createOpts).Extract() if err != nil { return checkForRetryableError(err) } return nil }) if err != nil { return fmt.Errorf("Error creating L7 Policy: %s", err) } // Wait for L7 Policy to become active before continuing err = waitForLBV2L7Policy(lbClient, parentListener, l7Policy, "ACTIVE", lbPendingStatuses, timeout) if err != nil { return err } d.SetId(l7Policy.ID) return resourceL7PolicyV2Read(d, meta) } func resourceL7PolicyV2Read(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) lbClient, err := chooseLBV2Client(d, config) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } l7Policy, err := l7policies.Get(lbClient, d.Id()).Extract() if err != nil { return CheckDeleted(d, err, "L7 Policy") } log.Printf("[DEBUG] Retrieved L7 Policy %s: %#v", d.Id(), l7Policy) d.Set("action", l7Policy.Action) d.Set("description", l7Policy.Description) d.Set("tenant_id", l7Policy.TenantID) d.Set("name", l7Policy.Name) d.Set("position", int(l7Policy.Position)) d.Set("redirect_url", l7Policy.RedirectURL) d.Set("redirect_pool_id", l7Policy.RedirectPoolID) d.Set("region", GetRegion(d, config)) d.Set("admin_state_up", l7Policy.AdminStateUp) return nil } func resourceL7PolicyV2Update(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) lbClient, err := chooseLBV2Client(d, config) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } // Assign some required variables for use in updating. listenerID := d.Get("listener_id").(string) action := d.Get("action").(string) redirectPoolID := d.Get("redirect_pool_id").(string) redirectURL := d.Get("redirect_url").(string) var updateOpts l7policies.UpdateOpts if d.HasChange("action") { updateOpts.Action = l7policies.Action(action) } if d.HasChange("name") { name := d.Get("name").(string) updateOpts.Name = &name } if d.HasChange("description") { description := d.Get("description").(string) updateOpts.Description = &description } if d.HasChange("redirect_pool_id") { redirectPoolID = d.Get("redirect_pool_id").(string) updateOpts.RedirectPoolID = &redirectPoolID } if d.HasChange("redirect_url") { redirectURL = d.Get("redirect_url").(string) updateOpts.RedirectURL = &redirectURL } if d.HasChange("position") { updateOpts.Position = int32(d.Get("position").(int)) } if d.HasChange("admin_state_up") { adminStateUp := d.Get("admin_state_up").(bool) updateOpts.AdminStateUp = &adminStateUp } // Ensure the right combination of options have been specified. err = checkL7PolicyAction(action, redirectURL, redirectPoolID) if err != nil { return err } // Make sure the pool is active before continuing. timeout := d.Timeout(schema.TimeoutUpdate) if redirectPoolID != "" { pool, err := pools.Get(lbClient, redirectPoolID).Extract() if err != nil { return fmt.Errorf("Unable to retrieve %s: %s", redirectPoolID, err) } err = waitForLBV2Pool(lbClient, pool, "ACTIVE", lbPendingStatuses, timeout) if err != nil { return err } } // Get a clean copy of the parent listener. parentListener, err := listeners.Get(lbClient, listenerID).Extract() if err != nil { return fmt.Errorf("Unable to retrieve parent listener %s: %s", listenerID, err) } // Get a clean copy of the L7 Policy. l7Policy, err := l7policies.Get(lbClient, d.Id()).Extract() if err != nil { return fmt.Errorf("Unable to retrieve L7 Policy: %s: %s", d.Id(), err) } // Wait for parent Listener to become active before continuing. err = waitForLBV2Listener(lbClient, parentListener, "ACTIVE", lbPendingStatuses, timeout) if err != nil { return err } // Wait for L7 Policy to become active before continuing err = waitForLBV2L7Policy(lbClient, parentListener, l7Policy, "ACTIVE", lbPendingStatuses, timeout) if err != nil { return err } log.Printf("[DEBUG] Updating L7 Policy %s with options: %#v", d.Id(), updateOpts) err = resource.Retry(timeout, func() *resource.RetryError { _, err = l7policies.Update(lbClient, d.Id(), updateOpts).Extract() if err != nil { return checkForRetryableError(err) } return nil }) if err != nil { return fmt.Errorf("Unable to update L7 Policy %s: %s", d.Id(), err) } // Wait for L7 Policy to become active before continuing err = waitForLBV2L7Policy(lbClient, parentListener, l7Policy, "ACTIVE", lbPendingStatuses, timeout) if err != nil { return err } return resourceL7PolicyV2Read(d, meta) } func resourceL7PolicyV2Delete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) lbClient, err := chooseLBV2Client(d, config) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } timeout := d.Timeout(schema.TimeoutDelete) listenerID := d.Get("listener_id").(string) // Get a clean copy of the listener. listener, err := listeners.Get(lbClient, listenerID).Extract() if err != nil { return fmt.Errorf("Unable to retrieve parent listener (%s) for the L7 Policy: %s", listenerID, err) } // Get a clean copy of the L7 Policy. l7Policy, err := l7policies.Get(lbClient, d.Id()).Extract() if err != nil { return CheckDeleted(d, err, "Unable to retrieve L7 Policy") } // Wait for Listener to become active before continuing. err = waitForLBV2Listener(lbClient, listener, "ACTIVE", lbPendingStatuses, timeout) if err != nil { return err } log.Printf("[DEBUG] Attempting to delete L7 Policy %s", d.Id()) err = resource.Retry(timeout, func() *resource.RetryError { err = l7policies.Delete(lbClient, d.Id()).ExtractErr() if err != nil { return checkForRetryableError(err) } return nil }) if err != nil { return CheckDeleted(d, err, "Error deleting L7 Policy") } err = waitForLBV2L7Policy(lbClient, listener, l7Policy, "DELETED", lbPendingDeleteStatuses, timeout) if err != nil { return err } return nil } func resourceL7PolicyV2Import(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { config := meta.(*Config) lbClient, err := chooseLBV2Client(d, config) if err != nil { return nil, fmt.Errorf("Error creating OpenStack networking client: %s", err) } l7Policy, err := l7policies.Get(lbClient, d.Id()).Extract() if err != nil { return nil, CheckDeleted(d, err, "L7 Policy") } log.Printf("[DEBUG] Retrieved L7 Policy %s during the import: %#v", d.Id(), l7Policy) if l7Policy.ListenerID != "" { d.Set("listener_id", l7Policy.ListenerID) } else { // Fallback for the Neutron LBaaSv2 extension listenerID, err := getListenerIDForL7Policy(lbClient, d.Id()) if err != nil { return nil, err } d.Set("listener_id", listenerID) } return []*schema.ResourceData{d}, nil } func checkL7PolicyAction(action, redirectURL, redirectPoolID string) error { if action == "REJECT" { if redirectURL != "" || redirectPoolID != "" { return fmt.Errorf( "redirect_url and redirect_pool_id must be empty when action is set to %s", action) } } if action == "REDIRECT_TO_POOL" && redirectURL != "" { return fmt.Errorf("redirect_url must be empty when action is set to %s", action) } if action == "REDIRECT_TO_URL" && redirectPoolID != "" { return fmt.Errorf("redirect_pool_id must be empty when action is set to %s", action) } return nil }