[builder/openstack] adds option to discover provisioning network

This commit is contained in:
Andrey Kaipov 2019-10-26 17:40:11 -04:00
parent 0b9391b092
commit 5da5b00e1c
13 changed files with 755 additions and 13 deletions

View File

@ -101,6 +101,11 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
SourceMostRecent: b.config.SourceImageFilters.MostRecent,
SourceProperties: b.config.SourceImageFilters.Filters.Properties,
},
&StepDiscoverNetwork{
Networks: b.config.Networks,
NetworkDiscoveryCIDRs: b.config.NetworkDiscoveryCIDRs,
Ports: b.config.Ports,
},
&StepCreateVolume{
UseBlockStorageVolume: b.config.UseBlockStorageVolume,
VolumeName: b.config.VolumeName,
@ -110,8 +115,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&StepRunSourceServer{
Name: b.config.InstanceName,
SecurityGroups: b.config.SecurityGroups,
Networks: b.config.Networks,
Ports: b.config.Ports,
AvailabilityZone: b.config.AvailabilityZone,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,

View File

@ -98,6 +98,7 @@ type FlatConfig struct {
SecurityGroups []string `mapstructure:"security_groups" required:"false" cty:"security_groups"`
Networks []string `mapstructure:"networks" required:"false" cty:"networks"`
Ports []string `mapstructure:"ports" required:"false" cty:"ports"`
NetworkDiscoveryCIDRs []string `mapstructure:"network_discovery_cidrs" required:"false" cty:"network_discovery_cidrs"`
UserData *string `mapstructure:"user_data" required:"false" cty:"user_data"`
UserDataFile *string `mapstructure:"user_data_file" required:"false" cty:"user_data_file"`
InstanceName *string `mapstructure:"instance_name" required:"false" cty:"instance_name"`
@ -211,6 +212,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"security_groups": &hcldec.AttrSpec{Name: "security_groups", Type: cty.List(cty.String), Required: false},
"networks": &hcldec.AttrSpec{Name: "networks", Type: cty.List(cty.String), Required: false},
"ports": &hcldec.AttrSpec{Name: "ports", Type: cty.List(cty.String), Required: false},
"network_discovery_cidrs": &hcldec.AttrSpec{Name: "network_discovery_cidrs", Type: cty.List(cty.String), Required: false},
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
"user_data_file": &hcldec.AttrSpec{Name: "user_data_file", Type: cty.String, Required: false},
"instance_name": &hcldec.AttrSpec{Name: "instance_name", Type: cty.String, Required: false},

View File

@ -3,6 +3,7 @@ package openstack
import (
"fmt"
"log"
"net"
"github.com/google/uuid"
"github.com/gophercloud/gophercloud"
@ -10,6 +11,7 @@ import (
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips"
"github.com/gophercloud/gophercloud/openstack/networking/v2/networks"
"github.com/gophercloud/gophercloud/openstack/networking/v2/subnets"
"github.com/gophercloud/gophercloud/pagination"
)
@ -136,3 +138,43 @@ func GetFloatingIPNetworkIDByName(client *gophercloud.ServiceClient, networkName
return externalNetworks[0].ID, nil
}
// DiscoverProvisioningNetwork finds the first network whose subnet matches the given network ranges.
func DiscoverProvisioningNetwork(client *gophercloud.ServiceClient, cidrs []string) (string, error) {
allPages, err := subnets.List(client, subnets.ListOpts{}).AllPages()
if err != nil {
return "", err
}
allSubnets, err := subnets.ExtractSubnets(allPages)
if err != nil {
return "", err
}
for _, subnet := range allSubnets {
_, tenantIPNet, err := net.ParseCIDR(subnet.CIDR)
if err != nil {
return "", err
}
for _, cidr := range cidrs {
_, candidateIPNet, err := net.ParseCIDR(cidr)
if err != nil {
return "", err
}
if containsNet(candidateIPNet, tenantIPNet) {
return subnet.NetworkID, nil
}
}
}
return "", fmt.Errorf("failed to discover a provisioning network")
}
// containsNet returns true whenever IPNet `a` contains IPNet `b`
func containsNet(a *net.IPNet, b *net.IPNet) bool {
aMask, _ := a.Mask.Size()
bMask, _ := b.Mask.Size()
return a.Contains(b.IP) && aMask <= bMask
}

View File

@ -0,0 +1,54 @@
package openstack
import (
"net"
"testing"
)
func testYes(t *testing.T, a, b string) {
var m, n *net.IPNet
_, m, _ = net.ParseCIDR(a)
_, n, _ = net.ParseCIDR(b)
if !containsNet(m, n) {
t.Errorf("%s expected to contain %s", m, n)
}
}
func testNot(t *testing.T, a, b string) {
var m, n *net.IPNet
_, m, _ = net.ParseCIDR(a)
_, n, _ = net.ParseCIDR(b)
if containsNet(m, n) {
t.Errorf("%s expected to not contain %s", m, n)
}
}
func TestNetworkDiscovery_SubnetContainsGood_IPv4(t *testing.T) {
testYes(t, "192.168.0.0/23", "192.168.0.0/24")
testYes(t, "192.168.0.0/24", "192.168.0.0/24")
testNot(t, "192.168.0.0/25", "192.168.0.0/24")
testYes(t, "192.168.101.202/16", "192.168.202.101/16")
testNot(t, "192.168.101.202/24", "192.168.202.101/24")
testNot(t, "192.168.202.101/24", "192.168.101.202/24")
testYes(t, "0.0.0.0/0", "192.168.0.0/24")
testYes(t, "0.0.0.0/0", "0.0.0.0/1")
testNot(t, "192.168.0.0/24", "0.0.0.0/0")
testNot(t, "0.0.0.0/1", "0.0.0.0/0")
}
func TestNetworkDiscovery_SubnetContainsGood_IPv6(t *testing.T) {
testYes(t, "2001:db8::/63", "2001:db8::/64")
testYes(t, "2001:db8::/64", "2001:db8::/64")
testNot(t, "2001:db8::/65", "2001:db8::/64")
testYes(t, "2001:db8:fefe:b00b::/32", "2001:db8:b00b:fefe::/32")
testNot(t, "2001:db8:fefe:b00b::/64", "2001:db8:b00b:fefe::/64")
testNot(t, "2001:db8:b00b:fefe::/64", "2001:db8:fefe:b00b::/64")
testYes(t, "::/0", "2001:db8::/64")
testYes(t, "::/0", "::/1")
testNot(t, "2001:db8::/64", "::/0")
testNot(t, "::/1", "::/0")
}

View File

@ -118,6 +118,10 @@ type RunConfig struct {
Networks []string `mapstructure:"networks" required:"false"`
// A list of ports by UUID to attach to this instance.
Ports []string `mapstructure:"ports" required:"false"`
// A list of network CIDRs to discover the network to attach to this instance.
// The first network whose subnet is contained within any of the given CIDRs
// is used. Ignored if either of the above two options are provided.
NetworkDiscoveryCIDRs []string `mapstructure:"network_discovery_cidrs" required:"false"`
// User data to apply when launching the instance. Note that you need to be
// careful about escaping characters due to the templates being JSON. It is
// often more convenient to use user_data_file, instead. Packer will not

View File

@ -0,0 +1,55 @@
package openstack
import (
"context"
"fmt"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type StepDiscoverNetwork struct {
Networks []string
NetworkDiscoveryCIDRs []string
Ports []string
}
func (s *StepDiscoverNetwork) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
networkClient, err := config.networkV2Client()
if err != nil {
err = fmt.Errorf("Error initializing network client: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
networks := []servers.Network{}
for _, port := range s.Ports {
networks = append(networks, servers.Network{Port: port})
}
for _, uuid := range s.Networks {
networks = append(networks, servers.Network{UUID: uuid})
}
cidrs := s.NetworkDiscoveryCIDRs
if len(networks) == 0 && len(cidrs) > 0 {
ui.Say(fmt.Sprintf("Discovering provisioning network..."))
networkID, err := DiscoverProvisioningNetwork(networkClient, cidrs)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
ui.Message(fmt.Sprintf("Found network ID: %s", networkID))
networks = append(networks, servers.Network{UUID: networkID})
}
state.Put("networks", networks)
return multistep.ActionContinue
}
func (s *StepDiscoverNetwork) Cleanup(state multistep.StateBag) {}

View File

@ -16,8 +16,6 @@ import (
type StepRunSourceServer struct {
Name string
SecurityGroups []string
Networks []string
Ports []string
AvailabilityZone string
UserData string
UserDataFile string
@ -32,6 +30,7 @@ func (s *StepRunSourceServer) Run(ctx context.Context, state multistep.StateBag)
config := state.Get("config").(*Config)
flavor := state.Get("flavor_id").(string)
sourceImage := state.Get("source_image").(string)
networks := state.Get("networks").([]servers.Network)
ui := state.Get("ui").(packer.Ui)
// We need the v2 compute client
@ -42,15 +41,6 @@ func (s *StepRunSourceServer) Run(ctx context.Context, state multistep.StateBag)
return multistep.ActionHalt
}
networks := make([]servers.Network, len(s.Networks)+len(s.Ports))
i := 0
for ; i < len(s.Ports); i++ {
networks[i].Port = s.Ports[i]
}
for ; i < len(networks); i++ {
networks[i].UUID = s.Networks[i-len(s.Ports)]
}
userData := []byte(s.UserData)
if s.UserDataFile != "" {
userData, err = ioutil.ReadFile(s.UserDataFile)

View File

@ -0,0 +1,135 @@
/*
Package subnets contains functionality for working with Neutron subnet
resources. A subnet represents an IP address block that can be used to
assign IP addresses to virtual instances. Each subnet must have a CIDR and
must be associated with a network. IPs can either be selected from the whole
subnet CIDR or from allocation pools specified by the user.
A subnet can also have a gateway, a list of DNS name servers, and host routes.
This information is pushed to instances whose interfaces are associated with
the subnet.
Example to List Subnets
listOpts := subnets.ListOpts{
IPVersion: 4,
}
allPages, err := subnets.List(networkClient, listOpts).AllPages()
if err != nil {
panic(err)
}
allSubnets, err := subnets.ExtractSubnets(allPages)
if err != nil {
panic(err)
}
for _, subnet := range allSubnets {
fmt.Printf("%+v\n", subnet)
}
Example to Create a Subnet With Specified Gateway
var gatewayIP = "192.168.199.1"
createOpts := subnets.CreateOpts{
NetworkID: "d32019d3-bc6e-4319-9c1d-6722fc136a22",
IPVersion: 4,
CIDR: "192.168.199.0/24",
GatewayIP: &gatewayIP,
AllocationPools: []subnets.AllocationPool{
{
Start: "192.168.199.2",
End: "192.168.199.254",
},
},
DNSNameservers: []string{"foo"},
}
subnet, err := subnets.Create(networkClient, createOpts).Extract()
if err != nil {
panic(err)
}
Example to Create a Subnet With No Gateway
var noGateway = ""
createOpts := subnets.CreateOpts{
NetworkID: "d32019d3-bc6e-4319-9c1d-6722fc136a23",
IPVersion: 4,
CIDR: "192.168.1.0/24",
GatewayIP: &noGateway,
AllocationPools: []subnets.AllocationPool{
{
Start: "192.168.1.2",
End: "192.168.1.254",
},
},
DNSNameservers: []string{},
}
subnet, err := subnets.Create(networkClient, createOpts).Extract()
if err != nil {
panic(err)
}
Example to Create a Subnet With a Default Gateway
createOpts := subnets.CreateOpts{
NetworkID: "d32019d3-bc6e-4319-9c1d-6722fc136a23",
IPVersion: 4,
CIDR: "192.168.1.0/24",
AllocationPools: []subnets.AllocationPool{
{
Start: "192.168.1.2",
End: "192.168.1.254",
},
},
DNSNameservers: []string{},
}
subnet, err := subnets.Create(networkClient, createOpts).Extract()
if err != nil {
panic(err)
}
Example to Update a Subnet
subnetID := "db77d064-e34f-4d06-b060-f21e28a61c23"
dnsNameservers := []string{"8.8.8.8"}
name := "new_name"
updateOpts := subnets.UpdateOpts{
Name: &name,
DNSNameservers: &dnsNameservers,
}
subnet, err := subnets.Update(networkClient, subnetID, updateOpts).Extract()
if err != nil {
panic(err)
}
Example to Remove a Gateway From a Subnet
var noGateway = ""
subnetID := "db77d064-e34f-4d06-b060-f21e28a61c23"
updateOpts := subnets.UpdateOpts{
GatewayIP: &noGateway,
}
subnet, err := subnets.Update(networkClient, subnetID, updateOpts).Extract()
if err != nil {
panic(err)
}
Example to Delete a Subnet
subnetID := "db77d064-e34f-4d06-b060-f21e28a61c23"
err := subnets.Delete(networkClient, subnetID).ExtractErr()
if err != nil {
panic(err)
}
*/
package subnets

View File

@ -0,0 +1,269 @@
package subnets
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// ListOptsBuilder allows extensions to add additional parameters to the
// List request.
type ListOptsBuilder interface {
ToSubnetListQuery() (string, error)
}
// 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 subnet attributes you want to see returned. SortKey allows you to sort
// by a particular subnet attribute. SortDir sets the direction, and is either
// `asc' or `desc'. Marker and Limit are used for pagination.
type ListOpts struct {
Name string `q:"name"`
Description string `q:"description"`
EnableDHCP *bool `q:"enable_dhcp"`
NetworkID string `q:"network_id"`
TenantID string `q:"tenant_id"`
ProjectID string `q:"project_id"`
IPVersion int `q:"ip_version"`
GatewayIP string `q:"gateway_ip"`
CIDR string `q:"cidr"`
IPv6AddressMode string `q:"ipv6_address_mode"`
IPv6RAMode string `q:"ipv6_ra_mode"`
ID string `q:"id"`
SubnetPoolID string `q:"subnetpool_id"`
Limit int `q:"limit"`
Marker string `q:"marker"`
SortKey string `q:"sort_key"`
SortDir string `q:"sort_dir"`
Tags string `q:"tags"`
TagsAny string `q:"tags-any"`
NotTags string `q:"not-tags"`
NotTagsAny string `q:"not-tags-any"`
}
// ToSubnetListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToSubnetListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
return q.String(), err
}
// List returns a Pager which allows you to iterate over a collection of
// subnets. It accepts a ListOpts struct, which allows you to filter and sort
// the returned collection for greater efficiency.
//
// Default policy settings return only those subnets that are owned by the tenant
// who submits the request, unless the request is submitted by a user with
// administrative rights.
func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
url := listURL(c)
if opts != nil {
query, err := opts.ToSubnetListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
}
return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page {
return SubnetPage{pagination.LinkedPageBase{PageResult: r}}
})
}
// Get retrieves a specific subnet based on its unique ID.
func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = c.Get(getURL(c, id), &r.Body, nil)
return
}
// CreateOptsBuilder allows extensions to add additional parameters to the
// List request.
type CreateOptsBuilder interface {
ToSubnetCreateMap() (map[string]interface{}, error)
}
// CreateOpts represents the attributes used when creating a new subnet.
type CreateOpts struct {
// NetworkID is the UUID of the network the subnet will be associated with.
NetworkID string `json:"network_id" required:"true"`
// CIDR is the address CIDR of the subnet.
CIDR string `json:"cidr,omitempty"`
// Name is a human-readable name of the subnet.
Name string `json:"name,omitempty"`
// Description of the subnet.
Description string `json:"description,omitempty"`
// The UUID of the project who owns the Subnet. Only administrative users
// can specify a project UUID other than their own.
TenantID string `json:"tenant_id,omitempty"`
// The UUID of the project who owns the Subnet. Only administrative users
// can specify a project UUID other than their own.
ProjectID string `json:"project_id,omitempty"`
// AllocationPools are IP Address pools that will be available for DHCP.
AllocationPools []AllocationPool `json:"allocation_pools,omitempty"`
// GatewayIP sets gateway information for the subnet. Setting to nil will
// cause a default gateway to automatically be created. Setting to an empty
// string will cause the subnet to be created with no gateway. Setting to
// an explicit address will set that address as the gateway.
GatewayIP *string `json:"gateway_ip,omitempty"`
// IPVersion is the IP version for the subnet.
IPVersion gophercloud.IPVersion `json:"ip_version,omitempty"`
// EnableDHCP will either enable to disable the DHCP service.
EnableDHCP *bool `json:"enable_dhcp,omitempty"`
// DNSNameservers are the nameservers to be set via DHCP.
DNSNameservers []string `json:"dns_nameservers,omitempty"`
// HostRoutes are any static host routes to be set via DHCP.
HostRoutes []HostRoute `json:"host_routes,omitempty"`
// The IPv6 address modes specifies mechanisms for assigning IPv6 IP addresses.
IPv6AddressMode string `json:"ipv6_address_mode,omitempty"`
// The IPv6 router advertisement specifies whether the networking service
// should transmit ICMPv6 packets.
IPv6RAMode string `json:"ipv6_ra_mode,omitempty"`
// SubnetPoolID is the id of the subnet pool that subnet should be associated to.
SubnetPoolID string `json:"subnetpool_id,omitempty"`
// Prefixlen is used when user creates a subnet from the subnetpool. It will
// overwrite the "default_prefixlen" value of the referenced subnetpool.
Prefixlen int `json:"prefixlen,omitempty"`
}
// ToSubnetCreateMap builds a request body from CreateOpts.
func (opts CreateOpts) ToSubnetCreateMap() (map[string]interface{}, error) {
b, err := gophercloud.BuildRequestBody(opts, "subnet")
if err != nil {
return nil, err
}
if m := b["subnet"].(map[string]interface{}); m["gateway_ip"] == "" {
m["gateway_ip"] = nil
}
return b, nil
}
// Create accepts a CreateOpts struct and creates a new subnet using the values
// provided. You must remember to provide a valid NetworkID, CIDR and IP
// version.
func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToSubnetCreateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = c.Post(createURL(c), b, &r.Body, nil)
return
}
// UpdateOptsBuilder allows extensions to add additional parameters to the
// Update request.
type UpdateOptsBuilder interface {
ToSubnetUpdateMap() (map[string]interface{}, error)
}
// UpdateOpts represents the attributes used when updating an existing subnet.
type UpdateOpts struct {
// Name is a human-readable name of the subnet.
Name *string `json:"name,omitempty"`
// Description of the subnet.
Description *string `json:"description,omitempty"`
// AllocationPools are IP Address pools that will be available for DHCP.
AllocationPools []AllocationPool `json:"allocation_pools,omitempty"`
// GatewayIP sets gateway information for the subnet. Setting to nil will
// cause a default gateway to automatically be created. Setting to an empty
// string will cause the subnet to be created with no gateway. Setting to
// an explicit address will set that address as the gateway.
GatewayIP *string `json:"gateway_ip,omitempty"`
// DNSNameservers are the nameservers to be set via DHCP.
DNSNameservers *[]string `json:"dns_nameservers,omitempty"`
// HostRoutes are any static host routes to be set via DHCP.
HostRoutes *[]HostRoute `json:"host_routes,omitempty"`
// EnableDHCP will either enable to disable the DHCP service.
EnableDHCP *bool `json:"enable_dhcp,omitempty"`
}
// ToSubnetUpdateMap builds a request body from UpdateOpts.
func (opts UpdateOpts) ToSubnetUpdateMap() (map[string]interface{}, error) {
b, err := gophercloud.BuildRequestBody(opts, "subnet")
if err != nil {
return nil, err
}
if m := b["subnet"].(map[string]interface{}); m["gateway_ip"] == "" {
m["gateway_ip"] = nil
}
return b, nil
}
// Update accepts a UpdateOpts struct and updates an existing subnet using the
// values provided.
func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
b, err := opts.ToSubnetUpdateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = c.Put(updateURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 201},
})
return
}
// Delete accepts a unique ID and deletes the subnet associated with it.
func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = c.Delete(deleteURL(c, id), nil)
return
}
// IDFromName is a convenience function that returns a subnet's ID,
// given its name.
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
count := 0
id := ""
listOpts := ListOpts{
Name: name,
}
pages, err := List(client, listOpts).AllPages()
if err != nil {
return "", err
}
all, err := ExtractSubnets(pages)
if err != nil {
return "", err
}
for _, s := range all {
if s.Name == name {
count++
id = s.ID
}
}
switch count {
case 0:
return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "subnet"}
case 1:
return id, nil
default:
return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "subnet"}
}
}

View File

@ -0,0 +1,152 @@
package subnets
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
type commonResult struct {
gophercloud.Result
}
// Extract is a function that accepts a result and extracts a subnet resource.
func (r commonResult) Extract() (*Subnet, error) {
var s struct {
Subnet *Subnet `json:"subnet"`
}
err := r.ExtractInto(&s)
return s.Subnet, err
}
// CreateResult represents the result of a create operation. Call its Extract
// method to interpret it as a Subnet.
type CreateResult struct {
commonResult
}
// GetResult represents the result of a get operation. Call its Extract
// method to interpret it as a Subnet.
type GetResult struct {
commonResult
}
// UpdateResult represents the result of an update operation. Call its Extract
// method to interpret it as a Subnet.
type UpdateResult struct {
commonResult
}
// DeleteResult represents the result of a delete operation. Call its
// ExtractErr method to determine if the request succeeded or failed.
type DeleteResult struct {
gophercloud.ErrResult
}
// AllocationPool represents a sub-range of cidr available for dynamic
// allocation to ports, e.g. {Start: "10.0.0.2", End: "10.0.0.254"}
type AllocationPool struct {
Start string `json:"start"`
End string `json:"end"`
}
// HostRoute represents a route that should be used by devices with IPs from
// a subnet (not including local subnet route).
type HostRoute struct {
DestinationCIDR string `json:"destination"`
NextHop string `json:"nexthop"`
}
// Subnet represents a subnet. See package documentation for a top-level
// description of what this is.
type Subnet struct {
// UUID representing the subnet.
ID string `json:"id"`
// UUID of the parent network.
NetworkID string `json:"network_id"`
// Human-readable name for the subnet. Might not be unique.
Name string `json:"name"`
// Description for the subnet.
Description string `json:"description"`
// IP version, either `4' or `6'.
IPVersion int `json:"ip_version"`
// CIDR representing IP range for this subnet, based on IP version.
CIDR string `json:"cidr"`
// Default gateway used by devices in this subnet.
GatewayIP string `json:"gateway_ip"`
// DNS name servers used by hosts in this subnet.
DNSNameservers []string `json:"dns_nameservers"`
// Sub-ranges of CIDR available for dynamic allocation to ports.
// See AllocationPool.
AllocationPools []AllocationPool `json:"allocation_pools"`
// Routes that should be used by devices with IPs from this subnet
// (not including local subnet route).
HostRoutes []HostRoute `json:"host_routes"`
// Specifies whether DHCP is enabled for this subnet or not.
EnableDHCP bool `json:"enable_dhcp"`
// TenantID is the project owner of the subnet.
TenantID string `json:"tenant_id"`
// ProjectID is the project owner of the subnet.
ProjectID string `json:"project_id"`
// The IPv6 address modes specifies mechanisms for assigning IPv6 IP addresses.
IPv6AddressMode string `json:"ipv6_address_mode"`
// The IPv6 router advertisement specifies whether the networking service
// should transmit ICMPv6 packets.
IPv6RAMode string `json:"ipv6_ra_mode"`
// SubnetPoolID is the id of the subnet pool associated with the subnet.
SubnetPoolID string `json:"subnetpool_id"`
// Tags optionally set via extensions/attributestags
Tags []string `json:"tags"`
}
// SubnetPage is the page returned by a pager when traversing over a collection
// of subnets.
type SubnetPage struct {
pagination.LinkedPageBase
}
// NextPageURL is invoked when a paginated collection of subnets 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 (r SubnetPage) NextPageURL() (string, error) {
var s struct {
Links []gophercloud.Link `json:"subnets_links"`
}
err := r.ExtractInto(&s)
if err != nil {
return "", err
}
return gophercloud.ExtractNextURL(s.Links)
}
// IsEmpty checks whether a SubnetPage struct is empty.
func (r SubnetPage) IsEmpty() (bool, error) {
is, err := ExtractSubnets(r)
return len(is) == 0, err
}
// ExtractSubnets accepts a Page struct, specifically a SubnetPage struct,
// and extracts the elements into a slice of Subnet structs. In other words,
// a generic collection is mapped into a relevant slice.
func ExtractSubnets(r pagination.Page) ([]Subnet, error) {
var s struct {
Subnets []Subnet `json:"subnets"`
}
err := (r.(SubnetPage)).ExtractInto(&s)
return s.Subnets, err
}

View File

@ -0,0 +1,31 @@
package subnets
import "github.com/gophercloud/gophercloud"
func resourceURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL("subnets", id)
}
func rootURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("subnets")
}
func listURL(c *gophercloud.ServiceClient) string {
return rootURL(c)
}
func getURL(c *gophercloud.ServiceClient, id string) string {
return resourceURL(c, id)
}
func createURL(c *gophercloud.ServiceClient) string {
return rootURL(c)
}
func updateURL(c *gophercloud.ServiceClient, id string) string {
return resourceURL(c, id)
}
func deleteURL(c *gophercloud.ServiceClient, id string) string {
return resourceURL(c, id)
}

1
vendor/modules.txt vendored
View File

@ -275,6 +275,7 @@ github.com/gophercloud/gophercloud/openstack/imageservice/v2/members
github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external
github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips
github.com/gophercloud/gophercloud/openstack/networking/v2/networks
github.com/gophercloud/gophercloud/openstack/networking/v2/subnets
github.com/gophercloud/gophercloud/openstack/utils
github.com/gophercloud/gophercloud/pagination
# github.com/gophercloud/utils v0.0.0-20190124192022-a5c25e7a53a6

View File

@ -41,6 +41,10 @@
- `ports` ([]string) - A list of ports by UUID to attach to this instance.
- `network_discovery_cidrs` ([]string) - A list of network CIDRs to discover the network to attach to this instance.
The first network whose subnet is contained within any of the given CIDRs
is used. Ignored if either of the above two options are provided.
- `user_data` (string) - User data to apply when launching the instance. Note that you need to be
careful about escaping characters due to the templates being JSON. It is
often more convenient to use user_data_file, instead. Packer will not