Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding some IPv6 support #118

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions bigip/resource_bigip_ltm_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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) {
Expand Down
53 changes: 53 additions & 0 deletions bigip/resource_bigip_ltm_node_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 = `
Expand All @@ -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 + `"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
76 changes: 61 additions & 15 deletions bigip/resource_bigip_ltm_virtual_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"log"
"regexp"
"strconv"
"strings"

"github.com/f5devcentral/go-bigip"
"github.com/hashicorp/terraform/helper/schema"
Expand Down Expand Up @@ -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",
},

Expand All @@ -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\"",
},

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -215,18 +243,36 @@ 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)
}

// 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)
Expand All @@ -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))
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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),
Expand Down
79 changes: 79 additions & 0 deletions bigip/resource_bigip_ltm_virtual_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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),
Expand Down Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions bigip/validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down