mirror of
https://github.com/kubernetes/client-go.git
synced 2025-09-27 07:34:04 +00:00
Merge pull request #39587 from zhouhaibing089/openstack-auth-provider
Automatic merge from submit-queue (batch tested with PRs 50087, 39587, 50042, 50241, 49914) plugin/pkg/client/auth: add openstack auth provider This is an implementation of auth provider for OpenStack world, just like python-openstackclient, we read the environment variables of a list `OS_*`, and client will cache a token to interact with each components, we can do the same here, the client side can cache a token locally at the first time, and rotate automatically when it expires. This requires an implementation of token authenticator at server side, refer: 1. [made by me] https://github.com/kubernetes/kubernetes/pull/25536, I can carry this on when it is fine to go. 2. [made by @kfox1111] https://github.com/kubernetes/kubernetes/pull/25391 The reason why I want to add this is due to the `client-side` nature, it will be confusing to implement it downstream, we would like to add this support here, and customers can get `kubectl` like they usually do(`brew install kubernetes-cli`), and it will just work. When this is done, we can deprecate the password keystone authenticator as the following reasons: 1. as mentioned at some other places, the `domain` is another parameters which should be provided. 2. in case the user supplies `apikey` and `secrets`, we might want to fill the `UserInfo` with the real name which is not implemented for now. cc @erictune @liggitt ``` add openstack auth provider ``` Kubernetes-commit: 59b8fa32f129be29f146bfd4888a5d1ab7e71ca5
This commit is contained in:
124
Godeps/Godeps.json
generated
124
Godeps/Godeps.json
generated
@@ -174,6 +174,34 @@
|
|||||||
"ImportPath": "github.com/googleapis/gnostic/extensions",
|
"ImportPath": "github.com/googleapis/gnostic/extensions",
|
||||||
"Rev": "0c5108395e2debce0d731cf0287ddf7242066aba"
|
"Rev": "0c5108395e2debce0d731cf0287ddf7242066aba"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/gophercloud/gophercloud",
|
||||||
|
"Rev": "ed590d9afe113c6107cd60717b196155e6579e78"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/gophercloud/gophercloud/openstack",
|
||||||
|
"Rev": "ed590d9afe113c6107cd60717b196155e6579e78"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v2/tenants",
|
||||||
|
"Rev": "ed590d9afe113c6107cd60717b196155e6579e78"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens",
|
||||||
|
"Rev": "ed590d9afe113c6107cd60717b196155e6579e78"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens",
|
||||||
|
"Rev": "ed590d9afe113c6107cd60717b196155e6579e78"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/gophercloud/gophercloud/openstack/utils",
|
||||||
|
"Rev": "ed590d9afe113c6107cd60717b196155e6579e78"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/gophercloud/gophercloud/pagination",
|
||||||
|
"Rev": "ed590d9afe113c6107cd60717b196155e6579e78"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/gregjones/httpcache",
|
"ImportPath": "github.com/gregjones/httpcache",
|
||||||
"Rev": "787624de3eb7bd915c329cba748687a3b22666a6"
|
"Rev": "787624de3eb7bd915c329cba748687a3b22666a6"
|
||||||
@@ -432,195 +460,195 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/api/equality",
|
"ImportPath": "k8s.io/apimachinery/pkg/api/equality",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/api/errors",
|
"ImportPath": "k8s.io/apimachinery/pkg/api/errors",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/api/meta",
|
"ImportPath": "k8s.io/apimachinery/pkg/api/meta",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/api/resource",
|
"ImportPath": "k8s.io/apimachinery/pkg/api/resource",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/apimachinery",
|
"ImportPath": "k8s.io/apimachinery/pkg/apimachinery",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/apimachinery/registered",
|
"ImportPath": "k8s.io/apimachinery/pkg/apimachinery/registered",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1",
|
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured",
|
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1alpha1",
|
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1alpha1",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/conversion",
|
"ImportPath": "k8s.io/apimachinery/pkg/conversion",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/conversion/queryparams",
|
"ImportPath": "k8s.io/apimachinery/pkg/conversion/queryparams",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/conversion/unstructured",
|
"ImportPath": "k8s.io/apimachinery/pkg/conversion/unstructured",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/fields",
|
"ImportPath": "k8s.io/apimachinery/pkg/fields",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/labels",
|
"ImportPath": "k8s.io/apimachinery/pkg/labels",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/runtime",
|
"ImportPath": "k8s.io/apimachinery/pkg/runtime",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/runtime/schema",
|
"ImportPath": "k8s.io/apimachinery/pkg/runtime/schema",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer",
|
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/json",
|
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/json",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/protobuf",
|
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/protobuf",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/recognizer",
|
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/recognizer",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/streaming",
|
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/streaming",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/versioning",
|
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/versioning",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/selection",
|
"ImportPath": "k8s.io/apimachinery/pkg/selection",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/types",
|
"ImportPath": "k8s.io/apimachinery/pkg/types",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/util/cache",
|
"ImportPath": "k8s.io/apimachinery/pkg/util/cache",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/util/clock",
|
"ImportPath": "k8s.io/apimachinery/pkg/util/clock",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/util/diff",
|
"ImportPath": "k8s.io/apimachinery/pkg/util/diff",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/util/errors",
|
"ImportPath": "k8s.io/apimachinery/pkg/util/errors",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/util/framer",
|
"ImportPath": "k8s.io/apimachinery/pkg/util/framer",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/util/httpstream",
|
"ImportPath": "k8s.io/apimachinery/pkg/util/httpstream",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/util/httpstream/spdy",
|
"ImportPath": "k8s.io/apimachinery/pkg/util/httpstream/spdy",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/util/intstr",
|
"ImportPath": "k8s.io/apimachinery/pkg/util/intstr",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/util/json",
|
"ImportPath": "k8s.io/apimachinery/pkg/util/json",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/util/mergepatch",
|
"ImportPath": "k8s.io/apimachinery/pkg/util/mergepatch",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/util/net",
|
"ImportPath": "k8s.io/apimachinery/pkg/util/net",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/util/remotecommand",
|
"ImportPath": "k8s.io/apimachinery/pkg/util/remotecommand",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/util/runtime",
|
"ImportPath": "k8s.io/apimachinery/pkg/util/runtime",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/util/sets",
|
"ImportPath": "k8s.io/apimachinery/pkg/util/sets",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/util/strategicpatch",
|
"ImportPath": "k8s.io/apimachinery/pkg/util/strategicpatch",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/util/validation",
|
"ImportPath": "k8s.io/apimachinery/pkg/util/validation",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/util/validation/field",
|
"ImportPath": "k8s.io/apimachinery/pkg/util/validation/field",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/util/wait",
|
"ImportPath": "k8s.io/apimachinery/pkg/util/wait",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/util/yaml",
|
"ImportPath": "k8s.io/apimachinery/pkg/util/yaml",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/version",
|
"ImportPath": "k8s.io/apimachinery/pkg/version",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/watch",
|
"ImportPath": "k8s.io/apimachinery/pkg/watch",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/third_party/forked/golang/json",
|
"ImportPath": "k8s.io/apimachinery/third_party/forked/golang/json",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/third_party/forked/golang/netutil",
|
"ImportPath": "k8s.io/apimachinery/third_party/forked/golang/netutil",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/third_party/forked/golang/reflect",
|
"ImportPath": "k8s.io/apimachinery/third_party/forked/golang/reflect",
|
||||||
"Rev": "8cc513375dee45d0409b6cd3d1bec72cf3ea1be8"
|
"Rev": "8b987737205f40c7d6f2a8837c39fa0cc4d44cfd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kube-openapi/pkg/common",
|
"ImportPath": "k8s.io/kube-openapi/pkg/common",
|
||||||
|
@@ -15,6 +15,7 @@ go_library(
|
|||||||
"//vendor/k8s.io/client-go/plugin/pkg/client/auth/azure:go_default_library",
|
"//vendor/k8s.io/client-go/plugin/pkg/client/auth/azure:go_default_library",
|
||||||
"//vendor/k8s.io/client-go/plugin/pkg/client/auth/gcp:go_default_library",
|
"//vendor/k8s.io/client-go/plugin/pkg/client/auth/gcp:go_default_library",
|
||||||
"//vendor/k8s.io/client-go/plugin/pkg/client/auth/oidc:go_default_library",
|
"//vendor/k8s.io/client-go/plugin/pkg/client/auth/oidc:go_default_library",
|
||||||
|
"//vendor/k8s.io/client-go/plugin/pkg/client/auth/openstack:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ filegroup(
|
|||||||
"//staging/src/k8s.io/client-go/plugin/pkg/client/auth/azure:all-srcs",
|
"//staging/src/k8s.io/client-go/plugin/pkg/client/auth/azure:all-srcs",
|
||||||
"//staging/src/k8s.io/client-go/plugin/pkg/client/auth/gcp:all-srcs",
|
"//staging/src/k8s.io/client-go/plugin/pkg/client/auth/gcp:all-srcs",
|
||||||
"//staging/src/k8s.io/client-go/plugin/pkg/client/auth/oidc:all-srcs",
|
"//staging/src/k8s.io/client-go/plugin/pkg/client/auth/oidc:all-srcs",
|
||||||
|
"//staging/src/k8s.io/client-go/plugin/pkg/client/auth/openstack:all-srcs",
|
||||||
],
|
],
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
)
|
)
|
||||||
|
40
plugin/pkg/client/auth/openstack/BUILD
Normal file
40
plugin/pkg/client/auth/openstack/BUILD
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
load(
|
||||||
|
"@io_bazel_rules_go//go:def.bzl",
|
||||||
|
"go_library",
|
||||||
|
"go_test",
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = ["openstack_test.go"],
|
||||||
|
library = ":go_default_library",
|
||||||
|
tags = ["automanaged"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["openstack.go"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [
|
||||||
|
"//vendor/github.com/golang/glog:go_default_library",
|
||||||
|
"//vendor/github.com/gophercloud/gophercloud/openstack:go_default_library",
|
||||||
|
"//vendor/k8s.io/client-go/rest:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
)
|
161
plugin/pkg/client/auth/openstack/openstack.go
Normal file
161
plugin/pkg/client/auth/openstack/openstack.go
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"github.com/gophercloud/gophercloud/openstack"
|
||||||
|
|
||||||
|
restclient "k8s.io/client-go/rest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if err := restclient.RegisterAuthProviderPlugin("openstack", newOpenstackAuthProvider); err != nil {
|
||||||
|
glog.Fatalf("Failed to register openstack auth plugin: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultTTLDuration is the time before a token gets expired.
|
||||||
|
const DefaultTTLDuration = 10 * time.Minute
|
||||||
|
|
||||||
|
// openstackAuthProvider is an authprovider for openstack. this provider reads
|
||||||
|
// the environment variables to determine the client identity, and generates a
|
||||||
|
// token which will be inserted into the request header later.
|
||||||
|
type openstackAuthProvider struct {
|
||||||
|
ttl time.Duration
|
||||||
|
|
||||||
|
tokenGetter TokenGetter
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenGetter returns a bearer token that can be inserted into request.
|
||||||
|
type TokenGetter interface {
|
||||||
|
Token() (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type tokenGetter struct{}
|
||||||
|
|
||||||
|
// Token creates a token by authenticate with keystone.
|
||||||
|
func (*tokenGetter) Token() (string, error) {
|
||||||
|
options, err := openstack.AuthOptionsFromEnv()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to read openstack env vars: %s", err)
|
||||||
|
}
|
||||||
|
client, err := openstack.AuthenticatedClient(options)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("authentication failed: %s", err)
|
||||||
|
}
|
||||||
|
return client.TokenID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// cachedGetter caches a token until it gets expired, after the expiration, it will
|
||||||
|
// generate another token and cache it.
|
||||||
|
type cachedGetter struct {
|
||||||
|
mutex sync.Mutex
|
||||||
|
tokenGetter TokenGetter
|
||||||
|
|
||||||
|
token string
|
||||||
|
born time.Time
|
||||||
|
ttl time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token returns the current available token, create a new one if expired.
|
||||||
|
func (c *cachedGetter) Token() (string, error) {
|
||||||
|
c.mutex.Lock()
|
||||||
|
defer c.mutex.Unlock()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
// no token or exceeds the TTL
|
||||||
|
if c.token == "" || time.Now().Sub(c.born) > c.ttl {
|
||||||
|
c.token, err = c.tokenGetter.Token()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to get token: %s", err)
|
||||||
|
}
|
||||||
|
c.born = time.Now()
|
||||||
|
}
|
||||||
|
return c.token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// tokenRoundTripper implements the RoundTripper interface: adding the bearer token
|
||||||
|
// into the request header.
|
||||||
|
type tokenRoundTripper struct {
|
||||||
|
http.RoundTripper
|
||||||
|
|
||||||
|
tokenGetter TokenGetter
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoundTrip adds the bearer token into the request.
|
||||||
|
func (t *tokenRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
// if the authorization header already present, use it.
|
||||||
|
if req.Header.Get("Authorization") != "" {
|
||||||
|
return t.RoundTripper.RoundTrip(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := t.tokenGetter.Token()
|
||||||
|
if err == nil {
|
||||||
|
req.Header.Set("Authorization", "Bearer "+token)
|
||||||
|
} else {
|
||||||
|
glog.V(4).Infof("failed to get token: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.RoundTripper.RoundTrip(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newOpenstackAuthProvider creates an auth provider which works with openstack
|
||||||
|
// environment.
|
||||||
|
func newOpenstackAuthProvider(clusterAddress string, config map[string]string, persister restclient.AuthProviderConfigPersister) (restclient.AuthProvider, error) {
|
||||||
|
var ttlDuration time.Duration
|
||||||
|
var err error
|
||||||
|
|
||||||
|
ttl, found := config["ttl"]
|
||||||
|
if !found {
|
||||||
|
ttlDuration = DefaultTTLDuration
|
||||||
|
// persist to config
|
||||||
|
config["ttl"] = ttlDuration.String()
|
||||||
|
if err = persister.Persist(config); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to persist config: %s", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ttlDuration, err = time.ParseDuration(ttl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse ttl config: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: read/persist client configuration(OS_XXX env vars) in config
|
||||||
|
|
||||||
|
return &openstackAuthProvider{
|
||||||
|
ttl: ttlDuration,
|
||||||
|
tokenGetter: &tokenGetter{},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (oap *openstackAuthProvider) WrapTransport(rt http.RoundTripper) http.RoundTripper {
|
||||||
|
return &tokenRoundTripper{
|
||||||
|
RoundTripper: rt,
|
||||||
|
tokenGetter: &cachedGetter{
|
||||||
|
tokenGetter: oap.tokenGetter,
|
||||||
|
ttl: oap.ttl,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (oap *openstackAuthProvider) Login() error { return nil }
|
116
plugin/pkg/client/auth/openstack/openstack_test.go
Normal file
116
plugin/pkg/client/auth/openstack/openstack_test.go
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// testTokenGetter is a simple random token getter.
|
||||||
|
type testTokenGetter struct{}
|
||||||
|
|
||||||
|
const LetterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
|
||||||
|
func RandStringBytes(n int) string {
|
||||||
|
b := make([]byte, n)
|
||||||
|
for i := range b {
|
||||||
|
b[i] = LetterBytes[rand.Intn(len(LetterBytes))]
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*testTokenGetter) Token() (string, error) {
|
||||||
|
return RandStringBytes(32), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// testRoundTripper is mocked roundtripper which responds with unauthorized when
|
||||||
|
// there is no authorization header, otherwise returns status ok.
|
||||||
|
type testRoundTripper struct{}
|
||||||
|
|
||||||
|
func (trt *testRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
authHeader := req.Header.Get("Authorization")
|
||||||
|
if authHeader == "" || authHeader == "Bearer " {
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: http.StatusUnauthorized,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return &http.Response{StatusCode: http.StatusOK}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOpenstackAuthProvider(t *testing.T) {
|
||||||
|
trt := &tokenRoundTripper{
|
||||||
|
RoundTripper: &testRoundTripper{},
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
ttl time.Duration
|
||||||
|
interval time.Duration
|
||||||
|
same bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "normal",
|
||||||
|
ttl: 2 * time.Second,
|
||||||
|
interval: 1 * time.Second,
|
||||||
|
same: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "expire",
|
||||||
|
ttl: 1 * time.Second,
|
||||||
|
interval: 2 * time.Second,
|
||||||
|
same: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
trt.tokenGetter = &cachedGetter{
|
||||||
|
tokenGetter: &testTokenGetter{},
|
||||||
|
ttl: test.ttl,
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodPost, "https://test-api-server.com", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to new request: %s", err)
|
||||||
|
}
|
||||||
|
trt.RoundTrip(req)
|
||||||
|
header := req.Header.Get("Authorization")
|
||||||
|
if header == "" {
|
||||||
|
t.Errorf("expect to see token in header, but is absent")
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(test.interval)
|
||||||
|
|
||||||
|
req, err = http.NewRequest(http.MethodPost, "https://test-api-server.com", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to new request: %s", err)
|
||||||
|
}
|
||||||
|
trt.RoundTrip(req)
|
||||||
|
newHeader := req.Header.Get("Authorization")
|
||||||
|
if newHeader == "" {
|
||||||
|
t.Errorf("expect to see token in header, but is absent")
|
||||||
|
}
|
||||||
|
|
||||||
|
same := newHeader == header
|
||||||
|
if same != test.same {
|
||||||
|
t.Errorf("expect to get %t when compare header, but saw %t", test.same, same)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -21,4 +21,5 @@ import (
|
|||||||
_ "k8s.io/client-go/plugin/pkg/client/auth/azure"
|
_ "k8s.io/client-go/plugin/pkg/client/auth/azure"
|
||||||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||||
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
|
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
|
||||||
|
_ "k8s.io/client-go/plugin/pkg/client/auth/openstack"
|
||||||
)
|
)
|
||||||
|
1
vendor/github.com/gophercloud/gophercloud/.gitignore
generated
vendored
Normal file
1
vendor/github.com/gophercloud/gophercloud/.gitignore
generated
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
**/*.swp
|
19
vendor/github.com/gophercloud/gophercloud/.travis.yml
generated
vendored
Normal file
19
vendor/github.com/gophercloud/gophercloud/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
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 get golang.org/x/tools/cmd/goimports
|
||||||
|
go:
|
||||||
|
- 1.8
|
||||||
|
- tip
|
||||||
|
env:
|
||||||
|
global:
|
||||||
|
- secure: "xSQsAG5wlL9emjbCdxzz/hYQsSpJ/bABO1kkbwMSISVcJ3Nk0u4ywF+LS4bgeOnwPfmFvNTOqVDu3RwEvMeWXSI76t1piCPcObutb2faKLVD/hLoAS76gYX+Z8yGWGHrSB7Do5vTPj1ERe2UljdrnsSeOXzoDwFxYRaZLX4bBOB4AyoGvRniil5QXPATiA1tsWX1VMicj8a4F8X+xeESzjt1Q5Iy31e7vkptu71bhvXCaoo5QhYwT+pLR9dN0S1b7Ro0KVvkRefmr1lUOSYd2e74h6Lc34tC1h3uYZCS4h47t7v5cOXvMNxinEj2C51RvbjvZI1RLVdkuAEJD1Iz4+Ote46nXbZ//6XRZMZz/YxQ13l7ux1PFjgEB6HAapmF5Xd8PRsgeTU9LRJxpiTJ3P5QJ3leS1va8qnziM5kYipj/Rn+V8g2ad/rgkRox9LSiR9VYZD2Pe45YCb1mTKSl2aIJnV7nkOqsShY5LNB4JZSg7xIffA+9YVDktw8dJlATjZqt7WvJJ49g6A61mIUV4C15q2JPGKTkZzDiG81NtmS7hFa7k0yaE2ELgYocbcuyUcAahhxntYTC0i23nJmEHVNiZmBO3u7EgpWe4KGVfumU+lt12tIn5b3dZRBBUk3QakKKozSK1QPHGpk/AZGrhu7H6l8to6IICKWtDcyMPQ="
|
||||||
|
script:
|
||||||
|
- ./script/coverage
|
||||||
|
- ./script/format
|
||||||
|
after_success:
|
||||||
|
- $HOME/gopath/bin/goveralls -service=travis-ci -coverprofile=cover.out
|
0
vendor/github.com/gophercloud/gophercloud/CHANGELOG.md
generated
vendored
Normal file
0
vendor/github.com/gophercloud/gophercloud/CHANGELOG.md
generated
vendored
Normal file
148
vendor/github.com/gophercloud/gophercloud/FAQ.md
generated
vendored
Normal file
148
vendor/github.com/gophercloud/gophercloud/FAQ.md
generated
vendored
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
# Tips
|
||||||
|
|
||||||
|
## Implementing default logging and re-authentication attempts
|
||||||
|
|
||||||
|
You can implement custom logging and/or limit re-auth attempts by creating a custom HTTP client
|
||||||
|
like the following and setting it as the provider client's HTTP Client (via the
|
||||||
|
`gophercloud.ProviderClient.HTTPClient` field):
|
||||||
|
|
||||||
|
```go
|
||||||
|
//...
|
||||||
|
|
||||||
|
// LogRoundTripper satisfies the http.RoundTripper interface and is used to
|
||||||
|
// customize the default Gophercloud RoundTripper to allow for logging.
|
||||||
|
type LogRoundTripper struct {
|
||||||
|
rt http.RoundTripper
|
||||||
|
numReauthAttempts int
|
||||||
|
}
|
||||||
|
|
||||||
|
// newHTTPClient return a custom HTTP client that allows for logging relevant
|
||||||
|
// information before and after the HTTP request.
|
||||||
|
func newHTTPClient() http.Client {
|
||||||
|
return http.Client{
|
||||||
|
Transport: &LogRoundTripper{
|
||||||
|
rt: http.DefaultTransport,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoundTrip performs a round-trip HTTP request and logs relevant information about it.
|
||||||
|
func (lrt *LogRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) {
|
||||||
|
glog.Infof("Request URL: %s\n", request.URL)
|
||||||
|
|
||||||
|
response, err := lrt.rt.RoundTrip(request)
|
||||||
|
if response == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.StatusCode == http.StatusUnauthorized {
|
||||||
|
if lrt.numReauthAttempts == 3 {
|
||||||
|
return response, fmt.Errorf("Tried to re-authenticate 3 times with no success.")
|
||||||
|
}
|
||||||
|
lrt.numReauthAttempts++
|
||||||
|
}
|
||||||
|
|
||||||
|
glog.Debugf("Response Status: %s\n", response.Status)
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoint := "https://127.0.0.1/auth"
|
||||||
|
pc := openstack.NewClient(endpoint)
|
||||||
|
pc.HTTPClient = newHTTPClient()
|
||||||
|
|
||||||
|
//...
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Implementing custom objects
|
||||||
|
|
||||||
|
OpenStack request/response objects may differ among variable names or types.
|
||||||
|
|
||||||
|
### Custom request objects
|
||||||
|
|
||||||
|
To pass custom options to a request, implement the desired `<ACTION>OptsBuilder` interface. For
|
||||||
|
example, to pass in
|
||||||
|
|
||||||
|
```go
|
||||||
|
type MyCreateServerOpts struct {
|
||||||
|
Name string
|
||||||
|
Size int
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
to `servers.Create`, simply implement the `servers.CreateOptsBuilder` interface:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (o MyCreateServeropts) ToServerCreateMap() (map[string]interface{}, error) {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"name": o.Name,
|
||||||
|
"size": o.Size,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
create an instance of your custom options object, and pass it to `servers.Create`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// ...
|
||||||
|
myOpts := MyCreateServerOpts{
|
||||||
|
Name: "s1",
|
||||||
|
Size: "100",
|
||||||
|
}
|
||||||
|
server, err := servers.Create(computeClient, myOpts).Extract()
|
||||||
|
// ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom response objects
|
||||||
|
|
||||||
|
Some OpenStack services have extensions. Extensions that are supported in Gophercloud can be
|
||||||
|
combined to create a custom object:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// ...
|
||||||
|
type MyVolume struct {
|
||||||
|
volumes.Volume
|
||||||
|
tenantattr.VolumeExt
|
||||||
|
}
|
||||||
|
|
||||||
|
var v struct {
|
||||||
|
MyVolume `json:"volume"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := volumes.Get(client, volID).ExtractInto(&v)
|
||||||
|
// ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Overriding default `UnmarshalJSON` method
|
||||||
|
|
||||||
|
For some response objects, a field may be a custom type or may be allowed to take on
|
||||||
|
different types. In these cases, overriding the default `UnmarshalJSON` method may be
|
||||||
|
necessary. To do this, declare the JSON `struct` field tag as "-" and create an `UnmarshalJSON`
|
||||||
|
method on the type:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// ...
|
||||||
|
type MyVolume struct {
|
||||||
|
ID string `json: "id"`
|
||||||
|
TimeCreated time.Time `json: "-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MyVolume) UnmarshalJSON(b []byte) error {
|
||||||
|
type tmp MyVolume
|
||||||
|
var s struct {
|
||||||
|
tmp
|
||||||
|
TimeCreated gophercloud.JSONRFC3339MilliNoZ `json:"created_at"`
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(b, &s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*r = Volume(s.tmp)
|
||||||
|
|
||||||
|
r.TimeCreated = time.Time(s.CreatedAt)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// ...
|
||||||
|
```
|
191
vendor/github.com/gophercloud/gophercloud/LICENSE
generated
vendored
Normal file
191
vendor/github.com/gophercloud/gophercloud/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
Copyright 2012-2013 Rackspace, Inc.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||||
|
this file except in compliance with the License. You may obtain a copy of the
|
||||||
|
License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software distributed
|
||||||
|
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||||
|
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
|
------
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
32
vendor/github.com/gophercloud/gophercloud/MIGRATING.md
generated
vendored
Normal file
32
vendor/github.com/gophercloud/gophercloud/MIGRATING.md
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Compute
|
||||||
|
|
||||||
|
## Floating IPs
|
||||||
|
|
||||||
|
* `github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingip` is now `github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips`
|
||||||
|
* `floatingips.Associate` and `floatingips.Disassociate` have been removed.
|
||||||
|
* `floatingips.DisassociateOpts` is now required to disassociate a Floating IP.
|
||||||
|
|
||||||
|
## Security Groups
|
||||||
|
|
||||||
|
* `secgroups.AddServerToGroup` is now `secgroups.AddServer`.
|
||||||
|
* `secgroups.RemoveServerFromGroup` is now `secgroups.RemoveServer`.
|
||||||
|
|
||||||
|
## Servers
|
||||||
|
|
||||||
|
* `servers.Reboot` now requires a `servers.RebootOpts` struct:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
rebootOpts := &servers.RebootOpts{
|
||||||
|
Type: servers.SoftReboot,
|
||||||
|
}
|
||||||
|
res := servers.Reboot(client, server.ID, rebootOpts)
|
||||||
|
```
|
||||||
|
|
||||||
|
# Identity
|
||||||
|
|
||||||
|
## V3
|
||||||
|
|
||||||
|
### Tokens
|
||||||
|
|
||||||
|
* `Token.ExpiresAt` is now of type `gophercloud.JSONRFC3339Milli` instead of
|
||||||
|
`time.Time`
|
143
vendor/github.com/gophercloud/gophercloud/README.md
generated
vendored
Normal file
143
vendor/github.com/gophercloud/gophercloud/README.md
generated
vendored
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
# Gophercloud: an OpenStack SDK for Go
|
||||||
|
[](https://travis-ci.org/gophercloud/gophercloud)
|
||||||
|
[](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://openstack.example.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).
|
||||||
|
|
||||||
|
## Advanced Usage
|
||||||
|
|
||||||
|
Have a look at the [FAQ](./FAQ.md) for some tips on customizing the way Gophercloud works.
|
||||||
|
|
||||||
|
## 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).
|
74
vendor/github.com/gophercloud/gophercloud/STYLEGUIDE.md
generated
vendored
Normal file
74
vendor/github.com/gophercloud/gophercloud/STYLEGUIDE.md
generated
vendored
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
|
||||||
|
## 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 `@`.
|
||||||
|
|
||||||
|
- Forcing PRs to be small can have the effect of users submitting PRs in a hierarchical chain, with
|
||||||
|
one depending on the next. If a PR depends on another one, it should have a [Pending #PRNUM]
|
||||||
|
prefix in the PR title. In addition, it will be the PR submitter's responsibility to remove the
|
||||||
|
[Pending #PRNUM] tag once the PR has been updated with the merged, dependent PR. That will
|
||||||
|
let reviewers know it is ready to review.
|
||||||
|
|
||||||
|
- 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 `results.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`).
|
327
vendor/github.com/gophercloud/gophercloud/auth_options.go
generated
vendored
Normal file
327
vendor/github.com/gophercloud/gophercloud/auth_options.go
generated
vendored
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
package gophercloud
|
||||||
|
|
||||||
|
/*
|
||||||
|
AuthOptions stores information needed to authenticate to an OpenStack Cloud.
|
||||||
|
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:"-"`
|
||||||
|
|
||||||
|
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:"-"`
|
||||||
|
DomainName string `json:"name,omitempty"`
|
||||||
|
|
||||||
|
// The TenantID and TenantName fields are optional for the Identity V2 API.
|
||||||
|
// The same fields are known as project_id and project_name in the Identity
|
||||||
|
// V3 API, but are collected as TenantID and TenantName here in both cases.
|
||||||
|
// 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.
|
||||||
|
// If DomainID or DomainName are provided, they will also apply to TenantName.
|
||||||
|
// It is not currently possible to authenticate with Username and a Domain
|
||||||
|
// and scope to a Project in a different Domain by using TenantName. To
|
||||||
|
// accomplish that, the ProjectID will need to be provided to the TenantID
|
||||||
|
// option.
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
} else {
|
||||||
|
if opts.TenantName != "" {
|
||||||
|
scope.ProjectName = opts.TenantName
|
||||||
|
scope.DomainID = opts.DomainID
|
||||||
|
scope.DomainName = opts.DomainName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
69
vendor/github.com/gophercloud/gophercloud/doc.go
generated
vendored
Normal file
69
vendor/github.com/gophercloud/gophercloud/doc.go
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
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. The IdentityEndpoint is typically refered to as
|
||||||
|
"auth_url" in information provided by the cloud operator. Additionally,
|
||||||
|
the cloud may refer to TenantID or TenantName as project_id and project_name.
|
||||||
|
These are defined like so:
|
||||||
|
|
||||||
|
opts := gophercloud.AuthOptions{
|
||||||
|
IdentityEndpoint: "https://openstack.example.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
|
76
vendor/github.com/gophercloud/gophercloud/endpoint_search.go
generated
vendored
Normal file
76
vendor/github.com/gophercloud/gophercloud/endpoint_search.go
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
package gophercloud
|
||||||
|
|
||||||
|
// Availability indicates to whom a specific service endpoint is accessible:
|
||||||
|
// the internet at large, internal networks only, or only to administrators.
|
||||||
|
// Different identity services use different terminology for these. Identity v2
|
||||||
|
// lists them as different kinds of URLs within the service catalog ("adminURL",
|
||||||
|
// "internalURL", and "publicURL"), while v3 lists them as "Interfaces" in an
|
||||||
|
// endpoint's response.
|
||||||
|
type Availability string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// AvailabilityAdmin indicates that an endpoint is only available to
|
||||||
|
// administrators.
|
||||||
|
AvailabilityAdmin Availability = "admin"
|
||||||
|
|
||||||
|
// AvailabilityPublic indicates that an endpoint is available to everyone on
|
||||||
|
// the internet.
|
||||||
|
AvailabilityPublic Availability = "public"
|
||||||
|
|
||||||
|
// AvailabilityInternal indicates that an endpoint is only available within
|
||||||
|
// the cluster's internal network.
|
||||||
|
AvailabilityInternal Availability = "internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EndpointOpts specifies search criteria used by queries against an
|
||||||
|
// OpenStack service catalog. The options must contain enough information to
|
||||||
|
// unambiguously identify one, and only one, endpoint within the catalog.
|
||||||
|
//
|
||||||
|
// Usually, these are passed to service client factory functions in a provider
|
||||||
|
// package, like "rackspace.NewComputeV2()".
|
||||||
|
type EndpointOpts struct {
|
||||||
|
// Type [required] is the service type for the client (e.g., "compute",
|
||||||
|
// "object-store"). Generally, this will be supplied by the service client
|
||||||
|
// function, but a user-given value will be honored if provided.
|
||||||
|
Type string
|
||||||
|
|
||||||
|
// Name [optional] is the service name for the client (e.g., "nova") as it
|
||||||
|
// appears in the service catalog. Services can have the same Type but a
|
||||||
|
// different Name, which is why both Type and Name are sometimes needed.
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// Region [required] is the geographic region in which the endpoint resides,
|
||||||
|
// generally specifying which datacenter should house your resources.
|
||||||
|
// Required only for services that span multiple regions.
|
||||||
|
Region string
|
||||||
|
|
||||||
|
// Availability [optional] is the visibility of the endpoint to be returned.
|
||||||
|
// Valid types include the constants AvailabilityPublic, AvailabilityInternal,
|
||||||
|
// or AvailabilityAdmin from this package.
|
||||||
|
//
|
||||||
|
// Availability is not required, and defaults to AvailabilityPublic. Not all
|
||||||
|
// providers or services offer all Availability options.
|
||||||
|
Availability Availability
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
EndpointLocator is an internal function to be used by provider implementations.
|
||||||
|
|
||||||
|
It provides an implementation that locates a single endpoint from a service
|
||||||
|
catalog for a specific ProviderClient based on user-provided EndpointOpts. The
|
||||||
|
provider then uses it to discover related ServiceClients.
|
||||||
|
*/
|
||||||
|
type EndpointLocator func(EndpointOpts) (string, error)
|
||||||
|
|
||||||
|
// ApplyDefaults is an internal method to be used by provider implementations.
|
||||||
|
//
|
||||||
|
// It sets EndpointOpts fields if not already set, including a default type.
|
||||||
|
// Currently, EndpointOpts.Availability defaults to the public endpoint.
|
||||||
|
func (eo *EndpointOpts) ApplyDefaults(t string) {
|
||||||
|
if eo.Type == "" {
|
||||||
|
eo.Type = t
|
||||||
|
}
|
||||||
|
if eo.Availability == "" {
|
||||||
|
eo.Availability = AvailabilityPublic
|
||||||
|
}
|
||||||
|
}
|
408
vendor/github.com/gophercloud/gophercloud/errors.go
generated
vendored
Normal file
408
vendor/github.com/gophercloud/gophercloud/errors.go
generated
vendored
Normal file
@@ -0,0 +1,408 @@
|
|||||||
|
package gophercloud
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// BaseError is an error type that all other error types embed.
|
||||||
|
type BaseError struct {
|
||||||
|
DefaultErrString string
|
||||||
|
Info string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e BaseError) Error() string {
|
||||||
|
e.DefaultErrString = "An error occurred while executing a Gophercloud request."
|
||||||
|
return e.choseErrString()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e BaseError) choseErrString() string {
|
||||||
|
if e.Info != "" {
|
||||||
|
return e.Info
|
||||||
|
}
|
||||||
|
return e.DefaultErrString
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrMissingInput is the error when input is required in a particular
|
||||||
|
// situation but not provided by the user
|
||||||
|
type ErrMissingInput struct {
|
||||||
|
BaseError
|
||||||
|
Argument string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrMissingInput) Error() string {
|
||||||
|
e.DefaultErrString = fmt.Sprintf("Missing input for argument [%s]", e.Argument)
|
||||||
|
return e.choseErrString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrInvalidInput is an error type used for most non-HTTP Gophercloud errors.
|
||||||
|
type ErrInvalidInput struct {
|
||||||
|
ErrMissingInput
|
||||||
|
Value interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrInvalidInput) Error() string {
|
||||||
|
e.DefaultErrString = fmt.Sprintf("Invalid input provided for argument [%s]: [%+v]", e.Argument, e.Value)
|
||||||
|
return e.choseErrString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrUnexpectedResponseCode is returned by the Request method when a response code other than
|
||||||
|
// those listed in OkCodes is encountered.
|
||||||
|
type ErrUnexpectedResponseCode struct {
|
||||||
|
BaseError
|
||||||
|
URL string
|
||||||
|
Method string
|
||||||
|
Expected []int
|
||||||
|
Actual int
|
||||||
|
Body []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrUnexpectedResponseCode) Error() string {
|
||||||
|
e.DefaultErrString = fmt.Sprintf(
|
||||||
|
"Expected HTTP response code %v when accessing [%s %s], but got %d instead\n%s",
|
||||||
|
e.Expected, e.Method, e.URL, e.Actual, e.Body,
|
||||||
|
)
|
||||||
|
return e.choseErrString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrDefault400 is the default error type returned on a 400 HTTP response code.
|
||||||
|
type ErrDefault400 struct {
|
||||||
|
ErrUnexpectedResponseCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrDefault401 is the default error type returned on a 401 HTTP response code.
|
||||||
|
type ErrDefault401 struct {
|
||||||
|
ErrUnexpectedResponseCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrDefault404 is the default error type returned on a 404 HTTP response code.
|
||||||
|
type ErrDefault404 struct {
|
||||||
|
ErrUnexpectedResponseCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrDefault405 is the default error type returned on a 405 HTTP response code.
|
||||||
|
type ErrDefault405 struct {
|
||||||
|
ErrUnexpectedResponseCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrDefault408 is the default error type returned on a 408 HTTP response code.
|
||||||
|
type ErrDefault408 struct {
|
||||||
|
ErrUnexpectedResponseCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrDefault429 is the default error type returned on a 429 HTTP response code.
|
||||||
|
type ErrDefault429 struct {
|
||||||
|
ErrUnexpectedResponseCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrDefault500 is the default error type returned on a 500 HTTP response code.
|
||||||
|
type ErrDefault500 struct {
|
||||||
|
ErrUnexpectedResponseCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrDefault503 is the default error type returned on a 503 HTTP response code.
|
||||||
|
type ErrDefault503 struct {
|
||||||
|
ErrUnexpectedResponseCode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrDefault400) Error() string {
|
||||||
|
return "Invalid request due to incorrect syntax or missing required parameters."
|
||||||
|
}
|
||||||
|
func (e ErrDefault401) Error() string {
|
||||||
|
return "Authentication failed"
|
||||||
|
}
|
||||||
|
func (e ErrDefault404) Error() string {
|
||||||
|
return "Resource not found"
|
||||||
|
}
|
||||||
|
func (e ErrDefault405) Error() string {
|
||||||
|
return "Method not allowed"
|
||||||
|
}
|
||||||
|
func (e ErrDefault408) Error() string {
|
||||||
|
return "The server timed out waiting for the request"
|
||||||
|
}
|
||||||
|
func (e ErrDefault429) Error() string {
|
||||||
|
return "Too many requests have been sent in a given amount of time. Pause" +
|
||||||
|
" requests, wait up to one minute, and try again."
|
||||||
|
}
|
||||||
|
func (e ErrDefault500) Error() string {
|
||||||
|
return "Internal Server Error"
|
||||||
|
}
|
||||||
|
func (e ErrDefault503) Error() string {
|
||||||
|
return "The service is currently unable to handle the request due to a temporary" +
|
||||||
|
" overloading or maintenance. This is a temporary condition. Try again later."
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err400er is the interface resource error types implement to override the error message
|
||||||
|
// from a 400 error.
|
||||||
|
type Err400er interface {
|
||||||
|
Error400(ErrUnexpectedResponseCode) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err401er is the interface resource error types implement to override the error message
|
||||||
|
// from a 401 error.
|
||||||
|
type Err401er interface {
|
||||||
|
Error401(ErrUnexpectedResponseCode) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err404er is the interface resource error types implement to override the error message
|
||||||
|
// from a 404 error.
|
||||||
|
type Err404er interface {
|
||||||
|
Error404(ErrUnexpectedResponseCode) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err405er is the interface resource error types implement to override the error message
|
||||||
|
// from a 405 error.
|
||||||
|
type Err405er interface {
|
||||||
|
Error405(ErrUnexpectedResponseCode) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err408er is the interface resource error types implement to override the error message
|
||||||
|
// from a 408 error.
|
||||||
|
type Err408er interface {
|
||||||
|
Error408(ErrUnexpectedResponseCode) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err429er is the interface resource error types implement to override the error message
|
||||||
|
// from a 429 error.
|
||||||
|
type Err429er interface {
|
||||||
|
Error429(ErrUnexpectedResponseCode) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err500er is the interface resource error types implement to override the error message
|
||||||
|
// from a 500 error.
|
||||||
|
type Err500er interface {
|
||||||
|
Error500(ErrUnexpectedResponseCode) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err503er is the interface resource error types implement to override the error message
|
||||||
|
// from a 503 error.
|
||||||
|
type Err503er interface {
|
||||||
|
Error503(ErrUnexpectedResponseCode) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrTimeOut is the error type returned when an operations times out.
|
||||||
|
type ErrTimeOut struct {
|
||||||
|
BaseError
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrTimeOut) Error() string {
|
||||||
|
e.DefaultErrString = "A time out occurred"
|
||||||
|
return e.choseErrString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrUnableToReauthenticate is the error type returned when reauthentication fails.
|
||||||
|
type ErrUnableToReauthenticate struct {
|
||||||
|
BaseError
|
||||||
|
ErrOriginal error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrUnableToReauthenticate) Error() string {
|
||||||
|
e.DefaultErrString = fmt.Sprintf("Unable to re-authenticate: %s", e.ErrOriginal)
|
||||||
|
return e.choseErrString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrErrorAfterReauthentication is the error type returned when reauthentication
|
||||||
|
// succeeds, but an error occurs afterword (usually an HTTP error).
|
||||||
|
type ErrErrorAfterReauthentication struct {
|
||||||
|
BaseError
|
||||||
|
ErrOriginal error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrErrorAfterReauthentication) Error() string {
|
||||||
|
e.DefaultErrString = fmt.Sprintf("Successfully re-authenticated, but got error executing request: %s", e.ErrOriginal)
|
||||||
|
return e.choseErrString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrServiceNotFound is returned when no service in a service catalog matches
|
||||||
|
// the provided EndpointOpts. This is generally returned by provider service
|
||||||
|
// factory methods like "NewComputeV2()" and can mean that a service is not
|
||||||
|
// enabled for your account.
|
||||||
|
type ErrServiceNotFound struct {
|
||||||
|
BaseError
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrServiceNotFound) Error() string {
|
||||||
|
e.DefaultErrString = "No suitable service could be found in the service catalog."
|
||||||
|
return e.choseErrString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrEndpointNotFound is returned when no available endpoints match the
|
||||||
|
// provided EndpointOpts. This is also generally returned by provider service
|
||||||
|
// factory methods, and usually indicates that a region was specified
|
||||||
|
// incorrectly.
|
||||||
|
type ErrEndpointNotFound struct {
|
||||||
|
BaseError
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrEndpointNotFound) Error() string {
|
||||||
|
e.DefaultErrString = "No suitable endpoint could be found in the service catalog."
|
||||||
|
return e.choseErrString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrResourceNotFound is the error when trying to retrieve a resource's
|
||||||
|
// ID by name and the resource doesn't exist.
|
||||||
|
type ErrResourceNotFound struct {
|
||||||
|
BaseError
|
||||||
|
Name string
|
||||||
|
ResourceType string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrResourceNotFound) Error() string {
|
||||||
|
e.DefaultErrString = fmt.Sprintf("Unable to find %s with name %s", e.ResourceType, e.Name)
|
||||||
|
return e.choseErrString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrMultipleResourcesFound is the error when trying to retrieve a resource's
|
||||||
|
// ID by name and multiple resources have the user-provided name.
|
||||||
|
type ErrMultipleResourcesFound struct {
|
||||||
|
BaseError
|
||||||
|
Name string
|
||||||
|
Count int
|
||||||
|
ResourceType string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrMultipleResourcesFound) Error() string {
|
||||||
|
e.DefaultErrString = fmt.Sprintf("Found %d %ss matching %s", e.Count, e.ResourceType, e.Name)
|
||||||
|
return e.choseErrString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrUnexpectedType is the error when an unexpected type is encountered
|
||||||
|
type ErrUnexpectedType struct {
|
||||||
|
BaseError
|
||||||
|
Expected string
|
||||||
|
Actual string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrUnexpectedType) Error() string {
|
||||||
|
e.DefaultErrString = fmt.Sprintf("Expected %s but got %s", e.Expected, e.Actual)
|
||||||
|
return e.choseErrString()
|
||||||
|
}
|
||||||
|
|
||||||
|
func unacceptedAttributeErr(attribute string) string {
|
||||||
|
return fmt.Sprintf("The base Identity V3 API does not accept authentication by %s", attribute)
|
||||||
|
}
|
||||||
|
|
||||||
|
func redundantWithTokenErr(attribute string) string {
|
||||||
|
return fmt.Sprintf("%s may not be provided when authenticating with a TokenID", attribute)
|
||||||
|
}
|
||||||
|
|
||||||
|
func redundantWithUserID(attribute string) string {
|
||||||
|
return fmt.Sprintf("%s may not be provided when authenticating with a UserID", attribute)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrAPIKeyProvided indicates that an APIKey was provided but can't be used.
|
||||||
|
type ErrAPIKeyProvided struct{ BaseError }
|
||||||
|
|
||||||
|
func (e ErrAPIKeyProvided) Error() string {
|
||||||
|
return unacceptedAttributeErr("APIKey")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrTenantIDProvided indicates that a TenantID was provided but can't be used.
|
||||||
|
type ErrTenantIDProvided struct{ BaseError }
|
||||||
|
|
||||||
|
func (e ErrTenantIDProvided) Error() string {
|
||||||
|
return unacceptedAttributeErr("TenantID")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrTenantNameProvided indicates that a TenantName was provided but can't be used.
|
||||||
|
type ErrTenantNameProvided struct{ BaseError }
|
||||||
|
|
||||||
|
func (e ErrTenantNameProvided) Error() string {
|
||||||
|
return unacceptedAttributeErr("TenantName")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrUsernameWithToken indicates that a Username was provided, but token authentication is being used instead.
|
||||||
|
type ErrUsernameWithToken struct{ BaseError }
|
||||||
|
|
||||||
|
func (e ErrUsernameWithToken) Error() string {
|
||||||
|
return redundantWithTokenErr("Username")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrUserIDWithToken indicates that a UserID was provided, but token authentication is being used instead.
|
||||||
|
type ErrUserIDWithToken struct{ BaseError }
|
||||||
|
|
||||||
|
func (e ErrUserIDWithToken) Error() string {
|
||||||
|
return redundantWithTokenErr("UserID")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrDomainIDWithToken indicates that a DomainID was provided, but token authentication is being used instead.
|
||||||
|
type ErrDomainIDWithToken struct{ BaseError }
|
||||||
|
|
||||||
|
func (e ErrDomainIDWithToken) Error() string {
|
||||||
|
return redundantWithTokenErr("DomainID")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrDomainNameWithToken indicates that a DomainName was provided, but token authentication is being used instead.s
|
||||||
|
type ErrDomainNameWithToken struct{ BaseError }
|
||||||
|
|
||||||
|
func (e ErrDomainNameWithToken) Error() string {
|
||||||
|
return redundantWithTokenErr("DomainName")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrUsernameOrUserID indicates that neither username nor userID are specified, or both are at once.
|
||||||
|
type ErrUsernameOrUserID struct{ BaseError }
|
||||||
|
|
||||||
|
func (e ErrUsernameOrUserID) Error() string {
|
||||||
|
return "Exactly one of Username and UserID must be provided for password authentication"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrDomainIDWithUserID indicates that a DomainID was provided, but unnecessary because a UserID is being used.
|
||||||
|
type ErrDomainIDWithUserID struct{ BaseError }
|
||||||
|
|
||||||
|
func (e ErrDomainIDWithUserID) Error() string {
|
||||||
|
return redundantWithUserID("DomainID")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrDomainNameWithUserID indicates that a DomainName was provided, but unnecessary because a UserID is being used.
|
||||||
|
type ErrDomainNameWithUserID struct{ BaseError }
|
||||||
|
|
||||||
|
func (e ErrDomainNameWithUserID) Error() string {
|
||||||
|
return redundantWithUserID("DomainName")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrDomainIDOrDomainName indicates that a username was provided, but no domain to scope it.
|
||||||
|
// It may also indicate that both a DomainID and a DomainName were provided at once.
|
||||||
|
type ErrDomainIDOrDomainName struct{ BaseError }
|
||||||
|
|
||||||
|
func (e ErrDomainIDOrDomainName) Error() string {
|
||||||
|
return "You must provide exactly one of DomainID or DomainName to authenticate by Username"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrMissingPassword indicates that no password was provided and no token is available.
|
||||||
|
type ErrMissingPassword struct{ BaseError }
|
||||||
|
|
||||||
|
func (e ErrMissingPassword) Error() string {
|
||||||
|
return "You must provide a password to authenticate"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrScopeDomainIDOrDomainName indicates that a domain ID or Name was required in a Scope, but not present.
|
||||||
|
type ErrScopeDomainIDOrDomainName struct{ BaseError }
|
||||||
|
|
||||||
|
func (e ErrScopeDomainIDOrDomainName) Error() string {
|
||||||
|
return "You must provide exactly one of DomainID or DomainName in a Scope with ProjectName"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrScopeProjectIDOrProjectName indicates that both a ProjectID and a ProjectName were provided in a Scope.
|
||||||
|
type ErrScopeProjectIDOrProjectName struct{ BaseError }
|
||||||
|
|
||||||
|
func (e ErrScopeProjectIDOrProjectName) Error() string {
|
||||||
|
return "You must provide at most one of ProjectID or ProjectName in a Scope"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrScopeProjectIDAlone indicates that a ProjectID was provided with other constraints in a Scope.
|
||||||
|
type ErrScopeProjectIDAlone struct{ BaseError }
|
||||||
|
|
||||||
|
func (e ErrScopeProjectIDAlone) Error() string {
|
||||||
|
return "ProjectID must be supplied alone in a Scope"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrScopeDomainName indicates that a DomainName was provided alone in a Scope.
|
||||||
|
type ErrScopeDomainName struct{ BaseError }
|
||||||
|
|
||||||
|
func (e ErrScopeDomainName) Error() string {
|
||||||
|
return "DomainName must be supplied with a ProjectName or ProjectID in a Scope"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrScopeEmpty indicates that no credentials were provided in a Scope.
|
||||||
|
type ErrScopeEmpty struct{ BaseError }
|
||||||
|
|
||||||
|
func (e ErrScopeEmpty) Error() string {
|
||||||
|
return "You must provide either a Project or Domain in a Scope"
|
||||||
|
}
|
52
vendor/github.com/gophercloud/gophercloud/openstack/auth_env.go
generated
vendored
Normal file
52
vendor/github.com/gophercloud/gophercloud/openstack/auth_env.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
)
|
||||||
|
|
||||||
|
var nilOptions = gophercloud.AuthOptions{}
|
||||||
|
|
||||||
|
// AuthOptionsFromEnv fills out an identity.AuthOptions structure with the settings found on the various OpenStack
|
||||||
|
// OS_* environment variables. The following variables provide sources of truth: OS_AUTH_URL, OS_USERNAME,
|
||||||
|
// OS_PASSWORD, OS_TENANT_ID, and OS_TENANT_NAME. Of these, OS_USERNAME, OS_PASSWORD, and OS_AUTH_URL must
|
||||||
|
// have settings, or an error will result. OS_TENANT_ID and OS_TENANT_NAME are optional.
|
||||||
|
func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) {
|
||||||
|
authURL := os.Getenv("OS_AUTH_URL")
|
||||||
|
username := os.Getenv("OS_USERNAME")
|
||||||
|
userID := os.Getenv("OS_USERID")
|
||||||
|
password := os.Getenv("OS_PASSWORD")
|
||||||
|
tenantID := os.Getenv("OS_TENANT_ID")
|
||||||
|
tenantName := os.Getenv("OS_TENANT_NAME")
|
||||||
|
domainID := os.Getenv("OS_DOMAIN_ID")
|
||||||
|
domainName := os.Getenv("OS_DOMAIN_NAME")
|
||||||
|
|
||||||
|
if authURL == "" {
|
||||||
|
err := gophercloud.ErrMissingInput{Argument: "authURL"}
|
||||||
|
return nilOptions, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if username == "" && userID == "" {
|
||||||
|
err := gophercloud.ErrMissingInput{Argument: "username"}
|
||||||
|
return nilOptions, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if password == "" {
|
||||||
|
err := gophercloud.ErrMissingInput{Argument: "password"}
|
||||||
|
return nilOptions, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ao := gophercloud.AuthOptions{
|
||||||
|
IdentityEndpoint: authURL,
|
||||||
|
UserID: userID,
|
||||||
|
Username: username,
|
||||||
|
Password: password,
|
||||||
|
TenantID: tenantID,
|
||||||
|
TenantName: tenantName,
|
||||||
|
DomainID: domainID,
|
||||||
|
DomainName: domainName,
|
||||||
|
}
|
||||||
|
|
||||||
|
return ao, nil
|
||||||
|
}
|
295
vendor/github.com/gophercloud/gophercloud/openstack/client.go
generated
vendored
Normal file
295
vendor/github.com/gophercloud/gophercloud/openstack/client.go
generated
vendored
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
|
||||||
|
tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
|
||||||
|
"github.com/gophercloud/gophercloud/openstack/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
v20 = "v2.0"
|
||||||
|
v30 = "v3.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewClient prepares an unauthenticated ProviderClient instance.
|
||||||
|
// Most users will probably prefer using the AuthenticatedClient function instead.
|
||||||
|
// This is useful if you wish to explicitly control the version of the identity service that's used for authentication explicitly,
|
||||||
|
// for example.
|
||||||
|
func NewClient(endpoint string) (*gophercloud.ProviderClient, error) {
|
||||||
|
u, err := url.Parse(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hadPath := u.Path != ""
|
||||||
|
u.Path, u.RawQuery, u.Fragment = "", "", ""
|
||||||
|
base := u.String()
|
||||||
|
|
||||||
|
endpoint = gophercloud.NormalizeURL(endpoint)
|
||||||
|
base = gophercloud.NormalizeURL(base)
|
||||||
|
|
||||||
|
if hadPath {
|
||||||
|
return &gophercloud.ProviderClient{
|
||||||
|
IdentityBase: base,
|
||||||
|
IdentityEndpoint: endpoint,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &gophercloud.ProviderClient{
|
||||||
|
IdentityBase: base,
|
||||||
|
IdentityEndpoint: "",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthenticatedClient logs in to an OpenStack cloud found at the identity endpoint specified by options, acquires a token, and
|
||||||
|
// returns a Client instance that's ready to operate.
|
||||||
|
// It first queries the root identity endpoint to determine which versions of the identity service are supported, then chooses
|
||||||
|
// the most recent identity service available to proceed.
|
||||||
|
func AuthenticatedClient(options gophercloud.AuthOptions) (*gophercloud.ProviderClient, error) {
|
||||||
|
client, err := NewClient(options.IdentityEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = Authenticate(client, options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authenticate or re-authenticate against the most recent identity service supported at the provided endpoint.
|
||||||
|
func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
|
||||||
|
versions := []*utils.Version{
|
||||||
|
{ID: v20, Priority: 20, Suffix: "/v2.0/"},
|
||||||
|
{ID: v30, Priority: 30, Suffix: "/v3/"},
|
||||||
|
}
|
||||||
|
|
||||||
|
chosen, endpoint, err := utils.ChooseVersion(client, versions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch chosen.ID {
|
||||||
|
case v20:
|
||||||
|
return v2auth(client, endpoint, options, gophercloud.EndpointOpts{})
|
||||||
|
case v30:
|
||||||
|
return v3auth(client, endpoint, &options, gophercloud.EndpointOpts{})
|
||||||
|
default:
|
||||||
|
// The switch statement must be out of date from the versions list.
|
||||||
|
return fmt.Errorf("Unrecognized identity version: %s", chosen.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthenticateV2 explicitly authenticates against the identity v2 endpoint.
|
||||||
|
func AuthenticateV2(client *gophercloud.ProviderClient, options gophercloud.AuthOptions, eo gophercloud.EndpointOpts) error {
|
||||||
|
return v2auth(client, "", options, eo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func v2auth(client *gophercloud.ProviderClient, endpoint string, options gophercloud.AuthOptions, eo gophercloud.EndpointOpts) error {
|
||||||
|
v2Client, err := NewIdentityV2(client, eo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if endpoint != "" {
|
||||||
|
v2Client.Endpoint = endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
v2Opts := tokens2.AuthOptions{
|
||||||
|
IdentityEndpoint: options.IdentityEndpoint,
|
||||||
|
Username: options.Username,
|
||||||
|
Password: options.Password,
|
||||||
|
TenantID: options.TenantID,
|
||||||
|
TenantName: options.TenantName,
|
||||||
|
AllowReauth: options.AllowReauth,
|
||||||
|
TokenID: options.TokenID,
|
||||||
|
}
|
||||||
|
|
||||||
|
result := tokens2.Create(v2Client, v2Opts)
|
||||||
|
|
||||||
|
token, err := result.ExtractToken()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
catalog, err := result.ExtractServiceCatalog()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.AllowReauth {
|
||||||
|
client.ReauthFunc = func() error {
|
||||||
|
client.TokenID = ""
|
||||||
|
return v2auth(client, endpoint, options, eo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
client.TokenID = token.ID
|
||||||
|
client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
|
||||||
|
return V2EndpointURL(catalog, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthenticateV3 explicitly authenticates against the identity v3 service.
|
||||||
|
func AuthenticateV3(client *gophercloud.ProviderClient, options tokens3.AuthOptionsBuilder, eo gophercloud.EndpointOpts) error {
|
||||||
|
return v3auth(client, "", options, eo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.AuthOptionsBuilder, eo gophercloud.EndpointOpts) error {
|
||||||
|
// Override the generated service endpoint with the one returned by the version endpoint.
|
||||||
|
v3Client, err := NewIdentityV3(client, eo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if endpoint != "" {
|
||||||
|
v3Client.Endpoint = endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
result := tokens3.Create(v3Client, opts)
|
||||||
|
|
||||||
|
token, err := result.ExtractToken()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
catalog, err := result.ExtractServiceCatalog()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
client.TokenID = token.ID
|
||||||
|
|
||||||
|
if opts.CanReauth() {
|
||||||
|
client.ReauthFunc = func() error {
|
||||||
|
client.TokenID = ""
|
||||||
|
return v3auth(client, endpoint, opts, eo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
|
||||||
|
return V3EndpointURL(catalog, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIdentityV2 creates a ServiceClient that may be used to interact with the v2 identity service.
|
||||||
|
func NewIdentityV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
endpoint := client.IdentityBase + "v2.0/"
|
||||||
|
clientType := "identity"
|
||||||
|
var err error
|
||||||
|
if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) {
|
||||||
|
eo.ApplyDefaults(clientType)
|
||||||
|
endpoint, err = client.EndpointLocator(eo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &gophercloud.ServiceClient{
|
||||||
|
ProviderClient: client,
|
||||||
|
Endpoint: endpoint,
|
||||||
|
Type: clientType,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIdentityV3 creates a ServiceClient that may be used to access the v3 identity service.
|
||||||
|
func NewIdentityV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
endpoint := client.IdentityBase + "v3/"
|
||||||
|
clientType := "identity"
|
||||||
|
var err error
|
||||||
|
if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) {
|
||||||
|
eo.ApplyDefaults(clientType)
|
||||||
|
endpoint, err = client.EndpointLocator(eo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &gophercloud.ServiceClient{
|
||||||
|
ProviderClient: client,
|
||||||
|
Endpoint: endpoint,
|
||||||
|
Type: clientType,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func initClientOpts(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts, clientType string) (*gophercloud.ServiceClient, error) {
|
||||||
|
sc := new(gophercloud.ServiceClient)
|
||||||
|
eo.ApplyDefaults(clientType)
|
||||||
|
url, err := client.EndpointLocator(eo)
|
||||||
|
if err != nil {
|
||||||
|
return sc, err
|
||||||
|
}
|
||||||
|
sc.ProviderClient = client
|
||||||
|
sc.Endpoint = url
|
||||||
|
sc.Type = clientType
|
||||||
|
return sc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewObjectStorageV1 creates a ServiceClient that may be used with the v1 object storage package.
|
||||||
|
func NewObjectStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
return initClientOpts(client, eo, "object-store")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewComputeV2 creates a ServiceClient that may be used with the v2 compute package.
|
||||||
|
func NewComputeV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
return initClientOpts(client, eo, "compute")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNetworkV2 creates a ServiceClient that may be used with the v2 network package.
|
||||||
|
func NewNetworkV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
sc, err := initClientOpts(client, eo, "network")
|
||||||
|
sc.ResourceBase = sc.Endpoint + "v2.0/"
|
||||||
|
return sc, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBlockStorageV1 creates a ServiceClient that may be used to access the v1 block storage service.
|
||||||
|
func NewBlockStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
return initClientOpts(client, eo, "volume")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBlockStorageV2 creates a ServiceClient that may be used to access the v2 block storage service.
|
||||||
|
func NewBlockStorageV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
return initClientOpts(client, eo, "volumev2")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSharedFileSystemV2 creates a ServiceClient that may be used to access the v2 shared file system service.
|
||||||
|
func NewSharedFileSystemV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
return initClientOpts(client, eo, "sharev2")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCDNV1 creates a ServiceClient that may be used to access the OpenStack v1
|
||||||
|
// CDN service.
|
||||||
|
func NewCDNV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
return initClientOpts(client, eo, "cdn")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewOrchestrationV1 creates a ServiceClient that may be used to access the v1 orchestration service.
|
||||||
|
func NewOrchestrationV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
return initClientOpts(client, eo, "orchestration")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDBV1 creates a ServiceClient that may be used to access the v1 DB service.
|
||||||
|
func NewDBV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
return initClientOpts(client, eo, "database")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDNSV2 creates a ServiceClient that may be used to access the v2 DNS service.
|
||||||
|
func NewDNSV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
sc, err := initClientOpts(client, eo, "dns")
|
||||||
|
sc.ResourceBase = sc.Endpoint + "v2/"
|
||||||
|
return sc, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewImageServiceV2 creates a ServiceClient that may be used to access the v2 image service.
|
||||||
|
func NewImageServiceV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
|
sc, err := initClientOpts(client, eo, "image")
|
||||||
|
sc.ResourceBase = sc.Endpoint + "v2/"
|
||||||
|
return sc, err
|
||||||
|
}
|
99
vendor/github.com/gophercloud/gophercloud/openstack/endpoint_location.go
generated
vendored
Normal file
99
vendor/github.com/gophercloud/gophercloud/openstack/endpoint_location.go
generated
vendored
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
|
||||||
|
tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
|
||||||
|
)
|
||||||
|
|
||||||
|
// V2EndpointURL discovers the endpoint URL for a specific service from a ServiceCatalog acquired
|
||||||
|
// during the v2 identity service. The specified EndpointOpts are used to identify a unique,
|
||||||
|
// unambiguous endpoint to return. It's an error both when multiple endpoints match the provided
|
||||||
|
// criteria and when none do. The minimum that can be specified is a Type, but you will also often
|
||||||
|
// need to specify a Name and/or a Region depending on what's available on your OpenStack
|
||||||
|
// deployment.
|
||||||
|
func V2EndpointURL(catalog *tokens2.ServiceCatalog, opts gophercloud.EndpointOpts) (string, error) {
|
||||||
|
// Extract Endpoints from the catalog entries that match the requested Type, Name if provided, and Region if provided.
|
||||||
|
var endpoints = make([]tokens2.Endpoint, 0, 1)
|
||||||
|
for _, entry := range catalog.Entries {
|
||||||
|
if (entry.Type == opts.Type) && (opts.Name == "" || entry.Name == opts.Name) {
|
||||||
|
for _, endpoint := range entry.Endpoints {
|
||||||
|
if opts.Region == "" || endpoint.Region == opts.Region {
|
||||||
|
endpoints = append(endpoints, endpoint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report an error if the options were ambiguous.
|
||||||
|
if len(endpoints) > 1 {
|
||||||
|
err := &ErrMultipleMatchingEndpointsV2{}
|
||||||
|
err.Endpoints = endpoints
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the appropriate URL from the matching Endpoint.
|
||||||
|
for _, endpoint := range endpoints {
|
||||||
|
switch opts.Availability {
|
||||||
|
case gophercloud.AvailabilityPublic:
|
||||||
|
return gophercloud.NormalizeURL(endpoint.PublicURL), nil
|
||||||
|
case gophercloud.AvailabilityInternal:
|
||||||
|
return gophercloud.NormalizeURL(endpoint.InternalURL), nil
|
||||||
|
case gophercloud.AvailabilityAdmin:
|
||||||
|
return gophercloud.NormalizeURL(endpoint.AdminURL), nil
|
||||||
|
default:
|
||||||
|
err := &ErrInvalidAvailabilityProvided{}
|
||||||
|
err.Argument = "Availability"
|
||||||
|
err.Value = opts.Availability
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report an error if there were no matching endpoints.
|
||||||
|
err := &gophercloud.ErrEndpointNotFound{}
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// V3EndpointURL discovers the endpoint URL for a specific service from a Catalog acquired
|
||||||
|
// during the v3 identity service. The specified EndpointOpts are used to identify a unique,
|
||||||
|
// unambiguous endpoint to return. It's an error both when multiple endpoints match the provided
|
||||||
|
// criteria and when none do. The minimum that can be specified is a Type, but you will also often
|
||||||
|
// need to specify a Name and/or a Region depending on what's available on your OpenStack
|
||||||
|
// deployment.
|
||||||
|
func V3EndpointURL(catalog *tokens3.ServiceCatalog, opts gophercloud.EndpointOpts) (string, error) {
|
||||||
|
// Extract Endpoints from the catalog entries that match the requested Type, Interface,
|
||||||
|
// Name if provided, and Region if provided.
|
||||||
|
var endpoints = make([]tokens3.Endpoint, 0, 1)
|
||||||
|
for _, entry := range catalog.Entries {
|
||||||
|
if (entry.Type == opts.Type) && (opts.Name == "" || entry.Name == opts.Name) {
|
||||||
|
for _, endpoint := range entry.Endpoints {
|
||||||
|
if opts.Availability != gophercloud.AvailabilityAdmin &&
|
||||||
|
opts.Availability != gophercloud.AvailabilityPublic &&
|
||||||
|
opts.Availability != gophercloud.AvailabilityInternal {
|
||||||
|
err := &ErrInvalidAvailabilityProvided{}
|
||||||
|
err.Argument = "Availability"
|
||||||
|
err.Value = opts.Availability
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if (opts.Availability == gophercloud.Availability(endpoint.Interface)) &&
|
||||||
|
(opts.Region == "" || endpoint.Region == opts.Region) {
|
||||||
|
endpoints = append(endpoints, endpoint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report an error if the options were ambiguous.
|
||||||
|
if len(endpoints) > 1 {
|
||||||
|
return "", ErrMultipleMatchingEndpointsV3{Endpoints: endpoints}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the URL from the matching Endpoint.
|
||||||
|
for _, endpoint := range endpoints {
|
||||||
|
return gophercloud.NormalizeURL(endpoint.URL), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report an error if there were no matching endpoints.
|
||||||
|
err := &gophercloud.ErrEndpointNotFound{}
|
||||||
|
return "", err
|
||||||
|
}
|
71
vendor/github.com/gophercloud/gophercloud/openstack/errors.go
generated
vendored
Normal file
71
vendor/github.com/gophercloud/gophercloud/openstack/errors.go
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
|
||||||
|
tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrEndpointNotFound is the error when no suitable endpoint can be found
|
||||||
|
// in the user's catalog
|
||||||
|
type ErrEndpointNotFound struct{ gophercloud.BaseError }
|
||||||
|
|
||||||
|
func (e ErrEndpointNotFound) Error() string {
|
||||||
|
return "No suitable endpoint could be found in the service catalog."
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrInvalidAvailabilityProvided is the error when an invalid endpoint
|
||||||
|
// availability is provided
|
||||||
|
type ErrInvalidAvailabilityProvided struct{ gophercloud.ErrInvalidInput }
|
||||||
|
|
||||||
|
func (e ErrInvalidAvailabilityProvided) Error() string {
|
||||||
|
return fmt.Sprintf("Unexpected availability in endpoint query: %s", e.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrMultipleMatchingEndpointsV2 is the error when more than one endpoint
|
||||||
|
// for the given options is found in the v2 catalog
|
||||||
|
type ErrMultipleMatchingEndpointsV2 struct {
|
||||||
|
gophercloud.BaseError
|
||||||
|
Endpoints []tokens2.Endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrMultipleMatchingEndpointsV2) Error() string {
|
||||||
|
return fmt.Sprintf("Discovered %d matching endpoints: %#v", len(e.Endpoints), e.Endpoints)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrMultipleMatchingEndpointsV3 is the error when more than one endpoint
|
||||||
|
// for the given options is found in the v3 catalog
|
||||||
|
type ErrMultipleMatchingEndpointsV3 struct {
|
||||||
|
gophercloud.BaseError
|
||||||
|
Endpoints []tokens3.Endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrMultipleMatchingEndpointsV3) Error() string {
|
||||||
|
return fmt.Sprintf("Discovered %d matching endpoints: %#v", len(e.Endpoints), e.Endpoints)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrNoAuthURL is the error when the OS_AUTH_URL environment variable is not
|
||||||
|
// found
|
||||||
|
type ErrNoAuthURL struct{ gophercloud.ErrInvalidInput }
|
||||||
|
|
||||||
|
func (e ErrNoAuthURL) Error() string {
|
||||||
|
return "Environment variable OS_AUTH_URL needs to be set."
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrNoUsername is the error when the OS_USERNAME environment variable is not
|
||||||
|
// found
|
||||||
|
type ErrNoUsername struct{ gophercloud.ErrInvalidInput }
|
||||||
|
|
||||||
|
func (e ErrNoUsername) Error() string {
|
||||||
|
return "Environment variable OS_USERNAME needs to be set."
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrNoPassword is the error when the OS_PASSWORD environment variable is not
|
||||||
|
// found
|
||||||
|
type ErrNoPassword struct{ gophercloud.ErrInvalidInput }
|
||||||
|
|
||||||
|
func (e ErrNoPassword) Error() string {
|
||||||
|
return "Environment variable OS_PASSWORD needs to be set."
|
||||||
|
}
|
7
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/doc.go
generated
vendored
Normal file
7
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/doc.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// Package tenants provides information and interaction with the
|
||||||
|
// tenants API resource for the OpenStack Identity service.
|
||||||
|
//
|
||||||
|
// See http://developer.openstack.org/api-ref-identity-v2.html#identity-auth-v2
|
||||||
|
// and http://developer.openstack.org/api-ref-identity-v2.html#admin-tenants
|
||||||
|
// for more information.
|
||||||
|
package tenants
|
29
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/requests.go
generated
vendored
Normal file
29
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/requests.go
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package tenants
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
"github.com/gophercloud/gophercloud/pagination"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ListOpts filters the Tenants that are returned by the List call.
|
||||||
|
type ListOpts struct {
|
||||||
|
// Marker is the ID of the last Tenant on the previous page.
|
||||||
|
Marker string `q:"marker"`
|
||||||
|
// Limit specifies the page size.
|
||||||
|
Limit int `q:"limit"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// List enumerates the Tenants to which the current token has access.
|
||||||
|
func List(client *gophercloud.ServiceClient, opts *ListOpts) pagination.Pager {
|
||||||
|
url := listURL(client)
|
||||||
|
if opts != nil {
|
||||||
|
q, err := gophercloud.BuildQueryString(opts)
|
||||||
|
if err != nil {
|
||||||
|
return pagination.Pager{Err: err}
|
||||||
|
}
|
||||||
|
url += q.String()
|
||||||
|
}
|
||||||
|
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
|
||||||
|
return TenantPage{pagination.LinkedPageBase{PageResult: r}}
|
||||||
|
})
|
||||||
|
}
|
53
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/results.go
generated
vendored
Normal file
53
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/results.go
generated
vendored
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package tenants
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
"github.com/gophercloud/gophercloud/pagination"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tenant is a grouping of users in the identity service.
|
||||||
|
type Tenant struct {
|
||||||
|
// ID is a unique identifier for this tenant.
|
||||||
|
ID string `json:"id"`
|
||||||
|
|
||||||
|
// Name is a friendlier user-facing name for this tenant.
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
// Description is a human-readable explanation of this Tenant's purpose.
|
||||||
|
Description string `json:"description"`
|
||||||
|
|
||||||
|
// Enabled indicates whether or not a tenant is active.
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TenantPage is a single page of Tenant results.
|
||||||
|
type TenantPage struct {
|
||||||
|
pagination.LinkedPageBase
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpty determines whether or not a page of Tenants contains any results.
|
||||||
|
func (r TenantPage) IsEmpty() (bool, error) {
|
||||||
|
tenants, err := ExtractTenants(r)
|
||||||
|
return len(tenants) == 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextPageURL extracts the "next" link from the tenants_links section of the result.
|
||||||
|
func (r TenantPage) NextPageURL() (string, error) {
|
||||||
|
var s struct {
|
||||||
|
Links []gophercloud.Link `json:"tenants_links"`
|
||||||
|
}
|
||||||
|
err := r.ExtractInto(&s)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return gophercloud.ExtractNextURL(s.Links)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractTenants returns a slice of Tenants contained in a single page of results.
|
||||||
|
func ExtractTenants(r pagination.Page) ([]Tenant, error) {
|
||||||
|
var s struct {
|
||||||
|
Tenants []Tenant `json:"tenants"`
|
||||||
|
}
|
||||||
|
err := (r.(TenantPage)).ExtractInto(&s)
|
||||||
|
return s.Tenants, err
|
||||||
|
}
|
7
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/urls.go
generated
vendored
Normal file
7
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/urls.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package tenants
|
||||||
|
|
||||||
|
import "github.com/gophercloud/gophercloud"
|
||||||
|
|
||||||
|
func listURL(client *gophercloud.ServiceClient) string {
|
||||||
|
return client.ServiceURL("tenants")
|
||||||
|
}
|
5
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/doc.go
generated
vendored
Normal file
5
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/doc.go
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
// Package tokens provides information and interaction with the token API
|
||||||
|
// resource for the OpenStack Identity service.
|
||||||
|
// For more information, see:
|
||||||
|
// http://developer.openstack.org/api-ref-identity-v2.html#identity-auth-v2
|
||||||
|
package tokens
|
99
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/requests.go
generated
vendored
Normal file
99
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/requests.go
generated
vendored
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
package tokens
|
||||||
|
|
||||||
|
import "github.com/gophercloud/gophercloud"
|
||||||
|
|
||||||
|
type PasswordCredentialsV2 struct {
|
||||||
|
Username string `json:"username" required:"true"`
|
||||||
|
Password string `json:"password" required:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TokenCredentialsV2 struct {
|
||||||
|
ID string `json:"id,omitempty" required:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthOptionsV2 wraps a gophercloud AuthOptions in order to adhere to the AuthOptionsBuilder
|
||||||
|
// interface.
|
||||||
|
type AuthOptionsV2 struct {
|
||||||
|
PasswordCredentials *PasswordCredentialsV2 `json:"passwordCredentials,omitempty" xor:"TokenCredentials"`
|
||||||
|
|
||||||
|
// The TenantID and TenantName fields are optional for the Identity V2 API.
|
||||||
|
// Some providers allow you to specify a TenantName instead of the TenantId.
|
||||||
|
// Some require both. Your provider's authentication policies will determine
|
||||||
|
// how these fields influence authentication.
|
||||||
|
TenantID string `json:"tenantId,omitempty"`
|
||||||
|
TenantName string `json:"tenantName,omitempty"`
|
||||||
|
|
||||||
|
// TokenCredentials allows users to authenticate (possibly as another user) with an
|
||||||
|
// authentication token ID.
|
||||||
|
TokenCredentials *TokenCredentialsV2 `json:"token,omitempty" xor:"PasswordCredentials"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthOptionsBuilder describes any argument that may be passed to the Create call.
|
||||||
|
type AuthOptionsBuilder interface {
|
||||||
|
// ToTokenCreateMap assembles the Create request body, returning an error if parameters are
|
||||||
|
// missing or inconsistent.
|
||||||
|
ToTokenV2CreateMap() (map[string]interface{}, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthOptions are the valid options for Openstack Identity v2 authentication.
|
||||||
|
// For field descriptions, see gophercloud.AuthOptions.
|
||||||
|
type AuthOptions struct {
|
||||||
|
IdentityEndpoint string `json:"-"`
|
||||||
|
Username string `json:"username,omitempty"`
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
TenantID string `json:"tenantId,omitempty"`
|
||||||
|
TenantName string `json:"tenantName,omitempty"`
|
||||||
|
AllowReauth bool `json:"-"`
|
||||||
|
TokenID string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToTokenV2CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder
|
||||||
|
// interface in the v2 tokens package
|
||||||
|
func (opts AuthOptions) ToTokenV2CreateMap() (map[string]interface{}, error) {
|
||||||
|
v2Opts := AuthOptionsV2{
|
||||||
|
TenantID: opts.TenantID,
|
||||||
|
TenantName: opts.TenantName,
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Password != "" {
|
||||||
|
v2Opts.PasswordCredentials = &PasswordCredentialsV2{
|
||||||
|
Username: opts.Username,
|
||||||
|
Password: opts.Password,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
v2Opts.TokenCredentials = &TokenCredentialsV2{
|
||||||
|
ID: opts.TokenID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := gophercloud.BuildRequestBody(v2Opts, "auth")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create authenticates to the identity service and attempts to acquire a Token.
|
||||||
|
// If successful, the CreateResult
|
||||||
|
// Generally, rather than interact with this call directly, end users should call openstack.AuthenticatedClient(),
|
||||||
|
// which abstracts all of the gory details about navigating service catalogs and such.
|
||||||
|
func Create(client *gophercloud.ServiceClient, auth AuthOptionsBuilder) (r CreateResult) {
|
||||||
|
b, err := auth.ToTokenV2CreateMap()
|
||||||
|
if err != nil {
|
||||||
|
r.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, r.Err = client.Post(CreateURL(client), b, &r.Body, &gophercloud.RequestOpts{
|
||||||
|
OkCodes: []int{200, 203},
|
||||||
|
MoreHeaders: map[string]string{"X-Auth-Token": ""},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get validates and retrieves information for user's token.
|
||||||
|
func Get(client *gophercloud.ServiceClient, token string) (r GetResult) {
|
||||||
|
_, r.Err = client.Get(GetURL(client, token), &r.Body, &gophercloud.RequestOpts{
|
||||||
|
OkCodes: []int{200, 203},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
144
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/results.go
generated
vendored
Normal file
144
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/results.go
generated
vendored
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
13
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/urls.go
generated
vendored
Normal file
13
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/urls.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package tokens
|
||||||
|
|
||||||
|
import "github.com/gophercloud/gophercloud"
|
||||||
|
|
||||||
|
// CreateURL generates the URL used to create new Tokens.
|
||||||
|
func CreateURL(client *gophercloud.ServiceClient) string {
|
||||||
|
return client.ServiceURL("tokens")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetURL generates the URL used to Validate Tokens.
|
||||||
|
func GetURL(client *gophercloud.ServiceClient, token string) string {
|
||||||
|
return client.ServiceURL("tokens", token)
|
||||||
|
}
|
6
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/doc.go
generated
vendored
Normal file
6
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/doc.go
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
// Package tokens provides information and interaction with the token API
|
||||||
|
// resource for the OpenStack Identity service.
|
||||||
|
//
|
||||||
|
// For more information, see:
|
||||||
|
// http://developer.openstack.org/api-ref-identity-v3.html#tokens-v3
|
||||||
|
package tokens
|
200
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go
generated
vendored
Normal file
200
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go
generated
vendored
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
package tokens
|
||||||
|
|
||||||
|
import "github.com/gophercloud/gophercloud"
|
||||||
|
|
||||||
|
// Scope allows a created token to be limited to a specific domain or project.
|
||||||
|
type Scope struct {
|
||||||
|
ProjectID string
|
||||||
|
ProjectName string
|
||||||
|
DomainID string
|
||||||
|
DomainName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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:"-"`
|
||||||
|
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{200, 204, 400, 401, 403, 404},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.StatusCode == 200 || 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
|
||||||
|
}
|
103
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/results.go
generated
vendored
Normal file
103
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/results.go
generated
vendored
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
package tokens
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"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 `json:"catalog"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 Token
|
||||||
|
err := r.ExtractInto(&s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the token itself from the stored headers.
|
||||||
|
s.ID = r.Header.Get("X-Subject-Token")
|
||||||
|
|
||||||
|
return &s, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractServiceCatalog returns the ServiceCatalog that was generated along with the user's Token.
|
||||||
|
func (r CreateResult) ExtractServiceCatalog() (*ServiceCatalog, error) {
|
||||||
|
var s ServiceCatalog
|
||||||
|
err := r.ExtractInto(&s)
|
||||||
|
return &s, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 time.Time `json:"expires_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r commonResult) ExtractInto(v interface{}) error {
|
||||||
|
return r.ExtractIntoStructPtr(v, "token")
|
||||||
|
}
|
7
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/urls.go
generated
vendored
Normal file
7
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/urls.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package tokens
|
||||||
|
|
||||||
|
import "github.com/gophercloud/gophercloud"
|
||||||
|
|
||||||
|
func tokenURL(c *gophercloud.ServiceClient) string {
|
||||||
|
return c.ServiceURL("auth", "tokens")
|
||||||
|
}
|
114
vendor/github.com/gophercloud/gophercloud/openstack/utils/choose_version.go
generated
vendored
Normal file
114
vendor/github.com/gophercloud/gophercloud/openstack/utils/choose_version.go
generated
vendored
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Version is a supported API version, corresponding to a vN package within the appropriate service.
|
||||||
|
type Version struct {
|
||||||
|
ID string
|
||||||
|
Suffix string
|
||||||
|
Priority int
|
||||||
|
}
|
||||||
|
|
||||||
|
var goodStatus = map[string]bool{
|
||||||
|
"current": true,
|
||||||
|
"supported": true,
|
||||||
|
"stable": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChooseVersion queries the base endpoint of an API to choose the most recent non-experimental alternative from a service's
|
||||||
|
// published versions.
|
||||||
|
// It returns the highest-Priority Version among the alternatives that are provided, as well as its corresponding endpoint.
|
||||||
|
func ChooseVersion(client *gophercloud.ProviderClient, recognized []*Version) (*Version, string, error) {
|
||||||
|
type linkResp struct {
|
||||||
|
Href string `json:"href"`
|
||||||
|
Rel string `json:"rel"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type valueResp struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Links []linkResp `json:"links"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type versionsResp struct {
|
||||||
|
Values []valueResp `json:"values"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type response struct {
|
||||||
|
Versions versionsResp `json:"versions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
normalize := func(endpoint string) string {
|
||||||
|
if !strings.HasSuffix(endpoint, "/") {
|
||||||
|
return endpoint + "/"
|
||||||
|
}
|
||||||
|
return endpoint
|
||||||
|
}
|
||||||
|
identityEndpoint := normalize(client.IdentityEndpoint)
|
||||||
|
|
||||||
|
// If a full endpoint is specified, check version suffixes for a match first.
|
||||||
|
for _, v := range recognized {
|
||||||
|
if strings.HasSuffix(identityEndpoint, v.Suffix) {
|
||||||
|
return v, identityEndpoint, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp response
|
||||||
|
_, err := client.Request("GET", client.IdentityBase, &gophercloud.RequestOpts{
|
||||||
|
JSONResponse: &resp,
|
||||||
|
OkCodes: []int{200, 300},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
byID := make(map[string]*Version)
|
||||||
|
for _, version := range recognized {
|
||||||
|
byID[version.ID] = version
|
||||||
|
}
|
||||||
|
|
||||||
|
var highest *Version
|
||||||
|
var endpoint string
|
||||||
|
|
||||||
|
for _, value := range resp.Versions.Values {
|
||||||
|
href := ""
|
||||||
|
for _, link := range value.Links {
|
||||||
|
if link.Rel == "self" {
|
||||||
|
href = normalize(link.Href)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if matching, ok := byID[value.ID]; ok {
|
||||||
|
// Prefer a version that exactly matches the provided endpoint.
|
||||||
|
if href == identityEndpoint {
|
||||||
|
if href == "" {
|
||||||
|
return nil, "", fmt.Errorf("Endpoint missing in version %s response from %s", value.ID, client.IdentityBase)
|
||||||
|
}
|
||||||
|
return matching, href, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, find the highest-priority version with a whitelisted status.
|
||||||
|
if goodStatus[strings.ToLower(value.Status)] {
|
||||||
|
if highest == nil || matching.Priority > highest.Priority {
|
||||||
|
highest = matching
|
||||||
|
endpoint = href
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if highest == nil {
|
||||||
|
return nil, "", fmt.Errorf("No supported version available from endpoint %s", client.IdentityBase)
|
||||||
|
}
|
||||||
|
if endpoint == "" {
|
||||||
|
return nil, "", fmt.Errorf("Endpoint missing in version %s response from %s", highest.ID, client.IdentityBase)
|
||||||
|
}
|
||||||
|
|
||||||
|
return highest, endpoint, nil
|
||||||
|
}
|
60
vendor/github.com/gophercloud/gophercloud/pagination/http.go
generated
vendored
Normal file
60
vendor/github.com/gophercloud/gophercloud/pagination/http.go
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package pagination
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PageResult stores the HTTP response that returned the current page of results.
|
||||||
|
type PageResult struct {
|
||||||
|
gophercloud.Result
|
||||||
|
url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
// PageResultFrom parses an HTTP response as JSON and returns a PageResult containing the
|
||||||
|
// results, interpreting it as JSON if the content type indicates.
|
||||||
|
func PageResultFrom(resp *http.Response) (PageResult, error) {
|
||||||
|
var parsedBody interface{}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
rawBody, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return PageResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(resp.Header.Get("Content-Type"), "application/json") {
|
||||||
|
err = json.Unmarshal(rawBody, &parsedBody)
|
||||||
|
if err != nil {
|
||||||
|
return PageResult{}, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parsedBody = rawBody
|
||||||
|
}
|
||||||
|
|
||||||
|
return PageResultFromParsed(resp, parsedBody), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// PageResultFromParsed constructs a PageResult from an HTTP response that has already had its
|
||||||
|
// body parsed as JSON (and closed).
|
||||||
|
func PageResultFromParsed(resp *http.Response, body interface{}) PageResult {
|
||||||
|
return PageResult{
|
||||||
|
Result: gophercloud.Result{
|
||||||
|
Body: body,
|
||||||
|
Header: resp.Header,
|
||||||
|
},
|
||||||
|
URL: *resp.Request.URL,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request performs an HTTP request and extracts the http.Response from the result.
|
||||||
|
func Request(client *gophercloud.ServiceClient, headers map[string]string, url string) (*http.Response, error) {
|
||||||
|
return client.Get(url, nil, &gophercloud.RequestOpts{
|
||||||
|
MoreHeaders: headers,
|
||||||
|
OkCodes: []int{200, 204, 300},
|
||||||
|
})
|
||||||
|
}
|
92
vendor/github.com/gophercloud/gophercloud/pagination/linked.go
generated
vendored
Normal file
92
vendor/github.com/gophercloud/gophercloud/pagination/linked.go
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
package pagination
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LinkedPageBase may be embedded to implement a page that provides navigational "Next" and "Previous" links within its result.
|
||||||
|
type LinkedPageBase struct {
|
||||||
|
PageResult
|
||||||
|
|
||||||
|
// LinkPath lists the keys that should be traversed within a response to arrive at the "next" pointer.
|
||||||
|
// If any link along the path is missing, an empty URL will be returned.
|
||||||
|
// If any link results in an unexpected value type, an error will be returned.
|
||||||
|
// When left as "nil", []string{"links", "next"} will be used as a default.
|
||||||
|
LinkPath []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextPageURL extracts the pagination structure from a JSON response and returns the "next" link, if one is present.
|
||||||
|
// It assumes that the links are available in a "links" element of the top-level response object.
|
||||||
|
// If this is not the case, override NextPageURL on your result type.
|
||||||
|
func (current LinkedPageBase) NextPageURL() (string, error) {
|
||||||
|
var path []string
|
||||||
|
var key string
|
||||||
|
|
||||||
|
if current.LinkPath == nil {
|
||||||
|
path = []string{"links", "next"}
|
||||||
|
} else {
|
||||||
|
path = current.LinkPath
|
||||||
|
}
|
||||||
|
|
||||||
|
submap, ok := current.Body.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
err := gophercloud.ErrUnexpectedType{}
|
||||||
|
err.Expected = "map[string]interface{}"
|
||||||
|
err.Actual = fmt.Sprintf("%v", reflect.TypeOf(current.Body))
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
key, path = path[0], path[1:len(path)]
|
||||||
|
|
||||||
|
value, ok := submap[key]
|
||||||
|
if !ok {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(path) > 0 {
|
||||||
|
submap, ok = value.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
err := gophercloud.ErrUnexpectedType{}
|
||||||
|
err.Expected = "map[string]interface{}"
|
||||||
|
err.Actual = fmt.Sprintf("%v", reflect.TypeOf(value))
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if value == nil {
|
||||||
|
// Actual null element.
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
url, ok := value.(string)
|
||||||
|
if !ok {
|
||||||
|
err := gophercloud.ErrUnexpectedType{}
|
||||||
|
err.Expected = "string"
|
||||||
|
err.Actual = fmt.Sprintf("%v", reflect.TypeOf(value))
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return url, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpty satisifies the IsEmpty method of the Page interface
|
||||||
|
func (current LinkedPageBase) IsEmpty() (bool, error) {
|
||||||
|
if b, ok := current.Body.([]interface{}); ok {
|
||||||
|
return len(b) == 0, nil
|
||||||
|
}
|
||||||
|
err := gophercloud.ErrUnexpectedType{}
|
||||||
|
err.Expected = "[]interface{}"
|
||||||
|
err.Actual = fmt.Sprintf("%v", reflect.TypeOf(current.Body))
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBody returns the linked page's body. This method is needed to satisfy the
|
||||||
|
// Page interface.
|
||||||
|
func (current LinkedPageBase) GetBody() interface{} {
|
||||||
|
return current.Body
|
||||||
|
}
|
58
vendor/github.com/gophercloud/gophercloud/pagination/marker.go
generated
vendored
Normal file
58
vendor/github.com/gophercloud/gophercloud/pagination/marker.go
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package pagination
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MarkerPage is a stricter Page interface that describes additional functionality required for use with NewMarkerPager.
|
||||||
|
// For convenience, embed the MarkedPageBase struct.
|
||||||
|
type MarkerPage interface {
|
||||||
|
Page
|
||||||
|
|
||||||
|
// LastMarker returns the last "marker" value on this page.
|
||||||
|
LastMarker() (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkerPageBase is a page in a collection that's paginated by "limit" and "marker" query parameters.
|
||||||
|
type MarkerPageBase struct {
|
||||||
|
PageResult
|
||||||
|
|
||||||
|
// Owner is a reference to the embedding struct.
|
||||||
|
Owner MarkerPage
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextPageURL generates the URL for the page of results after this one.
|
||||||
|
func (current MarkerPageBase) NextPageURL() (string, error) {
|
||||||
|
currentURL := current.URL
|
||||||
|
|
||||||
|
mark, err := current.Owner.LastMarker()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
q := currentURL.Query()
|
||||||
|
q.Set("marker", mark)
|
||||||
|
currentURL.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
return currentURL.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpty satisifies the IsEmpty method of the Page interface
|
||||||
|
func (current MarkerPageBase) IsEmpty() (bool, error) {
|
||||||
|
if b, ok := current.Body.([]interface{}); ok {
|
||||||
|
return len(b) == 0, nil
|
||||||
|
}
|
||||||
|
err := gophercloud.ErrUnexpectedType{}
|
||||||
|
err.Expected = "[]interface{}"
|
||||||
|
err.Actual = fmt.Sprintf("%v", reflect.TypeOf(current.Body))
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBody returns the linked page's body. This method is needed to satisfy the
|
||||||
|
// Page interface.
|
||||||
|
func (current MarkerPageBase) GetBody() interface{} {
|
||||||
|
return current.Body
|
||||||
|
}
|
238
vendor/github.com/gophercloud/gophercloud/pagination/pager.go
generated
vendored
Normal file
238
vendor/github.com/gophercloud/gophercloud/pagination/pager.go
generated
vendored
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
package pagination
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrPageNotAvailable is returned from a Pager when a next or previous page is requested, but does not exist.
|
||||||
|
ErrPageNotAvailable = errors.New("The requested page does not exist.")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Page must be satisfied by the result type of any resource collection.
|
||||||
|
// It allows clients to interact with the resource uniformly, regardless of whether or not or how it's paginated.
|
||||||
|
// Generally, rather than implementing this interface directly, implementors should embed one of the concrete PageBase structs,
|
||||||
|
// instead.
|
||||||
|
// Depending on the pagination strategy of a particular resource, there may be an additional subinterface that the result type
|
||||||
|
// will need to implement.
|
||||||
|
type Page interface {
|
||||||
|
|
||||||
|
// NextPageURL generates the URL for the page of data that follows this collection.
|
||||||
|
// Return "" if no such page exists.
|
||||||
|
NextPageURL() (string, error)
|
||||||
|
|
||||||
|
// IsEmpty returns true if this Page has no items in it.
|
||||||
|
IsEmpty() (bool, error)
|
||||||
|
|
||||||
|
// GetBody returns the Page Body. This is used in the `AllPages` method.
|
||||||
|
GetBody() interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pager knows how to advance through a specific resource collection, one page at a time.
|
||||||
|
type Pager struct {
|
||||||
|
client *gophercloud.ServiceClient
|
||||||
|
|
||||||
|
initialURL string
|
||||||
|
|
||||||
|
createPage func(r PageResult) Page
|
||||||
|
|
||||||
|
Err error
|
||||||
|
|
||||||
|
// Headers supplies additional HTTP headers to populate on each paged request.
|
||||||
|
Headers map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPager constructs a manually-configured pager.
|
||||||
|
// Supply the URL for the first page, a function that requests a specific page given a URL, and a function that counts a page.
|
||||||
|
func NewPager(client *gophercloud.ServiceClient, initialURL string, createPage func(r PageResult) Page) Pager {
|
||||||
|
return Pager{
|
||||||
|
client: client,
|
||||||
|
initialURL: initialURL,
|
||||||
|
createPage: createPage,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithPageCreator returns a new Pager that substitutes a different page creation function. This is
|
||||||
|
// useful for overriding List functions in delegation.
|
||||||
|
func (p Pager) WithPageCreator(createPage func(r PageResult) Page) Pager {
|
||||||
|
return Pager{
|
||||||
|
client: p.client,
|
||||||
|
initialURL: p.initialURL,
|
||||||
|
createPage: createPage,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Pager) fetchNextPage(url string) (Page, error) {
|
||||||
|
resp, err := Request(p.client, p.Headers, url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
remembered, err := PageResultFrom(resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.createPage(remembered), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EachPage iterates over each page returned by a Pager, yielding one at a time to a handler function.
|
||||||
|
// Return "false" from the handler to prematurely stop iterating.
|
||||||
|
func (p Pager) EachPage(handler func(Page) (bool, error)) error {
|
||||||
|
if p.Err != nil {
|
||||||
|
return p.Err
|
||||||
|
}
|
||||||
|
currentURL := p.initialURL
|
||||||
|
for {
|
||||||
|
currentPage, err := p.fetchNextPage(currentURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
empty, err := currentPage.IsEmpty()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if empty {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, err := handler(currentPage)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
currentURL, err = currentPage.NextPageURL()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if currentURL == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllPages returns all the pages from a `List` operation in a single page,
|
||||||
|
// allowing the user to retrieve all the pages at once.
|
||||||
|
func (p Pager) AllPages() (Page, error) {
|
||||||
|
// pagesSlice holds all the pages until they get converted into as Page Body.
|
||||||
|
var pagesSlice []interface{}
|
||||||
|
// body will contain the final concatenated Page body.
|
||||||
|
var body reflect.Value
|
||||||
|
|
||||||
|
// Grab a test page to ascertain the page body type.
|
||||||
|
testPage, err := p.fetchNextPage(p.initialURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Store the page type so we can use reflection to create a new mega-page of
|
||||||
|
// that type.
|
||||||
|
pageType := reflect.TypeOf(testPage)
|
||||||
|
|
||||||
|
// if it's a single page, just return the testPage (first page)
|
||||||
|
if _, found := pageType.FieldByName("SinglePageBase"); found {
|
||||||
|
return testPage, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switch on the page body type. Recognized types are `map[string]interface{}`,
|
||||||
|
// `[]byte`, and `[]interface{}`.
|
||||||
|
switch pb := testPage.GetBody().(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
// key is the map key for the page body if the body type is `map[string]interface{}`.
|
||||||
|
var key string
|
||||||
|
// Iterate over the pages to concatenate the bodies.
|
||||||
|
err = p.EachPage(func(page Page) (bool, error) {
|
||||||
|
b := page.GetBody().(map[string]interface{})
|
||||||
|
for k, v := range b {
|
||||||
|
// If it's a linked page, we don't want the `links`, we want the other one.
|
||||||
|
if !strings.HasSuffix(k, "links") {
|
||||||
|
// check the field's type. we only want []interface{} (which is really []map[string]interface{})
|
||||||
|
switch vt := v.(type) {
|
||||||
|
case []interface{}:
|
||||||
|
key = k
|
||||||
|
pagesSlice = append(pagesSlice, vt...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Set body to value of type `map[string]interface{}`
|
||||||
|
body = reflect.MakeMap(reflect.MapOf(reflect.TypeOf(key), reflect.TypeOf(pagesSlice)))
|
||||||
|
body.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(pagesSlice))
|
||||||
|
case []byte:
|
||||||
|
// Iterate over the pages to concatenate the bodies.
|
||||||
|
err = p.EachPage(func(page Page) (bool, error) {
|
||||||
|
b := page.GetBody().([]byte)
|
||||||
|
pagesSlice = append(pagesSlice, b)
|
||||||
|
// seperate pages with a comma
|
||||||
|
pagesSlice = append(pagesSlice, []byte{10})
|
||||||
|
return true, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(pagesSlice) > 0 {
|
||||||
|
// Remove the trailing comma.
|
||||||
|
pagesSlice = pagesSlice[:len(pagesSlice)-1]
|
||||||
|
}
|
||||||
|
var b []byte
|
||||||
|
// Combine the slice of slices in to a single slice.
|
||||||
|
for _, slice := range pagesSlice {
|
||||||
|
b = append(b, slice.([]byte)...)
|
||||||
|
}
|
||||||
|
// Set body to value of type `bytes`.
|
||||||
|
body = reflect.New(reflect.TypeOf(b)).Elem()
|
||||||
|
body.SetBytes(b)
|
||||||
|
case []interface{}:
|
||||||
|
// Iterate over the pages to concatenate the bodies.
|
||||||
|
err = p.EachPage(func(page Page) (bool, error) {
|
||||||
|
b := page.GetBody().([]interface{})
|
||||||
|
pagesSlice = append(pagesSlice, b...)
|
||||||
|
return true, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Set body to value of type `[]interface{}`
|
||||||
|
body = reflect.MakeSlice(reflect.TypeOf(pagesSlice), len(pagesSlice), len(pagesSlice))
|
||||||
|
for i, s := range pagesSlice {
|
||||||
|
body.Index(i).Set(reflect.ValueOf(s))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err := gophercloud.ErrUnexpectedType{}
|
||||||
|
err.Expected = "map[string]interface{}/[]byte/[]interface{}"
|
||||||
|
err.Actual = fmt.Sprintf("%T", pb)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each `Extract*` function is expecting a specific type of page coming back,
|
||||||
|
// otherwise the type assertion in those functions will fail. pageType is needed
|
||||||
|
// to create a type in this method that has the same type that the `Extract*`
|
||||||
|
// function is expecting and set the Body of that object to the concatenated
|
||||||
|
// pages.
|
||||||
|
page := reflect.New(pageType)
|
||||||
|
// Set the page body to be the concatenated pages.
|
||||||
|
page.Elem().FieldByName("Body").Set(body)
|
||||||
|
// Set any additional headers that were pass along. The `objectstorage` pacakge,
|
||||||
|
// for example, passes a Content-Type header.
|
||||||
|
h := make(http.Header)
|
||||||
|
for k, v := range p.Headers {
|
||||||
|
h.Add(k, v)
|
||||||
|
}
|
||||||
|
page.Elem().FieldByName("Header").Set(reflect.ValueOf(h))
|
||||||
|
// Type assert the page to a Page interface so that the type assertion in the
|
||||||
|
// `Extract*` methods will work.
|
||||||
|
return page.Elem().Interface().(Page), err
|
||||||
|
}
|
4
vendor/github.com/gophercloud/gophercloud/pagination/pkg.go
generated
vendored
Normal file
4
vendor/github.com/gophercloud/gophercloud/pagination/pkg.go
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/*
|
||||||
|
Package pagination contains utilities and convenience structs that implement common pagination idioms within OpenStack APIs.
|
||||||
|
*/
|
||||||
|
package pagination
|
33
vendor/github.com/gophercloud/gophercloud/pagination/single.go
generated
vendored
Normal file
33
vendor/github.com/gophercloud/gophercloud/pagination/single.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package pagination
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SinglePageBase may be embedded in a Page that contains all of the results from an operation at once.
|
||||||
|
type SinglePageBase PageResult
|
||||||
|
|
||||||
|
// NextPageURL always returns "" to indicate that there are no more pages to return.
|
||||||
|
func (current SinglePageBase) NextPageURL() (string, error) {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpty satisifies the IsEmpty method of the Page interface
|
||||||
|
func (current SinglePageBase) IsEmpty() (bool, error) {
|
||||||
|
if b, ok := current.Body.([]interface{}); ok {
|
||||||
|
return len(b) == 0, nil
|
||||||
|
}
|
||||||
|
err := gophercloud.ErrUnexpectedType{}
|
||||||
|
err.Expected = "[]interface{}"
|
||||||
|
err.Actual = fmt.Sprintf("%v", reflect.TypeOf(current.Body))
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBody returns the single page's body. This method is needed to satisfy the
|
||||||
|
// Page interface.
|
||||||
|
func (current SinglePageBase) GetBody() interface{} {
|
||||||
|
return current.Body
|
||||||
|
}
|
445
vendor/github.com/gophercloud/gophercloud/params.go
generated
vendored
Normal file
445
vendor/github.com/gophercloud/gophercloud/params.go
generated
vendored
Normal file
@@ -0,0 +1,445 @@
|
|||||||
|
package gophercloud
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BuildRequestBody builds a map[string]interface from the given `struct`. If
|
||||||
|
// parent is not the empty string, the final map[string]interface returned will
|
||||||
|
// encapsulate the built one
|
||||||
|
//
|
||||||
|
func BuildRequestBody(opts interface{}, parent string) (map[string]interface{}, error) {
|
||||||
|
optsValue := reflect.ValueOf(opts)
|
||||||
|
if optsValue.Kind() == reflect.Ptr {
|
||||||
|
optsValue = optsValue.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
optsType := reflect.TypeOf(opts)
|
||||||
|
if optsType.Kind() == reflect.Ptr {
|
||||||
|
optsType = optsType.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
optsMap := make(map[string]interface{})
|
||||||
|
if optsValue.Kind() == reflect.Struct {
|
||||||
|
//fmt.Printf("optsValue.Kind() is a reflect.Struct: %+v\n", optsValue.Kind())
|
||||||
|
for i := 0; i < optsValue.NumField(); i++ {
|
||||||
|
v := optsValue.Field(i)
|
||||||
|
f := optsType.Field(i)
|
||||||
|
|
||||||
|
if f.Name != strings.Title(f.Name) {
|
||||||
|
//fmt.Printf("Skipping field: %s...\n", f.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
//fmt.Printf("Starting on field: %s...\n", f.Name)
|
||||||
|
|
||||||
|
zero := isZero(v)
|
||||||
|
//fmt.Printf("v is zero?: %v\n", zero)
|
||||||
|
|
||||||
|
// if the field has a required tag that's set to "true"
|
||||||
|
if requiredTag := f.Tag.Get("required"); requiredTag == "true" {
|
||||||
|
//fmt.Printf("Checking required field [%s]:\n\tv: %+v\n\tisZero:%v\n", f.Name, v.Interface(), zero)
|
||||||
|
// if the field's value is zero, return a missing-argument error
|
||||||
|
if zero {
|
||||||
|
// if the field has a 'required' tag, it can't have a zero-value
|
||||||
|
err := ErrMissingInput{}
|
||||||
|
err.Argument = f.Name
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if xorTag := f.Tag.Get("xor"); xorTag != "" {
|
||||||
|
//fmt.Printf("Checking `xor` tag for field [%s] with value %+v:\n\txorTag: %s\n", f.Name, v, xorTag)
|
||||||
|
xorField := optsValue.FieldByName(xorTag)
|
||||||
|
var xorFieldIsZero bool
|
||||||
|
if reflect.ValueOf(xorField.Interface()) == reflect.Zero(xorField.Type()) {
|
||||||
|
xorFieldIsZero = true
|
||||||
|
} else {
|
||||||
|
if xorField.Kind() == reflect.Ptr {
|
||||||
|
xorField = xorField.Elem()
|
||||||
|
}
|
||||||
|
xorFieldIsZero = isZero(xorField)
|
||||||
|
}
|
||||||
|
if !(zero != xorFieldIsZero) {
|
||||||
|
err := ErrMissingInput{}
|
||||||
|
err.Argument = fmt.Sprintf("%s/%s", f.Name, xorTag)
|
||||||
|
err.Info = fmt.Sprintf("Exactly one of %s and %s must be provided", f.Name, xorTag)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if orTag := f.Tag.Get("or"); orTag != "" {
|
||||||
|
//fmt.Printf("Checking `or` tag for field with:\n\tname: %+v\n\torTag:%s\n", f.Name, orTag)
|
||||||
|
//fmt.Printf("field is zero?: %v\n", zero)
|
||||||
|
if zero {
|
||||||
|
orField := optsValue.FieldByName(orTag)
|
||||||
|
var orFieldIsZero bool
|
||||||
|
if reflect.ValueOf(orField.Interface()) == reflect.Zero(orField.Type()) {
|
||||||
|
orFieldIsZero = true
|
||||||
|
} else {
|
||||||
|
if orField.Kind() == reflect.Ptr {
|
||||||
|
orField = orField.Elem()
|
||||||
|
}
|
||||||
|
orFieldIsZero = isZero(orField)
|
||||||
|
}
|
||||||
|
if orFieldIsZero {
|
||||||
|
err := ErrMissingInput{}
|
||||||
|
err.Argument = fmt.Sprintf("%s/%s", f.Name, orTag)
|
||||||
|
err.Info = fmt.Sprintf("At least one of %s and %s must be provided", f.Name, orTag)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Kind() == reflect.Struct || (v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct) {
|
||||||
|
if zero {
|
||||||
|
//fmt.Printf("value before change: %+v\n", optsValue.Field(i))
|
||||||
|
if jsonTag := f.Tag.Get("json"); jsonTag != "" {
|
||||||
|
jsonTagPieces := strings.Split(jsonTag, ",")
|
||||||
|
if len(jsonTagPieces) > 1 && jsonTagPieces[1] == "omitempty" {
|
||||||
|
if v.CanSet() {
|
||||||
|
if !v.IsNil() {
|
||||||
|
if v.Kind() == reflect.Ptr {
|
||||||
|
v.Set(reflect.Zero(v.Type()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//fmt.Printf("value after change: %+v\n", optsValue.Field(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
//fmt.Printf("Calling BuildRequestBody with:\n\tv: %+v\n\tf.Name:%s\n", v.Interface(), f.Name)
|
||||||
|
_, err := BuildRequestBody(v.Interface(), f.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//fmt.Printf("opts: %+v \n", opts)
|
||||||
|
|
||||||
|
b, err := json.Marshal(opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//fmt.Printf("string(b): %s\n", string(b))
|
||||||
|
|
||||||
|
err = json.Unmarshal(b, &optsMap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//fmt.Printf("optsMap: %+v\n", optsMap)
|
||||||
|
|
||||||
|
if parent != "" {
|
||||||
|
optsMap = map[string]interface{}{parent: optsMap}
|
||||||
|
}
|
||||||
|
//fmt.Printf("optsMap after parent added: %+v\n", optsMap)
|
||||||
|
return optsMap, nil
|
||||||
|
}
|
||||||
|
// Return an error if the underlying type of 'opts' isn't a struct.
|
||||||
|
return nil, fmt.Errorf("Options type is not a struct.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnabledState is a convenience type, mostly used in Create and Update
|
||||||
|
// operations. Because the zero value of a bool is FALSE, we need to use a
|
||||||
|
// pointer instead to indicate zero-ness.
|
||||||
|
type EnabledState *bool
|
||||||
|
|
||||||
|
// Convenience vars for EnabledState values.
|
||||||
|
var (
|
||||||
|
iTrue = true
|
||||||
|
iFalse = false
|
||||||
|
|
||||||
|
Enabled EnabledState = &iTrue
|
||||||
|
Disabled EnabledState = &iFalse
|
||||||
|
)
|
||||||
|
|
||||||
|
// IPVersion is a type for the possible IP address versions. Valid instances
|
||||||
|
// are IPv4 and IPv6
|
||||||
|
type IPVersion int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// IPv4 is used for IP version 4 addresses
|
||||||
|
IPv4 IPVersion = 4
|
||||||
|
// IPv6 is used for IP version 6 addresses
|
||||||
|
IPv6 IPVersion = 6
|
||||||
|
)
|
||||||
|
|
||||||
|
// IntToPointer is a function for converting integers into integer pointers.
|
||||||
|
// This is useful when passing in options to operations.
|
||||||
|
func IntToPointer(i int) *int {
|
||||||
|
return &i
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
MaybeString is an internal function to be used by request methods in individual
|
||||||
|
resource packages.
|
||||||
|
|
||||||
|
It takes a string that might be a zero value and returns either a pointer to its
|
||||||
|
address or nil. This is useful for allowing users to conveniently omit values
|
||||||
|
from an options struct by leaving them zeroed, but still pass nil to the JSON
|
||||||
|
serializer so they'll be omitted from the request body.
|
||||||
|
*/
|
||||||
|
func MaybeString(original string) *string {
|
||||||
|
if original != "" {
|
||||||
|
return &original
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
MaybeInt is an internal function to be used by request methods in individual
|
||||||
|
resource packages.
|
||||||
|
|
||||||
|
Like MaybeString, it accepts an int that may or may not be a zero value, and
|
||||||
|
returns either a pointer to its address or nil. It's intended to hint that the
|
||||||
|
JSON serializer should omit its field.
|
||||||
|
*/
|
||||||
|
func MaybeInt(original int) *int {
|
||||||
|
if original != 0 {
|
||||||
|
return &original
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
func isUnderlyingStructZero(v reflect.Value) bool {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Ptr:
|
||||||
|
return isUnderlyingStructZero(v.Elem())
|
||||||
|
default:
|
||||||
|
return isZero(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
var t time.Time
|
||||||
|
|
||||||
|
func isZero(v reflect.Value) bool {
|
||||||
|
//fmt.Printf("\n\nchecking isZero for value: %+v\n", v)
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Ptr:
|
||||||
|
if v.IsNil() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case reflect.Func, reflect.Map, reflect.Slice:
|
||||||
|
return v.IsNil()
|
||||||
|
case reflect.Array:
|
||||||
|
z := true
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
z = z && isZero(v.Index(i))
|
||||||
|
}
|
||||||
|
return z
|
||||||
|
case reflect.Struct:
|
||||||
|
if v.Type() == reflect.TypeOf(t) {
|
||||||
|
if v.Interface().(time.Time).IsZero() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
z := true
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
z = z && isZero(v.Field(i))
|
||||||
|
}
|
||||||
|
return z
|
||||||
|
}
|
||||||
|
// Compare other types directly:
|
||||||
|
z := reflect.Zero(v.Type())
|
||||||
|
//fmt.Printf("zero type for value: %+v\n\n\n", z)
|
||||||
|
return v.Interface() == z.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
BuildQueryString is an internal function to be used by request methods in
|
||||||
|
individual resource packages.
|
||||||
|
|
||||||
|
It accepts a tagged structure and expands it into a URL struct. Field names are
|
||||||
|
converted into query parameters based on a "q" tag. For example:
|
||||||
|
|
||||||
|
type struct Something {
|
||||||
|
Bar string `q:"x_bar"`
|
||||||
|
Baz int `q:"lorem_ipsum"`
|
||||||
|
}
|
||||||
|
|
||||||
|
instance := Something{
|
||||||
|
Bar: "AAA",
|
||||||
|
Baz: "BBB",
|
||||||
|
}
|
||||||
|
|
||||||
|
will be converted into "?x_bar=AAA&lorem_ipsum=BBB".
|
||||||
|
|
||||||
|
The struct's fields may be strings, integers, or boolean values. Fields left at
|
||||||
|
their type's zero value will be omitted from the query.
|
||||||
|
*/
|
||||||
|
func BuildQueryString(opts interface{}) (*url.URL, error) {
|
||||||
|
optsValue := reflect.ValueOf(opts)
|
||||||
|
if optsValue.Kind() == reflect.Ptr {
|
||||||
|
optsValue = optsValue.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
optsType := reflect.TypeOf(opts)
|
||||||
|
if optsType.Kind() == reflect.Ptr {
|
||||||
|
optsType = optsType.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
params := url.Values{}
|
||||||
|
|
||||||
|
if optsValue.Kind() == reflect.Struct {
|
||||||
|
for i := 0; i < optsValue.NumField(); i++ {
|
||||||
|
v := optsValue.Field(i)
|
||||||
|
f := optsType.Field(i)
|
||||||
|
qTag := f.Tag.Get("q")
|
||||||
|
|
||||||
|
// if the field has a 'q' tag, it goes in the query string
|
||||||
|
if qTag != "" {
|
||||||
|
tags := strings.Split(qTag, ",")
|
||||||
|
|
||||||
|
// if the field is set, add it to the slice of query pieces
|
||||||
|
if !isZero(v) {
|
||||||
|
loop:
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Ptr:
|
||||||
|
v = v.Elem()
|
||||||
|
goto loop
|
||||||
|
case reflect.String:
|
||||||
|
params.Add(tags[0], v.String())
|
||||||
|
case reflect.Int:
|
||||||
|
params.Add(tags[0], strconv.FormatInt(v.Int(), 10))
|
||||||
|
case reflect.Bool:
|
||||||
|
params.Add(tags[0], strconv.FormatBool(v.Bool()))
|
||||||
|
case reflect.Slice:
|
||||||
|
switch v.Type().Elem() {
|
||||||
|
case reflect.TypeOf(0):
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
params.Add(tags[0], strconv.FormatInt(v.Index(i).Int(), 10))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
params.Add(tags[0], v.Index(i).String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Otherwise, the field is not set.
|
||||||
|
if len(tags) == 2 && tags[1] == "required" {
|
||||||
|
// And the field is required. Return an error.
|
||||||
|
return nil, fmt.Errorf("Required query parameter [%s] not set.", f.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &url.URL{RawQuery: params.Encode()}, nil
|
||||||
|
}
|
||||||
|
// Return an error if the underlying type of 'opts' isn't a struct.
|
||||||
|
return nil, fmt.Errorf("Options type is not a struct.")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
BuildHeaders is an internal function to be used by request methods in
|
||||||
|
individual resource packages.
|
||||||
|
|
||||||
|
It accepts an arbitrary tagged structure and produces a string map that's
|
||||||
|
suitable for use as the HTTP headers of an outgoing request. Field names are
|
||||||
|
mapped to header names based in "h" tags.
|
||||||
|
|
||||||
|
type struct Something {
|
||||||
|
Bar string `h:"x_bar"`
|
||||||
|
Baz int `h:"lorem_ipsum"`
|
||||||
|
}
|
||||||
|
|
||||||
|
instance := Something{
|
||||||
|
Bar: "AAA",
|
||||||
|
Baz: "BBB",
|
||||||
|
}
|
||||||
|
|
||||||
|
will be converted into:
|
||||||
|
|
||||||
|
map[string]string{
|
||||||
|
"x_bar": "AAA",
|
||||||
|
"lorem_ipsum": "BBB",
|
||||||
|
}
|
||||||
|
|
||||||
|
Untagged fields and fields left at their zero values are skipped. Integers,
|
||||||
|
booleans and string values are supported.
|
||||||
|
*/
|
||||||
|
func BuildHeaders(opts interface{}) (map[string]string, error) {
|
||||||
|
optsValue := reflect.ValueOf(opts)
|
||||||
|
if optsValue.Kind() == reflect.Ptr {
|
||||||
|
optsValue = optsValue.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
optsType := reflect.TypeOf(opts)
|
||||||
|
if optsType.Kind() == reflect.Ptr {
|
||||||
|
optsType = optsType.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
optsMap := make(map[string]string)
|
||||||
|
if optsValue.Kind() == reflect.Struct {
|
||||||
|
for i := 0; i < optsValue.NumField(); i++ {
|
||||||
|
v := optsValue.Field(i)
|
||||||
|
f := optsType.Field(i)
|
||||||
|
hTag := f.Tag.Get("h")
|
||||||
|
|
||||||
|
// if the field has a 'h' tag, it goes in the header
|
||||||
|
if hTag != "" {
|
||||||
|
tags := strings.Split(hTag, ",")
|
||||||
|
|
||||||
|
// if the field is set, add it to the slice of query pieces
|
||||||
|
if !isZero(v) {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
optsMap[tags[0]] = v.String()
|
||||||
|
case reflect.Int:
|
||||||
|
optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10)
|
||||||
|
case reflect.Bool:
|
||||||
|
optsMap[tags[0]] = strconv.FormatBool(v.Bool())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Otherwise, the field is not set.
|
||||||
|
if len(tags) == 2 && tags[1] == "required" {
|
||||||
|
// And the field is required. Return an error.
|
||||||
|
return optsMap, fmt.Errorf("Required header not set.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return optsMap, nil
|
||||||
|
}
|
||||||
|
// Return an error if the underlying type of 'opts' isn't a struct.
|
||||||
|
return optsMap, fmt.Errorf("Options type is not a struct.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDSliceToQueryString takes a slice of elements and converts them into a query
|
||||||
|
// string. For example, if name=foo and slice=[]int{20, 40, 60}, then the
|
||||||
|
// result would be `?name=20&name=40&name=60'
|
||||||
|
func IDSliceToQueryString(name string, ids []int) string {
|
||||||
|
str := ""
|
||||||
|
for k, v := range ids {
|
||||||
|
if k == 0 {
|
||||||
|
str += "?"
|
||||||
|
} else {
|
||||||
|
str += "&"
|
||||||
|
}
|
||||||
|
str += fmt.Sprintf("%s=%s", name, strconv.Itoa(v))
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntWithinRange returns TRUE if an integer falls within a defined range, and
|
||||||
|
// FALSE if not.
|
||||||
|
func IntWithinRange(val, min, max int) bool {
|
||||||
|
return val > min && val < max
|
||||||
|
}
|
307
vendor/github.com/gophercloud/gophercloud/provider_client.go
generated
vendored
Normal file
307
vendor/github.com/gophercloud/gophercloud/provider_client.go
generated
vendored
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
package gophercloud
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultUserAgent is the default User-Agent string set in the request header.
|
||||||
|
const DefaultUserAgent = "gophercloud/2.0.0"
|
||||||
|
|
||||||
|
// UserAgent represents a User-Agent header.
|
||||||
|
type UserAgent struct {
|
||||||
|
// prepend is the slice of User-Agent strings to prepend to DefaultUserAgent.
|
||||||
|
// All the strings to prepend are accumulated and prepended in the Join method.
|
||||||
|
prepend []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepend prepends a user-defined string to the default User-Agent string. Users
|
||||||
|
// may pass in one or more strings to prepend.
|
||||||
|
func (ua *UserAgent) Prepend(s ...string) {
|
||||||
|
ua.prepend = append(s, ua.prepend...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join concatenates all the user-defined User-Agend strings with the default
|
||||||
|
// Gophercloud User-Agent string.
|
||||||
|
func (ua *UserAgent) Join() string {
|
||||||
|
uaSlice := append(ua.prepend, DefaultUserAgent)
|
||||||
|
return strings.Join(uaSlice, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProviderClient stores details that are required to interact with any
|
||||||
|
// services within a specific provider's API.
|
||||||
|
//
|
||||||
|
// Generally, you acquire a ProviderClient by calling the NewClient method in
|
||||||
|
// the appropriate provider's child package, providing whatever authentication
|
||||||
|
// credentials are required.
|
||||||
|
type ProviderClient struct {
|
||||||
|
// IdentityBase is the base URL used for a particular provider's identity
|
||||||
|
// service - it will be used when issuing authenticatation requests. It
|
||||||
|
// should point to the root resource of the identity service, not a specific
|
||||||
|
// identity version.
|
||||||
|
IdentityBase string
|
||||||
|
|
||||||
|
// IdentityEndpoint is the identity endpoint. This may be a specific version
|
||||||
|
// of the identity service. If this is the case, this endpoint is used rather
|
||||||
|
// than querying versions first.
|
||||||
|
IdentityEndpoint string
|
||||||
|
|
||||||
|
// TokenID is the ID of the most recently issued valid token.
|
||||||
|
TokenID string
|
||||||
|
|
||||||
|
// EndpointLocator describes how this provider discovers the endpoints for
|
||||||
|
// its constituent services.
|
||||||
|
EndpointLocator EndpointLocator
|
||||||
|
|
||||||
|
// HTTPClient allows users to interject arbitrary http, https, or other transit behaviors.
|
||||||
|
HTTPClient http.Client
|
||||||
|
|
||||||
|
// UserAgent represents the User-Agent header in the HTTP request.
|
||||||
|
UserAgent UserAgent
|
||||||
|
|
||||||
|
// ReauthFunc is the function used to re-authenticate the user if the request
|
||||||
|
// fails with a 401 HTTP response code. This a needed because there may be multiple
|
||||||
|
// authentication functions for different Identity service versions.
|
||||||
|
ReauthFunc func() error
|
||||||
|
|
||||||
|
Debug bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthenticatedHeaders returns a map of HTTP headers that are common for all
|
||||||
|
// authenticated service requests.
|
||||||
|
func (client *ProviderClient) AuthenticatedHeaders() map[string]string {
|
||||||
|
if client.TokenID == "" {
|
||||||
|
return map[string]string{}
|
||||||
|
}
|
||||||
|
return map[string]string{"X-Auth-Token": client.TokenID}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestOpts customizes the behavior of the provider.Request() method.
|
||||||
|
type RequestOpts struct {
|
||||||
|
// JSONBody, if provided, will be encoded as JSON and used as the body of the HTTP request. The
|
||||||
|
// content type of the request will default to "application/json" unless overridden by MoreHeaders.
|
||||||
|
// It's an error to specify both a JSONBody and a RawBody.
|
||||||
|
JSONBody interface{}
|
||||||
|
// RawBody contains an io.Reader that will be consumed by the request directly. No content-type
|
||||||
|
// will be set unless one is provided explicitly by MoreHeaders.
|
||||||
|
RawBody io.Reader
|
||||||
|
// JSONResponse, if provided, will be populated with the contents of the response body parsed as
|
||||||
|
// JSON.
|
||||||
|
JSONResponse interface{}
|
||||||
|
// OkCodes contains a list of numeric HTTP status codes that should be interpreted as success. If
|
||||||
|
// the response has a different code, an error will be returned.
|
||||||
|
OkCodes []int
|
||||||
|
// MoreHeaders specifies additional HTTP headers to be provide on the request. If a header is
|
||||||
|
// provided with a blank value (""), that header will be *omitted* instead: use this to suppress
|
||||||
|
// the default Accept header or an inferred Content-Type, for example.
|
||||||
|
MoreHeaders map[string]string
|
||||||
|
// ErrorContext specifies the resource error type to return if an error is encountered.
|
||||||
|
// This lets resources override default error messages based on the response status code.
|
||||||
|
ErrorContext error
|
||||||
|
}
|
||||||
|
|
||||||
|
var applicationJSON = "application/json"
|
||||||
|
|
||||||
|
// Request performs an HTTP request using the ProviderClient's current HTTPClient. An authentication
|
||||||
|
// header will automatically be provided.
|
||||||
|
func (client *ProviderClient) Request(method, url string, options *RequestOpts) (*http.Response, error) {
|
||||||
|
var body io.Reader
|
||||||
|
var contentType *string
|
||||||
|
|
||||||
|
// Derive the content body by either encoding an arbitrary object as JSON, or by taking a provided
|
||||||
|
// io.ReadSeeker as-is. Default the content-type to application/json.
|
||||||
|
if options.JSONBody != nil {
|
||||||
|
if options.RawBody != nil {
|
||||||
|
panic("Please provide only one of JSONBody or RawBody to gophercloud.Request().")
|
||||||
|
}
|
||||||
|
|
||||||
|
rendered, err := json.Marshal(options.JSONBody)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
body = bytes.NewReader(rendered)
|
||||||
|
contentType = &applicationJSON
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.RawBody != nil {
|
||||||
|
body = options.RawBody
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct the http.Request.
|
||||||
|
req, err := http.NewRequest(method, url, body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate the request headers. Apply options.MoreHeaders last, to give the caller the chance to
|
||||||
|
// modify or omit any header.
|
||||||
|
if contentType != nil {
|
||||||
|
req.Header.Set("Content-Type", *contentType)
|
||||||
|
}
|
||||||
|
req.Header.Set("Accept", applicationJSON)
|
||||||
|
|
||||||
|
for k, v := range client.AuthenticatedHeaders() {
|
||||||
|
req.Header.Add(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the User-Agent header
|
||||||
|
req.Header.Set("User-Agent", client.UserAgent.Join())
|
||||||
|
|
||||||
|
if options.MoreHeaders != nil {
|
||||||
|
for k, v := range options.MoreHeaders {
|
||||||
|
if v != "" {
|
||||||
|
req.Header.Set(k, v)
|
||||||
|
} else {
|
||||||
|
req.Header.Del(k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set connection parameter to close the connection immediately when we've got the response
|
||||||
|
req.Close = true
|
||||||
|
|
||||||
|
// Issue the request.
|
||||||
|
resp, err := client.HTTPClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow default OkCodes if none explicitly set
|
||||||
|
if options.OkCodes == nil {
|
||||||
|
options.OkCodes = defaultOkCodes(method)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the HTTP response status.
|
||||||
|
var ok bool
|
||||||
|
for _, code := range options.OkCodes {
|
||||||
|
if resp.StatusCode == code {
|
||||||
|
ok = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
body, _ := ioutil.ReadAll(resp.Body)
|
||||||
|
resp.Body.Close()
|
||||||
|
//pc := make([]uintptr, 1)
|
||||||
|
//runtime.Callers(2, pc)
|
||||||
|
//f := runtime.FuncForPC(pc[0])
|
||||||
|
respErr := ErrUnexpectedResponseCode{
|
||||||
|
URL: url,
|
||||||
|
Method: method,
|
||||||
|
Expected: options.OkCodes,
|
||||||
|
Actual: resp.StatusCode,
|
||||||
|
Body: body,
|
||||||
|
}
|
||||||
|
//respErr.Function = "gophercloud.ProviderClient.Request"
|
||||||
|
|
||||||
|
errType := options.ErrorContext
|
||||||
|
switch resp.StatusCode {
|
||||||
|
case http.StatusBadRequest:
|
||||||
|
err = ErrDefault400{respErr}
|
||||||
|
if error400er, ok := errType.(Err400er); ok {
|
||||||
|
err = error400er.Error400(respErr)
|
||||||
|
}
|
||||||
|
case http.StatusUnauthorized:
|
||||||
|
if client.ReauthFunc != nil {
|
||||||
|
err = client.ReauthFunc()
|
||||||
|
if err != nil {
|
||||||
|
e := &ErrUnableToReauthenticate{}
|
||||||
|
e.ErrOriginal = respErr
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
if options.RawBody != nil {
|
||||||
|
if seeker, ok := options.RawBody.(io.Seeker); ok {
|
||||||
|
seeker.Seek(0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resp, err = client.Request(method, url, options)
|
||||||
|
if err != nil {
|
||||||
|
switch err.(type) {
|
||||||
|
case *ErrUnexpectedResponseCode:
|
||||||
|
e := &ErrErrorAfterReauthentication{}
|
||||||
|
e.ErrOriginal = err.(*ErrUnexpectedResponseCode)
|
||||||
|
return nil, e
|
||||||
|
default:
|
||||||
|
e := &ErrErrorAfterReauthentication{}
|
||||||
|
e.ErrOriginal = err
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
err = ErrDefault401{respErr}
|
||||||
|
if error401er, ok := errType.(Err401er); ok {
|
||||||
|
err = error401er.Error401(respErr)
|
||||||
|
}
|
||||||
|
case http.StatusNotFound:
|
||||||
|
err = ErrDefault404{respErr}
|
||||||
|
if error404er, ok := errType.(Err404er); ok {
|
||||||
|
err = error404er.Error404(respErr)
|
||||||
|
}
|
||||||
|
case http.StatusMethodNotAllowed:
|
||||||
|
err = ErrDefault405{respErr}
|
||||||
|
if error405er, ok := errType.(Err405er); ok {
|
||||||
|
err = error405er.Error405(respErr)
|
||||||
|
}
|
||||||
|
case http.StatusRequestTimeout:
|
||||||
|
err = ErrDefault408{respErr}
|
||||||
|
if error408er, ok := errType.(Err408er); ok {
|
||||||
|
err = error408er.Error408(respErr)
|
||||||
|
}
|
||||||
|
case 429:
|
||||||
|
err = ErrDefault429{respErr}
|
||||||
|
if error429er, ok := errType.(Err429er); ok {
|
||||||
|
err = error429er.Error429(respErr)
|
||||||
|
}
|
||||||
|
case http.StatusInternalServerError:
|
||||||
|
err = ErrDefault500{respErr}
|
||||||
|
if error500er, ok := errType.(Err500er); ok {
|
||||||
|
err = error500er.Error500(respErr)
|
||||||
|
}
|
||||||
|
case http.StatusServiceUnavailable:
|
||||||
|
err = ErrDefault503{respErr}
|
||||||
|
if error503er, ok := errType.(Err503er); ok {
|
||||||
|
err = error503er.Error503(respErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
err = respErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the response body as JSON, if requested to do so.
|
||||||
|
if options.JSONResponse != nil {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(options.JSONResponse); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultOkCodes(method string) []int {
|
||||||
|
switch {
|
||||||
|
case method == "GET":
|
||||||
|
return []int{200}
|
||||||
|
case method == "POST":
|
||||||
|
return []int{201, 202}
|
||||||
|
case method == "PUT":
|
||||||
|
return []int{201, 202}
|
||||||
|
case method == "PATCH":
|
||||||
|
return []int{200, 204}
|
||||||
|
case method == "DELETE":
|
||||||
|
return []int{202, 204}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []int{}
|
||||||
|
}
|
336
vendor/github.com/gophercloud/gophercloud/results.go
generated
vendored
Normal file
336
vendor/github.com/gophercloud/gophercloud/results.go
generated
vendored
Normal file
@@ -0,0 +1,336 @@
|
|||||||
|
package gophercloud
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Result is an internal type to be used by individual resource packages, but its
|
||||||
|
methods will be available on a wide variety of user-facing embedding types.
|
||||||
|
|
||||||
|
It acts as a base struct that other Result types, returned from request
|
||||||
|
functions, can embed for convenience. All Results capture basic information
|
||||||
|
from the HTTP transaction that was performed, including the response body,
|
||||||
|
HTTP headers, and any errors that happened.
|
||||||
|
|
||||||
|
Generally, each Result type will have an Extract method that can be used to
|
||||||
|
further interpret the result's payload in a specific context. Extensions or
|
||||||
|
providers can then provide additional extraction functions to pull out
|
||||||
|
provider- or extension-specific information as well.
|
||||||
|
*/
|
||||||
|
type Result struct {
|
||||||
|
// Body is the payload of the HTTP response from the server. In most cases,
|
||||||
|
// this will be the deserialized JSON structure.
|
||||||
|
Body interface{}
|
||||||
|
|
||||||
|
// Header contains the HTTP header structure from the original response.
|
||||||
|
Header http.Header
|
||||||
|
|
||||||
|
// Err is an error that occurred during the operation. It's deferred until
|
||||||
|
// extraction to make it easier to chain the Extract call.
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractInto allows users to provide an object into which `Extract` will extract
|
||||||
|
// the `Result.Body`. This would be useful for OpenStack providers that have
|
||||||
|
// different fields in the response object than OpenStack proper.
|
||||||
|
func (r Result) ExtractInto(to interface{}) error {
|
||||||
|
if r.Err != nil {
|
||||||
|
return r.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
if reader, ok := r.Body.(io.Reader); ok {
|
||||||
|
if readCloser, ok := reader.(io.Closer); ok {
|
||||||
|
defer readCloser.Close()
|
||||||
|
}
|
||||||
|
return json.NewDecoder(reader).Decode(to)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(b, to)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Result) extractIntoPtr(to interface{}, label string) error {
|
||||||
|
if label == "" {
|
||||||
|
return r.ExtractInto(&to)
|
||||||
|
}
|
||||||
|
|
||||||
|
var m map[string]interface{}
|
||||||
|
err := r.ExtractInto(&m)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(m[label])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(b, &to)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractIntoStructPtr will unmarshal the Result (r) into the provided
|
||||||
|
// interface{} (to).
|
||||||
|
//
|
||||||
|
// NOTE: For internal use only
|
||||||
|
//
|
||||||
|
// `to` must be a pointer to an underlying struct type
|
||||||
|
//
|
||||||
|
// If provided, `label` will be filtered out of the response
|
||||||
|
// body prior to `r` being unmarshalled into `to`.
|
||||||
|
func (r Result) ExtractIntoStructPtr(to interface{}, label string) error {
|
||||||
|
if r.Err != nil {
|
||||||
|
return r.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
t := reflect.TypeOf(to)
|
||||||
|
if k := t.Kind(); k != reflect.Ptr {
|
||||||
|
return fmt.Errorf("Expected pointer, got %v", k)
|
||||||
|
}
|
||||||
|
switch t.Elem().Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
return r.extractIntoPtr(to, label)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Expected pointer to struct, got: %v", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractIntoSlicePtr will unmarshal the Result (r) into the provided
|
||||||
|
// interface{} (to).
|
||||||
|
//
|
||||||
|
// NOTE: For internal use only
|
||||||
|
//
|
||||||
|
// `to` must be a pointer to an underlying slice type
|
||||||
|
//
|
||||||
|
// If provided, `label` will be filtered out of the response
|
||||||
|
// body prior to `r` being unmarshalled into `to`.
|
||||||
|
func (r Result) ExtractIntoSlicePtr(to interface{}, label string) error {
|
||||||
|
if r.Err != nil {
|
||||||
|
return r.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
t := reflect.TypeOf(to)
|
||||||
|
if k := t.Kind(); k != reflect.Ptr {
|
||||||
|
return fmt.Errorf("Expected pointer, got %v", k)
|
||||||
|
}
|
||||||
|
switch t.Elem().Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
return r.extractIntoPtr(to, label)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Expected pointer to slice, got: %v", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrettyPrintJSON creates a string containing the full response body as
|
||||||
|
// pretty-printed JSON. It's useful for capturing test fixtures and for
|
||||||
|
// debugging extraction bugs. If you include its output in an issue related to
|
||||||
|
// a buggy extraction function, we will all love you forever.
|
||||||
|
func (r Result) PrettyPrintJSON() string {
|
||||||
|
pretty, err := json.MarshalIndent(r.Body, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
panic(err.Error())
|
||||||
|
}
|
||||||
|
return string(pretty)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrResult is an internal type to be used by individual resource packages, but
|
||||||
|
// its methods will be available on a wide variety of user-facing embedding
|
||||||
|
// types.
|
||||||
|
//
|
||||||
|
// It represents results that only contain a potential error and
|
||||||
|
// nothing else. Usually, if the operation executed successfully, the Err field
|
||||||
|
// will be nil; otherwise it will be stocked with a relevant error. Use the
|
||||||
|
// ExtractErr method
|
||||||
|
// to cleanly pull it out.
|
||||||
|
type ErrResult struct {
|
||||||
|
Result
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractErr is a function that extracts error information, or nil, from a result.
|
||||||
|
func (r ErrResult) ExtractErr() error {
|
||||||
|
return r.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
HeaderResult is an internal type to be used by individual resource packages, but
|
||||||
|
its methods will be available on a wide variety of user-facing embedding types.
|
||||||
|
|
||||||
|
It represents a result that only contains an error (possibly nil) and an
|
||||||
|
http.Header. This is used, for example, by the objectstorage packages in
|
||||||
|
openstack, because most of the operations don't return response bodies, but do
|
||||||
|
have relevant information in headers.
|
||||||
|
*/
|
||||||
|
type HeaderResult struct {
|
||||||
|
Result
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractHeader will return the http.Header and error from the HeaderResult.
|
||||||
|
//
|
||||||
|
// header, err := objects.Create(client, "my_container", objects.CreateOpts{}).ExtractHeader()
|
||||||
|
func (r HeaderResult) ExtractInto(to interface{}) error {
|
||||||
|
if r.Err != nil {
|
||||||
|
return r.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpHeaderMap := map[string]string{}
|
||||||
|
for k, v := range r.Header {
|
||||||
|
if len(v) > 0 {
|
||||||
|
tmpHeaderMap[k] = v[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(tmpHeaderMap)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(b, to)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RFC3339Milli describes a common time format used by some API responses.
|
||||||
|
const RFC3339Milli = "2006-01-02T15:04:05.999999Z"
|
||||||
|
|
||||||
|
type JSONRFC3339Milli time.Time
|
||||||
|
|
||||||
|
func (jt *JSONRFC3339Milli) UnmarshalJSON(data []byte) error {
|
||||||
|
b := bytes.NewBuffer(data)
|
||||||
|
dec := json.NewDecoder(b)
|
||||||
|
var s string
|
||||||
|
if err := dec.Decode(&s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t, err := time.Parse(RFC3339Milli, s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*jt = JSONRFC3339Milli(t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const RFC3339MilliNoZ = "2006-01-02T15:04:05.999999"
|
||||||
|
|
||||||
|
type JSONRFC3339MilliNoZ time.Time
|
||||||
|
|
||||||
|
func (jt *JSONRFC3339MilliNoZ) UnmarshalJSON(data []byte) error {
|
||||||
|
var s string
|
||||||
|
if err := json.Unmarshal(data, &s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if s == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
t, err := time.Parse(RFC3339MilliNoZ, s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*jt = JSONRFC3339MilliNoZ(t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type JSONRFC1123 time.Time
|
||||||
|
|
||||||
|
func (jt *JSONRFC1123) UnmarshalJSON(data []byte) error {
|
||||||
|
var s string
|
||||||
|
if err := json.Unmarshal(data, &s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if s == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
t, err := time.Parse(time.RFC1123, s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*jt = JSONRFC1123(t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type JSONUnix time.Time
|
||||||
|
|
||||||
|
func (jt *JSONUnix) UnmarshalJSON(data []byte) error {
|
||||||
|
var s string
|
||||||
|
if err := json.Unmarshal(data, &s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if s == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
unix, err := strconv.ParseInt(s, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t = time.Unix(unix, 0)
|
||||||
|
*jt = JSONUnix(t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RFC3339NoZ is the time format used in Heat (Orchestration).
|
||||||
|
const RFC3339NoZ = "2006-01-02T15:04:05"
|
||||||
|
|
||||||
|
type JSONRFC3339NoZ time.Time
|
||||||
|
|
||||||
|
func (jt *JSONRFC3339NoZ) UnmarshalJSON(data []byte) error {
|
||||||
|
var s string
|
||||||
|
if err := json.Unmarshal(data, &s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if s == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
t, err := time.Parse(RFC3339NoZ, s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*jt = JSONRFC3339NoZ(t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Link is an internal type to be used in packages of collection resources that are
|
||||||
|
paginated in a certain way.
|
||||||
|
|
||||||
|
It's a response substructure common to many paginated collection results that is
|
||||||
|
used to point to related pages. Usually, the one we care about is the one with
|
||||||
|
Rel field set to "next".
|
||||||
|
*/
|
||||||
|
type Link struct {
|
||||||
|
Href string `json:"href"`
|
||||||
|
Rel string `json:"rel"`
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
ExtractNextURL is an internal function useful for packages of collection
|
||||||
|
resources that are paginated in a certain way.
|
||||||
|
|
||||||
|
It attempts to extract the "next" URL from slice of Link structs, or
|
||||||
|
"" if no such URL is present.
|
||||||
|
*/
|
||||||
|
func ExtractNextURL(links []Link) (string, error) {
|
||||||
|
var url string
|
||||||
|
|
||||||
|
for _, l := range links {
|
||||||
|
if l.Rel == "next" {
|
||||||
|
url = l.Href
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if url == "" {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return url, nil
|
||||||
|
}
|
122
vendor/github.com/gophercloud/gophercloud/service_client.go
generated
vendored
Normal file
122
vendor/github.com/gophercloud/gophercloud/service_client.go
generated
vendored
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
package gophercloud
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ServiceClient stores details required to interact with a specific service API implemented by a provider.
|
||||||
|
// Generally, you'll acquire these by calling the appropriate `New` method on a ProviderClient.
|
||||||
|
type ServiceClient struct {
|
||||||
|
// ProviderClient is a reference to the provider that implements this service.
|
||||||
|
*ProviderClient
|
||||||
|
|
||||||
|
// Endpoint is the base URL of the service's API, acquired from a service catalog.
|
||||||
|
// It MUST end with a /.
|
||||||
|
Endpoint string
|
||||||
|
|
||||||
|
// ResourceBase is the base URL shared by the resources within a service's API. It should include
|
||||||
|
// the API version and, like Endpoint, MUST end with a / if set. If not set, the Endpoint is used
|
||||||
|
// as-is, instead.
|
||||||
|
ResourceBase string
|
||||||
|
|
||||||
|
// This is the service client type (e.g. compute, sharev2).
|
||||||
|
// NOTE: FOR INTERNAL USE ONLY. DO NOT SET. GOPHERCLOUD WILL SET THIS.
|
||||||
|
// It is only exported because it gets set in a different package.
|
||||||
|
Type string
|
||||||
|
|
||||||
|
// The microversion of the service to use. Set this to use a particular microversion.
|
||||||
|
Microversion string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResourceBaseURL returns the base URL of any resources used by this service. It MUST end with a /.
|
||||||
|
func (client *ServiceClient) ResourceBaseURL() string {
|
||||||
|
if client.ResourceBase != "" {
|
||||||
|
return client.ResourceBase
|
||||||
|
}
|
||||||
|
return client.Endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServiceURL constructs a URL for a resource belonging to this provider.
|
||||||
|
func (client *ServiceClient) ServiceURL(parts ...string) string {
|
||||||
|
return client.ResourceBaseURL() + strings.Join(parts, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (client *ServiceClient) initReqOpts(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) {
|
||||||
|
if v, ok := (JSONBody).(io.Reader); ok {
|
||||||
|
opts.RawBody = v
|
||||||
|
} else if JSONBody != nil {
|
||||||
|
opts.JSONBody = JSONBody
|
||||||
|
}
|
||||||
|
|
||||||
|
if JSONResponse != nil {
|
||||||
|
opts.JSONResponse = JSONResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.MoreHeaders == nil {
|
||||||
|
opts.MoreHeaders = make(map[string]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
if client.Microversion != "" {
|
||||||
|
client.setMicroversionHeader(opts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get calls `Request` with the "GET" HTTP verb.
|
||||||
|
func (client *ServiceClient) Get(url string, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) {
|
||||||
|
if opts == nil {
|
||||||
|
opts = new(RequestOpts)
|
||||||
|
}
|
||||||
|
client.initReqOpts(url, nil, JSONResponse, opts)
|
||||||
|
return client.Request("GET", url, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post calls `Request` with the "POST" HTTP verb.
|
||||||
|
func (client *ServiceClient) Post(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) {
|
||||||
|
if opts == nil {
|
||||||
|
opts = new(RequestOpts)
|
||||||
|
}
|
||||||
|
client.initReqOpts(url, JSONBody, JSONResponse, opts)
|
||||||
|
return client.Request("POST", url, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put calls `Request` with the "PUT" HTTP verb.
|
||||||
|
func (client *ServiceClient) Put(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) {
|
||||||
|
if opts == nil {
|
||||||
|
opts = new(RequestOpts)
|
||||||
|
}
|
||||||
|
client.initReqOpts(url, JSONBody, JSONResponse, opts)
|
||||||
|
return client.Request("PUT", url, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patch calls `Request` with the "PATCH" HTTP verb.
|
||||||
|
func (client *ServiceClient) Patch(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) {
|
||||||
|
if opts == nil {
|
||||||
|
opts = new(RequestOpts)
|
||||||
|
}
|
||||||
|
client.initReqOpts(url, JSONBody, JSONResponse, opts)
|
||||||
|
return client.Request("PATCH", url, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete calls `Request` with the "DELETE" HTTP verb.
|
||||||
|
func (client *ServiceClient) Delete(url string, opts *RequestOpts) (*http.Response, error) {
|
||||||
|
if opts == nil {
|
||||||
|
opts = new(RequestOpts)
|
||||||
|
}
|
||||||
|
client.initReqOpts(url, nil, nil, opts)
|
||||||
|
return client.Request("DELETE", url, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (client *ServiceClient) setMicroversionHeader(opts *RequestOpts) {
|
||||||
|
switch client.Type {
|
||||||
|
case "compute":
|
||||||
|
opts.MoreHeaders["X-OpenStack-Nova-API-Version"] = client.Microversion
|
||||||
|
case "sharev2":
|
||||||
|
opts.MoreHeaders["X-OpenStack-Manila-API-Version"] = client.Microversion
|
||||||
|
}
|
||||||
|
|
||||||
|
if client.Type != "" {
|
||||||
|
opts.MoreHeaders["OpenStack-API-Version"] = client.Type + " " + client.Microversion
|
||||||
|
}
|
||||||
|
}
|
102
vendor/github.com/gophercloud/gophercloud/util.go
generated
vendored
Normal file
102
vendor/github.com/gophercloud/gophercloud/util.go
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
package gophercloud
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WaitFor polls a predicate function, once per second, up to a timeout limit.
|
||||||
|
// This is useful to wait for a resource to transition to a certain state.
|
||||||
|
// To handle situations when the predicate might hang indefinitely, the
|
||||||
|
// predicate will be prematurely cancelled after the timeout.
|
||||||
|
// Resource packages will wrap this in a more convenient function that's
|
||||||
|
// specific to a certain resource, but it can also be useful on its own.
|
||||||
|
func WaitFor(timeout int, predicate func() (bool, error)) error {
|
||||||
|
type WaitForResult struct {
|
||||||
|
Success bool
|
||||||
|
Error error
|
||||||
|
}
|
||||||
|
|
||||||
|
start := time.Now().Unix()
|
||||||
|
|
||||||
|
for {
|
||||||
|
// If a timeout is set, and that's been exceeded, shut it down.
|
||||||
|
if timeout >= 0 && time.Now().Unix()-start >= int64(timeout) {
|
||||||
|
return fmt.Errorf("A timeout occurred")
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
|
var result WaitForResult
|
||||||
|
ch := make(chan bool, 1)
|
||||||
|
go func() {
|
||||||
|
defer close(ch)
|
||||||
|
satisfied, err := predicate()
|
||||||
|
result.Success = satisfied
|
||||||
|
result.Error = err
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ch:
|
||||||
|
if result.Error != nil {
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
if result.Success {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// If the predicate has not finished by the timeout, cancel it.
|
||||||
|
case <-time.After(time.Duration(timeout) * time.Second):
|
||||||
|
return fmt.Errorf("A timeout occurred")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NormalizeURL is an internal function to be used by provider clients.
|
||||||
|
//
|
||||||
|
// It ensures that each endpoint URL has a closing `/`, as expected by
|
||||||
|
// ServiceClient's methods.
|
||||||
|
func NormalizeURL(url string) string {
|
||||||
|
if !strings.HasSuffix(url, "/") {
|
||||||
|
return url + "/"
|
||||||
|
}
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
// NormalizePathURL is used to convert rawPath to a fqdn, using basePath as
|
||||||
|
// a reference in the filesystem, if necessary. basePath is assumed to contain
|
||||||
|
// either '.' when first used, or the file:// type fqdn of the parent resource.
|
||||||
|
// e.g. myFavScript.yaml => file://opt/lib/myFavScript.yaml
|
||||||
|
func NormalizePathURL(basePath, rawPath string) (string, error) {
|
||||||
|
u, err := url.Parse(rawPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// if a scheme is defined, it must be a fqdn already
|
||||||
|
if u.Scheme != "" {
|
||||||
|
return u.String(), nil
|
||||||
|
}
|
||||||
|
// if basePath is a url, then child resources are assumed to be relative to it
|
||||||
|
bu, err := url.Parse(basePath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
var basePathSys, absPathSys string
|
||||||
|
if bu.Scheme != "" {
|
||||||
|
basePathSys = filepath.FromSlash(bu.Path)
|
||||||
|
absPathSys = filepath.Join(basePathSys, rawPath)
|
||||||
|
bu.Path = filepath.ToSlash(absPathSys)
|
||||||
|
return bu.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
absPathSys = filepath.Join(basePath, rawPath)
|
||||||
|
u.Path = filepath.ToSlash(absPathSys)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
u.Scheme = "file"
|
||||||
|
return u.String(), nil
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user