Commit 79bef05f authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

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.
parents 8bf4a320 10b3b96c
{ {
"ImportPath": "k8s.io/kubernetes", "ImportPath": "k8s.io/kubernetes",
"GoVersion": "go1.7", "GoVersion": "go1.7",
"GodepVersion": "v74", "GodepVersion": "v75",
"Packages": [ "Packages": [
"github.com/ugorji/go/codec/codecgen", "github.com/ugorji/go/codec/codecgen",
"github.com/onsi/ginkgo/ginkgo", "github.com/onsi/ginkgo/ginkgo",
...@@ -1401,6 +1401,118 @@ ...@@ -1401,6 +1401,118 @@
"Rev": "44d81051d367757e1c7c6a5a86423ece9afcf63c" "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", "ImportPath": "github.com/gorilla/context",
"Rev": "215affda49addc4c8ef7e2534915df2c8c35c6cd" "Rev": "215affda49addc4c8ef7e2534915df2c8c35c6cd"
}, },
...@@ -1965,11 +2077,6 @@ ...@@ -1965,11 +2077,6 @@
"Rev": "e00690e87603abe613e9f02c816c7c4bef82e063" "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", "ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/bootfromvolume",
"Comment": "v1.0.0-1012-ge00690e8", "Comment": "v1.0.0-1012-ge00690e8",
"Rev": "e00690e87603abe613e9f02c816c7c4bef82e063" "Rev": "e00690e87603abe613e9f02c816c7c4bef82e063"
...@@ -2010,86 +2117,11 @@ ...@@ -2010,86 +2117,11 @@
"Rev": "e00690e87603abe613e9f02c816c7c4bef82e063" "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", "ImportPath": "github.com/rackspace/gophercloud/openstack/identity/v3/tokens",
"Comment": "v1.0.0-1012-ge00690e8", "Comment": "v1.0.0-1012-ge00690e8",
"Rev": "e00690e87603abe613e9f02c816c7c4bef82e063" "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", "ImportPath": "github.com/rackspace/gophercloud/openstack/utils",
"Comment": "v1.0.0-1012-ge00690e8", "Comment": "v1.0.0-1012-ge00690e8",
"Rev": "e00690e87603abe613e9f02c816c7c4bef82e063" "Rev": "e00690e87603abe613e9f02c816c7c4bef82e063"
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -27,30 +27,30 @@ go_library( ...@@ -27,30 +27,30 @@ go_library(
"//pkg/util/mount:go_default_library", "//pkg/util/mount:go_default_library",
"//pkg/volume:go_default_library", "//pkg/volume:go_default_library",
"//vendor:github.com/golang/glog", "//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/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:gopkg.in/gcfg.v1",
"//vendor:k8s.io/apimachinery/pkg/api/resource", "//vendor:k8s.io/apimachinery/pkg/api/resource",
"//vendor:k8s.io/apimachinery/pkg/types", "//vendor:k8s.io/apimachinery/pkg/types",
...@@ -69,8 +69,8 @@ go_test( ...@@ -69,8 +69,8 @@ go_test(
deps = [ deps = [
"//pkg/api/v1:go_default_library", "//pkg/api/v1:go_default_library",
"//pkg/cloudprovider:go_default_library", "//pkg/cloudprovider:go_default_library",
"//vendor:github.com/rackspace/gophercloud", "//vendor:github.com/gophercloud/gophercloud",
"//vendor:github.com/rackspace/gophercloud/openstack/compute/v2/servers", "//vendor:github.com/gophercloud/gophercloud/openstack/compute/v2/servers",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/types", "//vendor:k8s.io/apimachinery/pkg/types",
"//vendor:k8s.io/apimachinery/pkg/util/rand", "//vendor:k8s.io/apimachinery/pkg/util/rand",
......
...@@ -26,13 +26,13 @@ import ( ...@@ -26,13 +26,13 @@ import (
"strings" "strings"
"time" "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/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" "gopkg.in/gcfg.v1"
"github.com/golang/glog" "github.com/golang/glog"
...@@ -110,7 +110,6 @@ type Config struct { ...@@ -110,7 +110,6 @@ type Config struct {
Username string Username string
UserId string `gcfg:"user-id"` UserId string `gcfg:"user-id"`
Password string Password string
ApiKey string `gcfg:"api-key"`
TenantId string `gcfg:"tenant-id"` TenantId string `gcfg:"tenant-id"`
TenantName string `gcfg:"tenant-name"` TenantName string `gcfg:"tenant-name"`
TrustId string `gcfg:"trust-id"` TrustId string `gcfg:"trust-id"`
...@@ -139,7 +138,6 @@ func (cfg Config) toAuthOptions() gophercloud.AuthOptions { ...@@ -139,7 +138,6 @@ func (cfg Config) toAuthOptions() gophercloud.AuthOptions {
Username: cfg.Global.Username, Username: cfg.Global.Username,
UserID: cfg.Global.UserId, UserID: cfg.Global.UserId,
Password: cfg.Global.Password, Password: cfg.Global.Password,
APIKey: cfg.Global.ApiKey,
TenantID: cfg.Global.TenantId, TenantID: cfg.Global.TenantId,
TenantName: cfg.Global.TenantName, TenantName: cfg.Global.TenantName,
DomainID: cfg.Global.DomainId, DomainID: cfg.Global.DomainId,
...@@ -150,6 +148,18 @@ func (cfg Config) toAuthOptions() gophercloud.AuthOptions { ...@@ -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) { func readConfig(config io.Reader) (Config, error) {
if config == nil { if config == nil {
err := fmt.Errorf("no OpenStack cloud provider config file given") err := fmt.Errorf("no OpenStack cloud provider config file given")
...@@ -205,11 +215,12 @@ func newOpenStack(cfg Config) (*OpenStack, error) { ...@@ -205,11 +215,12 @@ func newOpenStack(cfg Config) (*OpenStack, error) {
return nil, err return nil, err
} }
if cfg.Global.TrustId != "" { if cfg.Global.TrustId != "" {
authOptionsExt := trust.AuthOptionsExt{ opts := cfg.toAuth3Options()
TrustID: cfg.Global.TrustId, authOptsExt := trusts.AuthOptsExt{
AuthOptions: token3.AuthOptions{AuthOptions: cfg.toAuthOptions()}, TrustID: cfg.Global.TrustId,
AuthOptionsBuilder: &opts,
} }
err = trust.AuthenticateV3Trust(provider, authOptionsExt) err = openstack.AuthenticateV3(provider, authOptsExt, gophercloud.EndpointOpts{})
} else { } else {
err = openstack.Authenticate(provider, cfg.toAuthOptions()) err = openstack.Authenticate(provider, cfg.toAuthOptions())
} }
...@@ -446,7 +457,7 @@ func (os *OpenStack) LoadBalancer() (cloudprovider.LoadBalancer, bool) { ...@@ -446,7 +457,7 @@ func (os *OpenStack) LoadBalancer() (cloudprovider.LoadBalancer, bool) {
} }
func isNotFound(err error) bool { func isNotFound(err error) bool {
e, ok := err.(*gophercloud.UnexpectedResponseCodeError) e, ok := err.(*gophercloud.ErrUnexpectedResponseCode)
return ok && e.Actual == http.StatusNotFound return ok && e.Actual == http.StatusNotFound
} }
......
...@@ -20,11 +20,11 @@ import ( ...@@ -20,11 +20,11 @@ import (
"errors" "errors"
"github.com/golang/glog" "github.com/golang/glog"
"github.com/rackspace/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/rackspace/gophercloud/openstack" "github.com/gophercloud/gophercloud/openstack"
"github.com/rackspace/gophercloud/openstack/compute/v2/flavors" "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
"github.com/rackspace/gophercloud/openstack/compute/v2/servers" "github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
"github.com/rackspace/gophercloud/pagination" "github.com/gophercloud/gophercloud/pagination"
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
......
...@@ -19,10 +19,10 @@ package openstack ...@@ -19,10 +19,10 @@ package openstack
import ( import (
"errors" "errors"
"github.com/rackspace/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/rackspace/gophercloud/openstack/compute/v2/servers" "github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/routers" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers"
neutronports "github.com/rackspace/gophercloud/openstack/networking/v2/ports" neutronports "github.com/gophercloud/gophercloud/openstack/networking/v2/ports"
"github.com/golang/glog" "github.com/golang/glog"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
......
...@@ -24,8 +24,8 @@ import ( ...@@ -24,8 +24,8 @@ import (
"testing" "testing"
"time" "time"
"github.com/rackspace/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/rackspace/gophercloud/openstack/compute/v2/servers" "github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/rand" "k8s.io/apimachinery/pkg/util/rand"
...@@ -247,14 +247,13 @@ func configFromEnv() (cfg Config, ok bool) { ...@@ -247,14 +247,13 @@ func configFromEnv() (cfg Config, ok bool) {
cfg.Global.Username = os.Getenv("OS_USERNAME") cfg.Global.Username = os.Getenv("OS_USERNAME")
cfg.Global.Password = os.Getenv("OS_PASSWORD") 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.Region = os.Getenv("OS_REGION_NAME")
cfg.Global.DomainId = os.Getenv("OS_DOMAIN_ID") cfg.Global.DomainId = os.Getenv("OS_DOMAIN_ID")
cfg.Global.DomainName = os.Getenv("OS_DOMAIN_NAME") cfg.Global.DomainName = os.Getenv("OS_DOMAIN_NAME")
ok = (cfg.Global.AuthUrl != "" && ok = (cfg.Global.AuthUrl != "" &&
cfg.Global.Username != "" && cfg.Global.Username != "" &&
(cfg.Global.Password != "" || cfg.Global.ApiKey != "") && cfg.Global.Password != "" &&
(cfg.Global.TenantId != "" || cfg.Global.TenantName != "" || (cfg.Global.TenantId != "" || cfg.Global.TenantName != "" ||
cfg.Global.DomainId != "" || cfg.Global.DomainName != "")) cfg.Global.DomainId != "" || cfg.Global.DomainName != ""))
......
...@@ -25,11 +25,11 @@ import ( ...@@ -25,11 +25,11 @@ import (
"k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume"
"github.com/rackspace/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/rackspace/gophercloud/openstack" "github.com/gophercloud/gophercloud/openstack"
"github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes" "github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach"
"github.com/rackspace/gophercloud/pagination" "github.com/gophercloud/gophercloud/pagination"
"github.com/golang/glog" "github.com/golang/glog"
) )
......
...@@ -23,8 +23,8 @@ import ( ...@@ -23,8 +23,8 @@ import (
"strings" "strings"
"github.com/golang/glog" "github.com/golang/glog"
"github.com/rackspace/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/rackspace/gophercloud/openstack" "github.com/gophercloud/gophercloud/openstack"
netutil "k8s.io/apimachinery/pkg/util/net" netutil "k8s.io/apimachinery/pkg/util/net"
"k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authentication/user"
......
This source diff could not be displayed because it is too large. You can view the blob instead.
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
# 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`
# 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).
## 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`).
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
}
/*
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
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
}
}
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
}
// 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
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"}
}
}
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
}
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)
}
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
})
}
package extensions package extensions
import ( import (
"github.com/rackspace/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/rackspace/gophercloud/pagination" "github.com/gophercloud/gophercloud/pagination"
) )
// Get retrieves information for a specific extension using its alias. // Get retrieves information for a specific extension using its alias.
func Get(c *gophercloud.ServiceClient, alias string) GetResult { func Get(c *gophercloud.ServiceClient, alias string) (r GetResult) {
var res GetResult _, r.Err = c.Get(ExtensionURL(c, alias), &r.Body, nil)
_, res.Err = c.Get(ExtensionURL(c, alias), &res.Body, nil) return
return res
} }
// List returns a Pager which allows you to iterate over the full collection of extensions. // List returns a Pager which allows you to iterate over the full collection of extensions.
......
package extensions package extensions
import ( import (
"github.com/mitchellh/mapstructure" "github.com/gophercloud/gophercloud"
"github.com/rackspace/gophercloud" "github.com/gophercloud/gophercloud/pagination"
"github.com/rackspace/gophercloud/pagination"
) )
// GetResult temporarily stores the result of a Get call. // GetResult temporarily stores the result of a Get call.
...@@ -14,27 +13,21 @@ type GetResult struct { ...@@ -14,27 +13,21 @@ type GetResult struct {
// Extract interprets a GetResult as an Extension. // Extract interprets a GetResult as an Extension.
func (r GetResult) Extract() (*Extension, error) { func (r GetResult) Extract() (*Extension, error) {
if r.Err != nil { var s struct {
return nil, r.Err
}
var res struct {
Extension *Extension `json:"extension"` Extension *Extension `json:"extension"`
} }
err := r.ExtractInto(&s)
err := mapstructure.Decode(r.Body, &res) return s.Extension, err
return res.Extension, err
} }
// Extension is a struct that represents an OpenStack extension. // Extension is a struct that represents an OpenStack extension.
type Extension struct { type Extension struct {
Updated string `json:"updated" mapstructure:"updated"` Updated string `json:"updated"`
Name string `json:"name" mapstructure:"name"` Name string `json:"name"`
Links []interface{} `json:"links" mapstructure:"links"` Links []interface{} `json:"links"`
Namespace string `json:"namespace" mapstructure:"namespace"` Namespace string `json:"namespace"`
Alias string `json:"alias" mapstructure:"alias"` Alias string `json:"alias"`
Description string `json:"description" mapstructure:"description"` Description string `json:"description"`
} }
// ExtensionPage is the page returned by a pager when traversing over a collection of extensions. // ExtensionPage is the page returned by a pager when traversing over a collection of extensions.
...@@ -45,21 +38,16 @@ type ExtensionPage struct { ...@@ -45,21 +38,16 @@ type ExtensionPage struct {
// IsEmpty checks whether an ExtensionPage struct is empty. // IsEmpty checks whether an ExtensionPage struct is empty.
func (r ExtensionPage) IsEmpty() (bool, error) { func (r ExtensionPage) IsEmpty() (bool, error) {
is, err := ExtractExtensions(r) is, err := ExtractExtensions(r)
if err != nil { return len(is) == 0, err
return true, err
}
return len(is) == 0, nil
} }
// ExtractExtensions accepts a Page struct, specifically an ExtensionPage struct, and extracts the // ExtractExtensions accepts a Page struct, specifically an ExtensionPage struct, and extracts the
// elements into a slice of Extension structs. // elements into a slice of Extension structs.
// In other words, a generic collection is mapped into a relevant slice. // In other words, a generic collection is mapped into a relevant slice.
func ExtractExtensions(page pagination.Page) ([]Extension, error) { func ExtractExtensions(r pagination.Page) ([]Extension, error) {
var resp struct { var s struct {
Extensions []Extension `mapstructure:"extensions"` Extensions []Extension `json:"extensions"`
} }
err := (r.(ExtensionPage)).ExtractInto(&s)
err := mapstructure.Decode(page.(ExtensionPage).Body, &resp) return s.Extensions, err
return resp.Extensions, err
} }
package extensions package extensions
import "github.com/rackspace/gophercloud" import "github.com/gophercloud/gophercloud"
// ExtensionURL generates the URL for an extension resource by name. // ExtensionURL generates the URL for an extension resource by name.
func ExtensionURL(c *gophercloud.ServiceClient, name string) string { func ExtensionURL(c *gophercloud.ServiceClient, name string) string {
......
// Package volumeattach provides the ability to attach and detach volumes
// to instances
package volumeattach
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
}
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
}
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)
}
// 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
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
}
}
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
}
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")
}
// 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
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
}
}
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
}
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)
}
// 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
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)
}
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")
}
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
})
}
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
}
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."
}
// 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
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}}
})
}
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
}
package tenants
import "github.com/gophercloud/gophercloud"
func listURL(client *gophercloud.ServiceClient) string {
return client.ServiceURL("tenants")
}
// 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
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
}
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
}
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)
}
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()
}
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"`
}
// 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
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
}
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"`
}
package tokens
import "github.com/gophercloud/gophercloud"
func tokenURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("auth", "tokens")
}
package extensions package extensions
import ( import (
"github.com/rackspace/gophercloud" "github.com/gophercloud/gophercloud"
common "github.com/rackspace/gophercloud/openstack/common/extensions" common "github.com/gophercloud/gophercloud/openstack/common/extensions"
"github.com/rackspace/gophercloud/pagination" "github.com/gophercloud/gophercloud/pagination"
) )
// Extension is a single OpenStack extension. // Extension is a single OpenStack extension.
......
package floatingips package floatingips
import ( import (
"fmt" "github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
) )
// ListOpts allows the filtering and sorting of paginated collections through // ListOpts allows the filtering and sorting of paginated collections through
...@@ -39,20 +37,28 @@ func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager { ...@@ -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 // CreateOpts contains all the values needed to create a new floating IP
// resource. The only required fields are FloatingNetworkID and PortID which // resource. The only required fields are FloatingNetworkID and PortID which
// refer to the external network and internal port respectively. // refer to the external network and internal port respectively.
type CreateOpts struct { type CreateOpts struct {
FloatingNetworkID string FloatingNetworkID string `json:"floating_network_id" required:"true"`
FloatingIP string FloatingIP string `json:"floating_ip_address,omitempty"`
PortID string PortID string `json:"port_id,omitempty"`
FixedIP string FixedIP string `json:"fixed_ip_address,omitempty"`
TenantID string TenantID string `json:"tenant_id,omitempty"`
} }
var ( // ToFloatingIPCreateMap allows CreateOpts to satisfy the CreateOptsBuilder
errFloatingNetworkIDRequired = fmt.Errorf("A NetworkID is required") // 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 // 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 // new floating IP resource. You can create floating IPs on external networks
...@@ -78,45 +84,26 @@ var ( ...@@ -78,45 +84,26 @@ var (
// operation will fail and return a 400 error code. If the PortID and FixedIP // 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 // are already associated with another resource, the operation will fail and
// returns a 409 error code. // returns a 409 error code.
func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult { func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
var res CreateResult b, err := opts.ToFloatingIPCreateMap()
if err != nil {
// Validate r.Err = err
if opts.FloatingNetworkID == "" { return
res.Err = errFloatingNetworkIDRequired
return res
}
// 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"`
} }
_, r.Err = c.Post(rootURL(c), b, &r.Body, nil)
// Populate request body return
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
} }
// Get retrieves a particular floating IP resource based on its unique ID. // Get retrieves a particular floating IP resource based on its unique ID.
func Get(c *gophercloud.ServiceClient, id string) GetResult { func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
var res GetResult _, r.Err = c.Get(resourceURL(c, id), &r.Body, nil)
_, res.Err = c.Get(resourceURL(c, id), &res.Body, nil) return
return res }
// 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 // UpdateOpts contains the values used when updating a floating IP resource. The
...@@ -124,45 +111,35 @@ func Get(c *gophercloud.ServiceClient, id string) GetResult { ...@@ -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 // 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. // ID. To disassociate the floating IP from all ports, provide an empty string.
type UpdateOpts struct { 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 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 // "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 // disassociated it from all ports. See UpdateOpts for instructions of how to
// do this. // do this.
func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResult { func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
type floatingIP struct { b, err := opts.ToFloatingIPUpdateMap()
PortID *string `json:"port_id"` 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
} }
_, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
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{
OkCodes: []int{200}, OkCodes: []int{200},
}) })
return
return res
} }
// Delete will permanently delete a particular floating IP resource. Please // Delete will permanently delete a particular floating IP resource. Please
// ensure this is what you want - you can also disassociate the IP from existing // ensure this is what you want - you can also disassociate the IP from existing
// internal ports. // internal ports.
func Delete(c *gophercloud.ServiceClient, id string) DeleteResult { func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
var res DeleteResult _, r.Err = c.Delete(resourceURL(c, id), nil)
_, res.Err = c.Delete(resourceURL(c, id), nil) return
return res
} }
package floatingips package floatingips
import ( import (
"fmt" "github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
) )
// FloatingIP represents a floating IP resource. A floating IP is an external // FloatingIP represents a floating IP resource. A floating IP is an external
...@@ -16,27 +13,27 @@ import ( ...@@ -16,27 +13,27 @@ import (
// attribute (provided by the external network extension) is set to True. // attribute (provided by the external network extension) is set to True.
type FloatingIP struct { type FloatingIP struct {
// Unique identifier for the floating IP instance. // 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. // 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. // 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. // 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 // The specific IP address of the internal port which should be associated
// with the floating IP. // 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 // Owner of the floating IP. Only admin users can specify a tenant identifier
// other than its own. // other than its own.
TenantID string `json:"tenant_id" mapstructure:"tenant_id"` TenantID string `json:"tenant_id"`
// The condition of the API resource. // The condition of the API resource.
Status string `json:"status" mapstructure:"status"` Status string `json:"status"`
} }
type commonResult struct { type commonResult struct {
...@@ -45,20 +42,11 @@ type commonResult struct { ...@@ -45,20 +42,11 @@ type commonResult struct {
// Extract a result and extracts a FloatingIP resource. // Extract a result and extracts a FloatingIP resource.
func (r commonResult) Extract() (*FloatingIP, error) { func (r commonResult) Extract() (*FloatingIP, error) {
if r.Err != nil { var s struct {
return nil, r.Err
}
var res struct {
FloatingIP *FloatingIP `json:"floatingip"` FloatingIP *FloatingIP `json:"floatingip"`
} }
err := r.ExtractInto(&s)
err := mapstructure.Decode(r.Body, &res) return s.FloatingIP, err
if err != nil {
return nil, fmt.Errorf("Error decoding Neutron floating IP: %v", err)
}
return res.FloatingIP, nil
} }
// CreateResult represents the result of a create operation. // CreateResult represents the result of a create operation.
...@@ -90,38 +78,30 @@ type FloatingIPPage struct { ...@@ -90,38 +78,30 @@ type FloatingIPPage struct {
// NextPageURL is invoked when a paginated collection of floating IPs has reached // 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 // 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. // to do this, it needs to construct the next page's URL.
func (p FloatingIPPage) NextPageURL() (string, error) { func (r FloatingIPPage) NextPageURL() (string, error) {
type resp struct { var s struct {
Links []gophercloud.Link `mapstructure:"floatingips_links"` Links []gophercloud.Link `json:"floatingips_links"`
} }
err := r.ExtractInto(&s)
var r resp
err := mapstructure.Decode(p.Body, &r)
if err != nil { if err != nil {
return "", err return "", err
} }
return gophercloud.ExtractNextURL(s.Links)
return gophercloud.ExtractNextURL(r.Links)
} }
// IsEmpty checks whether a NetworkPage struct is empty. // IsEmpty checks whether a NetworkPage struct is empty.
func (p FloatingIPPage) IsEmpty() (bool, error) { func (r FloatingIPPage) IsEmpty() (bool, error) {
is, err := ExtractFloatingIPs(p) is, err := ExtractFloatingIPs(r)
if err != nil { return len(is) == 0, err
return true, nil
}
return len(is) == 0, nil
} }
// ExtractFloatingIPs accepts a Page struct, specifically a FloatingIPPage struct, // ExtractFloatingIPs accepts a Page struct, specifically a FloatingIPPage struct,
// and extracts the elements into a slice of FloatingIP structs. In other words, // and extracts the elements into a slice of FloatingIP structs. In other words,
// a generic collection is mapped into a relevant slice. // a generic collection is mapped into a relevant slice.
func ExtractFloatingIPs(page pagination.Page) ([]FloatingIP, error) { func ExtractFloatingIPs(r pagination.Page) ([]FloatingIP, error) {
var resp struct { var s struct {
FloatingIPs []FloatingIP `mapstructure:"floatingips" json:"floatingips"` FloatingIPs []FloatingIP `json:"floatingips"`
} }
err := (r.(FloatingIPPage)).ExtractInto(&s)
err := mapstructure.Decode(page.(FloatingIPPage).Body, &resp) return s.FloatingIPs, err
return resp.FloatingIPs, err
} }
package floatingips package floatingips
import "github.com/rackspace/gophercloud" import "github.com/gophercloud/gophercloud"
const resourcePath = "floatingips" const resourcePath = "floatingips"
......
package routers package routers
import ( import (
"errors" "github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
) )
// ListOpts allows the filtering and sorting of paginated collections through // ListOpts allows the filtering and sorting of paginated collections through
...@@ -53,38 +51,15 @@ type CreateOptsBuilder interface { ...@@ -53,38 +51,15 @@ type CreateOptsBuilder interface {
// CreateOpts contains all the values needed to create a new router. There are // CreateOpts contains all the values needed to create a new router. There are
// no required values. // no required values.
type CreateOpts struct { type CreateOpts struct {
Name string Name string `json:"name,omitempty"`
AdminStateUp *bool AdminStateUp *bool `json:"admin_state_up,omitempty"`
Distributed *bool Distributed *bool `json:"distributed,omitempty"`
TenantID string TenantID string `json:"tenant_id,omitempty"`
GatewayInfo *GatewayInfo GatewayInfo *GatewayInfo `json:"external_gateway_info,omitempty"`
} }
// ToRouterCreateMap casts a CreateOpts struct to a map.
func (opts CreateOpts) ToRouterCreateMap() (map[string]interface{}, error) { func (opts CreateOpts) ToRouterCreateMap() (map[string]interface{}, error) {
r := make(map[string]interface{}) return gophercloud.BuildRequestBody(opts, "router")
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
} }
// Create accepts a CreateOpts struct and uses the values to create a new // 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) { ...@@ -95,33 +70,37 @@ func (opts CreateOpts) ToRouterCreateMap() (map[string]interface{}, error) {
// GatewayInfo struct. The external gateway for the router must be plugged into // 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 // an external network (it is external if its `router:external' field is set to
// true). // true).
func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult { func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
var res CreateResult b, err := opts.ToRouterCreateMap()
reqBody, err := opts.ToRouterCreateMap()
if err != nil { if err != nil {
res.Err = err r.Err = err
return res return
} }
_, r.Err = c.Post(rootURL(c), b, &r.Body, nil)
_, res.Err = c.Post(rootURL(c), reqBody, &res.Body, nil) return
return res
} }
// Get retrieves a particular router based on its unique ID. // Get retrieves a particular router based on its unique ID.
func Get(c *gophercloud.ServiceClient, id string) GetResult { func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
var res GetResult _, r.Err = c.Get(resourceURL(c, id), &r.Body, nil)
_, res.Err = c.Get(resourceURL(c, id), &res.Body, nil) return
return res }
type UpdateOptsBuilder interface {
ToRouterUpdateMap() (map[string]interface{}, error)
} }
// UpdateOpts contains the values used when updating a router. // UpdateOpts contains the values used when updating a router.
type UpdateOpts struct { type UpdateOpts struct {
Name string Name string `json:"name,omitempty"`
AdminStateUp *bool AdminStateUp *bool `json:"admin_state_up,omitempty"`
Distributed *bool Distributed *bool `json:"distributed,omitempty"`
GatewayInfo *GatewayInfo GatewayInfo *GatewayInfo `json:"external_gateway_info,omitempty"`
Routes []Route 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 // Update allows routers to be updated. You can update the name, administrative
...@@ -129,56 +108,41 @@ type UpdateOpts struct { ...@@ -129,56 +108,41 @@ type UpdateOpts struct {
// external gateway for a router, see Create. This operation does not enable // external gateway for a router, see Create. This operation does not enable
// the update of router interfaces. To do this, use the AddInterface and // the update of router interfaces. To do this, use the AddInterface and
// RemoveInterface functions. // RemoveInterface functions.
func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResult { func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
type router struct { b, err := opts.ToRouterUpdateMap()
Name *string `json:"name,omitempty"` if err != nil {
AdminStateUp *bool `json:"admin_state_up,omitempty"` r.Err = err
Distributed *bool `json:"distributed,omitempty"` return
GatewayInfo *GatewayInfo `json:"external_gateway_info,omitempty"`
Routes []Route `json:"routes"`
}
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
} }
_, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
// Send request to API
var res UpdateResult
_, res.Err = c.Put(resourceURL(c, id), reqBody, &res.Body, &gophercloud.RequestOpts{
OkCodes: []int{200}, OkCodes: []int{200},
}) })
return
return res
} }
// Delete will permanently delete a particular router based on its unique ID. // Delete will permanently delete a particular router based on its unique ID.
func Delete(c *gophercloud.ServiceClient, id string) DeleteResult { func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
var res DeleteResult _, r.Err = c.Delete(resourceURL(c, id), nil)
_, res.Err = c.Delete(resourceURL(c, id), nil) return
return res
} }
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. // an internal interface from a router.
type InterfaceOpts struct { type AddInterfaceOpts struct {
SubnetID string SubnetID string `json:"subnet_id,omitempty" xor:"PortID"`
PortID string 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 // AddInterface attaches a subnet to an internal router interface. You must
...@@ -202,27 +166,35 @@ type InterfaceOpts struct { ...@@ -202,27 +166,35 @@ type InterfaceOpts struct {
// identifier of a new port created by this operation. After the operation // 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 // completes, the device ID of the port is set to the router ID, and the
// device owner attribute is set to `network:router_interface'. // device owner attribute is set to `network:router_interface'.
func AddInterface(c *gophercloud.ServiceClient, id string, opts InterfaceOpts) InterfaceResult { func AddInterface(c *gophercloud.ServiceClient, id string, opts AddInterfaceOptsBuilder) (r InterfaceResult) {
var res InterfaceResult b, err := opts.ToRouterAddInterfaceMap()
if err != nil {
// Validate r.Err = err
if (opts.SubnetID == "" && opts.PortID == "") || (opts.SubnetID != "" && opts.PortID != "") { return
res.Err = errInvalidInterfaceOpts
return res
}
type request struct {
SubnetID string `json:"subnet_id,omitempty"`
PortID string `json:"port_id,omitempty"`
} }
_, r.Err = c.Put(addInterfaceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
body := request{SubnetID: opts.SubnetID, PortID: opts.PortID}
_, res.Err = c.Put(addInterfaceURL(c, id), body, &res.Body, &gophercloud.RequestOpts{
OkCodes: []int{200}, OkCodes: []int{200},
}) })
return
}
// 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"`
}
return res // 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 // RemoveInterface removes an internal router interface, which detaches a
...@@ -238,19 +210,14 @@ func AddInterface(c *gophercloud.ServiceClient, id string, opts InterfaceOpts) I ...@@ -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 // 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 // returned. After this operation completes, the port connecting the router
// with the subnet is removed from the subnet for the network. // with the subnet is removed from the subnet for the network.
func RemoveInterface(c *gophercloud.ServiceClient, id string, opts InterfaceOpts) InterfaceResult { func RemoveInterface(c *gophercloud.ServiceClient, id string, opts RemoveInterfaceOptsBuilder) (r InterfaceResult) {
var res InterfaceResult b, err := opts.ToRouterRemoveInterfaceMap()
if err != nil {
type request struct { r.Err = err
SubnetID string `json:"subnet_id,omitempty"` return
PortID string `json:"port_id,omitempty"`
} }
_, r.Err = c.Put(removeInterfaceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
body := request{SubnetID: opts.SubnetID, PortID: opts.PortID}
_, res.Err = c.Put(removeInterfaceURL(c, id), body, &res.Body, &gophercloud.RequestOpts{
OkCodes: []int{200}, OkCodes: []int{200},
}) })
return
return res
} }
package routers package routers
import ( import (
"github.com/mitchellh/mapstructure" "github.com/gophercloud/gophercloud"
"github.com/rackspace/gophercloud" "github.com/gophercloud/gophercloud/pagination"
"github.com/rackspace/gophercloud/pagination"
) )
// GatewayInfo represents the information of an external gateway for any // GatewayInfo represents the information of an external gateway for any
// particular network router. // particular network router.
type GatewayInfo struct { 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 { type Route struct {
NextHop string `mapstructure:"nexthop" json:"nexthop"` NextHop string `json:"nexthop"`
DestinationCIDR string `mapstructure:"destination" json:"destination"` DestinationCIDR string `json:"destination"`
} }
// Router represents a Neutron router. A router is a logical entity that // Router represents a Neutron router. A router is a logical entity that
...@@ -27,28 +27,28 @@ type Route struct { ...@@ -27,28 +27,28 @@ type Route struct {
// interface is added to the subnet's network. // interface is added to the subnet's network.
type Router struct { type Router struct {
// Indicates whether or not a router is currently operational. // 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. // 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. // 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.. // 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. // 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. // 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 // Owner of the router. Only admin users can specify a tenant identifier
// other than its own. // 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 // RouterPage is the page returned by a pager when traversing over a
...@@ -60,40 +60,32 @@ type RouterPage struct { ...@@ -60,40 +60,32 @@ type RouterPage struct {
// NextPageURL is invoked when a paginated collection of routers has reached // 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 // 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. // to do this, it needs to construct the next page's URL.
func (p RouterPage) NextPageURL() (string, error) { func (r RouterPage) NextPageURL() (string, error) {
type resp struct { var s struct {
Links []gophercloud.Link `mapstructure:"routers_links"` Links []gophercloud.Link `json:"routers_links"`
} }
err := r.ExtractInto(&s)
var r resp
err := mapstructure.Decode(p.Body, &r)
if err != nil { if err != nil {
return "", err return "", err
} }
return gophercloud.ExtractNextURL(s.Links)
return gophercloud.ExtractNextURL(r.Links)
} }
// IsEmpty checks whether a RouterPage struct is empty. // IsEmpty checks whether a RouterPage struct is empty.
func (p RouterPage) IsEmpty() (bool, error) { func (r RouterPage) IsEmpty() (bool, error) {
is, err := ExtractRouters(p) is, err := ExtractRouters(r)
if err != nil { return len(is) == 0, err
return true, nil
}
return len(is) == 0, nil
} }
// ExtractRouters accepts a Page struct, specifically a RouterPage struct, // ExtractRouters accepts a Page struct, specifically a RouterPage struct,
// and extracts the elements into a slice of Router structs. In other words, // and extracts the elements into a slice of Router structs. In other words,
// a generic collection is mapped into a relevant slice. // a generic collection is mapped into a relevant slice.
func ExtractRouters(page pagination.Page) ([]Router, error) { func ExtractRouters(r pagination.Page) ([]Router, error) {
var resp struct { var s struct {
Routers []Router `mapstructure:"routers" json:"routers"` Routers []Router `json:"routers"`
} }
err := (r.(RouterPage)).ExtractInto(&s)
err := mapstructure.Decode(page.(RouterPage).Body, &resp) return s.Routers, err
return resp.Routers, err
} }
type commonResult struct { type commonResult struct {
...@@ -102,17 +94,11 @@ type commonResult struct { ...@@ -102,17 +94,11 @@ type commonResult struct {
// Extract is a function that accepts a result and extracts a router. // Extract is a function that accepts a result and extracts a router.
func (r commonResult) Extract() (*Router, error) { func (r commonResult) Extract() (*Router, error) {
if r.Err != nil { var s struct {
return nil, r.Err
}
var res struct {
Router *Router `json:"router"` Router *Router `json:"router"`
} }
err := r.ExtractInto(&s)
err := mapstructure.Decode(r.Body, &res) return s.Router, err
return res.Router, err
} }
// CreateResult represents the result of a create operation. // CreateResult represents the result of a create operation.
...@@ -140,16 +126,16 @@ type DeleteResult struct { ...@@ -140,16 +126,16 @@ type DeleteResult struct {
// interface. // interface.
type InterfaceInfo struct { type InterfaceInfo struct {
// The ID of the subnet which this interface is associated with. // 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. // 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. // The UUID of the interface.
ID string `json:"id" mapstructure:"id"` ID string `json:"id"`
// Owner of the interface. // 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 // InterfaceResult represents the result of interface operations, such as
...@@ -160,12 +146,7 @@ type InterfaceResult struct { ...@@ -160,12 +146,7 @@ type InterfaceResult struct {
// Extract is a function that accepts a result and extracts an information struct. // Extract is a function that accepts a result and extracts an information struct.
func (r InterfaceResult) Extract() (*InterfaceInfo, error) { func (r InterfaceResult) Extract() (*InterfaceInfo, error) {
if r.Err != nil { var s InterfaceInfo
return nil, r.Err err := r.ExtractInto(&s)
} return &s, err
var res *InterfaceInfo
err := mapstructure.Decode(r.Body, &res)
return res, err
} }
package routers package routers
import "github.com/rackspace/gophercloud" import "github.com/gophercloud/gophercloud"
const resourcePath = "routers" const resourcePath = "routers"
......
package members package members
import ( import (
"github.com/rackspace/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/rackspace/gophercloud/pagination" "github.com/gophercloud/gophercloud/pagination"
) )
// ListOpts allows the filtering and sorting of paginated collections through // ListOpts allows the filtering and sorting of paginated collections through
...@@ -42,82 +42,74 @@ func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager { ...@@ -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. // CreateOpts contains all the values needed to create a new pool member.
type CreateOpts struct { 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 // Only required if the caller has an admin role and wants to create a pool
// for another tenant. // 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. func (opts CreateOpts) ToLBMemberCreateMap() (map[string]interface{}, error) {
PoolID string return gophercloud.BuildRequestBody(opts, "member")
} }
// Create accepts a CreateOpts struct and uses the values to create a new // Create accepts a CreateOpts struct and uses the values to create a new
// load balancer pool member. // load balancer pool member.
func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult { func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
type member struct { b, err := opts.ToLBMemberCreateMap()
TenantID string `json:"tenant_id,omitempty"` if err != nil {
ProtocolPort int `json:"protocol_port"` r.Err = err
Address string `json:"address"` return
PoolID string `json:"pool_id"`
}
type request struct {
Member member `json:"member"`
} }
_, r.Err = c.Post(rootURL(c), b, &r.Body, nil)
reqBody := request{Member: member{ return
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
} }
// Get retrieves a particular pool member based on its unique ID. // Get retrieves a particular pool member based on its unique ID.
func Get(c *gophercloud.ServiceClient, id string) GetResult { func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
var res GetResult _, r.Err = c.Get(resourceURL(c, id), &r.Body, nil)
_, res.Err = c.Get(resourceURL(c, id), &res.Body, nil) return
return res }
type UpdateOptsBuilder interface {
ToLBMemberUpdateMap() (map[string]interface{}, error)
} }
// UpdateOpts contains the values used when updating a pool member. // UpdateOpts contains the values used when updating a pool member.
type UpdateOpts struct { type UpdateOpts struct {
// The administrative state of the member, which is up (true) or down (false). // 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. // Update allows members to be updated.
func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResult { func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
type member struct { b, err := opts.ToLBMemberUpdateMap()
AdminStateUp bool `json:"admin_state_up"` if err != nil {
} r.Err = err
type request struct { return
Member member `json:"member"`
} }
_, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
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{
OkCodes: []int{200, 201, 202}, OkCodes: []int{200, 201, 202},
}) })
return res return
} }
// Delete will permanently delete a particular member based on its unique ID. // Delete will permanently delete a particular member based on its unique ID.
func Delete(c *gophercloud.ServiceClient, id string) DeleteResult { func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
var res DeleteResult _, r.Err = c.Delete(resourceURL(c, id), nil)
_, res.Err = c.Delete(resourceURL(c, id), nil) return
return res
} }
package members package members
import ( import (
"github.com/mitchellh/mapstructure" "github.com/gophercloud/gophercloud"
"github.com/rackspace/gophercloud" "github.com/gophercloud/gophercloud/pagination"
"github.com/rackspace/gophercloud/pagination"
) )
// Member represents the application running on a backend server. // Member represents the application running on a backend server.
...@@ -15,20 +14,20 @@ type Member struct { ...@@ -15,20 +14,20 @@ type Member struct {
Weight int Weight int
// The administrative state of the member, which is up (true) or down (false). // 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 // Owner of the member. Only an administrative user can specify a tenant ID
// other than its own. // other than its own.
TenantID string `json:"tenant_id" mapstructure:"tenant_id"` TenantID string `json:"tenant_id"`
// The pool to which the member belongs. // 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. // The IP address of the member.
Address string Address string
// The port on which the application is hosted. // 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. // The unique ID for the member.
ID string ID string
...@@ -43,43 +42,32 @@ type MemberPage struct { ...@@ -43,43 +42,32 @@ type MemberPage struct {
// NextPageURL is invoked when a paginated collection of members has reached // 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 // 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. // to do this, it needs to construct the next page's URL.
func (p MemberPage) NextPageURL() (string, error) { func (r MemberPage) NextPageURL() (string, error) {
type resp struct { var s struct {
Links []gophercloud.Link `mapstructure:"members_links"` Links []gophercloud.Link `json:"members_links"`
} }
err := r.ExtractInto(&s)
var r resp
err := mapstructure.Decode(p.Body, &r)
if err != nil { if err != nil {
return "", err return "", err
} }
return gophercloud.ExtractNextURL(s.Links)
return gophercloud.ExtractNextURL(r.Links)
} }
// IsEmpty checks whether a MemberPage struct is empty. // IsEmpty checks whether a MemberPage struct is empty.
func (p MemberPage) IsEmpty() (bool, error) { func (r MemberPage) IsEmpty() (bool, error) {
is, err := ExtractMembers(p) is, err := ExtractMembers(r)
if err != nil { return len(is) == 0, err
return true, nil
}
return len(is) == 0, nil
} }
// ExtractMembers accepts a Page struct, specifically a MemberPage struct, // ExtractMembers accepts a Page struct, specifically a MemberPage struct,
// and extracts the elements into a slice of Member structs. In other words, // and extracts the elements into a slice of Member structs. In other words,
// a generic collection is mapped into a relevant slice. // a generic collection is mapped into a relevant slice.
func ExtractMembers(page pagination.Page) ([]Member, error) { func ExtractMembers(r pagination.Page) ([]Member, error) {
var resp struct { var s struct {
Members []Member `mapstructure:"members" json:"members"` Members []Member `json:"members"`
}
err := mapstructure.Decode(page.(MemberPage).Body, &resp)
if err != nil {
return nil, err
} }
err := (r.(MemberPage)).ExtractInto(&s)
return resp.Members, nil return s.Members, err
} }
type commonResult struct { type commonResult struct {
...@@ -88,17 +76,11 @@ type commonResult struct { ...@@ -88,17 +76,11 @@ type commonResult struct {
// Extract is a function that accepts a result and extracts a router. // Extract is a function that accepts a result and extracts a router.
func (r commonResult) Extract() (*Member, error) { func (r commonResult) Extract() (*Member, error) {
if r.Err != nil { var s struct {
return nil, r.Err
}
var res struct {
Member *Member `json:"member"` Member *Member `json:"member"`
} }
err := r.ExtractInto(&s)
err := mapstructure.Decode(r.Body, &res) return s.Member, err
return res.Member, err
} }
// CreateResult represents the result of a create operation. // CreateResult represents the result of a create operation.
......
package members package members
import "github.com/rackspace/gophercloud" import "github.com/gophercloud/gophercloud"
const ( const (
rootPath = "lb" rootPath = "lb"
......
package monitors package monitors
import ( import (
"github.com/mitchellh/mapstructure" "github.com/gophercloud/gophercloud"
"github.com/rackspace/gophercloud" "github.com/gophercloud/gophercloud/pagination"
"github.com/rackspace/gophercloud/pagination"
) )
// Monitor represents a load balancer health monitor. A health monitor is used // Monitor represents a load balancer health monitor. A health monitor is used
...@@ -30,7 +29,7 @@ type Monitor struct { ...@@ -30,7 +29,7 @@ type Monitor struct {
// Owner of the VIP. Only an administrative user can specify a tenant ID // Owner of the VIP. Only an administrative user can specify a tenant ID
// other than its own. // 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, // The type of probe sent by the load balancer to verify the member state,
// which is PING, TCP, HTTP, or HTTPS. // which is PING, TCP, HTTP, or HTTPS.
...@@ -45,20 +44,20 @@ type Monitor struct { ...@@ -45,20 +44,20 @@ type Monitor struct {
// Number of allowed connection failures before changing the status of the // Number of allowed connection failures before changing the status of the
// member to INACTIVE. A valid value is from 1 to 10. // 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. // 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 // 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 (/). // 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. // 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). // 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 // The status of the health monitor. Indicates whether the health monitor is
// operational. // operational.
...@@ -74,40 +73,33 @@ type MonitorPage struct { ...@@ -74,40 +73,33 @@ type MonitorPage struct {
// NextPageURL is invoked when a paginated collection of monitors has reached // 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 // 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. // to do this, it needs to construct the next page's URL.
func (p MonitorPage) NextPageURL() (string, error) { func (r MonitorPage) NextPageURL() (string, error) {
type resp struct { var s struct {
Links []gophercloud.Link `mapstructure:"health_monitors_links"` Links []gophercloud.Link `json:"health_monitors_links"`
} }
err := r.ExtractInto(&s)
var r resp
err := mapstructure.Decode(p.Body, &r)
if err != nil { if err != nil {
return "", err return "", err
} }
return gophercloud.ExtractNextURL(r.Links) return gophercloud.ExtractNextURL(s.Links)
} }
// IsEmpty checks whether a PoolPage struct is empty. // IsEmpty checks whether a PoolPage struct is empty.
func (p MonitorPage) IsEmpty() (bool, error) { func (r MonitorPage) IsEmpty() (bool, error) {
is, err := ExtractMonitors(p) is, err := ExtractMonitors(r)
if err != nil { return len(is) == 0, err
return true, nil
}
return len(is) == 0, nil
} }
// ExtractMonitors accepts a Page struct, specifically a MonitorPage struct, // ExtractMonitors accepts a Page struct, specifically a MonitorPage struct,
// and extracts the elements into a slice of Monitor structs. In other words, // and extracts the elements into a slice of Monitor structs. In other words,
// a generic collection is mapped into a relevant slice. // a generic collection is mapped into a relevant slice.
func ExtractMonitors(page pagination.Page) ([]Monitor, error) { func ExtractMonitors(r pagination.Page) ([]Monitor, error) {
var resp struct { var s struct {
Monitors []Monitor `mapstructure:"health_monitors" json:"health_monitors"` Monitors []Monitor `json:"health_monitors"`
} }
err := (r.(MonitorPage)).ExtractInto(&s)
err := mapstructure.Decode(page.(MonitorPage).Body, &resp) return s.Monitors, err
return resp.Monitors, err
} }
type commonResult struct { type commonResult struct {
...@@ -116,17 +108,11 @@ type commonResult struct { ...@@ -116,17 +108,11 @@ type commonResult struct {
// Extract is a function that accepts a result and extracts a monitor. // Extract is a function that accepts a result and extracts a monitor.
func (r commonResult) Extract() (*Monitor, error) { func (r commonResult) Extract() (*Monitor, error) {
if r.Err != nil { var s struct {
return nil, r.Err Monitor *Monitor `json:"health_monitor"`
} }
err := r.ExtractInto(&s)
var res struct { return s.Monitor, err
Monitor *Monitor `json:"health_monitor" mapstructure:"health_monitor"`
}
err := mapstructure.Decode(r.Body, &res)
return res.Monitor, err
} }
// CreateResult represents the result of a create operation. // CreateResult represents the result of a create operation.
......
package monitors package monitors
import "github.com/rackspace/gophercloud" import "github.com/gophercloud/gophercloud"
const ( const (
rootPath = "lb" rootPath = "lb"
......
package pools package pools
import ( import (
"github.com/rackspace/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/rackspace/gophercloud/pagination" "github.com/gophercloud/gophercloud/pagination"
) )
// ListOpts allows the filtering and sorting of paginated collections through // ListOpts allows the filtering and sorting of paginated collections through
...@@ -43,117 +43,111 @@ func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager { ...@@ -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. // Supported attributes for create/update operations.
const ( const (
LBMethodRoundRobin = "ROUND_ROBIN" LBMethodRoundRobin LBMethod = "ROUND_ROBIN"
LBMethodLeastConnections = "LEAST_CONNECTIONS" LBMethodLeastConnections LBMethod = "LEAST_CONNECTIONS"
ProtocolTCP = "TCP" ProtocolTCP LBProtocol = "TCP"
ProtocolHTTP = "HTTP" ProtocolHTTP LBProtocol = "HTTP"
ProtocolHTTPS = "HTTPS" 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. // CreateOpts contains all the values needed to create a new pool.
type CreateOpts struct { 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 // Only required if the caller has an admin role and wants to create a pool
// for another tenant. // for another tenant.
TenantID string TenantID string `json:"tenant_id,omitempty"`
// 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
// The network on which the members of the pool will be located. Only members // 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. // 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 // The algorithm used to distribute load between the members of the pool. The
// current specification supports LBMethodRoundRobin and // current specification supports LBMethodRoundRobin and
// LBMethodLeastConnections as valid values for this attribute. // LBMethodLeastConnections as valid values for this attribute.
LBMethod string LBMethod LBMethod `json:"lb_method" required:"true"`
// The provider of the pool // 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. // load balancer pool.
func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult { func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
type pool struct { b, err := opts.ToLBPoolCreateMap()
Name string `json:"name"` if err != nil {
TenantID string `json:"tenant_id,omitempty"` r.Err = err
Protocol string `json:"protocol"` return
SubnetID string `json:"subnet_id"`
LBMethod string `json:"lb_method"`
Provider string `json:"provider,omitempty"`
}
type request struct {
Pool pool `json:"pool"`
} }
_, r.Err = c.Post(rootURL(c), b, &r.Body, nil)
reqBody := request{Pool: pool{ return
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
} }
// Get retrieves a particular pool based on its unique ID. // Get retrieves a particular pool based on its unique ID.
func Get(c *gophercloud.ServiceClient, id string) GetResult { func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
var res GetResult _, r.Err = c.Get(resourceURL(c, id), &r.Body, nil)
_, res.Err = c.Get(resourceURL(c, id), &res.Body, nil) return
return res }
// 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. // UpdateOpts contains the values used when updating a pool.
type UpdateOpts struct { type UpdateOpts struct {
// Required. Name of the pool. // Name of the pool.
Name string Name string `json:"name,omitempty"`
// The algorithm used to distribute load between the members of the pool. The // The algorithm used to distribute load between the members of the pool. The
// current specification supports LBMethodRoundRobin and // current specification supports LBMethodRoundRobin and
// LBMethodLeastConnections as valid values for this attribute. // 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. // Update allows pools to be updated.
func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResult { func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
type pool struct { b, err := opts.ToLBPoolUpdateMap()
Name string `json:"name,"` if err != nil {
LBMethod string `json:"lb_method"` r.Err = err
} return
type request struct {
Pool pool `json:"pool"`
} }
_, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
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{
OkCodes: []int{200}, OkCodes: []int{200},
}) })
return res return
} }
// Delete will permanently delete a particular pool based on its unique ID. // Delete will permanently delete a particular pool based on its unique ID.
func Delete(c *gophercloud.ServiceClient, id string) DeleteResult { func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
var res DeleteResult _, r.Err = c.Delete(resourceURL(c, id), nil)
_, res.Err = c.Delete(resourceURL(c, id), nil) return
return res
} }
// AssociateMonitor will associate a health monitor with a particular pool. // AssociateMonitor will associate a health monitor with a particular pool.
...@@ -161,26 +155,16 @@ func Delete(c *gophercloud.ServiceClient, id string) DeleteResult { ...@@ -161,26 +155,16 @@ func Delete(c *gophercloud.ServiceClient, id string) DeleteResult {
// pool and will deactivate these members if they are deemed unhealthy. A // 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 // member can be deactivated (status set to INACTIVE) if any of health monitors
// finds it unhealthy. // finds it unhealthy.
func AssociateMonitor(c *gophercloud.ServiceClient, poolID, monitorID string) AssociateResult { func AssociateMonitor(c *gophercloud.ServiceClient, poolID, monitorID string) (r AssociateResult) {
type hm struct { b := map[string]interface{}{"health_monitor": map[string]string{"id": monitorID}}
ID string `json:"id"` _, r.Err = c.Post(associateURL(c, poolID), b, &r.Body, nil)
} return
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
} }
// DisassociateMonitor will disassociate a health monitor with a particular // DisassociateMonitor will disassociate a health monitor with a particular
// pool. When dissociation is successful, the health monitor will no longer // pool. When dissociation is successful, the health monitor will no longer
// check for the health of the members of the pool. // check for the health of the members of the pool.
func DisassociateMonitor(c *gophercloud.ServiceClient, poolID, monitorID string) AssociateResult { func DisassociateMonitor(c *gophercloud.ServiceClient, poolID, monitorID string) (r AssociateResult) {
var res AssociateResult _, r.Err = c.Delete(disassociateURL(c, poolID, monitorID), nil)
_, res.Err = c.Delete(disassociateURL(c, poolID, monitorID), nil) return
return res
} }
package pools package pools
import ( import (
"github.com/mitchellh/mapstructure" "github.com/gophercloud/gophercloud"
"github.com/rackspace/gophercloud" "github.com/gophercloud/gophercloud/pagination"
"github.com/rackspace/gophercloud/pagination"
) )
// Pool represents a logical set of devices, such as web servers, that you // Pool represents a logical set of devices, such as web servers, that you
...@@ -18,7 +17,7 @@ type Pool struct { ...@@ -18,7 +17,7 @@ type Pool struct {
// The load-balancer algorithm, which is round-robin, least-connections, and // The load-balancer algorithm, which is round-robin, least-connections, and
// so on. This value, which must be supported, is dependent on the provider. // so on. This value, which must be supported, is dependent on the provider.
// Round-robin must be supported. // 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. // The protocol of the pool, which is TCP, HTTP, or HTTPS.
Protocol string Protocol string
...@@ -27,30 +26,30 @@ type Pool struct { ...@@ -27,30 +26,30 @@ type Pool struct {
Description string Description string
// The IDs of associated monitors which check the health of the pool members. // 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 // 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. // 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 // Owner of the pool. Only an administrative user can specify a tenant ID
// other than its own. // 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). // 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. // Pool name. Does not have to be unique.
Name string Name string
// List of member IDs that belong to the pool. // 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. // The unique ID for the pool.
ID string ID string
// The ID of the virtual IP associated with this pool // 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 // The provider
Provider string Provider string
...@@ -65,40 +64,32 @@ type PoolPage struct { ...@@ -65,40 +64,32 @@ type PoolPage struct {
// NextPageURL is invoked when a paginated collection of pools has reached // 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 // 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. // to do this, it needs to construct the next page's URL.
func (p PoolPage) NextPageURL() (string, error) { func (r PoolPage) NextPageURL() (string, error) {
type resp struct { var s struct {
Links []gophercloud.Link `mapstructure:"pools_links"` Links []gophercloud.Link `json:"pools_links"`
} }
err := r.ExtractInto(&s)
var r resp
err := mapstructure.Decode(p.Body, &r)
if err != nil { if err != nil {
return "", err return "", err
} }
return gophercloud.ExtractNextURL(s.Links)
return gophercloud.ExtractNextURL(r.Links)
} }
// IsEmpty checks whether a PoolPage struct is empty. // IsEmpty checks whether a PoolPage struct is empty.
func (p PoolPage) IsEmpty() (bool, error) { func (r PoolPage) IsEmpty() (bool, error) {
is, err := ExtractPools(p) is, err := ExtractPools(r)
if err != nil { return len(is) == 0, err
return true, nil
}
return len(is) == 0, nil
} }
// ExtractPools accepts a Page struct, specifically a RouterPage struct, // ExtractPools accepts a Page struct, specifically a RouterPage struct,
// and extracts the elements into a slice of Router structs. In other words, // and extracts the elements into a slice of Router structs. In other words,
// a generic collection is mapped into a relevant slice. // a generic collection is mapped into a relevant slice.
func ExtractPools(page pagination.Page) ([]Pool, error) { func ExtractPools(r pagination.Page) ([]Pool, error) {
var resp struct { var s struct {
Pools []Pool `mapstructure:"pools" json:"pools"` Pools []Pool `json:"pools"`
} }
err := (r.(PoolPage)).ExtractInto(&s)
err := mapstructure.Decode(page.(PoolPage).Body, &resp) return s.Pools, err
return resp.Pools, err
} }
type commonResult struct { type commonResult struct {
...@@ -107,17 +98,11 @@ type commonResult struct { ...@@ -107,17 +98,11 @@ type commonResult struct {
// Extract is a function that accepts a result and extracts a router. // Extract is a function that accepts a result and extracts a router.
func (r commonResult) Extract() (*Pool, error) { func (r commonResult) Extract() (*Pool, error) {
if r.Err != nil { var s struct {
return nil, r.Err
}
var res struct {
Pool *Pool `json:"pool"` Pool *Pool `json:"pool"`
} }
err := r.ExtractInto(&s)
err := mapstructure.Decode(r.Body, &res) return s.Pool, err
return res.Pool, err
} }
// CreateResult represents the result of a create operation. // CreateResult represents the result of a create operation.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment