Skip to content

Commit

Permalink
LoadBalancer Rules support
Browse files Browse the repository at this point in the history
  • Loading branch information
Timothy W Polich committed Apr 8, 2021
1 parent 343ceab commit 4734196
Show file tree
Hide file tree
Showing 2 changed files with 388 additions and 0 deletions.
324 changes: 324 additions & 0 deletions cloudflare/resource_cloudflare_load_balancer.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cloudflare

import (
"context"
"encoding/json"
"fmt"
"log"
"strconv"
Expand Down Expand Up @@ -97,6 +98,7 @@ func resourceCloudflareLoadBalancer() *schema.Resource {
"session_affinity_ttl": {
Type: schema.TypeInt,
Optional: true,
Default: nil,
ValidateFunc: validation.IntBetween(1800, 604800),
},

Expand All @@ -108,6 +110,12 @@ func resourceCloudflareLoadBalancer() *schema.Resource {
},
},

"rules": {
Type: schema.TypeList,
Optional: true,
Elem: rulesElem,
},

// nb enterprise only
"pop_pools": {
Type: schema.TypeSet,
Expand Down Expand Up @@ -136,6 +144,149 @@ func resourceCloudflareLoadBalancer() *schema.Resource {
}
}

var rulesElem = &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringLenBetween(1, 200),
},

"priority": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
Default: nil,
},

"disabled": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},

"condition": {
Type: schema.TypeString,
Optional: true,
Default: "",
},

"terminates": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},

"overrides_tf": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{

"session_affinity": {
Type: schema.TypeString,
Optional: true,
Default: nil,
ValidateFunc: validation.StringInSlice([]string{"", "none", "cookie", "ip_cookie"}, false),
},

"session_affinity_ttl": {
Type: schema.TypeInt,
Optional: true,
Default: nil,
ValidateFunc: validation.IntBetween(1800, 604800),
},

"session_affinity_attributes": {
Type: schema.TypeMap,
Optional: true,
Default: nil,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},

"ttl": {
Type: schema.TypeInt,
Optional: true,
Default: nil,
},

"steering_policy": {
Type: schema.TypeString,
Optional: true,
Default: nil,
ValidateFunc: validation.StringInSlice([]string{"off", "geo", "dynamic_latency", "random", ""}, false),
Computed: true,
},

"fallback_pool": {
Type: schema.TypeString,
Optional: true,
Default: nil,
ValidateFunc: validation.StringLenBetween(1, 32),
},

"default_pools": {
Type: schema.TypeList,
Optional: true,
Default: nil,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.StringLenBetween(1, 32),
},
},

"pop_pools": {
Type: schema.TypeSet,
Optional: true,
Default: nil,
Elem: popPoolElem,
},

"region_pools": {
Type: schema.TypeSet,
Optional: true,
Default: nil,
Elem: regionPoolElem,
},
},
},
},

"fixed_response": {
Type: schema.TypeMap,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"message_body": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringLenBetween(0, 1024),
},

"status_code": {
Type: schema.TypeInt,
Optional: true,
},

"content_type": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringLenBetween(0, 32),
},

"location": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringLenBetween(0, 2048),
},
},
},
},
},
}

var popPoolElem = &schema.Resource{
Schema: map[string]*schema.Schema{
"pop": {
Expand Down Expand Up @@ -232,6 +383,14 @@ func resourceCloudflareLoadBalancerCreate(d *schema.ResourceData, meta interface
newLoadBalancer.SessionAffinityAttributes = sessionAffinityAttributes
}

if rules, ok := d.GetOk("rules"); ok {
v, err := expandRules(rules)
if err != nil {
return err
}
newLoadBalancer.Rules = v
}

log.Printf("[INFO] Creating Cloudflare Load Balancer from struct: %+v", newLoadBalancer)

r, err := client.CreateLoadBalancer(context.Background(), zoneID, newLoadBalancer)
Expand Down Expand Up @@ -300,6 +459,14 @@ func resourceCloudflareLoadBalancerUpdate(d *schema.ResourceData, meta interface
loadBalancer.SessionAffinityAttributes = sessionAffinityAttributes
}

if rules, ok := d.GetOk("rules"); ok {
v, err := expandRules(rules)
if err != nil {
return err
}
loadBalancer.Rules = v
}

log.Printf("[INFO] Updating Cloudflare Load Balancer from struct: %+v", loadBalancer)

_, err := client.ModifyLoadBalancer(context.Background(), zoneID, loadBalancer)
Expand Down Expand Up @@ -360,6 +527,16 @@ func resourceCloudflareLoadBalancerRead(d *schema.ResourceData, meta interface{}
}
}

if len(loadBalancer.Rules) > 0 {
fr, err := flattenRules(loadBalancer.Rules)
if err != nil {
return fmt.Errorf("failed to flatten rules: %s", err)
}
if err := d.Set("rules", fr); err != nil {
return fmt.Errorf("failed to set rules: %s\n %v", err, fr)
}
}

if err := d.Set("default_pool_ids", loadBalancer.DefaultPools); err != nil {
log.Printf("[WARN] Error setting default_pool_ids on load balancer %q: %s", d.Id(), err)
}
Expand Down Expand Up @@ -434,6 +611,153 @@ func resourceCloudflareLoadBalancerImport(d *schema.ResourceData, meta interface
return []*schema.ResourceData{d}, nil
}

func flattenRules(rules []*cloudflare.LoadBalancerRule) (interface{}, error) {

if len(rules) == 0 {
return nil, nil
}

bb, err := json.Marshal(rules)
if err != nil {
return nil, err
}

var rout []interface{}
if err := json.Unmarshal(bb, &rout); err != nil {
return nil, err
}

// terraform doesn't support maps of maps so lets
// rewrite out normal map into the "set" type of "overrides_tf"
for _, rewrite := range rout {
v := rewrite.(map[string]interface{})
if ov, ok := v["overrides"]; ok {
delete(v, "overrides")
v["overrides_tf"] = []interface{}{ov}
}

// fix up status_code
if fr, ok := v["fixed_response"]; ok {
mfr := fr.(map[string]interface{})
if sc, ok := mfr["status_code"]; ok {
mfr["status_code"] = strconv.FormatInt(int64(sc.(float64)), 10)
}
}
}

return rout, nil
}

func expandRules(rdata interface{}) ([]*cloudflare.LoadBalancerRule, error) {

var rules []*cloudflare.LoadBalancerRule
for _, ele := range rdata.([]interface{}) {
r := ele.(map[string]interface{})
lbr := &cloudflare.LoadBalancerRule{
Name: r["name"].(string),
}
if v, ok := r["priority"]; ok {
lbr.Priority = v.(int)
}
if d, ok := r["disabled"]; ok {
lbr.Disabled = d.(bool)
}
if c, ok := r["condition"]; ok {
lbr.Condition = c.(string)
}
if t, ok := r["terminates"]; ok {
lbr.Terminates = t.(bool)
}

if overridesData, ok := r["overrides_tf"]; ok && len(overridesData.([]interface{})) > 0 {
ov := overridesData.([]interface{})[0].(map[string]interface{})

if sa, ok := ov["session_affinity"]; ok {
lbr.Overrides.Persistence = sa.(string)
}

if sattl, ok := ov["session_affinity_ttl"]; ok {
v := uint(sattl.(int))
lbr.Overrides.PersistenceTTL = &v
}

if saattr, ok := ov["session_affinity_attributes"]; ok {
attr := saattr.(map[string]interface{})
v := &cloudflare.LoadBalancerRuleOverridesSessionAffinityAttrs{}
if ss, ok := attr["samesite"]; ok {
v.SameSite = ss.(string)
lbr.Overrides.SessionAffinityAttrs = v
}
if sec, ok := attr["secure"]; ok {
v.Secure = sec.(string)
lbr.Overrides.SessionAffinityAttrs = v
}
}

if ttl, ok := ov["ttl"]; ok {
lbr.Overrides.TTL = uint(ttl.(int))
}

if sp, ok := ov["steering_policy"]; ok {
lbr.Overrides.SteeringPolicy = sp.(string)
}

if fb, ok := ov["fallback_pool"]; ok {
lbr.Overrides.FallbackPool = fb.(string)
}

if dp, ok := ov["default_pools"]; ok {
lbr.Overrides.DefaultPools = expandInterfaceToStringList(dp)
}

if pp, ok := ov["pop_pools"]; ok {
expandedPopPools, err := expandGeoPools(pp, "pop")
if err != nil {
return nil, err
}
lbr.Overrides.PoPPools = expandedPopPools
}

if rp, ok := ov["region_pools"]; ok {
expandedRegionPools, err := expandGeoPools(rp, "region")
if err != nil {
return nil, err
}
lbr.Overrides.RegionPools = expandedRegionPools
}
}

if fixedResponseData, ok := r["fixed_response"]; ok {
frd := fixedResponseData.(map[string]interface{})
// we don't add this into our LB unless one of the cases below is true
fr := &cloudflare.LoadBalancerFixedResponseData{}
if mb, ok := frd["message_body"]; ok {
fr.MessageBody = mb.(string)
lbr.FixedResponse = fr
}
if sc, ok := frd["status_code"]; ok {
scint, err := strconv.ParseInt(sc.(string), 10, 64)
if err != nil {
return nil, err
}
fr.StatusCode = int(scint)
lbr.FixedResponse = fr
}
if ct, ok := frd["content_type"]; ok {
fr.ContentType = ct.(string)
lbr.FixedResponse = fr
}
if l, ok := frd["location"]; ok {
fr.Location = l.(string)
lbr.FixedResponse = fr
}

}
rules = append(rules, lbr)
}
return rules, nil
}

func expandSessionAffinityAttrs(attrs interface{}) (*cloudflare.SessionAffinityAttributes, error) {
var cfSessionAffinityAttrs cloudflare.SessionAffinityAttributes

Expand Down
Loading

0 comments on commit 4734196

Please sign in to comment.