From af0e8279759215ad023890e69622b176177891c4 Mon Sep 17 00:00:00 2001 From: Adam Burnett Date: Tue, 15 Mar 2016 21:44:35 -0400 Subject: [PATCH 1/4] added policy creation --- bigip/provider.go | 40 +++++++++-- bigip/provider_test.go | 21 +++++- bigip/resource_bigip_ltm_irule.go | 26 +++---- bigip/resource_bigip_ltm_monitor.go | 84 +++++++++++----------- bigip/resource_bigip_ltm_node.go | 24 +++---- bigip/resource_bigip_ltm_pool.go | 64 ++++++++--------- bigip/resource_bigip_ltm_virtual_server.go | 3 +- main.go | 2 +- 8 files changed, 155 insertions(+), 109 deletions(-) diff --git a/bigip/provider.go b/bigip/provider.go index 83a420c..1fc27bc 100644 --- a/bigip/provider.go +++ b/bigip/provider.go @@ -3,6 +3,9 @@ package bigip import ( "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/terraform" + "log" + "reflect" + "strings" ) const DEFAULT_PARTITION = "Common" @@ -30,7 +33,7 @@ func Provider() terraform.ResourceProvider { Optional: true, Default: false, Description: "Enable to use an external authentication source (LDAP, TACACS, etc)", - }, + }, "login_ref": &schema.Schema{ Type: schema.TypeString, Optional: true, @@ -40,12 +43,13 @@ func Provider() terraform.ResourceProvider { }, ResourcesMap: map[string]*schema.Resource{ - "bigip_ltm_virtual_server": resourceBigipLtmVirtualServer(), - "bigip_ltm_node": resourceBigipLtmNode(), - "bigip_ltm_pool": resourceBigipLtmPool(), - "bigip_ltm_monitor": resourceBigipLtmMonitor(), - "bigip_ltm_irule": resourceBigipLtmIRule(), + "bigip_ltm_virtual_server": resourceBigipLtmVirtualServer(), + "bigip_ltm_node": resourceBigipLtmNode(), + "bigip_ltm_pool": resourceBigipLtmPool(), + "bigip_ltm_monitor": resourceBigipLtmMonitor(), + "bigip_ltm_irule": resourceBigipLtmIRule(), "bigip_ltm_virtual_address": resourceBigipLtmVirtualAddress(), + "bigip_ltm_policy": resourceBigipLtmPolicy(), }, ConfigureFunc: providerConfigure, @@ -65,6 +69,7 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) { return config.Client() } +//Convert slice of strings to schema.Set func makeStringSet(list *[]string) *schema.Set { ilist := make([]interface{}, len(*list)) for i, v := range *list { @@ -73,6 +78,7 @@ func makeStringSet(list *[]string) *schema.Set { return schema.NewSet(schema.HashString, ilist) } +//Convert schema.Set to a slice of strings func setToStringSlice(s *schema.Set) []string { list := make([]string, s.Len()) for i, v := range s.List() { @@ -80,3 +86,25 @@ func setToStringSlice(s *schema.Set) []string { } return list } + +//Copy map values into an object where map key == object field name (e.g. map[foo] == &{Foo: ...} +func mapEntity(d map[string]interface{}, obj interface{}) { + val := reflect.ValueOf(obj).Elem() + for field, _ := range d { + f := val.FieldByName(strings.Title(field)) + if f.IsValid() { + if f.Kind() == reflect.Slice { + incoming := d[field].([]interface{}) + s := reflect.MakeSlice(f.Type(), len(incoming), len(incoming)) + for i := 0; i < len(incoming); i++ { + s.Index(i).Set(reflect.ValueOf(incoming[i])) + } + f.Set(s) + } else { + f.Set(reflect.ValueOf(d[field])) + } + } else { + log.Printf("[WARN] You probably weren't expecting %s to be an invalid field", field) + } + } +} diff --git a/bigip/provider_test.go b/bigip/provider_test.go index 5021f9f..7ed8934 100644 --- a/bigip/provider_test.go +++ b/bigip/provider_test.go @@ -5,6 +5,8 @@ import ( "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/terraform" + "github.com/scottdware/go-bigip" + "github.com/stretchr/testify/assert" ) var testAccProviders map[string]terraform.ResourceProvider @@ -21,4 +23,21 @@ func TestProvider(t *testing.T) { if err := Provider().(*schema.Provider).InternalValidate(); err != nil { t.Fatalf("err: %s", err) } -} \ No newline at end of file +} + +func TestMapEntity(t *testing.T) { + var a bigip.PolicyRuleAction + m := map[string]interface{}{ + "name": "foo", + "asm": true, + "timeout": 1, + "clonePool": "pool", + } + + mapEntity(m, &a) + + assert.Equal(t, "foo", a.Name) + assert.Equal(t, true, a.Asm) + assert.Equal(t, 1, a.Timeout) + assert.Equal(t, "pool", a.ClonePool) +} diff --git a/bigip/resource_bigip_ltm_irule.go b/bigip/resource_bigip_ltm_irule.go index 1ea2112..8f93202 100644 --- a/bigip/resource_bigip_ltm_irule.go +++ b/bigip/resource_bigip_ltm_irule.go @@ -3,8 +3,8 @@ package bigip import ( "log" - "github.com/scottdware/go-bigip" "github.com/hashicorp/terraform/helper/schema" + "github.com/scottdware/go-bigip" ) func resourceBigipLtmIRule() *schema.Resource { @@ -17,22 +17,22 @@ func resourceBigipLtmIRule() *schema.Resource { Schema: map[string]*schema.Schema{ "name": &schema.Schema{ - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, Description: "Name of the iRule", - ForceNew: true, + ForceNew: true, }, "partition": &schema.Schema{ - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Optional: true, Description: "LTM Partition", - ForceNew: true, + ForceNew: true, }, "irule": &schema.Schema{ - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, Description: "The iRule body", }, }, @@ -58,7 +58,7 @@ func resourceBigipLtmIRuleRead(d *schema.ResourceData, meta interface{}) error { name := d.Id() irule, err := client.IRule(name) - if err != nil{ + if err != nil { return err } d.Set("partition", irule.Partition) @@ -87,9 +87,9 @@ func resourceBigipLtmIRuleUpdate(d *schema.ResourceData, meta interface{}) error name := d.Id() r := &bigip.IRule{ - Name: name, + Name: name, Partition: d.Get("partition").(string), - Rule: d.Get("irule").(string), + Rule: d.Get("irule").(string), } return client.ModifyIRule(name, r) @@ -99,4 +99,4 @@ func resourceBigipLtmIRuleDelete(d *schema.ResourceData, meta interface{}) error client := meta.(*bigip.BigIP) name := d.Id() return client.DeleteIRule(name) -} \ No newline at end of file +} diff --git a/bigip/resource_bigip_ltm_monitor.go b/bigip/resource_bigip_ltm_monitor.go index ca5760e..ad69aa4 100644 --- a/bigip/resource_bigip_ltm_monitor.go +++ b/bigip/resource_bigip_ltm_monitor.go @@ -1,11 +1,11 @@ package bigip import ( - "log" "fmt" + "log" - "github.com/scottdware/go-bigip" "github.com/hashicorp/terraform/helper/schema" + "github.com/scottdware/go-bigip" ) func resourceBigipLtmMonitor() *schema.Resource { @@ -18,89 +18,89 @@ func resourceBigipLtmMonitor() *schema.Resource { Schema: map[string]*schema.Schema{ "name": &schema.Schema{ - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, Description: "Name of the monitor", - ForceNew: true, + ForceNew: true, }, "parent": &schema.Schema{ - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, ValidateFunc: validateParent, - ForceNew: true, - Description: "Existing monitor to inherit from. Must be one of http, https, icmp or gateway-icmp.", + ForceNew: true, + Description: "Existing monitor to inherit from. Must be one of http, https, icmp or gateway-icmp.", }, "interval": &schema.Schema{ - Type: schema.TypeInt, - Optional: true, + Type: schema.TypeInt, + Optional: true, Description: "Check interval in seconds", - Default: 3, + Default: 3, }, "timeout": &schema.Schema{ - Type: schema.TypeInt, - Optional: true, + Type: schema.TypeInt, + Optional: true, Description: "Timeout in seconds", - Default: 16, + Default: 16, }, "send": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Default: "GET /\\r\\n", + Type: schema.TypeString, + Optional: true, + Default: "GET /\\r\\n", Description: "Request string to send.", }, "receive": &schema.Schema{ - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Optional: true, Description: "Expected response string.", }, "receive_disable": &schema.Schema{ - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Optional: true, Description: "Expected response string.", }, "partition": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Default: DEFAULT_PARTITION, + Type: schema.TypeString, + Optional: true, + Default: DEFAULT_PARTITION, Description: "LTM Partition", - ForceNew: true, + ForceNew: true, }, "reverse": &schema.Schema{ Type: schema.TypeBool, Optional: true, - Default: false, + Default: false, }, "transparent": &schema.Schema{ Type: schema.TypeBool, Optional: true, - Default: false, + Default: false, }, "manual_resume": &schema.Schema{ Type: schema.TypeBool, Optional: true, - Default: false, + Default: false, }, "ip_dscp": &schema.Schema{ Type: schema.TypeInt, Optional: true, - Default: 0, + Default: 0, }, "time_until_up": &schema.Schema{ - Type: schema.TypeInt, - Optional: true, - Default: 0, + Type: schema.TypeInt, + Optional: true, + Default: 0, Description: "Time in seconds", }, }, @@ -182,16 +182,16 @@ func resourceBigipLtmMonitorUpdate(d *schema.ResourceData, meta interface{}) err name := d.Id() m := &bigip.Monitor{ - Interval: d.Get("interval").(int), - Timeout: d.Get("timeout").(int), - SendString: d.Get("send").(string), - ReceiveString: d.Get("receive").(string), + Interval: d.Get("interval").(int), + Timeout: d.Get("timeout").(int), + SendString: d.Get("send").(string), + ReceiveString: d.Get("receive").(string), ReceiveDisable: d.Get("receive_disable").(string), - Reverse: d.Get("reverse").(bool), - Transparent: d.Get("transparent").(bool), - IPDSCP: d.Get("ip_dscp").(int), - TimeUntilUp: d.Get("time_until_up").(int), - ManualResume: d.Get("manual_resume").(bool), + Reverse: d.Get("reverse").(bool), + Transparent: d.Get("transparent").(bool), + IPDSCP: d.Get("ip_dscp").(int), + TimeUntilUp: d.Get("time_until_up").(int), + ManualResume: d.Get("manual_resume").(bool), } return client.ModifyMonitor(name, d.Get("parent").(string), m) diff --git a/bigip/resource_bigip_ltm_node.go b/bigip/resource_bigip_ltm_node.go index 4918f71..9afcf85 100644 --- a/bigip/resource_bigip_ltm_node.go +++ b/bigip/resource_bigip_ltm_node.go @@ -3,8 +3,8 @@ package bigip import ( "log" - "github.com/scottdware/go-bigip" "github.com/hashicorp/terraform/helper/schema" + "github.com/scottdware/go-bigip" "regexp" "strings" ) @@ -19,17 +19,17 @@ func resourceBigipLtmNode() *schema.Resource { Schema: map[string]*schema.Schema{ "name": &schema.Schema{ - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, Description: "Name of the node", - ForceNew: true, + ForceNew: true, }, "address": &schema.Schema{ - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, Description: "Address of the node", - ForceNew: true, + ForceNew: true, }, }, } @@ -75,7 +75,7 @@ func resourceBigipLtmNodeRead(d *schema.ResourceData, meta interface{}) error { d.Set("name", node.Name) d.Set("address", node.Address) - return nil; + return nil } func resourceBigipLtmNodeExists(d *schema.ResourceData, meta interface{}) (bool, error) { @@ -102,7 +102,7 @@ func resourceBigipLtmNodeUpdate(d *schema.ResourceData, meta interface{}) error name := d.Id() vs := &bigip.Node{ - Name: name, + Name: name, Address: d.Get("address").(string), } @@ -126,9 +126,9 @@ func resourceBigipLtmNodeDelete(d *schema.ResourceData, meta interface{}) error if e != nil { return e } - for _,member := range(members) { - if strings.HasPrefix(member,name + ":") { - e = client.DeletePoolMember(poolName,member) + for _, member := range members { + if strings.HasPrefix(member, name+":") { + e = client.DeletePoolMember(poolName, member) if e != nil { return e } diff --git a/bigip/resource_bigip_ltm_pool.go b/bigip/resource_bigip_ltm_pool.go index d3eebaa..23043e6 100644 --- a/bigip/resource_bigip_ltm_pool.go +++ b/bigip/resource_bigip_ltm_pool.go @@ -5,8 +5,8 @@ import ( "regexp" "strings" - "github.com/scottdware/go-bigip" "github.com/hashicorp/terraform/helper/schema" + "github.com/scottdware/go-bigip" ) var NODE_VALIDATION = regexp.MustCompile(":\\d{2,5}$") @@ -21,54 +21,54 @@ func resourceBigipLtmPool() *schema.Resource { Schema: map[string]*schema.Schema{ "name": &schema.Schema{ - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, Description: "Name of the pool", - ForceNew: true, + ForceNew: true, }, "nodes": &schema.Schema{ - Type: schema.TypeSet, - Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, - Optional: true, + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + Optional: true, Description: "Nodes to add to the pool. Format node_name:port. e.g. node01:443", }, "monitors": &schema.Schema{ - Type: schema.TypeSet, - Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, - Optional: true, + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + Optional: true, Description: "Assign monitors to a pool.", }, "partition": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Default: DEFAULT_PARTITION, + Type: schema.TypeString, + Optional: true, + Default: DEFAULT_PARTITION, Description: "LTM Partition", - ForceNew: true, + ForceNew: true, }, "allow_nat": &schema.Schema{ - Type: schema.TypeBool, - Optional: true, - Default: true, + Type: schema.TypeBool, + Optional: true, + Default: true, Description: "Allow NAT", }, "allow_snat": &schema.Schema{ - Type: schema.TypeBool, - Optional: true, - Default: true, + Type: schema.TypeBool, + Optional: true, + Default: true, Description: "Allow SNAT", }, "load_balancing_mode": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Default: "round-robin", + Type: schema.TypeString, + Optional: true, + Default: "round-robin", Description: "Possible values: round-robin, ...", }, }, @@ -127,7 +127,7 @@ func resourceBigipLtmPoolRead(d *schema.ResourceData, meta interface{}) error { monitors := strings.Split(strings.TrimSpace(pool.Monitor), " and ") d.Set("monitors", makeStringSet(&monitors)) - return nil; + return nil } func resourceBigipLtmPoolExists(d *schema.ResourceData, meta interface{}) (bool, error) { @@ -162,11 +162,11 @@ func resourceBigipLtmPoolUpdate(d *schema.ResourceData, meta interface{}) error } pool := &bigip.Pool{ - Name: name, - AllowNAT: d.Get("allow_nat").(bool), - AllowSNAT: d.Get("allow_snat").(bool), + Name: name, + AllowNAT: d.Get("allow_nat").(bool), + AllowSNAT: d.Get("allow_snat").(bool), LoadBalancingMode: d.Get("load_balancing_mode").(string), - Monitor: strings.Join(monitors, " and "), + Monitor: strings.Join(monitors, " and "), //Partition: d.Get("partition").(string), } @@ -198,8 +198,8 @@ func resourceBigipLtmPoolUpdate(d *schema.ResourceData, meta interface{}) error return nil } -func mapify(s []string) (map[string]struct{}) { - set := make(map[string] struct{}, len(s)) +func mapify(s []string) map[string]struct{} { + set := make(map[string]struct{}, len(s)) for _, s := range s { set[s] = struct{}{} } diff --git a/bigip/resource_bigip_ltm_virtual_server.go b/bigip/resource_bigip_ltm_virtual_server.go index 3f515ac..15b6a3c 100644 --- a/bigip/resource_bigip_ltm_virtual_server.go +++ b/bigip/resource_bigip_ltm_virtual_server.go @@ -4,8 +4,7 @@ import ( "fmt" "log" "regexp" - - "strings" + "strings" "github.com/hashicorp/terraform/helper/schema" "github.com/scottdware/go-bigip" diff --git a/main.go b/main.go index 0b47aac..6adfe85 100644 --- a/main.go +++ b/main.go @@ -9,4 +9,4 @@ func main() { plugin.Serve(&plugin.ServeOpts{ ProviderFunc: bigip.Provider, }) -} \ No newline at end of file +} From 1314299b6924e6c377e2a3c33bfcda5792c51130 Mon Sep 17 00:00:00 2001 From: Adam Burnett Date: Wed, 16 Mar 2016 17:50:31 -0400 Subject: [PATCH 2/4] [Added] LTM Policy support [Fixed] iRule exists check [Fixed] Source formatting (go fmt) --- bigip/provider_test.go | 19 - bigip/resource_bigip_ltm_irule.go | 2 +- bigip/resource_bigip_ltm_policy.go | 1197 +++++++++++++++++++++++ bigip/resource_bigip_ltm_policy_test.go | 67 ++ bigip/resource_bigip_ltm_pool.go | 8 - bigip/validators.go | 28 + bigip/validators_test.go | 17 + 7 files changed, 1310 insertions(+), 28 deletions(-) create mode 100644 bigip/resource_bigip_ltm_policy.go create mode 100644 bigip/resource_bigip_ltm_policy_test.go create mode 100644 bigip/validators.go create mode 100644 bigip/validators_test.go diff --git a/bigip/provider_test.go b/bigip/provider_test.go index 7ed8934..6dca4b9 100644 --- a/bigip/provider_test.go +++ b/bigip/provider_test.go @@ -5,8 +5,6 @@ import ( "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/terraform" - "github.com/scottdware/go-bigip" - "github.com/stretchr/testify/assert" ) var testAccProviders map[string]terraform.ResourceProvider @@ -24,20 +22,3 @@ func TestProvider(t *testing.T) { t.Fatalf("err: %s", err) } } - -func TestMapEntity(t *testing.T) { - var a bigip.PolicyRuleAction - m := map[string]interface{}{ - "name": "foo", - "asm": true, - "timeout": 1, - "clonePool": "pool", - } - - mapEntity(m, &a) - - assert.Equal(t, "foo", a.Name) - assert.Equal(t, true, a.Asm) - assert.Equal(t, 1, a.Timeout) - assert.Equal(t, "pool", a.ClonePool) -} diff --git a/bigip/resource_bigip_ltm_irule.go b/bigip/resource_bigip_ltm_irule.go index 8f93202..dcfd71d 100644 --- a/bigip/resource_bigip_ltm_irule.go +++ b/bigip/resource_bigip_ltm_irule.go @@ -75,7 +75,7 @@ func resourceBigipLtmIRuleExists(d *schema.ResourceData, meta interface{}) (bool _, err := client.IRule(name) if err != nil { - return false, nil + return false, err } return true, nil diff --git a/bigip/resource_bigip_ltm_policy.go b/bigip/resource_bigip_ltm_policy.go new file mode 100644 index 0000000..c4d0719 --- /dev/null +++ b/bigip/resource_bigip_ltm_policy.go @@ -0,0 +1,1197 @@ +package bigip + +import ( + "log" + + "fmt" + "github.com/hashicorp/terraform/helper/schema" + "github.com/scottdware/go-bigip" + "reflect" + "strings" +) + +var CONTROLS = schema.NewSet(schema.HashString, []interface{}{"caching", "compression", "classification", "forwarding", "request-adaptation", "response-adpatation", "server-ssl"}) +var REQUIRES = schema.NewSet(schema.HashString, []interface{}{"client-ssl", "ssl-persistence", "tcp", "http"}) + +func resourceBigipLtmPolicy() *schema.Resource { + return &schema.Resource{ + Create: resourceBigipLtmPolicyCreate, + Read: resourceBigipLtmPolicyRead, + Update: resourceBigipLtmPolicyUpdate, + Delete: resourceBigipLtmPolicyDelete, + Exists: resourceBigipLtmPolicyExists, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + Description: "Name of the Policy", + ForceNew: true, + }, + + "partition": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "LTM Partition", + ForceNew: true, + }, + + "controls": &schema.Schema{ + Type: schema.TypeSet, + Set: schema.HashString, + Elem: &schema.Schema{Type: schema.TypeString}, + Required: true, + //ValidateFunc: validateSetValues(CONTROLS), + }, + + "requires": &schema.Schema{ + Type: schema.TypeSet, + Set: schema.HashString, + Elem: &schema.Schema{Type: schema.TypeString}, + Required: true, + //ValidateFunc: validateSetValues(REQUIRES), + }, + + "strategy": &schema.Schema{ + Type: schema.TypeString, + Required: true, + Description: "Policy Strategy (i.e. /Common/first-match)", + }, + + "rule": &schema.Schema{ + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + Description: "Rule name", + }, + "action": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "appService": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "application": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "asm": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "avr": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "cache": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "carp": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "category": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "classify": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "clonePool": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "code": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "compress": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "content": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "cookieHash": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "cookieInsert": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "cookiePassive": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "cookieRewrite": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "decompress": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "defer": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "destinationAddress": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "disable": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "domain": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "enable": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "expiry": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "expirySecs": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "expression": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "extension": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "facility": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "forward": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "fromProfile": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "hash": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "host": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "http": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "httpBasicAuth": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "httpCookie": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "httpHeader": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "httpHost": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "httpReferer": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "httpReply": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "httpSetCookie": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "httpUri": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "ifile": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "insert": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "internalVirtual": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "ipAddress": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "key": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "l7dos": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "length": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "location": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "log": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "ltmPolicy": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "member": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "message": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "tmName": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "netmask": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "nexthop": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "node": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "offset": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "path": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "pem": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "persist": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "pin": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "policy": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "pool": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "port": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "priority": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "profile": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "protocol": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "queryString": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "rateclass": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "redirect": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "remove": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "replace": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "request": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "requestAdapt": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "reset": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "response": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "responseAdapt": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "scheme": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "script": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "select": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "serverSsl": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "setVariable": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "snat": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "snatpool": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "sourceAddress": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "sslClientHello": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "sslServerHandshake": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "sslServerHello": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "sslSessionId": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "status": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "tcl": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "tcpNagle": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "text": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "timeout": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "uie": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "universal": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "value": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "virtual": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "vlan": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "vlanId": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "wam": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "write": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + }, + }, + }, + "condition": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "address": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "all": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "appService": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "browserType": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "browserVersion": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "caseInsensitive": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "caseSensitive": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "cipher": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "cipherBits": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "clientSsl": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "code": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "commonName": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "contains": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "continent": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "countryCode": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "countryName": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "cpuUsage": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "deviceMake": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "deviceModel": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "domain": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "endsWith": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "equals": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "expiry": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "extension": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "external": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "geoip": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "greater": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "greaterOrEqual": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "host": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "httpBasicAuth": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "httpCookie": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "httpHeader": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "httpHost": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "httpMethod": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "httpReferer": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "httpSetCookie": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "httpStatus": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "httpUri": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "httpUserAgent": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "httpVersion": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "index": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "internal": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "isp": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "last_15secs": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "last_1min": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "last_5mins": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "less": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "lessOrEqual": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "local": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "major": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "matches": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "minor": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "missing": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "mss": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "tmName": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "not": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "org": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "password": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "path": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "pathSegment": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "port": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "present": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "protocol": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "queryParameter": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "queryString": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "regionCode": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "regionName": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "remote": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "request": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "response": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "routeDomain": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "rtt": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "scheme": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "serverName": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "sslCert": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "sslClientHello": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "sslExtension": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "sslServerHandshake": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "sslServerHello": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "startsWith": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "tcp": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "text": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "unnamedQueryParameter": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "userAgentToken": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "username": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "value": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "values": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "version": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "vlan": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "vlanId": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func resourceBigipLtmPolicyCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*bigip.BigIP) + name := d.Get("name").(string) + log.Println("[INFO] Creating Policy " + name) + + p := dataToPolicy(name, d) + + d.SetId(name) + err := client.CreatePolicy(&p) + if err != nil { + return err + } + + return resourceBigipLtmPolicyRead(d, meta) +} + +func resourceBigipLtmPolicyRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*bigip.BigIP) + name := d.Id() + + log.Println("[INFO] Fetching policy " + name) + p, err := client.GetPolicy(name) + if err != nil { + return err + } + + return policyToData(p, d) +} + +func resourceBigipLtmPolicyExists(d *schema.ResourceData, meta interface{}) (bool, error) { + client := meta.(*bigip.BigIP) + + name := d.Id() + log.Println("[INFO] Fetching policy " + name) + + _, err := client.GetPolicy(name) + if err != nil { + if strings.HasPrefix(err.Error(), "HTTP 404") { + return false, nil + } + return false, err + } + + return true, nil +} + +func resourceBigipLtmPolicyUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*bigip.BigIP) + name := d.Id() + log.Println("[INFO] Updating Policy " + name) + p := dataToPolicy(name, d) + return client.UpdatePolicy(name, &p) +} + +func resourceBigipLtmPolicyDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*bigip.BigIP) + name := d.Id() + return client.DeletePolicy(name) +} + +func dataToPolicy(name string, d *schema.ResourceData) bigip.Policy { + var p bigip.Policy + p.Name = name + p.Partition = d.Get("partition").(string) + p.Strategy = d.Get("strategy").(string) + p.Controls = setToStringSlice(d.Get("controls").(*schema.Set)) + p.Requires = setToStringSlice(d.Get("requires").(*schema.Set)) + + ruleCount := d.Get("rule.#").(int) + p.Rules = make([]bigip.PolicyRule, 0, ruleCount) + for i := 0; i < ruleCount; i++ { + var r bigip.PolicyRule + prefix := fmt.Sprintf("rule.%d", i) + r.Name = d.Get(prefix + ".name").(string) + + actionCount := d.Get(prefix + ".action.#").(int) + r.Actions = make([]bigip.PolicyRuleAction, actionCount, actionCount) + for x := 0; x < actionCount; x++ { + var a bigip.PolicyRuleAction + mapEntity(d.Get(fmt.Sprintf("%s.action.%d", prefix, x)).(map[string]interface{}), &a) + r.Actions[x] = a + } + + conditionCount := d.Get(prefix + ".condition.#").(int) + r.Conditions = make([]bigip.PolicyRuleCondition, conditionCount, conditionCount) + for x := 0; x < conditionCount; x++ { + var c bigip.PolicyRuleCondition + mapEntity(d.Get(fmt.Sprintf("%s.condition.%d", prefix, x)).(map[string]interface{}), &c) + r.Conditions[x] = c + } + p.Rules = append(p.Rules, r) + } + + return p +} + +func policyToData(p *bigip.Policy, d *schema.ResourceData) error { + d.Set("partition", p.Partition) + d.Set("strategy", p.Strategy) + d.Set("controls", makeStringSet(&p.Controls)) + d.Set("requires", makeStringSet(&p.Requires)) + + rules := make([]map[string]interface{}, 0, len(p.Rules)) + for _, r := range p.Rules { + rule := make(map[string]interface{}) + rule["name"] = r.Name + + actions := make([]map[string]interface{}, 0, len(r.Actions)) + for _, a := range r.Actions { + actions = append(actions, mapify(a)) + } + rule["action"] = actions + + conditions := make([]map[string]interface{}, 0, len(r.Conditions)) + for _, c := range r.Conditions { + conditions = append(conditions, mapify(c)) + } + rule["condition"] = conditions + + rules = append(rules, rule) + } + err := d.Set("rule", rules) + if err != nil { + return err + } + return nil +} + +//Turn an object into a map +func mapify(obj interface{}) map[string]interface{} { + m := make(map[string]interface{}) + v := reflect.ValueOf(obj) + for fi := 0; fi < v.NumField(); fi++ { + fn := v.Type().Field(fi).Name + if fn != "Name" && fn != "Generation" { + f := v.Field(fi) + if (f.Kind() == reflect.Slice && f.Interface() != nil) || f.Interface() != reflect.Zero(f.Type()).Interface() { + m[fmt.Sprintf("%s%s", strings.ToLower(fn[0:1]), fn[1:])] = f.Interface() + } + } + } + return m +} diff --git a/bigip/resource_bigip_ltm_policy_test.go b/bigip/resource_bigip_ltm_policy_test.go new file mode 100644 index 0000000..956bc70 --- /dev/null +++ b/bigip/resource_bigip_ltm_policy_test.go @@ -0,0 +1,67 @@ +package bigip + +import ( + "github.com/hashicorp/terraform/helper/schema" + "github.com/scottdware/go-bigip" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestMapEntity(t *testing.T) { + var c bigip.PolicyRuleCondition + m := map[string]interface{}{ + "name": "foo", + "address": true, + "generation": 1, + "values": []interface{}{"biz", "baz"}, + } + + mapEntity(m, &c) + + assert.Equal(t, "foo", c.Name) + assert.Equal(t, true, c.Address) + assert.Equal(t, 1, c.Generation) + assert.Equal(t, []string{"biz", "baz"}, c.Values) +} + +func TestMapFromEntity(t *testing.T) { + p := bigip.Policy{ + Name: "policy", + Strategy: "/Common/first-match", + Controls: []string{"forwarding"}, + Requires: []string{"http"}, + Rules: []bigip.PolicyRule{ + bigip.PolicyRule{ + Name: "rule", + Actions: []bigip.PolicyRuleAction{ + bigip.PolicyRuleAction{ + HttpUri: true, + Value: "/something", + }, + }, + Conditions: []bigip.PolicyRuleCondition{ + bigip.PolicyRuleCondition{ + Name: "foo", + Generation: 1, + Address: true, + }, + bigip.PolicyRuleCondition{ + Values: []string{"biz", "baz"}, + }, + }, + }, + }, + } + + d := resourceBigipLtmPolicy().TestResourceData() + err := policyToData(&p, d) + + assert.Nil(t, err, err) + assert.Equal(t, "/Common/first-match", d.Get("strategy").(string)) + assert.Equal(t, []string{"forwarding"}, setToStringSlice(d.Get("controls").(*schema.Set))) + assert.Equal(t, []string{"http"}, setToStringSlice(d.Get("requires").(*schema.Set))) + assert.Equal(t, "rule", d.Get("rule.0.name").(string)) + assert.Equal(t, true, d.Get("rule.0.condition.0.address").(bool)) + assert.Equal(t, []interface{}{"biz", "baz"}, d.Get("rule.0.condition.1.values").([]interface{})) + assert.Equal(t, "/something", d.Get("rule.0.action.0.value").(string)) +} diff --git a/bigip/resource_bigip_ltm_pool.go b/bigip/resource_bigip_ltm_pool.go index 23043e6..2738334 100644 --- a/bigip/resource_bigip_ltm_pool.go +++ b/bigip/resource_bigip_ltm_pool.go @@ -198,14 +198,6 @@ func resourceBigipLtmPoolUpdate(d *schema.ResourceData, meta interface{}) error return nil } -func mapify(s []string) map[string]struct{} { - set := make(map[string]struct{}, len(s)) - for _, s := range s { - set[s] = struct{}{} - } - return set -} - func resourceBigipLtmPoolDelete(d *schema.ResourceData, meta interface{}) error { client := meta.(*bigip.BigIP) diff --git a/bigip/validators.go b/bigip/validators.go new file mode 100644 index 0000000..7818339 --- /dev/null +++ b/bigip/validators.go @@ -0,0 +1,28 @@ +package bigip + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/schema" +) + +//Validate the incoming set only contains values from the specified set +func validateSetValues(valid *schema.Set) schema.SchemaValidateFunc { + return func(value interface{}, field string) (ws []string, errors []error) { + if valid.Intersection(value.(*schema.Set)).Len() != value.(*schema.Set).Len() { + errors = append(errors, fmt.Errorf("%q can only contain %v", field, value.(*schema.Set).List())) + } + return + } +} + +func validateStringValue(values []string) schema.SchemaValidateFunc { + return func(value interface{}, field string) (ws []string, errors []error) { + for _, v := range values { + if v == value.(string) { + return + } + } + errors = append(errors, fmt.Errorf("%q must be one of %v", field, values)) + return + } +} diff --git a/bigip/validators_test.go b/bigip/validators_test.go new file mode 100644 index 0000000..15fc0b8 --- /dev/null +++ b/bigip/validators_test.go @@ -0,0 +1,17 @@ +package bigip + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestValidStringValue(t *testing.T) { + _, errors := validateStringValue([]string{"a", "b", "c"})("b", "field") + assert.Equal(t, 0, len(errors)) +} + +func TestInvalidStringValue(t *testing.T) { + _, errors := validateStringValue([]string{"a", "b", "c"})("d", "field") + assert.Equal(t, 1, len(errors)) + assert.Equal(t, "\"field\" must be one of [a b c]", errors[0].Error()) +} From 2e05d8c2cbeb92d5a1cdadfb67c2b7cad2a60dca Mon Sep 17 00:00:00 2001 From: Adam Burnett Date: Wed, 16 Mar 2016 21:17:50 -0400 Subject: [PATCH 3/4] [ADDED] - Documentation for policy resource --- README.md | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/README.md b/README.md index 9a5cb62..b2361f1 100644 --- a/README.md +++ b/README.md @@ -231,3 +231,64 @@ resource "bigip_ltm_virtual_address" "vs_va" { `traffic_group` - (Optional, Default=/Common/traffic-group-1) Specify the partition and traffic group +## bigip_ltm_policy + +Configure [local traffic policies](https://support.f5.com/kb/en-us/solutions/public/15000/000/sol15085.html). +This is a fairly low level resource that does little to make actually using policies any simpler. A solid +understanding of how policies and their associated rules, actions and conditions +are managed through iControlREST is recommended. + +### Example + +``` +resource "bigip_ltm_policy" "policy" { + name = "my_policy" + strategy = "/Common/first-match" + requires = ["http"] + controls = ["forwarding"] + rule { + name = "rule1" + + condition { + httpUri = true + startsWith = true + values = ["/foo"] + } + + condition { + httpMethod = true + values = ["GET"] + } + + action { + forward = true + pool = "/Common/my_pool" + } + } +} +``` + +### Reference + +`name` - (Required) Name of the policy + +`strategy` - (Required) Strategy selection when more than one rule matches. + +`requires` - (Required) Defines the types of conditions that you can use when configuring a rule. + +`controls` - (Required) Defines the types of actions that you can use when configuring a rule. + +`rule` - defines a single rule to add to the policy. Multiple rules can be defined for a single policy. + + #### Rules + + Actions and Conditions support all fields available via the iControlREST API. You can see all of the + available fields in the [iControlREST API documentation](https://devcentral.f5.com/d/icontrol-rest-api-reference-version-120). + Each field in the actions and conditions objects is available. Pro tip: Create your policy via the GUI first then use + the REST API to figure out how to configure the terraform resource. + + `name` (Required) - Name of the rule + + `action` - Defines a single action. Multiple actions can exist per rule. + + `condition` - Defines a single condition. Multiple conditions can exist per rule. From af642bafeb78de1a2e1d1f38fd8ded1849505f43 Mon Sep 17 00:00:00 2001 From: Adam Burnett Date: Wed, 16 Mar 2016 21:19:38 -0400 Subject: [PATCH 4/4] fix formatting --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b2361f1..326d2e6 100644 --- a/README.md +++ b/README.md @@ -280,7 +280,7 @@ resource "bigip_ltm_policy" "policy" { `rule` - defines a single rule to add to the policy. Multiple rules can be defined for a single policy. - #### Rules +**Rules** Actions and Conditions support all fields available via the iControlREST API. You can see all of the available fields in the [iControlREST API documentation](https://devcentral.f5.com/d/icontrol-rest-api-reference-version-120).