provider/openstack: Add support for FWaaS routerinsertion extension (#12589)

* vendor: Add gophercloud/routerinsertion package and update
gophercloud/firewall to support router insertion

* provider/openstack: Add support for associating
`openstack_fw_firewall_v1` resources with router(s).
Added `associated_routers` and `no_routers` arguments.

* website: Add documentation for `associated_routers`and `no_routers` arguments on `openstack_fw_firewall_v1` resource.

* provider/openstack: Add `AddValueSpecs` function and refactor existing
uses.
This commit is contained in:
Gavin Williams 2017-06-01 06:19:59 +01:00 committed by Joe Topjian
parent f27aa3c826
commit 1da7fda417
10 changed files with 422 additions and 50 deletions

View File

@ -7,6 +7,7 @@ import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/routerinsertion"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)
@ -57,6 +58,18 @@ func resourceFWFirewallV1() *schema.Resource {
ForceNew: true,
Computed: true,
},
"associated_routers": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
ConflictsWith: []string{"no_routers"},
},
"no_routers": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
ConflictsWith: []string{"associated_routers"},
},
"value_specs": &schema.Schema{
Type: schema.TypeMap,
Optional: true,
@ -74,22 +87,49 @@ func resourceFWFirewallV1Create(d *schema.ResourceData, meta interface{}) error
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
}
adminStateUp := d.Get("admin_state_up").(bool)
var createOpts firewalls.CreateOptsBuilder
firewallConfiguration := FirewallCreateOpts{
firewalls.CreateOpts{
Name: d.Get("name").(string),
Description: d.Get("description").(string),
PolicyID: d.Get("policy_id").(string),
AdminStateUp: &adminStateUp,
TenantID: d.Get("tenant_id").(string),
},
adminStateUp := d.Get("admin_state_up").(bool)
createOpts = &firewalls.CreateOpts{
Name: d.Get("name").(string),
Description: d.Get("description").(string),
PolicyID: d.Get("policy_id").(string),
AdminStateUp: &adminStateUp,
TenantID: d.Get("tenant_id").(string),
}
associatedRoutersRaw := d.Get("associated_routers").(*schema.Set).List()
if len(associatedRoutersRaw) > 0 {
log.Printf("[DEBUG] Will attempt to associate Firewall with router(s): %+v", associatedRoutersRaw)
var routerIds []string
for _, v := range associatedRoutersRaw {
routerIds = append(routerIds, v.(string))
}
createOpts = &routerinsertion.CreateOptsExt{
CreateOptsBuilder: createOpts,
RouterIDs: routerIds,
}
}
if d.Get("no_routers").(bool) {
routerIds := make([]string, 0)
log.Println("[DEBUG] No routers specified. Setting to empty slice")
createOpts = &routerinsertion.CreateOptsExt{
CreateOptsBuilder: createOpts,
RouterIDs: routerIds,
}
}
createOpts = &FirewallCreateOpts{
createOpts,
MapValueSpecs(d),
}
log.Printf("[DEBUG] Create firewall: %#v", firewallConfiguration)
log.Printf("[DEBUG] Create firewall: %#v", createOpts)
firewall, err := firewalls.Create(networkingClient, firewallConfiguration).Extract()
firewall, err := firewalls.Create(networkingClient, createOpts).Extract()
if err != nil {
return err
}
@ -106,6 +146,7 @@ func resourceFWFirewallV1Create(d *schema.ResourceData, meta interface{}) error
}
_, err = stateConf.WaitForState()
log.Printf("[DEBUG] Firewall (%s) is active.", firewall.ID)
d.SetId(firewall.ID)
@ -121,7 +162,8 @@ func resourceFWFirewallV1Read(d *schema.ResourceData, meta interface{}) error {
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
}
firewall, err := firewalls.Get(networkingClient, d.Id()).Extract()
var firewall Firewall
err = firewalls.Get(networkingClient, d.Id()).ExtractInto(&firewall)
if err != nil {
return CheckDeleted(d, err, "firewall")
}
@ -134,6 +176,7 @@ func resourceFWFirewallV1Read(d *schema.ResourceData, meta interface{}) error {
d.Set("admin_state_up", firewall.AdminStateUp)
d.Set("tenant_id", firewall.TenantID)
d.Set("region", GetRegion(d))
d.Set("associated_routers", firewall.RouterIDs)
return nil
}
@ -146,7 +189,10 @@ func resourceFWFirewallV1Update(d *schema.ResourceData, meta interface{}) error
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
}
opts := firewalls.UpdateOpts{}
// PolicyID is required
opts := firewalls.UpdateOpts{
PolicyID: d.Get("policy_id").(string),
}
if d.HasChange("name") {
opts.Name = d.Get("name").(string)
@ -156,16 +202,39 @@ func resourceFWFirewallV1Update(d *schema.ResourceData, meta interface{}) error
opts.Description = d.Get("description").(string)
}
if d.HasChange("policy_id") {
opts.PolicyID = d.Get("policy_id").(string)
}
if d.HasChange("admin_state_up") {
adminStateUp := d.Get("admin_state_up").(bool)
opts.AdminStateUp = &adminStateUp
}
log.Printf("[DEBUG] Updating firewall with id %s: %#v", d.Id(), opts)
var updateOpts firewalls.UpdateOptsBuilder
var routerIds []string
if d.HasChange("associated_routers") || d.HasChange("no_routers") {
// 'no_routers' = true means 'associated_routers' will be empty...
if d.Get("no_routers").(bool) {
log.Printf("[DEBUG] 'no_routers' is true.")
routerIds = make([]string, 0)
} else {
associatedRoutersRaw := d.Get("associated_routers").(*schema.Set).List()
for _, v := range associatedRoutersRaw {
routerIds = append(routerIds, v.(string))
}
}
updateOpts = routerinsertion.UpdateOptsExt{
UpdateOptsBuilder: opts,
RouterIDs: routerIds,
}
} else {
updateOpts = opts
}
log.Printf("[DEBUG] Updating firewall with id %s: %#v", d.Id(), updateOpts)
err = firewalls.Update(networkingClient, d.Id(), updateOpts).Err
if err != nil {
return err
}
stateConf := &resource.StateChangeConf{
Pending: []string{"PENDING_CREATE", "PENDING_UPDATE"},
@ -178,11 +247,6 @@ func resourceFWFirewallV1Update(d *schema.ResourceData, meta interface{}) error
_, err = stateConf.WaitForState()
err = firewalls.Update(networkingClient, d.Id(), opts).Err
if err != nil {
return err
}
return resourceFWFirewallV1Read(d, meta)
}
@ -230,9 +294,9 @@ func resourceFWFirewallV1Delete(d *schema.ResourceData, meta interface{}) error
func waitForFirewallActive(networkingClient *gophercloud.ServiceClient, id string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
fw, err := firewalls.Get(networkingClient, id).Extract()
log.Printf("[DEBUG] Get firewall %s => %#v", id, fw)
var fw Firewall
err := firewalls.Get(networkingClient, id).ExtractInto(&fw)
if err != nil {
return nil, "", err
}
@ -244,7 +308,7 @@ func waitForFirewallDeletion(networkingClient *gophercloud.ServiceClient, id str
return func() (interface{}, string, error) {
fw, err := firewalls.Get(networkingClient, id).Extract()
log.Printf("[DEBUG] Get firewall %s => %#v", id, fw)
log.Printf("[DEBUG] Got firewall %s => %#v", id, fw)
if err != nil {
if _, ok := err.(gophercloud.ErrDefault404); ok {

View File

@ -22,13 +22,13 @@ func TestAccFWFirewallV1_basic(t *testing.T) {
resource.TestStep{
Config: testAccFWFirewallV1_basic_1,
Check: resource.ComposeTestCheckFunc(
testAccCheckFWFirewallV1Exists("openstack_fw_firewall_v1.fw_1", "", "", policyID),
testAccCheckFWFirewallV1("openstack_fw_firewall_v1.fw_1", "", "", policyID),
),
},
resource.TestStep{
Config: testAccFWFirewallV1_basic_2,
Check: resource.ComposeTestCheckFunc(
testAccCheckFWFirewallV1Exists(
testAccCheckFWFirewallV1(
"openstack_fw_firewall_v1.fw_1", "fw_1", "terraform acceptance test", policyID),
),
},
@ -47,7 +47,98 @@ func TestAccFWFirewallV1_timeout(t *testing.T) {
resource.TestStep{
Config: testAccFWFirewallV1_timeout,
Check: resource.ComposeTestCheckFunc(
testAccCheckFWFirewallV1Exists("openstack_fw_firewall_v1.fw_1", "", "", policyID),
testAccCheckFWFirewallV1("openstack_fw_firewall_v1.fw_1", "", "", policyID),
),
},
},
})
}
func TestAccFWFirewallV1_router(t *testing.T) {
var firewall Firewall
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckFWFirewallV1Destroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccFWFirewallV1_router,
Check: resource.ComposeTestCheckFunc(
testAccCheckFWFirewallV1Exists("openstack_fw_firewall_v1.fw_1", &firewall),
testAccCheckFWFirewallRouterCount(&firewall, 1),
),
},
},
})
}
func TestAccFWFirewallV1_no_router(t *testing.T) {
var firewall Firewall
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckFWFirewallV1Destroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccFWFirewallV1_no_router,
Check: resource.ComposeTestCheckFunc(
testAccCheckFWFirewallV1Exists("openstack_fw_firewall_v1.fw_1", &firewall),
resource.TestCheckResourceAttr("openstack_fw_firewall_v1.fw_1", "description", "firewall router test"),
testAccCheckFWFirewallRouterCount(&firewall, 0),
),
},
},
})
}
func TestAccFWFirewallV1_router_update(t *testing.T) {
var firewall Firewall
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckFWFirewallV1Destroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccFWFirewallV1_router,
Check: resource.ComposeTestCheckFunc(
testAccCheckFWFirewallV1Exists("openstack_fw_firewall_v1.fw_1", &firewall),
testAccCheckFWFirewallRouterCount(&firewall, 1),
),
},
resource.TestStep{
Config: testAccFWFirewallV1_router_add,
Check: resource.ComposeTestCheckFunc(
testAccCheckFWFirewallV1Exists("openstack_fw_firewall_v1.fw_1", &firewall),
testAccCheckFWFirewallRouterCount(&firewall, 2),
),
},
},
})
}
func TestAccFWFirewallV1_router_remove(t *testing.T) {
var firewall Firewall
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckFWFirewallV1Destroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccFWFirewallV1_router,
Check: resource.ComposeTestCheckFunc(
testAccCheckFWFirewallV1Exists("openstack_fw_firewall_v1.fw_1", &firewall),
testAccCheckFWFirewallRouterCount(&firewall, 1),
),
},
resource.TestStep{
Config: testAccFWFirewallV1_router_remove,
Check: resource.ComposeTestCheckFunc(
testAccCheckFWFirewallV1Exists("openstack_fw_firewall_v1.fw_1", &firewall),
testAccCheckFWFirewallRouterCount(&firewall, 0),
),
},
},
@ -76,7 +167,50 @@ func testAccCheckFWFirewallV1Destroy(s *terraform.State) error {
return nil
}
func testAccCheckFWFirewallV1Exists(n, expectedName, expectedDescription string, policyID *string) resource.TestCheckFunc {
func testAccCheckFWFirewallV1Exists(n string, firewall *Firewall) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set")
}
config := testAccProvider.Meta().(*Config)
networkingClient, err := config.networkingV2Client(OS_REGION_NAME)
if err != nil {
return fmt.Errorf("Exists) Error creating OpenStack networking client: %s", err)
}
var found Firewall
err = firewalls.Get(networkingClient, rs.Primary.ID).ExtractInto(&found)
if err != nil {
return err
}
if found.ID != rs.Primary.ID {
return fmt.Errorf("Firewall not found")
}
*firewall = found
return nil
}
}
func testAccCheckFWFirewallRouterCount(firewall *Firewall, expected int) resource.TestCheckFunc {
return func(s *terraform.State) error {
if len(firewall.RouterIDs) != expected {
return fmt.Errorf("Expected %d Routers, got %d", expected, len(firewall.RouterIDs))
}
return nil
}
}
func testAccCheckFWFirewallV1(n, expectedName, expectedDescription string, policyID *string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
@ -169,3 +303,76 @@ resource "openstack_fw_policy_v1" "policy_1" {
name = "policy_1"
}
`
const testAccFWFirewallV1_router = `
resource "openstack_networking_router_v2" "router_1" {
name = "router_1"
admin_state_up = "true"
distributed = "false"
}
resource "openstack_fw_policy_v1" "policy_1" {
name = "policy_1"
}
resource "openstack_fw_firewall_v1" "fw_1" {
name = "firewall_1"
description = "firewall router test"
policy_id = "${openstack_fw_policy_v1.policy_1.id}"
associated_routers = ["${openstack_networking_router_v2.router_1.id}"]
}
`
const testAccFWFirewallV1_router_add = `
resource "openstack_networking_router_v2" "router_1" {
name = "router_1"
admin_state_up = "true"
distributed = "false"
}
resource "openstack_networking_router_v2" "router_2" {
name = "router_2"
admin_state_up = "true"
distributed = "false"
}
resource "openstack_fw_policy_v1" "policy_1" {
name = "policy_1"
}
resource "openstack_fw_firewall_v1" "fw_1" {
name = "firewall_1"
description = "firewall router test"
policy_id = "${openstack_fw_policy_v1.policy_1.id}"
associated_routers = [
"${openstack_networking_router_v2.router_1.id}",
"${openstack_networking_router_v2.router_2.id}"
]
}
`
const testAccFWFirewallV1_router_remove = `
resource "openstack_fw_policy_v1" "policy_1" {
name = "policy_1"
}
resource "openstack_fw_firewall_v1" "fw_1" {
name = "firewall_1"
description = "firewall router test"
policy_id = "${openstack_fw_policy_v1.policy_1.id}"
no_routers = true
}
`
const testAccFWFirewallV1_no_router = `
resource "openstack_fw_policy_v1" "policy_1" {
name = "policy_1"
}
resource "openstack_fw_firewall_v1" "fw_1" {
name = "firewall_1"
description = "firewall router test"
policy_id = "${openstack_fw_policy_v1.policy_1.id}"
no_routers = true
}
`

View File

@ -17,6 +17,7 @@ import (
"github.com/gophercloud/gophercloud/openstack/dns/v2/zones"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/policies"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/routerinsertion"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/rules"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers"
@ -163,15 +164,35 @@ func (lrt *LogRoundTripper) formatJSON(raw []byte) string {
return string(pretty)
}
// Firewall is an OpenStack firewall.
type Firewall struct {
firewalls.Firewall
routerinsertion.FirewallExt
}
// FirewallCreateOpts represents the attributes used when creating a new firewall.
type FirewallCreateOpts struct {
firewalls.CreateOpts
firewalls.CreateOptsBuilder
ValueSpecs map[string]string `json:"value_specs,omitempty"`
}
// ToFirewallCreateMap casts a CreateOpts struct to a map.
// ToFirewallCreateMap casts a CreateOptsExt struct to a map.
// It overrides firewalls.ToFirewallCreateMap to add the ValueSpecs field.
func (opts FirewallCreateOpts) ToFirewallCreateMap() (map[string]interface{}, error) {
body, err := opts.CreateOptsBuilder.ToFirewallCreateMap()
if err != nil {
return nil, err
}
return AddValueSpecs(body), nil
}
//FirewallUpdateOpts
type FirewallUpdateOpts struct {
firewalls.UpdateOptsBuilder
}
func (opts FirewallUpdateOpts) ToFirewallUpdateMap() (map[string]interface{}, error) {
return BuildRequest(opts, "firewall")
}

View File

@ -18,12 +18,7 @@ func BuildRequest(opts interface{}, parent string) (map[string]interface{}, erro
return nil, err
}
if b["value_specs"] != nil {
for k, v := range b["value_specs"].(map[string]interface{}) {
b[k] = v
}
delete(b, "value_specs")
}
b = AddValueSpecs(b)
return map[string]interface{}{parent: b}, nil
}
@ -52,6 +47,19 @@ func GetRegion(d *schema.ResourceData) string {
return ""
}
// AddValueSpecs expands the 'value_specs' object and removes 'value_specs'
// from the reqeust body.
func AddValueSpecs(body map[string]interface{}) map[string]interface{} {
if body["value_specs"] != nil {
for k, v := range body["value_specs"].(map[string]interface{}) {
body[k] = v
}
delete(body, "value_specs")
}
return body
}
// MapValueSpecs converts ResourceData into a map
func MapValueSpecs(d *schema.ResourceData) map[string]string {
m := make(map[string]string)

View File

@ -22,11 +22,17 @@ type commonResult struct {
// Extract is a function that accepts a result and extracts a firewall.
func (r commonResult) Extract() (*Firewall, error) {
var s struct {
Firewall *Firewall `json:"firewall"`
}
var s Firewall
err := r.ExtractInto(&s)
return s.Firewall, err
return &s, err
}
func (r commonResult) ExtractInto(v interface{}) error {
return r.Result.ExtractIntoStructPtr(v, "firewall")
}
func ExtractFirewallsInto(r pagination.Page, v interface{}) error {
return r.(FirewallPage).Result.ExtractIntoSlicePtr(v, "firewalls")
}
// FirewallPage is the page returned by a pager when traversing over a
@ -59,11 +65,9 @@ func (r FirewallPage) IsEmpty() (bool, error) {
// and extracts the elements into a slice of Router structs. In other words,
// a generic collection is mapped into a relevant slice.
func ExtractFirewalls(r pagination.Page) ([]Firewall, error) {
var s struct {
Firewalls []Firewall `json:"firewalls" json:"firewalls"`
}
err := (r.(FirewallPage)).ExtractInto(&s)
return s.Firewalls, err
var s []Firewall
err := ExtractFirewallsInto(r, &s)
return s, err
}
// GetResult represents the result of a get operation.

View File

@ -0,0 +1,2 @@
// Package routerinsertion implements the fwaasrouterinsertion FWaaS extension.
package routerinsertion

View File

@ -0,0 +1,43 @@
package routerinsertion
import (
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls"
)
// CreateOptsExt adds a RouterIDs option to the base CreateOpts.
type CreateOptsExt struct {
firewalls.CreateOptsBuilder
RouterIDs []string `json:"router_ids"`
}
// ToFirewallCreateMap adds router_ids to the base firewall creation options.
func (opts CreateOptsExt) ToFirewallCreateMap() (map[string]interface{}, error) {
base, err := opts.CreateOptsBuilder.ToFirewallCreateMap()
if err != nil {
return nil, err
}
firewallMap := base["firewall"].(map[string]interface{})
firewallMap["router_ids"] = opts.RouterIDs
return base, nil
}
// UpdateOptsExt updates a RouterIDs option to the base UpdateOpts.
type UpdateOptsExt struct {
firewalls.UpdateOptsBuilder
RouterIDs []string `json:"router_ids"`
}
// ToFirewallUpdateMap adds router_ids to the base firewall update options.
func (opts UpdateOptsExt) ToFirewallUpdateMap() (map[string]interface{}, error) {
base, err := opts.UpdateOptsBuilder.ToFirewallUpdateMap()
if err != nil {
return nil, err
}
firewallMap := base["firewall"].(map[string]interface{})
firewallMap["router_ids"] = opts.RouterIDs
return base, nil
}

View File

@ -0,0 +1,7 @@
package routerinsertion
// FirewallExt is an extension to the base Firewall object
type FirewallExt struct {
// RouterIDs are the routers that the firewall is attached to.
RouterIDs []string `json:"router_ids"`
}

12
vendor/vendor.json vendored
View File

@ -1799,10 +1799,10 @@
"revisionTime": "2017-03-10T01:59:53Z"
},
{
"checksumSHA1": "aTHxjMlfNXFJ3l2TZyvIwqt/3kM=",
"checksumSHA1": "YqqqrMdOCKY2xwFkwxskkx8XtTQ=",
"path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls",
"revision": "0f64da0e36de86a0ca1a8f2fc1b0570a0d3f7504",
"revisionTime": "2017-03-10T01:59:53Z"
"revision": "3027adb1ce72bc52b87b2decccc7852574b90031",
"revisionTime": "2017-05-24T13:09:59Z"
},
{
"checksumSHA1": "14ZhP0wE/WCL/6oujcML755AaH4=",
@ -1810,6 +1810,12 @@
"revision": "0f64da0e36de86a0ca1a8f2fc1b0570a0d3f7504",
"revisionTime": "2017-03-10T01:59:53Z"
},
{
"checksumSHA1": "nHGhVRP69paJsebTRp4Q1cfQtag=",
"path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/routerinsertion",
"revision": "3027adb1ce72bc52b87b2decccc7852574b90031",
"revisionTime": "2017-05-24T13:09:59Z"
},
{
"checksumSHA1": "sYET5A7WTyJ7dpuxR/VXYoReldw=",
"path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/rules",

View File

@ -71,6 +71,14 @@ The following arguments are supported:
to create a firewall for another tenant. Changing this creates a new
firewall.
* `associated_routers` - (Optional) Router(s) to associate this firewall instance
with. Must be a list of strings. Changing this updates the associated routers
of an existing firewall. Conflicts with `no_routers`.
* `no_routers` - (Optional) Should this firewall not be associated with any routers
(must be "true" or "false" if provide - defaults to "false").
Conflicts with `associated_routers`.
* `value_specs` - (Optional) Map of additional options.
## Attributes Reference
@ -83,6 +91,8 @@ The following attributes are exported:
* `description` - See Argument Reference above.
* `admin_state_up` - See Argument Reference above.
* `tenant_id` - See Argument Reference above.
* `associated_routers` - See Argument Reference above.
* `no_routers` - See Argument Reference above.
## Import