From 805f97bddc7b4deed82061845da740380242cad6 Mon Sep 17 00:00:00 2001 From: Thomas Pressnell Date: Fri, 14 Jun 2019 21:04:19 +0100 Subject: [PATCH] Adding some IPv6 support --- bigip/resource_bigip_ltm_node.go | 5 +- bigip/resource_bigip_ltm_node_test.go | 53 +++++++++++++ bigip/resource_bigip_ltm_virtual_server.go | 76 ++++++++++++++---- .../resource_bigip_ltm_virtual_server_test.go | 79 +++++++++++++++++++ bigip/validators.go | 4 +- 5 files changed, 198 insertions(+), 19 deletions(-) diff --git a/bigip/resource_bigip_ltm_node.go b/bigip/resource_bigip_ltm_node.go index c2da9c87b..49008c810 100644 --- a/bigip/resource_bigip_ltm_node.go +++ b/bigip/resource_bigip_ltm_node.go @@ -185,7 +185,8 @@ func resourceBigipLtmNodeRead(d *schema.ResourceData, meta interface{}) error { } } else { // xxx.xxx.xxx.xxx(%x) - regex := regexp.MustCompile(`((?:[0-9]{1,3}\.){3}[0-9]{1,3})(?:\%\d+)?`) + // x:x(%x) + regex := regexp.MustCompile(`((?:(?:[0-9]{1,3}\.){3}[0-9]{1,3})|(?:.*:[^%]*))(?:\%\d+)?`) address := regex.FindStringSubmatch(node.Address) if err := d.Set("address", address[1]); err != nil { return fmt.Errorf("[DEBUG] Error saving address to state for Node (%s): %s", d.Id(), err) @@ -234,7 +235,7 @@ func resourceBigipLtmNodeUpdate(d *schema.ResourceData, meta interface{}) error name := d.Id() address := d.Get("address").(string) - r, _ := regexp.Compile("^((?:[0-9]{1,3}.){3}[0-9]{1,3})|(.*:.*)$") + r, _ := regexp.Compile("^((?:[0-9]{1,3}.){3}[0-9]{1,3})|(.*:[^%]*)$") var node *bigip.Node if r.MatchString(address) { diff --git a/bigip/resource_bigip_ltm_node_test.go b/bigip/resource_bigip_ltm_node_test.go index 757e217b9..1e617f707 100644 --- a/bigip/resource_bigip_ltm_node_test.go +++ b/bigip/resource_bigip_ltm_node_test.go @@ -10,6 +10,7 @@ import ( ) var TEST_NODE_NAME = fmt.Sprintf("/%s/test-node", TEST_PARTITION) +var TEST_V6_NODE_NAME = fmt.Sprintf("/%s/test-v6-node", TEST_PARTITION) var TEST_FQDN_NODE_NAME = fmt.Sprintf("/%s/test-fqdn-node", TEST_PARTITION) var TEST_NODE_RESOURCE = ` @@ -22,6 +23,16 @@ resource "bigip_ltm_node" "test-node" { rate_limit = "disabled" } ` +var TEST_V6_NODE_RESOURCE = ` +resource "bigip_ltm_node" "test-node" { + name = "` + TEST_V6_NODE_NAME + `" + address = "fe80::10" + connection_limit = "0" + dynamic_ratio = "1" + monitor = "default" + rate_limit = "disabled" +} +` var TEST_FQDN_NODE_RESOURCE = ` resource "bigip_ltm_node" "test-fqdn-node" { name = "` + TEST_FQDN_NODE_NAME + `" @@ -58,6 +69,29 @@ func TestAccBigipLtmNode_create(t *testing.T) { }, }) + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAcctPreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: testCheckNodesDestroyed, + Steps: []resource.TestStep{ + { + Config: TEST_V6_NODE_RESOURCE, + Check: resource.ComposeTestCheckFunc( + testCheckNodeExists(TEST_V6_NODE_NAME, true), + resource.TestCheckResourceAttr("bigip_ltm_node.test-node", "name", TEST_V6_NODE_NAME), + resource.TestCheckResourceAttr("bigip_ltm_node.test-node", "address", "fe80::10"), + resource.TestCheckResourceAttr("bigip_ltm_node.test-node", "connection_limit", "0"), + resource.TestCheckResourceAttr("bigip_ltm_node.test-node", "dynamic_ratio", "1"), + resource.TestCheckResourceAttr("bigip_ltm_node.test-node", "monitor", "default"), + resource.TestCheckResourceAttr("bigip_ltm_node.test-node", "rate_limit", "disabled"), + resource.TestCheckResourceAttr("bigip_ltm_node.test-node", "state", "unchecked"), + ), + }, + }, + }) + resource.Test(t, resource.TestCase{ PreCheck: func() { testAcctPreCheck(t) @@ -103,6 +137,25 @@ func TestAccBigipLtmNode_import(t *testing.T) { }, }) + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAcctPreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: testCheckNodesDestroyed, + Steps: []resource.TestStep{ + { + Config: TEST_V6_NODE_RESOURCE, + Check: resource.ComposeTestCheckFunc( + testCheckNodeExists(TEST_V6_NODE_NAME, true), + ), + ResourceName: TEST_V6_NODE_NAME, + ImportState: false, + ImportStateVerify: true, + }, + }, + }) + resource.Test(t, resource.TestCase{ PreCheck: func() { testAcctPreCheck(t) diff --git a/bigip/resource_bigip_ltm_virtual_server.go b/bigip/resource_bigip_ltm_virtual_server.go index c4246b4c1..b805d50df 100755 --- a/bigip/resource_bigip_ltm_virtual_server.go +++ b/bigip/resource_bigip_ltm_virtual_server.go @@ -5,6 +5,7 @@ import ( "log" "regexp" "strconv" + "strings" "github.com/f5devcentral/go-bigip" "github.com/hashicorp/terraform/helper/schema" @@ -38,7 +39,7 @@ func resourceBigipLtmVirtualServer() *schema.Resource { "source": { Type: schema.TypeString, Optional: true, - Default: "0.0.0.0/0", + Computed: true, Description: "Source IP and mask for the virtual server", }, @@ -57,7 +58,7 @@ func resourceBigipLtmVirtualServer() *schema.Resource { "mask": { Type: schema.TypeString, Optional: true, - Default: "255.255.255.255", + Computed: true, Description: "Mask can either be in CIDR notation or decimal, i.e.: \"24\" or \"255.255.255.0\". A CIDR mask of \"0\" is the same as \"0.0.0.0\"", }, @@ -162,9 +163,36 @@ func resourceBigipLtmVirtualServer() *schema.Resource { } } +func resourceBigipLtmVirtualServerAttrDefaults(d *schema.ResourceData) { + _, hasMask := d.GetOk("mask") + _, hasSource := d.GetOk("source") + + // Set default mask if nil + if !hasMask { + // looks like IPv6, lets set to /128 + if strings.Contains(d.Get("destination").(string), ":") { + d.Set("mask", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff") + } else { // /32 for IPv4 + d.Set("mask", "255.255.255.255") + } + } + + // set default source if nil + if !hasSource { + // looks like IPv6, lets set to ::/0 + if strings.Contains(d.Get("destination").(string), ":") { + d.Set("source", "::/0") + } else { // 0.0.0.0/0 + d.Set("source", "0.0.0.0/0") + } + } +} + func resourceBigipLtmVirtualServerCreate(d *schema.ResourceData, meta interface{}) error { client := meta.(*bigip.BigIP) + resourceBigipLtmVirtualServerAttrDefaults(d) + name := d.Get("name").(string) port := d.Get("port").(int) TranslateAddress := d.Get("translate_port").(string) @@ -215,11 +243,19 @@ func resourceBigipLtmVirtualServerRead(d *schema.ResourceData, meta interface{}) return nil } // Extract destination address from "/partition_name/(virtual_server_address)[%route_domain]:port" - regex := regexp.MustCompile(`(\/.+\/)((?:[0-9]{1,3}\.){3}[0-9]{1,3})(?:\%\d+)?(\:\d+)`) + regex := regexp.MustCompile(`(\/.+\/)((?:[0-9]{1,3}\.){3}[0-9]{1,3})(?:\%\d+)?(?:\:(\d+))`) destination := regex.FindStringSubmatch(vs.Destination) - if len(destination) < 3 { - return fmt.Errorf("Unable to extract destination address from virtual server destination: " + vs.Destination) + if destination == nil { + // We should try a IPv6 extraction + + regex = regexp.MustCompile(`^(\/.+\/)(.*:[^%]*)(?:\%\d+)?(?:\.(\d+))$`) + destination = regex.FindStringSubmatch(vs.Destination) + + if destination == nil { + return fmt.Errorf("Unable to extract destination address and port from virtual server destination: " + vs.Destination) + } } + if err := d.Set("destination", destination[2]); err != nil { return fmt.Errorf("[DEBUG] Error saving Destination to state for Virtual Server (%s): %s", d.Id(), err) } @@ -227,6 +263,16 @@ func resourceBigipLtmVirtualServerRead(d *schema.ResourceData, meta interface{}) // Extract source address from "(source_address)[%route_domain](/mask)" groups 1 + 2 regex = regexp.MustCompile(`((?:[0-9]{1,3}\.){3}[0-9]{1,3})(?:\%\d+)?(\/\d+)`) source := regex.FindStringSubmatch(vs.Source) + if source == nil { + // We should try a IPv6 extraction + + regex = regexp.MustCompile(`^(.*:[^%]*)(?:\%\d+)?(\/\d+)$`) + source = regex.FindStringSubmatch(vs.Source) + + if source == nil { + return fmt.Errorf("Unable to extract source address and mask from virtual server destination: " + vs.Source) + } + } parsedSource := source[1] + source[2] if err := d.Set("source", parsedSource); err != nil { return fmt.Errorf("[DEBUG] Error saving Source to state for Virtual Server (%s): %s", d.Id(), err) @@ -241,15 +287,8 @@ func resourceBigipLtmVirtualServerRead(d *schema.ResourceData, meta interface{}) return fmt.Errorf("[DEBUG] Error saving Mask to state for Virtual Server (%s): %s", d.Id(), err) } - /* Service port is provided by the API in the destination attribute "/partition_name/virtual_server_address[%route_domain]:(port)" - so we need to extract it - */ - regex = regexp.MustCompile(`\:(\d+)`) - port := regex.FindStringSubmatch(vs.Destination) - if len(port) < 2 { - return fmt.Errorf("Unable to extract service port from virtual server destination: %s", vs.Destination) - } - parsedPort, _ := strconv.Atoi(port[1]) + // Service port was extracted earlier + parsedPort, _ := strconv.Atoi(destination[3]) d.Set("port", parsedPort) d.Set("irules", makeStringList(&vs.Rules)) @@ -330,6 +369,8 @@ func resourceBigipLtmVirtualServerExists(d *schema.ResourceData, meta interface{ func resourceBigipLtmVirtualServerUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*bigip.BigIP) + resourceBigipLtmVirtualServerAttrDefaults(d) + name := d.Id() var profiles []bigip.Profile @@ -371,8 +412,13 @@ func resourceBigipLtmVirtualServerUpdate(d *schema.ResourceData, meta interface{ rules = listToStringSlice(cfg_rules.([]interface{})) } + destPort := fmt.Sprintf("%s:%d", d.Get("destination").(string), d.Get("port").(int)) + if strings.Contains(d.Get("destination").(string), ":") { + destPort = fmt.Sprintf("%s.%d", d.Get("destination").(string), d.Get("port").(int)) + } + vs := &bigip.VirtualServer{ - Destination: fmt.Sprintf("%s:%d", d.Get("destination").(string), d.Get("port").(int)), + Destination: destPort, FallbackPersistenceProfile: d.Get("fallback_persistence_profile").(string), Source: d.Get("source").(string), Pool: d.Get("pool").(string), diff --git a/bigip/resource_bigip_ltm_virtual_server_test.go b/bigip/resource_bigip_ltm_virtual_server_test.go index ddf58ea9d..c3d464f3f 100644 --- a/bigip/resource_bigip_ltm_virtual_server_test.go +++ b/bigip/resource_bigip_ltm_virtual_server_test.go @@ -32,6 +32,26 @@ resource "bigip_ltm_virtual_server" "test-vs" { } ` +var TEST_VS6_NAME = fmt.Sprintf("/%s/test-vs6", TEST_PARTITION) + +var TEST_VS6_RESOURCE = TEST_IRULE_RESOURCE + ` + + +resource "bigip_ltm_virtual_server" "test-vs" { + name = "` + TEST_VS6_NAME + `" + destination = "fe80::11" + port = 9999 + source_address_translation = "automap" + ip_protocol = "tcp" + irules = ["${bigip_ltm_irule.test-rule.name}"] + profiles = ["/Common/http"] + client_profiles = ["/Common/tcp"] + server_profiles = ["/Common/tcp-lan-optimized"] + persistence_profiles = ["/Common/source_addr"] + fallback_persistence_profile = "/Common/dest_addr" +} +` + func TestAccBigipLtmVS_create(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { @@ -51,6 +71,47 @@ func TestAccBigipLtmVS_create(t *testing.T) { resource.TestCheckResourceAttr("bigip_ltm_virtual_server.test-vs", "destination", "10.255.255.254"), resource.TestCheckResourceAttr("bigip_ltm_virtual_server.test-vs", "port", "9999"), resource.TestCheckResourceAttr("bigip_ltm_virtual_server.test-vs", "mask", "255.255.255.255"), + resource.TestCheckResourceAttr("bigip_ltm_virtual_server.test-vs", "source", "0.0.0.0/0"), + resource.TestCheckResourceAttr("bigip_ltm_virtual_server.test-vs", "source_address_translation", "automap"), + resource.TestCheckResourceAttr("bigip_ltm_virtual_server.test-vs", "ip_protocol", "tcp"), + resource.TestCheckResourceAttr("bigip_ltm_virtual_server.test-vs", "irules.0", TEST_IRULE_NAME), + resource.TestCheckResourceAttr("bigip_ltm_virtual_server.test-vs", + fmt.Sprintf("profiles.%d", schema.HashString("/Common/http")), + "/Common/http"), + resource.TestCheckResourceAttr("bigip_ltm_virtual_server.test-vs", + fmt.Sprintf("client_profiles.%d", schema.HashString("/Common/tcp")), + "/Common/tcp"), + resource.TestCheckResourceAttr("bigip_ltm_virtual_server.test-vs", + fmt.Sprintf("server_profiles.%d", schema.HashString("/Common/tcp-lan-optimized")), + "/Common/tcp-lan-optimized"), + resource.TestCheckResourceAttr("bigip_ltm_virtual_server.test-vs", + fmt.Sprintf("persistence_profiles.%d", schema.HashString("/Common/source_addr")), + "/Common/source_addr"), + resource.TestCheckResourceAttr("bigip_ltm_virtual_server.test-vs", "fallback_persistence_profile", "/Common/dest_addr"), + ), + }, + }, + }) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAcctPreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: resource.ComposeTestCheckFunc( + testCheckVSsDestroyed, + testCheckIRulesDestroyed, + ), + Steps: []resource.TestStep{ + { + Config: TEST_VS6_RESOURCE, + Check: resource.ComposeTestCheckFunc( + testCheckVSExists(TEST_VS6_NAME, true), + resource.TestCheckResourceAttr("bigip_ltm_virtual_server.test-vs", "name", TEST_VS6_NAME), + resource.TestCheckResourceAttr("bigip_ltm_virtual_server.test-vs", "destination", "fe80::11"), + resource.TestCheckResourceAttr("bigip_ltm_virtual_server.test-vs", "port", "9999"), + resource.TestCheckResourceAttr("bigip_ltm_virtual_server.test-vs", "mask", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), + resource.TestCheckResourceAttr("bigip_ltm_virtual_server.test-vs", "source", "::/0"), resource.TestCheckResourceAttr("bigip_ltm_virtual_server.test-vs", "source_address_translation", "automap"), resource.TestCheckResourceAttr("bigip_ltm_virtual_server.test-vs", "ip_protocol", "tcp"), resource.TestCheckResourceAttr("bigip_ltm_virtual_server.test-vs", "irules.0", TEST_IRULE_NAME), @@ -92,6 +153,24 @@ func TestAccBigipLtmVS_import(t *testing.T) { }, }, }) + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAcctPreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: testCheckVSsDestroyed, + Steps: []resource.TestStep{ + { + Config: TEST_VS6_RESOURCE, + Check: resource.ComposeTestCheckFunc( + testCheckVSExists(TEST_VS6_NAME, true), + ), + ResourceName: TEST_VS6_NAME, + ImportState: false, + ImportStateVerify: true, + }, + }, + }) } func testCheckVSExists(name string, exists bool) resource.TestCheckFunc { diff --git a/bigip/validators.go b/bigip/validators.go index 671341ca1..b3c4029df 100644 --- a/bigip/validators.go +++ b/bigip/validators.go @@ -50,9 +50,9 @@ func validateF5Name(value interface{}, field string) (ws []string, errors []erro } for _, v := range values { - match, _ := regexp.MatchString("^/[\\w_\\-.]+/[\\w_\\-.]+$", v) + match, _ := regexp.MatchString("^/[\\w_\\-.]+/[\\w_\\-.:]+$", v) if !match { - errors = append(errors, fmt.Errorf("%q must match /Partition/Name and contain letters, numbers or [._-]. e.g. /Common/my-pool", field)) + errors = append(errors, fmt.Errorf("%q must match /Partition/Name and contain letters, numbers or [._-:]. e.g. /Common/my-pool", field)) } } return