Merge pull request #36344 from anguslees/gophercloud

Automatic merge from submit-queue (batch tested with PRs 41756, 36344, 34259, 40843, 41526)

Migrate rackspace/gophercloud -> gophercloud/gophercloud

`gophercloud` is the go library we use to interact with openstack.  It has recently been renamed and incorporated a number of breaking code cleanups.  This change migrates the Kubernetes openstack code (openstack provider, keystone auth, cinder volumes) to the new library.

Fixes #30404

```release-note
The openstack cloud provider config no longer supports the deprecated `api-key` option. 
```

Note this PR doesn't change the rackspace provider.  It uses some rackspace-specific APIs and continues to use the older rackspace/gophercloud library.
This commit is contained in:
Kubernetes Submit Queue 2017-02-20 13:39:36 -08:00 committed by GitHub
commit 79bef05f2d
130 changed files with 19333 additions and 12433 deletions

194
Godeps/Godeps.json generated
View File

@ -1,7 +1,7 @@
{
"ImportPath": "k8s.io/kubernetes",
"GoVersion": "go1.7",
"GodepVersion": "v74",
"GodepVersion": "v75",
"Packages": [
"github.com/ugorji/go/codec/codecgen",
"github.com/onsi/ginkgo/ginkgo",
@ -1400,6 +1400,118 @@
"ImportPath": "github.com/google/gofuzz",
"Rev": "44d81051d367757e1c7c6a5a86423ece9afcf63c"
},
{
"ImportPath": "github.com/gophercloud/gophercloud",
"Rev": "12f19e5e04d617182cffa5c11f189ef0013b9791"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack",
"Rev": "12f19e5e04d617182cffa5c11f189ef0013b9791"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes",
"Rev": "12f19e5e04d617182cffa5c11f189ef0013b9791"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/common/extensions",
"Rev": "12f19e5e04d617182cffa5c11f189ef0013b9791"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach",
"Rev": "12f19e5e04d617182cffa5c11f189ef0013b9791"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors",
"Rev": "12f19e5e04d617182cffa5c11f189ef0013b9791"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/compute/v2/images",
"Rev": "12f19e5e04d617182cffa5c11f189ef0013b9791"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/compute/v2/servers",
"Rev": "12f19e5e04d617182cffa5c11f189ef0013b9791"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v2/tenants",
"Rev": "12f19e5e04d617182cffa5c11f189ef0013b9791"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens",
"Rev": "12f19e5e04d617182cffa5c11f189ef0013b9791"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts",
"Rev": "12f19e5e04d617182cffa5c11f189ef0013b9791"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens",
"Rev": "12f19e5e04d617182cffa5c11f189ef0013b9791"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions",
"Rev": "12f19e5e04d617182cffa5c11f189ef0013b9791"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips",
"Rev": "12f19e5e04d617182cffa5c11f189ef0013b9791"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers",
"Rev": "12f19e5e04d617182cffa5c11f189ef0013b9791"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/members",
"Rev": "12f19e5e04d617182cffa5c11f189ef0013b9791"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/monitors",
"Rev": "12f19e5e04d617182cffa5c11f189ef0013b9791"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/pools",
"Rev": "12f19e5e04d617182cffa5c11f189ef0013b9791"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/vips",
"Rev": "12f19e5e04d617182cffa5c11f189ef0013b9791"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners",
"Rev": "12f19e5e04d617182cffa5c11f189ef0013b9791"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers",
"Rev": "12f19e5e04d617182cffa5c11f189ef0013b9791"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors",
"Rev": "12f19e5e04d617182cffa5c11f189ef0013b9791"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools",
"Rev": "12f19e5e04d617182cffa5c11f189ef0013b9791"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups",
"Rev": "12f19e5e04d617182cffa5c11f189ef0013b9791"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules",
"Rev": "12f19e5e04d617182cffa5c11f189ef0013b9791"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/ports",
"Rev": "12f19e5e04d617182cffa5c11f189ef0013b9791"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/utils",
"Rev": "12f19e5e04d617182cffa5c11f189ef0013b9791"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/pagination",
"Rev": "12f19e5e04d617182cffa5c11f189ef0013b9791"
},
{
"ImportPath": "github.com/gorilla/context",
"Rev": "215affda49addc4c8ef7e2534915df2c8c35c6cd"
@ -1964,11 +2076,6 @@
"Comment": "v1.0.0-1012-ge00690e8",
"Rev": "e00690e87603abe613e9f02c816c7c4bef82e063"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/common/extensions",
"Comment": "v1.0.0-1012-ge00690e8",
"Rev": "e00690e87603abe613e9f02c816c7c4bef82e063"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/bootfromvolume",
"Comment": "v1.0.0-1012-ge00690e8",
@ -2009,86 +2116,11 @@
"Comment": "v1.0.0-1012-ge00690e8",
"Rev": "e00690e87603abe613e9f02c816c7c4bef82e063"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/identity/v3/extensions/trust",
"Comment": "v1.0.0-1012-ge00690e8",
"Rev": "e00690e87603abe613e9f02c816c7c4bef82e063"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/identity/v3/tokens",
"Comment": "v1.0.0-1012-ge00690e8",
"Rev": "e00690e87603abe613e9f02c816c7c4bef82e063"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions",
"Comment": "v1.0.0-1012-ge00690e8",
"Rev": "e00690e87603abe613e9f02c816c7c4bef82e063"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips",
"Comment": "v1.0.0-1012-ge00690e8",
"Rev": "e00690e87603abe613e9f02c816c7c4bef82e063"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/routers",
"Comment": "v1.0.0-1012-ge00690e8",
"Rev": "e00690e87603abe613e9f02c816c7c4bef82e063"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/members",
"Comment": "v1.0.0-1012-ge00690e8",
"Rev": "e00690e87603abe613e9f02c816c7c4bef82e063"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/monitors",
"Comment": "v1.0.0-1012-ge00690e8",
"Rev": "e00690e87603abe613e9f02c816c7c4bef82e063"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/pools",
"Comment": "v1.0.0-1012-ge00690e8",
"Rev": "e00690e87603abe613e9f02c816c7c4bef82e063"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/vips",
"Comment": "v1.0.0-1012-ge00690e8",
"Rev": "e00690e87603abe613e9f02c816c7c4bef82e063"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners",
"Comment": "v1.0.0-1012-ge00690e8",
"Rev": "e00690e87603abe613e9f02c816c7c4bef82e063"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers",
"Comment": "v1.0.0-1012-ge00690e8",
"Rev": "e00690e87603abe613e9f02c816c7c4bef82e063"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors",
"Comment": "v1.0.0-1012-ge00690e8",
"Rev": "e00690e87603abe613e9f02c816c7c4bef82e063"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools",
"Comment": "v1.0.0-1012-ge00690e8",
"Rev": "e00690e87603abe613e9f02c816c7c4bef82e063"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups",
"Comment": "v1.0.0-1012-ge00690e8",
"Rev": "e00690e87603abe613e9f02c816c7c4bef82e063"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules",
"Comment": "v1.0.0-1012-ge00690e8",
"Rev": "e00690e87603abe613e9f02c816c7c4bef82e063"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/ports",
"Comment": "v1.0.0-1012-ge00690e8",
"Rev": "e00690e87603abe613e9f02c816c7c4bef82e063"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/utils",
"Comment": "v1.0.0-1012-ge00690e8",

8756
Godeps/LICENSES generated

File diff suppressed because it is too large Load Diff

View File

@ -27,30 +27,30 @@ go_library(
"//pkg/util/mount:go_default_library",
"//pkg/volume:go_default_library",
"//vendor:github.com/golang/glog",
"//vendor:github.com/gophercloud/gophercloud",
"//vendor:github.com/gophercloud/gophercloud/openstack",
"//vendor:github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes",
"//vendor:github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach",
"//vendor:github.com/gophercloud/gophercloud/openstack/compute/v2/flavors",
"//vendor:github.com/gophercloud/gophercloud/openstack/compute/v2/servers",
"//vendor:github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts",
"//vendor:github.com/gophercloud/gophercloud/openstack/identity/v3/tokens",
"//vendor:github.com/gophercloud/gophercloud/openstack/networking/v2/extensions",
"//vendor:github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips",
"//vendor:github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers",
"//vendor:github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/members",
"//vendor:github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/monitors",
"//vendor:github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/pools",
"//vendor:github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/vips",
"//vendor:github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners",
"//vendor:github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers",
"//vendor:github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors",
"//vendor:github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools",
"//vendor:github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups",
"//vendor:github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules",
"//vendor:github.com/gophercloud/gophercloud/openstack/networking/v2/ports",
"//vendor:github.com/gophercloud/gophercloud/pagination",
"//vendor:github.com/mitchellh/mapstructure",
"//vendor:github.com/rackspace/gophercloud",
"//vendor:github.com/rackspace/gophercloud/openstack",
"//vendor:github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes",
"//vendor:github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach",
"//vendor:github.com/rackspace/gophercloud/openstack/compute/v2/flavors",
"//vendor:github.com/rackspace/gophercloud/openstack/compute/v2/servers",
"//vendor:github.com/rackspace/gophercloud/openstack/identity/v3/extensions/trust",
"//vendor:github.com/rackspace/gophercloud/openstack/identity/v3/tokens",
"//vendor:github.com/rackspace/gophercloud/openstack/networking/v2/extensions",
"//vendor:github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips",
"//vendor:github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/routers",
"//vendor:github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/members",
"//vendor:github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/monitors",
"//vendor:github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/pools",
"//vendor:github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/vips",
"//vendor:github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners",
"//vendor:github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers",
"//vendor:github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors",
"//vendor:github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools",
"//vendor:github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups",
"//vendor:github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules",
"//vendor:github.com/rackspace/gophercloud/openstack/networking/v2/ports",
"//vendor:github.com/rackspace/gophercloud/pagination",
"//vendor:gopkg.in/gcfg.v1",
"//vendor:k8s.io/apimachinery/pkg/api/resource",
"//vendor:k8s.io/apimachinery/pkg/types",
@ -69,8 +69,8 @@ go_test(
deps = [
"//pkg/api/v1:go_default_library",
"//pkg/cloudprovider:go_default_library",
"//vendor:github.com/rackspace/gophercloud",
"//vendor:github.com/rackspace/gophercloud/openstack/compute/v2/servers",
"//vendor:github.com/gophercloud/gophercloud",
"//vendor:github.com/gophercloud/gophercloud/openstack/compute/v2/servers",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/types",
"//vendor:k8s.io/apimachinery/pkg/util/rand",

View File

@ -26,13 +26,13 @@ import (
"strings"
"time"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
"github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts"
tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
"github.com/gophercloud/gophercloud/pagination"
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack"
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
"github.com/rackspace/gophercloud/openstack/identity/v3/extensions/trust"
token3 "github.com/rackspace/gophercloud/openstack/identity/v3/tokens"
"github.com/rackspace/gophercloud/pagination"
"gopkg.in/gcfg.v1"
"github.com/golang/glog"
@ -110,7 +110,6 @@ type Config struct {
Username string
UserId string `gcfg:"user-id"`
Password string
ApiKey string `gcfg:"api-key"`
TenantId string `gcfg:"tenant-id"`
TenantName string `gcfg:"tenant-name"`
TrustId string `gcfg:"trust-id"`
@ -139,7 +138,6 @@ func (cfg Config) toAuthOptions() gophercloud.AuthOptions {
Username: cfg.Global.Username,
UserID: cfg.Global.UserId,
Password: cfg.Global.Password,
APIKey: cfg.Global.ApiKey,
TenantID: cfg.Global.TenantId,
TenantName: cfg.Global.TenantName,
DomainID: cfg.Global.DomainId,
@ -150,6 +148,18 @@ func (cfg Config) toAuthOptions() gophercloud.AuthOptions {
}
}
func (cfg Config) toAuth3Options() tokens3.AuthOptions {
return tokens3.AuthOptions{
IdentityEndpoint: cfg.Global.AuthUrl,
Username: cfg.Global.Username,
UserID: cfg.Global.UserId,
Password: cfg.Global.Password,
DomainID: cfg.Global.DomainId,
DomainName: cfg.Global.DomainName,
AllowReauth: true,
}
}
func readConfig(config io.Reader) (Config, error) {
if config == nil {
err := fmt.Errorf("no OpenStack cloud provider config file given")
@ -205,11 +215,12 @@ func newOpenStack(cfg Config) (*OpenStack, error) {
return nil, err
}
if cfg.Global.TrustId != "" {
authOptionsExt := trust.AuthOptionsExt{
opts := cfg.toAuth3Options()
authOptsExt := trusts.AuthOptsExt{
TrustID: cfg.Global.TrustId,
AuthOptions: token3.AuthOptions{AuthOptions: cfg.toAuthOptions()},
AuthOptionsBuilder: &opts,
}
err = trust.AuthenticateV3Trust(provider, authOptionsExt)
err = openstack.AuthenticateV3(provider, authOptsExt, gophercloud.EndpointOpts{})
} else {
err = openstack.Authenticate(provider, cfg.toAuthOptions())
}
@ -446,7 +457,7 @@ func (os *OpenStack) LoadBalancer() (cloudprovider.LoadBalancer, bool) {
}
func isNotFound(err error) bool {
e, ok := err.(*gophercloud.UnexpectedResponseCodeError)
e, ok := err.(*gophercloud.ErrUnexpectedResponseCode)
return ok && e.Actual == http.StatusNotFound
}

View File

@ -20,11 +20,11 @@ import (
"errors"
"github.com/golang/glog"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack"
"github.com/rackspace/gophercloud/openstack/compute/v2/flavors"
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
"github.com/rackspace/gophercloud/pagination"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack"
"github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
"github.com/gophercloud/gophercloud/pagination"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/types"

View File

@ -23,21 +23,21 @@ import (
"time"
"github.com/golang/glog"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/members"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/monitors"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/pools"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/vips"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers"
v2monitors "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors"
v2pools "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules"
neutronports "github.com/rackspace/gophercloud/openstack/networking/v2/ports"
"github.com/rackspace/gophercloud/pagination"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/members"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/monitors"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/pools"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/vips"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers"
v2monitors "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors"
v2pools "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules"
neutronports "github.com/gophercloud/gophercloud/openstack/networking/v2/ports"
"github.com/gophercloud/gophercloud/pagination"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/api/v1/service"
@ -221,7 +221,7 @@ func getLoadbalancerByName(client *gophercloud.ServiceClient, name string) (*loa
loadbalancerList := make([]loadbalancers.LoadBalancer, 0, 1)
err := pager.EachPage(func(page pagination.Page) (bool, error) {
v, err := loadbalancers.ExtractLoadbalancers(page)
v, err := loadbalancers.ExtractLoadBalancers(page)
if err != nil {
return false, err
}
@ -275,7 +275,7 @@ func getListenersByLoadBalancerID(client *gophercloud.ServiceClient, id string)
// get listener for a port or nil if does not exist
func getListenerForPort(existingListeners []listeners.Listener, port v1.ServicePort) *listeners.Listener {
for _, l := range existingListeners {
if l.Protocol == string(port.Protocol) && l.ProtocolPort == int(port.Port) {
if listeners.Protocol(l.Protocol) == toListenersProtocol(port.Protocol) && l.ProtocolPort == int(port.Port) {
return &l
}
}
@ -321,7 +321,7 @@ func getPoolByListenerID(client *gophercloud.ServiceClient, loadbalancerID strin
func getMembersByPoolID(client *gophercloud.ServiceClient, id string) ([]v2pools.Member, error) {
var members []v2pools.Member
err := v2pools.ListAssociateMembers(client, id, v2pools.MemberListOpts{}).EachPage(func(page pagination.Page) (bool, error) {
err := v2pools.ListMembers(client, id, v2pools.ListMembersOpts{}).EachPage(func(page pagination.Page) (bool, error) {
membersList, err := v2pools.ExtractMembers(page)
if err != nil {
return false, err
@ -477,25 +477,45 @@ func waitLoadbalancerDeleted(client *gophercloud.ServiceClient, loadbalancerID s
}
}
func createNodeSecurityGroup(client *gophercloud.ServiceClient, nodeSecurityGroupID string, port int, protocol string, lbSecGroup string) error {
func toRuleProtocol(protocol v1.Protocol) rules.RuleProtocol {
switch protocol {
case v1.ProtocolTCP:
return rules.ProtocolTCP
case v1.ProtocolUDP:
return rules.ProtocolUDP
default:
return rules.RuleProtocol(strings.ToLower(string(protocol)))
}
}
func toListenersProtocol(protocol v1.Protocol) listeners.Protocol {
switch protocol {
case v1.ProtocolTCP:
return listeners.ProtocolTCP
default:
return listeners.Protocol(string(protocol))
}
}
func createNodeSecurityGroup(client *gophercloud.ServiceClient, nodeSecurityGroupID string, port int, protocol v1.Protocol, lbSecGroup string) error {
v4NodeSecGroupRuleCreateOpts := rules.CreateOpts{
Direction: "ingress",
Direction: rules.DirIngress,
PortRangeMax: port,
PortRangeMin: port,
Protocol: strings.ToLower(protocol),
Protocol: toRuleProtocol(protocol),
RemoteGroupID: lbSecGroup,
SecGroupID: nodeSecurityGroupID,
EtherType: "IPv4",
EtherType: rules.EtherType4,
}
v6NodeSecGroupRuleCreateOpts := rules.CreateOpts{
Direction: "ingress",
Direction: rules.DirIngress,
PortRangeMax: port,
PortRangeMin: port,
Protocol: strings.ToLower(protocol),
Protocol: toRuleProtocol(protocol),
RemoteGroupID: lbSecGroup,
SecGroupID: nodeSecurityGroupID,
EtherType: "IPv6",
EtherType: rules.EtherType6,
}
_, err := rules.Create(client, v4NodeSecGroupRuleCreateOpts).Extract()
@ -705,7 +725,7 @@ func (lbaas *LbaasV2) EnsureLoadBalancer(clusterName string, apiService *v1.Serv
if !memberExists(members, addr, int(port.NodePort)) {
glog.V(4).Infof("Creating member for pool %s", pool.ID)
_, err := v2pools.CreateAssociateMember(lbaas.network, pool.ID, v2pools.MemberCreateOpts{
_, err := v2pools.CreateMember(lbaas.network, pool.ID, v2pools.CreateMemberOpts{
ProtocolPort: int(port.NodePort),
Address: addr,
SubnetID: lbaas.opts.SubnetId,
@ -848,7 +868,7 @@ func (lbaas *LbaasV2) EnsureLoadBalancer(clusterName string, apiService *v1.Serv
for _, port := range ports {
for _, sourceRange := range sourceRanges.StringSlice() {
ethertype := "IPv4"
ethertype := rules.EtherType4
network, _, err := net.ParseCIDR(sourceRange)
if err != nil {
@ -859,14 +879,14 @@ func (lbaas *LbaasV2) EnsureLoadBalancer(clusterName string, apiService *v1.Serv
}
if network.To4() == nil {
ethertype = "IPv6"
ethertype = rules.EtherType6
}
lbSecGroupRuleCreateOpts := rules.CreateOpts{
Direction: "ingress",
Direction: rules.DirIngress,
PortRangeMax: int(port.Port),
PortRangeMin: int(port.Port),
Protocol: strings.ToLower(string(port.Protocol)),
Protocol: toRuleProtocol(port.Protocol),
RemoteIPPrefix: sourceRange,
SecGroupID: lbSecGroup.ID,
EtherType: ethertype,
@ -881,7 +901,7 @@ func (lbaas *LbaasV2) EnsureLoadBalancer(clusterName string, apiService *v1.Serv
}
}
err := createNodeSecurityGroup(lbaas.network, lbaas.opts.NodeSecurityGroupID, int(port.NodePort), string(port.Protocol), lbSecGroup.ID)
err := createNodeSecurityGroup(lbaas.network, lbaas.opts.NodeSecurityGroupID, int(port.NodePort), port.Protocol, lbSecGroup.ID)
if err != nil {
glog.Errorf("Error occured creating security group for loadbalancer %s:", loadbalancer.ID)
_ = lbaas.EnsureLoadBalancerDeleted(clusterName, apiService)
@ -890,13 +910,13 @@ func (lbaas *LbaasV2) EnsureLoadBalancer(clusterName string, apiService *v1.Serv
}
lbSecGroupRuleCreateOpts := rules.CreateOpts{
Direction: "ingress",
Direction: rules.DirIngress,
PortRangeMax: 4, // ICMP: Code - Values for ICMP "Destination Unreachable: Fragmentation Needed and Don't Fragment was Set"
PortRangeMin: 3, // ICMP: Type
Protocol: "icmp",
Protocol: rules.ProtocolICMP,
RemoteIPPrefix: "0.0.0.0/0", // The Fragmentation packet can come from anywhere along the path back to the sourceRange - we need to all this from all
SecGroupID: lbSecGroup.ID,
EtherType: "IPv4",
EtherType: rules.EtherType4,
}
_, err = rules.Create(lbaas.network, lbSecGroupRuleCreateOpts).Extract()
@ -908,13 +928,13 @@ func (lbaas *LbaasV2) EnsureLoadBalancer(clusterName string, apiService *v1.Serv
}
lbSecGroupRuleCreateOpts = rules.CreateOpts{
Direction: "ingress",
Direction: rules.DirIngress,
PortRangeMax: 0, // ICMP: Code - Values for ICMP "Packet Too Big"
PortRangeMin: 2, // ICMP: Type
Protocol: "icmp",
Protocol: rules.ProtocolICMP,
RemoteIPPrefix: "::/0", // The Fragmentation packet can come from anywhere along the path back to the sourceRange - we need to all this from all
SecGroupID: lbSecGroup.ID,
EtherType: "IPv6",
EtherType: rules.EtherType6,
}
_, err = rules.Create(lbaas.network, lbSecGroupRuleCreateOpts).Extract()
@ -968,7 +988,7 @@ func (lbaas *LbaasV2) UpdateLoadBalancer(clusterName string, service *v1.Service
// Get all listeners for this loadbalancer, by "port key".
type portKey struct {
Protocol string
Protocol listeners.Protocol
Port int
}
@ -983,7 +1003,7 @@ func (lbaas *LbaasV2) UpdateLoadBalancer(clusterName string, service *v1.Service
// Double check this Listener belongs to the LB we're updating. Neutron's API filtering
// can't be counted on in older releases (i.e Liberty).
if loadbalancer.ID == lb.ID {
key := portKey{Protocol: l.Protocol, Port: l.ProtocolPort}
key := portKey{Protocol: listeners.Protocol(l.Protocol), Port: l.ProtocolPort}
lbListeners[key] = l
break
}
@ -1034,7 +1054,7 @@ func (lbaas *LbaasV2) UpdateLoadBalancer(clusterName string, service *v1.Service
for _, port := range ports {
// Get listener associated with this port
listener, ok := lbListeners[portKey{
Protocol: string(port.Protocol),
Protocol: toListenersProtocol(port.Protocol),
Port: int(port.Port),
}]
if !ok {
@ -1049,7 +1069,7 @@ func (lbaas *LbaasV2) UpdateLoadBalancer(clusterName string, service *v1.Service
// Find existing pool members (by address) for this port
members := make(map[string]v2pools.Member)
err := v2pools.ListAssociateMembers(lbaas.network, pool.ID, v2pools.MemberListOpts{}).EachPage(func(page pagination.Page) (bool, error) {
err := v2pools.ListMembers(lbaas.network, pool.ID, v2pools.ListMembersOpts{}).EachPage(func(page pagination.Page) (bool, error) {
membersList, err := v2pools.ExtractMembers(page)
if err != nil {
return false, err
@ -1069,7 +1089,7 @@ func (lbaas *LbaasV2) UpdateLoadBalancer(clusterName string, service *v1.Service
// Already exists, do not create member
continue
}
_, err := v2pools.CreateAssociateMember(lbaas.network, pool.ID, v2pools.MemberCreateOpts{
_, err := v2pools.CreateMember(lbaas.network, pool.ID, v2pools.CreateMemberOpts{
Address: addr,
ProtocolPort: int(port.NodePort),
SubnetID: lbaas.opts.SubnetId,
@ -1167,7 +1187,7 @@ func (lbaas *LbaasV2) EnsureLoadBalancerDeleted(clusterName string, service *v1.
// get all members associated with each poolIDs
var memberIDs []string
for _, poolID := range poolIDs {
err := v2pools.ListAssociateMembers(lbaas.network, poolID, v2pools.MemberListOpts{}).EachPage(func(page pagination.Page) (bool, error) {
err := v2pools.ListMembers(lbaas.network, poolID, v2pools.ListMembersOpts{}).EachPage(func(page pagination.Page) (bool, error) {
membersList, err := v2pools.ExtractMembers(page)
if err != nil {
return false, err
@ -1338,7 +1358,7 @@ func (lb *LbaasV1) EnsureLoadBalancer(clusterName string, apiService *v1.Service
}
}
lbmethod := lb.opts.LBMethod
lbmethod := pools.LBMethod(lb.opts.LBMethod)
if lbmethod == "" {
lbmethod = pools.LBMethodRoundRobin
}

View File

@ -19,10 +19,10 @@ package openstack
import (
"errors"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/routers"
neutronports "github.com/rackspace/gophercloud/openstack/networking/v2/ports"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers"
neutronports "github.com/gophercloud/gophercloud/openstack/networking/v2/ports"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/types"

View File

@ -24,8 +24,8 @@ import (
"testing"
"time"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/rand"
@ -247,14 +247,13 @@ func configFromEnv() (cfg Config, ok bool) {
cfg.Global.Username = os.Getenv("OS_USERNAME")
cfg.Global.Password = os.Getenv("OS_PASSWORD")
cfg.Global.ApiKey = os.Getenv("OS_API_KEY")
cfg.Global.Region = os.Getenv("OS_REGION_NAME")
cfg.Global.DomainId = os.Getenv("OS_DOMAIN_ID")
cfg.Global.DomainName = os.Getenv("OS_DOMAIN_NAME")
ok = (cfg.Global.AuthUrl != "" &&
cfg.Global.Username != "" &&
(cfg.Global.Password != "" || cfg.Global.ApiKey != "") &&
cfg.Global.Password != "" &&
(cfg.Global.TenantId != "" || cfg.Global.TenantName != "" ||
cfg.Global.DomainId != "" || cfg.Global.DomainName != ""))

View File

@ -25,11 +25,11 @@ import (
"k8s.io/kubernetes/pkg/volume"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack"
"github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach"
"github.com/rackspace/gophercloud/pagination"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack"
"github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach"
"github.com/gophercloud/gophercloud/pagination"
"github.com/golang/glog"
)

View File

@ -23,8 +23,8 @@ import (
"strings"
"github.com/golang/glog"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack"
netutil "k8s.io/apimachinery/pkg/util/net"
"k8s.io/apiserver/pkg/authentication/user"

10422
vendor/BUILD vendored

File diff suppressed because it is too large Load Diff

1
vendor/github.com/gophercloud/gophercloud/.gitignore generated vendored Normal file
View File

@ -0,0 +1 @@
**/*.swp

17
vendor/github.com/gophercloud/gophercloud/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,17 @@
language: go
sudo: false
install:
- go get golang.org/x/crypto/ssh
- go get -v -tags 'fixtures acceptance' ./...
- go get github.com/wadey/gocovmerge
- go get github.com/mattn/goveralls
go:
- 1.7
- tip
env:
global:
- secure: "xSQsAG5wlL9emjbCdxzz/hYQsSpJ/bABO1kkbwMSISVcJ3Nk0u4ywF+LS4bgeOnwPfmFvNTOqVDu3RwEvMeWXSI76t1piCPcObutb2faKLVD/hLoAS76gYX+Z8yGWGHrSB7Do5vTPj1ERe2UljdrnsSeOXzoDwFxYRaZLX4bBOB4AyoGvRniil5QXPATiA1tsWX1VMicj8a4F8X+xeESzjt1Q5Iy31e7vkptu71bhvXCaoo5QhYwT+pLR9dN0S1b7Ro0KVvkRefmr1lUOSYd2e74h6Lc34tC1h3uYZCS4h47t7v5cOXvMNxinEj2C51RvbjvZI1RLVdkuAEJD1Iz4+Ote46nXbZ//6XRZMZz/YxQ13l7ux1PFjgEB6HAapmF5Xd8PRsgeTU9LRJxpiTJ3P5QJ3leS1va8qnziM5kYipj/Rn+V8g2ad/rgkRox9LSiR9VYZD2Pe45YCb1mTKSl2aIJnV7nkOqsShY5LNB4JZSg7xIffA+9YVDktw8dJlATjZqt7WvJJ49g6A61mIUV4C15q2JPGKTkZzDiG81NtmS7hFa7k0yaE2ELgYocbcuyUcAahhxntYTC0i23nJmEHVNiZmBO3u7EgpWe4KGVfumU+lt12tIn5b3dZRBBUk3QakKKozSK1QPHGpk/AZGrhu7H6l8to6IICKWtDcyMPQ="
script:
- ./script/coverage
after_success:
- $HOME/gopath/bin/goveralls -service=travis-ci -coverprofile=cover.out

View File

191
vendor/github.com/gophercloud/gophercloud/LICENSE generated vendored Normal file
View File

@ -0,0 +1,191 @@
Copyright 2012-2013 Rackspace, Inc.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
------
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

32
vendor/github.com/gophercloud/gophercloud/MIGRATING.md generated vendored Normal file
View File

@ -0,0 +1,32 @@
# Compute
## Floating IPs
* `github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingip` is now `github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips`
* `floatingips.Associate` and `floatingips.Disassociate` have been removed.
* `floatingips.DisassociateOpts` is now required to disassociate a Floating IP.
## Security Groups
* `secgroups.AddServerToGroup` is now `secgroups.AddServer`.
* `secgroups.RemoveServerFromGroup` is now `secgroups.RemoveServer`.
## Servers
* `servers.Reboot` now requires a `servers.RebootOpts` struct:
```golang
rebootOpts := &servers.RebootOpts{
Type: servers.SoftReboot,
}
res := servers.Reboot(client, server.ID, rebootOpts)
```
# Identity
## V3
### Tokens
* `Token.ExpiresAt` is now of type `gophercloud.JSONRFC3339Milli` instead of
`time.Time`

139
vendor/github.com/gophercloud/gophercloud/README.md generated vendored Normal file
View File

@ -0,0 +1,139 @@
# Gophercloud: an OpenStack SDK for Go
[![Build Status](https://travis-ci.org/gophercloud/gophercloud.svg?branch=master)](https://travis-ci.org/gophercloud/gophercloud)
[![Coverage Status](https://coveralls.io/repos/github/gophercloud/gophercloud/badge.svg?branch=master)](https://coveralls.io/github/gophercloud/gophercloud?branch=master)
Gophercloud is an OpenStack Go SDK.
## Useful links
* [Reference documentation](http://godoc.org/github.com/gophercloud/gophercloud)
* [Effective Go](https://golang.org/doc/effective_go.html)
## How to install
Before installing, you need to ensure that your [GOPATH environment variable](https://golang.org/doc/code.html#GOPATH)
is pointing to an appropriate directory where you want to install Gophercloud:
```bash
mkdir $HOME/go
export GOPATH=$HOME/go
```
To protect yourself against changes in your dependencies, we highly recommend choosing a
[dependency management solution](https://github.com/golang/go/wiki/PackageManagementTools) for
your projects, such as [godep](https://github.com/tools/godep). Once this is set up, you can install
Gophercloud as a dependency like so:
```bash
go get github.com/gophercloud/gophercloud
# Edit your code to import relevant packages from "github.com/gophercloud/gophercloud"
godep save ./...
```
This will install all the source files you need into a `Godeps/_workspace` directory, which is
referenceable from your own source files when you use the `godep go` command.
## Getting started
### Credentials
Because you'll be hitting an API, you will need to retrieve your OpenStack
credentials and either store them as environment variables or in your local Go
files. The first method is recommended because it decouples credential
information from source code, allowing you to push the latter to your version
control system without any security risk.
You will need to retrieve the following:
* username
* password
* a valid Keystone identity URL
For users that have the OpenStack dashboard installed, there's a shortcut. If
you visit the `project/access_and_security` path in Horizon and click on the
"Download OpenStack RC File" button at the top right hand corner, you will
download a bash file that exports all of your access details to environment
variables. To execute the file, run `source admin-openrc.sh` and you will be
prompted for your password.
### Authentication
Once you have access to your credentials, you can begin plugging them into
Gophercloud. The next step is authentication, and this is handled by a base
"Provider" struct. To get one, you can either pass in your credentials
explicitly, or tell Gophercloud to use environment variables:
```go
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack"
"github.com/gophercloud/gophercloud/openstack/utils"
)
// Option 1: Pass in the values yourself
opts := gophercloud.AuthOptions{
IdentityEndpoint: "https://my-openstack.com:5000/v2.0",
Username: "{username}",
Password: "{password}",
}
// Option 2: Use a utility function to retrieve all your environment variables
opts, err := openstack.AuthOptionsFromEnv()
```
Once you have the `opts` variable, you can pass it in and get back a
`ProviderClient` struct:
```go
provider, err := openstack.AuthenticatedClient(opts)
```
The `ProviderClient` is the top-level client that all of your OpenStack services
derive from. The provider contains all of the authentication details that allow
your Go code to access the API - such as the base URL and token ID.
### Provision a server
Once we have a base Provider, we inject it as a dependency into each OpenStack
service. In order to work with the Compute API, we need a Compute service
client; which can be created like so:
```go
client, err := openstack.NewComputeV2(provider, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
```
We then use this `client` for any Compute API operation we want. In our case,
we want to provision a new server - so we invoke the `Create` method and pass
in the flavor ID (hardware specification) and image ID (operating system) we're
interested in:
```go
import "github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
server, err := servers.Create(client, servers.CreateOpts{
Name: "My new server!",
FlavorRef: "flavor_id",
ImageRef: "image_id",
}).Extract()
```
The above code sample creates a new server with the parameters, and embodies the
new resource in the `server` variable (a
[`servers.Server`](http://godoc.org/github.com/gophercloud/gophercloud) struct).
## Backwards-Compatibility Guarantees
None. Vendor it and write tests covering the parts you use.
## Contributing
See the [contributing guide](./.github/CONTRIBUTING.md).
## Help and feedback
If you're struggling with something or have spotted a potential bug, feel free
to submit an issue to our [bug tracker](/issues).

View File

@ -0,0 +1,68 @@
## On Pull Requests
- Before you start a PR there needs to be a Github issue and a discussion about it
on that issue with a core contributor, even if it's just a 'SGTM'.
- A PR's description must reference the issue it closes with a `For <ISSUE NUMBER>` (e.g. For #293).
- A PR's description must contain link(s) to the line(s) in the OpenStack
source code (on Github) that prove(s) the PR code to be valid. Links to documentation
are not good enough. The link(s) should be to a non-`master` branch. For example,
a pull request implementing the creation of a Neutron v2 subnet might put the
following link in the description:
https://github.com/openstack/neutron/blob/stable/mitaka/neutron/api/v2/attributes.py#L749
From that link, a reviewer (or user) can verify the fields in the request/response
objects in the PR.
- A PR that is in-progress should have `[wip]` in front of the PR's title. When
ready for review, remove the `[wip]` and ping a core contributor with an `@`.
- A PR should be small. Even if you intend on implementing an entire
service, a PR should only be one route of that service
(e.g. create server or get server, but not both).
- Unless explicitly asked, do not squash commits in the middle of a review; only
append. It makes it difficult for the reviewer to see what's changed from one
review to the next.
## On Code
- In re design: follow as closely as is reasonable the code already in the library.
Most operations (e.g. create, delete) admit the same design.
- Unit tests and acceptance (integration) tests must be written to cover each PR.
Tests for operations with several options (e.g. list, create) should include all
the options in the tests. This will allow users to verify an operation on their
own infrastructure and see an example of usage.
- If in doubt, ask in-line on the PR.
### File Structure
- The following should be used in most cases:
- `requests.go`: contains all the functions that make HTTP requests and the
types associated with the HTTP request (parameters for URL, body, etc)
- `results.go`: contains all the response objects and their methods
- `urls.go`: contains the endpoints to which the requests are made
### Naming
- For methods on a type in `response.go`, the receiver should be named `r` and the
variable into which it will be unmarshalled `s`.
- Functions in `requests.go`, with the exception of functions that return a
`pagination.Pager`, should be named returns of the name `r`.
- Functions in `requests.go` that accept request bodies should accept as their
last parameter an `interface` named `<Action>OptsBuilder` (eg `CreateOptsBuilder`).
This `interface` should have at the least a method named `To<Resource><Action>Map`
(eg `ToPortCreateMap`).
- Functions in `requests.go` that accept query strings should accept as their
last parameter an `interface` named `<Action>OptsBuilder` (eg `ListOptsBuilder`).
This `interface` should have at the least a method named `To<Resource><Action>Query`
(eg `ToServerListQuery`).

View File

@ -0,0 +1,331 @@
package gophercloud
/*
AuthOptions stores information needed to authenticate to an OpenStack cluster.
You can populate one manually, or use a provider's AuthOptionsFromEnv() function
to read relevant information from the standard environment variables. Pass one
to a provider's AuthenticatedClient function to authenticate and obtain a
ProviderClient representing an active session on that provider.
Its fields are the union of those recognized by each identity implementation and
provider.
*/
type AuthOptions struct {
// IdentityEndpoint specifies the HTTP endpoint that is required to work with
// the Identity API of the appropriate version. While it's ultimately needed by
// all of the identity services, it will often be populated by a provider-level
// function.
IdentityEndpoint string `json:"-"`
// Username is required if using Identity V2 API. Consult with your provider's
// control panel to discover your account's username. In Identity V3, either
// UserID or a combination of Username and DomainID or DomainName are needed.
Username string `json:"username,omitempty"`
UserID string `json:"id,omitempty"`
Password string `json:"password,omitempty"`
// At most one of DomainID and DomainName must be provided if using Username
// with Identity V3. Otherwise, either are optional.
DomainID string `json:"id,omitempty"`
DomainName string `json:"name,omitempty"`
// The TenantID and TenantName fields are optional for the Identity V2 API.
// Some providers allow you to specify a TenantName instead of the TenantId.
// Some require both. Your provider's authentication policies will determine
// how these fields influence authentication.
TenantID string `json:"tenantId,omitempty"`
TenantName string `json:"tenantName,omitempty"`
// AllowReauth should be set to true if you grant permission for Gophercloud to
// cache your credentials in memory, and to allow Gophercloud to attempt to
// 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 `json:"-"`
// TokenID allows users to authenticate (possibly as another user) with an
// authentication token ID.
TokenID string `json:"-"`
}
// ToTokenV2CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder
// interface in the v2 tokens package
func (opts AuthOptions) ToTokenV2CreateMap() (map[string]interface{}, error) {
// Populate the request map.
authMap := make(map[string]interface{})
if opts.Username != "" {
if opts.Password != "" {
authMap["passwordCredentials"] = map[string]interface{}{
"username": opts.Username,
"password": opts.Password,
}
} else {
return nil, ErrMissingInput{Argument: "Password"}
}
} else if opts.TokenID != "" {
authMap["token"] = map[string]interface{}{
"id": opts.TokenID,
}
} else {
return nil, ErrMissingInput{Argument: "Username"}
}
if opts.TenantID != "" {
authMap["tenantId"] = opts.TenantID
}
if opts.TenantName != "" {
authMap["tenantName"] = opts.TenantName
}
return map[string]interface{}{"auth": authMap}, nil
}
func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) {
type domainReq struct {
ID *string `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
}
type projectReq struct {
Domain *domainReq `json:"domain,omitempty"`
Name *string `json:"name,omitempty"`
ID *string `json:"id,omitempty"`
}
type userReq struct {
ID *string `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
Password string `json:"password"`
Domain *domainReq `json:"domain,omitempty"`
}
type passwordReq struct {
User userReq `json:"user"`
}
type tokenReq struct {
ID string `json:"id"`
}
type identityReq struct {
Methods []string `json:"methods"`
Password *passwordReq `json:"password,omitempty"`
Token *tokenReq `json:"token,omitempty"`
}
type authReq struct {
Identity identityReq `json:"identity"`
}
type request struct {
Auth authReq `json:"auth"`
}
// Populate the request structure based on the provided arguments. Create and return an error
// if insufficient or incompatible information is present.
var req request
// Test first for unrecognized arguments.
if opts.TenantID != "" {
return nil, ErrTenantIDProvided{}
}
if opts.TenantName != "" {
return nil, ErrTenantNameProvided{}
}
if opts.Password == "" {
if opts.TokenID != "" {
// Because we aren't using password authentication, it's an error to also provide any of the user-based authentication
// parameters.
if opts.Username != "" {
return nil, ErrUsernameWithToken{}
}
if opts.UserID != "" {
return nil, ErrUserIDWithToken{}
}
if opts.DomainID != "" {
return nil, ErrDomainIDWithToken{}
}
if opts.DomainName != "" {
return nil, ErrDomainNameWithToken{}
}
// Configure the request for Token authentication.
req.Auth.Identity.Methods = []string{"token"}
req.Auth.Identity.Token = &tokenReq{
ID: opts.TokenID,
}
} else {
// If no password or token ID are available, authentication can't continue.
return nil, ErrMissingPassword{}
}
} else {
// Password authentication.
req.Auth.Identity.Methods = []string{"password"}
// At least one of Username and UserID must be specified.
if opts.Username == "" && opts.UserID == "" {
return nil, ErrUsernameOrUserID{}
}
if opts.Username != "" {
// If Username is provided, UserID may not be provided.
if opts.UserID != "" {
return nil, ErrUsernameOrUserID{}
}
// Either DomainID or DomainName must also be specified.
if opts.DomainID == "" && opts.DomainName == "" {
return nil, ErrDomainIDOrDomainName{}
}
if opts.DomainID != "" {
if opts.DomainName != "" {
return nil, ErrDomainIDOrDomainName{}
}
// Configure the request for Username and Password authentication with a DomainID.
req.Auth.Identity.Password = &passwordReq{
User: userReq{
Name: &opts.Username,
Password: opts.Password,
Domain: &domainReq{ID: &opts.DomainID},
},
}
}
if opts.DomainName != "" {
// Configure the request for Username and Password authentication with a DomainName.
req.Auth.Identity.Password = &passwordReq{
User: userReq{
Name: &opts.Username,
Password: opts.Password,
Domain: &domainReq{Name: &opts.DomainName},
},
}
}
}
if opts.UserID != "" {
// If UserID is specified, neither DomainID nor DomainName may be.
if opts.DomainID != "" {
return nil, ErrDomainIDWithUserID{}
}
if opts.DomainName != "" {
return nil, ErrDomainNameWithUserID{}
}
// Configure the request for UserID and Password authentication.
req.Auth.Identity.Password = &passwordReq{
User: userReq{ID: &opts.UserID, Password: opts.Password},
}
}
}
b, err := BuildRequestBody(req, "")
if err != nil {
return nil, err
}
if len(scope) != 0 {
b["auth"].(map[string]interface{})["scope"] = scope
}
return b, nil
}
func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
var scope struct {
ProjectID string
ProjectName string
DomainID string
DomainName string
}
if opts.TenantID != "" {
scope.ProjectID = opts.TenantID
opts.TenantID = ""
opts.TenantName = ""
} else {
if opts.TenantName != "" {
scope.ProjectName = opts.TenantName
scope.DomainID = opts.DomainID
scope.DomainName = opts.DomainName
}
opts.TenantName = ""
}
if scope.ProjectName != "" {
// ProjectName provided: either DomainID or DomainName must also be supplied.
// ProjectID may not be supplied.
if scope.DomainID == "" && scope.DomainName == "" {
return nil, ErrScopeDomainIDOrDomainName{}
}
if scope.ProjectID != "" {
return nil, ErrScopeProjectIDOrProjectName{}
}
if scope.DomainID != "" {
// ProjectName + DomainID
return map[string]interface{}{
"project": map[string]interface{}{
"name": &scope.ProjectName,
"domain": map[string]interface{}{"id": &scope.DomainID},
},
}, nil
}
if scope.DomainName != "" {
// ProjectName + DomainName
return map[string]interface{}{
"project": map[string]interface{}{
"name": &scope.ProjectName,
"domain": map[string]interface{}{"name": &scope.DomainName},
},
}, nil
}
} else if scope.ProjectID != "" {
// ProjectID provided. ProjectName, DomainID, and DomainName may not be provided.
if scope.DomainID != "" {
return nil, ErrScopeProjectIDAlone{}
}
if scope.DomainName != "" {
return nil, ErrScopeProjectIDAlone{}
}
// ProjectID
return map[string]interface{}{
"project": map[string]interface{}{
"id": &scope.ProjectID,
},
}, nil
} else if scope.DomainID != "" {
// DomainID provided. ProjectID, ProjectName, and DomainName may not be provided.
if scope.DomainName != "" {
return nil, ErrScopeDomainIDOrDomainName{}
}
// DomainID
return map[string]interface{}{
"domain": map[string]interface{}{
"id": &scope.DomainID,
},
}, nil
} else if scope.DomainName != "" {
return nil, ErrScopeDomainName{}
}
return nil, nil
}
func (opts AuthOptions) CanReauth() bool {
return opts.AllowReauth
}

67
vendor/github.com/gophercloud/gophercloud/doc.go generated vendored Normal file
View File

@ -0,0 +1,67 @@
/*
Package gophercloud provides a multi-vendor interface to OpenStack-compatible
clouds. The library has a three-level hierarchy: providers, services, and
resources.
Provider structs represent the service providers that offer and manage a
collection of services. Examples of providers include: OpenStack, Rackspace,
HP. These are defined like so:
opts := gophercloud.AuthOptions{
IdentityEndpoint: "https://my-openstack.com:5000/v2.0",
Username: "{username}",
Password: "{password}",
TenantID: "{tenant_id}",
}
provider, err := openstack.AuthenticatedClient(opts)
Service structs are specific to a provider and handle all of the logic and
operations for a particular OpenStack service. Examples of services include:
Compute, Object Storage, Block Storage. In order to define one, you need to
pass in the parent provider, like so:
opts := gophercloud.EndpointOpts{Region: "RegionOne"}
client := openstack.NewComputeV2(provider, opts)
Resource structs are the domain models that services make use of in order
to work with and represent the state of API resources:
server, err := servers.Get(client, "{serverId}").Extract()
Intermediate Result structs are returned for API operations, which allow
generic access to the HTTP headers, response body, and any errors associated
with the network transaction. To turn a result into a usable resource struct,
you must call the Extract method which is chained to the response, or an
Extract function from an applicable extension:
result := servers.Get(client, "{serverId}")
// Attempt to extract the disk configuration from the OS-DCF disk config
// extension:
config, err := diskconfig.ExtractGet(result)
All requests that enumerate a collection return a Pager struct that is used to
iterate through the results one page at a time. Use the EachPage method on that
Pager to handle each successive Page in a closure, then use the appropriate
extraction method from that request's package to interpret that Page as a slice
of results:
err := servers.List(client, nil).EachPage(func (page pagination.Page) (bool, error) {
s, err := servers.ExtractServers(page)
if err != nil {
return false, err
}
// Handle the []servers.Server slice.
// Return "false" or an error to prematurely stop fetching new pages.
return true, nil
})
This top-level package contains utility functions and data types that are used
throughout the provider and service packages. Of particular note for end users
are the AuthOptions and EndpointOpts structs.
*/
package gophercloud

View File

@ -0,0 +1,76 @@
package gophercloud
// Availability indicates to whom a specific service endpoint is accessible:
// the internet at large, internal networks only, or only to administrators.
// Different identity services use different terminology for these. Identity v2
// lists them as different kinds of URLs within the service catalog ("adminURL",
// "internalURL", and "publicURL"), while v3 lists them as "Interfaces" in an
// endpoint's response.
type Availability string
const (
// AvailabilityAdmin indicates that an endpoint is only available to
// administrators.
AvailabilityAdmin Availability = "admin"
// AvailabilityPublic indicates that an endpoint is available to everyone on
// the internet.
AvailabilityPublic Availability = "public"
// AvailabilityInternal indicates that an endpoint is only available within
// the cluster's internal network.
AvailabilityInternal Availability = "internal"
)
// EndpointOpts specifies search criteria used by queries against an
// OpenStack service catalog. The options must contain enough information to
// unambiguously identify one, and only one, endpoint within the catalog.
//
// Usually, these are passed to service client factory functions in a provider
// package, like "rackspace.NewComputeV2()".
type EndpointOpts struct {
// Type [required] is the service type for the client (e.g., "compute",
// "object-store"). Generally, this will be supplied by the service client
// function, but a user-given value will be honored if provided.
Type string
// Name [optional] is the service name for the client (e.g., "nova") as it
// appears in the service catalog. Services can have the same Type but a
// different Name, which is why both Type and Name are sometimes needed.
Name string
// Region [required] is the geographic region in which the endpoint resides,
// generally specifying which datacenter should house your resources.
// Required only for services that span multiple regions.
Region string
// Availability [optional] is the visibility of the endpoint to be returned.
// Valid types include the constants AvailabilityPublic, AvailabilityInternal,
// or AvailabilityAdmin from this package.
//
// Availability is not required, and defaults to AvailabilityPublic. Not all
// providers or services offer all Availability options.
Availability Availability
}
/*
EndpointLocator is an internal function to be used by provider implementations.
It provides an implementation that locates a single endpoint from a service
catalog for a specific ProviderClient based on user-provided EndpointOpts. The
provider then uses it to discover related ServiceClients.
*/
type EndpointLocator func(EndpointOpts) (string, error)
// ApplyDefaults is an internal method to be used by provider implementations.
//
// It sets EndpointOpts fields if not already set, including a default type.
// Currently, EndpointOpts.Availability defaults to the public endpoint.
func (eo *EndpointOpts) ApplyDefaults(t string) {
if eo.Type == "" {
eo.Type = t
}
if eo.Availability == "" {
eo.Availability = AvailabilityPublic
}
}

408
vendor/github.com/gophercloud/gophercloud/errors.go generated vendored Normal file
View File

@ -0,0 +1,408 @@
package gophercloud
import "fmt"
// BaseError is an error type that all other error types embed.
type BaseError struct {
DefaultErrString string
Info string
}
func (e BaseError) Error() string {
e.DefaultErrString = "An error occurred while executing a Gophercloud request."
return e.choseErrString()
}
func (e BaseError) choseErrString() string {
if e.Info != "" {
return e.Info
}
return e.DefaultErrString
}
// ErrMissingInput is the error when input is required in a particular
// situation but not provided by the user
type ErrMissingInput struct {
BaseError
Argument string
}
func (e ErrMissingInput) Error() string {
e.DefaultErrString = fmt.Sprintf("Missing input for argument [%s]", e.Argument)
return e.choseErrString()
}
// ErrInvalidInput is an error type used for most non-HTTP Gophercloud errors.
type ErrInvalidInput struct {
ErrMissingInput
Value interface{}
}
func (e ErrInvalidInput) Error() string {
e.DefaultErrString = fmt.Sprintf("Invalid input provided for argument [%s]: [%+v]", e.Argument, e.Value)
return e.choseErrString()
}
// ErrUnexpectedResponseCode is returned by the Request method when a response code other than
// those listed in OkCodes is encountered.
type ErrUnexpectedResponseCode struct {
BaseError
URL string
Method string
Expected []int
Actual int
Body []byte
}
func (e ErrUnexpectedResponseCode) Error() string {
e.DefaultErrString = fmt.Sprintf(
"Expected HTTP response code %v when accessing [%s %s], but got %d instead\n%s",
e.Expected, e.Method, e.URL, e.Actual, e.Body,
)
return e.choseErrString()
}
// ErrDefault400 is the default error type returned on a 400 HTTP response code.
type ErrDefault400 struct {
ErrUnexpectedResponseCode
}
// ErrDefault401 is the default error type returned on a 401 HTTP response code.
type ErrDefault401 struct {
ErrUnexpectedResponseCode
}
// ErrDefault404 is the default error type returned on a 404 HTTP response code.
type ErrDefault404 struct {
ErrUnexpectedResponseCode
}
// ErrDefault405 is the default error type returned on a 405 HTTP response code.
type ErrDefault405 struct {
ErrUnexpectedResponseCode
}
// ErrDefault408 is the default error type returned on a 408 HTTP response code.
type ErrDefault408 struct {
ErrUnexpectedResponseCode
}
// ErrDefault429 is the default error type returned on a 429 HTTP response code.
type ErrDefault429 struct {
ErrUnexpectedResponseCode
}
// ErrDefault500 is the default error type returned on a 500 HTTP response code.
type ErrDefault500 struct {
ErrUnexpectedResponseCode
}
// ErrDefault503 is the default error type returned on a 503 HTTP response code.
type ErrDefault503 struct {
ErrUnexpectedResponseCode
}
func (e ErrDefault400) Error() string {
return "Invalid request due to incorrect syntax or missing required parameters."
}
func (e ErrDefault401) Error() string {
return "Authentication failed"
}
func (e ErrDefault404) Error() string {
return "Resource not found"
}
func (e ErrDefault405) Error() string {
return "Method not allowed"
}
func (e ErrDefault408) Error() string {
return "The server timed out waiting for the request"
}
func (e ErrDefault429) Error() string {
return "Too many requests have been sent in a given amount of time. Pause" +
" requests, wait up to one minute, and try again."
}
func (e ErrDefault500) Error() string {
return "Internal Server Error"
}
func (e ErrDefault503) Error() string {
return "The service is currently unable to handle the request due to a temporary" +
" overloading or maintenance. This is a temporary condition. Try again later."
}
// Err400er is the interface resource error types implement to override the error message
// from a 400 error.
type Err400er interface {
Error400(ErrUnexpectedResponseCode) error
}
// Err401er is the interface resource error types implement to override the error message
// from a 401 error.
type Err401er interface {
Error401(ErrUnexpectedResponseCode) error
}
// Err404er is the interface resource error types implement to override the error message
// from a 404 error.
type Err404er interface {
Error404(ErrUnexpectedResponseCode) error
}
// Err405er is the interface resource error types implement to override the error message
// from a 405 error.
type Err405er interface {
Error405(ErrUnexpectedResponseCode) error
}
// Err408er is the interface resource error types implement to override the error message
// from a 408 error.
type Err408er interface {
Error408(ErrUnexpectedResponseCode) error
}
// Err429er is the interface resource error types implement to override the error message
// from a 429 error.
type Err429er interface {
Error429(ErrUnexpectedResponseCode) error
}
// Err500er is the interface resource error types implement to override the error message
// from a 500 error.
type Err500er interface {
Error500(ErrUnexpectedResponseCode) error
}
// Err503er is the interface resource error types implement to override the error message
// from a 503 error.
type Err503er interface {
Error503(ErrUnexpectedResponseCode) error
}
// ErrTimeOut is the error type returned when an operations times out.
type ErrTimeOut struct {
BaseError
}
func (e ErrTimeOut) Error() string {
e.DefaultErrString = "A time out occurred"
return e.choseErrString()
}
// ErrUnableToReauthenticate is the error type returned when reauthentication fails.
type ErrUnableToReauthenticate struct {
BaseError
ErrOriginal error
}
func (e ErrUnableToReauthenticate) Error() string {
e.DefaultErrString = fmt.Sprintf("Unable to re-authenticate: %s", e.ErrOriginal)
return e.choseErrString()
}
// ErrErrorAfterReauthentication is the error type returned when reauthentication
// succeeds, but an error occurs afterword (usually an HTTP error).
type ErrErrorAfterReauthentication struct {
BaseError
ErrOriginal error
}
func (e ErrErrorAfterReauthentication) Error() string {
e.DefaultErrString = fmt.Sprintf("Successfully re-authenticated, but got error executing request: %s", e.ErrOriginal)
return e.choseErrString()
}
// ErrServiceNotFound is returned when no service in a service catalog matches
// the provided EndpointOpts. This is generally returned by provider service
// factory methods like "NewComputeV2()" and can mean that a service is not
// enabled for your account.
type ErrServiceNotFound struct {
BaseError
}
func (e ErrServiceNotFound) Error() string {
e.DefaultErrString = "No suitable service could be found in the service catalog."
return e.choseErrString()
}
// ErrEndpointNotFound is returned when no available endpoints match the
// provided EndpointOpts. This is also generally returned by provider service
// factory methods, and usually indicates that a region was specified
// incorrectly.
type ErrEndpointNotFound struct {
BaseError
}
func (e ErrEndpointNotFound) Error() string {
e.DefaultErrString = "No suitable endpoint could be found in the service catalog."
return e.choseErrString()
}
// ErrResourceNotFound is the error when trying to retrieve a resource's
// ID by name and the resource doesn't exist.
type ErrResourceNotFound struct {
BaseError
Name string
ResourceType string
}
func (e ErrResourceNotFound) Error() string {
e.DefaultErrString = fmt.Sprintf("Unable to find %s with name %s", e.ResourceType, e.Name)
return e.choseErrString()
}
// ErrMultipleResourcesFound is the error when trying to retrieve a resource's
// ID by name and multiple resources have the user-provided name.
type ErrMultipleResourcesFound struct {
BaseError
Name string
Count int
ResourceType string
}
func (e ErrMultipleResourcesFound) Error() string {
e.DefaultErrString = fmt.Sprintf("Found %d %ss matching %s", e.Count, e.ResourceType, e.Name)
return e.choseErrString()
}
// ErrUnexpectedType is the error when an unexpected type is encountered
type ErrUnexpectedType struct {
BaseError
Expected string
Actual string
}
func (e ErrUnexpectedType) Error() string {
e.DefaultErrString = fmt.Sprintf("Expected %s but got %s", e.Expected, e.Actual)
return e.choseErrString()
}
func unacceptedAttributeErr(attribute string) string {
return fmt.Sprintf("The base Identity V3 API does not accept authentication by %s", attribute)
}
func redundantWithTokenErr(attribute string) string {
return fmt.Sprintf("%s may not be provided when authenticating with a TokenID", attribute)
}
func redundantWithUserID(attribute string) string {
return fmt.Sprintf("%s may not be provided when authenticating with a UserID", attribute)
}
// ErrAPIKeyProvided indicates that an APIKey was provided but can't be used.
type ErrAPIKeyProvided struct{ BaseError }
func (e ErrAPIKeyProvided) Error() string {
return unacceptedAttributeErr("APIKey")
}
// ErrTenantIDProvided indicates that a TenantID was provided but can't be used.
type ErrTenantIDProvided struct{ BaseError }
func (e ErrTenantIDProvided) Error() string {
return unacceptedAttributeErr("TenantID")
}
// ErrTenantNameProvided indicates that a TenantName was provided but can't be used.
type ErrTenantNameProvided struct{ BaseError }
func (e ErrTenantNameProvided) Error() string {
return unacceptedAttributeErr("TenantName")
}
// ErrUsernameWithToken indicates that a Username was provided, but token authentication is being used instead.
type ErrUsernameWithToken struct{ BaseError }
func (e ErrUsernameWithToken) Error() string {
return redundantWithTokenErr("Username")
}
// ErrUserIDWithToken indicates that a UserID was provided, but token authentication is being used instead.
type ErrUserIDWithToken struct{ BaseError }
func (e ErrUserIDWithToken) Error() string {
return redundantWithTokenErr("UserID")
}
// ErrDomainIDWithToken indicates that a DomainID was provided, but token authentication is being used instead.
type ErrDomainIDWithToken struct{ BaseError }
func (e ErrDomainIDWithToken) Error() string {
return redundantWithTokenErr("DomainID")
}
// ErrDomainNameWithToken indicates that a DomainName was provided, but token authentication is being used instead.s
type ErrDomainNameWithToken struct{ BaseError }
func (e ErrDomainNameWithToken) Error() string {
return redundantWithTokenErr("DomainName")
}
// ErrUsernameOrUserID indicates that neither username nor userID are specified, or both are at once.
type ErrUsernameOrUserID struct{ BaseError }
func (e ErrUsernameOrUserID) Error() string {
return "Exactly one of Username and UserID must be provided for password authentication"
}
// ErrDomainIDWithUserID indicates that a DomainID was provided, but unnecessary because a UserID is being used.
type ErrDomainIDWithUserID struct{ BaseError }
func (e ErrDomainIDWithUserID) Error() string {
return redundantWithUserID("DomainID")
}
// ErrDomainNameWithUserID indicates that a DomainName was provided, but unnecessary because a UserID is being used.
type ErrDomainNameWithUserID struct{ BaseError }
func (e ErrDomainNameWithUserID) Error() string {
return redundantWithUserID("DomainName")
}
// ErrDomainIDOrDomainName indicates that a username was provided, but no domain to scope it.
// It may also indicate that both a DomainID and a DomainName were provided at once.
type ErrDomainIDOrDomainName struct{ BaseError }
func (e ErrDomainIDOrDomainName) Error() string {
return "You must provide exactly one of DomainID or DomainName to authenticate by Username"
}
// ErrMissingPassword indicates that no password was provided and no token is available.
type ErrMissingPassword struct{ BaseError }
func (e ErrMissingPassword) Error() string {
return "You must provide a password to authenticate"
}
// ErrScopeDomainIDOrDomainName indicates that a domain ID or Name was required in a Scope, but not present.
type ErrScopeDomainIDOrDomainName struct{ BaseError }
func (e ErrScopeDomainIDOrDomainName) Error() string {
return "You must provide exactly one of DomainID or DomainName in a Scope with ProjectName"
}
// ErrScopeProjectIDOrProjectName indicates that both a ProjectID and a ProjectName were provided in a Scope.
type ErrScopeProjectIDOrProjectName struct{ BaseError }
func (e ErrScopeProjectIDOrProjectName) Error() string {
return "You must provide at most one of ProjectID or ProjectName in a Scope"
}
// ErrScopeProjectIDAlone indicates that a ProjectID was provided with other constraints in a Scope.
type ErrScopeProjectIDAlone struct{ BaseError }
func (e ErrScopeProjectIDAlone) Error() string {
return "ProjectID must be supplied alone in a Scope"
}
// ErrScopeDomainName indicates that a DomainName was provided alone in a Scope.
type ErrScopeDomainName struct{ BaseError }
func (e ErrScopeDomainName) Error() string {
return "DomainName must be supplied with a ProjectName or ProjectID in a Scope"
}
// ErrScopeEmpty indicates that no credentials were provided in a Scope.
type ErrScopeEmpty struct{ BaseError }
func (e ErrScopeEmpty) Error() string {
return "You must provide either a Project or Domain in a Scope"
}

View File

@ -0,0 +1,52 @@
package openstack
import (
"os"
"github.com/gophercloud/gophercloud"
)
var nilOptions = gophercloud.AuthOptions{}
// AuthOptionsFromEnv fills out an identity.AuthOptions structure with the settings found on the various OpenStack
// OS_* environment variables. The following variables provide sources of truth: OS_AUTH_URL, OS_USERNAME,
// OS_PASSWORD, OS_TENANT_ID, and OS_TENANT_NAME. Of these, OS_USERNAME, OS_PASSWORD, and OS_AUTH_URL must
// have settings, or an error will result. OS_TENANT_ID and OS_TENANT_NAME are optional.
func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) {
authURL := os.Getenv("OS_AUTH_URL")
username := os.Getenv("OS_USERNAME")
userID := os.Getenv("OS_USERID")
password := os.Getenv("OS_PASSWORD")
tenantID := os.Getenv("OS_TENANT_ID")
tenantName := os.Getenv("OS_TENANT_NAME")
domainID := os.Getenv("OS_DOMAIN_ID")
domainName := os.Getenv("OS_DOMAIN_NAME")
if authURL == "" {
err := gophercloud.ErrMissingInput{Argument: "authURL"}
return nilOptions, err
}
if username == "" && userID == "" {
err := gophercloud.ErrMissingInput{Argument: "username"}
return nilOptions, err
}
if password == "" {
err := gophercloud.ErrMissingInput{Argument: "password"}
return nilOptions, err
}
ao := gophercloud.AuthOptions{
IdentityEndpoint: authURL,
UserID: userID,
Username: username,
Password: password,
TenantID: tenantID,
TenantName: tenantName,
DomainID: domainID,
DomainName: domainName,
}
return ao, nil
}

View File

@ -0,0 +1,5 @@
// Package volumes provides information and interaction with volumes in the
// OpenStack Block Storage service. A volume is a detachable block storage
// device, akin to a USB hard drive. It can only be attached to one instance at
// a time.
package volumes

View File

@ -0,0 +1,167 @@
package volumes
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// CreateOptsBuilder allows extensions to add additional parameters to the
// Create request.
type CreateOptsBuilder interface {
ToVolumeCreateMap() (map[string]interface{}, error)
}
// CreateOpts contains options for creating a Volume. This object is passed to
// the volumes.Create function. For more information about these parameters,
// see the Volume object.
type CreateOpts struct {
Size int `json:"size" required:"true"`
Availability string `json:"availability,omitempty"`
Description string `json:"display_description,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
Name string `json:"display_name,omitempty"`
SnapshotID string `json:"snapshot_id,omitempty"`
SourceVolID string `json:"source_volid,omitempty"`
ImageID string `json:"imageRef,omitempty"`
VolumeType string `json:"volume_type,omitempty"`
}
// ToVolumeCreateMap assembles a request body based on the contents of a
// CreateOpts.
func (opts CreateOpts) ToVolumeCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "volume")
}
// Create will create a new Volume based on the values in CreateOpts. To extract
// the Volume object from the response, call the Extract method on the
// CreateResult.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToVolumeCreateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 201},
})
return
}
// Delete will delete the existing Volume with the provided ID.
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, id), nil)
return
}
// Get retrieves the Volume with the provided ID. To extract the Volume object
// from the response, call the Extract method on the GetResult.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
return
}
// ListOptsBuilder allows extensions to add additional parameters to the List
// request.
type ListOptsBuilder interface {
ToVolumeListQuery() (string, error)
}
// ListOpts holds options for listing Volumes. It is passed to the volumes.List
// function.
type ListOpts struct {
// admin-only option. Set it to true to see all tenant volumes.
AllTenants bool `q:"all_tenants"`
// List only volumes that contain Metadata.
Metadata map[string]string `q:"metadata"`
// List only volumes that have Name as the display name.
Name string `q:"display_name"`
// List only volumes that have a status of Status.
Status string `q:"status"`
}
// ToVolumeListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToVolumeListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
return q.String(), err
}
// List returns Volumes optionally limited by the conditions provided in ListOpts.
func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
url := listURL(client)
if opts != nil {
query, err := opts.ToVolumeListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
}
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
return VolumePage{pagination.SinglePageBase(r)}
})
}
// UpdateOptsBuilder allows extensions to add additional parameters to the
// Update request.
type UpdateOptsBuilder interface {
ToVolumeUpdateMap() (map[string]interface{}, error)
}
// UpdateOpts contain options for updating an existing Volume. This object is passed
// to the volumes.Update function. For more information about the parameters, see
// the Volume object.
type UpdateOpts struct {
Name string `json:"display_name,omitempty"`
Description string `json:"display_description,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
// ToVolumeUpdateMap assembles a request body based on the contents of an
// UpdateOpts.
func (opts UpdateOpts) ToVolumeUpdateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "volume")
}
// Update will update the Volume with provided information. To extract the updated
// Volume from the response, call the Extract method on the UpdateResult.
func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
b, err := opts.ToVolumeUpdateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// IDFromName is a convienience function that returns a server's ID given its name.
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
count := 0
id := ""
pages, err := List(client, nil).AllPages()
if err != nil {
return "", err
}
all, err := ExtractVolumes(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: "volume"}
case 1:
return id, nil
default:
return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "volume"}
}
}

View File

@ -0,0 +1,89 @@
package volumes
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// Volume contains all the information associated with an OpenStack Volume.
type Volume struct {
// Current status of the volume.
Status string `json:"status"`
// Human-readable display name for the volume.
Name string `json:"display_name"`
// Instances onto which the volume is attached.
Attachments []map[string]interface{} `json:"attachments"`
// This parameter is no longer used.
AvailabilityZone string `json:"availability_zone"`
// Indicates whether this is a bootable volume.
Bootable string `json:"bootable"`
// The date when this volume was created.
CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"`
// Human-readable description for the volume.
Description string `json:"display_description"`
// The type of volume to create, either SATA or SSD.
VolumeType string `json:"volume_type"`
// The ID of the snapshot from which the volume was created
SnapshotID string `json:"snapshot_id"`
// The ID of another block storage volume from which the current volume was created
SourceVolID string `json:"source_volid"`
// Arbitrary key-value pairs defined by the user.
Metadata map[string]string `json:"metadata"`
// Unique identifier for the volume.
ID string `json:"id"`
// Size of the volume in GB.
Size int `json:"size"`
}
// CreateResult contains the response body and error from a Create request.
type CreateResult struct {
commonResult
}
// GetResult contains the response body and error from a Get request.
type GetResult struct {
commonResult
}
// DeleteResult contains the response body and error from a Delete request.
type DeleteResult struct {
gophercloud.ErrResult
}
// VolumePage is a pagination.pager that is returned from a call to the List function.
type VolumePage struct {
pagination.SinglePageBase
}
// IsEmpty returns true if a VolumePage contains no Volumes.
func (r VolumePage) IsEmpty() (bool, error) {
volumes, err := ExtractVolumes(r)
return len(volumes) == 0, err
}
// ExtractVolumes extracts and returns Volumes. It is used while iterating over a volumes.List call.
func ExtractVolumes(r pagination.Page) ([]Volume, error) {
var s struct {
Volumes []Volume `json:"volumes"`
}
err := (r.(VolumePage)).ExtractInto(&s)
return s.Volumes, err
}
// UpdateResult contains the response body and error from an Update request.
type UpdateResult struct {
commonResult
}
type commonResult struct {
gophercloud.Result
}
// Extract will get the Volume object out of the commonResult object.
func (r commonResult) Extract() (*Volume, error) {
var s struct {
Volume *Volume `json:"volume"`
}
err := r.ExtractInto(&s)
return s.Volume, err
}

View File

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

View File

@ -0,0 +1,22 @@
package volumes
import (
"github.com/gophercloud/gophercloud"
)
// WaitForStatus will continually poll the resource, checking for a particular
// status. It will do this for the amount of seconds defined.
func WaitForStatus(c *gophercloud.ServiceClient, id, status string, secs int) error {
return gophercloud.WaitFor(secs, func() (bool, error) {
current, err := Get(c, id).Extract()
if err != nil {
return false, err
}
if current.Status == status {
return true, nil
}
return false, nil
})
}

View File

@ -0,0 +1,323 @@
package openstack
import (
"fmt"
"net/url"
"reflect"
"github.com/gophercloud/gophercloud"
tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
"github.com/gophercloud/gophercloud/openstack/utils"
)
const (
v20 = "v2.0"
v30 = "v3.0"
)
// NewClient prepares an unauthenticated ProviderClient instance.
// Most users will probably prefer using the AuthenticatedClient function instead.
// This is useful if you wish to explicitly control the version of the identity service that's used for authentication explicitly,
// for example.
func NewClient(endpoint string) (*gophercloud.ProviderClient, error) {
u, err := url.Parse(endpoint)
if err != nil {
return nil, err
}
hadPath := u.Path != ""
u.Path, u.RawQuery, u.Fragment = "", "", ""
base := u.String()
endpoint = gophercloud.NormalizeURL(endpoint)
base = gophercloud.NormalizeURL(base)
if hadPath {
return &gophercloud.ProviderClient{
IdentityBase: base,
IdentityEndpoint: endpoint,
}, nil
}
return &gophercloud.ProviderClient{
IdentityBase: base,
IdentityEndpoint: "",
}, nil
}
// AuthenticatedClient logs in to an OpenStack cloud found at the identity endpoint specified by options, acquires a token, and
// returns a Client instance that's ready to operate.
// It first queries the root identity endpoint to determine which versions of the identity service are supported, then chooses
// the most recent identity service available to proceed.
func AuthenticatedClient(options gophercloud.AuthOptions) (*gophercloud.ProviderClient, error) {
client, err := NewClient(options.IdentityEndpoint)
if err != nil {
return nil, err
}
err = Authenticate(client, options)
if err != nil {
return nil, err
}
return client, nil
}
// Authenticate or re-authenticate against the most recent identity service supported at the provided endpoint.
func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
versions := []*utils.Version{
{ID: v20, Priority: 20, Suffix: "/v2.0/"},
{ID: v30, Priority: 30, Suffix: "/v3/"},
}
chosen, endpoint, err := utils.ChooseVersion(client, versions)
if err != nil {
return err
}
switch chosen.ID {
case v20:
return v2auth(client, endpoint, options, gophercloud.EndpointOpts{})
case v30:
return v3auth(client, endpoint, &options, gophercloud.EndpointOpts{})
default:
// The switch statement must be out of date from the versions list.
return fmt.Errorf("Unrecognized identity version: %s", chosen.ID)
}
}
// AuthenticateV2 explicitly authenticates against the identity v2 endpoint.
func AuthenticateV2(client *gophercloud.ProviderClient, options gophercloud.AuthOptions, eo gophercloud.EndpointOpts) error {
return v2auth(client, "", options, eo)
}
func v2auth(client *gophercloud.ProviderClient, endpoint string, options gophercloud.AuthOptions, eo gophercloud.EndpointOpts) error {
v2Client, err := NewIdentityV2(client, eo)
if err != nil {
return err
}
if endpoint != "" {
v2Client.Endpoint = endpoint
}
v2Opts := tokens2.AuthOptions{
IdentityEndpoint: options.IdentityEndpoint,
Username: options.Username,
Password: options.Password,
TenantID: options.TenantID,
TenantName: options.TenantName,
AllowReauth: options.AllowReauth,
TokenID: options.TokenID,
}
result := tokens2.Create(v2Client, v2Opts)
token, err := result.ExtractToken()
if err != nil {
return err
}
catalog, err := result.ExtractServiceCatalog()
if err != nil {
return err
}
if options.AllowReauth {
client.ReauthFunc = func() error {
client.TokenID = ""
return v2auth(client, endpoint, options, eo)
}
}
client.TokenID = token.ID
client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
return V2EndpointURL(catalog, opts)
}
return nil
}
// AuthenticateV3 explicitly authenticates against the identity v3 service.
func AuthenticateV3(client *gophercloud.ProviderClient, options tokens3.AuthOptionsBuilder, eo gophercloud.EndpointOpts) error {
return v3auth(client, "", options, eo)
}
func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.AuthOptionsBuilder, eo gophercloud.EndpointOpts) error {
// Override the generated service endpoint with the one returned by the version endpoint.
v3Client, err := NewIdentityV3(client, eo)
if err != nil {
return err
}
if endpoint != "" {
v3Client.Endpoint = endpoint
}
result := tokens3.Create(v3Client, opts)
token, err := result.ExtractToken()
if err != nil {
return err
}
catalog, err := result.ExtractServiceCatalog()
if err != nil {
return err
}
client.TokenID = token.ID
if opts.CanReauth() {
client.ReauthFunc = func() error {
client.TokenID = ""
return v3auth(client, endpoint, opts, eo)
}
}
client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
return V3EndpointURL(catalog, opts)
}
return nil
}
// NewIdentityV2 creates a ServiceClient that may be used to interact with the v2 identity service.
func NewIdentityV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
endpoint := client.IdentityBase + "v2.0/"
var err error
if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) {
eo.ApplyDefaults("identity")
endpoint, err = client.EndpointLocator(eo)
if err != nil {
return nil, err
}
}
return &gophercloud.ServiceClient{
ProviderClient: client,
Endpoint: endpoint,
}, nil
}
// NewIdentityV3 creates a ServiceClient that may be used to access the v3 identity service.
func NewIdentityV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
endpoint := client.IdentityBase + "v3/"
var err error
if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) {
eo.ApplyDefaults("identity")
endpoint, err = client.EndpointLocator(eo)
if err != nil {
return nil, err
}
}
return &gophercloud.ServiceClient{
ProviderClient: client,
Endpoint: endpoint,
}, nil
}
// NewObjectStorageV1 creates a ServiceClient that may be used with the v1 object storage package.
func NewObjectStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("object-store")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
}
// NewComputeV2 creates a ServiceClient that may be used with the v2 compute package.
func NewComputeV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("compute")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
}
// NewNetworkV2 creates a ServiceClient that may be used with the v2 network package.
func NewNetworkV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("network")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{
ProviderClient: client,
Endpoint: url,
ResourceBase: url + "v2.0/",
}, nil
}
// NewBlockStorageV1 creates a ServiceClient that may be used to access the v1 block storage service.
func NewBlockStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("volume")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
}
// NewBlockStorageV2 creates a ServiceClient that may be used to access the v2 block storage service.
func NewBlockStorageV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("volumev2")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
}
// NewSharedFileSystemV2 creates a ServiceClient that may be used to access the v2 shared file system service.
func NewSharedFileSystemV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("sharev2")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
}
// NewCDNV1 creates a ServiceClient that may be used to access the OpenStack v1
// CDN service.
func NewCDNV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("cdn")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
}
// NewOrchestrationV1 creates a ServiceClient that may be used to access the v1 orchestration service.
func NewOrchestrationV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("orchestration")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
}
// NewDBV1 creates a ServiceClient that may be used to access the v1 DB service.
func NewDBV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("database")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
}
// NewImageServiceV2 creates a ServiceClient that may be used to access the v2 image service.
func NewImageServiceV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("image")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client,
Endpoint: url,
ResourceBase: url + "v2/"}, nil
}

View File

@ -1,15 +1,14 @@
package extensions
import (
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// Get retrieves information for a specific extension using its alias.
func Get(c *gophercloud.ServiceClient, alias string) GetResult {
var res GetResult
_, res.Err = c.Get(ExtensionURL(c, alias), &res.Body, nil)
return res
func Get(c *gophercloud.ServiceClient, alias string) (r GetResult) {
_, r.Err = c.Get(ExtensionURL(c, alias), &r.Body, nil)
return
}
// List returns a Pager which allows you to iterate over the full collection of extensions.

View File

@ -1,9 +1,8 @@
package extensions
import (
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// GetResult temporarily stores the result of a Get call.
@ -14,27 +13,21 @@ type GetResult struct {
// Extract interprets a GetResult as an Extension.
func (r GetResult) Extract() (*Extension, error) {
if r.Err != nil {
return nil, r.Err
}
var res struct {
var s struct {
Extension *Extension `json:"extension"`
}
err := mapstructure.Decode(r.Body, &res)
return res.Extension, err
err := r.ExtractInto(&s)
return s.Extension, err
}
// Extension is a struct that represents an OpenStack extension.
type Extension struct {
Updated string `json:"updated" mapstructure:"updated"`
Name string `json:"name" mapstructure:"name"`
Links []interface{} `json:"links" mapstructure:"links"`
Namespace string `json:"namespace" mapstructure:"namespace"`
Alias string `json:"alias" mapstructure:"alias"`
Description string `json:"description" mapstructure:"description"`
Updated string `json:"updated"`
Name string `json:"name"`
Links []interface{} `json:"links"`
Namespace string `json:"namespace"`
Alias string `json:"alias"`
Description string `json:"description"`
}
// ExtensionPage is the page returned by a pager when traversing over a collection of extensions.
@ -45,21 +38,16 @@ type ExtensionPage struct {
// IsEmpty checks whether an ExtensionPage struct is empty.
func (r ExtensionPage) IsEmpty() (bool, error) {
is, err := ExtractExtensions(r)
if err != nil {
return true, err
}
return len(is) == 0, nil
return len(is) == 0, err
}
// ExtractExtensions accepts a Page struct, specifically an ExtensionPage struct, and extracts the
// elements into a slice of Extension structs.
// In other words, a generic collection is mapped into a relevant slice.
func ExtractExtensions(page pagination.Page) ([]Extension, error) {
var resp struct {
Extensions []Extension `mapstructure:"extensions"`
func ExtractExtensions(r pagination.Page) ([]Extension, error) {
var s struct {
Extensions []Extension `json:"extensions"`
}
err := mapstructure.Decode(page.(ExtensionPage).Body, &resp)
return resp.Extensions, err
err := (r.(ExtensionPage)).ExtractInto(&s)
return s.Extensions, err
}

View File

@ -1,6 +1,6 @@
package extensions
import "github.com/rackspace/gophercloud"
import "github.com/gophercloud/gophercloud"
// ExtensionURL generates the URL for an extension resource by name.
func ExtensionURL(c *gophercloud.ServiceClient, name string) string {

View File

@ -0,0 +1,3 @@
// Package volumeattach provides the ability to attach and detach volumes
// to instances
package volumeattach

View File

@ -0,0 +1,57 @@
package volumeattach
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// List returns a Pager that allows you to iterate over a collection of VolumeAttachments.
func List(client *gophercloud.ServiceClient, serverID string) pagination.Pager {
return pagination.NewPager(client, listURL(client, serverID), func(r pagination.PageResult) pagination.Page {
return VolumeAttachmentPage{pagination.SinglePageBase(r)}
})
}
// CreateOptsBuilder describes struct types that can be accepted by the Create call. Notable, the
// CreateOpts struct in this package does.
type CreateOptsBuilder interface {
ToVolumeAttachmentCreateMap() (map[string]interface{}, error)
}
// CreateOpts specifies volume attachment creation or import parameters.
type CreateOpts struct {
// Device is the device that the volume will attach to the instance as. Omit for "auto"
Device string `json:"device,omitempty"`
// VolumeID is the ID of the volume to attach to the instance
VolumeID string `json:"volumeId" required:"true"`
}
// ToVolumeAttachmentCreateMap constructs a request body from CreateOpts.
func (opts CreateOpts) ToVolumeAttachmentCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "volumeAttachment")
}
// Create requests the creation of a new volume attachment on the server
func Create(client *gophercloud.ServiceClient, serverID string, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToVolumeAttachmentCreateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(createURL(client, serverID), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// Get returns public data about a previously created VolumeAttachment.
func Get(client *gophercloud.ServiceClient, serverID, attachmentID string) (r GetResult) {
_, r.Err = client.Get(getURL(client, serverID, attachmentID), &r.Body, nil)
return
}
// Delete requests the deletion of a previous stored VolumeAttachment from the server.
func Delete(client *gophercloud.ServiceClient, serverID, attachmentID string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, serverID, attachmentID), nil)
return
}

View File

@ -0,0 +1,76 @@
package volumeattach
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// VolumeAttachment controls the attachment of a volume to an instance.
type VolumeAttachment struct {
// ID is a unique id of the attachment
ID string `json:"id"`
// Device is what device the volume is attached as
Device string `json:"device"`
// VolumeID is the ID of the attached volume
VolumeID string `json:"volumeId"`
// ServerID is the ID of the instance that has the volume attached
ServerID string `json:"serverId"`
}
// VolumeAttachmentPage stores a single, only page of VolumeAttachments
// results from a List call.
type VolumeAttachmentPage struct {
pagination.SinglePageBase
}
// IsEmpty determines whether or not a VolumeAttachmentsPage is empty.
func (page VolumeAttachmentPage) IsEmpty() (bool, error) {
va, err := ExtractVolumeAttachments(page)
return len(va) == 0, err
}
// ExtractVolumeAttachments interprets a page of results as a slice of
// VolumeAttachments.
func ExtractVolumeAttachments(r pagination.Page) ([]VolumeAttachment, error) {
var s struct {
VolumeAttachments []VolumeAttachment `json:"volumeAttachments"`
}
err := (r.(VolumeAttachmentPage)).ExtractInto(&s)
return s.VolumeAttachments, err
}
// VolumeAttachmentResult is the result from a volume attachment operation.
type VolumeAttachmentResult struct {
gophercloud.Result
}
// Extract is a method that attempts to interpret any VolumeAttachment resource
// response as a VolumeAttachment struct.
func (r VolumeAttachmentResult) Extract() (*VolumeAttachment, error) {
var s struct {
VolumeAttachment *VolumeAttachment `json:"volumeAttachment"`
}
err := r.ExtractInto(&s)
return s.VolumeAttachment, err
}
// CreateResult is the response from a Create operation. Call its Extract method to interpret it
// as a VolumeAttachment.
type CreateResult struct {
VolumeAttachmentResult
}
// GetResult is the response from a Get operation. Call its Extract method to interpret it
// as a VolumeAttachment.
type GetResult struct {
VolumeAttachmentResult
}
// DeleteResult is the response from a Delete operation. Call its Extract method to determine if
// the call succeeded or failed.
type DeleteResult struct {
gophercloud.ErrResult
}

View File

@ -0,0 +1,25 @@
package volumeattach
import "github.com/gophercloud/gophercloud"
const resourcePath = "os-volume_attachments"
func resourceURL(c *gophercloud.ServiceClient, serverID string) string {
return c.ServiceURL("servers", serverID, resourcePath)
}
func listURL(c *gophercloud.ServiceClient, serverID string) string {
return resourceURL(c, serverID)
}
func createURL(c *gophercloud.ServiceClient, serverID string) string {
return resourceURL(c, serverID)
}
func getURL(c *gophercloud.ServiceClient, serverID, aID string) string {
return c.ServiceURL("servers", serverID, resourcePath, aID)
}
func deleteURL(c *gophercloud.ServiceClient, serverID, aID string) string {
return getURL(c, serverID, aID)
}

View File

@ -0,0 +1,7 @@
// Package flavors provides information and interaction with the flavor API
// resource in the OpenStack Compute service.
//
// A flavor is an available hardware configuration for a server. Each flavor
// has a unique combination of disk space, memory capacity and priority for CPU
// time.
package flavors

View File

@ -0,0 +1,100 @@
package flavors
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// ListOptsBuilder allows extensions to add additional parameters to the
// List request.
type ListOptsBuilder interface {
ToFlavorListQuery() (string, error)
}
// ListOpts helps control the results returned by the List() function.
// For example, a flavor with a minDisk field of 10 will not be returned if you specify MinDisk set to 20.
// Typically, software will use the last ID of the previous call to List to set the Marker for the current call.
type ListOpts struct {
// ChangesSince, if provided, instructs List to return only those things which have changed since the timestamp provided.
ChangesSince string `q:"changes-since"`
// MinDisk and MinRAM, if provided, elides flavors which do not meet your criteria.
MinDisk int `q:"minDisk"`
MinRAM int `q:"minRam"`
// Marker and Limit control paging.
// Marker instructs List where to start listing from.
Marker string `q:"marker"`
// Limit instructs List to refrain from sending excessively large lists of flavors.
Limit int `q:"limit"`
}
// ToFlavorListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToFlavorListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
return q.String(), err
}
// ListDetail instructs OpenStack to provide a list of flavors.
// You may provide criteria by which List curtails its results for easier processing.
// See ListOpts for more details.
func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
url := listURL(client)
if opts != nil {
query, err := opts.ToFlavorListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
}
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
return FlavorPage{pagination.LinkedPageBase{PageResult: r}}
})
}
// Get instructs OpenStack to provide details on a single flavor, identified by its ID.
// Use ExtractFlavor to convert its result into a Flavor.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
return
}
// IDFromName is a convienience function that returns a flavor's ID given its name.
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
count := 0
id := ""
allPages, err := ListDetail(client, nil).AllPages()
if err != nil {
return "", err
}
all, err := ExtractFlavors(allPages)
if err != nil {
return "", err
}
for _, f := range all {
if f.Name == name {
count++
id = f.ID
}
}
switch count {
case 0:
err := &gophercloud.ErrResourceNotFound{}
err.ResourceType = "flavor"
err.Name = name
return "", err
case 1:
return id, nil
default:
err := &gophercloud.ErrMultipleResourcesFound{}
err.ResourceType = "flavor"
err.Name = name
err.Count = count
return "", err
}
}

View File

@ -0,0 +1,114 @@
package flavors
import (
"encoding/json"
"strconv"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// GetResult temporarily holds the response from a Get call.
type GetResult struct {
gophercloud.Result
}
// Extract provides access to the individual Flavor returned by the Get function.
func (r GetResult) Extract() (*Flavor, error) {
var s struct {
Flavor *Flavor `json:"flavor"`
}
err := r.ExtractInto(&s)
return s.Flavor, err
}
// Flavor records represent (virtual) hardware configurations for server resources in a region.
type Flavor struct {
// The Id field contains the flavor's unique identifier.
// For example, this identifier will be useful when specifying which hardware configuration to use for a new server instance.
ID string `json:"id"`
// The Disk and RA< fields provide a measure of storage space offered by the flavor, in GB and MB, respectively.
Disk int `json:"disk"`
RAM int `json:"ram"`
// The Name field provides a human-readable moniker for the flavor.
Name string `json:"name"`
RxTxFactor float64 `json:"rxtx_factor"`
// Swap indicates how much space is reserved for swap.
// If not provided, this field will be set to 0.
Swap int `json:"swap"`
// VCPUs indicates how many (virtual) CPUs are available for this flavor.
VCPUs int `json:"vcpus"`
}
func (f *Flavor) UnmarshalJSON(b []byte) error {
var flavor struct {
ID string `json:"id"`
Disk int `json:"disk"`
RAM int `json:"ram"`
Name string `json:"name"`
RxTxFactor float64 `json:"rxtx_factor"`
Swap interface{} `json:"swap"`
VCPUs int `json:"vcpus"`
}
err := json.Unmarshal(b, &flavor)
if err != nil {
return err
}
f.ID = flavor.ID
f.Disk = flavor.Disk
f.RAM = flavor.RAM
f.Name = flavor.Name
f.RxTxFactor = flavor.RxTxFactor
f.VCPUs = flavor.VCPUs
switch t := flavor.Swap.(type) {
case float64:
f.Swap = int(t)
case string:
switch t {
case "":
f.Swap = 0
default:
swap, err := strconv.ParseFloat(t, 64)
if err != nil {
return err
}
f.Swap = int(swap)
}
}
return nil
}
// FlavorPage contains a single page of the response from a List call.
type FlavorPage struct {
pagination.LinkedPageBase
}
// IsEmpty determines if a page contains any results.
func (page FlavorPage) IsEmpty() (bool, error) {
flavors, err := ExtractFlavors(page)
return len(flavors) == 0, err
}
// NextPageURL uses the response's embedded link reference to navigate to the next page of results.
func (page FlavorPage) NextPageURL() (string, error) {
var s struct {
Links []gophercloud.Link `json:"flavors_links"`
}
err := page.ExtractInto(&s)
if err != nil {
return "", err
}
return gophercloud.ExtractNextURL(s.Links)
}
// ExtractFlavors provides access to the list of flavors in a page acquired from the List operation.
func ExtractFlavors(r pagination.Page) ([]Flavor, error) {
var s struct {
Flavors []Flavor `json:"flavors"`
}
err := (r.(FlavorPage)).ExtractInto(&s)
return s.Flavors, err
}

View File

@ -0,0 +1,13 @@
package flavors
import (
"github.com/gophercloud/gophercloud"
)
func getURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("flavors", id)
}
func listURL(client *gophercloud.ServiceClient) string {
return client.ServiceURL("flavors", "detail")
}

View File

@ -0,0 +1,7 @@
// Package images provides information and interaction with the image API
// resource in the OpenStack Compute service.
//
// An image is a collection of files used to create or rebuild a server.
// Operators provide a number of pre-built OS images by default. You may also
// create custom images from cloud servers you have launched.
package images

View File

@ -0,0 +1,102 @@
package images
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// ListOptsBuilder allows extensions to add additional parameters to the
// List request.
type ListOptsBuilder interface {
ToImageListQuery() (string, error)
}
// ListOpts contain options for limiting the number of Images returned from a call to ListDetail.
type ListOpts struct {
// When the image last changed status (in date-time format).
ChangesSince string `q:"changes-since"`
// The number of Images to return.
Limit int `q:"limit"`
// UUID of the Image at which to set a marker.
Marker string `q:"marker"`
// The name of the Image.
Name string `q:"name"`
// The name of the Server (in URL format).
Server string `q:"server"`
// The current status of the Image.
Status string `q:"status"`
// The value of the type of image (e.g. BASE, SERVER, ALL)
Type string `q:"type"`
}
// ToImageListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToImageListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
return q.String(), err
}
// ListDetail enumerates the available images.
func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
url := listDetailURL(client)
if opts != nil {
query, err := opts.ToImageListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
}
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
return ImagePage{pagination.LinkedPageBase{PageResult: r}}
})
}
// Get acquires additional detail about a specific image by ID.
// Use ExtractImage() to interpret the result as an openstack Image.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
return
}
// Delete deletes the specified image ID.
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, id), nil)
return
}
// IDFromName is a convienience function that returns an image's ID given its name.
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
count := 0
id := ""
allPages, err := ListDetail(client, nil).AllPages()
if err != nil {
return "", err
}
all, err := ExtractImages(allPages)
if err != nil {
return "", err
}
for _, f := range all {
if f.Name == name {
count++
id = f.ID
}
}
switch count {
case 0:
err := &gophercloud.ErrResourceNotFound{}
err.ResourceType = "image"
err.Name = name
return "", err
case 1:
return id, nil
default:
err := &gophercloud.ErrMultipleResourcesFound{}
err.ResourceType = "image"
err.Name = name
err.Count = count
return "", err
}
}

View File

@ -0,0 +1,83 @@
package images
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// GetResult temporarily stores a Get response.
type GetResult struct {
gophercloud.Result
}
// DeleteResult represents the result of an image.Delete operation.
type DeleteResult struct {
gophercloud.ErrResult
}
// Extract interprets a GetResult as an Image.
func (r GetResult) Extract() (*Image, error) {
var s struct {
Image *Image `json:"image"`
}
err := r.ExtractInto(&s)
return s.Image, err
}
// Image is used for JSON (un)marshalling.
// It provides a description of an OS image.
type Image struct {
// ID contains the image's unique identifier.
ID string
Created string
// MinDisk and MinRAM specify the minimum resources a server must provide to be able to install the image.
MinDisk int
MinRAM int
// Name provides a human-readable moniker for the OS image.
Name string
// The Progress and Status fields indicate image-creation status.
// Any usable image will have 100% progress.
Progress int
Status string
Updated string
Metadata map[string]string
}
// ImagePage contains a single page of results from a List operation.
// Use ExtractImages to convert it into a slice of usable structs.
type ImagePage struct {
pagination.LinkedPageBase
}
// IsEmpty returns true if a page contains no Image results.
func (page ImagePage) IsEmpty() (bool, error) {
images, err := ExtractImages(page)
return len(images) == 0, err
}
// NextPageURL uses the response's embedded link reference to navigate to the next page of results.
func (page ImagePage) NextPageURL() (string, error) {
var s struct {
Links []gophercloud.Link `json:"images_links"`
}
err := page.ExtractInto(&s)
if err != nil {
return "", err
}
return gophercloud.ExtractNextURL(s.Links)
}
// ExtractImages converts a page of List results into a slice of usable Image structs.
func ExtractImages(r pagination.Page) ([]Image, error) {
var s struct {
Images []Image `json:"images"`
}
err := (r.(ImagePage)).ExtractInto(&s)
return s.Images, err
}

View File

@ -0,0 +1,15 @@
package images
import "github.com/gophercloud/gophercloud"
func listDetailURL(client *gophercloud.ServiceClient) string {
return client.ServiceURL("images", "detail")
}
func getURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("images", id)
}
func deleteURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("images", id)
}

View File

@ -0,0 +1,6 @@
// Package servers provides information and interaction with the server API
// resource in the OpenStack Compute service.
//
// A server is a virtual machine instance in the compute system. In order for
// one to be provisioned, a valid flavor and image are required.
package servers

View File

@ -0,0 +1,71 @@
package servers
import (
"fmt"
"github.com/gophercloud/gophercloud"
)
// ErrNeitherImageIDNorImageNameProvided is the error when neither the image
// ID nor the image name is provided for a server operation
type ErrNeitherImageIDNorImageNameProvided struct{ gophercloud.ErrMissingInput }
func (e ErrNeitherImageIDNorImageNameProvided) Error() string {
return "One and only one of the image ID and the image name must be provided."
}
// ErrNeitherFlavorIDNorFlavorNameProvided is the error when neither the flavor
// ID nor the flavor name is provided for a server operation
type ErrNeitherFlavorIDNorFlavorNameProvided struct{ gophercloud.ErrMissingInput }
func (e ErrNeitherFlavorIDNorFlavorNameProvided) Error() string {
return "One and only one of the flavor ID and the flavor name must be provided."
}
type ErrNoClientProvidedForIDByName struct{ gophercloud.ErrMissingInput }
func (e ErrNoClientProvidedForIDByName) Error() string {
return "A service client must be provided to find a resource ID by name."
}
// ErrInvalidHowParameterProvided is the error when an unknown value is given
// for the `how` argument
type ErrInvalidHowParameterProvided struct{ gophercloud.ErrInvalidInput }
// ErrNoAdminPassProvided is the error when an administrative password isn't
// provided for a server operation
type ErrNoAdminPassProvided struct{ gophercloud.ErrMissingInput }
// ErrNoImageIDProvided is the error when an image ID isn't provided for a server
// operation
type ErrNoImageIDProvided struct{ gophercloud.ErrMissingInput }
// ErrNoIDProvided is the error when a server ID isn't provided for a server
// operation
type ErrNoIDProvided struct{ gophercloud.ErrMissingInput }
// ErrServer is a generic error type for servers HTTP operations.
type ErrServer struct {
gophercloud.ErrUnexpectedResponseCode
ID string
}
func (se ErrServer) Error() string {
return fmt.Sprintf("Error while executing HTTP request for server [%s]", se.ID)
}
// Error404 overrides the generic 404 error message.
func (se ErrServer) Error404(e gophercloud.ErrUnexpectedResponseCode) error {
se.ErrUnexpectedResponseCode = e
return &ErrServerNotFound{se}
}
// ErrServerNotFound is the error when a 404 is received during server HTTP
// operations.
type ErrServerNotFound struct {
ErrServer
}
func (e ErrServerNotFound) Error() string {
return fmt.Sprintf("I couldn't find server [%s]", e.ID)
}

View File

@ -0,0 +1,737 @@
package servers
import (
"encoding/base64"
"encoding/json"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
"github.com/gophercloud/gophercloud/openstack/compute/v2/images"
"github.com/gophercloud/gophercloud/pagination"
)
// ListOptsBuilder allows extensions to add additional parameters to the
// List request.
type ListOptsBuilder interface {
ToServerListQuery() (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 server attributes you want to see returned. Marker and Limit are used
// for pagination.
type ListOpts struct {
// A time/date stamp for when the server last changed status.
ChangesSince string `q:"changes-since"`
// Name of the image in URL format.
Image string `q:"image"`
// Name of the flavor in URL format.
Flavor string `q:"flavor"`
// Name of the server as a string; can be queried with regular expressions.
// Realize that ?name=bob returns both bob and bobb. If you need to match bob
// only, you can use a regular expression matching the syntax of the
// underlying database server implemented for Compute.
Name string `q:"name"`
// Value of the status of the server so that you can filter on "ACTIVE" for example.
Status string `q:"status"`
// Name of the host as a string.
Host string `q:"host"`
// UUID of the server at which you want to set a marker.
Marker string `q:"marker"`
// Integer value for the limit of values to return.
Limit int `q:"limit"`
// Bool to show all tenants
AllTenants bool `q:"all_tenants"`
}
// ToServerListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToServerListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
return q.String(), err
}
// List makes a request against the API to list servers accessible to you.
func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
url := listDetailURL(client)
if opts != nil {
query, err := opts.ToServerListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
}
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
return ServerPage{pagination.LinkedPageBase{PageResult: r}}
})
}
// CreateOptsBuilder describes struct types that can be accepted by the Create call.
// The CreateOpts struct in this package does.
type CreateOptsBuilder interface {
ToServerCreateMap() (map[string]interface{}, error)
}
// Network is used within CreateOpts to control a new server's network attachments.
type Network struct {
// UUID of a nova-network to attach to the newly provisioned server.
// Required unless Port is provided.
UUID string
// Port of a neutron network to attach to the newly provisioned server.
// Required unless UUID is provided.
Port string
// FixedIP [optional] specifies a fixed IPv4 address to be used on this network.
FixedIP string
}
// Personality is an array of files that are injected into the server at launch.
type Personality []*File
// File is used within CreateOpts and RebuildOpts to inject a file into the server at launch.
// File implements the json.Marshaler interface, so when a Create or Rebuild operation is requested,
// json.Marshal will call File's MarshalJSON method.
type File struct {
// Path of the file
Path string
// Contents of the file. Maximum content size is 255 bytes.
Contents []byte
}
// MarshalJSON marshals the escaped file, base64 encoding the contents.
func (f *File) MarshalJSON() ([]byte, error) {
file := struct {
Path string `json:"path"`
Contents string `json:"contents"`
}{
Path: f.Path,
Contents: base64.StdEncoding.EncodeToString(f.Contents),
}
return json.Marshal(file)
}
// CreateOpts specifies server creation parameters.
type CreateOpts struct {
// Name is the name to assign to the newly launched server.
Name string `json:"name" required:"true"`
// ImageRef [optional; required if ImageName is not provided] is the ID or full
// URL to the image that contains the server's OS and initial state.
// Also optional if using the boot-from-volume extension.
ImageRef string `json:"imageRef"`
// ImageName [optional; required if ImageRef is not provided] is the name of the
// image that contains the server's OS and initial state.
// Also optional if using the boot-from-volume extension.
ImageName string `json:"-"`
// FlavorRef [optional; required if FlavorName is not provided] is the ID or
// full URL to the flavor that describes the server's specs.
FlavorRef string `json:"flavorRef"`
// FlavorName [optional; required if FlavorRef is not provided] is the name of
// the flavor that describes the server's specs.
FlavorName string `json:"-"`
// SecurityGroups lists the names of the security groups to which this server should belong.
SecurityGroups []string `json:"-"`
// UserData contains configuration information or scripts to use upon launch.
// Create will base64-encode it for you.
UserData []byte `json:"-"`
// AvailabilityZone in which to launch the server.
AvailabilityZone string `json:"availability_zone,omitempty"`
// Networks dictates how this server will be attached to available networks.
// By default, the server will be attached to all isolated networks for the tenant.
Networks []Network `json:"-"`
// Metadata contains key-value pairs (up to 255 bytes each) to attach to the server.
Metadata map[string]string `json:"metadata,omitempty"`
// Personality includes files to inject into the server at launch.
// Create will base64-encode file contents for you.
Personality Personality `json:"-"`
// ConfigDrive enables metadata injection through a configuration drive.
ConfigDrive *bool `json:"config_drive,omitempty"`
// AdminPass sets the root user password. If not set, a randomly-generated
// password will be created and returned in the rponse.
AdminPass string `json:"adminPass,omitempty"`
// AccessIPv4 specifies an IPv4 address for the instance.
AccessIPv4 string `json:"accessIPv4,omitempty"`
// AccessIPv6 pecifies an IPv6 address for the instance.
AccessIPv6 string `json:"accessIPv6,omitempty"`
// ServiceClient will allow calls to be made to retrieve an image or
// flavor ID by name.
ServiceClient *gophercloud.ServiceClient `json:"-"`
}
// ToServerCreateMap assembles a request body based on the contents of a CreateOpts.
func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) {
sc := opts.ServiceClient
opts.ServiceClient = nil
b, err := gophercloud.BuildRequestBody(opts, "")
if err != nil {
return nil, err
}
if opts.UserData != nil {
encoded := base64.StdEncoding.EncodeToString(opts.UserData)
b["user_data"] = &encoded
}
if len(opts.SecurityGroups) > 0 {
securityGroups := make([]map[string]interface{}, len(opts.SecurityGroups))
for i, groupName := range opts.SecurityGroups {
securityGroups[i] = map[string]interface{}{"name": groupName}
}
b["security_groups"] = securityGroups
}
if len(opts.Networks) > 0 {
networks := make([]map[string]interface{}, len(opts.Networks))
for i, net := range opts.Networks {
networks[i] = make(map[string]interface{})
if net.UUID != "" {
networks[i]["uuid"] = net.UUID
}
if net.Port != "" {
networks[i]["port"] = net.Port
}
if net.FixedIP != "" {
networks[i]["fixed_ip"] = net.FixedIP
}
}
b["networks"] = networks
}
// If ImageRef isn't provided, check if ImageName was provided to ascertain
// the image ID.
if opts.ImageRef == "" {
if opts.ImageName != "" {
if sc == nil {
err := ErrNoClientProvidedForIDByName{}
err.Argument = "ServiceClient"
return nil, err
}
imageID, err := images.IDFromName(sc, opts.ImageName)
if err != nil {
return nil, err
}
b["imageRef"] = imageID
}
}
// If FlavorRef isn't provided, use FlavorName to ascertain the flavor ID.
if opts.FlavorRef == "" {
if opts.FlavorName == "" {
err := ErrNeitherFlavorIDNorFlavorNameProvided{}
err.Argument = "FlavorRef/FlavorName"
return nil, err
}
if sc == nil {
err := ErrNoClientProvidedForIDByName{}
err.Argument = "ServiceClient"
return nil, err
}
flavorID, err := flavors.IDFromName(sc, opts.FlavorName)
if err != nil {
return nil, err
}
b["flavorRef"] = flavorID
}
return map[string]interface{}{"server": b}, nil
}
// Create requests a server to be provisioned to the user in the current tenant.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
reqBody, err := opts.ToServerCreateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(listURL(client), reqBody, &r.Body, nil)
return
}
// Delete requests that a server previously provisioned be removed from your account.
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, id), nil)
return
}
// ForceDelete forces the deletion of a server
func ForceDelete(client *gophercloud.ServiceClient, id string) (r ActionResult) {
_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"forceDelete": ""}, nil, nil)
return
}
// Get requests details on a single server, by ID.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = client.Get(getURL(client, id), &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 203},
})
return
}
// UpdateOptsBuilder allows extensions to add additional attributes to the Update request.
type UpdateOptsBuilder interface {
ToServerUpdateMap() (map[string]interface{}, error)
}
// UpdateOpts specifies the base attributes that may be updated on an existing server.
type UpdateOpts struct {
// Name changes the displayed name of the server.
// The server host name will *not* change.
// Server names are not constrained to be unique, even within the same tenant.
Name string `json:"name,omitempty"`
// AccessIPv4 provides a new IPv4 address for the instance.
AccessIPv4 string `json:"accessIPv4,omitempty"`
// AccessIPv6 provides a new IPv6 address for the instance.
AccessIPv6 string `json:"accessIPv6,omitempty"`
}
// ToServerUpdateMap formats an UpdateOpts structure into a request body.
func (opts UpdateOpts) ToServerUpdateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "server")
}
// Update requests that various attributes of the indicated server be changed.
func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
b, err := opts.ToServerUpdateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// ChangeAdminPassword alters the administrator or root password for a specified server.
func ChangeAdminPassword(client *gophercloud.ServiceClient, id, newPassword string) (r ActionResult) {
b := map[string]interface{}{
"changePassword": map[string]string{
"adminPass": newPassword,
},
}
_, r.Err = client.Post(actionURL(client, id), b, nil, nil)
return
}
// RebootMethod describes the mechanisms by which a server reboot can be requested.
type RebootMethod string
// These constants determine how a server should be rebooted.
// See the Reboot() function for further details.
const (
SoftReboot RebootMethod = "SOFT"
HardReboot RebootMethod = "HARD"
OSReboot = SoftReboot
PowerCycle = HardReboot
)
// RebootOptsBuilder is an interface that options must satisfy in order to be
// used when rebooting a server instance
type RebootOptsBuilder interface {
ToServerRebootMap() (map[string]interface{}, error)
}
// RebootOpts satisfies the RebootOptsBuilder interface
type RebootOpts struct {
Type RebootMethod `json:"type" required:"true"`
}
// ToServerRebootMap allows RebootOpts to satisfiy the RebootOptsBuilder
// interface
func (opts *RebootOpts) ToServerRebootMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "reboot")
}
// Reboot requests that a given server reboot.
// Two methods exist for rebooting a server:
//
// HardReboot (aka PowerCycle) starts the server instance by physically cutting power to the machine, or if a VM,
// terminating it at the hypervisor level.
// It's done. Caput. Full stop.
// Then, after a brief while, power is rtored or the VM instance rtarted.
//
// SoftReboot (aka OSReboot) simply tells the OS to rtart under its own procedur.
// E.g., in Linux, asking it to enter runlevel 6, or executing "sudo shutdown -r now", or by asking Windows to rtart the machine.
func Reboot(client *gophercloud.ServiceClient, id string, opts RebootOptsBuilder) (r ActionResult) {
b, err := opts.ToServerRebootMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(actionURL(client, id), b, nil, nil)
return
}
// RebuildOptsBuilder is an interface that allows extensions to override the
// default behaviour of rebuild options
type RebuildOptsBuilder interface {
ToServerRebuildMap() (map[string]interface{}, error)
}
// RebuildOpts represents the configuration options used in a server rebuild
// operation
type RebuildOpts struct {
// The server's admin password
AdminPass string `json:"adminPass" required:"true"`
// The ID of the image you want your server to be provisioned on
ImageID string `json:"imageRef"`
ImageName string `json:"-"`
//ImageName string `json:"-"`
// Name to set the server to
Name string `json:"name,omitempty"`
// AccessIPv4 [optional] provides a new IPv4 address for the instance.
AccessIPv4 string `json:"accessIPv4,omitempty"`
// AccessIPv6 [optional] provides a new IPv6 address for the instance.
AccessIPv6 string `json:"accessIPv6,omitempty"`
// Metadata [optional] contains key-value pairs (up to 255 bytes each) to attach to the server.
Metadata map[string]string `json:"metadata,omitempty"`
// Personality [optional] includes files to inject into the server at launch.
// Rebuild will base64-encode file contents for you.
Personality Personality `json:"personality,omitempty"`
ServiceClient *gophercloud.ServiceClient `json:"-"`
}
// ToServerRebuildMap formats a RebuildOpts struct into a map for use in JSON
func (opts RebuildOpts) ToServerRebuildMap() (map[string]interface{}, error) {
b, err := gophercloud.BuildRequestBody(opts, "")
if err != nil {
return nil, err
}
// If ImageRef isn't provided, check if ImageName was provided to ascertain
// the image ID.
if opts.ImageID == "" {
if opts.ImageName != "" {
if opts.ServiceClient == nil {
err := ErrNoClientProvidedForIDByName{}
err.Argument = "ServiceClient"
return nil, err
}
imageID, err := images.IDFromName(opts.ServiceClient, opts.ImageName)
if err != nil {
return nil, err
}
b["imageRef"] = imageID
}
}
return map[string]interface{}{"rebuild": b}, nil
}
// Rebuild will reprovision the server according to the configuration options
// provided in the RebuildOpts struct.
func Rebuild(client *gophercloud.ServiceClient, id string, opts RebuildOptsBuilder) (r RebuildResult) {
b, err := opts.ToServerRebuildMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(actionURL(client, id), b, &r.Body, nil)
return
}
// ResizeOptsBuilder is an interface that allows extensions to override the default structure of
// a Resize request.
type ResizeOptsBuilder interface {
ToServerResizeMap() (map[string]interface{}, error)
}
// ResizeOpts represents the configuration options used to control a Resize operation.
type ResizeOpts struct {
// FlavorRef is the ID of the flavor you wish your server to become.
FlavorRef string `json:"flavorRef" required:"true"`
}
// ToServerResizeMap formats a ResizeOpts as a map that can be used as a JSON request body for the
// Resize request.
func (opts ResizeOpts) ToServerResizeMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "resize")
}
// Resize instructs the provider to change the flavor of the server.
// Note that this implies rebuilding it.
// Unfortunately, one cannot pass rebuild parameters to the resize function.
// When the resize completes, the server will be in RESIZE_VERIFY state.
// While in this state, you can explore the use of the new server's configuration.
// If you like it, call ConfirmResize() to commit the resize permanently.
// Otherwise, call RevertResize() to restore the old configuration.
func Resize(client *gophercloud.ServiceClient, id string, opts ResizeOptsBuilder) (r ActionResult) {
b, err := opts.ToServerResizeMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(actionURL(client, id), b, nil, nil)
return
}
// ConfirmResize confirms a previous resize operation on a server.
// See Resize() for more details.
func ConfirmResize(client *gophercloud.ServiceClient, id string) (r ActionResult) {
_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"confirmResize": nil}, nil, &gophercloud.RequestOpts{
OkCodes: []int{201, 202, 204},
})
return
}
// RevertResize cancels a previous resize operation on a server.
// See Resize() for more details.
func RevertResize(client *gophercloud.ServiceClient, id string) (r ActionResult) {
_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"revertResize": nil}, nil, nil)
return
}
// RescueOptsBuilder is an interface that allows extensions to override the
// default structure of a Rescue request.
type RescueOptsBuilder interface {
ToServerRescueMap() (map[string]interface{}, error)
}
// RescueOpts represents the configuration options used to control a Rescue
// option.
type RescueOpts struct {
// AdminPass is the desired administrative password for the instance in
// RESCUE mode. If it's left blank, the server will generate a password.
AdminPass string `json:"adminPass,omitempty"`
}
// ToServerRescueMap formats a RescueOpts as a map that can be used as a JSON
// request body for the Rescue request.
func (opts RescueOpts) ToServerRescueMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "rescue")
}
// Rescue instructs the provider to place the server into RESCUE mode.
func Rescue(client *gophercloud.ServiceClient, id string, opts RescueOptsBuilder) (r RescueResult) {
b, err := opts.ToServerRescueMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// ResetMetadataOptsBuilder allows extensions to add additional parameters to the
// Reset request.
type ResetMetadataOptsBuilder interface {
ToMetadataResetMap() (map[string]interface{}, error)
}
// MetadataOpts is a map that contains key-value pairs.
type MetadataOpts map[string]string
// ToMetadataResetMap assembles a body for a Reset request based on the contents of a MetadataOpts.
func (opts MetadataOpts) ToMetadataResetMap() (map[string]interface{}, error) {
return map[string]interface{}{"metadata": opts}, nil
}
// ToMetadataUpdateMap assembles a body for an Update request based on the contents of a MetadataOpts.
func (opts MetadataOpts) ToMetadataUpdateMap() (map[string]interface{}, error) {
return map[string]interface{}{"metadata": opts}, nil
}
// ResetMetadata will create multiple new key-value pairs for the given server ID.
// Note: Using this operation will erase any already-existing metadata and create
// the new metadata provided. To keep any already-existing metadata, use the
// UpdateMetadatas or UpdateMetadata function.
func ResetMetadata(client *gophercloud.ServiceClient, id string, opts ResetMetadataOptsBuilder) (r ResetMetadataResult) {
b, err := opts.ToMetadataResetMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Put(metadataURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// Metadata requests all the metadata for the given server ID.
func Metadata(client *gophercloud.ServiceClient, id string) (r GetMetadataResult) {
_, r.Err = client.Get(metadataURL(client, id), &r.Body, nil)
return
}
// UpdateMetadataOptsBuilder allows extensions to add additional parameters to the
// Create request.
type UpdateMetadataOptsBuilder interface {
ToMetadataUpdateMap() (map[string]interface{}, error)
}
// UpdateMetadata updates (or creates) all the metadata specified by opts for the given server ID.
// This operation does not affect already-existing metadata that is not specified
// by opts.
func UpdateMetadata(client *gophercloud.ServiceClient, id string, opts UpdateMetadataOptsBuilder) (r UpdateMetadataResult) {
b, err := opts.ToMetadataUpdateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(metadataURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// MetadatumOptsBuilder allows extensions to add additional parameters to the
// Create request.
type MetadatumOptsBuilder interface {
ToMetadatumCreateMap() (map[string]interface{}, string, error)
}
// MetadatumOpts is a map of length one that contains a key-value pair.
type MetadatumOpts map[string]string
// ToMetadatumCreateMap assembles a body for a Create request based on the contents of a MetadataumOpts.
func (opts MetadatumOpts) ToMetadatumCreateMap() (map[string]interface{}, string, error) {
if len(opts) != 1 {
err := gophercloud.ErrInvalidInput{}
err.Argument = "servers.MetadatumOpts"
err.Info = "Must have 1 and only 1 key-value pair"
return nil, "", err
}
metadatum := map[string]interface{}{"meta": opts}
var key string
for k := range metadatum["meta"].(MetadatumOpts) {
key = k
}
return metadatum, key, nil
}
// CreateMetadatum will create or update the key-value pair with the given key for the given server ID.
func CreateMetadatum(client *gophercloud.ServiceClient, id string, opts MetadatumOptsBuilder) (r CreateMetadatumResult) {
b, key, err := opts.ToMetadatumCreateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Put(metadatumURL(client, id, key), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// Metadatum requests the key-value pair with the given key for the given server ID.
func Metadatum(client *gophercloud.ServiceClient, id, key string) (r GetMetadatumResult) {
_, r.Err = client.Get(metadatumURL(client, id, key), &r.Body, nil)
return
}
// DeleteMetadatum will delete the key-value pair with the given key for the given server ID.
func DeleteMetadatum(client *gophercloud.ServiceClient, id, key string) (r DeleteMetadatumResult) {
_, r.Err = client.Delete(metadatumURL(client, id, key), nil)
return
}
// ListAddresses makes a request against the API to list the servers IP addresses.
func ListAddresses(client *gophercloud.ServiceClient, id string) pagination.Pager {
return pagination.NewPager(client, listAddressesURL(client, id), func(r pagination.PageResult) pagination.Page {
return AddressPage{pagination.SinglePageBase(r)}
})
}
// ListAddressesByNetwork makes a request against the API to list the servers IP addresses
// for the given network.
func ListAddressesByNetwork(client *gophercloud.ServiceClient, id, network string) pagination.Pager {
return pagination.NewPager(client, listAddressesByNetworkURL(client, id, network), func(r pagination.PageResult) pagination.Page {
return NetworkAddressPage{pagination.SinglePageBase(r)}
})
}
// CreateImageOptsBuilder is the interface types must satisfy in order to be
// used as CreateImage options
type CreateImageOptsBuilder interface {
ToServerCreateImageMap() (map[string]interface{}, error)
}
// CreateImageOpts satisfies the CreateImageOptsBuilder
type CreateImageOpts struct {
// Name of the image/snapshot
Name string `json:"name" required:"true"`
// Metadata contains key-value pairs (up to 255 bytes each) to attach to the created image.
Metadata map[string]string `json:"metadata,omitempty"`
}
// ToServerCreateImageMap formats a CreateImageOpts structure into a request body.
func (opts CreateImageOpts) ToServerCreateImageMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "createImage")
}
// CreateImage makes a request against the nova API to schedule an image to be created of the server
func CreateImage(client *gophercloud.ServiceClient, id string, opts CreateImageOptsBuilder) (r CreateImageResult) {
b, err := opts.ToServerCreateImageMap()
if err != nil {
r.Err = err
return
}
resp, err := client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
OkCodes: []int{202},
})
r.Err = err
r.Header = resp.Header
return
}
// IDFromName is a convienience function that returns a server's ID given its name.
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
count := 0
id := ""
allPages, err := List(client, nil).AllPages()
if err != nil {
return "", err
}
all, err := ExtractServers(allPages)
if err != nil {
return "", err
}
for _, f := range all {
if f.Name == name {
count++
id = f.ID
}
}
switch count {
case 0:
return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "server"}
case 1:
return id, nil
default:
return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "server"}
}
}
// GetPassword makes a request against the nova API to get the encrypted administrative password.
func GetPassword(client *gophercloud.ServiceClient, serverId string) (r GetPasswordResult) {
_, r.Err = client.Get(passwordURL(client, serverId), &r.Body, nil)
return
}

View File

@ -0,0 +1,344 @@
package servers
import (
"crypto/rsa"
"encoding/base64"
"encoding/json"
"fmt"
"net/url"
"path"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
type serverResult struct {
gophercloud.Result
}
// Extract interprets any serverResult as a Server, if possible.
func (r serverResult) Extract() (*Server, error) {
var s struct {
Server *Server `json:"server"`
}
err := r.ExtractInto(&s)
return s.Server, err
}
// CreateResult temporarily contains the response from a Create call.
type CreateResult struct {
serverResult
}
// GetResult temporarily contains the response from a Get call.
type GetResult struct {
serverResult
}
// UpdateResult temporarily contains the response from an Update call.
type UpdateResult struct {
serverResult
}
// DeleteResult temporarily contains the response from a Delete call.
type DeleteResult struct {
gophercloud.ErrResult
}
// RebuildResult temporarily contains the response from a Rebuild call.
type RebuildResult struct {
serverResult
}
// ActionResult represents the result of server action operations, like reboot
type ActionResult struct {
gophercloud.ErrResult
}
// RescueResult represents the result of a server rescue operation
type RescueResult struct {
ActionResult
}
// CreateImageResult represents the result of an image creation operation
type CreateImageResult struct {
gophercloud.Result
}
// GetPasswordResult represent the result of a get os-server-password operation.
type GetPasswordResult struct {
gophercloud.Result
}
// ExtractPassword gets the encrypted password.
// If privateKey != nil the password is decrypted with the private key.
// If privateKey == nil the encrypted password is returned and can be decrypted with:
// echo '<pwd>' | base64 -D | openssl rsautl -decrypt -inkey <private_key>
func (r GetPasswordResult) ExtractPassword(privateKey *rsa.PrivateKey) (string, error) {
var s struct {
Password string `json:"password"`
}
err := r.ExtractInto(&s)
if err == nil && privateKey != nil && s.Password != "" {
return decryptPassword(s.Password, privateKey)
}
return s.Password, err
}
func decryptPassword(encryptedPassword string, privateKey *rsa.PrivateKey) (string, error) {
b64EncryptedPassword := make([]byte, base64.StdEncoding.DecodedLen(len(encryptedPassword)))
n, err := base64.StdEncoding.Decode(b64EncryptedPassword, []byte(encryptedPassword))
if err != nil {
return "", fmt.Errorf("Failed to base64 decode encrypted password: %s", err)
}
password, err := rsa.DecryptPKCS1v15(nil, privateKey, b64EncryptedPassword[0:n])
if err != nil {
return "", fmt.Errorf("Failed to decrypt password: %s", err)
}
return string(password), nil
}
// ExtractImageID gets the ID of the newly created server image from the header
func (res CreateImageResult) ExtractImageID() (string, error) {
if res.Err != nil {
return "", res.Err
}
// Get the image id from the header
u, err := url.ParseRequestURI(res.Header.Get("Location"))
if err != nil {
return "", err
}
imageID := path.Base(u.Path)
if imageID == "." || imageID == "/" {
return "", fmt.Errorf("Failed to parse the ID of newly created image: %s", u)
}
return imageID, nil
}
// Extract interprets any RescueResult as an AdminPass, if possible.
func (r RescueResult) Extract() (string, error) {
var s struct {
AdminPass string `json:"adminPass"`
}
err := r.ExtractInto(&s)
return s.AdminPass, err
}
// Server exposes only the standard OpenStack fields corresponding to a given server on the user's account.
type Server struct {
// ID uniquely identifies this server amongst all other servers, including those not accessible to the current tenant.
ID string `json:"id"`
// TenantID identifies the tenant owning this server resource.
TenantID string `json:"tenant_id"`
// UserID uniquely identifies the user account owning the tenant.
UserID string `json:"user_id"`
// Name contains the human-readable name for the server.
Name string `json:"name"`
// Updated and Created contain ISO-8601 timestamps of when the state of the server last changed, and when it was created.
Updated string
Created string
HostID string
// Status contains the current operational status of the server, such as IN_PROGRESS or ACTIVE.
Status string
// Progress ranges from 0..100.
// A request made against the server completes only once Progress reaches 100.
Progress int
// AccessIPv4 and AccessIPv6 contain the IP addresses of the server, suitable for remote access for administration.
AccessIPv4, AccessIPv6 string
// Image refers to a JSON object, which itself indicates the OS image used to deploy the server.
Image map[string]interface{}
// Flavor refers to a JSON object, which itself indicates the hardware configuration of the deployed server.
Flavor map[string]interface{}
// Addresses includes a list of all IP addresses assigned to the server, keyed by pool.
Addresses map[string]interface{}
// Metadata includes a list of all user-specified key-value pairs attached to the server.
Metadata map[string]string
// Links includes HTTP references to the itself, useful for passing along to other APIs that might want a server reference.
Links []interface{}
// KeyName indicates which public key was injected into the server on launch.
KeyName string `json:"key_name"`
// AdminPass will generally be empty (""). However, it will contain the administrative password chosen when provisioning a new server without a set AdminPass setting in the first place.
// Note that this is the ONLY time this field will be valid.
AdminPass string `json:"adminPass"`
// SecurityGroups includes the security groups that this instance has applied to it
SecurityGroups []map[string]interface{} `json:"security_groups"`
}
func (s *Server) UnmarshalJSON(b []byte) error {
type tmp Server
var server *struct {
tmp
Image interface{}
}
err := json.Unmarshal(b, &server)
if err != nil {
return err
}
*s = Server(server.tmp)
switch t := server.Image.(type) {
case map[string]interface{}:
s.Image = t
case string:
switch t {
case "":
s.Image = nil
}
}
return nil
}
// ServerPage abstracts the raw results of making a List() request against the API.
// As OpenStack extensions may freely alter the response bodies of structures returned to the client, you may only safely access the
// data provided through the ExtractServers call.
type ServerPage struct {
pagination.LinkedPageBase
}
// IsEmpty returns true if a page contains no Server results.
func (page ServerPage) IsEmpty() (bool, error) {
servers, err := ExtractServers(page)
return len(servers) == 0, err
}
// NextPageURL uses the response's embedded link reference to navigate to the next page of results.
func (page ServerPage) NextPageURL() (string, error) {
var s struct {
Links []gophercloud.Link `json:"servers_links"`
}
err := page.ExtractInto(&s)
if err != nil {
return "", err
}
return gophercloud.ExtractNextURL(s.Links)
}
// ExtractServers interprets the results of a single page from a List() call, producing a slice of Server entities.
func ExtractServers(r pagination.Page) ([]Server, error) {
var s struct {
Servers []Server `json:"servers"`
}
err := (r.(ServerPage)).ExtractInto(&s)
return s.Servers, err
}
// MetadataResult contains the result of a call for (potentially) multiple key-value pairs.
type MetadataResult struct {
gophercloud.Result
}
// GetMetadataResult temporarily contains the response from a metadata Get call.
type GetMetadataResult struct {
MetadataResult
}
// ResetMetadataResult temporarily contains the response from a metadata Reset call.
type ResetMetadataResult struct {
MetadataResult
}
// UpdateMetadataResult temporarily contains the response from a metadata Update call.
type UpdateMetadataResult struct {
MetadataResult
}
// MetadatumResult contains the result of a call for individual a single key-value pair.
type MetadatumResult struct {
gophercloud.Result
}
// GetMetadatumResult temporarily contains the response from a metadatum Get call.
type GetMetadatumResult struct {
MetadatumResult
}
// CreateMetadatumResult temporarily contains the response from a metadatum Create call.
type CreateMetadatumResult struct {
MetadatumResult
}
// DeleteMetadatumResult temporarily contains the response from a metadatum Delete call.
type DeleteMetadatumResult struct {
gophercloud.ErrResult
}
// Extract interprets any MetadataResult as a Metadata, if possible.
func (r MetadataResult) Extract() (map[string]string, error) {
var s struct {
Metadata map[string]string `json:"metadata"`
}
err := r.ExtractInto(&s)
return s.Metadata, err
}
// Extract interprets any MetadatumResult as a Metadatum, if possible.
func (r MetadatumResult) Extract() (map[string]string, error) {
var s struct {
Metadatum map[string]string `json:"meta"`
}
err := r.ExtractInto(&s)
return s.Metadatum, err
}
// Address represents an IP address.
type Address struct {
Version int `json:"version"`
Address string `json:"addr"`
}
// AddressPage abstracts the raw results of making a ListAddresses() request against the API.
// As OpenStack extensions may freely alter the response bodies of structures returned
// to the client, you may only safely access the data provided through the ExtractAddresses call.
type AddressPage struct {
pagination.SinglePageBase
}
// IsEmpty returns true if an AddressPage contains no networks.
func (r AddressPage) IsEmpty() (bool, error) {
addresses, err := ExtractAddresses(r)
return len(addresses) == 0, err
}
// ExtractAddresses interprets the results of a single page from a ListAddresses() call,
// producing a map of addresses.
func ExtractAddresses(r pagination.Page) (map[string][]Address, error) {
var s struct {
Addresses map[string][]Address `json:"addresses"`
}
err := (r.(AddressPage)).ExtractInto(&s)
return s.Addresses, err
}
// NetworkAddressPage abstracts the raw results of making a ListAddressesByNetwork() request against the API.
// As OpenStack extensions may freely alter the response bodies of structures returned
// to the client, you may only safely access the data provided through the ExtractAddresses call.
type NetworkAddressPage struct {
pagination.SinglePageBase
}
// IsEmpty returns true if a NetworkAddressPage contains no addresses.
func (r NetworkAddressPage) IsEmpty() (bool, error) {
addresses, err := ExtractNetworkAddresses(r)
return len(addresses) == 0, err
}
// ExtractNetworkAddresses interprets the results of a single page from a ListAddressesByNetwork() call,
// producing a slice of addresses.
func ExtractNetworkAddresses(r pagination.Page) ([]Address, error) {
var s map[string][]Address
err := (r.(NetworkAddressPage)).ExtractInto(&s)
if err != nil {
return nil, err
}
var key string
for k := range s {
key = k
}
return s[key], err
}

View File

@ -0,0 +1,51 @@
package servers
import "github.com/gophercloud/gophercloud"
func createURL(client *gophercloud.ServiceClient) string {
return client.ServiceURL("servers")
}
func listURL(client *gophercloud.ServiceClient) string {
return createURL(client)
}
func listDetailURL(client *gophercloud.ServiceClient) string {
return client.ServiceURL("servers", "detail")
}
func deleteURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("servers", id)
}
func getURL(client *gophercloud.ServiceClient, id string) string {
return deleteURL(client, id)
}
func updateURL(client *gophercloud.ServiceClient, id string) string {
return deleteURL(client, id)
}
func actionURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("servers", id, "action")
}
func metadatumURL(client *gophercloud.ServiceClient, id, key string) string {
return client.ServiceURL("servers", id, "metadata", key)
}
func metadataURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("servers", id, "metadata")
}
func listAddressesURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("servers", id, "ips")
}
func listAddressesByNetworkURL(client *gophercloud.ServiceClient, id, network string) string {
return client.ServiceURL("servers", id, "ips", network)
}
func passwordURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("servers", id, "os-server-password")
}

View File

@ -0,0 +1,20 @@
package servers
import "github.com/gophercloud/gophercloud"
// WaitForStatus will continually poll a server until it successfully transitions to a specified
// status. It will do this for at most the number of seconds specified.
func WaitForStatus(c *gophercloud.ServiceClient, id, status string, secs int) error {
return gophercloud.WaitFor(secs, func() (bool, error) {
current, err := Get(c, id).Extract()
if err != nil {
return false, err
}
if current.Status == status {
return true, nil
}
return false, nil
})
}

View File

@ -0,0 +1,99 @@
package openstack
import (
"github.com/gophercloud/gophercloud"
tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
)
// V2EndpointURL discovers the endpoint URL for a specific service from a ServiceCatalog acquired
// during the v2 identity service. The specified EndpointOpts are used to identify a unique,
// unambiguous endpoint to return. It's an error both when multiple endpoints match the provided
// criteria and when none do. The minimum that can be specified is a Type, but you will also often
// need to specify a Name and/or a Region depending on what's available on your OpenStack
// deployment.
func V2EndpointURL(catalog *tokens2.ServiceCatalog, opts gophercloud.EndpointOpts) (string, error) {
// Extract Endpoints from the catalog entries that match the requested Type, Name if provided, and Region if provided.
var endpoints = make([]tokens2.Endpoint, 0, 1)
for _, entry := range catalog.Entries {
if (entry.Type == opts.Type) && (opts.Name == "" || entry.Name == opts.Name) {
for _, endpoint := range entry.Endpoints {
if opts.Region == "" || endpoint.Region == opts.Region {
endpoints = append(endpoints, endpoint)
}
}
}
}
// Report an error if the options were ambiguous.
if len(endpoints) > 1 {
err := &ErrMultipleMatchingEndpointsV2{}
err.Endpoints = endpoints
return "", err
}
// Extract the appropriate URL from the matching Endpoint.
for _, endpoint := range endpoints {
switch opts.Availability {
case gophercloud.AvailabilityPublic:
return gophercloud.NormalizeURL(endpoint.PublicURL), nil
case gophercloud.AvailabilityInternal:
return gophercloud.NormalizeURL(endpoint.InternalURL), nil
case gophercloud.AvailabilityAdmin:
return gophercloud.NormalizeURL(endpoint.AdminURL), nil
default:
err := &ErrInvalidAvailabilityProvided{}
err.Argument = "Availability"
err.Value = opts.Availability
return "", err
}
}
// Report an error if there were no matching endpoints.
err := &gophercloud.ErrEndpointNotFound{}
return "", err
}
// V3EndpointURL discovers the endpoint URL for a specific service from a Catalog acquired
// during the v3 identity service. The specified EndpointOpts are used to identify a unique,
// unambiguous endpoint to return. It's an error both when multiple endpoints match the provided
// criteria and when none do. The minimum that can be specified is a Type, but you will also often
// need to specify a Name and/or a Region depending on what's available on your OpenStack
// deployment.
func V3EndpointURL(catalog *tokens3.ServiceCatalog, opts gophercloud.EndpointOpts) (string, error) {
// Extract Endpoints from the catalog entries that match the requested Type, Interface,
// Name if provided, and Region if provided.
var endpoints = make([]tokens3.Endpoint, 0, 1)
for _, entry := range catalog.Entries {
if (entry.Type == opts.Type) && (opts.Name == "" || entry.Name == opts.Name) {
for _, endpoint := range entry.Endpoints {
if opts.Availability != gophercloud.AvailabilityAdmin &&
opts.Availability != gophercloud.AvailabilityPublic &&
opts.Availability != gophercloud.AvailabilityInternal {
err := &ErrInvalidAvailabilityProvided{}
err.Argument = "Availability"
err.Value = opts.Availability
return "", err
}
if (opts.Availability == gophercloud.Availability(endpoint.Interface)) &&
(opts.Region == "" || endpoint.Region == opts.Region) {
endpoints = append(endpoints, endpoint)
}
}
}
}
// Report an error if the options were ambiguous.
if len(endpoints) > 1 {
return "", ErrMultipleMatchingEndpointsV3{Endpoints: endpoints}
}
// Extract the URL from the matching Endpoint.
for _, endpoint := range endpoints {
return gophercloud.NormalizeURL(endpoint.URL), nil
}
// Report an error if there were no matching endpoints.
err := &gophercloud.ErrEndpointNotFound{}
return "", err
}

View File

@ -0,0 +1,71 @@
package openstack
import (
"fmt"
"github.com/gophercloud/gophercloud"
tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
)
// ErrEndpointNotFound is the error when no suitable endpoint can be found
// in the user's catalog
type ErrEndpointNotFound struct{ gophercloud.BaseError }
func (e ErrEndpointNotFound) Error() string {
return "No suitable endpoint could be found in the service catalog."
}
// ErrInvalidAvailabilityProvided is the error when an invalid endpoint
// availability is provided
type ErrInvalidAvailabilityProvided struct{ gophercloud.ErrInvalidInput }
func (e ErrInvalidAvailabilityProvided) Error() string {
return fmt.Sprintf("Unexpected availability in endpoint query: %s", e.Value)
}
// ErrMultipleMatchingEndpointsV2 is the error when more than one endpoint
// for the given options is found in the v2 catalog
type ErrMultipleMatchingEndpointsV2 struct {
gophercloud.BaseError
Endpoints []tokens2.Endpoint
}
func (e ErrMultipleMatchingEndpointsV2) Error() string {
return fmt.Sprintf("Discovered %d matching endpoints: %#v", len(e.Endpoints), e.Endpoints)
}
// ErrMultipleMatchingEndpointsV3 is the error when more than one endpoint
// for the given options is found in the v3 catalog
type ErrMultipleMatchingEndpointsV3 struct {
gophercloud.BaseError
Endpoints []tokens3.Endpoint
}
func (e ErrMultipleMatchingEndpointsV3) Error() string {
return fmt.Sprintf("Discovered %d matching endpoints: %#v", len(e.Endpoints), e.Endpoints)
}
// ErrNoAuthURL is the error when the OS_AUTH_URL environment variable is not
// found
type ErrNoAuthURL struct{ gophercloud.ErrInvalidInput }
func (e ErrNoAuthURL) Error() string {
return "Environment variable OS_AUTH_URL needs to be set."
}
// ErrNoUsername is the error when the OS_USERNAME environment variable is not
// found
type ErrNoUsername struct{ gophercloud.ErrInvalidInput }
func (e ErrNoUsername) Error() string {
return "Environment variable OS_USERNAME needs to be set."
}
// ErrNoPassword is the error when the OS_PASSWORD environment variable is not
// found
type ErrNoPassword struct{ gophercloud.ErrInvalidInput }
func (e ErrNoPassword) Error() string {
return "Environment variable OS_PASSWORD needs to be set."
}

View File

@ -0,0 +1,7 @@
// Package tenants provides information and interaction with the
// tenants API resource for the OpenStack Identity service.
//
// See http://developer.openstack.org/api-ref-identity-v2.html#identity-auth-v2
// and http://developer.openstack.org/api-ref-identity-v2.html#admin-tenants
// for more information.
package tenants

View File

@ -0,0 +1,29 @@
package tenants
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// ListOpts filters the Tenants that are returned by the List call.
type ListOpts struct {
// Marker is the ID of the last Tenant on the previous page.
Marker string `q:"marker"`
// Limit specifies the page size.
Limit int `q:"limit"`
}
// List enumerates the Tenants to which the current token has access.
func List(client *gophercloud.ServiceClient, opts *ListOpts) pagination.Pager {
url := listURL(client)
if opts != nil {
q, err := gophercloud.BuildQueryString(opts)
if err != nil {
return pagination.Pager{Err: err}
}
url += q.String()
}
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
return TenantPage{pagination.LinkedPageBase{PageResult: r}}
})
}

View File

@ -0,0 +1,53 @@
package tenants
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// Tenant is a grouping of users in the identity service.
type Tenant struct {
// ID is a unique identifier for this tenant.
ID string `json:"id"`
// Name is a friendlier user-facing name for this tenant.
Name string `json:"name"`
// Description is a human-readable explanation of this Tenant's purpose.
Description string `json:"description"`
// Enabled indicates whether or not a tenant is active.
Enabled bool `json:"enabled"`
}
// TenantPage is a single page of Tenant results.
type TenantPage struct {
pagination.LinkedPageBase
}
// IsEmpty determines whether or not a page of Tenants contains any results.
func (r TenantPage) IsEmpty() (bool, error) {
tenants, err := ExtractTenants(r)
return len(tenants) == 0, err
}
// NextPageURL extracts the "next" link from the tenants_links section of the result.
func (r TenantPage) NextPageURL() (string, error) {
var s struct {
Links []gophercloud.Link `json:"tenants_links"`
}
err := r.ExtractInto(&s)
if err != nil {
return "", err
}
return gophercloud.ExtractNextURL(s.Links)
}
// ExtractTenants returns a slice of Tenants contained in a single page of results.
func ExtractTenants(r pagination.Page) ([]Tenant, error) {
var s struct {
Tenants []Tenant `json:"tenants"`
}
err := (r.(TenantPage)).ExtractInto(&s)
return s.Tenants, err
}

View File

@ -0,0 +1,7 @@
package tenants
import "github.com/gophercloud/gophercloud"
func listURL(client *gophercloud.ServiceClient) string {
return client.ServiceURL("tenants")
}

View File

@ -0,0 +1,5 @@
// Package tokens provides information and interaction with the token API
// resource for the OpenStack Identity service.
// For more information, see:
// http://developer.openstack.org/api-ref-identity-v2.html#identity-auth-v2
package tokens

View File

@ -0,0 +1,99 @@
package tokens
import "github.com/gophercloud/gophercloud"
type PasswordCredentialsV2 struct {
Username string `json:"username" required:"true"`
Password string `json:"password" required:"true"`
}
type TokenCredentialsV2 struct {
ID string `json:"id,omitempty" required:"true"`
}
// AuthOptionsV2 wraps a gophercloud AuthOptions in order to adhere to the AuthOptionsBuilder
// interface.
type AuthOptionsV2 struct {
PasswordCredentials *PasswordCredentialsV2 `json:"passwordCredentials,omitempty" xor:"TokenCredentials"`
// The TenantID and TenantName fields are optional for the Identity V2 API.
// Some providers allow you to specify a TenantName instead of the TenantId.
// Some require both. Your provider's authentication policies will determine
// how these fields influence authentication.
TenantID string `json:"tenantId,omitempty"`
TenantName string `json:"tenantName,omitempty"`
// TokenCredentials allows users to authenticate (possibly as another user) with an
// authentication token ID.
TokenCredentials *TokenCredentialsV2 `json:"token,omitempty" xor:"PasswordCredentials"`
}
// AuthOptionsBuilder describes any argument that may be passed to the Create call.
type AuthOptionsBuilder interface {
// ToTokenCreateMap assembles the Create request body, returning an error if parameters are
// missing or inconsistent.
ToTokenV2CreateMap() (map[string]interface{}, error)
}
// AuthOptions are the valid options for Openstack Identity v2 authentication.
// For field descriptions, see gophercloud.AuthOptions.
type AuthOptions struct {
IdentityEndpoint string `json:"-"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
TenantID string `json:"tenantId,omitempty"`
TenantName string `json:"tenantName,omitempty"`
AllowReauth bool `json:"-"`
TokenID string
}
// ToTokenV2CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder
// interface in the v2 tokens package
func (opts AuthOptions) ToTokenV2CreateMap() (map[string]interface{}, error) {
v2Opts := AuthOptionsV2{
TenantID: opts.TenantID,
TenantName: opts.TenantName,
}
if opts.Password != "" {
v2Opts.PasswordCredentials = &PasswordCredentialsV2{
Username: opts.Username,
Password: opts.Password,
}
} else {
v2Opts.TokenCredentials = &TokenCredentialsV2{
ID: opts.TokenID,
}
}
b, err := gophercloud.BuildRequestBody(v2Opts, "auth")
if err != nil {
return nil, err
}
return b, nil
}
// Create authenticates to the identity service and attempts to acquire a Token.
// If successful, the CreateResult
// Generally, rather than interact with this call directly, end users should call openstack.AuthenticatedClient(),
// which abstracts all of the gory details about navigating service catalogs and such.
func Create(client *gophercloud.ServiceClient, auth AuthOptionsBuilder) (r CreateResult) {
b, err := auth.ToTokenV2CreateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(CreateURL(client), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 203},
MoreHeaders: map[string]string{"X-Auth-Token": ""},
})
return
}
// Get validates and retrieves information for user's token.
func Get(client *gophercloud.ServiceClient, token string) (r GetResult) {
_, r.Err = client.Get(GetURL(client, token), &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 203},
})
return
}

View File

@ -0,0 +1,149 @@
package tokens
import (
"time"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/identity/v2/tenants"
)
// Token provides only the most basic information related to an authentication token.
type Token struct {
// ID provides the primary means of identifying a user to the OpenStack API.
// OpenStack defines this field as an opaque value, so do not depend on its content.
// It is safe, however, to compare for equality.
ID string
// ExpiresAt provides a timestamp in ISO 8601 format, indicating when the authentication token becomes invalid.
// After this point in time, future API requests made using this authentication token will respond with errors.
// Either the caller will need to reauthenticate manually, or more preferably, the caller should exploit automatic re-authentication.
// See the AuthOptions structure for more details.
ExpiresAt time.Time
// Tenant provides information about the tenant to which this token grants access.
Tenant tenants.Tenant
}
// Role is a role for a user.
type Role struct {
Name string `json:"name"`
}
// User is an OpenStack user.
type User struct {
ID string `json:"id"`
Name string `json:"name"`
UserName string `json:"username"`
Roles []Role `json:"roles"`
}
// Endpoint represents a single API endpoint offered by a service.
// It provides the public and internal URLs, if supported, along with a region specifier, again if provided.
// The significance of the Region field will depend upon your provider.
//
// In addition, the interface offered by the service will have version information associated with it
// through the VersionId, VersionInfo, and VersionList fields, if provided or supported.
//
// In all cases, fields which aren't supported by the provider and service combined will assume a zero-value ("").
type Endpoint struct {
TenantID string `json:"tenantId"`
PublicURL string `json:"publicURL"`
InternalURL string `json:"internalURL"`
AdminURL string `json:"adminURL"`
Region string `json:"region"`
VersionID string `json:"versionId"`
VersionInfo string `json:"versionInfo"`
VersionList string `json:"versionList"`
}
// CatalogEntry provides a type-safe interface to an Identity API V2 service catalog listing.
// Each class of service, such as cloud DNS or block storage services, will have a single
// CatalogEntry representing it.
//
// Note: when looking for the desired service, try, whenever possible, to key off the type field.
// Otherwise, you'll tie the representation of the service to a specific provider.
type CatalogEntry struct {
// Name will contain the provider-specified name for the service.
Name string `json:"name"`
// Type will contain a type string if OpenStack defines a type for the service.
// Otherwise, for provider-specific services, the provider may assign their own type strings.
Type string `json:"type"`
// Endpoints will let the caller iterate over all the different endpoints that may exist for
// the service.
Endpoints []Endpoint `json:"endpoints"`
}
// ServiceCatalog provides a view into the service catalog from a previous, successful authentication.
type ServiceCatalog struct {
Entries []CatalogEntry
}
// CreateResult defers the interpretation of a created token.
// Use ExtractToken() to interpret it as a Token, or ExtractServiceCatalog() to interpret it as a service catalog.
type CreateResult struct {
gophercloud.Result
}
// GetResult is the deferred response from a Get call, which is the same with a Created token.
// Use ExtractUser() to interpret it as a User.
type GetResult struct {
CreateResult
}
// ExtractToken returns the just-created Token from a CreateResult.
func (r CreateResult) ExtractToken() (*Token, error) {
var s struct {
Access struct {
Token struct {
Expires string `json:"expires"`
ID string `json:"id"`
Tenant tenants.Tenant `json:"tenant"`
} `json:"token"`
} `json:"access"`
}
err := r.ExtractInto(&s)
if err != nil {
return nil, err
}
expiresTs, err := time.Parse(gophercloud.RFC3339Milli, s.Access.Token.Expires)
if err != nil {
return nil, err
}
return &Token{
ID: s.Access.Token.ID,
ExpiresAt: expiresTs,
Tenant: s.Access.Token.Tenant,
}, nil
}
// ExtractServiceCatalog returns the ServiceCatalog that was generated along with the user's Token.
func (r CreateResult) ExtractServiceCatalog() (*ServiceCatalog, error) {
var s struct {
Access struct {
Entries []CatalogEntry `json:"serviceCatalog"`
} `json:"access"`
}
err := r.ExtractInto(&s)
return &ServiceCatalog{Entries: s.Access.Entries}, err
}
// createErr quickly packs an error in a CreateResult.
func createErr(err error) CreateResult {
return CreateResult{gophercloud.Result{Err: err}}
}
// ExtractUser returns the User from a GetResult.
func (r GetResult) ExtractUser() (*User, error) {
var s struct {
Access struct {
User User `json:"user"`
} `json:"access"`
}
err := r.ExtractInto(&s)
return &s.Access.User, err
}

View File

@ -0,0 +1,13 @@
package tokens
import "github.com/gophercloud/gophercloud"
// CreateURL generates the URL used to create new Tokens.
func CreateURL(client *gophercloud.ServiceClient) string {
return client.ServiceURL("tokens")
}
// GetURL generates the URL used to Validate Tokens.
func GetURL(client *gophercloud.ServiceClient, token string) string {
return client.ServiceURL("tokens", token)
}

View File

@ -0,0 +1,34 @@
package trusts
import "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
type AuthOptsExt struct {
tokens.AuthOptionsBuilder
TrustID string `json:"id"`
}
func (opts AuthOptsExt) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) {
return opts.AuthOptionsBuilder.ToTokenV3CreateMap(scope)
}
func (opts AuthOptsExt) ToTokenV3ScopeMap() (map[string]interface{}, error) {
b, err := opts.AuthOptionsBuilder.ToTokenV3ScopeMap()
if err != nil {
return nil, err
}
if opts.TrustID != "" {
if b == nil {
b = make(map[string]interface{})
}
b["OS-TRUST:trust"] = map[string]interface{}{
"id": opts.TrustID,
}
}
return b, nil
}
func (opts AuthOptsExt) CanReauth() bool {
return opts.AuthOptionsBuilder.CanReauth()
}

View File

@ -0,0 +1,29 @@
package trusts
import "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
type TrusteeUser struct {
ID string `json:"id"`
}
type TrustorUser struct {
ID string `json:"id"`
}
type Trust struct {
ID string `json:"id"`
Impersonation bool `json:"impersonation"`
TrusteeUser TrusteeUser `json:"trustee_user"`
TrustorUser TrustorUser `json:"trustor_user"`
RedelegatedTrustID string `json:"redelegated_trust_id"`
RedelegationCount int `json:"redelegation_count"`
}
type Token struct {
tokens.Token
Trust Trust `json:"OS-TRUST:trust"`
}
type TokenExt struct {
Token Token `json:"token"`
}

View File

@ -0,0 +1,6 @@
// Package tokens provides information and interaction with the token API
// resource for the OpenStack Identity service.
//
// For more information, see:
// http://developer.openstack.org/api-ref-identity-v3.html#tokens-v3
package tokens

View File

@ -0,0 +1,200 @@
package tokens
import "github.com/gophercloud/gophercloud"
// Scope allows a created token to be limited to a specific domain or project.
type Scope struct {
ProjectID string `json:"scope.project.id,omitempty" not:"ProjectName,DomainID,DomainName"`
ProjectName string `json:"scope.project.name,omitempty"`
DomainID string `json:"scope.project.id,omitempty" not:"ProjectName,ProjectID,DomainName"`
DomainName string `json:"scope.project.id,omitempty"`
}
// AuthOptionsBuilder describes any argument that may be passed to the Create call.
type AuthOptionsBuilder interface {
// ToTokenV3CreateMap assembles the Create request body, returning an error if parameters are
// missing or inconsistent.
ToTokenV3CreateMap(map[string]interface{}) (map[string]interface{}, error)
ToTokenV3ScopeMap() (map[string]interface{}, error)
CanReauth() bool
}
type AuthOptions struct {
// IdentityEndpoint specifies the HTTP endpoint that is required to work with
// the Identity API of the appropriate version. While it's ultimately needed by
// all of the identity services, it will often be populated by a provider-level
// function.
IdentityEndpoint string `json:"-"`
// Username is required if using Identity V2 API. Consult with your provider's
// control panel to discover your account's username. In Identity V3, either
// UserID or a combination of Username and DomainID or DomainName are needed.
Username string `json:"username,omitempty"`
UserID string `json:"id,omitempty"`
Password string `json:"password,omitempty"`
// At most one of DomainID and DomainName must be provided if using Username
// with Identity V3. Otherwise, either are optional.
DomainID string `json:"id,omitempty"`
DomainName string `json:"name,omitempty"`
// AllowReauth should be set to true if you grant permission for Gophercloud to
// cache your credentials in memory, and to allow Gophercloud to attempt to
// 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.
AllowReauth bool `json:"-"`
// TokenID allows users to authenticate (possibly as another user) with an
// authentication token ID.
TokenID string `json:"-"`
Scope Scope `json:"-"`
}
func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) {
gophercloudAuthOpts := gophercloud.AuthOptions{
Username: opts.Username,
UserID: opts.UserID,
Password: opts.Password,
DomainID: opts.DomainID,
DomainName: opts.DomainName,
AllowReauth: opts.AllowReauth,
TokenID: opts.TokenID,
}
return gophercloudAuthOpts.ToTokenV3CreateMap(scope)
}
func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
if opts.Scope.ProjectName != "" {
// ProjectName provided: either DomainID or DomainName must also be supplied.
// ProjectID may not be supplied.
if opts.Scope.DomainID == "" && opts.Scope.DomainName == "" {
return nil, gophercloud.ErrScopeDomainIDOrDomainName{}
}
if opts.Scope.ProjectID != "" {
return nil, gophercloud.ErrScopeProjectIDOrProjectName{}
}
if opts.Scope.DomainID != "" {
// ProjectName + DomainID
return map[string]interface{}{
"project": map[string]interface{}{
"name": &opts.Scope.ProjectName,
"domain": map[string]interface{}{"id": &opts.Scope.DomainID},
},
}, nil
}
if opts.Scope.DomainName != "" {
// ProjectName + DomainName
return map[string]interface{}{
"project": map[string]interface{}{
"name": &opts.Scope.ProjectName,
"domain": map[string]interface{}{"name": &opts.Scope.DomainName},
},
}, nil
}
} else if opts.Scope.ProjectID != "" {
// ProjectID provided. ProjectName, DomainID, and DomainName may not be provided.
if opts.Scope.DomainID != "" {
return nil, gophercloud.ErrScopeProjectIDAlone{}
}
if opts.Scope.DomainName != "" {
return nil, gophercloud.ErrScopeProjectIDAlone{}
}
// ProjectID
return map[string]interface{}{
"project": map[string]interface{}{
"id": &opts.Scope.ProjectID,
},
}, nil
} else if opts.Scope.DomainID != "" {
// DomainID provided. ProjectID, ProjectName, and DomainName may not be provided.
if opts.Scope.DomainName != "" {
return nil, gophercloud.ErrScopeDomainIDOrDomainName{}
}
// DomainID
return map[string]interface{}{
"domain": map[string]interface{}{
"id": &opts.Scope.DomainID,
},
}, nil
} else if opts.Scope.DomainName != "" {
return nil, gophercloud.ErrScopeDomainName{}
}
return nil, nil
}
func (opts *AuthOptions) CanReauth() bool {
return opts.AllowReauth
}
func subjectTokenHeaders(c *gophercloud.ServiceClient, subjectToken string) map[string]string {
return map[string]string{
"X-Subject-Token": subjectToken,
}
}
// Create authenticates and either generates a new token, or changes the Scope of an existing token.
func Create(c *gophercloud.ServiceClient, opts AuthOptionsBuilder) (r CreateResult) {
scope, err := opts.ToTokenV3ScopeMap()
if err != nil {
r.Err = err
return
}
b, err := opts.ToTokenV3CreateMap(scope)
if err != nil {
r.Err = err
return
}
resp, err := c.Post(tokenURL(c), b, &r.Body, &gophercloud.RequestOpts{
MoreHeaders: map[string]string{"X-Auth-Token": ""},
})
r.Err = err
if resp != nil {
r.Header = resp.Header
}
return
}
// Get validates and retrieves information about another token.
func Get(c *gophercloud.ServiceClient, token string) (r GetResult) {
resp, err := c.Get(tokenURL(c), &r.Body, &gophercloud.RequestOpts{
MoreHeaders: subjectTokenHeaders(c, token),
OkCodes: []int{200, 203},
})
if resp != nil {
r.Err = err
r.Header = resp.Header
}
return
}
// Validate determines if a specified token is valid or not.
func Validate(c *gophercloud.ServiceClient, token string) (bool, error) {
resp, err := c.Request("HEAD", tokenURL(c), &gophercloud.RequestOpts{
MoreHeaders: subjectTokenHeaders(c, token),
OkCodes: []int{204, 404},
})
if err != nil {
return false, err
}
return resp.StatusCode == 204, nil
}
// Revoke immediately makes specified token invalid.
func Revoke(c *gophercloud.ServiceClient, token string) (r RevokeResult) {
_, r.Err = c.Delete(tokenURL(c), &gophercloud.RequestOpts{
MoreHeaders: subjectTokenHeaders(c, token),
})
return
}

View File

@ -0,0 +1,114 @@
package tokens
import "errors"
import "github.com/gophercloud/gophercloud"
// Endpoint represents a single API endpoint offered by a service.
// It matches either a public, internal or admin URL.
// If supported, it contains a region specifier, again if provided.
// The significance of the Region field will depend upon your provider.
type Endpoint struct {
ID string `json:"id"`
Region string `json:"region"`
Interface string `json:"interface"`
URL string `json:"url"`
}
// CatalogEntry provides a type-safe interface to an Identity API V3 service catalog listing.
// Each class of service, such as cloud DNS or block storage services, could have multiple
// CatalogEntry representing it (one by interface type, e.g public, admin or internal).
//
// Note: when looking for the desired service, try, whenever possible, to key off the type field.
// Otherwise, you'll tie the representation of the service to a specific provider.
type CatalogEntry struct {
// Service ID
ID string `json:"id"`
// Name will contain the provider-specified name for the service.
Name string `json:"name"`
// Type will contain a type string if OpenStack defines a type for the service.
// Otherwise, for provider-specific services, the provider may assign their own type strings.
Type string `json:"type"`
// Endpoints will let the caller iterate over all the different endpoints that may exist for
// the service.
Endpoints []Endpoint `json:"endpoints"`
}
// ServiceCatalog provides a view into the service catalog from a previous, successful authentication.
type ServiceCatalog struct {
Entries []CatalogEntry
}
// commonResult is the deferred result of a Create or a Get call.
type commonResult struct {
gophercloud.Result
}
// Extract is a shortcut for ExtractToken.
// This function is deprecated and still present for backward compatibility.
func (r commonResult) Extract() (*Token, error) {
return r.ExtractToken()
}
// ExtractToken interprets a commonResult as a Token.
func (r commonResult) ExtractToken() (*Token, error) {
var s struct {
Token *Token `json:"token"`
}
err := r.ExtractInto(&s)
if err != nil {
return nil, err
}
if s.Token == nil {
return nil, errors.New("'token' missing in JSON response")
}
// Parse the token itself from the stored headers.
s.Token.ID = r.Header.Get("X-Subject-Token")
return s.Token, err
}
// ExtractServiceCatalog returns the ServiceCatalog that was generated along with the user's Token.
func (r CreateResult) ExtractServiceCatalog() (*ServiceCatalog, error) {
var s struct {
Token struct {
Entries []CatalogEntry `json:"catalog"`
} `json:"token"`
}
err := r.ExtractInto(&s)
return &ServiceCatalog{Entries: s.Token.Entries}, err
}
// CreateResult defers the interpretation of a created token.
// Use ExtractToken() to interpret it as a Token, or ExtractServiceCatalog() to interpret it as a service catalog.
type CreateResult struct {
commonResult
}
// createErr quickly creates a CreateResult that reports an error.
func createErr(err error) CreateResult {
return CreateResult{
commonResult: commonResult{Result: gophercloud.Result{Err: err}},
}
}
// GetResult is the deferred response from a Get call.
type GetResult struct {
commonResult
}
// RevokeResult is the deferred response from a Revoke call.
type RevokeResult struct {
commonResult
}
// Token is a string that grants a user access to a controlled set of services in an OpenStack provider.
// Each Token is valid for a set length of time.
type Token struct {
// ID is the issued token.
ID string `json:"id"`
// ExpiresAt is the timestamp at which this token will no longer be accepted.
ExpiresAt gophercloud.JSONRFC3339Milli `json:"expires_at"`
}

View File

@ -0,0 +1,7 @@
package tokens
import "github.com/gophercloud/gophercloud"
func tokenURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("auth", "tokens")
}

View File

@ -1,9 +1,9 @@
package extensions
import (
"github.com/rackspace/gophercloud"
common "github.com/rackspace/gophercloud/openstack/common/extensions"
"github.com/rackspace/gophercloud/pagination"
"github.com/gophercloud/gophercloud"
common "github.com/gophercloud/gophercloud/openstack/common/extensions"
"github.com/gophercloud/gophercloud/pagination"
)
// Extension is a single OpenStack extension.

View File

@ -1,10 +1,8 @@
package floatingips
import (
"fmt"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// ListOpts allows the filtering and sorting of paginated collections through
@ -39,20 +37,28 @@ func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager {
})
}
// CreateOptsBuilder is the interface type must satisfy to be used as Create
// options.
type CreateOptsBuilder interface {
ToFloatingIPCreateMap() (map[string]interface{}, error)
}
// CreateOpts contains all the values needed to create a new floating IP
// resource. The only required fields are FloatingNetworkID and PortID which
// refer to the external network and internal port respectively.
type CreateOpts struct {
FloatingNetworkID string
FloatingIP string
PortID string
FixedIP string
TenantID string
FloatingNetworkID string `json:"floating_network_id" required:"true"`
FloatingIP string `json:"floating_ip_address,omitempty"`
PortID string `json:"port_id,omitempty"`
FixedIP string `json:"fixed_ip_address,omitempty"`
TenantID string `json:"tenant_id,omitempty"`
}
var (
errFloatingNetworkIDRequired = fmt.Errorf("A NetworkID is required")
)
// ToFloatingIPCreateMap allows CreateOpts to satisfy the CreateOptsBuilder
// interface
func (opts CreateOpts) ToFloatingIPCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "floatingip")
}
// Create accepts a CreateOpts struct and uses the values provided to create a
// new floating IP resource. You can create floating IPs on external networks
@ -78,45 +84,26 @@ var (
// operation will fail and return a 400 error code. If the PortID and FixedIP
// are already associated with another resource, the operation will fail and
// returns a 409 error code.
func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult {
var res CreateResult
// Validate
if opts.FloatingNetworkID == "" {
res.Err = errFloatingNetworkIDRequired
return res
func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToFloatingIPCreateMap()
if err != nil {
r.Err = err
return
}
// Define structures
type floatingIP struct {
FloatingNetworkID string `json:"floating_network_id"`
FloatingIP string `json:"floating_ip_address,omitempty"`
PortID string `json:"port_id,omitempty"`
FixedIP string `json:"fixed_ip_address,omitempty"`
TenantID string `json:"tenant_id,omitempty"`
}
type request struct {
FloatingIP floatingIP `json:"floatingip"`
}
// Populate request body
reqBody := request{FloatingIP: floatingIP{
FloatingNetworkID: opts.FloatingNetworkID,
FloatingIP: opts.FloatingIP,
PortID: opts.PortID,
FixedIP: opts.FixedIP,
TenantID: opts.TenantID,
}}
_, res.Err = c.Post(rootURL(c), reqBody, &res.Body, nil)
return res
_, r.Err = c.Post(rootURL(c), b, &r.Body, nil)
return
}
// Get retrieves a particular floating IP resource 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
func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = c.Get(resourceURL(c, id), &r.Body, nil)
return
}
// UpdateOptsBuilder is the interface type must satisfy to be used as Update
// options.
type UpdateOptsBuilder interface {
ToFloatingIPUpdateMap() (map[string]interface{}, error)
}
// UpdateOpts contains the values used when updating a floating IP resource. The
@ -124,45 +111,35 @@ func Get(c *gophercloud.ServiceClient, id string) GetResult {
// linked to. To associate the floating IP with a new internal port, provide its
// ID. To disassociate the floating IP from all ports, provide an empty string.
type UpdateOpts struct {
PortID string
PortID *string `json:"port_id"`
}
// ToFloatingIPUpdateMap allows UpdateOpts to satisfy the UpdateOptsBuilder
// interface
func (opts UpdateOpts) ToFloatingIPUpdateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "floatingip")
}
// Update allows floating IP resources to be updated. Currently, the only way to
// "update" a floating IP is to associate it with a new internal port, or
// disassociated it from all ports. See UpdateOpts for instructions of how to
// do this.
func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResult {
type floatingIP struct {
PortID *string `json:"port_id"`
func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
b, err := opts.ToFloatingIPUpdateMap()
if err != nil {
r.Err = err
return
}
type request struct {
FloatingIP floatingIP `json:"floatingip"`
}
var portID *string
if opts.PortID == "" {
portID = nil
} else {
portID = &opts.PortID
}
reqBody := request{FloatingIP: floatingIP{PortID: portID}}
// Send request to API
var res UpdateResult
_, res.Err = c.Put(resourceURL(c, id), reqBody, &res.Body, &gophercloud.RequestOpts{
_, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return res
return
}
// Delete will permanently delete a particular floating IP resource. Please
// ensure this is what you want - you can also disassociate the IP from existing
// internal ports.
func Delete(c *gophercloud.ServiceClient, id string) DeleteResult {
var res DeleteResult
_, res.Err = c.Delete(resourceURL(c, id), nil)
return res
func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = c.Delete(resourceURL(c, id), nil)
return
}

View File

@ -1,11 +1,8 @@
package floatingips
import (
"fmt"
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// FloatingIP represents a floating IP resource. A floating IP is an external
@ -16,27 +13,27 @@ import (
// attribute (provided by the external network extension) is set to True.
type FloatingIP struct {
// Unique identifier for the floating IP instance.
ID string `json:"id" mapstructure:"id"`
ID string `json:"id"`
// UUID of the external network where the floating IP is to be created.
FloatingNetworkID string `json:"floating_network_id" mapstructure:"floating_network_id"`
FloatingNetworkID string `json:"floating_network_id"`
// Address of the floating IP on the external network.
FloatingIP string `json:"floating_ip_address" mapstructure:"floating_ip_address"`
FloatingIP string `json:"floating_ip_address"`
// UUID of the port on an internal network that is associated with the floating IP.
PortID string `json:"port_id" mapstructure:"port_id"`
PortID string `json:"port_id"`
// The specific IP address of the internal port which should be associated
// with the floating IP.
FixedIP string `json:"fixed_ip_address" mapstructure:"fixed_ip_address"`
FixedIP string `json:"fixed_ip_address"`
// Owner of the floating IP. Only admin users can specify a tenant identifier
// other than its own.
TenantID string `json:"tenant_id" mapstructure:"tenant_id"`
TenantID string `json:"tenant_id"`
// The condition of the API resource.
Status string `json:"status" mapstructure:"status"`
Status string `json:"status"`
}
type commonResult struct {
@ -45,20 +42,11 @@ type commonResult struct {
// Extract a result and extracts a FloatingIP resource.
func (r commonResult) Extract() (*FloatingIP, error) {
if r.Err != nil {
return nil, r.Err
}
var res struct {
var s struct {
FloatingIP *FloatingIP `json:"floatingip"`
}
err := mapstructure.Decode(r.Body, &res)
if err != nil {
return nil, fmt.Errorf("Error decoding Neutron floating IP: %v", err)
}
return res.FloatingIP, nil
err := r.ExtractInto(&s)
return s.FloatingIP, err
}
// CreateResult represents the result of a create operation.
@ -90,38 +78,30 @@ type FloatingIPPage struct {
// NextPageURL is invoked when a paginated collection of floating IPs 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 FloatingIPPage) NextPageURL() (string, error) {
type resp struct {
Links []gophercloud.Link `mapstructure:"floatingips_links"`
func (r FloatingIPPage) NextPageURL() (string, error) {
var s struct {
Links []gophercloud.Link `json:"floatingips_links"`
}
var r resp
err := mapstructure.Decode(p.Body, &r)
err := r.ExtractInto(&s)
if err != nil {
return "", err
}
return gophercloud.ExtractNextURL(r.Links)
return gophercloud.ExtractNextURL(s.Links)
}
// IsEmpty checks whether a NetworkPage struct is empty.
func (p FloatingIPPage) IsEmpty() (bool, error) {
is, err := ExtractFloatingIPs(p)
if err != nil {
return true, nil
}
return len(is) == 0, nil
func (r FloatingIPPage) IsEmpty() (bool, error) {
is, err := ExtractFloatingIPs(r)
return len(is) == 0, err
}
// ExtractFloatingIPs accepts a Page struct, specifically a FloatingIPPage struct,
// and extracts the elements into a slice of FloatingIP structs. In other words,
// a generic collection is mapped into a relevant slice.
func ExtractFloatingIPs(page pagination.Page) ([]FloatingIP, error) {
var resp struct {
FloatingIPs []FloatingIP `mapstructure:"floatingips" json:"floatingips"`
func ExtractFloatingIPs(r pagination.Page) ([]FloatingIP, error) {
var s struct {
FloatingIPs []FloatingIP `json:"floatingips"`
}
err := mapstructure.Decode(page.(FloatingIPPage).Body, &resp)
return resp.FloatingIPs, err
err := (r.(FloatingIPPage)).ExtractInto(&s)
return s.FloatingIPs, err
}

View File

@ -1,6 +1,6 @@
package floatingips
import "github.com/rackspace/gophercloud"
import "github.com/gophercloud/gophercloud"
const resourcePath = "floatingips"

View File

@ -1,10 +1,8 @@
package routers
import (
"errors"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// ListOpts allows the filtering and sorting of paginated collections through
@ -53,38 +51,15 @@ type CreateOptsBuilder interface {
// CreateOpts contains all the values needed to create a new router. There are
// no required values.
type CreateOpts struct {
Name string
AdminStateUp *bool
Distributed *bool
TenantID string
GatewayInfo *GatewayInfo
Name string `json:"name,omitempty"`
AdminStateUp *bool `json:"admin_state_up,omitempty"`
Distributed *bool `json:"distributed,omitempty"`
TenantID string `json:"tenant_id,omitempty"`
GatewayInfo *GatewayInfo `json:"external_gateway_info,omitempty"`
}
// ToRouterCreateMap casts a CreateOpts struct to a map.
func (opts CreateOpts) ToRouterCreateMap() (map[string]interface{}, error) {
r := make(map[string]interface{})
if gophercloud.MaybeString(opts.Name) != nil {
r["name"] = opts.Name
}
if opts.AdminStateUp != nil {
r["admin_state_up"] = opts.AdminStateUp
}
if opts.Distributed != nil {
r["distributed"] = opts.Distributed
}
if gophercloud.MaybeString(opts.TenantID) != nil {
r["tenant_id"] = opts.TenantID
}
if opts.GatewayInfo != nil {
r["external_gateway_info"] = opts.GatewayInfo
}
return map[string]interface{}{"router": r}, nil
return gophercloud.BuildRequestBody(opts, "router")
}
// Create accepts a CreateOpts struct and uses the values to create a new
@ -95,33 +70,37 @@ func (opts CreateOpts) ToRouterCreateMap() (map[string]interface{}, error) {
// GatewayInfo struct. The external gateway for the router must be plugged into
// an external network (it is external if its `router:external' field is set to
// true).
func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
var res CreateResult
reqBody, err := opts.ToRouterCreateMap()
func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToRouterCreateMap()
if err != nil {
res.Err = err
return res
r.Err = err
return
}
_, res.Err = c.Post(rootURL(c), reqBody, &res.Body, nil)
return res
_, r.Err = c.Post(rootURL(c), b, &r.Body, nil)
return
}
// Get retrieves a particular router 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
func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = c.Get(resourceURL(c, id), &r.Body, nil)
return
}
type UpdateOptsBuilder interface {
ToRouterUpdateMap() (map[string]interface{}, error)
}
// UpdateOpts contains the values used when updating a router.
type UpdateOpts struct {
Name string
AdminStateUp *bool
Distributed *bool
GatewayInfo *GatewayInfo
Routes []Route
Name string `json:"name,omitempty"`
AdminStateUp *bool `json:"admin_state_up,omitempty"`
Distributed *bool `json:"distributed,omitempty"`
GatewayInfo *GatewayInfo `json:"external_gateway_info,omitempty"`
Routes []Route `json:"routes"`
}
func (opts UpdateOpts) ToRouterUpdateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "router")
}
// Update allows routers to be updated. You can update the name, administrative
@ -129,56 +108,41 @@ type UpdateOpts struct {
// external gateway for a router, see Create. This operation does not enable
// the update of router interfaces. To do this, use the AddInterface and
// RemoveInterface functions.
func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResult {
type router struct {
Name *string `json:"name,omitempty"`
AdminStateUp *bool `json:"admin_state_up,omitempty"`
Distributed *bool `json:"distributed,omitempty"`
GatewayInfo *GatewayInfo `json:"external_gateway_info,omitempty"`
Routes []Route `json:"routes"`
func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
b, err := opts.ToRouterUpdateMap()
if err != nil {
r.Err = err
return
}
type request struct {
Router router `json:"router"`
}
reqBody := request{Router: router{
Name: gophercloud.MaybeString(opts.Name),
AdminStateUp: opts.AdminStateUp,
Distributed: opts.Distributed,
}}
if opts.GatewayInfo != nil {
reqBody.Router.GatewayInfo = opts.GatewayInfo
}
if opts.Routes != nil {
reqBody.Router.Routes = opts.Routes
}
// Send request to API
var res UpdateResult
_, res.Err = c.Put(resourceURL(c, id), reqBody, &res.Body, &gophercloud.RequestOpts{
_, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return res
return
}
// Delete will permanently delete a particular router 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
func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = c.Delete(resourceURL(c, id), nil)
return
}
var errInvalidInterfaceOpts = errors.New("When adding a router interface you must provide either a subnet ID or a port ID")
// AddInterfaceOptsBuilder is what types must satisfy to be used as AddInterface
// options.
type AddInterfaceOptsBuilder interface {
ToRouterAddInterfaceMap() (map[string]interface{}, error)
}
// InterfaceOpts allow you to work with operations that either add or remote
// AddInterfaceOpts allow you to work with operations that either add
// an internal interface from a router.
type InterfaceOpts struct {
SubnetID string
PortID string
type AddInterfaceOpts struct {
SubnetID string `json:"subnet_id,omitempty" xor:"PortID"`
PortID string `json:"port_id,omitempty" xor:"SubnetID"`
}
// ToRouterAddInterfaceMap allows InterfaceOpts to satisfy the InterfaceOptsBuilder
// interface
func (opts AddInterfaceOpts) ToRouterAddInterfaceMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "")
}
// AddInterface attaches a subnet to an internal router interface. You must
@ -202,27 +166,35 @@ type InterfaceOpts struct {
// identifier of a new port created by this operation. After the operation
// completes, the device ID of the port is set to the router ID, and the
// device owner attribute is set to `network:router_interface'.
func AddInterface(c *gophercloud.ServiceClient, id string, opts InterfaceOpts) InterfaceResult {
var res InterfaceResult
// Validate
if (opts.SubnetID == "" && opts.PortID == "") || (opts.SubnetID != "" && opts.PortID != "") {
res.Err = errInvalidInterfaceOpts
return res
func AddInterface(c *gophercloud.ServiceClient, id string, opts AddInterfaceOptsBuilder) (r InterfaceResult) {
b, err := opts.ToRouterAddInterfaceMap()
if err != nil {
r.Err = err
return
}
type request struct {
SubnetID string `json:"subnet_id,omitempty"`
PortID string `json:"port_id,omitempty"`
}
body := request{SubnetID: opts.SubnetID, PortID: opts.PortID}
_, res.Err = c.Put(addInterfaceURL(c, id), body, &res.Body, &gophercloud.RequestOpts{
_, r.Err = c.Put(addInterfaceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
return res
// RemoveInterfaceOptsBuilder is what types must satisfy to be used as RemoveInterface
// options.
type RemoveInterfaceOptsBuilder interface {
ToRouterRemoveInterfaceMap() (map[string]interface{}, error)
}
// RemoveInterfaceOpts allow you to work with operations that either add or remote
// an internal interface from a router.
type RemoveInterfaceOpts struct {
SubnetID string `json:"subnet_id,omitempty" or:"PortID"`
PortID string `json:"port_id,omitempty" or:"SubnetID"`
}
// ToRouterRemoveInterfaceMap allows RemoveInterfaceOpts to satisfy the RemoveInterfaceOptsBuilder
// interface
func (opts RemoveInterfaceOpts) ToRouterRemoveInterfaceMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "")
}
// RemoveInterface removes an internal router interface, which detaches a
@ -238,19 +210,14 @@ func AddInterface(c *gophercloud.ServiceClient, id string, opts InterfaceOpts) I
// visible to you, the operation will fail and a 404 Not Found error will be
// returned. After this operation completes, the port connecting the router
// with the subnet is removed from the subnet for the network.
func RemoveInterface(c *gophercloud.ServiceClient, id string, opts InterfaceOpts) InterfaceResult {
var res InterfaceResult
type request struct {
SubnetID string `json:"subnet_id,omitempty"`
PortID string `json:"port_id,omitempty"`
func RemoveInterface(c *gophercloud.ServiceClient, id string, opts RemoveInterfaceOptsBuilder) (r InterfaceResult) {
b, err := opts.ToRouterRemoveInterfaceMap()
if err != nil {
r.Err = err
return
}
body := request{SubnetID: opts.SubnetID, PortID: opts.PortID}
_, res.Err = c.Put(removeInterfaceURL(c, id), body, &res.Body, &gophercloud.RequestOpts{
_, r.Err = c.Put(removeInterfaceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return res
return
}

View File

@ -1,20 +1,20 @@
package routers
import (
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// GatewayInfo represents the information of an external gateway for any
// particular network router.
type GatewayInfo struct {
NetworkID string `json:"network_id" mapstructure:"network_id"`
NetworkID string `json:"network_id"`
}
// Route is a possible route in a router.
type Route struct {
NextHop string `mapstructure:"nexthop" json:"nexthop"`
DestinationCIDR string `mapstructure:"destination" json:"destination"`
NextHop string `json:"nexthop"`
DestinationCIDR string `json:"destination"`
}
// Router represents a Neutron router. A router is a logical entity that
@ -27,28 +27,28 @@ type Route struct {
// interface is added to the subnet's network.
type Router struct {
// Indicates whether or not a router is currently operational.
Status string `json:"status" mapstructure:"status"`
Status string `json:"status"`
// Information on external gateway for the router.
GatewayInfo GatewayInfo `json:"external_gateway_info" mapstructure:"external_gateway_info"`
GatewayInfo GatewayInfo `json:"external_gateway_info"`
// Administrative state of the router.
AdminStateUp bool `json:"admin_state_up" mapstructure:"admin_state_up"`
AdminStateUp bool `json:"admin_state_up"`
// Whether router is disitrubted or not..
Distributed bool `json:"distributed" mapstructure:"distributed"`
Distributed bool `json:"distributed"`
// Human readable name for the router. Does not have to be unique.
Name string `json:"name" mapstructure:"name"`
Name string `json:"name"`
// Unique identifier for the router.
ID string `json:"id" mapstructure:"id"`
ID string `json:"id"`
// Owner of the router. Only admin users can specify a tenant identifier
// other than its own.
TenantID string `json:"tenant_id" mapstructure:"tenant_id"`
TenantID string `json:"tenant_id"`
Routes []Route `json:"routes" mapstructure:"routes"`
Routes []Route `json:"routes"`
}
// RouterPage is the page returned by a pager when traversing over a
@ -60,40 +60,32 @@ type RouterPage struct {
// NextPageURL is invoked when a paginated collection of routers 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 RouterPage) NextPageURL() (string, error) {
type resp struct {
Links []gophercloud.Link `mapstructure:"routers_links"`
func (r RouterPage) NextPageURL() (string, error) {
var s struct {
Links []gophercloud.Link `json:"routers_links"`
}
var r resp
err := mapstructure.Decode(p.Body, &r)
err := r.ExtractInto(&s)
if err != nil {
return "", err
}
return gophercloud.ExtractNextURL(r.Links)
return gophercloud.ExtractNextURL(s.Links)
}
// IsEmpty checks whether a RouterPage struct is empty.
func (p RouterPage) IsEmpty() (bool, error) {
is, err := ExtractRouters(p)
if err != nil {
return true, nil
}
return len(is) == 0, nil
func (r RouterPage) IsEmpty() (bool, error) {
is, err := ExtractRouters(r)
return len(is) == 0, err
}
// ExtractRouters accepts a Page struct, specifically a RouterPage struct,
// and extracts the elements into a slice of Router structs. In other words,
// a generic collection is mapped into a relevant slice.
func ExtractRouters(page pagination.Page) ([]Router, error) {
var resp struct {
Routers []Router `mapstructure:"routers" json:"routers"`
func ExtractRouters(r pagination.Page) ([]Router, error) {
var s struct {
Routers []Router `json:"routers"`
}
err := mapstructure.Decode(page.(RouterPage).Body, &resp)
return resp.Routers, err
err := (r.(RouterPage)).ExtractInto(&s)
return s.Routers, err
}
type commonResult struct {
@ -102,17 +94,11 @@ type commonResult struct {
// Extract is a function that accepts a result and extracts a router.
func (r commonResult) Extract() (*Router, error) {
if r.Err != nil {
return nil, r.Err
}
var res struct {
var s struct {
Router *Router `json:"router"`
}
err := mapstructure.Decode(r.Body, &res)
return res.Router, err
err := r.ExtractInto(&s)
return s.Router, err
}
// CreateResult represents the result of a create operation.
@ -140,16 +126,16 @@ type DeleteResult struct {
// interface.
type InterfaceInfo struct {
// The ID of the subnet which this interface is associated with.
SubnetID string `json:"subnet_id" mapstructure:"subnet_id"`
SubnetID string `json:"subnet_id"`
// The ID of the port that is a part of the subnet.
PortID string `json:"port_id" mapstructure:"port_id"`
PortID string `json:"port_id"`
// The UUID of the interface.
ID string `json:"id" mapstructure:"id"`
ID string `json:"id"`
// Owner of the interface.
TenantID string `json:"tenant_id" mapstructure:"tenant_id"`
TenantID string `json:"tenant_id"`
}
// InterfaceResult represents the result of interface operations, such as
@ -160,12 +146,7 @@ type InterfaceResult struct {
// Extract is a function that accepts a result and extracts an information struct.
func (r InterfaceResult) Extract() (*InterfaceInfo, error) {
if r.Err != nil {
return nil, r.Err
}
var res *InterfaceInfo
err := mapstructure.Decode(r.Body, &res)
return res, err
var s InterfaceInfo
err := r.ExtractInto(&s)
return &s, err
}

View File

@ -1,6 +1,6 @@
package routers
import "github.com/rackspace/gophercloud"
import "github.com/gophercloud/gophercloud"
const resourcePath = "routers"

View File

@ -1,8 +1,8 @@
package members
import (
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// ListOpts allows the filtering and sorting of paginated collections through
@ -42,82 +42,74 @@ func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager {
})
}
type CreateOptsBuilder interface {
ToLBMemberCreateMap() (map[string]interface{}, error)
}
// CreateOpts contains all the values needed to create a new pool member.
type CreateOpts struct {
// The IP address of the member.
Address string `json:"address" required:"true"`
// The port on which the application is hosted.
ProtocolPort int `json:"protocol_port" required:"true"`
// The pool to which this member will belong.
PoolID string `json:"pool_id" required:"true"`
// Only required if the caller has an admin role and wants to create a pool
// for another tenant.
TenantID string
TenantID string `json:"tenant_id,omitempty"`
}
// Required. The IP address of the member.
Address string
// Required. The port on which the application is hosted.
ProtocolPort int
// Required. The pool to which this member will belong.
PoolID string
func (opts CreateOpts) ToLBMemberCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "member")
}
// Create accepts a CreateOpts struct and uses the values to create a new
// load balancer pool member.
func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult {
type member struct {
TenantID string `json:"tenant_id,omitempty"`
ProtocolPort int `json:"protocol_port"`
Address string `json:"address"`
PoolID string `json:"pool_id"`
func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToLBMemberCreateMap()
if err != nil {
r.Err = err
return
}
type request struct {
Member member `json:"member"`
}
reqBody := request{Member: member{
Address: opts.Address,
TenantID: opts.TenantID,
ProtocolPort: opts.ProtocolPort,
PoolID: opts.PoolID,
}}
var res CreateResult
_, res.Err = c.Post(rootURL(c), reqBody, &res.Body, nil)
return res
_, r.Err = c.Post(rootURL(c), b, &r.Body, nil)
return
}
// Get retrieves a particular pool member 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
func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = c.Get(resourceURL(c, id), &r.Body, nil)
return
}
type UpdateOptsBuilder interface {
ToLBMemberUpdateMap() (map[string]interface{}, error)
}
// UpdateOpts contains the values used when updating a pool member.
type UpdateOpts struct {
// The administrative state of the member, which is up (true) or down (false).
AdminStateUp bool
AdminStateUp *bool `json:"admin_state_up,omitempty"`
}
func (opts UpdateOpts) ToLBMemberUpdateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "member")
}
// Update allows members to be updated.
func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResult {
type member struct {
AdminStateUp bool `json:"admin_state_up"`
func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
b, err := opts.ToLBMemberUpdateMap()
if err != nil {
r.Err = err
return
}
type request struct {
Member member `json:"member"`
}
reqBody := request{Member: member{AdminStateUp: opts.AdminStateUp}}
// Send request to API
var res UpdateResult
_, res.Err = c.Put(resourceURL(c, id), reqBody, &res.Body, &gophercloud.RequestOpts{
_, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 201, 202},
})
return res
return
}
// Delete will permanently delete a particular member 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
func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = c.Delete(resourceURL(c, id), nil)
return
}

View File

@ -1,9 +1,8 @@
package members
import (
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// Member represents the application running on a backend server.
@ -15,20 +14,20 @@ type Member struct {
Weight int
// The administrative state of the member, which is up (true) or down (false).
AdminStateUp bool `json:"admin_state_up" mapstructure:"admin_state_up"`
AdminStateUp bool `json:"admin_state_up"`
// Owner of the member. Only an administrative user can specify a tenant ID
// other than its own.
TenantID string `json:"tenant_id" mapstructure:"tenant_id"`
TenantID string `json:"tenant_id"`
// The pool to which the member belongs.
PoolID string `json:"pool_id" mapstructure:"pool_id"`
PoolID string `json:"pool_id"`
// The IP address of the member.
Address string
// The port on which the application is hosted.
ProtocolPort int `json:"protocol_port" mapstructure:"protocol_port"`
ProtocolPort int `json:"protocol_port"`
// The unique ID for the member.
ID string
@ -43,43 +42,32 @@ type MemberPage struct {
// NextPageURL is invoked when a paginated collection of members 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 MemberPage) NextPageURL() (string, error) {
type resp struct {
Links []gophercloud.Link `mapstructure:"members_links"`
func (r MemberPage) NextPageURL() (string, error) {
var s struct {
Links []gophercloud.Link `json:"members_links"`
}
var r resp
err := mapstructure.Decode(p.Body, &r)
err := r.ExtractInto(&s)
if err != nil {
return "", err
}
return gophercloud.ExtractNextURL(r.Links)
return gophercloud.ExtractNextURL(s.Links)
}
// IsEmpty checks whether a MemberPage struct is empty.
func (p MemberPage) IsEmpty() (bool, error) {
is, err := ExtractMembers(p)
if err != nil {
return true, nil
}
return len(is) == 0, nil
func (r MemberPage) IsEmpty() (bool, error) {
is, err := ExtractMembers(r)
return len(is) == 0, err
}
// ExtractMembers accepts a Page struct, specifically a MemberPage struct,
// and extracts the elements into a slice of Member structs. In other words,
// a generic collection is mapped into a relevant slice.
func ExtractMembers(page pagination.Page) ([]Member, error) {
var resp struct {
Members []Member `mapstructure:"members" json:"members"`
func ExtractMembers(r pagination.Page) ([]Member, error) {
var s struct {
Members []Member `json:"members"`
}
err := mapstructure.Decode(page.(MemberPage).Body, &resp)
if err != nil {
return nil, err
}
return resp.Members, nil
err := (r.(MemberPage)).ExtractInto(&s)
return s.Members, err
}
type commonResult struct {
@ -88,17 +76,11 @@ type commonResult struct {
// Extract is a function that accepts a result and extracts a router.
func (r commonResult) Extract() (*Member, error) {
if r.Err != nil {
return nil, r.Err
}
var res struct {
var s struct {
Member *Member `json:"member"`
}
err := mapstructure.Decode(r.Body, &res)
return res.Member, err
err := r.ExtractInto(&s)
return s.Member, err
}
// CreateResult represents the result of a create operation.

View File

@ -1,6 +1,6 @@
package members
import "github.com/rackspace/gophercloud"
import "github.com/gophercloud/gophercloud"
const (
rootPath = "lb"

View File

@ -3,8 +3,8 @@ package monitors
import (
"fmt"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// ListOpts allows the filtering and sorting of paginated collections through
@ -42,64 +42,78 @@ func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager {
return pagination.Pager{Err: err}
}
u := rootURL(c) + q.String()
return pagination.NewPager(c, u, func(r pagination.PageResult) pagination.Page {
return MonitorPage{pagination.LinkedPageBase{PageResult: r}}
})
}
// MonitorType is the type for all the types of LB monitors
type MonitorType string
// Constants that represent approved monitoring types.
const (
TypePING = "PING"
TypeTCP = "TCP"
TypeHTTP = "HTTP"
TypeHTTPS = "HTTPS"
TypePING MonitorType = "PING"
TypeTCP MonitorType = "TCP"
TypeHTTP MonitorType = "HTTP"
TypeHTTPS MonitorType = "HTTPS"
)
var (
errValidTypeRequired = fmt.Errorf("A valid Type is required. Supported values are PING, TCP, HTTP and HTTPS")
errDelayRequired = fmt.Errorf("Delay is required")
errTimeoutRequired = fmt.Errorf("Timeout is required")
errMaxRetriesRequired = fmt.Errorf("MaxRetries is required")
errURLPathRequired = fmt.Errorf("URL path is required")
errExpectedCodesRequired = fmt.Errorf("ExpectedCodes is required")
errDelayMustGETimeout = fmt.Errorf("Delay must be greater than or equal to timeout")
)
// CreateOptsBuilder is what types must satisfy to be used as Create
// options.
type CreateOptsBuilder interface {
ToLBMonitorCreateMap() (map[string]interface{}, error)
}
// CreateOpts contains all the values needed to create a new health monitor.
type CreateOpts struct {
// Required for admins. Indicates the owner of the VIP.
TenantID string
// Required. The type of probe, which is PING, TCP, HTTP, or HTTPS, that is
// sent by the load balancer to verify the member state.
Type string
Type MonitorType `json:"type" required:"true"`
// Required. The time, in seconds, between sending probes to members.
Delay int
Delay int `json:"delay" required:"true"`
// Required. Maximum number of seconds for a monitor to wait for a ping reply
// before it times out. The value must be less than the delay value.
Timeout int
Timeout int `json:"timeout" required:"true"`
// Required. Number of permissible ping failures before changing the member's
// status to INACTIVE. Must be a number between 1 and 10.
MaxRetries int
MaxRetries int `json:"max_retries" required:"true"`
// Required for HTTP(S) types. URI path that will be accessed if monitor type
// is HTTP or HTTPS.
URLPath string
URLPath string `json:"url_path,omitempty"`
// Required for HTTP(S) types. The HTTP method used for requests by the
// monitor. If this attribute is not specified, it defaults to "GET".
HTTPMethod string
HTTPMethod string `json:"http_method,omitempty"`
// Required for HTTP(S) types. Expected HTTP codes for a passing HTTP(S)
// monitor. You can either specify a single status like "200", or a range
// like "200-202".
ExpectedCodes string
ExpectedCodes string `json:"expected_codes,omitempty"`
// Required for admins. Indicates the owner of the VIP.
TenantID string `json:"tenant_id,omitempty"`
AdminStateUp *bool `json:"admin_state_up,omitempty"`
}
AdminStateUp *bool
// ToLBMonitorCreateMap allows CreateOpts to satisfy the CreateOptsBuilder
// interface
func (opts CreateOpts) ToLBMonitorCreateMap() (map[string]interface{}, error) {
if opts.Type == TypeHTTP || opts.Type == TypeHTTPS {
if opts.URLPath == "" {
err := gophercloud.ErrMissingInput{}
err.Argument = "monitors.CreateOpts.URLPath"
return nil, err
}
if opts.ExpectedCodes == "" {
err := gophercloud.ErrMissingInput{}
err.Argument = "monitors.CreateOpts.ExpectedCodes"
return nil, err
}
}
if opts.Delay < opts.Timeout {
err := gophercloud.ErrInvalidInput{}
err.Argument = "monitors.CreateOpts.Delay/monitors.CreateOpts.Timeout"
err.Info = "Delay must be greater than or equal to timeout"
return nil, err
}
return gophercloud.BuildRequestBody(opts, "health_monitor")
}
// Create is an operation which provisions a new health monitor. There are
@ -116,150 +130,81 @@ type CreateOpts struct {
// CreateOpts{Type: TypeHTTP, Delay: 20, Timeout: 10, MaxRetries: 3,
// HttpMethod: "HEAD", ExpectedCodes: "200"}
//
func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult {
var res CreateResult
// Validate inputs
allowed := map[string]bool{TypeHTTP: true, TypeHTTPS: true, TypeTCP: true, TypePING: true}
if opts.Type == "" || allowed[opts.Type] == false {
res.Err = errValidTypeRequired
func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToLBMonitorCreateMap()
if err != nil {
r.Err = err
return
}
if opts.Delay == 0 {
res.Err = errDelayRequired
}
if opts.Timeout == 0 {
res.Err = errTimeoutRequired
}
if opts.MaxRetries == 0 {
res.Err = errMaxRetriesRequired
}
if opts.Type == TypeHTTP || opts.Type == TypeHTTPS {
if opts.URLPath == "" {
res.Err = errURLPathRequired
}
if opts.ExpectedCodes == "" {
res.Err = errExpectedCodesRequired
}
}
if opts.Delay < opts.Timeout {
res.Err = errDelayMustGETimeout
}
if res.Err != nil {
return res
}
type monitor struct {
Type string `json:"type"`
Delay int `json:"delay"`
Timeout int `json:"timeout"`
MaxRetries int `json:"max_retries"`
TenantID *string `json:"tenant_id,omitempty"`
URLPath *string `json:"url_path,omitempty"`
ExpectedCodes *string `json:"expected_codes,omitempty"`
HTTPMethod *string `json:"http_method,omitempty"`
AdminStateUp *bool `json:"admin_state_up,omitempty"`
}
type request struct {
Monitor monitor `json:"health_monitor"`
}
reqBody := request{Monitor: monitor{
Type: opts.Type,
Delay: opts.Delay,
Timeout: opts.Timeout,
MaxRetries: opts.MaxRetries,
TenantID: gophercloud.MaybeString(opts.TenantID),
URLPath: gophercloud.MaybeString(opts.URLPath),
ExpectedCodes: gophercloud.MaybeString(opts.ExpectedCodes),
HTTPMethod: gophercloud.MaybeString(opts.HTTPMethod),
AdminStateUp: opts.AdminStateUp,
}}
_, res.Err = c.Post(rootURL(c), reqBody, &res.Body, nil)
return res
_, r.Err = c.Post(rootURL(c), b, &r.Body, nil)
return
}
// Get retrieves a particular health monitor 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
func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = c.Get(resourceURL(c, id), &r.Body, nil)
return
}
// UpdateOptsBuilder is what types must satisfy to be used as Update
// options.
type UpdateOptsBuilder interface {
ToLBMonitorUpdateMap() (map[string]interface{}, error)
}
// UpdateOpts contains all the values needed to update an existing virtual IP.
// Attributes not listed here but appear in CreateOpts are immutable and cannot
// be updated.
type UpdateOpts struct {
// Required. The time, in seconds, between sending probes to members.
Delay int
// Required. Maximum number of seconds for a monitor to wait for a ping reply
// The time, in seconds, between sending probes to members.
Delay int `json:"delay,omitempty"`
// Maximum number of seconds for a monitor to wait for a ping reply
// before it times out. The value must be less than the delay value.
Timeout int
// Required. Number of permissible ping failures before changing the member's
Timeout int `json:"timeout,omitempty"`
// Number of permissible ping failures before changing the member's
// status to INACTIVE. Must be a number between 1 and 10.
MaxRetries int
// Required for HTTP(S) types. URI path that will be accessed if monitor type
MaxRetries int `json:"max_retries,omitempty"`
// URI path that will be accessed if monitor type
// is HTTP or HTTPS.
URLPath string
// Required for HTTP(S) types. The HTTP method used for requests by the
URLPath string `json:"url_path,omitempty"`
// The HTTP method used for requests by the
// monitor. If this attribute is not specified, it defaults to "GET".
HTTPMethod string
// Required for HTTP(S) types. Expected HTTP codes for a passing HTTP(S)
HTTPMethod string `json:"http_method,omitempty"`
// Expected HTTP codes for a passing HTTP(S)
// monitor. You can either specify a single status like "200", or a range
// like "200-202".
ExpectedCodes string
ExpectedCodes string `json:"expected_codes,omitempty"`
AdminStateUp *bool `json:"admin_state_up,omitempty"`
}
AdminStateUp *bool
// ToLBMonitorUpdateMap allows UpdateOpts to satisfy the UpdateOptsBuilder
// interface
func (opts UpdateOpts) ToLBMonitorUpdateMap() (map[string]interface{}, error) {
if opts.Delay > 0 && opts.Timeout > 0 && opts.Delay < opts.Timeout {
err := gophercloud.ErrInvalidInput{}
err.Argument = "monitors.CreateOpts.Delay/monitors.CreateOpts.Timeout"
err.Value = fmt.Sprintf("%d/%d", opts.Delay, opts.Timeout)
err.Info = "Delay must be greater than or equal to timeout"
return nil, err
}
return gophercloud.BuildRequestBody(opts, "health_monitor")
}
// Update is an operation which modifies the attributes of the specified monitor.
func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResult {
var res UpdateResult
if opts.Delay > 0 && opts.Timeout > 0 && opts.Delay < opts.Timeout {
res.Err = errDelayMustGETimeout
func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
b, err := opts.ToLBMonitorUpdateMap()
if err != nil {
r.Err = err
return
}
type monitor struct {
Delay int `json:"delay"`
Timeout int `json:"timeout"`
MaxRetries int `json:"max_retries"`
URLPath *string `json:"url_path,omitempty"`
ExpectedCodes *string `json:"expected_codes,omitempty"`
HTTPMethod *string `json:"http_method,omitempty"`
AdminStateUp *bool `json:"admin_state_up,omitempty"`
}
type request struct {
Monitor monitor `json:"health_monitor"`
}
reqBody := request{Monitor: monitor{
Delay: opts.Delay,
Timeout: opts.Timeout,
MaxRetries: opts.MaxRetries,
URLPath: gophercloud.MaybeString(opts.URLPath),
ExpectedCodes: gophercloud.MaybeString(opts.ExpectedCodes),
HTTPMethod: gophercloud.MaybeString(opts.HTTPMethod),
AdminStateUp: opts.AdminStateUp,
}}
_, res.Err = c.Put(resourceURL(c, id), reqBody, &res.Body, &gophercloud.RequestOpts{
_, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 202},
})
return res
return
}
// Delete will permanently delete a particular monitor 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
func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = c.Delete(resourceURL(c, id), nil)
return
}

View File

@ -1,9 +1,8 @@
package monitors
import (
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// Monitor represents a load balancer health monitor. A health monitor is used
@ -30,7 +29,7 @@ type Monitor struct {
// Owner of the VIP. Only an administrative user can specify a tenant ID
// other than its own.
TenantID string `json:"tenant_id" mapstructure:"tenant_id"`
TenantID string `json:"tenant_id"`
// The type of probe sent by the load balancer to verify the member state,
// which is PING, TCP, HTTP, or HTTPS.
@ -45,20 +44,20 @@ type Monitor struct {
// Number of allowed connection failures before changing the status of the
// member to INACTIVE. A valid value is from 1 to 10.
MaxRetries int `json:"max_retries" mapstructure:"max_retries"`
MaxRetries int `json:"max_retries"`
// The HTTP method that the monitor uses for requests.
HTTPMethod string `json:"http_method" mapstructure:"http_method"`
HTTPMethod string `json:"http_method"`
// The HTTP path of the request sent by the monitor to test the health of a
// member. Must be a string beginning with a forward slash (/).
URLPath string `json:"url_path" mapstructure:"url_path"`
URLPath string `json:"url_path"`
// Expected HTTP codes for a passing HTTP(S) monitor.
ExpectedCodes string `json:"expected_codes" mapstructure:"expected_codes"`
ExpectedCodes string `json:"expected_codes"`
// The administrative state of the health monitor, which is up (true) or down (false).
AdminStateUp bool `json:"admin_state_up" mapstructure:"admin_state_up"`
AdminStateUp bool `json:"admin_state_up"`
// The status of the health monitor. Indicates whether the health monitor is
// operational.
@ -74,40 +73,33 @@ type MonitorPage struct {
// NextPageURL is invoked when a paginated collection of monitors 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 MonitorPage) NextPageURL() (string, error) {
type resp struct {
Links []gophercloud.Link `mapstructure:"health_monitors_links"`
func (r MonitorPage) NextPageURL() (string, error) {
var s struct {
Links []gophercloud.Link `json:"health_monitors_links"`
}
var r resp
err := mapstructure.Decode(p.Body, &r)
err := r.ExtractInto(&s)
if err != nil {
return "", err
}
return gophercloud.ExtractNextURL(r.Links)
return gophercloud.ExtractNextURL(s.Links)
}
// IsEmpty checks whether a PoolPage struct is empty.
func (p MonitorPage) IsEmpty() (bool, error) {
is, err := ExtractMonitors(p)
if err != nil {
return true, nil
}
return len(is) == 0, nil
func (r MonitorPage) IsEmpty() (bool, error) {
is, err := ExtractMonitors(r)
return len(is) == 0, err
}
// ExtractMonitors accepts a Page struct, specifically a MonitorPage struct,
// and extracts the elements into a slice of Monitor structs. In other words,
// a generic collection is mapped into a relevant slice.
func ExtractMonitors(page pagination.Page) ([]Monitor, error) {
var resp struct {
Monitors []Monitor `mapstructure:"health_monitors" json:"health_monitors"`
func ExtractMonitors(r pagination.Page) ([]Monitor, error) {
var s struct {
Monitors []Monitor `json:"health_monitors"`
}
err := mapstructure.Decode(page.(MonitorPage).Body, &resp)
return resp.Monitors, err
err := (r.(MonitorPage)).ExtractInto(&s)
return s.Monitors, err
}
type commonResult struct {
@ -116,17 +108,11 @@ type commonResult struct {
// Extract is a function that accepts a result and extracts a monitor.
func (r commonResult) Extract() (*Monitor, error) {
if r.Err != nil {
return nil, r.Err
var s struct {
Monitor *Monitor `json:"health_monitor"`
}
var res struct {
Monitor *Monitor `json:"health_monitor" mapstructure:"health_monitor"`
}
err := mapstructure.Decode(r.Body, &res)
return res.Monitor, err
err := r.ExtractInto(&s)
return s.Monitor, err
}
// CreateResult represents the result of a create operation.

View File

@ -1,6 +1,6 @@
package monitors
import "github.com/rackspace/gophercloud"
import "github.com/gophercloud/gophercloud"
const (
rootPath = "lb"

View File

@ -1,8 +1,8 @@
package pools
import (
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// ListOpts allows the filtering and sorting of paginated collections through
@ -43,117 +43,111 @@ func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager {
})
}
// LBMethod is a type used for possible load balancing methods
type LBMethod string
// LBProtocol is a type used for possible load balancing protocols
type LBProtocol string
// Supported attributes for create/update operations.
const (
LBMethodRoundRobin = "ROUND_ROBIN"
LBMethodLeastConnections = "LEAST_CONNECTIONS"
LBMethodRoundRobin LBMethod = "ROUND_ROBIN"
LBMethodLeastConnections LBMethod = "LEAST_CONNECTIONS"
ProtocolTCP = "TCP"
ProtocolHTTP = "HTTP"
ProtocolHTTPS = "HTTPS"
ProtocolTCP LBProtocol = "TCP"
ProtocolHTTP LBProtocol = "HTTP"
ProtocolHTTPS LBProtocol = "HTTPS"
)
// CreateOptsBuilder is the interface types must satisfy to be used as options
// for the Create function
type CreateOptsBuilder interface {
ToLBPoolCreateMap() (map[string]interface{}, error)
}
// CreateOpts contains all the values needed to create a new pool.
type CreateOpts struct {
// Name of the pool.
Name string `json:"name" required:"true"`
// The protocol used by the pool members, you can use either
// ProtocolTCP, ProtocolHTTP, or ProtocolHTTPS.
Protocol LBProtocol `json:"protocol" required:"true"`
// Only required if the caller has an admin role and wants to create a pool
// for another tenant.
TenantID string
// Required. Name of the pool.
Name string
// Required. The protocol used by the pool members, you can use either
// ProtocolTCP, ProtocolHTTP, or ProtocolHTTPS.
Protocol string
TenantID string `json:"tenant_id,omitempty"`
// The network on which the members of the pool will be located. Only members
// that are on this network can be added to the pool.
SubnetID string
SubnetID string `json:"subnet_id,omitempty"`
// The algorithm used to distribute load between the members of the pool. The
// current specification supports LBMethodRoundRobin and
// LBMethodLeastConnections as valid values for this attribute.
LBMethod string
LBMethod LBMethod `json:"lb_method" required:"true"`
// The provider of the pool
Provider string
Provider string `json:"provider,omitempty"`
}
// Create accepts a CreateOpts struct and uses the values to create a new
// ToLBPoolCreateMap allows CreateOpts to satisfy the CreateOptsBuilder interface
func (opts CreateOpts) ToLBPoolCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "pool")
}
// Create accepts a CreateOptsBuilder and uses the values to create a new
// load balancer pool.
func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult {
type pool struct {
Name string `json:"name"`
TenantID string `json:"tenant_id,omitempty"`
Protocol string `json:"protocol"`
SubnetID string `json:"subnet_id"`
LBMethod string `json:"lb_method"`
Provider string `json:"provider,omitempty"`
func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToLBPoolCreateMap()
if err != nil {
r.Err = err
return
}
type request struct {
Pool pool `json:"pool"`
}
reqBody := request{Pool: pool{
Name: opts.Name,
TenantID: opts.TenantID,
Protocol: opts.Protocol,
SubnetID: opts.SubnetID,
LBMethod: opts.LBMethod,
Provider: opts.Provider,
}}
var res CreateResult
_, res.Err = c.Post(rootURL(c), reqBody, &res.Body, nil)
return res
_, r.Err = c.Post(rootURL(c), b, &r.Body, nil)
return
}
// Get retrieves a particular pool 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
func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = c.Get(resourceURL(c, id), &r.Body, nil)
return
}
// UpdateOptsBuilder is the interface types must satisfy to be used as options
// for the Update function
type UpdateOptsBuilder interface {
ToLBPoolUpdateMap() (map[string]interface{}, error)
}
// UpdateOpts contains the values used when updating a pool.
type UpdateOpts struct {
// Required. Name of the pool.
Name string
// Name of the pool.
Name string `json:"name,omitempty"`
// The algorithm used to distribute load between the members of the pool. The
// current specification supports LBMethodRoundRobin and
// LBMethodLeastConnections as valid values for this attribute.
LBMethod string
LBMethod LBMethod `json:"lb_method,omitempty"`
}
// ToLBPoolUpdateMap allows UpdateOpts to satisfy the UpdateOptsBuilder interface
func (opts UpdateOpts) ToLBPoolUpdateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "pool")
}
// Update allows pools to be updated.
func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResult {
type pool struct {
Name string `json:"name,"`
LBMethod string `json:"lb_method"`
func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
b, err := opts.ToLBPoolUpdateMap()
if err != nil {
r.Err = err
return
}
type request struct {
Pool pool `json:"pool"`
}
reqBody := request{Pool: pool{
Name: opts.Name,
LBMethod: opts.LBMethod,
}}
// Send request to API
var res UpdateResult
_, res.Err = c.Put(resourceURL(c, id), reqBody, &res.Body, &gophercloud.RequestOpts{
_, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return res
return
}
// Delete will permanently delete a particular pool 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
func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = c.Delete(resourceURL(c, id), nil)
return
}
// AssociateMonitor will associate a health monitor with a particular pool.
@ -161,26 +155,16 @@ func Delete(c *gophercloud.ServiceClient, id string) DeleteResult {
// pool and will deactivate these members if they are deemed unhealthy. A
// member can be deactivated (status set to INACTIVE) if any of health monitors
// finds it unhealthy.
func AssociateMonitor(c *gophercloud.ServiceClient, poolID, monitorID string) AssociateResult {
type hm struct {
ID string `json:"id"`
}
type request struct {
Monitor hm `json:"health_monitor"`
}
reqBody := request{hm{ID: monitorID}}
var res AssociateResult
_, res.Err = c.Post(associateURL(c, poolID), reqBody, &res.Body, nil)
return res
func AssociateMonitor(c *gophercloud.ServiceClient, poolID, monitorID string) (r AssociateResult) {
b := map[string]interface{}{"health_monitor": map[string]string{"id": monitorID}}
_, r.Err = c.Post(associateURL(c, poolID), b, &r.Body, nil)
return
}
// DisassociateMonitor will disassociate a health monitor with a particular
// pool. When dissociation is successful, the health monitor will no longer
// check for the health of the members of the pool.
func DisassociateMonitor(c *gophercloud.ServiceClient, poolID, monitorID string) AssociateResult {
var res AssociateResult
_, res.Err = c.Delete(disassociateURL(c, poolID, monitorID), nil)
return res
func DisassociateMonitor(c *gophercloud.ServiceClient, poolID, monitorID string) (r AssociateResult) {
_, r.Err = c.Delete(disassociateURL(c, poolID, monitorID), nil)
return
}

View File

@ -1,9 +1,8 @@
package pools
import (
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// Pool represents a logical set of devices, such as web servers, that you
@ -18,7 +17,7 @@ type Pool struct {
// The load-balancer algorithm, which is round-robin, least-connections, and
// so on. This value, which must be supported, is dependent on the provider.
// Round-robin must be supported.
LBMethod string `json:"lb_method" mapstructure:"lb_method"`
LBMethod string `json:"lb_method"`
// The protocol of the pool, which is TCP, HTTP, or HTTPS.
Protocol string
@ -27,30 +26,30 @@ type Pool struct {
Description string
// The IDs of associated monitors which check the health of the pool members.
MonitorIDs []string `json:"health_monitors" mapstructure:"health_monitors"`
MonitorIDs []string `json:"health_monitors"`
// The network on which the members of the pool will be located. Only members
// that are on this network can be added to the pool.
SubnetID string `json:"subnet_id" mapstructure:"subnet_id"`
SubnetID string `json:"subnet_id"`
// Owner of the pool. Only an administrative user can specify a tenant ID
// other than its own.
TenantID string `json:"tenant_id" mapstructure:"tenant_id"`
TenantID string `json:"tenant_id"`
// The administrative state of the pool, which is up (true) or down (false).
AdminStateUp bool `json:"admin_state_up" mapstructure:"admin_state_up"`
AdminStateUp bool `json:"admin_state_up"`
// Pool name. Does not have to be unique.
Name string
// List of member IDs that belong to the pool.
MemberIDs []string `json:"members" mapstructure:"members"`
MemberIDs []string `json:"members"`
// The unique ID for the pool.
ID string
// The ID of the virtual IP associated with this pool
VIPID string `json:"vip_id" mapstructure:"vip_id"`
VIPID string `json:"vip_id"`
// The provider
Provider string
@ -65,40 +64,32 @@ type PoolPage struct {
// NextPageURL is invoked when a paginated collection of pools 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 PoolPage) NextPageURL() (string, error) {
type resp struct {
Links []gophercloud.Link `mapstructure:"pools_links"`
func (r PoolPage) NextPageURL() (string, error) {
var s struct {
Links []gophercloud.Link `json:"pools_links"`
}
var r resp
err := mapstructure.Decode(p.Body, &r)
err := r.ExtractInto(&s)
if err != nil {
return "", err
}
return gophercloud.ExtractNextURL(r.Links)
return gophercloud.ExtractNextURL(s.Links)
}
// IsEmpty checks whether a PoolPage struct is empty.
func (p PoolPage) IsEmpty() (bool, error) {
is, err := ExtractPools(p)
if err != nil {
return true, nil
}
return len(is) == 0, nil
func (r PoolPage) IsEmpty() (bool, error) {
is, err := ExtractPools(r)
return len(is) == 0, err
}
// ExtractPools accepts a Page struct, specifically a RouterPage struct,
// and extracts the elements into a slice of Router structs. In other words,
// a generic collection is mapped into a relevant slice.
func ExtractPools(page pagination.Page) ([]Pool, error) {
var resp struct {
Pools []Pool `mapstructure:"pools" json:"pools"`
func ExtractPools(r pagination.Page) ([]Pool, error) {
var s struct {
Pools []Pool `json:"pools"`
}
err := mapstructure.Decode(page.(PoolPage).Body, &resp)
return resp.Pools, err
err := (r.(PoolPage)).ExtractInto(&s)
return s.Pools, err
}
type commonResult struct {
@ -107,17 +98,11 @@ type commonResult struct {
// Extract is a function that accepts a result and extracts a router.
func (r commonResult) Extract() (*Pool, error) {
if r.Err != nil {
return nil, r.Err
}
var res struct {
var s struct {
Pool *Pool `json:"pool"`
}
err := mapstructure.Decode(r.Body, &res)
return res.Pool, err
err := r.ExtractInto(&s)
return s.Pool, err
}
// CreateResult represents the result of a create operation.

View File

@ -1,6 +1,6 @@
package pools
import "github.com/rackspace/gophercloud"
import "github.com/gophercloud/gophercloud"
const (
rootPath = "lb"

View File

@ -0,0 +1,163 @@
package vips
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/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"`
AdminStateUp *bool `q:"admin_state_up"`
Status string `q:"status"`
TenantID string `q:"tenant_id"`
SubnetID string `q:"subnet_id"`
Address string `q:"address"`
PortID string `q:"port_id"`
Protocol string `q:"protocol"`
ProtocolPort int `q:"protocol_port"`
ConnectionLimit int `q:"connection_limit"`
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
// routers. It accepts a ListOpts struct, which allows you to filter and sort
// the returned collection for greater efficiency.
//
// Default policy settings return only those routers that are owned by the
// tenant who submits the request, unless an admin user submits the request.
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 VIPPage{pagination.LinkedPageBase{PageResult: r}}
})
}
// CreateOptsBuilder is what types must satisfy to be used as Create
// options.
type CreateOptsBuilder interface {
ToVIPCreateMap() (map[string]interface{}, error)
}
// CreateOpts contains all the values needed to create a new virtual IP.
type CreateOpts struct {
// Human-readable name for the VIP. Does not have to be unique.
Name string `json:"name" required:"true"`
// The network on which to allocate the VIP's address. A tenant can
// only create VIPs on networks authorized by policy (e.g. networks that
// belong to them or networks that are shared).
SubnetID string `json:"subnet_id" required:"true"`
// The protocol - can either be TCP, HTTP or HTTPS.
Protocol string `json:"protocol" required:"true"`
// The port on which to listen for client traffic.
ProtocolPort int `json:"protocol_port" required:"true"`
// The ID of the pool with which the VIP is associated.
PoolID string `json:"pool_id" required:"true"`
// Required for admins. Indicates the owner of the VIP.
TenantID string `json:"tenant_id,omitempty"`
// The IP address of the VIP.
Address string `json:"address,omitempty"`
// Human-readable description for the VIP.
Description string `json:"description,omitempty"`
// Omit this field to prevent session persistence.
Persistence *SessionPersistence `json:"session_persistence,omitempty"`
// The maximum number of connections allowed for the VIP.
ConnLimit *int `json:"connection_limit,omitempty"`
// The administrative state of the VIP. A valid value is true (UP)
// or false (DOWN).
AdminStateUp *bool `json:"admin_state_up,omitempty"`
}
// ToVIPCreateMap allows CreateOpts to satisfy the CreateOptsBuilder
// interface
func (opts CreateOpts) ToVIPCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "vip")
}
// Create is an operation which provisions a new virtual IP based on the
// configuration defined in the CreateOpts struct. Once the request is
// validated and progress has started on the provisioning process, a
// CreateResult will be returned.
//
// Please note that the PoolID should refer to a pool that is not already
// associated with another vip. If the pool is already used by another vip,
// then the operation will fail with a 409 Conflict error will be returned.
//
// Users with an admin role can create VIPs on behalf of other tenants by
// specifying a TenantID attribute different than their own.
func Create(c *gophercloud.ServiceClient, opts CreateOpts) (r CreateResult) {
b, err := opts.ToVIPCreateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = c.Post(rootURL(c), b, &r.Body, nil)
return
}
// Get retrieves a particular virtual IP based on its unique ID.
func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = c.Get(resourceURL(c, id), &r.Body, nil)
return
}
// UpdateOptsBuilder is what types must satisfy to be used as Update
// options.
type UpdateOptsBuilder interface {
ToVIPUpdateMap() (map[string]interface{}, error)
}
// UpdateOpts contains all the values needed to update an existing virtual IP.
// Attributes not listed here but appear in CreateOpts are immutable and cannot
// be updated.
type UpdateOpts struct {
// Human-readable name for the VIP. Does not have to be unique.
Name *string `json:"name,omitempty"`
// The ID of the pool with which the VIP is associated.
PoolID *string `json:"pool_id,omitempty"`
// Human-readable description for the VIP.
Description *string `json:"description,omitempty"`
// Omit this field to prevent session persistence.
Persistence *SessionPersistence `json:"session_persistence,omitempty"`
// The maximum number of connections allowed for the VIP.
ConnLimit *int `json:"connection_limit,omitempty"`
// The administrative state of the VIP. A valid value is true (UP)
// or false (DOWN).
AdminStateUp *bool `json:"admin_state_up,omitempty"`
}
// ToVIPUpdateMap allows UpdateOpts to satisfy the UpdateOptsBuilder interface
func (opts UpdateOpts) ToVIPUpdateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "vip")
}
// Update is an operation which modifies the attributes of the specified VIP.
func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
b, err := opts.ToVIPUpdateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 202},
})
return
}
// Delete will permanently delete a particular virtual IP based on its unique ID.
func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = c.Delete(resourceURL(c, id), nil)
return
}

View File

@ -1,9 +1,8 @@
package vips
import (
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// SessionPersistence represents the session persistence feature of the load
@ -23,10 +22,10 @@ import (
// same member of the pool.
type SessionPersistence struct {
// The type of persistence mode
Type string `mapstructure:"type" json:"type"`
Type string `json:"type"`
// Name of cookie if persistence mode is set appropriately
CookieName string `mapstructure:"cookie_name" json:"cookie_name,omitempty"`
CookieName string `json:"cookie_name,omitempty"`
}
// VirtualIP is the primary load balancing configuration object that specifies
@ -36,49 +35,49 @@ type SessionPersistence struct {
// server", a "vserver" or a "listener".
type VirtualIP struct {
// The unique ID for the VIP.
ID string `mapstructure:"id" json:"id"`
ID string `json:"id"`
// Owner of the VIP. Only an admin user can specify a tenant ID other than its own.
TenantID string `mapstructure:"tenant_id" json:"tenant_id"`
TenantID string `json:"tenant_id"`
// Human-readable name for the VIP. Does not have to be unique.
Name string `mapstructure:"name" json:"name"`
Name string `json:"name"`
// Human-readable description for the VIP.
Description string `mapstructure:"description" json:"description"`
Description string `json:"description"`
// The ID of the subnet on which to allocate the VIP address.
SubnetID string `mapstructure:"subnet_id" json:"subnet_id"`
SubnetID string `json:"subnet_id"`
// The IP address of the VIP.
Address string `mapstructure:"address" json:"address"`
Address string `json:"address"`
// The protocol of the VIP address. A valid value is TCP, HTTP, or HTTPS.
Protocol string `mapstructure:"protocol" json:"protocol"`
Protocol string `json:"protocol"`
// The port on which to listen to client traffic that is associated with the
// VIP address. A valid value is from 0 to 65535.
ProtocolPort int `mapstructure:"protocol_port" json:"protocol_port"`
ProtocolPort int `json:"protocol_port"`
// The ID of the pool with which the VIP is associated.
PoolID string `mapstructure:"pool_id" json:"pool_id"`
PoolID string `json:"pool_id"`
// The ID of the port which belongs to the load balancer
PortID string `mapstructure:"port_id" json:"port_id"`
PortID string `json:"port_id"`
// Indicates whether connections in the same session will be processed by the
// same pool member or not.
Persistence SessionPersistence `mapstructure:"session_persistence" json:"session_persistence"`
Persistence SessionPersistence `json:"session_persistence"`
// The maximum number of connections allowed for the VIP. Default is -1,
// meaning no limit.
ConnLimit int `mapstructure:"connection_limit" json:"connection_limit"`
ConnLimit int `json:"connection_limit"`
// The administrative state of the VIP. A valid value is true (UP) or false (DOWN).
AdminStateUp bool `mapstructure:"admin_state_up" json:"admin_state_up"`
AdminStateUp bool `json:"admin_state_up"`
// The status of the VIP. Indicates whether the VIP is operational.
Status string `mapstructure:"status" json:"status"`
Status string `json:"status"`
}
// VIPPage is the page returned by a pager when traversing over a
@ -90,40 +89,32 @@ type VIPPage struct {
// NextPageURL is invoked when a paginated collection of routers 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 VIPPage) NextPageURL() (string, error) {
type resp struct {
Links []gophercloud.Link `mapstructure:"vips_links"`
func (r VIPPage) NextPageURL() (string, error) {
var s struct {
Links []gophercloud.Link `json:"vips_links"`
}
var r resp
err := mapstructure.Decode(p.Body, &r)
err := r.ExtractInto(&s)
if err != nil {
return "", err
}
return gophercloud.ExtractNextURL(r.Links)
return gophercloud.ExtractNextURL(s.Links)
}
// IsEmpty checks whether a RouterPage struct is empty.
func (p VIPPage) IsEmpty() (bool, error) {
is, err := ExtractVIPs(p)
if err != nil {
return true, nil
}
return len(is) == 0, nil
// IsEmpty checks whether a VIPPage struct is empty.
func (r VIPPage) IsEmpty() (bool, error) {
is, err := ExtractVIPs(r)
return len(is) == 0, err
}
// ExtractVIPs accepts a Page struct, specifically a VIPPage struct,
// and extracts the elements into a slice of VirtualIP structs. In other words,
// a generic collection is mapped into a relevant slice.
func ExtractVIPs(page pagination.Page) ([]VirtualIP, error) {
var resp struct {
VIPs []VirtualIP `mapstructure:"vips" json:"vips"`
func ExtractVIPs(r pagination.Page) ([]VirtualIP, error) {
var s struct {
VIPs []VirtualIP `json:"vips"`
}
err := mapstructure.Decode(page.(VIPPage).Body, &resp)
return resp.VIPs, err
err := (r.(VIPPage)).ExtractInto(&s)
return s.VIPs, err
}
type commonResult struct {
@ -132,17 +123,11 @@ type commonResult struct {
// Extract is a function that accepts a result and extracts a router.
func (r commonResult) Extract() (*VirtualIP, error) {
if r.Err != nil {
return nil, r.Err
var s struct {
VirtualIP *VirtualIP `json:"vip" json:"vip"`
}
var res struct {
VirtualIP *VirtualIP `mapstructure:"vip" json:"vip"`
}
err := mapstructure.Decode(r.Body, &res)
return res.VirtualIP, err
err := r.ExtractInto(&s)
return s.VirtualIP, err
}
// CreateResult represents the result of a create operation.

View File

@ -1,6 +1,6 @@
package vips
import "github.com/rackspace/gophercloud"
import "github.com/gophercloud/gophercloud"
const (
rootPath = "lb"

View File

@ -1,59 +1,17 @@
package listeners
import (
"fmt"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// AdminState gives users a solid type to work with for create and update
// operations. It is recommended that users use the `Up` and `Down` enums.
type AdminState *bool
type Protocol string
type listenerOpts struct {
// Required. The protocol - can either be TCP, HTTP or HTTPS.
Protocol Protocol
// Required. The port on which to listen for client traffic.
ProtocolPort int
// Required for admins. Indicates the owner of the Listener.
TenantID string
// Required. The load balancer on which to provision this listener.
LoadbalancerID string
// Human-readable name for the Listener. Does not have to be unique.
Name string
// Optional. The ID of the default pool with which the Listener is associated.
DefaultPoolID string
// Optional. Human-readable description for the Listener.
Description string
// Optional. The maximum number of connections allowed for the Listener.
ConnLimit *int
// Optional. A reference to a container of TLS secrets.
DefaultTlsContainerRef string
// Optional. A list of references to TLS secrets.
SniContainerRefs []string
// Optional. The administrative state of the Listener. A valid value is true (UP)
// or false (DOWN).
AdminStateUp *bool
}
// Convenience vars for AdminStateUp values.
var (
iTrue = true
iFalse = false
Up AdminState = &iTrue
Down AdminState = &iFalse
// Supported attributes for create/update operations.
const (
ProtocolTCP Protocol = "TCP"
ProtocolHTTP Protocol = "HTTP"
ProtocolHTTPS Protocol = "HTTPS"
)
// ListOptsBuilder allows extensions to add additional parameters to the
@ -86,10 +44,7 @@ type ListOpts struct {
// ToListenerListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToListenerListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
if err != nil {
return "", err
}
return q.String(), nil
return q.String(), err
}
// List returns a Pager which allows you to iterate over a collection of
@ -107,27 +62,11 @@ func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
}
url += query
}
return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page {
return ListenerPage{pagination.LinkedPageBase{PageResult: r}}
})
}
type Protocol string
// Supported attributes for create/update operations.
const (
ProtocolTCP Protocol = "TCP"
ProtocolHTTP Protocol = "HTTP"
ProtocolHTTPS Protocol = "HTTPS"
)
var (
errLoadbalancerIdRequired = fmt.Errorf("LoadbalancerID is required")
errProtocolRequired = fmt.Errorf("Protocol is required")
errProtocolPortRequired = fmt.Errorf("ProtocolPort is required")
)
// CreateOptsBuilder is the interface options structs have to satisfy in order
// to be used in the main Create operation in this package. Since many
// extensions decorate or modify the common logic, it is useful for them to
@ -138,53 +77,35 @@ type CreateOptsBuilder interface {
// CreateOpts is the common options struct used in this package's Create
// operation.
type CreateOpts listenerOpts
type CreateOpts struct {
// The load balancer on which to provision this listener.
LoadbalancerID string `json:"loadbalancer_id" required:"true"`
// The protocol - can either be TCP, HTTP or HTTPS.
Protocol Protocol `json:"protocol" required:"true"`
// The port on which to listen for client traffic.
ProtocolPort int `json:"protocol_port" required:"true"`
// Indicates the owner of the Listener. Required for admins.
TenantID string `json:"tenant_id,omitempty"`
// Human-readable name for the Listener. Does not have to be unique.
Name string `json:"name,omitempty"`
// The ID of the default pool with which the Listener is associated.
DefaultPoolID string `json:"default_pool_id,omitempty"`
// Human-readable description for the Listener.
Description string `json:"description,omitempty"`
// The maximum number of connections allowed for the Listener.
ConnLimit *int `json:"connection_limit,omitempty"`
// A reference to a container of TLS secrets.
DefaultTlsContainerRef string `json:"default_tls_container_ref,omitempty"`
// A list of references to TLS secrets.
SniContainerRefs []string `json:"sni_container_refs,omitempty"`
// The administrative state of the Listener. A valid value is true (UP)
// or false (DOWN).
AdminStateUp *bool `json:"admin_state_up,omitempty"`
}
// ToListenerCreateMap casts a CreateOpts struct to a map.
func (opts CreateOpts) ToListenerCreateMap() (map[string]interface{}, error) {
l := make(map[string]interface{})
if opts.LoadbalancerID != "" {
l["loadbalancer_id"] = opts.LoadbalancerID
} else {
return nil, errLoadbalancerIdRequired
}
if opts.Protocol != "" {
l["protocol"] = opts.Protocol
} else {
return nil, errProtocolRequired
}
if opts.ProtocolPort != 0 {
l["protocol_port"] = opts.ProtocolPort
} else {
return nil, errProtocolPortRequired
}
if opts.AdminStateUp != nil {
l["admin_state_up"] = &opts.AdminStateUp
}
if opts.Name != "" {
l["name"] = opts.Name
}
if opts.TenantID != "" {
l["tenant_id"] = opts.TenantID
}
if opts.DefaultPoolID != "" {
l["default_pool_id"] = opts.DefaultPoolID
}
if opts.Description != "" {
l["description"] = opts.Description
}
if opts.ConnLimit != nil {
l["connection_limit"] = &opts.ConnLimit
}
if opts.DefaultTlsContainerRef != "" {
l["default_tls_container_ref"] = opts.DefaultTlsContainerRef
}
if opts.SniContainerRefs != nil {
l["sni_container_refs"] = opts.SniContainerRefs
}
return map[string]interface{}{"listener": l}, nil
return gophercloud.BuildRequestBody(opts, "listener")
}
// Create is an operation which provisions a new Listeners based on the
@ -194,25 +115,20 @@ func (opts CreateOpts) ToListenerCreateMap() (map[string]interface{}, error) {
//
// Users with an admin role can create Listeners on behalf of other tenants by
// specifying a TenantID attribute different than their own.
func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult {
var res CreateResult
reqBody, err := opts.ToListenerCreateMap()
func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToListenerCreateMap()
if err != nil {
res.Err = err
return res
r.Err = err
return
}
// Send request to API
_, res.Err = c.Post(rootURL(c), reqBody, &res.Body, nil)
return res
_, r.Err = c.Post(rootURL(c), b, &r.Body, nil)
return
}
// Get retrieves a particular Listeners 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
func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = c.Get(resourceURL(c, id), &r.Body, nil)
return
}
// UpdateOptsBuilder is the interface options structs have to satisfy in order
@ -225,55 +141,42 @@ type UpdateOptsBuilder interface {
// UpdateOpts is the common options struct used in this package's Update
// operation.
type UpdateOpts listenerOpts
type UpdateOpts struct {
// Human-readable name for the Listener. Does not have to be unique.
Name string `json:"name,omitempty"`
// Human-readable description for the Listener.
Description string `json:"description,omitempty"`
// The maximum number of connections allowed for the Listener.
ConnLimit *int `json:"connection_limit,omitempty"`
// A reference to a container of TLS secrets.
DefaultTlsContainerRef string `json:"default_tls_container_ref,omitempty"`
// A list of references to TLS secrets.
SniContainerRefs []string `json:"sni_container_refs,omitempty"`
// The administrative state of the Listener. A valid value is true (UP)
// or false (DOWN).
AdminStateUp *bool `json:"admin_state_up,omitempty"`
}
// ToListenerUpdateMap casts a UpdateOpts struct to a map.
func (opts UpdateOpts) ToListenerUpdateMap() (map[string]interface{}, error) {
l := make(map[string]interface{})
if opts.Name != "" {
l["name"] = opts.Name
}
if opts.Description != "" {
l["description"] = opts.Description
}
if opts.ConnLimit != nil {
l["connection_limit"] = &opts.ConnLimit
}
if opts.DefaultTlsContainerRef != "" {
l["default_tls_container_ref"] = opts.DefaultTlsContainerRef
}
if opts.SniContainerRefs != nil {
l["sni_container_refs"] = opts.SniContainerRefs
}
if opts.AdminStateUp != nil {
l["admin_state_up"] = &opts.AdminStateUp
}
return map[string]interface{}{"listener": l}, nil
return gophercloud.BuildRequestBody(opts, "listener")
}
// Update is an operation which modifies the attributes of the specified Listener.
func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResult {
var res UpdateResult
reqBody, err := opts.ToListenerUpdateMap()
func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) (r UpdateResult) {
b, err := opts.ToListenerUpdateMap()
if err != nil {
res.Err = err
return res
r.Err = err
return
}
// Send request to API
_, res.Err = c.Put(resourceURL(c, id), reqBody, &res.Body, &gophercloud.RequestOpts{
_, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 202},
})
return res
return
}
// Delete will permanently delete a particular Listeners 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
func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = c.Delete(resourceURL(c, id), nil)
return
}

View File

@ -1,14 +1,13 @@
package listeners
import (
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools"
"github.com/rackspace/gophercloud/pagination"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools"
"github.com/gophercloud/gophercloud/pagination"
)
type LoadBalancerID struct {
ID string `mapstructure:"id" json:"id"`
ID string `json:"id"`
}
// Listener is the primary load balancing configuration object that specifies
@ -16,44 +15,32 @@ type LoadBalancerID struct {
// as other details such as the load balancing method to be use, protocol, etc.
type Listener struct {
// The unique ID for the Listener.
ID string `mapstructure:"id" json:"id"`
ID string `json:"id"`
// Owner of the Listener. Only an admin user can specify a tenant ID other than its own.
TenantID string `mapstructure:"tenant_id" json:"tenant_id"`
TenantID string `json:"tenant_id"`
// Human-readable name for the Listener. Does not have to be unique.
Name string `mapstructure:"name" json:"name"`
Name string `json:"name"`
// Human-readable description for the Listener.
Description string `mapstructure:"description" json:"description"`
Description string `json:"description"`
// The protocol to loadbalance. A valid value is TCP, HTTP, or HTTPS.
Protocol string `mapstructure:"protocol" json:"protocol"`
Protocol string `json:"protocol"`
// The port on which to listen to client traffic that is associated with the
// Loadbalancer. A valid value is from 0 to 65535.
ProtocolPort int `mapstructure:"protocol_port" json:"protocol_port"`
ProtocolPort int `json:"protocol_port"`
// The UUID of default pool. Must have compatible protocol with listener.
DefaultPoolID string `mapstructure:"default_pool_id" json:"default_pool_id"`
DefaultPoolID string `json:"default_pool_id"`
// A list of load balancer IDs.
Loadbalancers []LoadBalancerID `mapstructure:"loadbalancers" json:"loadbalancers"`
Loadbalancers []LoadBalancerID `json:"loadbalancers"`
// The maximum number of connections allowed for the Loadbalancer. Default is -1,
// meaning no limit.
ConnLimit int `mapstructure:"connection_limit" json:"connection_limit"`
ConnLimit int `json:"connection_limit"`
// The list of references to TLS secrets.
SniContainerRefs []string `mapstructure:"sni_container_refs" json:"sni_container_refs"`
SniContainerRefs []string `json:"sni_container_refs"`
// Optional. A reference to a container of TLS secrets.
DefaultTlsContainerRef string `mapstructure:"default_tls_container_ref" json:"default_tls_container_ref"`
DefaultTlsContainerRef string `json:"default_tls_container_ref"`
// The administrative state of the Listener. A valid value is true (UP) or false (DOWN).
AdminStateUp bool `mapstructure:"admin_state_up" json:"admin_state_up"`
Pools []pools.Pool `mapstructure:"pools" json:"pools"`
AdminStateUp bool `json:"admin_state_up"`
Pools []pools.Pool `json:"pools"`
}
// ListenerPage is the page returned by a pager when traversing over a
@ -65,38 +52,32 @@ type ListenerPage struct {
// NextPageURL is invoked when a paginated collection of routers 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 ListenerPage) NextPageURL() (string, error) {
type resp struct {
Links []gophercloud.Link `mapstructure:"listeners_links"`
func (r ListenerPage) NextPageURL() (string, error) {
var s struct {
Links []gophercloud.Link `json:"listeners_links"`
}
var r resp
err := mapstructure.Decode(p.Body, &r)
err := r.ExtractInto(&s)
if err != nil {
return "", err
}
return gophercloud.ExtractNextURL(r.Links)
return gophercloud.ExtractNextURL(s.Links)
}
// IsEmpty checks whether a RouterPage struct is empty.
func (p ListenerPage) IsEmpty() (bool, error) {
is, err := ExtractListeners(p)
if err != nil {
return true, nil
}
return len(is) == 0, nil
func (r ListenerPage) IsEmpty() (bool, error) {
is, err := ExtractListeners(r)
return len(is) == 0, err
}
// ExtractListeners accepts a Page struct, specifically a ListenerPage struct,
// and extracts the elements into a slice of Listener structs. In other words,
// a generic collection is mapped into a relevant slice.
func ExtractListeners(page pagination.Page) ([]Listener, error) {
var resp struct {
Listeners []Listener `mapstructure:"listeners" json:"listeners"`
func ExtractListeners(r pagination.Page) ([]Listener, error) {
var s struct {
Listeners []Listener `json:"listeners"`
}
err := mapstructure.Decode(page.(ListenerPage).Body, &resp)
return resp.Listeners, err
err := (r.(ListenerPage)).ExtractInto(&s)
return s.Listeners, err
}
type commonResult struct {
@ -105,17 +86,11 @@ type commonResult struct {
// Extract is a function that accepts a result and extracts a router.
func (r commonResult) Extract() (*Listener, error) {
if r.Err != nil {
return nil, r.Err
var s struct {
Listener *Listener `json:"listener"`
}
var res struct {
Listener *Listener `mapstructure:"listener" json:"listener"`
}
err := mapstructure.Decode(r.Body, &res)
return res.Listener, err
err := r.ExtractInto(&s)
return s.Listener, err
}
// CreateResult represents the result of a create operation.

View File

@ -1,6 +1,6 @@
package listeners
import "github.com/rackspace/gophercloud"
import "github.com/gophercloud/gophercloud"
const (
rootPath = "lbaas"

View File

@ -1,59 +1,14 @@
package loadbalancers
import (
"fmt"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
)
// AdminState gives users a solid type to work with for create and update
// operations. It is recommended that users use the `Up` and `Down` enums.
type AdminState *bool
type loadbalancerOpts struct {
// Optional. Human-readable name for the Loadbalancer. Does not have to be unique.
Name string
// Optional. Human-readable description for the Loadbalancer.
Description string
// Required. The network on which to allocate the Loadbalancer's address. A tenant can
// only create Loadbalancers on networks authorized by policy (e.g. networks that
// belong to them or networks that are shared).
VipSubnetID string
// Required for admins. The UUID of the tenant who owns the Loadbalancer.
// Only administrative users can specify a tenant UUID other than their own.
TenantID string
// Optional. The IP address of the Loadbalancer.
VipAddress string
// Optional. The administrative state of the Loadbalancer. A valid value is true (UP)
// or false (DOWN).
AdminStateUp *bool
// Optional. The UUID of a flavor.
Flavor string
// Optional. The name of the provider.
Provider string
}
// Convenience vars for AdminStateUp values.
var (
iTrue = true
iFalse = false
Up AdminState = &iTrue
Down AdminState = &iFalse
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// ListOptsBuilder allows extensions to add additional parameters to the
// List request.
type ListOptsBuilder interface {
ToLoadbalancerListQuery() (string, error)
ToLoadBalancerListQuery() (string, error)
}
// ListOpts allows the filtering and sorting of paginated collections through
@ -67,6 +22,7 @@ type ListOpts struct {
TenantID string `q:"tenant_id"`
ProvisioningStatus string `q:"provisioning_status"`
VipAddress string `q:"vip_address"`
VipPortID string `q:"vip_port_id"`
VipSubnetID string `q:"vip_subnet_id"`
ID string `q:"id"`
OperatingStatus string `q:"operating_status"`
@ -80,12 +36,9 @@ type ListOpts struct {
}
// ToLoadbalancerListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToLoadbalancerListQuery() (string, error) {
func (opts ListOpts) ToLoadBalancerListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
if err != nil {
return "", err
}
return q.String(), nil
return q.String(), err
}
// List returns a Pager which allows you to iterate over a collection of
@ -97,66 +50,53 @@ func (opts ListOpts) ToLoadbalancerListQuery() (string, error) {
func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
url := rootURL(c)
if opts != nil {
query, err := opts.ToLoadbalancerListQuery()
query, err := opts.ToLoadBalancerListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
}
return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page {
return LoadbalancerPage{pagination.LinkedPageBase{PageResult: r}}
return LoadBalancerPage{pagination.LinkedPageBase{PageResult: r}}
})
}
var (
errVipSubnetIDRequried = fmt.Errorf("VipSubnetID is required")
)
// CreateOptsBuilder is the interface options structs have to satisfy in order
// to be used in the main Create operation in this package. Since many
// extensions decorate or modify the common logic, it is useful for them to
// satisfy a basic interface in order for them to be used.
type CreateOptsBuilder interface {
ToLoadbalancerCreateMap() (map[string]interface{}, error)
ToLoadBalancerCreateMap() (map[string]interface{}, error)
}
// CreateOpts is the common options struct used in this package's Create
// operation.
type CreateOpts loadbalancerOpts
type CreateOpts struct {
// Optional. Human-readable name for the Loadbalancer. Does not have to be unique.
Name string `json:"name,omitempty"`
// Optional. Human-readable description for the Loadbalancer.
Description string `json:"description,omitempty"`
// Required. The network on which to allocate the Loadbalancer's address. A tenant can
// only create Loadbalancers on networks authorized by policy (e.g. networks that
// belong to them or networks that are shared).
VipSubnetID string `json:"vip_subnet_id" required:"true"`
// Required for admins. The UUID of the tenant who owns the Loadbalancer.
// Only administrative users can specify a tenant UUID other than their own.
TenantID string `json:"tenant_id,omitempty"`
// Optional. The IP address of the Loadbalancer.
VipAddress string `json:"vip_address,omitempty"`
// Optional. The administrative state of the Loadbalancer. A valid value is true (UP)
// or false (DOWN).
AdminStateUp *bool `json:"admin_state_up,omitempty"`
// Optional. The UUID of a flavor.
Flavor string `json:"flavor,omitempty"`
// Optional. The name of the provider.
Provider string `json:"provider,omitempty"`
}
// ToLoadbalancerCreateMap casts a CreateOpts struct to a map.
func (opts CreateOpts) ToLoadbalancerCreateMap() (map[string]interface{}, error) {
l := make(map[string]interface{})
if opts.VipSubnetID != "" {
l["vip_subnet_id"] = opts.VipSubnetID
} else {
return nil, errVipSubnetIDRequried
}
if opts.AdminStateUp != nil {
l["admin_state_up"] = &opts.AdminStateUp
}
if opts.Name != "" {
l["name"] = opts.Name
}
if opts.TenantID != "" {
l["tenant_id"] = opts.TenantID
}
if opts.Description != "" {
l["description"] = opts.Description
}
if opts.VipAddress != "" {
l["vip_address"] = opts.VipAddress
}
if opts.Flavor != "" {
l["flavor"] = opts.Flavor
}
if opts.Provider != "" {
l["provider"] = opts.Provider
}
return map[string]interface{}{"loadbalancer": l}, nil
// ToLoadBalancerCreateMap casts a CreateOpts struct to a map.
func (opts CreateOpts) ToLoadBalancerCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "loadbalancer")
}
// Create is an operation which provisions a new loadbalancer based on the
@ -166,25 +106,20 @@ func (opts CreateOpts) ToLoadbalancerCreateMap() (map[string]interface{}, error)
//
// Users with an admin role can create loadbalancers on behalf of other tenants by
// specifying a TenantID attribute different than their own.
func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult {
var res CreateResult
reqBody, err := opts.ToLoadbalancerCreateMap()
func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToLoadBalancerCreateMap()
if err != nil {
res.Err = err
return res
r.Err = err
return
}
// Send request to API
_, res.Err = c.Post(rootURL(c), reqBody, &res.Body, nil)
return res
_, r.Err = c.Post(rootURL(c), b, &r.Body, nil)
return
}
// Get retrieves a particular Loadbalancer 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
func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = c.Get(resourceURL(c, id), &r.Body, nil)
return
}
// UpdateOptsBuilder is the interface options structs have to satisfy in order
@ -192,57 +127,46 @@ func Get(c *gophercloud.ServiceClient, id string) GetResult {
// extensions decorate or modify the common logic, it is useful for them to
// satisfy a basic interface in order for them to be used.
type UpdateOptsBuilder interface {
ToLoadbalancerUpdateMap() (map[string]interface{}, error)
ToLoadBalancerUpdateMap() (map[string]interface{}, error)
}
// UpdateOpts is the common options struct used in this package's Update
// operation.
type UpdateOpts loadbalancerOpts
// ToLoadbalancerUpdateMap casts a UpdateOpts struct to a map.
func (opts UpdateOpts) ToLoadbalancerUpdateMap() (map[string]interface{}, error) {
l := make(map[string]interface{})
if opts.Name != "" {
l["name"] = opts.Name
}
if opts.Description != "" {
l["description"] = opts.Description
}
if opts.AdminStateUp != nil {
l["admin_state_up"] = &opts.AdminStateUp
}
return map[string]interface{}{"loadbalancer": l}, nil
type UpdateOpts struct {
// Optional. Human-readable name for the Loadbalancer. Does not have to be unique.
Name string `json:"name,omitempty"`
// Optional. Human-readable description for the Loadbalancer.
Description string `json:"description,omitempty"`
// Optional. The administrative state of the Loadbalancer. A valid value is true (UP)
// or false (DOWN).
AdminStateUp *bool `json:"admin_state_up,omitempty"`
}
// Update is an operation which modifies the attributes of the specified Loadbalancer.
func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResult {
var res UpdateResult
// ToLoadBalancerUpdateMap casts a UpdateOpts struct to a map.
func (opts UpdateOpts) ToLoadBalancerUpdateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "loadbalancer")
}
reqBody, err := opts.ToLoadbalancerUpdateMap()
// Update is an operation which modifies the attributes of the specified LoadBalancer.
func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) (r UpdateResult) {
b, err := opts.ToLoadBalancerUpdateMap()
if err != nil {
res.Err = err
return res
r.Err = err
return
}
// Send request to API
_, res.Err = c.Put(resourceURL(c, id), reqBody, &res.Body, &gophercloud.RequestOpts{
_, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 202},
})
return res
return
}
// Delete will permanently delete a particular Loadbalancer 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
// Delete will permanently delete a particular LoadBalancer based on its unique ID.
func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = c.Delete(resourceURL(c, id), nil)
return
}
func GetStatuses(c *gophercloud.ServiceClient, id string) GetResult {
var res GetResult
_, res.Err = c.Get(statusRootURL(c, id), &res.Body, nil)
return res
func GetStatuses(c *gophercloud.ServiceClient, id string) (r GetStatusesResult) {
_, r.Err = c.Get(statusRootURL(c, id), &r.Body, nil)
return
}

View File

@ -0,0 +1,125 @@
package loadbalancers
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners"
"github.com/gophercloud/gophercloud/pagination"
)
// LoadBalancer is the primary load balancing configuration object that specifies
// the virtual IP address on which client traffic is received, as well
// as other details such as the load balancing method to be use, protocol, etc.
type LoadBalancer struct {
// Human-readable description for the Loadbalancer.
Description string `json:"description"`
// The administrative state of the Loadbalancer. A valid value is true (UP) or false (DOWN).
AdminStateUp bool `json:"admin_state_up"`
// Owner of the LoadBalancer. Only an admin user can specify a tenant ID other than its own.
TenantID string `json:"tenant_id"`
// The provisioning status of the LoadBalancer. This value is ACTIVE, PENDING_CREATE or ERROR.
ProvisioningStatus string `json:"provisioning_status"`
// The IP address of the Loadbalancer.
VipAddress string `json:"vip_address"`
// The UUID of the port associated with the IP address.
VipPortID string `json:"vip_port_id"`
// The UUID of the subnet on which to allocate the virtual IP for the Loadbalancer address.
VipSubnetID string `json:"vip_subnet_id"`
// The unique ID for the LoadBalancer.
ID string `json:"id"`
// The operating status of the LoadBalancer. This value is ONLINE or OFFLINE.
OperatingStatus string `json:"operating_status"`
// Human-readable name for the LoadBalancer. Does not have to be unique.
Name string `json:"name"`
// The UUID of a flavor if set.
Flavor string `json:"flavor"`
// The name of the provider.
Provider string `json:"provider"`
Listeners []listeners.Listener `json:"listeners"`
}
type StatusTree struct {
Loadbalancer *LoadBalancer `json:"loadbalancer"`
}
// LoadBalancerPage is the page returned by a pager when traversing over a
// collection of routers.
type LoadBalancerPage struct {
pagination.LinkedPageBase
}
// NextPageURL is invoked when a paginated collection of routers 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 LoadBalancerPage) NextPageURL() (string, error) {
var s struct {
Links []gophercloud.Link `json:"loadbalancers_links"`
}
err := r.ExtractInto(&s)
if err != nil {
return "", err
}
return gophercloud.ExtractNextURL(s.Links)
}
// IsEmpty checks whether a LoadBalancerPage struct is empty.
func (p LoadBalancerPage) IsEmpty() (bool, error) {
is, err := ExtractLoadBalancers(p)
return len(is) == 0, err
}
// ExtractLoadBalancers accepts a Page struct, specifically a LoadbalancerPage struct,
// and extracts the elements into a slice of LoadBalancer structs. In other words,
// a generic collection is mapped into a relevant slice.
func ExtractLoadBalancers(r pagination.Page) ([]LoadBalancer, error) {
var s struct {
LoadBalancers []LoadBalancer `json:"loadbalancers"`
}
err := (r.(LoadBalancerPage)).ExtractInto(&s)
return s.LoadBalancers, err
}
type commonResult struct {
gophercloud.Result
}
// Extract is a function that accepts a result and extracts a router.
func (r commonResult) Extract() (*LoadBalancer, error) {
var s struct {
LoadBalancer *LoadBalancer `json:"loadbalancer"`
}
err := r.ExtractInto(&s)
return s.LoadBalancer, err
}
type GetStatusesResult struct {
gophercloud.Result
}
// Extract is a function that accepts a result and extracts a Loadbalancer.
func (r GetStatusesResult) Extract() (*StatusTree, error) {
var s struct {
Statuses *StatusTree `json:"statuses"`
}
err := r.ExtractInto(&s)
return s.Statuses, 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
}
// UpdateResult represents the result of an update operation.
type UpdateResult struct {
commonResult
}
// DeleteResult represents the result of a delete operation.
type DeleteResult struct {
gophercloud.ErrResult
}

View File

@ -1,6 +1,6 @@
package loadbalancers
import "github.com/rackspace/gophercloud"
import "github.com/gophercloud/gophercloud"
const (
rootPath = "lbaas"

View File

@ -2,51 +2,11 @@ package monitors
import (
"fmt"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
type monitorOpts struct {
// Required. The Pool to Monitor.
PoolID string
// Optional. The Name of the Monitor.
Name string
// Required for admins. Indicates the owner of the Loadbalancer.
TenantID string
// Required. The type of probe, which is PING, TCP, HTTP, or HTTPS, that is
// sent by the load balancer to verify the member state.
Type string
// Required. The time, in seconds, between sending probes to members.
Delay int
// Required. Maximum number of seconds for a Monitor to wait for a ping reply
// before it times out. The value must be less than the delay value.
Timeout int
// Required. Number of permissible ping failures before changing the member's
// status to INACTIVE. Must be a number between 1 and 10.
MaxRetries int
// Required for HTTP(S) types. URI path that will be accessed if Monitor type
// is HTTP or HTTPS.
URLPath string
// Required for HTTP(S) types. The HTTP method used for requests by the
// Monitor. If this attribute is not specified, it defaults to "GET".
HTTPMethod string
// Required for HTTP(S) types. Expected HTTP codes for a passing HTTP(S)
// Monitor. You can either specify a single status like "200", or a range
// like "200-202".
ExpectedCodes string
AdminStateUp *bool
}
// ListOptsBuilder allows extensions to add additional parameters to the
// List request.
type ListOptsBuilder interface {
@ -102,7 +62,6 @@ func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
}
url += query
}
return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page {
return MonitorPage{pagination.LinkedPageBase{PageResult: r}}
})
@ -117,13 +76,6 @@ const (
)
var (
errPoolIDRequired = fmt.Errorf("PoolID to monitor is required")
errValidTypeRequired = fmt.Errorf("A valid Type is required. Supported values are PING, TCP, HTTP and HTTPS")
errDelayRequired = fmt.Errorf("Delay is required")
errTimeoutRequired = fmt.Errorf("Timeout is required")
errMaxRetriesRequired = fmt.Errorf("MaxRetries is required")
errURLPathRequired = fmt.Errorf("URL path is required")
errExpectedCodesRequired = fmt.Errorf("ExpectedCodes is required")
errDelayMustGETimeout = fmt.Errorf("Delay must be greater than or equal to timeout")
)
@ -137,64 +89,57 @@ type CreateOptsBuilder interface {
// CreateOpts is the common options struct used in this package's Create
// operation.
type CreateOpts monitorOpts
type CreateOpts struct {
// Required. The Pool to Monitor.
PoolID string `json:"pool_id" required:"true"`
// Required. The type of probe, which is PING, TCP, HTTP, or HTTPS, that is
// sent by the load balancer to verify the member state.
Type string `json:"type" required:"true"`
// Required. The time, in seconds, between sending probes to members.
Delay int `json:"delay" required:"true"`
// Required. Maximum number of seconds for a Monitor to wait for a ping reply
// before it times out. The value must be less than the delay value.
Timeout int `json:"timeout" required:"true"`
// Required. Number of permissible ping failures before changing the member's
// status to INACTIVE. Must be a number between 1 and 10.
MaxRetries int `json:"max_retries" required:"true"`
// Required for HTTP(S) types. URI path that will be accessed if Monitor type
// is HTTP or HTTPS.
URLPath string `json:"url_path,omitempty"`
// Required for HTTP(S) types. The HTTP method used for requests by the
// Monitor. If this attribute is not specified, it defaults to "GET".
HTTPMethod string `json:"http_method,omitempty"`
// Required for HTTP(S) types. Expected HTTP codes for a passing HTTP(S)
// Monitor. You can either specify a single status like "200", or a range
// like "200-202".
ExpectedCodes string `json:"expected_codes,omitempty"`
// Indicates the owner of the Loadbalancer. Required for admins.
TenantID string `json:"tenant_id,omitempty"`
// Optional. The Name of the Monitor.
Name string `json:"name,omitempty"`
AdminStateUp *bool `json:"admin_state_up,omitempty"`
}
// ToMonitorCreateMap casts a CreateOpts struct to a map.
func (opts CreateOpts) ToMonitorCreateMap() (map[string]interface{}, error) {
l := make(map[string]interface{})
allowed := map[string]bool{TypeHTTP: true, TypeHTTPS: true, TypeTCP: true, TypePING: true}
if allowed[opts.Type] {
l["type"] = opts.Type
} else {
return nil, errValidTypeRequired
}
if opts.Type == TypeHTTP || opts.Type == TypeHTTPS {
if opts.URLPath != "" {
l["url_path"] = opts.URLPath
} else {
return nil, errURLPathRequired
}
if opts.ExpectedCodes != "" {
l["expected_codes"] = opts.ExpectedCodes
} else {
return nil, errExpectedCodesRequired
}
}
if opts.PoolID != "" {
l["pool_id"] = opts.PoolID
} else {
return nil, errPoolIDRequired
}
if opts.Delay != 0 {
l["delay"] = opts.Delay
} else {
return nil, errDelayRequired
}
if opts.Timeout != 0 {
l["timeout"] = opts.Timeout
} else {
return nil, errMaxRetriesRequired
}
if opts.MaxRetries != 0 {
l["max_retries"] = opts.MaxRetries
} else {
return nil, errMaxRetriesRequired
}
if opts.AdminStateUp != nil {
l["admin_state_up"] = &opts.AdminStateUp
}
if opts.Name != "" {
l["name"] = opts.Name
}
if opts.TenantID != "" {
l["tenant_id"] = opts.TenantID
}
if opts.HTTPMethod != "" {
l["http_method"] = opts.HTTPMethod
b, err := gophercloud.BuildRequestBody(opts, "healthmonitor")
if err != nil {
return nil, err
}
return map[string]interface{}{"healthmonitor": l}, nil
switch opts.Type {
case TypeHTTP, TypeHTTPS:
switch opts.URLPath {
case "":
return nil, fmt.Errorf("URLPath must be provided for HTTP and HTTPS")
}
switch opts.ExpectedCodes {
case "":
return nil, fmt.Errorf("ExpectedCodes must be provided for HTTP and HTTPS")
}
}
return b, nil
}
/*
@ -212,26 +157,20 @@ func (opts CreateOpts) ToMonitorCreateMap() (map[string]interface{}, error) {
CreateOpts{Type: TypeHTTP, Delay: 20, Timeout: 10, MaxRetries: 3,
HttpMethod: "HEAD", ExpectedCodes: "200", PoolID: "2c946bfc-1804-43ab-a2ff-58f6a762b505"}
*/
func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
var res CreateResult
reqBody, err := opts.ToMonitorCreateMap()
func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToMonitorCreateMap()
if err != nil {
res.Err = err
return res
r.Err = err
return
}
// Send request to API
_, res.Err = c.Post(rootURL(c), reqBody, &res.Body, nil)
return res
_, r.Err = c.Post(rootURL(c), b, &r.Body, nil)
return
}
// Get retrieves a particular Health Monitor 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
func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = c.Get(resourceURL(c, id), &r.Body, nil)
return
}
// UpdateOptsBuilder is the interface options structs have to satisfy in order
@ -244,61 +183,51 @@ type UpdateOptsBuilder interface {
// UpdateOpts is the common options struct used in this package's Update
// operation.
type UpdateOpts monitorOpts
type UpdateOpts struct {
// Required. The time, in seconds, between sending probes to members.
Delay int `json:"delay,omitempty"`
// Required. Maximum number of seconds for a Monitor to wait for a ping reply
// before it times out. The value must be less than the delay value.
Timeout int `json:"timeout,omitempty"`
// Required. Number of permissible ping failures before changing the member's
// status to INACTIVE. Must be a number between 1 and 10.
MaxRetries int `json:"max_retries,omitempty"`
// Required for HTTP(S) types. URI path that will be accessed if Monitor type
// is HTTP or HTTPS.
URLPath string `json:"url_path,omitempty"`
// Required for HTTP(S) types. The HTTP method used for requests by the
// Monitor. If this attribute is not specified, it defaults to "GET".
HTTPMethod string `json:"http_method,omitempty"`
// Required for HTTP(S) types. Expected HTTP codes for a passing HTTP(S)
// Monitor. You can either specify a single status like "200", or a range
// like "200-202".
ExpectedCodes string `json:"expected_codes,omitempty"`
// Optional. The Name of the Monitor.
Name string `json:"name,omitempty"`
AdminStateUp *bool `json:"admin_state_up,omitempty"`
}
// ToMonitorUpdateMap casts a UpdateOpts struct to a map.
func (opts UpdateOpts) ToMonitorUpdateMap() (map[string]interface{}, error) {
l := make(map[string]interface{})
if opts.URLPath != "" {
l["url_path"] = opts.URLPath
}
if opts.ExpectedCodes != "" {
l["expected_codes"] = opts.ExpectedCodes
}
if opts.Delay != 0 {
l["delay"] = opts.Delay
}
if opts.Timeout != 0 {
l["timeout"] = opts.Timeout
}
if opts.MaxRetries != 0 {
l["max_retries"] = opts.MaxRetries
}
if opts.AdminStateUp != nil {
l["admin_state_up"] = &opts.AdminStateUp
}
if opts.Name != "" {
l["name"] = opts.Name
}
if opts.HTTPMethod != "" {
l["http_method"] = opts.HTTPMethod
}
return map[string]interface{}{"healthmonitor": l}, nil
return gophercloud.BuildRequestBody(opts, "healthmonitor")
}
// Update is an operation which modifies the attributes of the specified Monitor.
func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult {
var res UpdateResult
reqBody, err := opts.ToMonitorUpdateMap()
func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
b, err := opts.ToMonitorUpdateMap()
if err != nil {
res.Err = err
return res
r.Err = err
return
}
// Send request to API
_, res.Err = c.Put(resourceURL(c, id), reqBody, &res.Body, &gophercloud.RequestOpts{
_, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 202},
})
return res
return
}
// Delete will permanently delete a particular Monitor 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
func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = c.Delete(resourceURL(c, id), nil)
return
}

View File

@ -1,13 +1,12 @@
package monitors
import (
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
type PoolID struct {
ID string `mapstructure:"id" json:"id"`
ID string `json:"id"`
}
// Monitor represents a load balancer health monitor. A health monitor is used
@ -27,52 +26,49 @@ type PoolID struct {
// must declare the member to be healthy for it to stay ACTIVE.
type Monitor struct {
// The unique ID for the Monitor.
ID string
ID string `json:"id"`
// The Name of the Monitor.
Name string
Name string `json:"name"`
// Only an administrative user can specify a tenant ID
// other than its own.
TenantID string `json:"tenant_id" mapstructure:"tenant_id"`
TenantID string `json:"tenant_id"`
// The type of probe sent by the load balancer to verify the member state,
// which is PING, TCP, HTTP, or HTTPS.
Type string
Type string `json:"type"`
// The time, in seconds, between sending probes to members.
Delay int
Delay int `json:"delay"`
// The maximum number of seconds for a monitor to wait for a connection to be
// established before it times out. This value must be less than the delay value.
Timeout int
Timeout int `json:"timeout"`
// Number of allowed connection failures before changing the status of the
// member to INACTIVE. A valid value is from 1 to 10.
MaxRetries int `json:"max_retries" mapstructure:"max_retries"`
MaxRetries int `json:"max_retries"`
// The HTTP method that the monitor uses for requests.
HTTPMethod string `json:"http_method" mapstructure:"http_method"`
HTTPMethod string `json:"http_method"`
// The HTTP path of the request sent by the monitor to test the health of a
// member. Must be a string beginning with a forward slash (/).
URLPath string `json:"url_path" mapstructure:"url_path"`
URLPath string `json:"url_path" `
// Expected HTTP codes for a passing HTTP(S) monitor.
ExpectedCodes string `json:"expected_codes" mapstructure:"expected_codes"`
ExpectedCodes string `json:"expected_codes"`
// The administrative state of the health monitor, which is up (true) or down (false).
AdminStateUp bool `json:"admin_state_up" mapstructure:"admin_state_up"`
AdminStateUp bool `json:"admin_state_up"`
// The status of the health monitor. Indicates whether the health monitor is
// operational.
Status string
Status string `json:"status"`
// List of pools that are associated with the health monitor.
Pools []PoolID `mapstructure:"pools" json:"pools"`
}
type Pool struct {
Pools []PoolID `json:"pools"`
}
// MonitorPage is the page returned by a pager when traversing over a
@ -84,40 +80,34 @@ type MonitorPage struct {
// NextPageURL is invoked when a paginated collection of monitors 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 MonitorPage) NextPageURL() (string, error) {
type resp struct {
Links []gophercloud.Link `mapstructure:"healthmonitors_links"`
func (r MonitorPage) NextPageURL() (string, error) {
var s struct {
Links []gophercloud.Link `json:"healthmonitors_links"`
}
var r resp
err := mapstructure.Decode(p.Body, &r)
err := r.ExtractInto(&s)
if err != nil {
return "", err
}
return gophercloud.ExtractNextURL(r.Links)
return gophercloud.ExtractNextURL(s.Links)
}
// IsEmpty checks whether a PoolPage struct is empty.
func (p MonitorPage) IsEmpty() (bool, error) {
is, err := ExtractMonitors(p)
if err != nil {
return true, nil
}
return len(is) == 0, nil
// IsEmpty checks whether a MonitorPage struct is empty.
func (r MonitorPage) IsEmpty() (bool, error) {
is, err := ExtractMonitors(r)
return len(is) == 0, err
}
// ExtractMonitors accepts a Page struct, specifically a MonitorPage struct,
// and extracts the elements into a slice of Monitor structs. In other words,
// a generic collection is mapped into a relevant slice.
func ExtractMonitors(page pagination.Page) ([]Monitor, error) {
var resp struct {
Monitors []Monitor `mapstructure:"healthmonitors" json:"healthmonitors"`
func ExtractMonitors(r pagination.Page) ([]Monitor, error) {
var s struct {
Monitors []Monitor `json:"healthmonitors"`
}
err := mapstructure.Decode(page.(MonitorPage).Body, &resp)
return resp.Monitors, err
err := (r.(MonitorPage)).ExtractInto(&s)
return s.Monitors, err
}
type commonResult struct {
@ -126,17 +116,11 @@ type commonResult struct {
// Extract is a function that accepts a result and extracts a monitor.
func (r commonResult) Extract() (*Monitor, error) {
if r.Err != nil {
return nil, r.Err
var s struct {
Monitor *Monitor `json:"healthmonitor"`
}
var res struct {
Monitor *Monitor `json:"healthmonitor" mapstructure:"healthmonitor"`
}
err := mapstructure.Decode(r.Body, &res)
return res.Monitor, err
err := r.ExtractInto(&s)
return s.Monitor, err
}
// CreateResult represents the result of a create operation.

View File

@ -1,6 +1,6 @@
package monitors
import "github.com/rackspace/gophercloud"
import "github.com/gophercloud/gophercloud"
const (
rootPath = "lbaas"

View File

@ -1,58 +1,8 @@
package pools
import (
"fmt"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
)
// AdminState gives users a solid type to work with for create and update
// operations. It is recommended that users use the `Up` and `Down` enums.
type AdminState *bool
type poolOpts struct {
// Only required if the caller has an admin role and wants to create a pool
// for another tenant.
TenantID string
// Optional. Name of the pool.
Name string
// Optional. Human-readable description for the pool.
Description string
// Required. The protocol used by the pool members, you can use either
// ProtocolTCP, ProtocolHTTP, or ProtocolHTTPS.
Protocol Protocol
// The Loadbalancer on which the members of the pool will be associated with.
// Note: one of LoadbalancerID or ListenerID must be provided.
LoadbalancerID string
// The Listener on which the members of the pool will be associated with.
// Note: one of LoadbalancerID or ListenerID must be provided.
ListenerID string
// Required. The algorithm used to distribute load between the members of the pool. The
// current specification supports LBMethodRoundRobin, LBMethodLeastConnections
// and LBMethodSourceIp as valid values for this attribute.
LBMethod LBMethod
// Optional. Omit this field to prevent session persistence.
Persistence *SessionPersistence
// Optional. The administrative state of the Pool. A valid value is true (UP)
// or false (DOWN).
AdminStateUp *bool
}
// Convenience vars for AdminStateUp values.
var (
iTrue = true
iFalse = false
Up AdminState = &iTrue
Down AdminState = &iFalse
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// ListOptsBuilder allows extensions to add additional parameters to the
@ -84,10 +34,7 @@ type ListOpts struct {
// ToPoolListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToPoolListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
if err != nil {
return "", err
}
return q.String(), nil
return q.String(), err
}
// List returns a Pager which allows you to iterate over a collection of
@ -105,7 +52,6 @@ func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
}
url += query
}
return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page {
return PoolPage{pagination.LinkedPageBase{PageResult: r}}
})
@ -125,12 +71,6 @@ const (
ProtocolHTTPS Protocol = "HTTPS"
)
var (
errLoadbalancerOrListenerRequired = fmt.Errorf("A ListenerID or LoadbalancerID is required")
errValidLBMethodRequired = fmt.Errorf("A valid LBMethod is required. Supported values are ROUND_ROBIN, LEAST_CONNECTIONS, SOURCE_IP")
errValidProtocolRequired = fmt.Errorf("A valid Protocol is required. Supported values are TCP, HTTP, HTTPS")
)
// CreateOptsBuilder is the interface options structs have to satisfy in order
// to be used in the main Create operation in this package. Since many
// extensions decorate or modify the common logic, it is useful for them to
@ -141,71 +81,55 @@ type CreateOptsBuilder interface {
// CreateOpts is the common options struct used in this package's Create
// operation.
type CreateOpts poolOpts
type CreateOpts struct {
// The algorithm used to distribute load between the members of the pool. The
// current specification supports LBMethodRoundRobin, LBMethodLeastConnections
// and LBMethodSourceIp as valid values for this attribute.
LBMethod LBMethod `json:"lb_algorithm" required:"true"`
// The protocol used by the pool members, you can use either
// ProtocolTCP, ProtocolHTTP, or ProtocolHTTPS.
Protocol Protocol `json:"protocol" required:"true"`
// The Loadbalancer on which the members of the pool will be associated with.
// Note: one of LoadbalancerID or ListenerID must be provided.
LoadbalancerID string `json:"loadbalancer_id,omitempty" xor:"ListenerID"`
// The Listener on which the members of the pool will be associated with.
// Note: one of LoadbalancerID or ListenerID must be provided.
ListenerID string `json:"listener_id,omitempty" xor:"LoadbalancerID"`
// Only required if the caller has an admin role and wants to create a pool
// for another tenant.
TenantID string `json:"tenant_id,omitempty"`
// Name of the pool.
Name string `json:"name,omitempty"`
// Human-readable description for the pool.
Description string `json:"description,omitempty"`
// Omit this field to prevent session persistence.
Persistence *SessionPersistence `json:"session_persistence,omitempty"`
// The administrative state of the Pool. A valid value is true (UP)
// or false (DOWN).
AdminStateUp *bool `json:"admin_state_up,omitempty"`
}
// ToPoolCreateMap casts a CreateOpts struct to a map.
func (opts CreateOpts) ToPoolCreateMap() (map[string]interface{}, error) {
l := make(map[string]interface{})
allowedLBMethod := map[LBMethod]bool{LBMethodRoundRobin: true, LBMethodLeastConnections: true, LBMethodSourceIp: true}
allowedProtocol := map[Protocol]bool{ProtocolTCP: true, ProtocolHTTP: true, ProtocolHTTPS: true}
if allowedLBMethod[opts.LBMethod] {
l["lb_algorithm"] = opts.LBMethod
} else {
return nil, errValidLBMethodRequired
}
if allowedProtocol[opts.Protocol] {
l["protocol"] = opts.Protocol
} else {
return nil, errValidProtocolRequired
}
if opts.LoadbalancerID == "" && opts.ListenerID == "" {
return nil, errLoadbalancerOrListenerRequired
} else {
if opts.LoadbalancerID != "" {
l["loadbalancer_id"] = opts.LoadbalancerID
}
if opts.ListenerID != "" {
l["listener_id"] = opts.ListenerID
}
}
if opts.AdminStateUp != nil {
l["admin_state_up"] = &opts.AdminStateUp
}
if opts.Name != "" {
l["name"] = opts.Name
}
if opts.TenantID != "" {
l["tenant_id"] = opts.TenantID
}
if opts.Persistence != nil {
l["session_persistence"] = &opts.Persistence
}
return map[string]interface{}{"pool": l}, nil
return gophercloud.BuildRequestBody(opts, "pool")
}
// Create accepts a CreateOpts struct and uses the values to create a new
// load balancer pool.
func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult {
var res CreateResult
reqBody, err := opts.ToPoolCreateMap()
func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToPoolCreateMap()
if err != nil {
res.Err = err
return res
r.Err = err
return
}
// Send request to API
_, res.Err = c.Post(rootURL(c), reqBody, &res.Body, nil)
return res
_, r.Err = c.Post(rootURL(c), b, &r.Body, nil)
return
}
// Get retrieves a particular pool 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
func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = c.Get(resourceURL(c, id), &r.Body, nil)
return
}
// UpdateOptsBuilder is the interface options structs have to satisfy in order
@ -218,99 +142,56 @@ type UpdateOptsBuilder interface {
// UpdateOpts is the common options struct used in this package's Update
// operation.
type UpdateOpts poolOpts
type UpdateOpts struct {
// Name of the pool.
Name string `json:"name,omitempty"`
// Human-readable description for the pool.
Description string `json:"description,omitempty"`
// The algorithm used to distribute load between the members of the pool. The
// current specification supports LBMethodRoundRobin, LBMethodLeastConnections
// and LBMethodSourceIp as valid values for this attribute.
LBMethod LBMethod `json:"lb_algorithm,omitempty"`
// The administrative state of the Pool. A valid value is true (UP)
// or false (DOWN).
AdminStateUp *bool `json:"admin_state_up,omitempty"`
}
// ToPoolUpdateMap casts a UpdateOpts struct to a map.
func (opts UpdateOpts) ToPoolUpdateMap() (map[string]interface{}, error) {
l := make(map[string]interface{})
allowedLBMethod := map[LBMethod]bool{LBMethodRoundRobin: true, LBMethodLeastConnections: true, LBMethodSourceIp: true}
if opts.LBMethod != "" {
if allowedLBMethod[opts.LBMethod] {
l["lb_algorithm"] = opts.LBMethod
} else {
return nil, errValidLBMethodRequired
}
}
if opts.Name != "" {
l["name"] = opts.Name
}
if opts.Description != "" {
l["description"] = opts.Description
}
if opts.AdminStateUp != nil {
l["admin_state_up"] = &opts.AdminStateUp
}
return map[string]interface{}{"pool": l}, nil
return gophercloud.BuildRequestBody(opts, "pool")
}
// Update allows pools to be updated.
func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult {
var res UpdateResult
reqBody, err := opts.ToPoolUpdateMap()
func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
b, err := opts.ToPoolUpdateMap()
if err != nil {
res.Err = err
return res
r.Err = err
return
}
// Send request to API
_, res.Err = c.Put(resourceURL(c, id), reqBody, &res.Body, &gophercloud.RequestOpts{
_, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return res
return
}
// Delete will permanently delete a particular pool 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
func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = c.Delete(resourceURL(c, id), nil)
return
}
// CreateOpts contains all the values needed to create a new Member for a Pool.
type memberOpts struct {
// Optional. Name of the Member.
Name string
// Only required if the caller has an admin role and wants to create a Member
// for another tenant.
TenantID string
// Required. The IP address of the member to receive traffic from the load balancer.
Address string
// Required. The port on which to listen for client traffic.
ProtocolPort int
// Optional. A positive integer value that indicates the relative portion of
// traffic that this member should receive from the pool. For example, a
// member with a weight of 10 receives five times as much traffic as a member
// with a weight of 2.
Weight int
// Optional. If you omit this parameter, LBaaS uses the vip_subnet_id
// parameter value for the subnet UUID.
SubnetID string
// Optional. The administrative state of the Pool. A valid value is true (UP)
// or false (DOWN).
AdminStateUp *bool
// ListMemberOptsBuilder allows extensions to add additional parameters to the
// ListMembers request.
type ListMembersOptsBuilder interface {
ToMembersListQuery() (string, error)
}
// MemberListOptsBuilder allows extensions to add additional parameters to the
// Member List request.
type MemberListOptsBuilder interface {
ToMemberListQuery() (string, error)
}
// MemberListOpts allows the filtering and sorting of paginated collections through
// ListMembersOpts allows the filtering and sorting of paginated collections through
// the API. Filtering is achieved by passing in struct field values that map to
// the Member attributes you want to see returned. SortKey allows you to
// sort by a particular Member attribute. SortDir sets the direction, and is
// either `asc' or `desc'. Marker and Limit are used for pagination.
type MemberListOpts struct {
type ListMembersOpts struct {
Name string `q:"name"`
Weight int `q:"weight"`
AdminStateUp *bool `q:"admin_state_up"`
@ -325,161 +206,129 @@ type MemberListOpts struct {
}
// ToMemberListQuery formats a ListOpts into a query string.
func (opts MemberListOpts) ToMemberListQuery() (string, error) {
func (opts ListMembersOpts) ToMembersListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
if err != nil {
return "", err
}
return q.String(), nil
return q.String(), err
}
// List returns a Pager which allows you to iterate over a collection of
// members. It accepts a ListOpts struct, which allows you to filter and sort
// ListMembers returns a Pager which allows you to iterate over a collection of
// members. It accepts a ListMembersOptsBuilder, which allows you to filter and sort
// the returned collection for greater efficiency.
//
// Default policy settings return only those members that are owned by the
// tenant who submits the request, unless an admin user submits the request.
func ListAssociateMembers(c *gophercloud.ServiceClient, poolID string, opts MemberListOptsBuilder) pagination.Pager {
func ListMembers(c *gophercloud.ServiceClient, poolID string, opts ListMembersOptsBuilder) pagination.Pager {
url := memberRootURL(c, poolID)
if opts != nil {
query, err := opts.ToMemberListQuery()
query, err := opts.ToMembersListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
}
return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page {
return MemberPage{pagination.LinkedPageBase{PageResult: r}}
})
}
var (
errPoolIdRequired = fmt.Errorf("PoolID is required")
errAddressRequired = fmt.Errorf("Address is required")
errProtocolPortRequired = fmt.Errorf("ProtocolPort is required")
)
// MemberCreateOptsBuilder is the interface options structs have to satisfy in order
// to be used in the main Create operation in this package. Since many
// CreateMemberOptsBuilder is the interface options structs have to satisfy in order
// to be used in the CreateMember operation in this package. Since many
// extensions decorate or modify the common logic, it is useful for them to
// satisfy a basic interface in order for them to be used.
type MemberCreateOptsBuilder interface {
type CreateMemberOptsBuilder interface {
ToMemberCreateMap() (map[string]interface{}, error)
}
// MemberCreateOpts is the common options struct used in this package's Create
// CreateMemberOpts is the common options struct used in this package's CreateMember
// operation.
type MemberCreateOpts memberOpts
type CreateMemberOpts struct {
// Required. The IP address of the member to receive traffic from the load balancer.
Address string `json:"address" required:"true"`
// Required. The port on which to listen for client traffic.
ProtocolPort int `json:"protocol_port" required:"true"`
// Optional. Name of the Member.
Name string `json:"name,omitempty"`
// Only required if the caller has an admin role and wants to create a Member
// for another tenant.
TenantID string `json:"tenant_id,omitempty"`
// Optional. A positive integer value that indicates the relative portion of
// traffic that this member should receive from the pool. For example, a
// member with a weight of 10 receives five times as much traffic as a member
// with a weight of 2.
Weight int `json:"weight,omitempty"`
// Optional. If you omit this parameter, LBaaS uses the vip_subnet_id
// parameter value for the subnet UUID.
SubnetID string `json:"subnet_id,omitempty"`
// Optional. The administrative state of the Pool. A valid value is true (UP)
// or false (DOWN).
AdminStateUp *bool `json:"admin_state_up,omitempty"`
}
// ToMemberCreateMap casts a CreateOpts struct to a map.
func (opts MemberCreateOpts) ToMemberCreateMap() (map[string]interface{}, error) {
l := make(map[string]interface{})
if opts.Address != "" {
l["address"] = opts.Address
} else {
return nil, errAddressRequired
}
if opts.ProtocolPort != 0 {
l["protocol_port"] = opts.ProtocolPort
} else {
return nil, errProtocolPortRequired
}
if opts.AdminStateUp != nil {
l["admin_state_up"] = &opts.AdminStateUp
}
if opts.Name != "" {
l["name"] = opts.Name
}
if opts.TenantID != "" {
l["tenant_id"] = opts.TenantID
}
if opts.SubnetID != "" {
l["subnet_id"] = opts.SubnetID
}
if opts.Weight != 0 {
l["weight"] = opts.Weight
}
return map[string]interface{}{"member": l}, nil
func (opts CreateMemberOpts) ToMemberCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "member")
}
// CreateAssociateMember will create and associate a Member with a particular Pool.
func CreateAssociateMember(c *gophercloud.ServiceClient, poolID string, opts MemberCreateOpts) AssociateResult {
var res AssociateResult
if poolID == "" {
res.Err = errPoolIdRequired
return res
}
reqBody, err := opts.ToMemberCreateMap()
// CreateMember will create and associate a Member with a particular Pool.
func CreateMember(c *gophercloud.ServiceClient, poolID string, opts CreateMemberOpts) (r CreateMemberResult) {
b, err := opts.ToMemberCreateMap()
if err != nil {
res.Err = err
return res
r.Err = err
return
}
_, res.Err = c.Post(memberRootURL(c, poolID), reqBody, &res.Body, nil)
return res
_, r.Err = c.Post(memberRootURL(c, poolID), b, &r.Body, nil)
return
}
// Get retrieves a particular Pool Member based on its unique ID.
func GetAssociateMember(c *gophercloud.ServiceClient, poolID string, memberID string) GetResult {
var res GetResult
_, res.Err = c.Get(memberResourceURL(c, poolID, memberID), &res.Body, nil)
return res
// GetMember retrieves a particular Pool Member based on its unique ID.
func GetMember(c *gophercloud.ServiceClient, poolID string, memberID string) (r GetMemberResult) {
_, r.Err = c.Get(memberResourceURL(c, poolID, memberID), &r.Body, nil)
return
}
// MemberUpdateOptsBuilder is the interface options structs have to satisfy in order
// to be used in the main Update operation in this package. Since many
// extensions decorate or modify the common logic, it is useful for them to
// satisfy a basic interface in order for them to be used.
type MemberUpdateOptsBuilder interface {
type UpdateMemberOptsBuilder interface {
ToMemberUpdateMap() (map[string]interface{}, error)
}
// UpdateOpts is the common options struct used in this package's Update
// UpdateMemberOpts is the common options struct used in this package's Update
// operation.
type MemberUpdateOpts memberOpts
type UpdateMemberOpts struct {
// Optional. Name of the Member.
Name string `json:"name,omitempty"`
// Optional. A positive integer value that indicates the relative portion of
// traffic that this member should receive from the pool. For example, a
// member with a weight of 10 receives five times as much traffic as a member
// with a weight of 2.
Weight int `json:"weight,omitempty"`
// Optional. The administrative state of the Pool. A valid value is true (UP)
// or false (DOWN).
AdminStateUp *bool `json:"admin_state_up,omitempty"`
}
// ToMemberUpdateMap casts a UpdateOpts struct to a map.
func (opts MemberUpdateOpts) ToMemberUpdateMap() (map[string]interface{}, error) {
l := make(map[string]interface{})
if opts.AdminStateUp != nil {
l["admin_state_up"] = &opts.AdminStateUp
}
if opts.Name != "" {
l["name"] = opts.Name
}
if opts.Weight != 0 {
l["weight"] = opts.Weight
}
return map[string]interface{}{"member": l}, nil
func (opts UpdateMemberOpts) ToMemberUpdateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "member")
}
// Update allows Member to be updated.
func UpdateAssociateMember(c *gophercloud.ServiceClient, poolID string, memberID string, opts MemberUpdateOpts) UpdateResult {
var res UpdateResult
reqBody, err := opts.ToMemberUpdateMap()
func UpdateMember(c *gophercloud.ServiceClient, poolID string, memberID string, opts UpdateMemberOptsBuilder) (r UpdateMemberResult) {
b, err := opts.ToMemberUpdateMap()
if err != nil {
res.Err = err
return res
r.Err = err
return
}
// Send request to API
_, res.Err = c.Put(memberResourceURL(c, poolID, memberID), reqBody, &res.Body, &gophercloud.RequestOpts{
_, r.Err = c.Put(memberResourceURL(c, poolID, memberID), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 201, 202},
})
return res
return
}
// DisassociateMember will remove and disassociate a Member from a particular Pool.
func DeleteMember(c *gophercloud.ServiceClient, poolID string, memberID string) DeleteResult {
var res DeleteResult
_, res.Err = c.Delete(memberResourceURL(c, poolID, memberID), nil)
return res
func DeleteMember(c *gophercloud.ServiceClient, poolID string, memberID string) (r DeleteMemberResult) {
_, r.Err = c.Delete(memberResourceURL(c, poolID, memberID), nil)
return
}

View File

@ -1,10 +1,9 @@
package pools
import (
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors"
"github.com/rackspace/gophercloud/pagination"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors"
"github.com/gophercloud/gophercloud/pagination"
)
// SessionPersistence represents the session persistence feature of the load
@ -24,18 +23,18 @@ import (
// same Member of the Pool.
type SessionPersistence struct {
// The type of persistence mode
Type string `mapstructure:"type" json:"type"`
Type string `json:"type"`
// Name of cookie if persistence mode is set appropriately
CookieName string `mapstructure:"cookie_name" json:"cookie_name,omitempty"`
CookieName string `json:"cookie_name,omitempty"`
}
type LoadBalancerID struct {
ID string `mapstructure:"id" json:"id"`
ID string `json:"id"`
}
type ListenerID struct {
ID string `mapstructure:"id" json:"id"`
ID string `json:"id"`
}
// Pool represents a logical set of devices, such as web servers, that you
@ -46,51 +45,37 @@ type Pool struct {
// The load-balancer algorithm, which is round-robin, least-connections, and
// so on. This value, which must be supported, is dependent on the provider.
// Round-robin must be supported.
LBMethod string `json:"lb_algorithm" mapstructure:"lb_algorithm"`
LBMethod string `json:"lb_algorithm"`
// The protocol of the Pool, which is TCP, HTTP, or HTTPS.
Protocol string
Protocol string `json:"protocol"`
// Description for the Pool.
Description string
Description string `json:"description"`
// A list of listeners objects IDs.
Listeners []ListenerID `mapstructure:"listeners" json:"listeners"` //[]map[string]interface{}
Listeners []ListenerID `json:"listeners"` //[]map[string]interface{}
// A list of member objects IDs.
Members []Member `mapstructure:"members" json:"members"`
Members []Member `json:"members"`
// The ID of associated health monitor.
MonitorID string `json:"healthmonitor_id" mapstructure:"healthmonitor_id"`
MonitorID string `json:"healthmonitor_id"`
// The network on which the members of the Pool will be located. Only members
// that are on this network can be added to the Pool.
SubnetID string `json:"subnet_id" mapstructure:"subnet_id"`
SubnetID string `json:"subnet_id"`
// Owner of the Pool. Only an administrative user can specify a tenant ID
// other than its own.
TenantID string `json:"tenant_id" mapstructure:"tenant_id"`
TenantID string `json:"tenant_id"`
// The administrative state of the Pool, which is up (true) or down (false).
AdminStateUp bool `json:"admin_state_up" mapstructure:"admin_state_up"`
AdminStateUp bool `json:"admin_state_up"`
// Pool name. Does not have to be unique.
Name string
Name string `json:"name"`
// The unique ID for the Pool.
ID string
ID string `json:"id"`
// A list of load balancer objects IDs.
Loadbalancers []LoadBalancerID `mapstructure:"loadbalancers" json:"loadbalancers"`
Loadbalancers []LoadBalancerID `json:"loadbalancers"`
// Indicates whether connections in the same session will be processed by the
// same Pool member or not.
Persistence SessionPersistence `mapstructure:"session_persistence" json:"session_persistence"`
Persistence SessionPersistence `json:"session_persistence"`
// The provider
Provider string
Monitor monitors.Monitor `mapstructure:"healthmonitor" json:"healthmonitor"`
Provider string `json:"provider"`
Monitor monitors.Monitor `json:"healthmonitor"`
}
// PoolPage is the page returned by a pager when traversing over a
@ -102,40 +87,32 @@ type PoolPage struct {
// NextPageURL is invoked when a paginated collection of pools 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 PoolPage) NextPageURL() (string, error) {
type resp struct {
Links []gophercloud.Link `mapstructure:"pools_links"`
func (r PoolPage) NextPageURL() (string, error) {
var s struct {
Links []gophercloud.Link `json:"pools_links"`
}
var r resp
err := mapstructure.Decode(p.Body, &r)
err := r.ExtractInto(&s)
if err != nil {
return "", err
}
return gophercloud.ExtractNextURL(r.Links)
return gophercloud.ExtractNextURL(s.Links)
}
// IsEmpty checks whether a PoolPage struct is empty.
func (p PoolPage) IsEmpty() (bool, error) {
is, err := ExtractPools(p)
if err != nil {
return true, nil
}
return len(is) == 0, nil
func (r PoolPage) IsEmpty() (bool, error) {
is, err := ExtractPools(r)
return len(is) == 0, err
}
// ExtractPools accepts a Page struct, specifically a RouterPage struct,
// ExtractPools accepts a Page struct, specifically a PoolPage struct,
// and extracts the elements into a slice of Router structs. In other words,
// a generic collection is mapped into a relevant slice.
func ExtractPools(page pagination.Page) ([]Pool, error) {
var resp struct {
Pools []Pool `mapstructure:"pools" json:"pools"`
func ExtractPools(r pagination.Page) ([]Pool, error) {
var s struct {
Pools []Pool `json:"pools"`
}
err := mapstructure.Decode(page.(PoolPage).Body, &resp)
return resp.Pools, err
err := (r.(PoolPage)).ExtractInto(&s)
return s.Pools, err
}
type commonResult struct {
@ -144,48 +121,54 @@ type commonResult struct {
// Extract is a function that accepts a result and extracts a router.
func (r commonResult) Extract() (*Pool, error) {
if r.Err != nil {
return nil, r.Err
}
var res struct {
var s struct {
Pool *Pool `json:"pool"`
}
err := r.ExtractInto(&s)
return s.Pool, err
}
err := mapstructure.Decode(r.Body, &res)
// CreateResult represents the result of a Create operation.
type CreateResult struct {
commonResult
}
return res.Pool, err
// GetResult represents the result of a Get operation.
type GetResult struct {
commonResult
}
// UpdateResult represents the result of an Update operation.
type UpdateResult struct {
commonResult
}
// DeleteResult represents the result of a Delete operation.
type DeleteResult struct {
gophercloud.ErrResult
}
// Member represents the application running on a backend server.
type Member struct {
// Name of the Member.
Name string `json:"name" mapstructure:"name"`
Name string `json:"name"`
// Weight of Member.
Weight int `json:"weight" mapstructure:"weight"`
Weight int `json:"weight"`
// The administrative state of the member, which is up (true) or down (false).
AdminStateUp bool `json:"admin_state_up" mapstructure:"admin_state_up"`
AdminStateUp bool `json:"admin_state_up"`
// Owner of the Member. Only an administrative user can specify a tenant ID
// other than its own.
TenantID string `json:"tenant_id" mapstructure:"tenant_id"`
TenantID string `json:"tenant_id"`
// parameter value for the subnet UUID.
SubnetID string `json:"subnet_id" mapstructure:"subnet_id"`
SubnetID string `json:"subnet_id"`
// The Pool to which the Member belongs.
PoolID string `json:"pool_id" mapstructure:"pool_id"`
PoolID string `json:"pool_id"`
// The IP address of the Member.
Address string `json:"address" mapstructure:"address"`
Address string `json:"address"`
// The port on which the application is hosted.
ProtocolPort int `json:"protocol_port" mapstructure:"protocol_port"`
ProtocolPort int `json:"protocol_port"`
// The unique ID for the Member.
ID string
ID string `json:"id"`
}
// MemberPage is the page returned by a pager when traversing over a
@ -197,78 +180,63 @@ type MemberPage struct {
// NextPageURL is invoked when a paginated collection of members 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 MemberPage) NextPageURL() (string, error) {
type resp struct {
Links []gophercloud.Link `mapstructure:"members_links"`
func (r MemberPage) NextPageURL() (string, error) {
var s struct {
Links []gophercloud.Link `json:"members_links"`
}
var r resp
err := mapstructure.Decode(p.Body, &r)
err := r.ExtractInto(&s)
if err != nil {
return "", err
}
return gophercloud.ExtractNextURL(r.Links)
return gophercloud.ExtractNextURL(s.Links)
}
// IsEmpty checks whether a MemberPage struct is empty.
func (p MemberPage) IsEmpty() (bool, error) {
is, err := ExtractMembers(p)
if err != nil {
return true, nil
}
return len(is) == 0, nil
func (r MemberPage) IsEmpty() (bool, error) {
is, err := ExtractMembers(r)
return len(is) == 0, err
}
// ExtractMembers accepts a Page struct, specifically a RouterPage struct,
// and extracts the elements into a slice of Router structs. In other words,
// a generic collection is mapped into a relevant slice.
func ExtractMembers(page pagination.Page) ([]Member, error) {
var resp struct {
Member []Member `mapstructure:"members" json:"members"`
func ExtractMembers(r pagination.Page) ([]Member, error) {
var s struct {
Members []Member `json:"members"`
}
err := (r.(MemberPage)).ExtractInto(&s)
return s.Members, err
}
err := mapstructure.Decode(page.(MemberPage).Body, &resp)
return resp.Member, err
type commonMemberResult struct {
gophercloud.Result
}
// ExtractMember is a function that accepts a result and extracts a router.
func (r commonResult) ExtractMember() (*Member, error) {
if r.Err != nil {
return nil, r.Err
}
var res struct {
func (r commonMemberResult) Extract() (*Member, error) {
var s struct {
Member *Member `json:"member"`
}
err := mapstructure.Decode(r.Body, &res)
return res.Member, err
err := r.ExtractInto(&s)
return s.Member, err
}
// CreateResult represents the result of a create operation.
type CreateResult struct {
commonResult
// CreateMemberResult represents the result of a CreateMember operation.
type CreateMemberResult struct {
commonMemberResult
}
// GetResult represents the result of a get operation.
type GetResult struct {
commonResult
// GetMemberResult represents the result of a GetMember operation.
type GetMemberResult struct {
commonMemberResult
}
// UpdateResult represents the result of an update operation.
type UpdateResult struct {
commonResult
// UpdateMemberResult represents the result of an UpdateMember operation.
type UpdateMemberResult struct {
commonMemberResult
}
// DeleteResult represents the result of a delete operation.
type DeleteResult struct {
// DeleteMemberResult represents the result of a DeleteMember operation.
type DeleteMemberResult struct {
gophercloud.ErrResult
}
// AssociateResult represents the result of an association operation.
type AssociateResult struct {
commonResult
}

View File

@ -1,6 +1,6 @@
package pools
import "github.com/rackspace/gophercloud"
import "github.com/gophercloud/gophercloud"
const (
rootPath = "lbaas"

View File

@ -1,10 +1,8 @@
package groups
import (
"fmt"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// ListOpts allows the filtering and sorting of paginated collections through
@ -36,96 +34,75 @@ func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager {
})
}
var (
errNameRequired = fmt.Errorf("Name is required")
)
type CreateOptsBuilder interface {
ToSecGroupCreateMap() (map[string]interface{}, error)
}
// 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
Name string `json:"name" required:"true"`
// Required for admins. Indicates the owner of the VIP.
TenantID string
TenantID string `json:"tenant_id,omitempty"`
// Optional. Describes the security group.
Description string
Description string `json:"description,omitempty"`
}
func (opts CreateOpts) ToSecGroupCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "security_group")
}
// 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
func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToSecGroupCreateMap()
if err != nil {
r.Err = err
return
}
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
_, r.Err = c.Post(rootURL(c), b, &r.Body, nil)
return
}
// 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
func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = c.Get(resourceURL(c, id), &r.Body, nil)
return
}
// 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
func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = c.Delete(resourceURL(c, id), nil)
return
}
// 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)
count := 0
id := ""
pages, err := List(client, ListOpts{}).AllPages()
if err != nil {
return false, err
return "", err
}
for _, s := range securityGroupList {
all, err := ExtractGroups(pages)
if err != nil {
return "", err
}
for _, s := range all {
if s.Name == name {
securityGroupCount++
securityGroupID = s.ID
count++
id = s.ID
}
}
return true, nil
})
switch securityGroupCount {
switch count {
case 0:
return "", fmt.Errorf("Unable to find security group: %s", name)
return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "security group"}
case 1:
return securityGroupID, nil
return id, nil
default:
return "", fmt.Errorf("Found %d security groups matching %s", securityGroupCount, name)
return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "security group"}
}
}

Some files were not shown because too many files have changed in this diff Show More