diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 1a61d3e19a..3900144961 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -1001,183 +1001,193 @@ }, { "ImportPath": "github.com/rackspace/gophercloud", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/rackspace/gophercloud/openstack", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/bootfromvolume", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/schedulerhints", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/servergroups", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/tenantnetworks", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/flavors", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/images", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/servers", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/rackspace/gophercloud/openstack/identity/v2/tenants", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/rackspace/gophercloud/openstack/identity/v2/tokens", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/rackspace/gophercloud/openstack/identity/v3/tokens", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/policies", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/routers", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/members", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/monitors", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/pools", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/vips", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" + }, + { + "ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups", + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" + }, + { + "ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules", + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/networks", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/ports", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/subnets", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/rackspace/gophercloud/openstack/objectstorage/v1/accounts", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/rackspace/gophercloud/openstack/utils", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/rackspace/gophercloud/pagination", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/rackspace/gophercloud/testhelper", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/rackspace/gophercloud/testhelper/client", - "Comment": "v1.0.0-868-ga09b5b4", - "Rev": "a09b5b4eb58195b6fb3898496586b8d6aeb558e0" + "Comment": "v1.0.0-884-gc54bbac", + "Rev": "c54bbac81d19eb4df3ad167764dbb6ff2e7194de" }, { "ImportPath": "github.com/satori/go.uuid", diff --git a/builtin/providers/openstack/provider.go b/builtin/providers/openstack/provider.go index 7454a55c36..8b72ba22a6 100644 --- a/builtin/providers/openstack/provider.go +++ b/builtin/providers/openstack/provider.go @@ -101,6 +101,8 @@ func Provider() terraform.ResourceProvider { "openstack_networking_router_v2": resourceNetworkingRouterV2(), "openstack_networking_router_interface_v2": resourceNetworkingRouterInterfaceV2(), "openstack_networking_router_route_v2": resourceNetworkingRouterRouteV2(), + "openstack_networking_secgroup_v2": resourceNetworkingSecGroupV2(), + "openstack_networking_secgroup_rule_v2": resourceNetworkingSecGroupRuleV2(), "openstack_objectstorage_container_v1": resourceObjectStorageContainerV1(), }, diff --git a/builtin/providers/openstack/resource_openstack_networking_secgroup_rule_v2.go b/builtin/providers/openstack/resource_openstack_networking_secgroup_rule_v2.go new file mode 100644 index 0000000000..598813a389 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_networking_secgroup_rule_v2.go @@ -0,0 +1,209 @@ +package openstack + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules" +) + +func resourceNetworkingSecGroupRuleV2() *schema.Resource { + return &schema.Resource{ + Create: resourceNetworkingSecGroupRuleV2Create, + Read: resourceNetworkingSecGroupRuleV2Read, + Delete: resourceNetworkingSecGroupRuleV2Delete, + + Schema: map[string]*schema.Schema{ + "region": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""), + }, + "direction": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "ethertype": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "port_range_min": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + Computed: true, + }, + "port_range_max": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + Computed: true, + }, + "protocol": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + "remote_group_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + "remote_ip_prefix": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + "security_group_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "tenant_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + }, + } +} + +func resourceNetworkingSecGroupRuleV2Create(d *schema.ResourceData, meta interface{}) error { + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + portRangeMin := d.Get("port_range_min").(int) + portRangeMax := d.Get("port_range_max").(int) + protocol := d.Get("protocol").(string) + + if protocol == "" { + if portRangeMin != 0 || portRangeMax != 0 { + return fmt.Errorf("A protocol must be specified when using port_range_min and port_range_max") + } + } + + opts := rules.CreateOpts{ + Direction: d.Get("direction").(string), + EtherType: d.Get("ethertype").(string), + SecGroupID: d.Get("security_group_id").(string), + PortRangeMin: d.Get("port_range_min").(int), + PortRangeMax: d.Get("port_range_max").(int), + Protocol: d.Get("protocol").(string), + RemoteGroupID: d.Get("remote_group_id").(string), + RemoteIPPrefix: d.Get("remote_ip_prefix").(string), + TenantID: d.Get("tenant_id").(string), + } + + log.Printf("[DEBUG] Create OpenStack Neutron security group: %#v", opts) + + security_group_rule, err := rules.Create(networkingClient, opts).Extract() + if err != nil { + return err + } + + log.Printf("[DEBUG] OpenStack Neutron Security Group Rule created: %#v", security_group_rule) + + d.SetId(security_group_rule.ID) + + return resourceNetworkingSecGroupRuleV2Read(d, meta) +} + +func resourceNetworkingSecGroupRuleV2Read(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Retrieve information about security group rule: %s", d.Id()) + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + security_group_rule, err := rules.Get(networkingClient, d.Id()).Extract() + + if err != nil { + return CheckDeleted(d, err, "OpenStack Security Group Rule") + } + + d.Set("protocol", security_group_rule.Protocol) + d.Set("port_range_min", security_group_rule.PortRangeMin) + d.Set("port_range_max", security_group_rule.PortRangeMax) + d.Set("remote_group_id", security_group_rule.RemoteGroupID) + d.Set("remote_ip_prefix", security_group_rule.RemoteIPPrefix) + d.Set("tenant_id", security_group_rule.TenantID) + return nil +} + +func resourceNetworkingSecGroupRuleV2Delete(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Destroy security group rule: %s", d.Id()) + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"ACTIVE"}, + Target: []string{"DELETED"}, + Refresh: waitForSecGroupRuleDelete(networkingClient, d.Id()), + Timeout: 2 * time.Minute, + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error deleting OpenStack Neutron Security Group Rule: %s", err) + } + + d.SetId("") + return err +} + +func waitForSecGroupRuleDelete(networkingClient *gophercloud.ServiceClient, secGroupRuleId string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + log.Printf("[DEBUG] Attempting to delete OpenStack Security Group Rule %s.\n", secGroupRuleId) + + r, err := rules.Get(networkingClient, secGroupRuleId).Extract() + if err != nil { + errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) + if !ok { + return r, "ACTIVE", err + } + if errCode.Actual == 404 { + log.Printf("[DEBUG] Successfully deleted OpenStack Neutron Security Group Rule %s", secGroupRuleId) + return r, "DELETED", nil + } + } + + err = rules.Delete(networkingClient, secGroupRuleId).ExtractErr() + if err != nil { + errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) + if !ok { + return r, "ACTIVE", err + } + if errCode.Actual == 404 { + log.Printf("[DEBUG] Successfully deleted OpenStack Neutron Security Group Rule %s", secGroupRuleId) + return r, "DELETED", nil + } + } + + log.Printf("[DEBUG] OpenStack Neutron Security Group Rule %s still active.\n", secGroupRuleId) + return r, "ACTIVE", nil + } +} diff --git a/builtin/providers/openstack/resource_openstack_networking_secgroup_rule_v2_test.go b/builtin/providers/openstack/resource_openstack_networking_secgroup_rule_v2_test.go new file mode 100644 index 0000000000..5ea0cc3cd4 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_networking_secgroup_rule_v2_test.go @@ -0,0 +1,117 @@ +package openstack + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules" +) + +func TestAccNetworkingV2SecGroupRule_basic(t *testing.T) { + var security_group_1 groups.SecGroup + var security_group_2 groups.SecGroup + var security_group_rule_1 rules.SecGroupRule + var security_group_rule_2 rules.SecGroupRule + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckNetworkingV2SecGroupRuleDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccNetworkingV2SecGroupRule_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckNetworkingV2SecGroupExists(t, "openstack_networking_secgroup_v2.sg_foo", &security_group_1), + testAccCheckNetworkingV2SecGroupExists(t, "openstack_networking_secgroup_v2.sg_bar", &security_group_2), + testAccCheckNetworkingV2SecGroupRuleExists(t, "openstack_networking_secgroup_rule_v2.sr_foo", &security_group_rule_1), + testAccCheckNetworkingV2SecGroupRuleExists(t, "openstack_networking_secgroup_rule_v2.sr_bar", &security_group_rule_2), + ), + }, + }, + }) +} + +func testAccCheckNetworkingV2SecGroupRuleDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + networkingClient, err := config.networkingV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("(testAccCheckNetworkingV2SecGroupRuleDestroy) Error creating OpenStack networking client: %s", err) + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "openstack_networking_secgroup_rule_v2" { + continue + } + + _, err := rules.Get(networkingClient, rs.Primary.ID).Extract() + if err == nil { + return fmt.Errorf("Security group rule still exists") + } + } + + return nil +} + +func testAccCheckNetworkingV2SecGroupRuleExists(t *testing.T, n string, security_group_rule *rules.SecGroupRule) 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("(testAccCheckNetworkingV2SecGroupRuleExists) Error creating OpenStack networking client: %s", err) + } + + found, err := rules.Get(networkingClient, rs.Primary.ID).Extract() + if err != nil { + return err + } + + if found.ID != rs.Primary.ID { + return fmt.Errorf("Security group rule not found") + } + + *security_group_rule = *found + + return nil + } +} + +var testAccNetworkingV2SecGroupRule_basic = fmt.Sprintf(` + resource "openstack_networking_secgroup_v2" "sg_foo" { + name = "security_group_1" + description = "terraform security group rule acceptance test" + } + resource "openstack_networking_secgroup_v2" "sg_bar" { + name = "security_group_2" + description = "terraform security group rule acceptance test" + } + resource "openstack_networking_secgroup_rule_v2" "sr_foo" { + direction = "ingress" + ethertype = "IPv4" + port_range_max = 22 + port_range_min = 22 + protocol = "tcp" + remote_ip_prefix = "0.0.0.0/0" + security_group_id = "${openstack_networking_secgroup_v2.sg_foo.id}" + } + resource "openstack_networking_secgroup_rule_v2" "sr_bar" { + direction = "ingress" + ethertype = "IPv4" + port_range_max = 80 + port_range_min = 80 + protocol = "tcp" + remote_group_id = "${openstack_networking_secgroup_v2.sg_foo.id}" + security_group_id = "${openstack_networking_secgroup_v2.sg_bar.id}" + }`) diff --git a/builtin/providers/openstack/resource_openstack_networking_secgroup_v2.go b/builtin/providers/openstack/resource_openstack_networking_secgroup_v2.go new file mode 100644 index 0000000000..f08e9affc4 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_networking_secgroup_v2.go @@ -0,0 +1,155 @@ +package openstack + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups" +) + +func resourceNetworkingSecGroupV2() *schema.Resource { + return &schema.Resource{ + Create: resourceNetworkingSecGroupV2Create, + Read: resourceNetworkingSecGroupV2Read, + Delete: resourceNetworkingSecGroupV2Delete, + + Schema: map[string]*schema.Schema{ + "region": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""), + }, + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + "tenant_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + }, + } +} + +func resourceNetworkingSecGroupV2Create(d *schema.ResourceData, meta interface{}) error { + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + opts := groups.CreateOpts{ + Name: d.Get("name").(string), + Description: d.Get("description").(string), + TenantID: d.Get("tenant_id").(string), + } + + log.Printf("[DEBUG] Create OpenStack Neutron Security Group: %#v", opts) + + security_group, err := groups.Create(networkingClient, opts).Extract() + if err != nil { + return err + } + + log.Printf("[DEBUG] OpenStack Neutron Security Group created: %#v", security_group) + + d.SetId(security_group.ID) + + return resourceNetworkingSecGroupV2Read(d, meta) +} + +func resourceNetworkingSecGroupV2Read(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Retrieve information about security group: %s", d.Id()) + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + security_group, err := groups.Get(networkingClient, d.Id()).Extract() + + if err != nil { + return CheckDeleted(d, err, "OpenStack Neutron Security group") + } + + d.Set("description", security_group.Description) + d.Set("tenant_id", security_group.TenantID) + return nil +} + +func resourceNetworkingSecGroupV2Delete(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Destroy security group: %s", d.Id()) + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"ACTIVE"}, + Target: []string{"DELETED"}, + Refresh: waitForSecGroupDelete(networkingClient, d.Id()), + Timeout: 2 * time.Minute, + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error deleting OpenStack Neutron Security Group: %s", err) + } + + d.SetId("") + return err +} + +func waitForSecGroupDelete(networkingClient *gophercloud.ServiceClient, secGroupId string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + log.Printf("[DEBUG] Attempting to delete OpenStack Security Group %s.\n", secGroupId) + + r, err := groups.Get(networkingClient, secGroupId).Extract() + if err != nil { + errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) + if !ok { + return r, "ACTIVE", err + } + if errCode.Actual == 404 { + log.Printf("[DEBUG] Successfully deleted OpenStack Neutron Security Group %s", secGroupId) + return r, "DELETED", nil + } + } + + err = groups.Delete(networkingClient, secGroupId).ExtractErr() + if err != nil { + errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) + if !ok { + return r, "ACTIVE", err + } + if errCode.Actual == 404 { + log.Printf("[DEBUG] Successfully deleted OpenStack Neutron Security Group %s", secGroupId) + return r, "DELETED", nil + } + } + + log.Printf("[DEBUG] OpenStack Neutron Security Group %s still active.\n", secGroupId) + return r, "ACTIVE", nil + } +} diff --git a/builtin/providers/openstack/resource_openstack_networking_secgroup_v2_test.go b/builtin/providers/openstack/resource_openstack_networking_secgroup_v2_test.go new file mode 100644 index 0000000000..198d74a5c2 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_networking_secgroup_v2_test.go @@ -0,0 +1,100 @@ +package openstack + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups" +) + +func TestAccNetworkingV2SecGroup_basic(t *testing.T) { + var security_group groups.SecGroup + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckNetworkingV2SecGroupDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccNetworkingV2SecGroup_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckNetworkingV2SecGroupExists(t, "openstack_networking_secgroup_v2.foo", &security_group), + ), + }, + resource.TestStep{ + Config: testAccNetworkingV2SecGroup_update, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("openstack_networking_secgroup_v2.foo", "name", "security_group_2"), + ), + }, + }, + }) +} + +func testAccCheckNetworkingV2SecGroupDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + networkingClient, err := config.networkingV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("(testAccCheckNetworkingV2SecGroupDestroy) Error creating OpenStack networking client: %s", err) + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "openstack_networking_secgroup_v2" { + continue + } + + _, err := groups.Get(networkingClient, rs.Primary.ID).Extract() + if err == nil { + return fmt.Errorf("Security group still exists") + } + } + + return nil +} + +func testAccCheckNetworkingV2SecGroupExists(t *testing.T, n string, security_group *groups.SecGroup) 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("(testAccCheckNetworkingV2SecGroupExists) Error creating OpenStack networking client: %s", err) + } + + found, err := groups.Get(networkingClient, rs.Primary.ID).Extract() + if err != nil { + return err + } + + if found.ID != rs.Primary.ID { + return fmt.Errorf("Security group not found") + } + + *security_group = *found + + return nil + } +} + +var testAccNetworkingV2SecGroup_basic = fmt.Sprintf(` + resource "openstack_networking_secgroup_v2" "foo" { + name = "security_group" + description = "terraform security group acceptance test" + }`) + +var testAccNetworkingV2SecGroup_update = fmt.Sprintf(` + resource "openstack_networking_secgroup_v2" "foo" { + name = "security_group_2" + description = "terraform security group acceptance test" + }`) diff --git a/vendor/github.com/rackspace/gophercloud/auth_options.go b/vendor/github.com/rackspace/gophercloud/auth_options.go index d26e16ac1c..07ace1366b 100644 --- a/vendor/github.com/rackspace/gophercloud/auth_options.go +++ b/vendor/github.com/rackspace/gophercloud/auth_options.go @@ -42,6 +42,11 @@ type AuthOptions struct { // re-authenticate automatically if/when your token expires. If you set it to // false, it will not cache these settings, but re-authentication will not be // possible. This setting defaults to false. + // + // NOTE: The reauth function will try to re-authenticate endlessly if left unchecked. + // The way to limit the number of attempts is to provide a custom HTTP client to the provider client + // and provide a transport that implements the RoundTripper interface and stores the number of failed retries. + // For an example of this, see here: https://github.com/rackspace/rack/blob/1.0.0/auth/clients.go#L311 AllowReauth bool // TokenID allows users to authenticate (possibly as another user) with an diff --git a/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups/requests.go b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups/requests.go new file mode 100644 index 0000000000..2712ac1621 --- /dev/null +++ b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups/requests.go @@ -0,0 +1,131 @@ +package groups + +import ( + "fmt" + + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/pagination" +) + +// ListOpts allows the filtering and sorting of paginated collections through +// the API. Filtering is achieved by passing in struct field values that map to +// the floating IP attributes you want to see returned. SortKey allows you to +// sort by a particular network attribute. SortDir sets the direction, and is +// either `asc' or `desc'. Marker and Limit are used for pagination. +type ListOpts struct { + ID string `q:"id"` + Name string `q:"name"` + TenantID string `q:"tenant_id"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` +} + +// List returns a Pager which allows you to iterate over a collection of +// security groups. It accepts a ListOpts struct, which allows you to filter +// and sort the returned collection for greater efficiency. +func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager { + q, err := gophercloud.BuildQueryString(&opts) + if err != nil { + return pagination.Pager{Err: err} + } + u := rootURL(c) + q.String() + return pagination.NewPager(c, u, func(r pagination.PageResult) pagination.Page { + return SecGroupPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +var ( + errNameRequired = fmt.Errorf("Name is required") +) + +// CreateOpts contains all the values needed to create a new security group. +type CreateOpts struct { + // Required. Human-readable name for the VIP. Does not have to be unique. + Name string + + // Required for admins. Indicates the owner of the VIP. + TenantID string + + // Optional. Describes the security group. + Description string +} + +// Create is an operation which provisions a new security group with default +// security group rules for the IPv4 and IPv6 ether types. +func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult { + var res CreateResult + + // Validate required opts + if opts.Name == "" { + res.Err = errNameRequired + return res + } + + type secgroup struct { + Name string `json:"name"` + TenantID string `json:"tenant_id,omitempty"` + Description string `json:"description,omitempty"` + } + + type request struct { + SecGroup secgroup `json:"security_group"` + } + + reqBody := request{SecGroup: secgroup{ + Name: opts.Name, + TenantID: opts.TenantID, + Description: opts.Description, + }} + + _, res.Err = c.Post(rootURL(c), reqBody, &res.Body, nil) + return res +} + +// Get retrieves a particular security group based on its unique ID. +func Get(c *gophercloud.ServiceClient, id string) GetResult { + var res GetResult + _, res.Err = c.Get(resourceURL(c, id), &res.Body, nil) + return res +} + +// Delete will permanently delete a particular security group based on its unique ID. +func Delete(c *gophercloud.ServiceClient, id string) DeleteResult { + var res DeleteResult + _, res.Err = c.Delete(resourceURL(c, id), nil) + return res +} + +// IDFromName is a convenience function that returns a security group's ID given its name. +func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { + securityGroupCount := 0 + securityGroupID := "" + if name == "" { + return "", fmt.Errorf("A security group name must be provided.") + } + pager := List(client, ListOpts{}) + pager.EachPage(func(page pagination.Page) (bool, error) { + securityGroupList, err := ExtractGroups(page) + if err != nil { + return false, err + } + + for _, s := range securityGroupList { + if s.Name == name { + securityGroupCount++ + securityGroupID = s.ID + } + } + return true, nil + }) + + switch securityGroupCount { + case 0: + return "", fmt.Errorf("Unable to find security group: %s", name) + case 1: + return securityGroupID, nil + default: + return "", fmt.Errorf("Found %d security groups matching %s", securityGroupCount, name) + } +} diff --git a/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups/results.go b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups/results.go new file mode 100644 index 0000000000..49db261c22 --- /dev/null +++ b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups/results.go @@ -0,0 +1,108 @@ +package groups + +import ( + "github.com/mitchellh/mapstructure" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules" + "github.com/rackspace/gophercloud/pagination" +) + +// SecGroup represents a container for security group rules. +type SecGroup struct { + // The UUID for the security group. + ID string + + // Human-readable name for the security group. Might not be unique. Cannot be + // named "default" as that is automatically created for a tenant. + Name string + + // The security group description. + Description string + + // A slice of security group rules that dictate the permitted behaviour for + // traffic entering and leaving the group. + Rules []rules.SecGroupRule `json:"security_group_rules" mapstructure:"security_group_rules"` + + // Owner of the security group. Only admin users can specify a TenantID + // other than their own. + TenantID string `json:"tenant_id" mapstructure:"tenant_id"` +} + +// SecGroupPage is the page returned by a pager when traversing over a +// collection of security groups. +type SecGroupPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of security groups has +// reached the end of a page and the pager seeks to traverse over a new one. In +// order to do this, it needs to construct the next page's URL. +func (p SecGroupPage) NextPageURL() (string, error) { + type resp struct { + Links []gophercloud.Link `mapstructure:"security_groups_links"` + } + + var r resp + err := mapstructure.Decode(p.Body, &r) + if err != nil { + return "", err + } + + return gophercloud.ExtractNextURL(r.Links) +} + +// IsEmpty checks whether a SecGroupPage struct is empty. +func (p SecGroupPage) IsEmpty() (bool, error) { + is, err := ExtractGroups(p) + if err != nil { + return true, nil + } + return len(is) == 0, nil +} + +// ExtractGroups accepts a Page struct, specifically a SecGroupPage struct, +// and extracts the elements into a slice of SecGroup structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractGroups(page pagination.Page) ([]SecGroup, error) { + var resp struct { + SecGroups []SecGroup `mapstructure:"security_groups" json:"security_groups"` + } + + err := mapstructure.Decode(page.(SecGroupPage).Body, &resp) + + return resp.SecGroups, err +} + +type commonResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts a security group. +func (r commonResult) Extract() (*SecGroup, error) { + if r.Err != nil { + return nil, r.Err + } + + var res struct { + SecGroup *SecGroup `mapstructure:"security_group" json:"security_group"` + } + + err := mapstructure.Decode(r.Body, &res) + + return res.SecGroup, err +} + +// CreateResult represents the result of a create operation. +type CreateResult struct { + commonResult +} + +// GetResult represents the result of a get operation. +type GetResult struct { + commonResult +} + +// DeleteResult represents the result of a delete operation. +type DeleteResult struct { + gophercloud.ErrResult +} diff --git a/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups/urls.go b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups/urls.go new file mode 100644 index 0000000000..84f7324f09 --- /dev/null +++ b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups/urls.go @@ -0,0 +1,13 @@ +package groups + +import "github.com/rackspace/gophercloud" + +const rootPath = "security-groups" + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(rootPath) +} + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, id) +} diff --git a/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules/requests.go b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules/requests.go new file mode 100644 index 0000000000..e06934a09a --- /dev/null +++ b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules/requests.go @@ -0,0 +1,174 @@ +package rules + +import ( + "fmt" + + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/pagination" +) + +// ListOpts allows the filtering and sorting of paginated collections through +// the API. Filtering is achieved by passing in struct field values that map to +// the security group attributes you want to see returned. SortKey allows you to +// sort by a particular network attribute. SortDir sets the direction, and is +// either `asc' or `desc'. Marker and Limit are used for pagination. +type ListOpts struct { + Direction string `q:"direction"` + EtherType string `q:"ethertype"` + ID string `q:"id"` + PortRangeMax int `q:"port_range_max"` + PortRangeMin int `q:"port_range_min"` + Protocol string `q:"protocol"` + RemoteGroupID string `q:"remote_group_id"` + RemoteIPPrefix string `q:"remote_ip_prefix"` + SecGroupID string `q:"security_group_id"` + TenantID string `q:"tenant_id"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` +} + +// List returns a Pager which allows you to iterate over a collection of +// security group rules. It accepts a ListOpts struct, which allows you to filter +// and sort the returned collection for greater efficiency. +func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager { + q, err := gophercloud.BuildQueryString(&opts) + if err != nil { + return pagination.Pager{Err: err} + } + u := rootURL(c) + q.String() + return pagination.NewPager(c, u, func(r pagination.PageResult) pagination.Page { + return SecGroupRulePage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Errors +var ( + errValidDirectionRequired = fmt.Errorf("A valid Direction is required") + errValidEtherTypeRequired = fmt.Errorf("A valid EtherType is required") + errSecGroupIDRequired = fmt.Errorf("A valid SecGroupID is required") + errValidProtocolRequired = fmt.Errorf("A valid Protocol is required") +) + +// Constants useful for CreateOpts +const ( + DirIngress = "ingress" + DirEgress = "egress" + Ether4 = "IPv4" + Ether6 = "IPv6" + ProtocolTCP = "tcp" + ProtocolUDP = "udp" + ProtocolICMP = "icmp" +) + +// CreateOpts contains all the values needed to create a new security group rule. +type CreateOpts struct { + // Required. Must be either "ingress" or "egress": the direction in which the + // security group rule is applied. + Direction string + + // Required. Must be "IPv4" or "IPv6", and addresses represented in CIDR must + // match the ingress or egress rules. + EtherType string + + // Required. The security group ID to associate with this security group rule. + SecGroupID string + + // Optional. The maximum port number in the range that is matched by the + // security group rule. The PortRangeMin attribute constrains the PortRangeMax + // attribute. If the protocol is ICMP, this value must be an ICMP type. + PortRangeMax int + + // Optional. The minimum port number in the range that is matched by the + // security group rule. If the protocol is TCP or UDP, this value must be + // less than or equal to the value of the PortRangeMax attribute. If the + // protocol is ICMP, this value must be an ICMP type. + PortRangeMin int + + // Optional. The protocol that is matched by the security group rule. Valid + // values are "tcp", "udp", "icmp" or an empty string. + Protocol string + + // Optional. The remote group ID to be associated with this security group + // rule. You can specify either RemoteGroupID or RemoteIPPrefix. + RemoteGroupID string + + // Optional. The remote IP prefix to be associated with this security group + // rule. You can specify either RemoteGroupID or RemoteIPPrefix. This + // attribute matches the specified IP prefix as the source IP address of the + // IP packet. + RemoteIPPrefix string + + // Required for admins. Indicates the owner of the VIP. + TenantID string +} + +// Create is an operation which adds a new security group rule and associates it +// with an existing security group (whose ID is specified in CreateOpts). +func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult { + var res CreateResult + + // Validate required opts + if opts.Direction != DirIngress && opts.Direction != DirEgress { + res.Err = errValidDirectionRequired + return res + } + if opts.EtherType != Ether4 && opts.EtherType != Ether6 { + res.Err = errValidEtherTypeRequired + return res + } + if opts.SecGroupID == "" { + res.Err = errSecGroupIDRequired + return res + } + if opts.Protocol != "" && opts.Protocol != ProtocolTCP && opts.Protocol != ProtocolUDP && opts.Protocol != ProtocolICMP { + res.Err = errValidProtocolRequired + return res + } + + type secrule struct { + Direction string `json:"direction"` + EtherType string `json:"ethertype"` + SecGroupID string `json:"security_group_id"` + PortRangeMax int `json:"port_range_max,omitempty"` + PortRangeMin int `json:"port_range_min,omitempty"` + Protocol string `json:"protocol,omitempty"` + RemoteGroupID string `json:"remote_group_id,omitempty"` + RemoteIPPrefix string `json:"remote_ip_prefix,omitempty"` + TenantID string `json:"tenant_id,omitempty"` + } + + type request struct { + SecRule secrule `json:"security_group_rule"` + } + + reqBody := request{SecRule: secrule{ + Direction: opts.Direction, + EtherType: opts.EtherType, + SecGroupID: opts.SecGroupID, + PortRangeMax: opts.PortRangeMax, + PortRangeMin: opts.PortRangeMin, + Protocol: opts.Protocol, + RemoteGroupID: opts.RemoteGroupID, + RemoteIPPrefix: opts.RemoteIPPrefix, + TenantID: opts.TenantID, + }} + + _, res.Err = c.Post(rootURL(c), reqBody, &res.Body, nil) + return res +} + +// Get retrieves a particular security group rule based on its unique ID. +func Get(c *gophercloud.ServiceClient, id string) GetResult { + var res GetResult + _, res.Err = c.Get(resourceURL(c, id), &res.Body, nil) + return res +} + +// Delete will permanently delete a particular security group rule based on its unique ID. +func Delete(c *gophercloud.ServiceClient, id string) DeleteResult { + var res DeleteResult + _, res.Err = c.Delete(resourceURL(c, id), nil) + return res +} diff --git a/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules/results.go b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules/results.go new file mode 100644 index 0000000000..6e13857689 --- /dev/null +++ b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules/results.go @@ -0,0 +1,133 @@ +package rules + +import ( + "github.com/mitchellh/mapstructure" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/pagination" +) + +// SecGroupRule represents a rule to dictate the behaviour of incoming or +// outgoing traffic for a particular security group. +type SecGroupRule struct { + // The UUID for this security group rule. + ID string + + // The direction in which the security group rule is applied. The only values + // allowed are "ingress" or "egress". For a compute instance, an ingress + // security group rule is applied to incoming (ingress) traffic for that + // instance. An egress rule is applied to traffic leaving the instance. + Direction string + + // Must be IPv4 or IPv6, and addresses represented in CIDR must match the + // ingress or egress rules. + EtherType string `json:"ethertype" mapstructure:"ethertype"` + + // The security group ID to associate with this security group rule. + SecGroupID string `json:"security_group_id" mapstructure:"security_group_id"` + + // The minimum port number in the range that is matched by the security group + // rule. If the protocol is TCP or UDP, this value must be less than or equal + // to the value of the PortRangeMax attribute. If the protocol is ICMP, this + // value must be an ICMP type. + PortRangeMin int `json:"port_range_min" mapstructure:"port_range_min"` + + // The maximum port number in the range that is matched by the security group + // rule. The PortRangeMin attribute constrains the PortRangeMax attribute. If + // the protocol is ICMP, this value must be an ICMP type. + PortRangeMax int `json:"port_range_max" mapstructure:"port_range_max"` + + // The protocol that is matched by the security group rule. Valid values are + // "tcp", "udp", "icmp" or an empty string. + Protocol string + + // The remote group ID to be associated with this security group rule. You + // can specify either RemoteGroupID or RemoteIPPrefix. + RemoteGroupID string `json:"remote_group_id" mapstructure:"remote_group_id"` + + // The remote IP prefix to be associated with this security group rule. You + // can specify either RemoteGroupID or RemoteIPPrefix . This attribute + // matches the specified IP prefix as the source IP address of the IP packet. + RemoteIPPrefix string `json:"remote_ip_prefix" mapstructure:"remote_ip_prefix"` + + // The owner of this security group rule. + TenantID string `json:"tenant_id" mapstructure:"tenant_id"` +} + +// SecGroupRulePage is the page returned by a pager when traversing over a +// collection of security group rules. +type SecGroupRulePage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of security group rules has +// reached the end of a page and the pager seeks to traverse over a new one. In +// order to do this, it needs to construct the next page's URL. +func (p SecGroupRulePage) NextPageURL() (string, error) { + type resp struct { + Links []gophercloud.Link `mapstructure:"security_group_rules_links"` + } + + var r resp + err := mapstructure.Decode(p.Body, &r) + if err != nil { + return "", err + } + + return gophercloud.ExtractNextURL(r.Links) +} + +// IsEmpty checks whether a SecGroupRulePage struct is empty. +func (p SecGroupRulePage) IsEmpty() (bool, error) { + is, err := ExtractRules(p) + if err != nil { + return true, nil + } + return len(is) == 0, nil +} + +// ExtractRules accepts a Page struct, specifically a SecGroupRulePage struct, +// and extracts the elements into a slice of SecGroupRule structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractRules(page pagination.Page) ([]SecGroupRule, error) { + var resp struct { + SecGroupRules []SecGroupRule `mapstructure:"security_group_rules" json:"security_group_rules"` + } + + err := mapstructure.Decode(page.(SecGroupRulePage).Body, &resp) + + return resp.SecGroupRules, err +} + +type commonResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts a security rule. +func (r commonResult) Extract() (*SecGroupRule, error) { + if r.Err != nil { + return nil, r.Err + } + + var res struct { + SecGroupRule *SecGroupRule `mapstructure:"security_group_rule" json:"security_group_rule"` + } + + err := mapstructure.Decode(r.Body, &res) + + return res.SecGroupRule, err +} + +// CreateResult represents the result of a create operation. +type CreateResult struct { + commonResult +} + +// GetResult represents the result of a get operation. +type GetResult struct { + commonResult +} + +// DeleteResult represents the result of a delete operation. +type DeleteResult struct { + gophercloud.ErrResult +} diff --git a/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules/urls.go b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules/urls.go new file mode 100644 index 0000000000..8e2b2bb28d --- /dev/null +++ b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules/urls.go @@ -0,0 +1,13 @@ +package rules + +import "github.com/rackspace/gophercloud" + +const rootPath = "security-group-rules" + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(rootPath) +} + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, id) +} diff --git a/website/source/docs/providers/openstack/r/networking_secgroup_rule_v2.html.markdown b/website/source/docs/providers/openstack/r/networking_secgroup_rule_v2.html.markdown new file mode 100644 index 0000000000..e80ac6cf1f --- /dev/null +++ b/website/source/docs/providers/openstack/r/networking_secgroup_rule_v2.html.markdown @@ -0,0 +1,89 @@ +--- +layout: "openstack" +page_title: "OpenStack: openstack_networking_secgroup_rule_v2" +sidebar_current: "docs-openstack-resource-networking-secgroup-rule-v2" +description: |- + Manages a V2 Neutron security group rule resource within OpenStack. +--- + +# openstack\_networking\_secgroup\_rule_v2 + +Manages a V2 neutron security group rule resource within OpenStack. +Unlike Nova security groups, neutron separates the group from the rules +and also allows an admin to target a specific tenant_id. + +## Example Usage + +``` +resource "openstack_networking_secgroup_v2" "secgroup_1" { + name = "secgroup_1" + description = "My neutron security group" +} + +resource "openstack_networking_secgroup_rule_v2" "secgroup_rule_1" { + direction = "ingress" + ethertype = "IPv4" + protocol = "tcp" + port_range_min = 22 + port_range_max = 22 + remote_ip_prefix = "0.0.0.0/0" + security_group_id = "${openstack_networking_secgroup_v2.secgroup_1.id}" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `region` - (Required) The region in which to obtain the V2 networking client. + A networking client is needed to create a port. If omitted, the + `OS_REGION_NAME` environment variable is used. Changing this creates a new + security group rule. + +* `direction` - (Required) The direction of the rule, valid values are __ingress__ + or __egress__. Changing this creates a new security group rule. + +* `ethertype` - (Required) The layer 3 protocol type, valid values are __IPv4__ + or __IPv6__. Changing this creates a new security group rule. + +* `protocol` - (Optional) The layer 4 protocol type, valid values are __tcp__, + __udp__ or __icmp__. This is required if you want to specify a port range. + Changing this creates a new security group rule. + +* `port_range_min` - (Optional) The lower part of the allowed port range, valid + integer value needs to be between 1 and 65535. Changing this creates a new + security group rule. + +* `port_range_max` - (Optional) The higher part of the allowed port range, valid + integer value needs to be between 1 and 65535. Changing this creates a new + security group rule. + +* `remote_ip_prefix` - (Optional) The remote CIDR, the value needs to be a valid + CIDR (i.e. 192.168.0.0/16). Changing this creates a new security group rule. + +* `remote_group_id` - (Optional) The remote group id, the value needs to be an + Openstack ID of a security group in the same tenant. Changing this creates + a new security group rule. + +* `security_group_id` - (Required) The security group id the rule shoudl belong + to, the value needs to be an Openstack ID of a security group in the same + tenant. Changing this creates a new security group rule. + +* `tenant_id` - (Optional) The owner of the security group. Required if admin + wants to create a port for another tenant. Changing this creates a new + security group rule. + +## Attributes Reference + +The following attributes are exported: + +* `region` - See Argument Reference above. +* `direction` - See Argument Reference above. +* `ethertype` - See Argument Reference above. +* `protocol` - See Argument Reference above. +* `port_range_min` - See Argument Reference above. +* `port_range_max` - See Argument Reference above. +* `remote_ip_prefix` - See Argument Reference above. +* `remote_group_id` - See Argument Reference above. +* `security_group_id` - See Argument Reference above. +* `tenant_id` - See Argument Reference above. diff --git a/website/source/docs/providers/openstack/r/networking_secgroup_v2.html.markdown b/website/source/docs/providers/openstack/r/networking_secgroup_v2.html.markdown new file mode 100644 index 0000000000..ca49e4b66b --- /dev/null +++ b/website/source/docs/providers/openstack/r/networking_secgroup_v2.html.markdown @@ -0,0 +1,50 @@ +--- +layout: "openstack" +page_title: "OpenStack: openstack_networking_secgroup_v2" +sidebar_current: "docs-openstack-resource-networking-secgroup-v2" +description: |- + Manages a V2 Neutron security group resource within OpenStack. +--- + +# openstack\_networking\_secgroup_v2 + +Manages a V2 neutron security group resource within OpenStack. +Unlike Nova security groups, neutron separates the group from the rules +and also allows an admin to target a specific tenant_id. + +## Example Usage + +``` +resource "openstack_networking_secgroup_v2" "secgroup_1" { + name = "secgroup_1" + description = "My neutron security group" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `region` - (Required) The region in which to obtain the V2 networking client. + A networking client is needed to create a port. If omitted, the + `OS_REGION_NAME` environment variable is used. Changing this creates a new + security group. + +* `name` - (Required) A unique name for the security group. Changing this + creates a new security group. + +* `description` - (Optional) A unique name for the security group. Changing this + creates a new security group. + +* `tenant_id` - (Optional) The owner of the security group. Required if admin + wants to create a port for another tenant. Changing this creates a new + security group. + +## Attributes Reference + +The following attributes are exported: + +* `region` - See Argument Reference above. +* `name` - See Argument Reference above. +* `description` - See Argument Reference above. +* `tenant_id` - See Argument Reference above. diff --git a/website/source/layouts/openstack.erb b/website/source/layouts/openstack.erb index 92346a8f51..3be4214716 100644 --- a/website/source/layouts/openstack.erb +++ b/website/source/layouts/openstack.erb @@ -64,6 +64,12 @@