Merge pull request #9573 from zhsj/bump-gophercloud
Bump gophercloud to latest version
This commit is contained in:
commit
f5031a1eb5
|
@ -6,6 +6,7 @@ import (
|
|||
"log"
|
||||
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
|
||||
flavors_utils "github.com/gophercloud/utils/openstack/compute/v2/flavors"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
@ -37,7 +38,7 @@ func (s *StepLoadFlavor) Run(ctx context.Context, state multistep.StateBag) mult
|
|||
geterr := err
|
||||
|
||||
log.Printf("[INFO] Loading flavor by name: %s", s.Flavor)
|
||||
id, err := flavors.IDFromName(client, s.Flavor)
|
||||
id, err := flavors_utils.IDFromName(client, s.Flavor)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed to find flavor by name: %s", err)
|
||||
err = fmt.Errorf(
|
||||
|
|
4
go.mod
4
go.mod
|
@ -53,8 +53,8 @@ require (
|
|||
github.com/google/go-querystring v1.0.0 // indirect
|
||||
github.com/google/shlex v0.0.0-20150127133951-6f45313302b9
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/gophercloud/gophercloud v0.2.0
|
||||
github.com/gophercloud/utils v0.0.0-20190124192022-a5c25e7a53a6
|
||||
github.com/gophercloud/gophercloud v0.12.0
|
||||
github.com/gophercloud/utils v0.0.0-20200508015959-b0167b94122c
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
|
||||
github.com/gorilla/websocket v0.0.0-20170319172727-a91eba7f9777 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.1.0
|
||||
|
|
17
go.sum
17
go.sum
|
@ -285,10 +285,11 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
|||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gophercloud/gophercloud v0.2.0 h1:lD2Bce2xBAMNNcFZ0dObTpXkGLlVIb33RPVUNVpw6ic=
|
||||
github.com/gophercloud/gophercloud v0.2.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
|
||||
github.com/gophercloud/utils v0.0.0-20190124192022-a5c25e7a53a6 h1:Cw/B8Bu7Rryomxf7bjc8zNfIyLgjxsDd91n0eGRWpuo=
|
||||
github.com/gophercloud/utils v0.0.0-20190124192022-a5c25e7a53a6/go.mod h1:wjDF8z83zTeg5eMLml5EBSlAhbF7G8DobyI1YsMuyzw=
|
||||
github.com/gophercloud/gophercloud v0.6.1-0.20191122030953-d8ac278c1c9d/go.mod h1:ozGNgr9KYOVATV5jsgHl/ceCDXGuguqOZAzoQ/2vcNM=
|
||||
github.com/gophercloud/gophercloud v0.12.0 h1:mZrie07npp6ODiwHZolTicr5jV8Ogn43AvAsSMm6Ork=
|
||||
github.com/gophercloud/gophercloud v0.12.0/go.mod h1:gmC5oQqMDOMO1t1gq5DquX/yAU808e/4mzjjDA76+Ss=
|
||||
github.com/gophercloud/utils v0.0.0-20200508015959-b0167b94122c h1:iawx2ojEQA7c+GmkaVO5sN+k8YONibXyDO8RlsC+1bs=
|
||||
github.com/gophercloud/utils v0.0.0-20200508015959-b0167b94122c/go.mod h1:ehWUbLQJPqS0Ep+CxeD559hsm9pthPXadJNKwZkp43w=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/websocket v0.0.0-20170319172727-a91eba7f9777 h1:JIM+OacoOJRU30xpjMf8sulYqjr0ViA3WDrTX6j/yDI=
|
||||
|
@ -646,7 +647,6 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
|||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3 h1:KYQXGkl6vs02hK7pK4eIbw0NpNPedieTSTEiJ//bwGs=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190222235706-ffb98f73852f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
|
@ -656,6 +656,7 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmV
|
|||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200422194213-44a606286825 h1:dSChiwOTvzwbHFTMq2l6uRardHH7/E6SqEkqccinS/o=
|
||||
golang.org/x/crypto v0.0.0-20200422194213-44a606286825/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
|
@ -718,6 +719,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
|
@ -745,7 +747,6 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -763,6 +764,7 @@ golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -809,6 +811,7 @@ golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtn
|
|||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191203134012-c197fd4bf371/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
|
@ -901,6 +904,8 @@ gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4
|
|||
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
|
|
@ -7,9 +7,9 @@ install:
|
|||
- GO111MODULE=off go get github.com/mattn/goveralls
|
||||
- GO111MODULE=off go get golang.org/x/tools/cmd/goimports
|
||||
go:
|
||||
- "1.10"
|
||||
- "1.11"
|
||||
- "1.12"
|
||||
- "1.13"
|
||||
- "tip"
|
||||
env:
|
||||
global:
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
description: |
|
||||
Run gophercloud acceptance test on master branch
|
||||
run: .zuul/playbooks/gophercloud-acceptance-test/run.yaml
|
||||
nodeset: ubuntu-bionic
|
||||
|
||||
- job:
|
||||
name: gophercloud-acceptance-test-ironic
|
||||
|
@ -19,6 +20,7 @@
|
|||
description: |
|
||||
Run gophercloud ironic acceptance test on master branch
|
||||
run: .zuul/playbooks/gophercloud-acceptance-test-ironic/run.yaml
|
||||
nodeset: ubuntu-bionic
|
||||
|
||||
- job:
|
||||
name: gophercloud-acceptance-test-stein
|
||||
|
@ -34,6 +36,7 @@
|
|||
parent: gophercloud-acceptance-test
|
||||
description: |
|
||||
Run gophercloud acceptance test on rocky branch
|
||||
nodeset: ubuntu-xenial
|
||||
vars:
|
||||
global_env:
|
||||
OS_BRANCH: stable/rocky
|
||||
|
@ -43,6 +46,7 @@
|
|||
parent: gophercloud-acceptance-test
|
||||
description: |
|
||||
Run gophercloud acceptance test on queens branch
|
||||
nodeset: ubuntu-xenial
|
||||
vars:
|
||||
global_env:
|
||||
OS_BRANCH: stable/queens
|
||||
|
@ -52,6 +56,7 @@
|
|||
parent: gophercloud-acceptance-test
|
||||
description: |
|
||||
Run gophercloud acceptance test on pike branch
|
||||
nodeset: ubuntu-xenial
|
||||
vars:
|
||||
global_env:
|
||||
OS_BRANCH: stable/pike
|
||||
|
@ -61,6 +66,7 @@
|
|||
parent: gophercloud-acceptance-test
|
||||
description: |
|
||||
Run gophercloud acceptance test on ocata branch
|
||||
nodeset: ubuntu-xenial
|
||||
vars:
|
||||
global_env:
|
||||
OS_BRANCH: stable/ocata
|
||||
|
@ -70,20 +76,11 @@
|
|||
parent: gophercloud-acceptance-test
|
||||
description: |
|
||||
Run gophercloud acceptance test on newton branch
|
||||
nodeset: ubuntu-xenial
|
||||
vars:
|
||||
global_env:
|
||||
OS_BRANCH: stable/newton
|
||||
|
||||
- job:
|
||||
name: gophercloud-acceptance-test-mitaka
|
||||
parent: gophercloud-acceptance-test
|
||||
description: |
|
||||
Run gophercloud acceptance test on mitaka branch
|
||||
vars:
|
||||
global_env:
|
||||
OS_BRANCH: stable/mitaka
|
||||
nodeset: ubuntu-trusty
|
||||
|
||||
- project:
|
||||
name: gophercloud/gophercloud
|
||||
check:
|
||||
|
@ -91,9 +88,6 @@
|
|||
- gophercloud-unittest
|
||||
- gophercloud-acceptance-test
|
||||
- gophercloud-acceptance-test-ironic
|
||||
recheck-mitaka:
|
||||
jobs:
|
||||
- gophercloud-acceptance-test-mitaka
|
||||
recheck-newton:
|
||||
jobs:
|
||||
- gophercloud-acceptance-test-newton
|
||||
|
|
|
@ -1,4 +1,343 @@
|
|||
## 0.3.0 (Unreleaesd)
|
||||
## 0.13.0 (Unlreleased)
|
||||
|
||||
## 0.12.0 (June 25, 2020)
|
||||
|
||||
UPGRADE NOTES
|
||||
|
||||
* The URL used in the `compute/v2/extensions/bootfromvolume` package has been changed from `os-volumes_boot` to `servers`.
|
||||
|
||||
IMPROVEMENTS
|
||||
|
||||
* The URL used in the `compute/v2/extensions/bootfromvolume` package has been changed from `os-volumes_boot` to `servers` [GH-1973](https://github.com/gophercloud/gophercloud/pull/1973)
|
||||
* Modify `baremetal/v1/nodes.LogicalDisk.PhysicalDisks` type to support physical disks hints [GH-1982](https://github.com/gophercloud/gophercloud/pull/1982)
|
||||
* Added `baremetalintrospection/httpbasic` which provides an HTTP Basic Auth client [GH-1986](https://github.com/gophercloud/gophercloud/pull/1986)
|
||||
* Added `baremetal/httpbasic` which provides an HTTP Basic Auth client [GH-1983](https://github.com/gophercloud/gophercloud/pull/1983)
|
||||
* Added `containerinfra/v1/clusters.CreateOpts.MergeLabels` [GH-1985](https://github.com/gophercloud/gophercloud/pull/1985)
|
||||
|
||||
BUG FIXES
|
||||
|
||||
* Changed `containerinfra/v1/clusters.Cluster.HealthStatusReason` from `string` to `map[string]interface{}` [GH-1968](https://github.com/gophercloud/gophercloud/pull/1968)
|
||||
* Fixed marshalling of `blockstorage/extensions/backups.ImportBackup.Metadata` [GH-1967](https://github.com/gophercloud/gophercloud/pull/1967)
|
||||
* Fixed typo of "OAUth" to "OAuth" in `identity/v3/extensions/oauth1` [GH-1969](https://github.com/gophercloud/gophercloud/pull/1969)
|
||||
* Fixed goroutine leak during reauthentication [GH-1978](https://github.com/gophercloud/gophercloud/pull/1978)
|
||||
|
||||
## 0.11.0 (May 14, 2020)
|
||||
|
||||
UPGRADE NOTES
|
||||
|
||||
* Object storage container and object names are now URL encoded [GH-1930](https://github.com/gophercloud/gophercloud/pull/1930)
|
||||
* All responses now have access to the returned headers. Please report any issues this has caused [GH-1942](https://github.com/gophercloud/gophercloud/pull/1942)
|
||||
* Changes have been made to the internal HTTP client to ensure response bodies are handled in a way that enables connections to be re-used more efficiently [GH-1952](https://github.com/gophercloud/gophercloud/pull/1952)
|
||||
|
||||
IMPROVEMENTS
|
||||
|
||||
* Added `objectstorage/v1/containers.BulkDelete` [GH-1930](https://github.com/gophercloud/gophercloud/pull/1930)
|
||||
* Added `objectstorage/v1/objects.BulkDelete` [GH-1930](https://github.com/gophercloud/gophercloud/pull/1930)
|
||||
* Object storage container and object names are now URL encoded [GH-1930](https://github.com/gophercloud/gophercloud/pull/1930)
|
||||
* All responses now have access to the returned headers [GH-1942](https://github.com/gophercloud/gophercloud/pull/1942)
|
||||
* Added `compute/v2/extensions/injectnetworkinfo.InjectNetworkInfo` [GH-1941](https://github.com/gophercloud/gophercloud/pull/1941)
|
||||
* Added `compute/v2/extensions/resetnetwork.ResetNetwork` [GH-1941](https://github.com/gophercloud/gophercloud/pull/1941)
|
||||
* Added `identity/v3/extensions/trusts.ListRoles` [GH-1939](https://github.com/gophercloud/gophercloud/pull/1939)
|
||||
* Added `identity/v3/extensions/trusts.GetRole` [GH-1939](https://github.com/gophercloud/gophercloud/pull/1939)
|
||||
* Added `identity/v3/extensions/trusts.CheckRole` [GH-1939](https://github.com/gophercloud/gophercloud/pull/1939)
|
||||
* Added `identity/v3/extensions/oauth1.Create` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
|
||||
* Added `identity/v3/extensions/oauth1.CreateConsumer` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
|
||||
* Added `identity/v3/extensions/oauth1.DeleteConsumer` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
|
||||
* Added `identity/v3/extensions/oauth1.ListConsumers` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
|
||||
* Added `identity/v3/extensions/oauth1.GetConsumer` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
|
||||
* Added `identity/v3/extensions/oauth1.UpdateConsumer` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
|
||||
* Added `identity/v3/extensions/oauth1.RequestToken` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
|
||||
* Added `identity/v3/extensions/oauth1.AuthorizeToken` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
|
||||
* Added `identity/v3/extensions/oauth1.CreateAccessToken` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
|
||||
* Added `identity/v3/extensions/oauth1.GetAccessToken` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
|
||||
* Added `identity/v3/extensions/oauth1.RevokeAccessToken` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
|
||||
* Added `identity/v3/extensions/oauth1.ListAccessTokens` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
|
||||
* Added `identity/v3/extensions/oauth1.ListAccessTokenRoles` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
|
||||
* Added `identity/v3/extensions/oauth1.GetAccessTokenRole` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
|
||||
* Added `networking/v2/extensions/agents.Update` [GH-1954](https://github.com/gophercloud/gophercloud/pull/1954)
|
||||
* Added `networking/v2/extensions/agents.Delete` [GH-1954](https://github.com/gophercloud/gophercloud/pull/1954)
|
||||
* Added `networking/v2/extensions/agents.ScheduleDHCPNetwork` [GH-1954](https://github.com/gophercloud/gophercloud/pull/1954)
|
||||
* Added `networking/v2/extensions/agents.RemoveDHCPNetwork` [GH-1954](https://github.com/gophercloud/gophercloud/pull/1954)
|
||||
* Added `identity/v3/projects.CreateOpts.Extra` [GH-1951](https://github.com/gophercloud/gophercloud/pull/1951)
|
||||
* Added `identity/v3/projects.CreateOpts.Options` [GH-1951](https://github.com/gophercloud/gophercloud/pull/1951)
|
||||
* Added `identity/v3/projects.UpdateOpts.Extra` [GH-1951](https://github.com/gophercloud/gophercloud/pull/1951)
|
||||
* Added `identity/v3/projects.UpdateOpts.Options` [GH-1951](https://github.com/gophercloud/gophercloud/pull/1951)
|
||||
* Added `identity/v3/projects.Project.Extra` [GH-1951](https://github.com/gophercloud/gophercloud/pull/1951)
|
||||
* Added `identity/v3/projects.Options.Options` [GH-1951](https://github.com/gophercloud/gophercloud/pull/1951)
|
||||
* Added `imageservice/v2/images.Image.OpenStackImageImportMethods` [GH-1962](https://github.com/gophercloud/gophercloud/pull/1962)
|
||||
* Added `imageservice/v2/images.Image.OpenStackImageStoreIDs` [GH-1962](https://github.com/gophercloud/gophercloud/pull/1962)
|
||||
|
||||
BUG FIXES
|
||||
|
||||
* Changed`identity/v3/extensions/trusts.Trust.RemainingUses` from `bool` to `int` [GH-1939](https://github.com/gophercloud/gophercloud/pull/1939)
|
||||
* Changed `identity/v3/applicationcredentials.CreateOpts.ExpiresAt` from `string` to `*time.Time` [GH-1937](https://github.com/gophercloud/gophercloud/pull/1937)
|
||||
* Fixed issue with unmarshalling/decoding slices of composed structs [GH-1964](https://github.com/gophercloud/gophercloud/pull/1964)
|
||||
|
||||
## 0.10.0 (April 12, 2020)
|
||||
|
||||
UPGRADE NOTES
|
||||
|
||||
* The various `IDFromName` convenience functions have been moved to https://github.com/gophercloud/utils [GH-1897](https://github.com/gophercloud/gophercloud/pull/1897)
|
||||
* `sharedfilesystems/v2/shares.GetExportLocations` was renamed to `sharedfilesystems/v2/shares.ListExportLocations` [GH-1932](https://github.com/gophercloud/gophercloud/pull/1932)
|
||||
|
||||
IMPROVEMENTS
|
||||
|
||||
* Added `blockstorage/extensions/volumeactions.SetBootable` [GH-1891](https://github.com/gophercloud/gophercloud/pull/1891)
|
||||
* Added `blockstorage/extensions/backups.Export` [GH-1894](https://github.com/gophercloud/gophercloud/pull/1894)
|
||||
* Added `blockstorage/extensions/backups.Import` [GH-1894](https://github.com/gophercloud/gophercloud/pull/1894)
|
||||
* Added `placement/v1/resourceproviders.GetTraits` [GH-1899](https://github.com/gophercloud/gophercloud/pull/1899)
|
||||
* Added the ability to authenticate with Amazon EC2 Credentials [GH-1900](https://github.com/gophercloud/gophercloud/pull/1900)
|
||||
* Added ability to list Nova services by binary and host [GH-1904](https://github.com/gophercloud/gophercloud/pull/1904)
|
||||
* Added `compute/v2/extensions/services.Update` [GH-1902](https://github.com/gophercloud/gophercloud/pull/1902)
|
||||
* Added system scope to v3 authentication [GH-1908](https://github.com/gophercloud/gophercloud/pull/1908)
|
||||
* Added `identity/v3/extensions/ec2tokens.ValidateS3Token` [GH-1906](https://github.com/gophercloud/gophercloud/pull/1906)
|
||||
* Added `containerinfra/v1/clusters.Cluster.HealthStatus` [GH-1910](https://github.com/gophercloud/gophercloud/pull/1910)
|
||||
* Added `containerinfra/v1/clusters.Cluster.HealthStatusReason` [GH-1910](https://github.com/gophercloud/gophercloud/pull/1910)
|
||||
* Added `loadbalancer/v2/amphorae.Failover` [GH-1912](https://github.com/gophercloud/gophercloud/pull/1912)
|
||||
* Added `identity/v3/extensions/ec2credentials.List` [GH-1916](https://github.com/gophercloud/gophercloud/pull/1916)
|
||||
* Added `identity/v3/extensions/ec2credentials.Get` [GH-1916](https://github.com/gophercloud/gophercloud/pull/1916)
|
||||
* Added `identity/v3/extensions/ec2credentials.Create` [GH-1916](https://github.com/gophercloud/gophercloud/pull/1916)
|
||||
* Added `identity/v3/extensions/ec2credentials.Delete` [GH-1916](https://github.com/gophercloud/gophercloud/pull/1916)
|
||||
* Added `ErrUnexpectedResponseCode.ResponseHeader` [GH-1919](https://github.com/gophercloud/gophercloud/pull/1919)
|
||||
* Added support for TOTP authentication [GH-1922](https://github.com/gophercloud/gophercloud/pull/1922)
|
||||
* `sharedfilesystems/v2/shares.GetExportLocations` was renamed to `sharedfilesystems/v2/shares.ListExportLocations` [GH-1932](https://github.com/gophercloud/gophercloud/pull/1932)
|
||||
* Added `sharedfilesystems/v2/shares.GetExportLocation` [GH-1932](https://github.com/gophercloud/gophercloud/pull/1932)
|
||||
* Added `sharedfilesystems/v2/shares.Revert` [GH-1931](https://github.com/gophercloud/gophercloud/pull/1931)
|
||||
* Added `sharedfilesystems/v2/shares.ResetStatus` [GH-1931](https://github.com/gophercloud/gophercloud/pull/1931)
|
||||
* Added `sharedfilesystems/v2/shares.ForceDelete` [GH-1931](https://github.com/gophercloud/gophercloud/pull/1931)
|
||||
* Added `sharedfilesystems/v2/shares.Unmanage` [GH-1931](https://github.com/gophercloud/gophercloud/pull/1931)
|
||||
* Added `blockstorage/v3/attachments.Create` [GH-1934](https://github.com/gophercloud/gophercloud/pull/1934)
|
||||
* Added `blockstorage/v3/attachments.List` [GH-1934](https://github.com/gophercloud/gophercloud/pull/1934)
|
||||
* Added `blockstorage/v3/attachments.Get` [GH-1934](https://github.com/gophercloud/gophercloud/pull/1934)
|
||||
* Added `blockstorage/v3/attachments.Update` [GH-1934](https://github.com/gophercloud/gophercloud/pull/1934)
|
||||
* Added `blockstorage/v3/attachments.Delete` [GH-1934](https://github.com/gophercloud/gophercloud/pull/1934)
|
||||
* Added `blockstorage/v3/attachments.Complete` [GH-1934](https://github.com/gophercloud/gophercloud/pull/1934)
|
||||
|
||||
BUG FIXES
|
||||
|
||||
* Fixed issue with Orchestration `get_file` only being able to read JSON and YAML files [GH-1915](https://github.com/gophercloud/gophercloud/pull/1915)
|
||||
|
||||
## 0.9.0 (March 10, 2020)
|
||||
|
||||
UPGRADE NOTES
|
||||
|
||||
* The way we implement new API result fields added by microversions has changed. Previously, we would declare a dedicated `ExtractFoo` function in a file called `microversions.go`. Now, we are declaring those fields inline of the original result struct as a pointer. [GH-1854](https://github.com/gophercloud/gophercloud/pull/1854)
|
||||
|
||||
* `compute/v2/servers.CreateOpts.Networks` has changed from `[]Network` to `interface{}` in order to support creating servers that have no networks. [GH-1884](https://github.com/gophercloud/gophercloud/pull/1884)
|
||||
|
||||
IMPROVEMENTS
|
||||
|
||||
* Added `compute/v2/extensions/instanceactions.List` [GH-1848](https://github.com/gophercloud/gophercloud/pull/1848)
|
||||
* Added `compute/v2/extensions/instanceactions.Get` [GH-1848](https://github.com/gophercloud/gophercloud/pull/1848)
|
||||
* Added `networking/v2/ports.List.FixedIPs` [GH-1849](https://github.com/gophercloud/gophercloud/pull/1849)
|
||||
* Added `identity/v3/extensions/trusts.List` [GH-1855](https://github.com/gophercloud/gophercloud/pull/1855)
|
||||
* Added `identity/v3/extensions/trusts.Get` [GH-1855](https://github.com/gophercloud/gophercloud/pull/1855)
|
||||
* Added `identity/v3/extensions/trusts.Trust.ExpiresAt` [GH-1857](https://github.com/gophercloud/gophercloud/pull/1857)
|
||||
* Added `identity/v3/extensions/trusts.Trust.DeletedAt` [GH-1857](https://github.com/gophercloud/gophercloud/pull/1857)
|
||||
* Added `compute/v2/extensions/instanceactions.InstanceActionDetail` [GH-1851](https://github.com/gophercloud/gophercloud/pull/1851)
|
||||
* Added `compute/v2/extensions/instanceactions.Event` [GH-1851](https://github.com/gophercloud/gophercloud/pull/1851)
|
||||
* Added `compute/v2/extensions/instanceactions.ListOpts` [GH-1858](https://github.com/gophercloud/gophercloud/pull/1858)
|
||||
* Added `objectstorage/v1/containers.UpdateOpts.TempURLKey` [GH-1864](https://github.com/gophercloud/gophercloud/pull/1864)
|
||||
* Added `objectstorage/v1/containers.UpdateOpts.TempURLKey2` [GH-1864](https://github.com/gophercloud/gophercloud/pull/1864)
|
||||
* Added `placement/v1/resourceproviders.GetUsages` [GH-1862](https://github.com/gophercloud/gophercloud/pull/1862)
|
||||
* Added `placement/v1/resourceproviders.GetInventories` [GH-1862](https://github.com/gophercloud/gophercloud/pull/1862)
|
||||
* Added `imageservice/v2/images.ReplaceImageMinRam` [GH-1867](https://github.com/gophercloud/gophercloud/pull/1867)
|
||||
* Added `objectstorage/v1/containers.UpdateOpts.TempURLKey` [GH-1865](https://github.com/gophercloud/gophercloud/pull/1865)
|
||||
* Added `objectstorage/v1/containers.CreateOpts.TempURLKey2` [GH-1865](https://github.com/gophercloud/gophercloud/pull/1865)
|
||||
* Added `blockstorage/extensions/volumetransfers.List` [GH-1869](https://github.com/gophercloud/gophercloud/pull/1869)
|
||||
* Added `blockstorage/extensions/volumetransfers.Create` [GH-1869](https://github.com/gophercloud/gophercloud/pull/1869)
|
||||
* Added `blockstorage/extensions/volumetransfers.Accept` [GH-1869](https://github.com/gophercloud/gophercloud/pull/1869)
|
||||
* Added `blockstorage/extensions/volumetransfers.Get` [GH-1869](https://github.com/gophercloud/gophercloud/pull/1869)
|
||||
* Added `blockstorage/extensions/volumetransfers.Delete` [GH-1869](https://github.com/gophercloud/gophercloud/pull/1869)
|
||||
* Added `blockstorage/extensions/backups.RestoreFromBackup` [GH-1871](https://github.com/gophercloud/gophercloud/pull/1871)
|
||||
* Added `blockstorage/v3/volumes.CreateOpts.BackupID` [GH-1871](https://github.com/gophercloud/gophercloud/pull/1871)
|
||||
* Added `blockstorage/v3/volumes.Volume.BackupID` [GH-1871](https://github.com/gophercloud/gophercloud/pull/1871)
|
||||
* Added `identity/v3/projects.ListOpts.Tags` [GH-1882](https://github.com/gophercloud/gophercloud/pull/1882)
|
||||
* Added `identity/v3/projects.ListOpts.TagsAny` [GH-1882](https://github.com/gophercloud/gophercloud/pull/1882)
|
||||
* Added `identity/v3/projects.ListOpts.NotTags` [GH-1882](https://github.com/gophercloud/gophercloud/pull/1882)
|
||||
* Added `identity/v3/projects.ListOpts.NotTagsAny` [GH-1882](https://github.com/gophercloud/gophercloud/pull/1882)
|
||||
* Added `identity/v3/projects.CreateOpts.Tags` [GH-1882](https://github.com/gophercloud/gophercloud/pull/1882)
|
||||
* Added `identity/v3/projects.UpdateOpts.Tags` [GH-1882](https://github.com/gophercloud/gophercloud/pull/1882)
|
||||
* Added `identity/v3/projects.Project.Tags` [GH-1882](https://github.com/gophercloud/gophercloud/pull/1882)
|
||||
* Changed `compute/v2/servers.CreateOpts.Networks` from `[]Network` to `interface{}` to support creating servers with no networks. [GH-1884](https://github.com/gophercloud/gophercloud/pull/1884)
|
||||
|
||||
|
||||
BUG FIXES
|
||||
|
||||
* Added support for `int64` headers, which were previously being silently dropped [GH-1860](https://github.com/gophercloud/gophercloud/pull/1860)
|
||||
* Allow image properties with empty values [GH-1875](https://github.com/gophercloud/gophercloud/pull/1875)
|
||||
* Fixed `compute/v2/extensions/extendedserverattributes.ServerAttributesExt.Userdata` JSON tag [GH-1881](https://github.com/gophercloud/gophercloud/pull/1881)
|
||||
|
||||
## 0.8.0 (February 8, 2020)
|
||||
|
||||
UPGRADE NOTES
|
||||
|
||||
* The behavior of `keymanager/v1/acls.SetOpts` has changed. Instead of a struct, it is now `[]SetOpt`. See [GH-1816](https://github.com/gophercloud/gophercloud/pull/1816) for implementation details.
|
||||
|
||||
IMPROVEMENTS
|
||||
|
||||
* The result of `containerinfra/v1/clusters.Resize` now returns only the UUID when calling `Extract`. This is a backwards-breaking change from the previous struct that was returned [GH-1649](https://github.com/gophercloud/gophercloud/pull/1649)
|
||||
* Added `compute/v2/extensions/shelveunshelve.Shelve` [GH-1799](https://github.com/gophercloud/gophercloud/pull/1799)
|
||||
* Added `compute/v2/extensions/shelveunshelve.ShelveOffload` [GH-1799](https://github.com/gophercloud/gophercloud/pull/1799)
|
||||
* Added `compute/v2/extensions/shelveunshelve.Unshelve` [GH-1799](https://github.com/gophercloud/gophercloud/pull/1799)
|
||||
* Added `containerinfra/v1/nodegroups.Get` [GH-1774](https://github.com/gophercloud/gophercloud/pull/1774)
|
||||
* Added `containerinfra/v1/nodegroups.List` [GH-1774](https://github.com/gophercloud/gophercloud/pull/1774)
|
||||
* Added `orchestration/v1/resourcetypes.List` [GH-1806](https://github.com/gophercloud/gophercloud/pull/1806)
|
||||
* Added `orchestration/v1/resourcetypes.GetSchema` [GH-1806](https://github.com/gophercloud/gophercloud/pull/1806)
|
||||
* Added `orchestration/v1/resourcetypes.GenerateTemplate` [GH-1806](https://github.com/gophercloud/gophercloud/pull/1806)
|
||||
* Added `keymanager/v1/acls.SetOpt` and changed `keymanager/v1/acls.SetOpts` to `[]SetOpt` [GH-1816](https://github.com/gophercloud/gophercloud/pull/1816)
|
||||
* Added `blockstorage/apiversions.List` [GH-458](https://github.com/gophercloud/gophercloud/pull/458)
|
||||
* Added `blockstorage/apiversions.Get` [GH-458](https://github.com/gophercloud/gophercloud/pull/458)
|
||||
* Added `StatusCodeError` interface and `GetStatusCode` convenience method [GH-1820](https://github.com/gophercloud/gophercloud/pull/1820)
|
||||
* Added pagination support to `compute/v2/extensions/usage.SingleTenant` [GH-1819](https://github.com/gophercloud/gophercloud/pull/1819)
|
||||
* Added pagination support to `compute/v2/extensions/usage.AllTenants` [GH-1819](https://github.com/gophercloud/gophercloud/pull/1819)
|
||||
* Added `placement/v1/resourceproviders.List` [GH-1815](https://github.com/gophercloud/gophercloud/pull/1815)
|
||||
* Allow `CreateMemberOptsBuilder` to be passed in `loadbalancer/v2/pools.Create` [GH-1822](https://github.com/gophercloud/gophercloud/pull/1822)
|
||||
* Added `Backup` to `loadbalancer/v2/pools.CreateMemberOpts` [GH-1824](https://github.com/gophercloud/gophercloud/pull/1824)
|
||||
* Added `MonitorAddress` to `loadbalancer/v2/pools.CreateMemberOpts` [GH-1824](https://github.com/gophercloud/gophercloud/pull/1824)
|
||||
* Added `MonitorPort` to `loadbalancer/v2/pools.CreateMemberOpts` [GH-1824](https://github.com/gophercloud/gophercloud/pull/1824)
|
||||
* Changed `Impersonation` to a non-required field in `identity/v3/extensions/trusts.CreateOpts` [GH-1818](https://github.com/gophercloud/gophercloud/pull/1818)
|
||||
* Added `InsertHeaders` to `loadbalancer/v2/listeners.UpdateOpts` [GH-1835]
|
||||
* Added `NUMATopology` to `baremetalintrospection/v1/introspection.Data` [GH-1842](https://github.com/gophercloud/gophercloud/pull/1842)
|
||||
* Added `placement/v1/resourceproviders.Create` [GH-1841](https://github.com/gophercloud/gophercloud/pull/1841)
|
||||
* Added `blockstorage/extensions/volumeactions.UploadImageOpts.Visibility` [GH-1873](https://github.com/gophercloud/gophercloud/pull/1873)
|
||||
* Added `blockstorage/extensions/volumeactions.UploadImageOpts.Protected` [GH-1873](https://github.com/gophercloud/gophercloud/pull/1873)
|
||||
* Added `blockstorage/extensions/volumeactions.VolumeImage.Visibility` [GH-1873](https://github.com/gophercloud/gophercloud/pull/1873)
|
||||
* Added `blockstorage/extensions/volumeactions.VolumeImage.Protected` [GH-1873](https://github.com/gophercloud/gophercloud/pull/1873)
|
||||
|
||||
BUG FIXES
|
||||
|
||||
* Changed `sort_key` to `sort_keys` in ` workflow/v2/crontriggers.ListOpts` [GH-1809](https://github.com/gophercloud/gophercloud/pull/1809)
|
||||
* Allow `blockstorage/extensions/schedulerstats.Capabilities.MaxOverSubscriptionRatio` to accept both string and int/float responses [GH-1817](https://github.com/gophercloud/gophercloud/pull/1817)
|
||||
* Fixed bug in `NewLoadBalancerV2` for situations when the LBaaS service was advertised without a `/v2.0` endpoint [GH-1829](https://github.com/gophercloud/gophercloud/pull/1829)
|
||||
* Fixed JSON tags in `baremetal/v1/ports.UpdateOperation` [GH-1840](https://github.com/gophercloud/gophercloud/pull/1840)
|
||||
* Fixed JSON tags in `networking/v2/extensions/lbaas/vips.commonResult.Extract()` [GH-1840](https://github.com/gophercloud/gophercloud/pull/1840)
|
||||
|
||||
## 0.7.0 (December 3, 2019)
|
||||
|
||||
IMPROVEMENTS
|
||||
|
||||
* Allow a token to be used directly for authentication instead of generating a new token based on a given token [GH-1752](https://github.com/gophercloud/gophercloud/pull/1752)
|
||||
* Moved `tags.ServerTagsExt` to servers.TagsExt` [GH-1760](https://github.com/gophercloud/gophercloud/pull/1760)
|
||||
* Added `tags`, `tags-any`, `not-tags`, and `not-tags-any` to `compute/v2/servers.ListOpts` [GH-1759](https://github.com/gophercloud/gophercloud/pull/1759)
|
||||
* Added `AccessRule` to `identity/v3/applicationcredentials` [GH-1758](https://github.com/gophercloud/gophercloud/pull/1758)
|
||||
* Gophercloud no longer returns an error when multiple endpoints are found. Instead, it will choose the first endpoint and discard the others [GH-1766](https://github.com/gophercloud/gophercloud/pull/1766)
|
||||
* Added `networking/v2/extensions/fwaas_v2/rules.Create` [GH-1768](https://github.com/gophercloud/gophercloud/pull/1768)
|
||||
* Added `networking/v2/extensions/fwaas_v2/rules.Delete` [GH-1771](https://github.com/gophercloud/gophercloud/pull/1771)
|
||||
* Added `loadbalancer/v2/providers.List` [GH-1765](https://github.com/gophercloud/gophercloud/pull/1765)
|
||||
* Added `networking/v2/extensions/fwaas_v2/rules.Get` [GH-1772](https://github.com/gophercloud/gophercloud/pull/1772)
|
||||
* Added `networking/v2/extensions/fwaas_v2/rules.Update` [GH-1776](https://github.com/gophercloud/gophercloud/pull/1776)
|
||||
* Added `networking/v2/extensions/fwaas_v2/rules.List` [GH-1783](https://github.com/gophercloud/gophercloud/pull/1783)
|
||||
* Added `MaxRetriesDown` into `loadbalancer/v2/monitors.CreateOpts` [GH-1785](https://github.com/gophercloud/gophercloud/pull/1785)
|
||||
* Added `MaxRetriesDown` into `loadbalancer/v2/monitors.UpdateOpts` [GH-1786](https://github.com/gophercloud/gophercloud/pull/1786)
|
||||
* Added `MaxRetriesDown` into `loadbalancer/v2/monitors.Monitor` [GH-1787](https://github.com/gophercloud/gophercloud/pull/1787)
|
||||
* Added `MaxRetriesDown` into `loadbalancer/v2/monitors.ListOpts` [GH-1788](https://github.com/gophercloud/gophercloud/pull/1788)
|
||||
* Updated `go.mod` dependencies, specifically to account for CVE-2019-11840 with `golang.org/x/crypto` [GH-1793](https://github.com/gophercloud/gophercloud/pull/1788)
|
||||
|
||||
## 0.6.0 (October 17, 2019)
|
||||
|
||||
UPGRADE NOTES
|
||||
|
||||
* The way reauthentication works has been refactored. This should not cause a problem, but please report bugs if it does. See [GH-1746](https://github.com/gophercloud/gophercloud/pull/1746) for more information.
|
||||
|
||||
IMPROVEMENTS
|
||||
|
||||
* Added `networking/v2/extensions/quotas.Get` [GH-1742](https://github.com/gophercloud/gophercloud/pull/1742)
|
||||
* Added `networking/v2/extensions/quotas.Update` [GH-1747](https://github.com/gophercloud/gophercloud/pull/1747)
|
||||
* Refactored the reauthentication implementation to use goroutines and added a check to prevent an infinite loop in certain situations. [GH-1746](https://github.com/gophercloud/gophercloud/pull/1746)
|
||||
|
||||
BUG FIXES
|
||||
|
||||
* Changed `Flavor` to `FlavorID` in `loadbalancer/v2/loadbalancers` [GH-1744](https://github.com/gophercloud/gophercloud/pull/1744)
|
||||
* Changed `Flavor` to `FlavorID` in `networking/v2/extensions/lbaas_v2/loadbalancers` [GH-1744](https://github.com/gophercloud/gophercloud/pull/1744)
|
||||
* The `go-yaml` dependency was updated to `v2.2.4` to fix possible DDOS vulnerabilities [GH-1751](https://github.com/gophercloud/gophercloud/pull/1751)
|
||||
|
||||
## 0.5.0 (October 13, 2019)
|
||||
|
||||
IMPROVEMENTS
|
||||
|
||||
* Added `VolumeType` to `compute/v2/extensions/bootfromvolume.BlockDevice`[GH-1690](https://github.com/gophercloud/gophercloud/pull/1690)
|
||||
* Added `networking/v2/extensions/layer3/portforwarding.List` [GH-1688](https://github.com/gophercloud/gophercloud/pull/1688)
|
||||
* Added `networking/v2/extensions/layer3/portforwarding.Get` [GH-1698](https://github.com/gophercloud/gophercloud/pull/1696)
|
||||
* Added `compute/v2/extensions/tags.ReplaceAll` [GH-1696](https://github.com/gophercloud/gophercloud/pull/1696)
|
||||
* Added `compute/v2/extensions/tags.Add` [GH-1696](https://github.com/gophercloud/gophercloud/pull/1696)
|
||||
* Added `networking/v2/extensions/layer3/portforwarding.Update` [GH-1703](https://github.com/gophercloud/gophercloud/pull/1703)
|
||||
* Added `ExtractDomain` method to token results in `identity/v3/tokens` [GH-1712](https://github.com/gophercloud/gophercloud/pull/1712)
|
||||
* Added `AllowedCIDRs` to `loadbalancer/v2/listeners.CreateOpts` [GH-1710](https://github.com/gophercloud/gophercloud/pull/1710)
|
||||
* Added `AllowedCIDRs` to `loadbalancer/v2/listeners.UpdateOpts` [GH-1710](https://github.com/gophercloud/gophercloud/pull/1710)
|
||||
* Added `AllowedCIDRs` to `loadbalancer/v2/listeners.Listener` [GH-1710](https://github.com/gophercloud/gophercloud/pull/1710)
|
||||
* Added `compute/v2/extensions/tags.Add` [GH-1695](https://github.com/gophercloud/gophercloud/pull/1695)
|
||||
* Added `compute/v2/extensions/tags.ReplaceAll` [GH-1694](https://github.com/gophercloud/gophercloud/pull/1694)
|
||||
* Added `compute/v2/extensions/tags.Delete` [GH-1699](https://github.com/gophercloud/gophercloud/pull/1699)
|
||||
* Added `compute/v2/extensions/tags.DeleteAll` [GH-1700](https://github.com/gophercloud/gophercloud/pull/1700)
|
||||
* Added `ImageStatusImporting` as an image status [GH-1725](https://github.com/gophercloud/gophercloud/pull/1725)
|
||||
* Added `ByPath` to `baremetalintrospection/v1/introspection.RootDiskType` [GH-1730](https://github.com/gophercloud/gophercloud/pull/1730)
|
||||
* Added `AttachedVolumes` to `compute/v2/servers.Server` [GH-1732](https://github.com/gophercloud/gophercloud/pull/1732)
|
||||
* Enable unmarshaling server tags to a `compute/v2/servers.Server` struct [GH-1734]
|
||||
* Allow setting an empty members list in `loadbalancer/v2/pools.BatchUpdateMembers` [GH-1736](https://github.com/gophercloud/gophercloud/pull/1736)
|
||||
* Allow unsetting members' subnet ID and name in `loadbalancer/v2/pools.BatchUpdateMemberOpts` [GH-1738](https://github.com/gophercloud/gophercloud/pull/1738)
|
||||
|
||||
BUG FIXES
|
||||
|
||||
* Changed struct type for options in `networking/v2/extensions/lbaas_v2/listeners` to `UpdateOptsBuilder` interface instead of specific UpdateOpts type [GH-1705](https://github.com/gophercloud/gophercloud/pull/1705)
|
||||
* Changed struct type for options in `networking/v2/extensions/lbaas_v2/loadbalancers` to `UpdateOptsBuilder` interface instead of specific UpdateOpts type [GH-1706](https://github.com/gophercloud/gophercloud/pull/1706)
|
||||
* Fixed issue with `blockstorage/v1/volumes.Create` where the response was expected to be 202 [GH-1720](https://github.com/gophercloud/gophercloud/pull/1720)
|
||||
* Changed `DefaultTlsContainerRef` from `string` to `*string` in `loadbalancer/v2/listeners.UpdateOpts` to allow the value to be removed during update. [GH-1723](https://github.com/gophercloud/gophercloud/pull/1723)
|
||||
* Changed `SniContainerRefs` from `[]string{}` to `*[]string{}` in `loadbalancer/v2/listeners.UpdateOpts` to allow the value to be removed during update. [GH-1723](https://github.com/gophercloud/gophercloud/pull/1723)
|
||||
* Changed `DefaultTlsContainerRef` from `string` to `*string` in `networking/v2/extensions/lbaas_v2/listeners.UpdateOpts` to allow the value to be removed during update. [GH-1723](https://github.com/gophercloud/gophercloud/pull/1723)
|
||||
* Changed `SniContainerRefs` from `[]string{}` to `*[]string{}` in `networking/v2/extensions/lbaas_v2/listeners.UpdateOpts` to allow the value to be removed during update. [GH-1723](https://github.com/gophercloud/gophercloud/pull/1723)
|
||||
|
||||
|
||||
## 0.4.0 (September 3, 2019)
|
||||
|
||||
IMPROVEMENTS
|
||||
|
||||
* Added `blockstorage/extensions/quotasets.results.QuotaSet.Groups` [GH-1668](https://github.com/gophercloud/gophercloud/pull/1668)
|
||||
* Added `blockstorage/extensions/quotasets.results.QuotaUsageSet.Groups` [GH-1668](https://github.com/gophercloud/gophercloud/pull/1668)
|
||||
* Added `containerinfra/v1/clusters.CreateOpts.FixedNetwork` [GH-1674](https://github.com/gophercloud/gophercloud/pull/1674)
|
||||
* Added `containerinfra/v1/clusters.CreateOpts.FixedSubnet` [GH-1676](https://github.com/gophercloud/gophercloud/pull/1676)
|
||||
* Added `containerinfra/v1/clusters.CreateOpts.FloatingIPEnabled` [GH-1677](https://github.com/gophercloud/gophercloud/pull/1677)
|
||||
* Added `CreatedAt` and `UpdatedAt` to `loadbalancers/v2/loadbalancers.LoadBalancer` [GH-1681](https://github.com/gophercloud/gophercloud/pull/1681)
|
||||
* Added `networking/v2/extensions/layer3/portforwarding.Create` [GH-1651](https://github.com/gophercloud/gophercloud/pull/1651)
|
||||
* Added `networking/v2/extensions/agents.ListDHCPNetworks` [GH-1686](https://github.com/gophercloud/gophercloud/pull/1686)
|
||||
* Added `networking/v2/extensions/layer3/portforwarding.Delete` [GH-1652](https://github.com/gophercloud/gophercloud/pull/1652)
|
||||
* Added `compute/v2/extensions/tags.List` [GH-1679](https://github.com/gophercloud/gophercloud/pull/1679)
|
||||
* Added `compute/v2/extensions/tags.Check` [GH-1679](https://github.com/gophercloud/gophercloud/pull/1679)
|
||||
|
||||
BUG FIXES
|
||||
|
||||
* Changed `identity/v3/endpoints.ListOpts.RegionID` from `int` to `string` [GH-1664](https://github.com/gophercloud/gophercloud/pull/1664)
|
||||
* Fixed issue where older time formats in some networking APIs/resources were unable to be parsed [GH-1671](https://github.com/gophercloud/gophercloud/pull/1664)
|
||||
* Changed `SATA`, `SCSI`, and `SAS` types to `InterfaceType` in `baremetal/v1/nodes` [GH-1683]
|
||||
|
||||
## 0.3.0 (July 31, 2019)
|
||||
|
||||
IMPROVEMENTS
|
||||
|
||||
* Added `baremetal/apiversions.List` [GH-1577](https://github.com/gophercloud/gophercloud/pull/1577)
|
||||
* Added `baremetal/apiversions.Get` [GH-1577](https://github.com/gophercloud/gophercloud/pull/1577)
|
||||
* Added `compute/v2/extensions/servergroups.CreateOpts.Policy` [GH-1636](https://github.com/gophercloud/gophercloud/pull/1636)
|
||||
* Added `identity/v3/extensions/trusts.Create` [GH-1644](https://github.com/gophercloud/gophercloud/pull/1644)
|
||||
* Added `identity/v3/extensions/trusts.Delete` [GH-1644](https://github.com/gophercloud/gophercloud/pull/1644)
|
||||
* Added `CreatedAt` and `UpdatedAt` to `networking/v2/extensions/layer3/floatingips.FloatingIP` [GH-1647](https://github.com/gophercloud/gophercloud/issues/1646)
|
||||
* Added `CreatedAt` and `UpdatedAt` to `networking/v2/extensions/security/groups.SecGroup` [GH-1654](https://github.com/gophercloud/gophercloud/issues/1654)
|
||||
* Added `CreatedAt` and `UpdatedAt` to `networking/v2/networks.Network` [GH-1657](https://github.com/gophercloud/gophercloud/issues/1657)
|
||||
* Added `keymanager/v1/containers.CreateSecretRef` [GH-1659](https://github.com/gophercloud/gophercloud/issues/1659)
|
||||
* Added `keymanager/v1/containers.DeleteSecretRef` [GH-1659](https://github.com/gophercloud/gophercloud/issues/1659)
|
||||
* Added `sharedfilesystems/v2/shares.GetMetadata` [GH-1656](https://github.com/gophercloud/gophercloud/issues/1656)
|
||||
* Added `sharedfilesystems/v2/shares.GetMetadatum` [GH-1656](https://github.com/gophercloud/gophercloud/issues/1656)
|
||||
* Added `sharedfilesystems/v2/shares.SetMetadata` [GH-1656](https://github.com/gophercloud/gophercloud/issues/1656)
|
||||
* Added `sharedfilesystems/v2/shares.UpdateMetadata` [GH-1656](https://github.com/gophercloud/gophercloud/issues/1656)
|
||||
* Added `sharedfilesystems/v2/shares.DeleteMetadatum` [GH-1656](https://github.com/gophercloud/gophercloud/issues/1656)
|
||||
* Added `sharedfilesystems/v2/sharetypes.IDFromName` [GH-1662](https://github.com/gophercloud/gophercloud/issues/1662)
|
||||
|
||||
|
||||
|
||||
BUG FIXES
|
||||
|
||||
* Changed `baremetal/v1/nodes.CleanStep.Args` from `map[string]string` to `map[string]interface{}` [GH-1638](https://github.com/gophercloud/gophercloud/pull/1638)
|
||||
* Removed `URLPath` and `ExpectedCodes` from `loadbalancer/v2/monitors.ToMonitorCreateMap` since Octavia now provides default values when these fields are not specified [GH-1640](https://github.com/gophercloud/gophercloud/pull/1540)
|
||||
|
||||
|
||||
## 0.2.0 (June 17, 2019)
|
||||
|
||||
|
|
|
@ -45,6 +45,9 @@ type AuthOptions struct {
|
|||
|
||||
Password string `json:"password,omitempty"`
|
||||
|
||||
// Passcode is used in TOTP authentication method
|
||||
Passcode string `json:"passcode,omitempty"`
|
||||
|
||||
// At most one of DomainID and DomainName must be provided if using Username
|
||||
// with Identity V3. Otherwise, either are optional.
|
||||
DomainID string `json:"-"`
|
||||
|
@ -98,6 +101,7 @@ type AuthScope struct {
|
|||
ProjectName string
|
||||
DomainID string
|
||||
DomainName string
|
||||
System bool
|
||||
}
|
||||
|
||||
// ToTokenV2CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder
|
||||
|
@ -133,6 +137,8 @@ func (opts AuthOptions) ToTokenV2CreateMap() (map[string]interface{}, error) {
|
|||
return map[string]interface{}{"auth": authMap}, nil
|
||||
}
|
||||
|
||||
// ToTokenV3CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder
|
||||
// interface in the v3 tokens package
|
||||
func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) {
|
||||
type domainReq struct {
|
||||
ID *string `json:"id,omitempty"`
|
||||
|
@ -148,7 +154,8 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
|
|||
type userReq struct {
|
||||
ID *string `json:"id,omitempty"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Password *string `json:"password,omitempty"`
|
||||
Passcode *string `json:"passcode,omitempty"`
|
||||
Domain *domainReq `json:"domain,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -167,11 +174,16 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
|
|||
Secret *string `json:"secret,omitempty"`
|
||||
}
|
||||
|
||||
type totpReq struct {
|
||||
User *userReq `json:"user,omitempty"`
|
||||
}
|
||||
|
||||
type identityReq struct {
|
||||
Methods []string `json:"methods"`
|
||||
Password *passwordReq `json:"password,omitempty"`
|
||||
Token *tokenReq `json:"token,omitempty"`
|
||||
ApplicationCredential *applicationCredentialReq `json:"application_credential,omitempty"`
|
||||
TOTP *totpReq `json:"totp,omitempty"`
|
||||
}
|
||||
|
||||
type authReq struct {
|
||||
|
@ -186,7 +198,7 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
|
|||
// if insufficient or incompatible information is present.
|
||||
var req request
|
||||
|
||||
if opts.Password == "" {
|
||||
if opts.Password == "" && opts.Passcode == "" {
|
||||
if opts.TokenID != "" {
|
||||
// Because we aren't using password authentication, it's an error to also provide any of the user-based authentication
|
||||
// parameters.
|
||||
|
@ -274,7 +286,14 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
|
|||
}
|
||||
} else {
|
||||
// Password authentication.
|
||||
req.Auth.Identity.Methods = []string{"password"}
|
||||
if opts.Password != "" {
|
||||
req.Auth.Identity.Methods = append(req.Auth.Identity.Methods, "password")
|
||||
}
|
||||
|
||||
// TOTP authentication.
|
||||
if opts.Passcode != "" {
|
||||
req.Auth.Identity.Methods = append(req.Auth.Identity.Methods, "totp")
|
||||
}
|
||||
|
||||
// At least one of Username and UserID must be specified.
|
||||
if opts.Username == "" && opts.UserID == "" {
|
||||
|
@ -298,23 +317,46 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
|
|||
}
|
||||
|
||||
// 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.Password != "" {
|
||||
req.Auth.Identity.Password = &passwordReq{
|
||||
User: userReq{
|
||||
Name: &opts.Username,
|
||||
Password: &opts.Password,
|
||||
Domain: &domainReq{ID: &opts.DomainID},
|
||||
},
|
||||
}
|
||||
}
|
||||
if opts.Passcode != "" {
|
||||
req.Auth.Identity.TOTP = &totpReq{
|
||||
User: &userReq{
|
||||
Name: &opts.Username,
|
||||
Passcode: &opts.Passcode,
|
||||
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.Password != "" {
|
||||
req.Auth.Identity.Password = &passwordReq{
|
||||
User: userReq{
|
||||
Name: &opts.Username,
|
||||
Password: &opts.Password,
|
||||
Domain: &domainReq{Name: &opts.DomainName},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Passcode != "" {
|
||||
req.Auth.Identity.TOTP = &totpReq{
|
||||
User: &userReq{
|
||||
Name: &opts.Username,
|
||||
Passcode: &opts.Passcode,
|
||||
Domain: &domainReq{Name: &opts.DomainName},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -329,8 +371,22 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
|
|||
}
|
||||
|
||||
// Configure the request for UserID and Password authentication.
|
||||
req.Auth.Identity.Password = &passwordReq{
|
||||
User: userReq{ID: &opts.UserID, Password: opts.Password},
|
||||
if opts.Password != "" {
|
||||
req.Auth.Identity.Password = &passwordReq{
|
||||
User: userReq{
|
||||
ID: &opts.UserID,
|
||||
Password: &opts.Password,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Passcode != "" {
|
||||
req.Auth.Identity.TOTP = &totpReq{
|
||||
User: &userReq{
|
||||
ID: &opts.UserID,
|
||||
Passcode: &opts.Passcode,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -347,6 +403,8 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
|
|||
return b, nil
|
||||
}
|
||||
|
||||
// ToTokenV3ScopeMap builds a scope from AuthOptions and satisfies interface in
|
||||
// the v3 tokens package.
|
||||
func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
|
||||
// For backwards compatibility.
|
||||
// If AuthOptions.Scope was not set, try to determine it.
|
||||
|
@ -364,6 +422,14 @@ func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
|
|||
}
|
||||
}
|
||||
|
||||
if opts.Scope.System {
|
||||
return map[string]interface{}{
|
||||
"system": map[string]interface{}{
|
||||
"all": true,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
if opts.Scope.ProjectName != "" {
|
||||
// ProjectName provided: either DomainID or DomainName must also be supplied.
|
||||
// ProjectID may not be supplied.
|
||||
|
@ -433,5 +499,16 @@ func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
|
|||
}
|
||||
|
||||
func (opts AuthOptions) CanReauth() bool {
|
||||
if opts.Passcode != "" {
|
||||
// cannot reauth using TOTP passcode
|
||||
return false
|
||||
}
|
||||
|
||||
return opts.AllowReauth
|
||||
}
|
||||
|
||||
// ToTokenV3HeadersMap allows AuthOptions to satisfy the AuthOptionsBuilder
|
||||
// interface in the v3 tokens package.
|
||||
func (opts *AuthOptions) ToTokenV3HeadersMap(map[string]interface{}) (map[string]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package gophercloud
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
@ -77,11 +78,12 @@ func (e ErrMissingAnyoneOfEnvironmentVariables) Error() string {
|
|||
// those listed in OkCodes is encountered.
|
||||
type ErrUnexpectedResponseCode struct {
|
||||
BaseError
|
||||
URL string
|
||||
Method string
|
||||
Expected []int
|
||||
Actual int
|
||||
Body []byte
|
||||
URL string
|
||||
Method string
|
||||
Expected []int
|
||||
Actual int
|
||||
Body []byte
|
||||
ResponseHeader http.Header
|
||||
}
|
||||
|
||||
func (e ErrUnexpectedResponseCode) Error() string {
|
||||
|
@ -92,6 +94,23 @@ func (e ErrUnexpectedResponseCode) Error() string {
|
|||
return e.choseErrString()
|
||||
}
|
||||
|
||||
// GetStatusCode returns the actual status code of the error.
|
||||
func (e ErrUnexpectedResponseCode) GetStatusCode() int {
|
||||
return e.Actual
|
||||
}
|
||||
|
||||
// StatusCodeError is a convenience interface to easily allow access to the
|
||||
// status code field of the various ErrDefault* types.
|
||||
//
|
||||
// By using this interface, you only have to make a single type cast of
|
||||
// the returned error to err.(StatusCodeError) and then call GetStatusCode()
|
||||
// instead of having a large switch statement checking for each of the
|
||||
// ErrDefault* types.
|
||||
type StatusCodeError interface {
|
||||
Error() string
|
||||
GetStatusCode() int
|
||||
}
|
||||
|
||||
// ErrDefault400 is the default error type returned on a 400 HTTP response code.
|
||||
type ErrDefault400 struct {
|
||||
ErrUnexpectedResponseCode
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
module github.com/gophercloud/gophercloud
|
||||
|
||||
require (
|
||||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67
|
||||
golang.org/x/sys v0.0.0-20190209173611-3b5209105503 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.2
|
||||
golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e
|
||||
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933 // indirect
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
|
||||
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 // indirect
|
||||
golang.org/x/text v0.3.2 // indirect
|
||||
golang.org/x/tools v0.0.0-20191203134012-c197fd4bf371 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.7
|
||||
)
|
||||
|
|
|
@ -1,8 +1,26 @@
|
|||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67 h1:ng3VDlRp5/DHpSWl02R4rM9I+8M2rhmsuLwAMmkLQWE=
|
||||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/sys v0.0.0-20190209173611-3b5209105503 h1:5SvYFrOM3W8Mexn9/oA44Ji7vhXAZQ9hiP+1Q/DMrWg=
|
||||
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e h1:egKlR8l7Nu9vHGWbcUV8lqR4987UfUbBd7GbhqGzNYU=
|
||||
golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191203134012-c197fd4bf371/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
|
|
@ -38,6 +38,7 @@ func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) {
|
|||
username := os.Getenv("OS_USERNAME")
|
||||
userID := os.Getenv("OS_USERID")
|
||||
password := os.Getenv("OS_PASSWORD")
|
||||
passcode := os.Getenv("OS_PASSCODE")
|
||||
tenantID := os.Getenv("OS_TENANT_ID")
|
||||
tenantName := os.Getenv("OS_TENANT_NAME")
|
||||
domainID := os.Getenv("OS_DOMAIN_ID")
|
||||
|
@ -73,8 +74,9 @@ func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) {
|
|||
}
|
||||
}
|
||||
|
||||
if password == "" && applicationCredentialID == "" && applicationCredentialName == "" {
|
||||
if password == "" && passcode == "" && applicationCredentialID == "" && applicationCredentialName == "" {
|
||||
err := gophercloud.ErrMissingEnvironmentVariable{
|
||||
// silently ignore TOTP passcode warning, since it is not a common auth method
|
||||
EnvironmentVariable: "OS_PASSWORD",
|
||||
}
|
||||
return nilOptions, err
|
||||
|
@ -112,6 +114,7 @@ func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) {
|
|||
UserID: userID,
|
||||
Username: username,
|
||||
Password: password,
|
||||
Passcode: passcode,
|
||||
TenantID: tenantID,
|
||||
TenantName: tenantName,
|
||||
DomainID: domainID,
|
||||
|
|
|
@ -82,5 +82,16 @@ Example of Initializing a Volume Connection
|
|||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
Example of Setting a Volume's Bootable status
|
||||
|
||||
options := volumeactions.BootableOpts{
|
||||
Bootable: true,
|
||||
}
|
||||
|
||||
err := volumeactions.SetBootable(client, volume.ID, options).ExtractErr()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
*/
|
||||
package volumeactions
|
||||
|
|
|
@ -47,18 +47,20 @@ func Attach(client *gophercloud.ServiceClient, id string, opts AttachOptsBuilder
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
|
||||
resp, err := client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// BeginDetach will mark the volume as detaching.
|
||||
func BeginDetaching(client *gophercloud.ServiceClient, id string) (r BeginDetachingResult) {
|
||||
b := map[string]interface{}{"os-begin_detaching": make(map[string]interface{})}
|
||||
_, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
|
||||
resp, err := client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -87,27 +89,30 @@ func Detach(client *gophercloud.ServiceClient, id string, opts DetachOptsBuilder
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
|
||||
resp, err := client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Reserve will reserve a volume based on volume ID.
|
||||
func Reserve(client *gophercloud.ServiceClient, id string) (r ReserveResult) {
|
||||
b := map[string]interface{}{"os-reserve": make(map[string]interface{})}
|
||||
_, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
|
||||
resp, err := client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200, 201, 202},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Unreserve will unreserve a volume based on volume ID.
|
||||
func Unreserve(client *gophercloud.ServiceClient, id string) (r UnreserveResult) {
|
||||
b := map[string]interface{}{"os-unreserve": make(map[string]interface{})}
|
||||
_, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
|
||||
resp, err := client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200, 201, 202},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -145,9 +150,10 @@ func InitializeConnection(client *gophercloud.ServiceClient, id string, opts Ini
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
resp, err := client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200, 201, 202},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -183,9 +189,10 @@ func TerminateConnection(client *gophercloud.ServiceClient, id string, opts Term
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
|
||||
resp, err := client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -216,9 +223,10 @@ func ExtendSize(client *gophercloud.ServiceClient, id string, opts ExtendSizeOpt
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
|
||||
resp, err := client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -241,6 +249,14 @@ type UploadImageOpts struct {
|
|||
|
||||
// Force image creation, usable if volume attached to instance.
|
||||
Force bool `json:"force,omitempty"`
|
||||
|
||||
// Visibility defines who can see/use the image.
|
||||
// supported since 3.1 microversion
|
||||
Visibility string `json:"visibility,omitempty"`
|
||||
|
||||
// whether the image is not deletable.
|
||||
// supported since 3.1 microversion
|
||||
Protected bool `json:"protected,omitempty"`
|
||||
}
|
||||
|
||||
// ToVolumeUploadImageMap assembles a request body based on the contents of a
|
||||
|
@ -256,15 +272,17 @@ func UploadImage(client *gophercloud.ServiceClient, id string, opts UploadImageO
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
resp, err := client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// ForceDelete will delete the volume regardless of state.
|
||||
func ForceDelete(client *gophercloud.ServiceClient, id string) (r ForceDeleteResult) {
|
||||
_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"os-force_delete": ""}, nil, nil)
|
||||
resp, err := client.Post(actionURL(client, id), map[string]interface{}{"os-force_delete": ""}, nil, nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -293,8 +311,35 @@ func SetImageMetadata(client *gophercloud.ServiceClient, id string, opts ImageMe
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
resp, err := client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// BootableOpts contains options for setting bootable status to a volume.
|
||||
type BootableOpts struct {
|
||||
// Enables or disables the bootable attribute. You can boot an instance from a bootable volume.
|
||||
Bootable bool `json:"bootable"`
|
||||
}
|
||||
|
||||
// ToBootableMap assembles a request body based on the contents of a
|
||||
// BootableOpts.
|
||||
func (opts BootableOpts) ToBootableMap() (map[string]interface{}, error) {
|
||||
return gophercloud.BuildRequestBody(opts, "os-set_bootable")
|
||||
}
|
||||
|
||||
// SetBootable will set bootable status on a volume based on the values in BootableOpts
|
||||
func SetBootable(client *gophercloud.ServiceClient, id string, opts BootableOpts) (r SetBootableResult) {
|
||||
b, err := opts.ToBootableMap()
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
resp, err := client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -35,6 +35,12 @@ type SetImageMetadataResult struct {
|
|||
gophercloud.ErrResult
|
||||
}
|
||||
|
||||
// SetBootableResult contains the response body and error from a SetBootable
|
||||
// request.
|
||||
type SetBootableResult struct {
|
||||
gophercloud.ErrResult
|
||||
}
|
||||
|
||||
// ReserveResult contains the response body and error from a Reserve request.
|
||||
type ReserveResult struct {
|
||||
gophercloud.ErrResult
|
||||
|
@ -157,6 +163,14 @@ type VolumeImage struct {
|
|||
// Current status of the volume.
|
||||
Status string `json:"status"`
|
||||
|
||||
// Visibility defines who can see/use the image.
|
||||
// supported since 3.1 microversion
|
||||
Visibility string `json:"visibility"`
|
||||
|
||||
// whether the image is not deletable.
|
||||
// supported since 3.1 microversion
|
||||
Protected bool `json:"protected"`
|
||||
|
||||
// The date when this volume was last updated.
|
||||
UpdatedAt time.Time `json:"-"`
|
||||
|
||||
|
|
26
vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/doc.go
generated
vendored
26
vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/doc.go
generated
vendored
|
@ -1,5 +1,23 @@
|
|||
// Package volumes provides information and interaction with volumes in the
|
||||
// OpenStack Block Storage service. A volume is a detachable block storage
|
||||
// device, akin to a USB hard drive. It can only be attached to one instance at
|
||||
// a time.
|
||||
/*
|
||||
Package volumes provides information and interaction with volumes in the
|
||||
OpenStack Block Storage service. A volume is a detachable block storage
|
||||
device, akin to a USB hard drive. It can only be attached to one instance at
|
||||
a time.
|
||||
|
||||
Example to create a Volume from a Backup
|
||||
|
||||
backupID := "20c792f0-bb03-434f-b653-06ef238e337e"
|
||||
options := volumes.CreateOpts{
|
||||
Name: "vol-001",
|
||||
BackupID: &backupID,
|
||||
}
|
||||
|
||||
client.Microversion = "3.47"
|
||||
volume, err := volumes.Create(client, options).Extract()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(volume)
|
||||
*/
|
||||
package volumes
|
||||
|
|
53
vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/requests.go
generated
vendored
53
vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/requests.go
generated
vendored
|
@ -16,7 +16,7 @@ type CreateOptsBuilder interface {
|
|||
// see the Volume object.
|
||||
type CreateOpts struct {
|
||||
// The size of the volume, in GB
|
||||
Size int `json:"size" required:"true"`
|
||||
Size int `json:"size,omitempty"`
|
||||
// The availability zone
|
||||
AvailabilityZone string `json:"availability_zone,omitempty"`
|
||||
// ConsistencyGroupID is the ID of a consistency group
|
||||
|
@ -36,6 +36,9 @@ type CreateOpts struct {
|
|||
// The ID of the image from which you want to create the volume.
|
||||
// Required to create a bootable volume.
|
||||
ImageID string `json:"imageRef,omitempty"`
|
||||
// Specifies the backup ID, from which you want to create the volume.
|
||||
// Create a volume from a backup is supported since 3.47 microversion
|
||||
BackupID string `json:"backup_id,omitempty"`
|
||||
// The associated volume type
|
||||
VolumeType string `json:"volume_type,omitempty"`
|
||||
// Multiattach denotes if the volume is multi-attach capable.
|
||||
|
@ -57,9 +60,10 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
|
||||
resp, err := client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -93,14 +97,16 @@ func Delete(client *gophercloud.ServiceClient, id string, opts DeleteOptsBuilder
|
|||
}
|
||||
url += query
|
||||
}
|
||||
_, r.Err = client.Delete(url, nil)
|
||||
resp, err := client.Delete(url, nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Get retrieves the Volume with the provided ID. To extract the Volume object
|
||||
// from the response, call the Extract method on the GetResult.
|
||||
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
|
||||
_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
|
||||
resp, err := client.Get(getURL(client, id), &r.Body, nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -194,44 +200,9 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
resp, err := client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// IDFromName is a convienience function that returns a server's ID given its name.
|
||||
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
|
||||
count := 0
|
||||
id := ""
|
||||
|
||||
listOpts := ListOpts{
|
||||
Name: name,
|
||||
}
|
||||
|
||||
pages, err := List(client, listOpts).AllPages()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
all, err := ExtractVolumes(pages)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, s := range all {
|
||||
if s.Name == name {
|
||||
count++
|
||||
id = s.ID
|
||||
}
|
||||
}
|
||||
|
||||
switch count {
|
||||
case 0:
|
||||
return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "volume"}
|
||||
case 1:
|
||||
return id, nil
|
||||
default:
|
||||
return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "volume"}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,6 +63,9 @@ type Volume struct {
|
|||
SnapshotID string `json:"snapshot_id"`
|
||||
// The ID of another block storage volume from which the current volume was created
|
||||
SourceVolID string `json:"source_volid"`
|
||||
// The backup ID, from which the volume was restored
|
||||
// This field is supported since 3.47 microversion
|
||||
BackupID *string `json:"backup_id"`
|
||||
// Arbitrary key-value pairs defined by the user.
|
||||
Metadata map[string]string `json:"metadata"`
|
||||
// UserID is the id of the user who created the volume.
|
||||
|
|
|
@ -3,9 +3,12 @@ package openstack
|
|||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
|
||||
"github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens"
|
||||
"github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1"
|
||||
tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
|
||||
"github.com/gophercloud/gophercloud/openstack/utils"
|
||||
)
|
||||
|
@ -67,7 +70,7 @@ Example:
|
|||
|
||||
ao, err := openstack.AuthOptionsFromEnv()
|
||||
provider, err := openstack.AuthenticatedClient(ao)
|
||||
client, err := openstack.NewNetworkV2(client, gophercloud.EndpointOpts{
|
||||
client, err := openstack.NewNetworkV2(provider, gophercloud.EndpointOpts{
|
||||
Region: os.Getenv("OS_REGION_NAME"),
|
||||
})
|
||||
*/
|
||||
|
@ -187,16 +190,61 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.Au
|
|||
v3Client.Endpoint = endpoint
|
||||
}
|
||||
|
||||
result := tokens3.Create(v3Client, opts)
|
||||
var catalog *tokens3.ServiceCatalog
|
||||
|
||||
err = client.SetTokenAndAuthResult(result)
|
||||
if err != nil {
|
||||
return err
|
||||
var tokenID string
|
||||
// passthroughToken allows to passthrough the token without a scope
|
||||
var passthroughToken bool
|
||||
switch v := opts.(type) {
|
||||
case *gophercloud.AuthOptions:
|
||||
tokenID = v.TokenID
|
||||
passthroughToken = (v.Scope == nil || *v.Scope == gophercloud.AuthScope{})
|
||||
case *tokens3.AuthOptions:
|
||||
tokenID = v.TokenID
|
||||
passthroughToken = (v.Scope == tokens3.Scope{})
|
||||
}
|
||||
|
||||
catalog, err := result.ExtractServiceCatalog()
|
||||
if err != nil {
|
||||
return err
|
||||
if tokenID != "" && passthroughToken {
|
||||
// passing through the token ID without requesting a new scope
|
||||
if opts.CanReauth() {
|
||||
return fmt.Errorf("cannot use AllowReauth, when the token ID is defined and auth scope is not set")
|
||||
}
|
||||
|
||||
v3Client.SetToken(tokenID)
|
||||
result := tokens3.Get(v3Client, tokenID)
|
||||
if result.Err != nil {
|
||||
return result.Err
|
||||
}
|
||||
|
||||
err = client.SetTokenAndAuthResult(result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
catalog, err = result.ExtractServiceCatalog()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
var result tokens3.CreateResult
|
||||
switch opts.(type) {
|
||||
case *ec2tokens.AuthOptions:
|
||||
result = ec2tokens.Create(v3Client, opts)
|
||||
case *oauth1.AuthOptions:
|
||||
result = oauth1.Create(v3Client, opts)
|
||||
default:
|
||||
result = tokens3.Create(v3Client, opts)
|
||||
}
|
||||
|
||||
err = client.SetTokenAndAuthResult(result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
catalog, err = result.ExtractServiceCatalog()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.CanReauth() {
|
||||
|
@ -217,6 +265,14 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.Au
|
|||
o := *ot
|
||||
o.AllowReauth = false
|
||||
tao = &o
|
||||
case *ec2tokens.AuthOptions:
|
||||
o := *ot
|
||||
o.AllowReauth = false
|
||||
tao = &o
|
||||
case *oauth1.AuthOptions:
|
||||
o := *ot
|
||||
o.AllowReauth = false
|
||||
tao = &o
|
||||
default:
|
||||
tao = opts
|
||||
}
|
||||
|
@ -395,7 +451,11 @@ func NewImageServiceV2(client *gophercloud.ProviderClient, eo gophercloud.Endpoi
|
|||
// load balancer service.
|
||||
func NewLoadBalancerV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||
sc, err := initClientOpts(client, eo, "load-balancer")
|
||||
sc.ResourceBase = sc.Endpoint + "v2.0/"
|
||||
|
||||
// Fixes edge case having an OpenStack lb endpoint with trailing version number.
|
||||
endpoint := strings.Replace(sc.Endpoint, "v2.0/", "", -1)
|
||||
|
||||
sc.ResourceBase = endpoint + "v2.0/"
|
||||
return sc, err
|
||||
}
|
||||
|
||||
|
@ -436,3 +496,8 @@ func NewContainerInfraV1(client *gophercloud.ProviderClient, eo gophercloud.Endp
|
|||
func NewWorkflowV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||
return initClientOpts(client, eo, "workflowv2")
|
||||
}
|
||||
|
||||
// NewPlacementV1 creates a ServiceClient that may be used with the placement package.
|
||||
func NewPlacementV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||
return initClientOpts(client, eo, "placement")
|
||||
}
|
||||
|
|
52
vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/doc.go
generated
vendored
Normal file
52
vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
Package extensions provides information and interaction with the different
|
||||
extensions available for an OpenStack service.
|
||||
|
||||
The purpose of OpenStack API extensions is to:
|
||||
|
||||
- Introduce new features in the API without requiring a version change.
|
||||
- Introduce vendor-specific niche functionality.
|
||||
- Act as a proving ground for experimental functionalities that might be
|
||||
included in a future version of the API.
|
||||
|
||||
Extensions usually have tags that prevent conflicts with other extensions that
|
||||
define attributes or resources with the same names, and with core resources and
|
||||
attributes. Because an extension might not be supported by all plug-ins, its
|
||||
availability varies with deployments and the specific plug-in.
|
||||
|
||||
The results of this package vary depending on the type of Service Client used.
|
||||
In the following examples, note how the only difference is the creation of the
|
||||
Service Client.
|
||||
|
||||
Example of Retrieving Compute Extensions
|
||||
|
||||
ao, err := openstack.AuthOptionsFromEnv()
|
||||
provider, err := openstack.AuthenticatedClient(ao)
|
||||
computeClient, err := openstack.NewComputeV2(provider, gophercloud.EndpointOpts{
|
||||
Region: os.Getenv("OS_REGION_NAME"),
|
||||
})
|
||||
|
||||
allPages, err := extensions.List(computeClient).Allpages()
|
||||
allExtensions, err := extensions.ExtractExtensions(allPages)
|
||||
|
||||
for _, extension := range allExtensions{
|
||||
fmt.Println("%+v\n", extension)
|
||||
}
|
||||
|
||||
|
||||
Example of Retrieving Network Extensions
|
||||
|
||||
ao, err := openstack.AuthOptionsFromEnv()
|
||||
provider, err := openstack.AuthenticatedClient(ao)
|
||||
networkClient, err := openstack.NewNetworkV2(provider, gophercloud.EndpointOpts{
|
||||
Region: os.Getenv("OS_REGION_NAME"),
|
||||
})
|
||||
|
||||
allPages, err := extensions.List(networkClient).Allpages()
|
||||
allExtensions, err := extensions.ExtractExtensions(allPages)
|
||||
|
||||
for _, extension := range allExtensions{
|
||||
fmt.Println("%+v\n", extension)
|
||||
}
|
||||
*/
|
||||
package extensions
|
21
vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/requests.go
generated
vendored
Normal file
21
vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/requests.go
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
package extensions
|
||||
|
||||
import (
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// Get retrieves information for a specific extension using its alias.
|
||||
func Get(c *gophercloud.ServiceClient, alias string) (r GetResult) {
|
||||
resp, err := c.Get(ExtensionURL(c, alias), &r.Body, nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// List returns a Pager which allows you to iterate over the full collection of extensions.
|
||||
// It does not accept query parameters.
|
||||
func List(c *gophercloud.ServiceClient) pagination.Pager {
|
||||
return pagination.NewPager(c, ListExtensionURL(c), func(r pagination.PageResult) pagination.Page {
|
||||
return ExtensionPage{pagination.SinglePageBase(r)}
|
||||
})
|
||||
}
|
53
vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/results.go
generated
vendored
Normal file
53
vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/results.go
generated
vendored
Normal file
|
@ -0,0 +1,53 @@
|
|||
package extensions
|
||||
|
||||
import (
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// GetResult temporarily stores the result of a Get call.
|
||||
// Use its Extract() method to interpret it as an Extension.
|
||||
type GetResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
// Extract interprets a GetResult as an Extension.
|
||||
func (r GetResult) Extract() (*Extension, error) {
|
||||
var s struct {
|
||||
Extension *Extension `json:"extension"`
|
||||
}
|
||||
err := r.ExtractInto(&s)
|
||||
return s.Extension, err
|
||||
}
|
||||
|
||||
// Extension is a struct that represents an OpenStack extension.
|
||||
type Extension struct {
|
||||
Updated string `json:"updated"`
|
||||
Name string `json:"name"`
|
||||
Links []interface{} `json:"links"`
|
||||
Namespace string `json:"namespace"`
|
||||
Alias string `json:"alias"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
// ExtensionPage is the page returned by a pager when traversing over a collection of extensions.
|
||||
type ExtensionPage struct {
|
||||
pagination.SinglePageBase
|
||||
}
|
||||
|
||||
// IsEmpty checks whether an ExtensionPage struct is empty.
|
||||
func (r ExtensionPage) IsEmpty() (bool, error) {
|
||||
is, err := ExtractExtensions(r)
|
||||
return len(is) == 0, err
|
||||
}
|
||||
|
||||
// ExtractExtensions accepts a Page struct, specifically an ExtensionPage
|
||||
// struct, and extracts the elements into a slice of Extension structs.
|
||||
// In other words, a generic collection is mapped into a relevant slice.
|
||||
func ExtractExtensions(r pagination.Page) ([]Extension, error) {
|
||||
var s struct {
|
||||
Extensions []Extension `json:"extensions"`
|
||||
}
|
||||
err := (r.(ExtensionPage)).ExtractInto(&s)
|
||||
return s.Extensions, err
|
||||
}
|
13
vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/urls.go
generated
vendored
Normal file
13
vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/urls.go
generated
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
package extensions
|
||||
|
||||
import "github.com/gophercloud/gophercloud"
|
||||
|
||||
// ExtensionURL generates the URL for an extension resource by name.
|
||||
func ExtensionURL(c *gophercloud.ServiceClient, name string) string {
|
||||
return c.ServiceURL("extensions", name)
|
||||
}
|
||||
|
||||
// ListExtensionURL generates the URL for the extensions resource collection.
|
||||
func ListExtensionURL(c *gophercloud.ServiceClient) string {
|
||||
return c.ServiceURL("extensions")
|
||||
}
|
|
@ -14,9 +14,10 @@ func List(client *gophercloud.ServiceClient, serverID string) pagination.Pager {
|
|||
|
||||
// Get requests details on a single interface attachment by the server and port IDs.
|
||||
func Get(client *gophercloud.ServiceClient, serverID, portID string) (r GetResult) {
|
||||
_, r.Err = client.Get(getInterfaceURL(client, serverID, portID), &r.Body, &gophercloud.RequestOpts{
|
||||
resp, err := client.Get(getInterfaceURL(client, serverID, portID), &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -58,15 +59,17 @@ func Create(client *gophercloud.ServiceClient, serverID string, opts CreateOptsB
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(createInterfaceURL(client, serverID), b, &r.Body, &gophercloud.RequestOpts{
|
||||
resp, err := client.Post(createInterfaceURL(client, serverID), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete makes a request against the nova API to detach a single interface from the server.
|
||||
// It needs server and port IDs to make a such request.
|
||||
func Delete(client *gophercloud.ServiceClient, serverID, portID string) (r DeleteResult) {
|
||||
_, r.Err = client.Delete(deleteInterfaceURL(client, serverID, portID), nil)
|
||||
resp, err := client.Delete(deleteInterfaceURL(client, serverID, portID), nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -75,6 +75,10 @@ type BlockDevice struct {
|
|||
// DiskBus is the bus type of the block devices.
|
||||
// Examples of this are ide, usb, virtio, scsi, etc.
|
||||
DiskBus string `json:"disk_bus,omitempty"`
|
||||
|
||||
// VolumeType is the volume type of the block device.
|
||||
// This requires Compute API microversion 2.67 or later.
|
||||
VolumeType string `json:"volume_type,omitempty"`
|
||||
}
|
||||
|
||||
// CreateOptsExt is a structure that extends the server `CreateOpts` structure
|
||||
|
@ -121,8 +125,9 @@ func Create(client *gophercloud.ServiceClient, opts servers.CreateOptsBuilder) (
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
|
||||
resp, err := client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200, 202},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -3,5 +3,5 @@ package bootfromvolume
|
|||
import "github.com/gophercloud/gophercloud"
|
||||
|
||||
func createURL(c *gophercloud.ServiceClient) string {
|
||||
return c.ServiceURL("os-volumes_boot")
|
||||
return c.ServiceURL("servers")
|
||||
}
|
||||
|
|
23
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/delegate.go
generated
vendored
Normal file
23
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/delegate.go
generated
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
package extensions
|
||||
|
||||
import (
|
||||
"github.com/gophercloud/gophercloud"
|
||||
common "github.com/gophercloud/gophercloud/openstack/common/extensions"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// ExtractExtensions interprets a Page as a slice of Extensions.
|
||||
func ExtractExtensions(page pagination.Page) ([]common.Extension, error) {
|
||||
return common.ExtractExtensions(page)
|
||||
}
|
||||
|
||||
// Get retrieves information for a specific extension using its alias.
|
||||
func Get(c *gophercloud.ServiceClient, alias string) common.GetResult {
|
||||
return common.Get(c, alias)
|
||||
}
|
||||
|
||||
// List returns a Pager which allows you to iterate over the full collection of extensions.
|
||||
// It does not accept query parameters.
|
||||
func List(c *gophercloud.ServiceClient) pagination.Pager {
|
||||
return common.List(c)
|
||||
}
|
3
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/doc.go
generated
vendored
Normal file
3
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
// Package extensions provides information and interaction with the
|
||||
// different extensions available for the OpenStack Compute service.
|
||||
package extensions
|
|
@ -67,20 +67,23 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
|
||||
resp, err := client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200, 201},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Get returns public data about a previously uploaded KeyPair.
|
||||
func Get(client *gophercloud.ServiceClient, name string) (r GetResult) {
|
||||
_, r.Err = client.Get(getURL(client, name), &r.Body, nil)
|
||||
resp, err := client.Get(getURL(client, name), &r.Body, nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete requests the deletion of a previous stored KeyPair from the server.
|
||||
func Delete(client *gophercloud.ServiceClient, name string) (r DeleteResult) {
|
||||
_, r.Err = client.Delete(deleteURL(client, name), nil)
|
||||
resp, err := client.Delete(deleteURL(client, name), nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
package startstop
|
||||
|
||||
import "github.com/gophercloud/gophercloud"
|
||||
|
||||
func actionURL(client *gophercloud.ServiceClient, id string) string {
|
||||
return client.ServiceURL("servers", id, "action")
|
||||
}
|
||||
import (
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions"
|
||||
)
|
||||
|
||||
// Start is the operation responsible for starting a Compute server.
|
||||
func Start(client *gophercloud.ServiceClient, id string) (r StartResult) {
|
||||
_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"os-start": nil}, nil, nil)
|
||||
resp, err := client.Post(extensions.ActionURL(client, id), map[string]interface{}{"os-start": nil}, nil, nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Stop is the operation responsible for stopping a Compute server.
|
||||
func Stop(client *gophercloud.ServiceClient, id string) (r StopResult) {
|
||||
_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"os-stop": nil}, nil, nil)
|
||||
resp, err := client.Post(extensions.ActionURL(client, id), map[string]interface{}{"os-stop": nil}, nil, nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
|
7
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/urls.go
generated
vendored
Normal file
7
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/urls.go
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
package extensions
|
||||
|
||||
import "github.com/gophercloud/gophercloud"
|
||||
|
||||
func ActionURL(client *gophercloud.ServiceClient, id string) string {
|
||||
return client.ServiceURL("servers", id, "action")
|
||||
}
|
69
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/requests.go
generated
vendored
69
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/requests.go
generated
vendored
|
@ -142,22 +142,25 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
|
||||
resp, err := client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200, 201},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Get retrieves details of a single flavor. Use Extract to convert its
|
||||
// result into a Flavor.
|
||||
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
|
||||
_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
|
||||
resp, err := client.Get(getURL(client, id), &r.Body, nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete deletes the specified flavor ID.
|
||||
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
|
||||
_, r.Err = client.Delete(deleteURL(client, id), nil)
|
||||
resp, err := client.Delete(deleteURL(client, id), nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -194,9 +197,10 @@ func AddAccess(client *gophercloud.ServiceClient, id string, opts AddAccessOptsB
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(accessActionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
resp, err := client.Post(accessActionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -224,20 +228,23 @@ func RemoveAccess(client *gophercloud.ServiceClient, id string, opts RemoveAcces
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(accessActionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
resp, err := client.Post(accessActionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// ExtraSpecs requests all the extra-specs for the given flavor ID.
|
||||
func ListExtraSpecs(client *gophercloud.ServiceClient, flavorID string) (r ListExtraSpecsResult) {
|
||||
_, r.Err = client.Get(extraSpecsListURL(client, flavorID), &r.Body, nil)
|
||||
resp, err := client.Get(extraSpecsListURL(client, flavorID), &r.Body, nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
func GetExtraSpec(client *gophercloud.ServiceClient, flavorID string, key string) (r GetExtraSpecResult) {
|
||||
_, r.Err = client.Get(extraSpecsGetURL(client, flavorID, key), &r.Body, nil)
|
||||
resp, err := client.Get(extraSpecsGetURL(client, flavorID, key), &r.Body, nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -264,9 +271,10 @@ func CreateExtraSpecs(client *gophercloud.ServiceClient, flavorID string, opts C
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(extraSpecsCreateURL(client, flavorID), b, &r.Body, &gophercloud.RequestOpts{
|
||||
resp, err := client.Post(extraSpecsCreateURL(client, flavorID), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -302,56 +310,19 @@ func UpdateExtraSpec(client *gophercloud.ServiceClient, flavorID string, opts Up
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Put(extraSpecUpdateURL(client, flavorID, key), b, &r.Body, &gophercloud.RequestOpts{
|
||||
resp, err := client.Put(extraSpecUpdateURL(client, flavorID, key), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// DeleteExtraSpec will delete the key-value pair with the given key for the given
|
||||
// flavor ID.
|
||||
func DeleteExtraSpec(client *gophercloud.ServiceClient, flavorID, key string) (r DeleteExtraSpecResult) {
|
||||
_, r.Err = client.Delete(extraSpecDeleteURL(client, flavorID, key), &gophercloud.RequestOpts{
|
||||
resp, err := client.Delete(extraSpecDeleteURL(client, flavorID, key), &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// IDFromName is a convienience function that returns a flavor's ID given its
|
||||
// name.
|
||||
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
|
||||
count := 0
|
||||
id := ""
|
||||
allPages, err := ListDetail(client, nil).AllPages()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
all, err := ExtractFlavors(allPages)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, f := range all {
|
||||
if f.Name == name {
|
||||
count++
|
||||
id = f.ID
|
||||
}
|
||||
}
|
||||
|
||||
switch count {
|
||||
case 0:
|
||||
err := &gophercloud.ErrResourceNotFound{}
|
||||
err.ResourceType = "flavor"
|
||||
err.Name = name
|
||||
return "", err
|
||||
case 1:
|
||||
return id, nil
|
||||
default:
|
||||
err := &gophercloud.ErrMultipleResourcesFound{}
|
||||
err.ResourceType = "flavor"
|
||||
err.Name = name
|
||||
err.Count = count
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
Package images provides information and interaction with the images through
|
||||
the OpenStack Compute service.
|
||||
|
||||
This API is deprecated and will be removed from a future version of the Nova
|
||||
API service.
|
||||
|
||||
An image is a collection of files used to create or rebuild a server.
|
||||
Operators provide a number of pre-built OS images by default. You may also
|
||||
create custom images from cloud servers you have launched.
|
||||
|
||||
Example to List Images
|
||||
|
||||
listOpts := images.ListOpts{
|
||||
Limit: 2,
|
||||
}
|
||||
|
||||
allPages, err := images.ListDetail(computeClient, listOpts).AllPages()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
allImages, err := images.ExtractImages(allPages)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _, image := range allImages {
|
||||
fmt.Printf("%+v\n", image)
|
||||
}
|
||||
*/
|
||||
package images
|
109
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/requests.go
generated
vendored
109
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/requests.go
generated
vendored
|
@ -1,109 +0,0 @@
|
|||
package images
|
||||
|
||||
import (
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// ListOptsBuilder allows extensions to add additional parameters to the
|
||||
// ListDetail request.
|
||||
type ListOptsBuilder interface {
|
||||
ToImageListQuery() (string, error)
|
||||
}
|
||||
|
||||
// ListOpts contain options filtering Images returned from a call to ListDetail.
|
||||
type ListOpts struct {
|
||||
// ChangesSince filters Images based on the last changed status (in date-time
|
||||
// format).
|
||||
ChangesSince string `q:"changes-since"`
|
||||
|
||||
// Limit limits the number of Images to return.
|
||||
Limit int `q:"limit"`
|
||||
|
||||
// Mark is an Image UUID at which to set a marker.
|
||||
Marker string `q:"marker"`
|
||||
|
||||
// Name is the name of the Image.
|
||||
Name string `q:"name"`
|
||||
|
||||
// Server is the name of the Server (in URL format).
|
||||
Server string `q:"server"`
|
||||
|
||||
// Status is the current status of the Image.
|
||||
Status string `q:"status"`
|
||||
|
||||
// Type is the type of image (e.g. BASE, SERVER, ALL).
|
||||
Type string `q:"type"`
|
||||
}
|
||||
|
||||
// ToImageListQuery formats a ListOpts into a query string.
|
||||
func (opts ListOpts) ToImageListQuery() (string, error) {
|
||||
q, err := gophercloud.BuildQueryString(opts)
|
||||
return q.String(), err
|
||||
}
|
||||
|
||||
// ListDetail enumerates the available images.
|
||||
func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
|
||||
url := listDetailURL(client)
|
||||
if opts != nil {
|
||||
query, err := opts.ToImageListQuery()
|
||||
if err != nil {
|
||||
return pagination.Pager{Err: err}
|
||||
}
|
||||
url += query
|
||||
}
|
||||
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
|
||||
return ImagePage{pagination.LinkedPageBase{PageResult: r}}
|
||||
})
|
||||
}
|
||||
|
||||
// Get returns data about a specific image by its ID.
|
||||
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
|
||||
_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete deletes the specified image ID.
|
||||
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
|
||||
_, r.Err = client.Delete(deleteURL(client, id), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// IDFromName is a convienience function that returns an image's ID given its
|
||||
// name.
|
||||
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
|
||||
count := 0
|
||||
id := ""
|
||||
allPages, err := ListDetail(client, nil).AllPages()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
all, err := ExtractImages(allPages)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, f := range all {
|
||||
if f.Name == name {
|
||||
count++
|
||||
id = f.ID
|
||||
}
|
||||
}
|
||||
|
||||
switch count {
|
||||
case 0:
|
||||
err := &gophercloud.ErrResourceNotFound{}
|
||||
err.ResourceType = "image"
|
||||
err.Name = name
|
||||
return "", err
|
||||
case 1:
|
||||
return id, nil
|
||||
default:
|
||||
err := &gophercloud.ErrMultipleResourcesFound{}
|
||||
err.ResourceType = "image"
|
||||
err.Name = name
|
||||
err.Count = count
|
||||
return "", err
|
||||
}
|
||||
}
|
95
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/results.go
generated
vendored
95
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/results.go
generated
vendored
|
@ -1,95 +0,0 @@
|
|||
package images
|
||||
|
||||
import (
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// GetResult is the response from a Get operation. Call its Extract method to
|
||||
// interpret it as an Image.
|
||||
type GetResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
// DeleteResult is the result from a Delete operation. Call its ExtractErr
|
||||
// method to determine if the call succeeded or failed.
|
||||
type DeleteResult struct {
|
||||
gophercloud.ErrResult
|
||||
}
|
||||
|
||||
// Extract interprets a GetResult as an Image.
|
||||
func (r GetResult) Extract() (*Image, error) {
|
||||
var s struct {
|
||||
Image *Image `json:"image"`
|
||||
}
|
||||
err := r.ExtractInto(&s)
|
||||
return s.Image, err
|
||||
}
|
||||
|
||||
// Image represents an Image returned by the Compute API.
|
||||
type Image struct {
|
||||
// ID is the unique ID of an image.
|
||||
ID string
|
||||
|
||||
// Created is the date when the image was created.
|
||||
Created string
|
||||
|
||||
// MinDisk is the minimum amount of disk a flavor must have to be able
|
||||
// to create a server based on the image, measured in GB.
|
||||
MinDisk int
|
||||
|
||||
// MinRAM is the minimum amount of RAM a flavor must have to be able
|
||||
// to create a server based on the image, measured in MB.
|
||||
MinRAM int
|
||||
|
||||
// Name provides a human-readable moniker for the OS image.
|
||||
Name string
|
||||
|
||||
// The Progress and Status fields indicate image-creation status.
|
||||
Progress int
|
||||
|
||||
// Status is the current status of the image.
|
||||
Status string
|
||||
|
||||
// Update is the date when the image was updated.
|
||||
Updated string
|
||||
|
||||
// Metadata provides free-form key/value pairs that further describe the
|
||||
// image.
|
||||
Metadata map[string]interface{}
|
||||
}
|
||||
|
||||
// ImagePage contains a single page of all Images returne from a ListDetail
|
||||
// operation. Use ExtractImages to convert it into a slice of usable structs.
|
||||
type ImagePage struct {
|
||||
pagination.LinkedPageBase
|
||||
}
|
||||
|
||||
// IsEmpty returns true if an ImagePage contains no Image results.
|
||||
func (page ImagePage) IsEmpty() (bool, error) {
|
||||
images, err := ExtractImages(page)
|
||||
return len(images) == 0, err
|
||||
}
|
||||
|
||||
// NextPageURL uses the response's embedded link reference to navigate to the
|
||||
// next page of results.
|
||||
func (page ImagePage) NextPageURL() (string, error) {
|
||||
var s struct {
|
||||
Links []gophercloud.Link `json:"images_links"`
|
||||
}
|
||||
err := page.ExtractInto(&s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return gophercloud.ExtractNextURL(s.Links)
|
||||
}
|
||||
|
||||
// ExtractImages converts a page of List results into a slice of usable Image
|
||||
// structs.
|
||||
func ExtractImages(r pagination.Page) ([]Image, error) {
|
||||
var s struct {
|
||||
Images []Image `json:"images"`
|
||||
}
|
||||
err := (r.(ImagePage)).ExtractInto(&s)
|
||||
return s.Images, err
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
package images
|
||||
|
||||
import "github.com/gophercloud/gophercloud"
|
||||
|
||||
func listDetailURL(client *gophercloud.ServiceClient) string {
|
||||
return client.ServiceURL("images", "detail")
|
||||
}
|
||||
|
||||
func getURL(client *gophercloud.ServiceClient, id string) string {
|
||||
return client.ServiceURL("images", id)
|
||||
}
|
||||
|
||||
func deleteURL(client *gophercloud.ServiceClient, id string) string {
|
||||
return client.ServiceURL("images", id)
|
||||
}
|
11
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/microversions.go
generated
vendored
11
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/microversions.go
generated
vendored
|
@ -1,11 +0,0 @@
|
|||
package servers
|
||||
|
||||
// ExtractTags will extract the tags of a server.
|
||||
// This requires the client to be set to microversion 2.26 or later.
|
||||
func (r serverResult) ExtractTags() ([]string, error) {
|
||||
var s struct {
|
||||
Tags []string `json:"tags"`
|
||||
}
|
||||
err := r.ExtractInto(&s)
|
||||
return s.Tags, err
|
||||
}
|
232
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/requests.go
generated
vendored
232
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/requests.go
generated
vendored
|
@ -3,10 +3,9 @@ package servers
|
|||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/images"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
|
@ -55,6 +54,22 @@ type ListOpts struct {
|
|||
// TenantID lists servers for a particular tenant.
|
||||
// Setting "AllTenants = true" is required.
|
||||
TenantID string `q:"tenant_id"`
|
||||
|
||||
// This requires the client to be set to microversion 2.26 or later.
|
||||
// Tags filters on specific server tags. All tags must be present for the server.
|
||||
Tags string `q:"tags"`
|
||||
|
||||
// This requires the client to be set to microversion 2.26 or later.
|
||||
// TagsAny filters on specific server tags. At least one of the tags must be present for the server.
|
||||
TagsAny string `q:"tags-any"`
|
||||
|
||||
// This requires the client to be set to microversion 2.26 or later.
|
||||
// NotTags filters on specific server tags. All tags must be absent for the server.
|
||||
NotTags string `q:"not-tags"`
|
||||
|
||||
// This requires the client to be set to microversion 2.26 or later.
|
||||
// NotTagsAny filters on specific server tags. At least one of the tags must be absent for the server.
|
||||
NotTagsAny string `q:"not-tags-any"`
|
||||
}
|
||||
|
||||
// ToServerListQuery formats a ListOpts into a query string.
|
||||
|
@ -131,24 +146,14 @@ type CreateOpts struct {
|
|||
// Name is the name to assign to the newly launched server.
|
||||
Name string `json:"name" required:"true"`
|
||||
|
||||
// ImageRef [optional; required if ImageName is not provided] is the ID or
|
||||
// full URL to the image that contains the server's OS and initial state.
|
||||
// ImageRef is the ID or full URL to the image that contains the
|
||||
// server's OS and initial state.
|
||||
// Also optional if using the boot-from-volume extension.
|
||||
ImageRef string `json:"imageRef"`
|
||||
|
||||
// ImageName [optional; required if ImageRef is not provided] is the name of
|
||||
// the image that contains the server's OS and initial state.
|
||||
// Also optional if using the boot-from-volume extension.
|
||||
ImageName string `json:"-"`
|
||||
|
||||
// FlavorRef [optional; required if FlavorName is not provided] is the ID or
|
||||
// full URL to the flavor that describes the server's specs.
|
||||
// FlavorRef is the ID or full URL to the flavor that describes the server's specs.
|
||||
FlavorRef string `json:"flavorRef"`
|
||||
|
||||
// FlavorName [optional; required if FlavorRef is not provided] is the name of
|
||||
// the flavor that describes the server's specs.
|
||||
FlavorName string `json:"-"`
|
||||
|
||||
// SecurityGroups lists the names of the security groups to which this server
|
||||
// should belong.
|
||||
SecurityGroups []string `json:"-"`
|
||||
|
@ -163,7 +168,9 @@ type CreateOpts struct {
|
|||
// Networks dictates how this server will be attached to available networks.
|
||||
// By default, the server will be attached to all isolated networks for the
|
||||
// tenant.
|
||||
Networks []Network `json:"-"`
|
||||
// Starting with microversion 2.37 networks can also be an "auto" or "none"
|
||||
// string.
|
||||
Networks interface{} `json:"-"`
|
||||
|
||||
// Metadata contains key-value pairs (up to 255 bytes each) to attach to the
|
||||
// server.
|
||||
|
@ -204,7 +211,6 @@ type CreateOpts struct {
|
|||
// ToServerCreateMap assembles a request body based on the contents of a
|
||||
// CreateOpts.
|
||||
func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) {
|
||||
sc := opts.ServiceClient
|
||||
opts.ServiceClient = nil
|
||||
b, err := gophercloud.BuildRequestBody(opts, "")
|
||||
if err != nil {
|
||||
|
@ -229,59 +235,32 @@ func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) {
|
|||
b["security_groups"] = securityGroups
|
||||
}
|
||||
|
||||
if len(opts.Networks) > 0 {
|
||||
networks := make([]map[string]interface{}, len(opts.Networks))
|
||||
for i, net := range opts.Networks {
|
||||
networks[i] = make(map[string]interface{})
|
||||
if net.UUID != "" {
|
||||
networks[i]["uuid"] = net.UUID
|
||||
}
|
||||
if net.Port != "" {
|
||||
networks[i]["port"] = net.Port
|
||||
}
|
||||
if net.FixedIP != "" {
|
||||
networks[i]["fixed_ip"] = net.FixedIP
|
||||
switch v := opts.Networks.(type) {
|
||||
case []Network:
|
||||
if len(v) > 0 {
|
||||
networks := make([]map[string]interface{}, len(v))
|
||||
for i, net := range v {
|
||||
networks[i] = make(map[string]interface{})
|
||||
if net.UUID != "" {
|
||||
networks[i]["uuid"] = net.UUID
|
||||
}
|
||||
if net.Port != "" {
|
||||
networks[i]["port"] = net.Port
|
||||
}
|
||||
if net.FixedIP != "" {
|
||||
networks[i]["fixed_ip"] = net.FixedIP
|
||||
}
|
||||
}
|
||||
b["networks"] = networks
|
||||
}
|
||||
b["networks"] = networks
|
||||
}
|
||||
|
||||
// If ImageRef isn't provided, check if ImageName was provided to ascertain
|
||||
// the image ID.
|
||||
if opts.ImageRef == "" {
|
||||
if opts.ImageName != "" {
|
||||
if sc == nil {
|
||||
err := ErrNoClientProvidedForIDByName{}
|
||||
err.Argument = "ServiceClient"
|
||||
return nil, err
|
||||
}
|
||||
imageID, err := images.IDFromName(sc, opts.ImageName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b["imageRef"] = imageID
|
||||
case string:
|
||||
if v == "auto" || v == "none" {
|
||||
b["networks"] = v
|
||||
} else {
|
||||
return nil, fmt.Errorf(`networks must be a slice of Network struct or a string with "auto" or "none" values, current value is %q`, v)
|
||||
}
|
||||
}
|
||||
|
||||
// If FlavorRef isn't provided, use FlavorName to ascertain the flavor ID.
|
||||
if opts.FlavorRef == "" {
|
||||
if opts.FlavorName == "" {
|
||||
err := ErrNeitherFlavorIDNorFlavorNameProvided{}
|
||||
err.Argument = "FlavorRef/FlavorName"
|
||||
return nil, err
|
||||
}
|
||||
if sc == nil {
|
||||
err := ErrNoClientProvidedForIDByName{}
|
||||
err.Argument = "ServiceClient"
|
||||
return nil, err
|
||||
}
|
||||
flavorID, err := flavors.IDFromName(sc, opts.FlavorName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b["flavorRef"] = flavorID
|
||||
}
|
||||
|
||||
if opts.Min != 0 {
|
||||
b["min_count"] = opts.Min
|
||||
}
|
||||
|
@ -300,28 +279,32 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(listURL(client), reqBody, &r.Body, nil)
|
||||
resp, err := client.Post(listURL(client), reqBody, &r.Body, nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete requests that a server previously provisioned be removed from your
|
||||
// account.
|
||||
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
|
||||
_, r.Err = client.Delete(deleteURL(client, id), nil)
|
||||
resp, err := client.Delete(deleteURL(client, id), nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// ForceDelete forces the deletion of a server.
|
||||
func ForceDelete(client *gophercloud.ServiceClient, id string) (r ActionResult) {
|
||||
_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"forceDelete": ""}, nil, nil)
|
||||
resp, err := client.Post(actionURL(client, id), map[string]interface{}{"forceDelete": ""}, nil, nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Get requests details on a single server, by ID.
|
||||
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
|
||||
_, r.Err = client.Get(getURL(client, id), &r.Body, &gophercloud.RequestOpts{
|
||||
resp, err := client.Get(getURL(client, id), &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200, 203},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -358,9 +341,10 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
resp, err := client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -372,7 +356,8 @@ func ChangeAdminPassword(client *gophercloud.ServiceClient, id, newPassword stri
|
|||
"adminPass": newPassword,
|
||||
},
|
||||
}
|
||||
_, r.Err = client.Post(actionURL(client, id), b, nil, nil)
|
||||
resp, err := client.Post(actionURL(client, id), b, nil, nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -413,7 +398,7 @@ func (opts RebootOpts) ToServerRebootMap() (map[string]interface{}, error) {
|
|||
HardReboot (aka PowerCycle) starts the server instance by physically cutting
|
||||
power to the machine, or if a VM, terminating it at the hypervisor level.
|
||||
It's done. Caput. Full stop.
|
||||
Then, after a brief while, power is rtored or the VM instance restarted.
|
||||
Then, after a brief while, power is restored or the VM instance restarted.
|
||||
|
||||
SoftReboot (aka OSReboot) simply tells the OS to restart under its own
|
||||
procedure.
|
||||
|
@ -426,7 +411,8 @@ func Reboot(client *gophercloud.ServiceClient, id string, opts RebootOptsBuilder
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(actionURL(client, id), b, nil, nil)
|
||||
resp, err := client.Post(actionURL(client, id), b, nil, nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -442,11 +428,8 @@ type RebuildOpts struct {
|
|||
// AdminPass is the server's admin password
|
||||
AdminPass string `json:"adminPass,omitempty"`
|
||||
|
||||
// ImageID is the ID of the image you want your server to be provisioned on.
|
||||
ImageID string `json:"imageRef"`
|
||||
|
||||
// ImageName is readable name of an image.
|
||||
ImageName string `json:"-"`
|
||||
// ImageRef is the ID of the image you want your server to be provisioned on.
|
||||
ImageRef string `json:"imageRef"`
|
||||
|
||||
// Name to set the server to
|
||||
Name string `json:"name,omitempty"`
|
||||
|
@ -477,23 +460,6 @@ func (opts RebuildOpts) ToServerRebuildMap() (map[string]interface{}, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// If ImageRef isn't provided, check if ImageName was provided to ascertain
|
||||
// the image ID.
|
||||
if opts.ImageID == "" {
|
||||
if opts.ImageName != "" {
|
||||
if opts.ServiceClient == nil {
|
||||
err := ErrNoClientProvidedForIDByName{}
|
||||
err.Argument = "ServiceClient"
|
||||
return nil, err
|
||||
}
|
||||
imageID, err := images.IDFromName(opts.ServiceClient, opts.ImageName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b["imageRef"] = imageID
|
||||
}
|
||||
}
|
||||
|
||||
return map[string]interface{}{"rebuild": b}, nil
|
||||
}
|
||||
|
||||
|
@ -505,7 +471,8 @@ func Rebuild(client *gophercloud.ServiceClient, id string, opts RebuildOptsBuild
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(actionURL(client, id), b, &r.Body, nil)
|
||||
resp, err := client.Post(actionURL(client, id), b, &r.Body, nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -543,23 +510,26 @@ func Resize(client *gophercloud.ServiceClient, id string, opts ResizeOptsBuilder
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(actionURL(client, id), b, nil, nil)
|
||||
resp, err := client.Post(actionURL(client, id), b, nil, nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// ConfirmResize confirms a previous resize operation on a server.
|
||||
// See Resize() for more details.
|
||||
func ConfirmResize(client *gophercloud.ServiceClient, id string) (r ActionResult) {
|
||||
_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"confirmResize": nil}, nil, &gophercloud.RequestOpts{
|
||||
resp, err := client.Post(actionURL(client, id), map[string]interface{}{"confirmResize": nil}, nil, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{201, 202, 204},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// RevertResize cancels a previous resize operation on a server.
|
||||
// See Resize() for more details.
|
||||
func RevertResize(client *gophercloud.ServiceClient, id string) (r ActionResult) {
|
||||
_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"revertResize": nil}, nil, nil)
|
||||
resp, err := client.Post(actionURL(client, id), map[string]interface{}{"revertResize": nil}, nil, nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -595,15 +565,17 @@ func ResetMetadata(client *gophercloud.ServiceClient, id string, opts ResetMetad
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Put(metadataURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
resp, err := client.Put(metadataURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Metadata requests all the metadata for the given server ID.
|
||||
func Metadata(client *gophercloud.ServiceClient, id string) (r GetMetadataResult) {
|
||||
_, r.Err = client.Get(metadataURL(client, id), &r.Body, nil)
|
||||
resp, err := client.Get(metadataURL(client, id), &r.Body, nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -622,9 +594,10 @@ func UpdateMetadata(client *gophercloud.ServiceClient, id string, opts UpdateMet
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(metadataURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
resp, err := client.Post(metadataURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -662,23 +635,26 @@ func CreateMetadatum(client *gophercloud.ServiceClient, id string, opts Metadatu
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Put(metadatumURL(client, id, key), b, &r.Body, &gophercloud.RequestOpts{
|
||||
resp, err := client.Put(metadatumURL(client, id, key), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Metadatum requests the key-value pair with the given key for the given
|
||||
// server ID.
|
||||
func Metadatum(client *gophercloud.ServiceClient, id, key string) (r GetMetadatumResult) {
|
||||
_, r.Err = client.Get(metadatumURL(client, id, key), &r.Body, nil)
|
||||
resp, err := client.Get(metadatumURL(client, id, key), &r.Body, nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// DeleteMetadatum will delete the key-value pair with the given key for the
|
||||
// given server ID.
|
||||
func DeleteMetadatum(client *gophercloud.ServiceClient, id, key string) (r DeleteMetadatumResult) {
|
||||
_, r.Err = client.Delete(metadatumURL(client, id, key), nil)
|
||||
resp, err := client.Delete(metadatumURL(client, id, key), nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -731,52 +707,15 @@ func CreateImage(client *gophercloud.ServiceClient, id string, opts CreateImageO
|
|||
resp, err := client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
r.Err = err
|
||||
r.Header = resp.Header
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// IDFromName is a convienience function that returns a server's ID given its
|
||||
// name.
|
||||
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
|
||||
count := 0
|
||||
id := ""
|
||||
|
||||
listOpts := ListOpts{
|
||||
Name: name,
|
||||
}
|
||||
|
||||
allPages, err := List(client, listOpts).AllPages()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
all, err := ExtractServers(allPages)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, f := range all {
|
||||
if f.Name == name {
|
||||
count++
|
||||
id = f.ID
|
||||
}
|
||||
}
|
||||
|
||||
switch count {
|
||||
case 0:
|
||||
return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "server"}
|
||||
case 1:
|
||||
return id, nil
|
||||
default:
|
||||
return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "server"}
|
||||
}
|
||||
}
|
||||
|
||||
// GetPassword makes a request against the nova API to get the encrypted
|
||||
// administrative password.
|
||||
func GetPassword(client *gophercloud.ServiceClient, serverId string) (r GetPasswordResult) {
|
||||
_, r.Err = client.Get(passwordURL(client, serverId), &r.Body, nil)
|
||||
resp, err := client.Get(passwordURL(client, serverId), &r.Body, nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -805,8 +744,9 @@ func ShowConsoleOutput(client *gophercloud.ServiceClient, id string, opts ShowCo
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
resp, err := client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
|
11
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/results.go
generated
vendored
11
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/results.go
generated
vendored
|
@ -212,8 +212,19 @@ type Server struct {
|
|||
// to it.
|
||||
SecurityGroups []map[string]interface{} `json:"security_groups"`
|
||||
|
||||
// AttachedVolumes includes the volume attachments of this instance
|
||||
AttachedVolumes []AttachedVolume `json:"os-extended-volumes:volumes_attached"`
|
||||
|
||||
// Fault contains failure information about a server.
|
||||
Fault Fault `json:"fault"`
|
||||
|
||||
// Tags is a slice/list of string tags in a server.
|
||||
// The requires microversion 2.26 or later.
|
||||
Tags *[]string `json:"tags"`
|
||||
}
|
||||
|
||||
type AttachedVolume struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
type Fault struct {
|
||||
|
|
|
@ -7,7 +7,7 @@ Example of Creating a Service Client
|
|||
|
||||
ao, err := openstack.AuthOptionsFromEnv()
|
||||
provider, err := openstack.AuthenticatedClient(ao)
|
||||
client, err := openstack.NewNetworkV2(client, gophercloud.EndpointOpts{
|
||||
client, err := openstack.NewNetworkV2(provider, gophercloud.EndpointOpts{
|
||||
Region: os.Getenv("OS_REGION_NAME"),
|
||||
})
|
||||
*/
|
||||
|
|
|
@ -29,11 +29,12 @@ func V2EndpointURL(catalog *tokens2.ServiceCatalog, opts gophercloud.EndpointOpt
|
|||
}
|
||||
}
|
||||
|
||||
// Report an error if the options were ambiguous.
|
||||
// If multiple endpoints were found, use the first result
|
||||
// and disregard the other endpoints.
|
||||
//
|
||||
// This behavior matches the Python library. See GH-1764.
|
||||
if len(endpoints) > 1 {
|
||||
err := &ErrMultipleMatchingEndpointsV2{}
|
||||
err.Endpoints = endpoints
|
||||
return "", err
|
||||
endpoints = endpoints[0:1]
|
||||
}
|
||||
|
||||
// Extract the appropriate URL from the matching Endpoint.
|
||||
|
@ -91,9 +92,12 @@ func V3EndpointURL(catalog *tokens3.ServiceCatalog, opts gophercloud.EndpointOpt
|
|||
}
|
||||
}
|
||||
|
||||
// Report an error if the options were ambiguous.
|
||||
// If multiple endpoints were found, use the first result
|
||||
// and disregard the other endpoints.
|
||||
//
|
||||
// This behavior matches the Python library. See GH-1764.
|
||||
if len(endpoints) > 1 {
|
||||
return "", ErrMultipleMatchingEndpointsV3{Endpoints: endpoints}
|
||||
endpoints = endpoints[0:1]
|
||||
}
|
||||
|
||||
// Extract the URL from the matching Endpoint.
|
||||
|
|
|
@ -4,8 +4,6 @@ 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
|
||||
|
@ -24,28 +22,6 @@ 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 }
|
||||
|
|
|
@ -8,7 +8,7 @@ for more information.
|
|||
|
||||
Example to List Tenants
|
||||
|
||||
listOpts := tenants.ListOpts{
|
||||
listOpts := &tenants.ListOpts{
|
||||
Limit: 2,
|
||||
}
|
||||
|
||||
|
|
12
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/requests.go
generated
vendored
12
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/requests.go
generated
vendored
|
@ -60,15 +60,17 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
|
||||
resp, err := client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200, 201},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Get requests details on a single tenant by ID.
|
||||
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
|
||||
_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
|
||||
resp, err := client.Get(getURL(client, id), &r.Body, nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -103,14 +105,16 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Put(updateURL(client, id), &b, &r.Body, &gophercloud.RequestOpts{
|
||||
resp, err := client.Put(updateURL(client, id), &b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete is the operation responsible for permanently deleting a tenant.
|
||||
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
|
||||
_, r.Err = client.Delete(deleteURL(client, id), nil)
|
||||
resp, err := client.Delete(deleteURL(client, id), nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
|
6
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/requests.go
generated
vendored
6
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/requests.go
generated
vendored
|
@ -87,17 +87,19 @@ func Create(client *gophercloud.ServiceClient, auth AuthOptionsBuilder) (r Creat
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(CreateURL(client), b, &r.Body, &gophercloud.RequestOpts{
|
||||
resp, err := client.Post(CreateURL(client), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200, 203},
|
||||
MoreHeaders: map[string]string{"X-Auth-Token": ""},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
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{
|
||||
resp, err := client.Get(GetURL(client, token), &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200, 203},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
|
41
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens/doc.go
generated
vendored
Normal file
41
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
Package tokens provides information and interaction with the EC2 token API
|
||||
resource for the OpenStack Identity service.
|
||||
|
||||
For more information, see:
|
||||
https://docs.openstack.org/api-ref/identity/v2-ext/
|
||||
|
||||
Example to Create a Token From an EC2 access and secret keys
|
||||
|
||||
var authOptions tokens.AuthOptionsBuilder
|
||||
authOptions = &ec2tokens.AuthOptions{
|
||||
Access: "a7f1e798b7c2417cba4a02de97dc3cdc",
|
||||
Secret: "18f4f6761ada4e3795fa5273c30349b9",
|
||||
}
|
||||
|
||||
token, err := ec2tokens.Create(identityClient, authOptions).ExtractToken()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
Example to auth a client using EC2 access and secret keys
|
||||
|
||||
client, err := openstack.NewClient("http://localhost:5000/v3")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var authOptions tokens.AuthOptionsBuilder
|
||||
authOptions = &ec2tokens.AuthOptions{
|
||||
Access: "a7f1e798b7c2417cba4a02de97dc3cdc",
|
||||
Secret: "18f4f6761ada4e3795fa5273c30349b9",
|
||||
AllowReauth: true,
|
||||
}
|
||||
|
||||
err = openstack.AuthenticateV3(client, authOptions, gophercloud.EndpointOpts{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
*/
|
||||
package ec2tokens
|
377
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens/requests.go
generated
vendored
Normal file
377
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens/requests.go
generated
vendored
Normal file
|
@ -0,0 +1,377 @@
|
|||
package ec2tokens
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
|
||||
)
|
||||
|
||||
const (
|
||||
// EC2CredentialsAwsRequestV4 is a constant, used to generate AWS
|
||||
// Credential V4.
|
||||
EC2CredentialsAwsRequestV4 = "aws4_request"
|
||||
// EC2CredentialsHmacSha1V2 is a HMAC SHA1 signature method. Used to
|
||||
// generate AWS Credential V2.
|
||||
EC2CredentialsHmacSha1V2 = "HmacSHA1"
|
||||
// EC2CredentialsHmacSha256V2 is a HMAC SHA256 signature method. Used
|
||||
// to generate AWS Credential V2.
|
||||
EC2CredentialsHmacSha256V2 = "HmacSHA256"
|
||||
// EC2CredentialsAwsHmacV4 is an AWS signature V4 signing method.
|
||||
// More details:
|
||||
// https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
|
||||
EC2CredentialsAwsHmacV4 = "AWS4-HMAC-SHA256"
|
||||
// EC2CredentialsTimestampFormatV4 is an AWS signature V4 timestamp
|
||||
// format.
|
||||
EC2CredentialsTimestampFormatV4 = "20060102T150405Z"
|
||||
// EC2CredentialsDateFormatV4 is an AWS signature V4 date format.
|
||||
EC2CredentialsDateFormatV4 = "20060102"
|
||||
)
|
||||
|
||||
// AuthOptions represents options for authenticating a user using EC2 credentials.
|
||||
type AuthOptions struct {
|
||||
// Access is the EC2 Credential Access ID.
|
||||
Access string `json:"access" required:"true"`
|
||||
// Secret is the EC2 Credential Secret, used to calculate signature.
|
||||
// Not used, when a Signature is is.
|
||||
Secret string `json:"-"`
|
||||
// Host is a HTTP request Host header. Used to calculate an AWS
|
||||
// signature V2. For signature V4 set the Host inside Headers map.
|
||||
// Optional.
|
||||
Host string `json:"host"`
|
||||
// Path is a HTTP request path. Optional.
|
||||
Path string `json:"path"`
|
||||
// Verb is a HTTP request method. Optional.
|
||||
Verb string `json:"verb"`
|
||||
// Headers is a map of HTTP request headers. Optional.
|
||||
Headers map[string]string `json:"headers"`
|
||||
// Region is a region name to calculate an AWS signature V4. Optional.
|
||||
Region string `json:"-"`
|
||||
// Service is a service name to calculate an AWS signature V4. Optional.
|
||||
Service string `json:"-"`
|
||||
// Params is a map of GET method parameters. Optional.
|
||||
Params map[string]string `json:"params"`
|
||||
// AllowReauth allows Gophercloud to re-authenticate automatically
|
||||
// if/when your token expires.
|
||||
AllowReauth bool `json:"-"`
|
||||
// Signature can be either a []byte (encoded to base64 automatically) or
|
||||
// a string. You can set the singature explicitly, when you already know
|
||||
// it. In this case default Params won't be automatically set. Optional.
|
||||
Signature interface{} `json:"signature"`
|
||||
// BodyHash is a HTTP request body sha256 hash. When nil and Signature
|
||||
// is not set, a random hash is generated. Optional.
|
||||
BodyHash *string `json:"body_hash"`
|
||||
// Timestamp is a timestamp to calculate a V4 signature. Optional.
|
||||
Timestamp *time.Time `json:"-"`
|
||||
// Token is a []byte string (encoded to base64 automatically) which was
|
||||
// signed by an EC2 secret key. Used by S3 tokens for validation only.
|
||||
// Token must be set with a Signature. If a Signature is not provided,
|
||||
// a Token will be generated automatically along with a Signature.
|
||||
Token []byte `json:"token,omitempty"`
|
||||
}
|
||||
|
||||
// EC2CredentialsBuildCanonicalQueryStringV2 builds a canonical query string
|
||||
// for an AWS signature V2.
|
||||
// https://github.com/openstack/python-keystoneclient/blob/stable/train/keystoneclient/contrib/ec2/utils.py#L133
|
||||
func EC2CredentialsBuildCanonicalQueryStringV2(params map[string]string) string {
|
||||
var keys []string
|
||||
for k := range params {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
var pairs []string
|
||||
for _, k := range keys {
|
||||
pairs = append(pairs, fmt.Sprintf("%s=%s", k, url.QueryEscape(params[k])))
|
||||
}
|
||||
|
||||
return strings.Join(pairs, "&")
|
||||
}
|
||||
|
||||
// EC2CredentialsBuildStringToSignV2 builds a string to sign an AWS signature
|
||||
// V2.
|
||||
// https://github.com/openstack/python-keystoneclient/blob/stable/train/keystoneclient/contrib/ec2/utils.py#L148
|
||||
func EC2CredentialsBuildStringToSignV2(opts AuthOptions) []byte {
|
||||
stringToSign := strings.Join([]string{
|
||||
opts.Verb,
|
||||
opts.Host,
|
||||
opts.Path,
|
||||
}, "\n")
|
||||
|
||||
return []byte(strings.Join([]string{
|
||||
stringToSign,
|
||||
EC2CredentialsBuildCanonicalQueryStringV2(opts.Params),
|
||||
}, "\n"))
|
||||
}
|
||||
|
||||
// EC2CredentialsBuildCanonicalQueryStringV2 builds a canonical query string
|
||||
// for an AWS signature V4.
|
||||
// https://github.com/openstack/python-keystoneclient/blob/stable/train/keystoneclient/contrib/ec2/utils.py#L244
|
||||
func EC2CredentialsBuildCanonicalQueryStringV4(verb string, params map[string]string) string {
|
||||
if verb == "POST" {
|
||||
return ""
|
||||
}
|
||||
return EC2CredentialsBuildCanonicalQueryStringV2(params)
|
||||
}
|
||||
|
||||
// EC2CredentialsBuildCanonicalHeadersV4 builds a canonical string based on
|
||||
// "headers" map and "signedHeaders" string parameters.
|
||||
// https://github.com/openstack/python-keystoneclient/blob/stable/train/keystoneclient/contrib/ec2/utils.py#L216
|
||||
func EC2CredentialsBuildCanonicalHeadersV4(headers map[string]string, signedHeaders string) string {
|
||||
headersLower := make(map[string]string, len(headers))
|
||||
for k, v := range headers {
|
||||
headersLower[strings.ToLower(k)] = v
|
||||
}
|
||||
|
||||
var headersList []string
|
||||
for _, h := range strings.Split(signedHeaders, ";") {
|
||||
if v, ok := headersLower[h]; ok {
|
||||
headersList = append(headersList, h+":"+v)
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(headersList, "\n") + "\n"
|
||||
}
|
||||
|
||||
// EC2CredentialsBuildSignatureKeyV4 builds a HMAC 256 signature key based on
|
||||
// input parameters.
|
||||
// https://github.com/openstack/python-keystoneclient/blob/stable/train/keystoneclient/contrib/ec2/utils.py#L169
|
||||
func EC2CredentialsBuildSignatureKeyV4(secret, region, service string, date time.Time) []byte {
|
||||
kDate := sumHMAC256([]byte("AWS4"+secret), []byte(date.Format(EC2CredentialsDateFormatV4)))
|
||||
kRegion := sumHMAC256(kDate, []byte(region))
|
||||
kService := sumHMAC256(kRegion, []byte(service))
|
||||
return sumHMAC256(kService, []byte(EC2CredentialsAwsRequestV4))
|
||||
}
|
||||
|
||||
// EC2CredentialsBuildStringToSignV4 builds an AWS v4 signature string to sign
|
||||
// based on input parameters.
|
||||
// https://github.com/openstack/python-keystoneclient/blob/stable/train/keystoneclient/contrib/ec2/utils.py#L251
|
||||
func EC2CredentialsBuildStringToSignV4(opts AuthOptions, signedHeaders string, bodyHash string, date time.Time) []byte {
|
||||
scope := strings.Join([]string{
|
||||
date.Format(EC2CredentialsDateFormatV4),
|
||||
opts.Region,
|
||||
opts.Service,
|
||||
EC2CredentialsAwsRequestV4,
|
||||
}, "/")
|
||||
|
||||
canonicalRequest := strings.Join([]string{
|
||||
opts.Verb,
|
||||
opts.Path,
|
||||
EC2CredentialsBuildCanonicalQueryStringV4(opts.Verb, opts.Params),
|
||||
EC2CredentialsBuildCanonicalHeadersV4(opts.Headers, signedHeaders),
|
||||
signedHeaders,
|
||||
bodyHash,
|
||||
}, "\n")
|
||||
hash := sha256.Sum256([]byte(canonicalRequest))
|
||||
|
||||
return []byte(strings.Join([]string{
|
||||
EC2CredentialsAwsHmacV4,
|
||||
date.Format(EC2CredentialsTimestampFormatV4),
|
||||
scope,
|
||||
hex.EncodeToString(hash[:]),
|
||||
}, "\n"))
|
||||
}
|
||||
|
||||
// EC2CredentialsBuildSignatureV4 builds an AWS v4 signature based on input
|
||||
// parameters.
|
||||
// https://github.com/openstack/python-keystoneclient/blob/stable/train/keystoneclient/contrib/ec2/utils.py#L285..L286
|
||||
func EC2CredentialsBuildSignatureV4(key []byte, stringToSign []byte) string {
|
||||
return hex.EncodeToString(sumHMAC256(key, stringToSign))
|
||||
}
|
||||
|
||||
// EC2CredentialsBuildAuthorizationHeaderV4 builds an AWS v4 Authorization
|
||||
// header based on auth parameters, date and signature
|
||||
func EC2CredentialsBuildAuthorizationHeaderV4(opts AuthOptions, signedHeaders string, signature string, date time.Time) string {
|
||||
return fmt.Sprintf("%s Credential=%s/%s/%s/%s/%s, SignedHeaders=%s, Signature=%s",
|
||||
EC2CredentialsAwsHmacV4,
|
||||
opts.Access,
|
||||
date.Format(EC2CredentialsDateFormatV4),
|
||||
opts.Region,
|
||||
opts.Service,
|
||||
EC2CredentialsAwsRequestV4,
|
||||
signedHeaders,
|
||||
signature)
|
||||
}
|
||||
|
||||
// ToTokenV3ScopeMap is a dummy method to satisfy tokens.AuthOptionsBuilder
|
||||
// interface.
|
||||
func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ToTokenV3HeadersMap allows AuthOptions to satisfy the AuthOptionsBuilder
|
||||
// interface in the v3 tokens package.
|
||||
func (opts *AuthOptions) ToTokenV3HeadersMap(map[string]interface{}) (map[string]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// CanReauth is a method method to satisfy tokens.AuthOptionsBuilder interface
|
||||
func (opts *AuthOptions) CanReauth() bool {
|
||||
return opts.AllowReauth
|
||||
}
|
||||
|
||||
// ToTokenV3CreateMap formats an AuthOptions into a create request.
|
||||
func (opts *AuthOptions) ToTokenV3CreateMap(map[string]interface{}) (map[string]interface{}, error) {
|
||||
b, err := gophercloud.BuildRequestBody(opts, "credentials")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if opts.Signature != nil {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// calculate signature, when it is not set
|
||||
c, _ := b["credentials"].(map[string]interface{})
|
||||
h := interfaceToMap(c, "headers")
|
||||
p := interfaceToMap(c, "params")
|
||||
|
||||
// detect and process a signature v2
|
||||
if v, ok := p["SignatureVersion"]; ok && v == "2" {
|
||||
if _, ok := c["body_hash"]; ok {
|
||||
delete(c, "body_hash")
|
||||
}
|
||||
if _, ok := c["headers"]; ok {
|
||||
delete(c, "headers")
|
||||
}
|
||||
if v, ok := p["SignatureMethod"]; ok {
|
||||
// params is a map of strings
|
||||
strToSign := EC2CredentialsBuildStringToSignV2(*opts)
|
||||
switch v {
|
||||
case EC2CredentialsHmacSha1V2:
|
||||
// keystone uses this method only when HmacSHA256 is not available on the server side
|
||||
// https://github.com/openstack/python-keystoneclient/blob/stable/train/keystoneclient/contrib/ec2/utils.py#L151..L156
|
||||
c["signature"] = sumHMAC1([]byte(opts.Secret), strToSign)
|
||||
return b, nil
|
||||
case EC2CredentialsHmacSha256V2:
|
||||
c["signature"] = sumHMAC256([]byte(opts.Secret), strToSign)
|
||||
return b, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unsupported signature method: %s", v)
|
||||
}
|
||||
return nil, fmt.Errorf("signature method must be provided")
|
||||
} else if ok {
|
||||
return nil, fmt.Errorf("unsupported signature version: %s", v)
|
||||
}
|
||||
|
||||
// it is not a signature v2, but a signature v4
|
||||
date := time.Now().UTC()
|
||||
if opts.Timestamp != nil {
|
||||
date = *opts.Timestamp
|
||||
}
|
||||
if v, _ := c["body_hash"]; v == nil {
|
||||
// when body_hash is not set, generate a random one
|
||||
c["body_hash"] = randomBodyHash()
|
||||
}
|
||||
|
||||
signedHeaders, _ := h["X-Amz-SignedHeaders"]
|
||||
|
||||
stringToSign := EC2CredentialsBuildStringToSignV4(*opts, signedHeaders, c["body_hash"].(string), date)
|
||||
key := EC2CredentialsBuildSignatureKeyV4(opts.Secret, opts.Region, opts.Service, date)
|
||||
c["signature"] = EC2CredentialsBuildSignatureV4(key, stringToSign)
|
||||
h["X-Amz-Date"] = date.Format(EC2CredentialsTimestampFormatV4)
|
||||
h["Authorization"] = EC2CredentialsBuildAuthorizationHeaderV4(*opts, signedHeaders, c["signature"].(string), date)
|
||||
|
||||
// token is only used for S3 tokens validation and will be removed when using EC2 validation
|
||||
c["token"] = stringToSign
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// Create authenticates and either generates a new token from EC2 credentials
|
||||
func Create(c *gophercloud.ServiceClient, opts tokens.AuthOptionsBuilder) (r tokens.CreateResult) {
|
||||
b, err := opts.ToTokenV3CreateMap(nil)
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
// delete "token" element, since it is used in s3tokens
|
||||
deleteBodyElements(b, "token")
|
||||
|
||||
resp, err := c.Post(ec2tokensURL(c), b, &r.Body, &gophercloud.RequestOpts{
|
||||
MoreHeaders: map[string]string{"X-Auth-Token": ""},
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// ValidateS3Token authenticates an S3 request using EC2 credentials. Doesn't
|
||||
// generate a new token ID, but returns a tokens.CreateResult.
|
||||
func ValidateS3Token(c *gophercloud.ServiceClient, opts tokens.AuthOptionsBuilder) (r tokens.CreateResult) {
|
||||
b, err := opts.ToTokenV3CreateMap(nil)
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
// delete unused element, since it is used in ec2tokens only
|
||||
deleteBodyElements(b, "body_hash", "headers", "host", "params", "path", "verb")
|
||||
|
||||
resp, err := c.Post(s3tokensURL(c), b, &r.Body, &gophercloud.RequestOpts{
|
||||
MoreHeaders: map[string]string{"X-Auth-Token": ""},
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// The following are small helper functions used to help build the signature.
|
||||
|
||||
// sumHMAC1 is a func to implement the HMAC SHA1 signature method.
|
||||
func sumHMAC1(key []byte, data []byte) []byte {
|
||||
hash := hmac.New(sha1.New, key)
|
||||
hash.Write(data)
|
||||
return hash.Sum(nil)
|
||||
}
|
||||
|
||||
// sumHMAC256 is a func to implement the HMAC SHA256 signature method.
|
||||
func sumHMAC256(key []byte, data []byte) []byte {
|
||||
hash := hmac.New(sha256.New, key)
|
||||
hash.Write(data)
|
||||
return hash.Sum(nil)
|
||||
}
|
||||
|
||||
// randomBodyHash is a func to generate a random sha256 hexdigest.
|
||||
func randomBodyHash() string {
|
||||
h := make([]byte, 64)
|
||||
rand.Read(h)
|
||||
return hex.EncodeToString(h)
|
||||
}
|
||||
|
||||
// interfaceToMap is a func used to represent a "credentials" map element as a
|
||||
// "map[string]string"
|
||||
func interfaceToMap(c map[string]interface{}, key string) map[string]string {
|
||||
// convert map[string]interface{} to map[string]string
|
||||
m := make(map[string]string)
|
||||
if v, _ := c[key].(map[string]interface{}); v != nil {
|
||||
for k, v := range v {
|
||||
m[k] = v.(string)
|
||||
}
|
||||
}
|
||||
|
||||
c[key] = m
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// deleteBodyElements deletes map body elements
|
||||
func deleteBodyElements(b map[string]interface{}, elements ...string) {
|
||||
if c, ok := b["credentials"].(map[string]interface{}); ok {
|
||||
for _, k := range elements {
|
||||
if _, ok := c[k]; ok {
|
||||
delete(c, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens/urls.go
generated
vendored
Normal file
11
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens/urls.go
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
package ec2tokens
|
||||
|
||||
import "github.com/gophercloud/gophercloud"
|
||||
|
||||
func ec2tokensURL(c *gophercloud.ServiceClient) string {
|
||||
return c.ServiceURL("ec2tokens")
|
||||
}
|
||||
|
||||
func s3tokensURL(c *gophercloud.ServiceClient) string {
|
||||
return c.ServiceURL("s3tokens")
|
||||
}
|
123
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1/doc.go
generated
vendored
Normal file
123
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
Package oauth1 enables management of OpenStack OAuth1 tokens and Authentication.
|
||||
|
||||
Example to Create an OAuth1 Consumer
|
||||
|
||||
createConsumerOpts := oauth1.CreateConsumerOpts{
|
||||
Description: "My consumer",
|
||||
}
|
||||
consumer, err := oauth1.CreateConsumer(identityClient, createConsumerOpts).Extract()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// NOTE: Consumer secret is available only on create response
|
||||
fmt.Printf("Consumer: %+v\n", consumer)
|
||||
|
||||
Example to Request an unauthorized OAuth1 token
|
||||
|
||||
requestTokenOpts := oauth1.RequestTokenOpts{
|
||||
OAuthConsumerKey: consumer.ID,
|
||||
OAuthConsumerSecret: consumer.Secret,
|
||||
OAuthSignatureMethod: oauth1.HMACSHA1,
|
||||
RequestedProjectID: projectID,
|
||||
}
|
||||
requestToken, err := oauth1.RequestToken(identityClient, requestTokenOpts).Extract()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// NOTE: Request token secret is available only on request response
|
||||
fmt.Printf("Request token: %+v\n", requestToken)
|
||||
|
||||
Example to Authorize an unauthorized OAuth1 token
|
||||
|
||||
authorizeTokenOpts := oauth1.AuthorizeTokenOpts{
|
||||
Roles: []oauth1.Role{
|
||||
{Name: "member"},
|
||||
},
|
||||
}
|
||||
authToken, err := oauth1.AuthorizeToken(identityClient, requestToken.OAuthToken, authorizeTokenOpts).Extract()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Verifier ID of the unauthorized Token: %+v\n", authToken.OAuthVerifier)
|
||||
|
||||
Example to Create an OAuth1 Access Token
|
||||
|
||||
accessTokenOpts := oauth1.CreateAccessTokenOpts{
|
||||
OAuthConsumerKey: consumer.ID,
|
||||
OAuthConsumerSecret: consumer.Secret,
|
||||
OAuthToken: requestToken.OAuthToken,
|
||||
OAuthTokenSecret: requestToken.OAuthTokenSecret,
|
||||
OAuthVerifier: authToken.OAuthVerifier,
|
||||
OAuthSignatureMethod: oauth1.HMACSHA1,
|
||||
}
|
||||
accessToken, err := oauth1.CreateAccessToken(identityClient, accessTokenOpts).Extract()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// NOTE: Access token secret is available only on create response
|
||||
fmt.Printf("OAuth1 Access Token: %+v\n", accessToken)
|
||||
|
||||
Example to List User's OAuth1 Access Tokens
|
||||
|
||||
allPages, err := oauth1.ListAccessTokens(identityClient, userID).AllPages()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
accessTokens, err := oauth1.ExtractAccessTokens(allPages)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _, accessToken := range accessTokens {
|
||||
fmt.Printf("Access Token: %+v\n", accessToken)
|
||||
}
|
||||
|
||||
Example to Authenticate a client using OAuth1 method
|
||||
|
||||
client, err := openstack.NewClient("http://localhost:5000/v3")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
authOptions := &oauth1.AuthOptions{
|
||||
// consumer token, created earlier
|
||||
OAuthConsumerKey: consumer.ID,
|
||||
OAuthConsumerSecret: consumer.Secret,
|
||||
// access token, created earlier
|
||||
OAuthToken: accessToken.OAuthToken,
|
||||
OAuthTokenSecret: accessToken.OAuthTokenSecret,
|
||||
OAuthSignatureMethod: oauth1.HMACSHA1,
|
||||
}
|
||||
err = openstack.AuthenticateV3(client, authOptions, gophercloud.EndpointOpts{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
Example to Create a Token using OAuth1 method
|
||||
|
||||
var oauth1Token struct {
|
||||
tokens.Token
|
||||
oauth1.TokenExt
|
||||
}
|
||||
|
||||
createOpts := &oauth1.AuthOptions{
|
||||
// consumer token, created earlier
|
||||
OAuthConsumerKey: consumer.ID,
|
||||
OAuthConsumerSecret: consumer.Secret,
|
||||
// access token, created earlier
|
||||
OAuthToken: accessToken.OAuthToken,
|
||||
OAuthTokenSecret: accessToken.OAuthTokenSecret,
|
||||
OAuthSignatureMethod: oauth1.HMACSHA1,
|
||||
}
|
||||
err := tokens.Create(identityClient, createOpts).ExtractInto(&oauth1Token)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
*/
|
||||
package oauth1
|
587
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1/requests.go
generated
vendored
Normal file
587
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1/requests.go
generated
vendored
Normal file
|
@ -0,0 +1,587 @@
|
|||
package oauth1
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// Type SignatureMethod is a OAuth1 SignatureMethod type.
|
||||
type SignatureMethod string
|
||||
|
||||
const (
|
||||
// HMACSHA1 is a recommended OAuth1 signature method.
|
||||
HMACSHA1 SignatureMethod = "HMAC-SHA1"
|
||||
|
||||
// PLAINTEXT signature method is not recommended to be used in
|
||||
// production environment.
|
||||
PLAINTEXT SignatureMethod = "PLAINTEXT"
|
||||
|
||||
// OAuth1TokenContentType is a supported content type for an OAuth1
|
||||
// token.
|
||||
OAuth1TokenContentType = "application/x-www-form-urlencoded"
|
||||
)
|
||||
|
||||
// AuthOptions represents options for authenticating a user using OAuth1 tokens.
|
||||
type AuthOptions struct {
|
||||
// OAuthConsumerKey is the OAuth1 Consumer Key.
|
||||
OAuthConsumerKey string `q:"oauth_consumer_key" required:"true"`
|
||||
|
||||
// OAuthConsumerSecret is the OAuth1 Consumer Secret. Used to generate
|
||||
// an OAuth1 request signature.
|
||||
OAuthConsumerSecret string `required:"true"`
|
||||
|
||||
// OAuthToken is the OAuth1 Request Token.
|
||||
OAuthToken string `q:"oauth_token" required:"true"`
|
||||
|
||||
// OAuthTokenSecret is the OAuth1 Request Token Secret. Used to generate
|
||||
// an OAuth1 request signature.
|
||||
OAuthTokenSecret string `required:"true"`
|
||||
|
||||
// OAuthSignatureMethod is the OAuth1 signature method the Consumer used
|
||||
// to sign the request. Supported values are "HMAC-SHA1" or "PLAINTEXT".
|
||||
// "PLAINTEXT" is not recommended for production usage.
|
||||
OAuthSignatureMethod SignatureMethod `q:"oauth_signature_method" required:"true"`
|
||||
|
||||
// OAuthTimestamp is an OAuth1 request timestamp. If nil, current Unix
|
||||
// timestamp will be used.
|
||||
OAuthTimestamp *time.Time
|
||||
|
||||
// OAuthNonce is an OAuth1 request nonce. Nonce must be a random string,
|
||||
// uniquely generated for each request. Will be generated automatically
|
||||
// when it is not set.
|
||||
OAuthNonce string `q:"oauth_nonce"`
|
||||
|
||||
// AllowReauth allows Gophercloud to re-authenticate automatically
|
||||
// if/when your token expires.
|
||||
AllowReauth bool
|
||||
}
|
||||
|
||||
// ToTokenV3HeadersMap builds the headers required for an OAuth1-based create
|
||||
// request.
|
||||
func (opts AuthOptions) ToTokenV3HeadersMap(headerOpts map[string]interface{}) (map[string]string, error) {
|
||||
q, err := buildOAuth1QueryString(opts, opts.OAuthTimestamp, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signatureKeys := []string{opts.OAuthConsumerSecret, opts.OAuthTokenSecret}
|
||||
|
||||
method := headerOpts["method"].(string)
|
||||
u := headerOpts["url"].(string)
|
||||
stringToSign := buildStringToSign(method, u, q.Query())
|
||||
signature := url.QueryEscape(signString(opts.OAuthSignatureMethod, stringToSign, signatureKeys))
|
||||
|
||||
authHeader := buildAuthHeader(q.Query(), signature)
|
||||
|
||||
headers := map[string]string{
|
||||
"Authorization": authHeader,
|
||||
"X-Auth-Token": "",
|
||||
}
|
||||
|
||||
return headers, nil
|
||||
}
|
||||
|
||||
// ToTokenV3ScopeMap allows AuthOptions to satisfy the tokens.AuthOptionsBuilder
|
||||
// interface.
|
||||
func (opts AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// CanReauth allows AuthOptions to satisfy the tokens.AuthOptionsBuilder
|
||||
// interface.
|
||||
func (opts AuthOptions) CanReauth() bool {
|
||||
return opts.AllowReauth
|
||||
}
|
||||
|
||||
// ToTokenV3CreateMap builds a create request body.
|
||||
func (opts AuthOptions) ToTokenV3CreateMap(map[string]interface{}) (map[string]interface{}, error) {
|
||||
// identityReq defines the "identity" portion of an OAuth1-based authentication
|
||||
// create request body.
|
||||
type identityReq struct {
|
||||
Methods []string `json:"methods"`
|
||||
OAuth1 struct{} `json:"oauth1"`
|
||||
}
|
||||
|
||||
// authReq defines the "auth" portion of an OAuth1-based authentication
|
||||
// create request body.
|
||||
type authReq struct {
|
||||
Identity identityReq `json:"identity"`
|
||||
}
|
||||
|
||||
// oauth1Request defines how an OAuth1-based authentication create
|
||||
// request body looks.
|
||||
type oauth1Request struct {
|
||||
Auth authReq `json:"auth"`
|
||||
}
|
||||
|
||||
var req oauth1Request
|
||||
|
||||
req.Auth.Identity.Methods = []string{"oauth1"}
|
||||
return gophercloud.BuildRequestBody(req, "")
|
||||
}
|
||||
|
||||
// Create authenticates and either generates a new OpenStack token from an
|
||||
// OAuth1 token.
|
||||
func Create(client *gophercloud.ServiceClient, opts tokens.AuthOptionsBuilder) (r tokens.CreateResult) {
|
||||
b, err := opts.ToTokenV3CreateMap(nil)
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
headerOpts := map[string]interface{}{
|
||||
"method": "POST",
|
||||
"url": authURL(client),
|
||||
}
|
||||
|
||||
h, err := opts.ToTokenV3HeadersMap(headerOpts)
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := client.Post(authURL(client), b, &r.Body, &gophercloud.RequestOpts{
|
||||
MoreHeaders: h,
|
||||
OkCodes: []int{201},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// CreateConsumerOptsBuilder allows extensions to add additional parameters to
|
||||
// the CreateConsumer request.
|
||||
type CreateConsumerOptsBuilder interface {
|
||||
ToOAuth1CreateConsumerMap() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// CreateConsumerOpts provides options used to create a new Consumer.
|
||||
type CreateConsumerOpts struct {
|
||||
// Description is the consumer description.
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
// ToOAuth1CreateConsumerMap formats a CreateConsumerOpts into a create request.
|
||||
func (opts CreateConsumerOpts) ToOAuth1CreateConsumerMap() (map[string]interface{}, error) {
|
||||
return gophercloud.BuildRequestBody(opts, "consumer")
|
||||
}
|
||||
|
||||
// Create creates a new Consumer.
|
||||
func CreateConsumer(client *gophercloud.ServiceClient, opts CreateConsumerOptsBuilder) (r CreateConsumerResult) {
|
||||
b, err := opts.ToOAuth1CreateConsumerMap()
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
resp, err := client.Post(consumersURL(client), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{201},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete deletes a Consumer.
|
||||
func DeleteConsumer(client *gophercloud.ServiceClient, id string) (r DeleteConsumerResult) {
|
||||
resp, err := client.Delete(consumerURL(client, id), nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// List enumerates Consumers.
|
||||
func ListConsumers(client *gophercloud.ServiceClient) pagination.Pager {
|
||||
return pagination.NewPager(client, consumersURL(client), func(r pagination.PageResult) pagination.Page {
|
||||
return ConsumersPage{pagination.LinkedPageBase{PageResult: r}}
|
||||
})
|
||||
}
|
||||
|
||||
// GetConsumer retrieves details on a single Consumer by ID.
|
||||
func GetConsumer(client *gophercloud.ServiceClient, id string) (r GetConsumerResult) {
|
||||
resp, err := client.Get(consumerURL(client, id), &r.Body, nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateConsumerOpts provides options used to update a consumer.
|
||||
type UpdateConsumerOpts struct {
|
||||
// Description is the consumer description.
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
// ToOAuth1UpdateConsumerMap formats an UpdateConsumerOpts into a consumer update
|
||||
// request.
|
||||
func (opts UpdateConsumerOpts) ToOAuth1UpdateConsumerMap() (map[string]interface{}, error) {
|
||||
return gophercloud.BuildRequestBody(opts, "consumer")
|
||||
}
|
||||
|
||||
// UpdateConsumer updates an existing Consumer.
|
||||
func UpdateConsumer(client *gophercloud.ServiceClient, id string, opts UpdateConsumerOpts) (r UpdateConsumerResult) {
|
||||
b, err := opts.ToOAuth1UpdateConsumerMap()
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
resp, err := client.Patch(consumerURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// RequestTokenOptsBuilder allows extensions to add additional parameters to the
|
||||
// RequestToken request.
|
||||
type RequestTokenOptsBuilder interface {
|
||||
ToOAuth1RequestTokenHeaders(string, string) (map[string]string, error)
|
||||
}
|
||||
|
||||
// RequestTokenOpts provides options used to get a consumer unauthorized
|
||||
// request token.
|
||||
type RequestTokenOpts struct {
|
||||
// OAuthConsumerKey is the OAuth1 Consumer Key.
|
||||
OAuthConsumerKey string `q:"oauth_consumer_key" required:"true"`
|
||||
|
||||
// OAuthConsumerSecret is the OAuth1 Consumer Secret. Used to generate
|
||||
// an OAuth1 request signature.
|
||||
OAuthConsumerSecret string `required:"true"`
|
||||
|
||||
// OAuthSignatureMethod is the OAuth1 signature method the Consumer used
|
||||
// to sign the request. Supported values are "HMAC-SHA1" or "PLAINTEXT".
|
||||
// "PLAINTEXT" is not recommended for production usage.
|
||||
OAuthSignatureMethod SignatureMethod `q:"oauth_signature_method" required:"true"`
|
||||
|
||||
// OAuthTimestamp is an OAuth1 request timestamp. If nil, current Unix
|
||||
// timestamp will be used.
|
||||
OAuthTimestamp *time.Time
|
||||
|
||||
// OAuthNonce is an OAuth1 request nonce. Nonce must be a random string,
|
||||
// uniquely generated for each request. Will be generated automatically
|
||||
// when it is not set.
|
||||
OAuthNonce string `q:"oauth_nonce"`
|
||||
|
||||
// RequestedProjectID is a Project ID a consumer user requested an
|
||||
// access to.
|
||||
RequestedProjectID string `h:"Requested-Project-Id"`
|
||||
}
|
||||
|
||||
// ToOAuth1RequestTokenHeaders formats a RequestTokenOpts into a map of request
|
||||
// headers.
|
||||
func (opts RequestTokenOpts) ToOAuth1RequestTokenHeaders(method, u string) (map[string]string, error) {
|
||||
q, err := buildOAuth1QueryString(opts, opts.OAuthTimestamp, "oob")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h, err := gophercloud.BuildHeaders(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signatureKeys := []string{opts.OAuthConsumerSecret}
|
||||
stringToSign := buildStringToSign(method, u, q.Query())
|
||||
signature := url.QueryEscape(signString(opts.OAuthSignatureMethod, stringToSign, signatureKeys))
|
||||
authHeader := buildAuthHeader(q.Query(), signature)
|
||||
|
||||
h["Authorization"] = authHeader
|
||||
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// RequestToken requests an unauthorized OAuth1 Token.
|
||||
func RequestToken(client *gophercloud.ServiceClient, opts RequestTokenOptsBuilder) (r TokenResult) {
|
||||
h, err := opts.ToOAuth1RequestTokenHeaders("POST", requestTokenURL(client))
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := client.Post(requestTokenURL(client), nil, nil, &gophercloud.RequestOpts{
|
||||
MoreHeaders: h,
|
||||
OkCodes: []int{201},
|
||||
KeepResponseBody: true,
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
if r.Err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if v := r.Header.Get("Content-Type"); v != OAuth1TokenContentType {
|
||||
r.Err = fmt.Errorf("unsupported Content-Type: %q", v)
|
||||
return
|
||||
}
|
||||
r.Body, r.Err = ioutil.ReadAll(resp.Body)
|
||||
return
|
||||
}
|
||||
|
||||
// AuthorizeTokenOptsBuilder allows extensions to add additional parameters to
|
||||
// the AuthorizeToken request.
|
||||
type AuthorizeTokenOptsBuilder interface {
|
||||
ToOAuth1AuthorizeTokenMap() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// AuthorizeTokenOpts provides options used to authorize a request token.
|
||||
type AuthorizeTokenOpts struct {
|
||||
Roles []Role `json:"roles"`
|
||||
}
|
||||
|
||||
// Role is a struct representing a role object in a AuthorizeTokenOpts struct.
|
||||
type Role struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
// ToOAuth1AuthorizeTokenMap formats an AuthorizeTokenOpts into an authorize token
|
||||
// request.
|
||||
func (opts AuthorizeTokenOpts) ToOAuth1AuthorizeTokenMap() (map[string]interface{}, error) {
|
||||
for _, r := range opts.Roles {
|
||||
if r == (Role{}) {
|
||||
return nil, fmt.Errorf("role must not be empty")
|
||||
}
|
||||
}
|
||||
return gophercloud.BuildRequestBody(opts, "")
|
||||
}
|
||||
|
||||
// AuthorizeToken authorizes an unauthorized consumer token.
|
||||
func AuthorizeToken(client *gophercloud.ServiceClient, id string, opts AuthorizeTokenOptsBuilder) (r AuthorizeTokenResult) {
|
||||
b, err := opts.ToOAuth1AuthorizeTokenMap()
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
resp, err := client.Put(authorizeTokenURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// CreateAccessTokenOptsBuilder allows extensions to add additional parameters
|
||||
// to the CreateAccessToken request.
|
||||
type CreateAccessTokenOptsBuilder interface {
|
||||
ToOAuth1CreateAccessTokenHeaders(string, string) (map[string]string, error)
|
||||
}
|
||||
|
||||
// CreateAccessTokenOpts provides options used to create an OAuth1 token.
|
||||
type CreateAccessTokenOpts struct {
|
||||
// OAuthConsumerKey is the OAuth1 Consumer Key.
|
||||
OAuthConsumerKey string `q:"oauth_consumer_key" required:"true"`
|
||||
|
||||
// OAuthConsumerSecret is the OAuth1 Consumer Secret. Used to generate
|
||||
// an OAuth1 request signature.
|
||||
OAuthConsumerSecret string `required:"true"`
|
||||
|
||||
// OAuthToken is the OAuth1 Request Token.
|
||||
OAuthToken string `q:"oauth_token" required:"true"`
|
||||
|
||||
// OAuthTokenSecret is the OAuth1 Request Token Secret. Used to generate
|
||||
// an OAuth1 request signature.
|
||||
OAuthTokenSecret string `required:"true"`
|
||||
|
||||
// OAuthVerifier is the OAuth1 verification code.
|
||||
OAuthVerifier string `q:"oauth_verifier" required:"true"`
|
||||
|
||||
// OAuthSignatureMethod is the OAuth1 signature method the Consumer used
|
||||
// to sign the request. Supported values are "HMAC-SHA1" or "PLAINTEXT".
|
||||
// "PLAINTEXT" is not recommended for production usage.
|
||||
OAuthSignatureMethod SignatureMethod `q:"oauth_signature_method" required:"true"`
|
||||
|
||||
// OAuthTimestamp is an OAuth1 request timestamp. If nil, current Unix
|
||||
// timestamp will be used.
|
||||
OAuthTimestamp *time.Time
|
||||
|
||||
// OAuthNonce is an OAuth1 request nonce. Nonce must be a random string,
|
||||
// uniquely generated for each request. Will be generated automatically
|
||||
// when it is not set.
|
||||
OAuthNonce string `q:"oauth_nonce"`
|
||||
}
|
||||
|
||||
// ToOAuth1CreateAccessTokenHeaders formats a CreateAccessTokenOpts into a map of
|
||||
// request headers.
|
||||
func (opts CreateAccessTokenOpts) ToOAuth1CreateAccessTokenHeaders(method, u string) (map[string]string, error) {
|
||||
q, err := buildOAuth1QueryString(opts, opts.OAuthTimestamp, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signatureKeys := []string{opts.OAuthConsumerSecret, opts.OAuthTokenSecret}
|
||||
stringToSign := buildStringToSign(method, u, q.Query())
|
||||
signature := url.QueryEscape(signString(opts.OAuthSignatureMethod, stringToSign, signatureKeys))
|
||||
authHeader := buildAuthHeader(q.Query(), signature)
|
||||
|
||||
headers := map[string]string{
|
||||
"Authorization": authHeader,
|
||||
}
|
||||
|
||||
return headers, nil
|
||||
}
|
||||
|
||||
// CreateAccessToken creates a new OAuth1 Access Token
|
||||
func CreateAccessToken(client *gophercloud.ServiceClient, opts CreateAccessTokenOptsBuilder) (r TokenResult) {
|
||||
h, err := opts.ToOAuth1CreateAccessTokenHeaders("POST", createAccessTokenURL(client))
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := client.Post(createAccessTokenURL(client), nil, nil, &gophercloud.RequestOpts{
|
||||
MoreHeaders: h,
|
||||
OkCodes: []int{201},
|
||||
KeepResponseBody: true,
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
if r.Err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if v := r.Header.Get("Content-Type"); v != OAuth1TokenContentType {
|
||||
r.Err = fmt.Errorf("unsupported Content-Type: %q", v)
|
||||
return
|
||||
}
|
||||
r.Body, r.Err = ioutil.ReadAll(resp.Body)
|
||||
return
|
||||
}
|
||||
|
||||
// GetAccessToken retrieves details on a single OAuth1 access token by an ID.
|
||||
func GetAccessToken(client *gophercloud.ServiceClient, userID string, id string) (r GetAccessTokenResult) {
|
||||
resp, err := client.Get(userAccessTokenURL(client, userID, id), &r.Body, nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// RevokeAccessToken revokes an OAuth1 access token.
|
||||
func RevokeAccessToken(client *gophercloud.ServiceClient, userID string, id string) (r RevokeAccessTokenResult) {
|
||||
resp, err := client.Delete(userAccessTokenURL(client, userID, id), nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// ListAccessTokens enumerates authorized access tokens.
|
||||
func ListAccessTokens(client *gophercloud.ServiceClient, userID string) pagination.Pager {
|
||||
url := userAccessTokensURL(client, userID)
|
||||
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
|
||||
return AccessTokensPage{pagination.LinkedPageBase{PageResult: r}}
|
||||
})
|
||||
}
|
||||
|
||||
// ListAccessTokenRoles enumerates authorized access token roles.
|
||||
func ListAccessTokenRoles(client *gophercloud.ServiceClient, userID string, id string) pagination.Pager {
|
||||
url := userAccessTokenRolesURL(client, userID, id)
|
||||
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
|
||||
return AccessTokenRolesPage{pagination.LinkedPageBase{PageResult: r}}
|
||||
})
|
||||
}
|
||||
|
||||
// GetAccessTokenRole retrieves details on a single OAuth1 access token role by
|
||||
// an ID.
|
||||
func GetAccessTokenRole(client *gophercloud.ServiceClient, userID string, id string, roleID string) (r GetAccessTokenRoleResult) {
|
||||
resp, err := client.Get(userAccessTokenRoleURL(client, userID, id, roleID), &r.Body, nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// The following are small helper functions used to help build the signature.
|
||||
|
||||
// buildOAuth1QueryString builds a URLEncoded parameters string specific for
|
||||
// OAuth1-based requests.
|
||||
func buildOAuth1QueryString(opts interface{}, timestamp *time.Time, callback string) (*url.URL, error) {
|
||||
q, err := gophercloud.BuildQueryString(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query := q.Query()
|
||||
|
||||
if timestamp != nil {
|
||||
// use provided timestamp
|
||||
query.Set("oauth_timestamp", strconv.FormatInt(timestamp.Unix(), 10))
|
||||
} else {
|
||||
// use current timestamp
|
||||
query.Set("oauth_timestamp", strconv.FormatInt(time.Now().UTC().Unix(), 10))
|
||||
}
|
||||
|
||||
if query.Get("oauth_nonce") == "" {
|
||||
// when nonce is not set, generate a random one
|
||||
query.Set("oauth_nonce", strconv.FormatInt(rand.Int63(), 10)+query.Get("oauth_timestamp"))
|
||||
}
|
||||
|
||||
if callback != "" {
|
||||
query.Set("oauth_callback", callback)
|
||||
}
|
||||
query.Set("oauth_version", "1.0")
|
||||
|
||||
return &url.URL{RawQuery: query.Encode()}, nil
|
||||
}
|
||||
|
||||
// buildStringToSign builds a string to be signed.
|
||||
func buildStringToSign(method string, u string, query url.Values) []byte {
|
||||
parsedURL, _ := url.Parse(u)
|
||||
p := parsedURL.Port()
|
||||
s := parsedURL.Scheme
|
||||
|
||||
// Default scheme port must be stripped
|
||||
if s == "http" && p == "80" || s == "https" && p == "443" {
|
||||
parsedURL.Host = strings.TrimSuffix(parsedURL.Host, ":"+p)
|
||||
}
|
||||
|
||||
// Ensure that URL doesn't contain queries
|
||||
parsedURL.RawQuery = ""
|
||||
|
||||
v := strings.Join(
|
||||
[]string{method, url.QueryEscape(parsedURL.String()), url.QueryEscape(query.Encode())}, "&")
|
||||
|
||||
return []byte(v)
|
||||
}
|
||||
|
||||
// signString signs a string using an OAuth1 signature method.
|
||||
func signString(signatureMethod SignatureMethod, strToSign []byte, signatureKeys []string) string {
|
||||
var key []byte
|
||||
for i, k := range signatureKeys {
|
||||
key = append(key, []byte(url.QueryEscape(k))...)
|
||||
if i == 0 {
|
||||
key = append(key, '&')
|
||||
}
|
||||
}
|
||||
|
||||
var signedString string
|
||||
switch signatureMethod {
|
||||
case PLAINTEXT:
|
||||
signedString = string(key)
|
||||
default:
|
||||
h := hmac.New(sha1.New, key)
|
||||
h.Write(strToSign)
|
||||
signedString = base64.StdEncoding.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
return signedString
|
||||
}
|
||||
|
||||
// buildAuthHeader generates an OAuth1 Authorization header with a signature
|
||||
// calculated using an OAuth1 signature method.
|
||||
func buildAuthHeader(query url.Values, signature string) string {
|
||||
var authHeader []string
|
||||
var keys []string
|
||||
for k := range query {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, k := range keys {
|
||||
for _, v := range query[k] {
|
||||
authHeader = append(authHeader, fmt.Sprintf("%s=%q", k, url.QueryEscape(v)))
|
||||
}
|
||||
}
|
||||
|
||||
authHeader = append(authHeader, fmt.Sprintf("oauth_signature=%q", signature))
|
||||
|
||||
return "OAuth " + strings.Join(authHeader, ", ")
|
||||
}
|
305
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1/results.go
generated
vendored
Normal file
305
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1/results.go
generated
vendored
Normal file
|
@ -0,0 +1,305 @@
|
|||
package oauth1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// Consumer represents a delegated authorization request between two
|
||||
// identities.
|
||||
type Consumer struct {
|
||||
ID string `json:"id"`
|
||||
Secret string `json:"secret"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type consumerResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
// CreateConsumerResult is the response from a Create operation. Call its
|
||||
// Extract method to interpret it as a Consumer.
|
||||
type CreateConsumerResult struct {
|
||||
consumerResult
|
||||
}
|
||||
|
||||
// UpdateConsumerResult is the response from a Create operation. Call its
|
||||
// Extract method to interpret it as a Consumer.
|
||||
type UpdateConsumerResult struct {
|
||||
consumerResult
|
||||
}
|
||||
|
||||
// DeleteConsumerResult is the response from a Delete operation. Call its
|
||||
// ExtractErr to determine if the request succeeded or failed.
|
||||
type DeleteConsumerResult struct {
|
||||
gophercloud.ErrResult
|
||||
}
|
||||
|
||||
// ConsumersPage is a single page of Region results.
|
||||
type ConsumersPage struct {
|
||||
pagination.LinkedPageBase
|
||||
}
|
||||
|
||||
// GetConsumerResult is the response from a Get operation. Call its Extract
|
||||
// method to interpret it as a Consumer.
|
||||
type GetConsumerResult struct {
|
||||
consumerResult
|
||||
}
|
||||
|
||||
// IsEmpty determines whether or not a page of Consumers contains any results.
|
||||
func (c ConsumersPage) IsEmpty() (bool, error) {
|
||||
consumers, err := ExtractConsumers(c)
|
||||
return len(consumers) == 0, err
|
||||
}
|
||||
|
||||
// NextPageURL extracts the "next" link from the links section of the result.
|
||||
func (c ConsumersPage) NextPageURL() (string, error) {
|
||||
var s struct {
|
||||
Links struct {
|
||||
Next string `json:"next"`
|
||||
Previous string `json:"previous"`
|
||||
} `json:"links"`
|
||||
}
|
||||
err := c.ExtractInto(&s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return s.Links.Next, err
|
||||
}
|
||||
|
||||
// ExtractConsumers returns a slice of Consumers contained in a single page of
|
||||
// results.
|
||||
func ExtractConsumers(r pagination.Page) ([]Consumer, error) {
|
||||
var s struct {
|
||||
Consumers []Consumer `json:"consumers"`
|
||||
}
|
||||
err := (r.(ConsumersPage)).ExtractInto(&s)
|
||||
return s.Consumers, err
|
||||
}
|
||||
|
||||
// Extract interprets any consumer result as a Consumer.
|
||||
func (c consumerResult) Extract() (*Consumer, error) {
|
||||
var s struct {
|
||||
Consumer *Consumer `json:"consumer"`
|
||||
}
|
||||
err := c.ExtractInto(&s)
|
||||
return s.Consumer, err
|
||||
}
|
||||
|
||||
// Token contains an OAuth1 token.
|
||||
type Token struct {
|
||||
// OAuthToken is the key value for the oauth token that the Identity API returns.
|
||||
OAuthToken string `q:"oauth_token"`
|
||||
// OAuthTokenSecret is the secret value associated with the OAuth Token.
|
||||
OAuthTokenSecret string `q:"oauth_token_secret"`
|
||||
// OAuthExpiresAt is the date and time when an OAuth token expires.
|
||||
OAuthExpiresAt *time.Time `q:"-"`
|
||||
}
|
||||
|
||||
// TokenResult is a struct to handle
|
||||
// "Content-Type: application/x-www-form-urlencoded" response.
|
||||
type TokenResult struct {
|
||||
gophercloud.Result
|
||||
Body []byte
|
||||
}
|
||||
|
||||
// Extract interprets any OAuth1 token result as a Token.
|
||||
func (r TokenResult) Extract() (*Token, error) {
|
||||
if r.Err != nil {
|
||||
return nil, r.Err
|
||||
}
|
||||
|
||||
values, err := url.ParseQuery(string(r.Body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token := &Token{
|
||||
OAuthToken: values.Get("oauth_token"),
|
||||
OAuthTokenSecret: values.Get("oauth_token_secret"),
|
||||
}
|
||||
|
||||
if v := values.Get("oauth_expires_at"); v != "" {
|
||||
if t, err := time.Parse(gophercloud.RFC3339Milli, v); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
token.OAuthExpiresAt = &t
|
||||
}
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// AuthorizedToken contains an OAuth1 authorized token info.
|
||||
type AuthorizedToken struct {
|
||||
// OAuthVerifier is the ID of the token verifier.
|
||||
OAuthVerifier string `json:"oauth_verifier"`
|
||||
}
|
||||
|
||||
type AuthorizeTokenResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
// Extract interprets AuthorizeTokenResult result as a AuthorizedToken.
|
||||
func (r AuthorizeTokenResult) Extract() (*AuthorizedToken, error) {
|
||||
var s struct {
|
||||
AuthorizedToken *AuthorizedToken `json:"token"`
|
||||
}
|
||||
err := r.ExtractInto(&s)
|
||||
return s.AuthorizedToken, err
|
||||
}
|
||||
|
||||
// AccessToken represents an AccessToken response as a struct.
|
||||
type AccessToken struct {
|
||||
ID string `json:"id"`
|
||||
ConsumerID string `json:"consumer_id"`
|
||||
ProjectID string `json:"project_id"`
|
||||
AuthorizingUserID string `json:"authorizing_user_id"`
|
||||
ExpiresAt *time.Time `json:"-"`
|
||||
}
|
||||
|
||||
func (r *AccessToken) UnmarshalJSON(b []byte) error {
|
||||
type tmp AccessToken
|
||||
var s struct {
|
||||
tmp
|
||||
ExpiresAt *gophercloud.JSONRFC3339Milli `json:"expires_at"`
|
||||
}
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*r = AccessToken(s.tmp)
|
||||
|
||||
if s.ExpiresAt != nil {
|
||||
t := time.Time(*s.ExpiresAt)
|
||||
r.ExpiresAt = &t
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type GetAccessTokenResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
// Extract interprets any GetAccessTokenResult result as an AccessToken.
|
||||
func (r GetAccessTokenResult) Extract() (*AccessToken, error) {
|
||||
var s struct {
|
||||
AccessToken *AccessToken `json:"access_token"`
|
||||
}
|
||||
err := r.ExtractInto(&s)
|
||||
return s.AccessToken, err
|
||||
}
|
||||
|
||||
// RevokeAccessTokenResult is the response from a Delete operation. Call its
|
||||
// ExtractErr to determine if the request succeeded or failed.
|
||||
type RevokeAccessTokenResult struct {
|
||||
gophercloud.ErrResult
|
||||
}
|
||||
|
||||
// AccessTokensPage is a single page of Access Tokens results.
|
||||
type AccessTokensPage struct {
|
||||
pagination.LinkedPageBase
|
||||
}
|
||||
|
||||
// IsEmpty determines whether or not a an AccessTokensPage contains any results.
|
||||
func (r AccessTokensPage) IsEmpty() (bool, error) {
|
||||
accessTokens, err := ExtractAccessTokens(r)
|
||||
return len(accessTokens) == 0, err
|
||||
}
|
||||
|
||||
// NextPageURL extracts the "next" link from the links section of the result.
|
||||
func (r AccessTokensPage) NextPageURL() (string, error) {
|
||||
var s struct {
|
||||
Links struct {
|
||||
Next string `json:"next"`
|
||||
Previous string `json:"previous"`
|
||||
} `json:"links"`
|
||||
}
|
||||
err := r.ExtractInto(&s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return s.Links.Next, err
|
||||
}
|
||||
|
||||
// ExtractAccessTokens returns a slice of AccessTokens contained in a single
|
||||
// page of results.
|
||||
func ExtractAccessTokens(r pagination.Page) ([]AccessToken, error) {
|
||||
var s struct {
|
||||
AccessTokens []AccessToken `json:"access_tokens"`
|
||||
}
|
||||
err := (r.(AccessTokensPage)).ExtractInto(&s)
|
||||
return s.AccessTokens, err
|
||||
}
|
||||
|
||||
// AccessTokenRole represents an Access Token Role struct.
|
||||
type AccessTokenRole struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
DomainID string `json:"domain_id"`
|
||||
}
|
||||
|
||||
// AccessTokenRolesPage is a single page of Access Token roles results.
|
||||
type AccessTokenRolesPage struct {
|
||||
pagination.LinkedPageBase
|
||||
}
|
||||
|
||||
// IsEmpty determines whether or not a an AccessTokensPage contains any results.
|
||||
func (r AccessTokenRolesPage) IsEmpty() (bool, error) {
|
||||
accessTokenRoles, err := ExtractAccessTokenRoles(r)
|
||||
return len(accessTokenRoles) == 0, err
|
||||
}
|
||||
|
||||
// NextPageURL extracts the "next" link from the links section of the result.
|
||||
func (r AccessTokenRolesPage) NextPageURL() (string, error) {
|
||||
var s struct {
|
||||
Links struct {
|
||||
Next string `json:"next"`
|
||||
Previous string `json:"previous"`
|
||||
} `json:"links"`
|
||||
}
|
||||
err := r.ExtractInto(&s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return s.Links.Next, err
|
||||
}
|
||||
|
||||
// ExtractAccessTokenRoles returns a slice of AccessTokenRole contained in a
|
||||
// single page of results.
|
||||
func ExtractAccessTokenRoles(r pagination.Page) ([]AccessTokenRole, error) {
|
||||
var s struct {
|
||||
AccessTokenRoles []AccessTokenRole `json:"roles"`
|
||||
}
|
||||
err := (r.(AccessTokenRolesPage)).ExtractInto(&s)
|
||||
return s.AccessTokenRoles, err
|
||||
}
|
||||
|
||||
type GetAccessTokenRoleResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
// Extract interprets any GetAccessTokenRoleResult result as an AccessTokenRole.
|
||||
func (r GetAccessTokenRoleResult) Extract() (*AccessTokenRole, error) {
|
||||
var s struct {
|
||||
AccessTokenRole *AccessTokenRole `json:"role"`
|
||||
}
|
||||
err := r.ExtractInto(&s)
|
||||
return s.AccessTokenRole, err
|
||||
}
|
||||
|
||||
// OAuth1 is an OAuth1 object, returned in OAuth1 token result.
|
||||
type OAuth1 struct {
|
||||
AccessTokenID string `json:"access_token_id"`
|
||||
ConsumerID string `json:"consumer_id"`
|
||||
}
|
||||
|
||||
// TokenExt represents an extension of the base token result.
|
||||
type TokenExt struct {
|
||||
OAuth1 OAuth1 `json:"OS-OAUTH1"`
|
||||
}
|
43
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1/urls.go
generated
vendored
Normal file
43
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1/urls.go
generated
vendored
Normal file
|
@ -0,0 +1,43 @@
|
|||
package oauth1
|
||||
|
||||
import "github.com/gophercloud/gophercloud"
|
||||
|
||||
func consumersURL(c *gophercloud.ServiceClient) string {
|
||||
return c.ServiceURL("OS-OAUTH1", "consumers")
|
||||
}
|
||||
|
||||
func consumerURL(c *gophercloud.ServiceClient, id string) string {
|
||||
return c.ServiceURL("OS-OAUTH1", "consumers", id)
|
||||
}
|
||||
|
||||
func requestTokenURL(c *gophercloud.ServiceClient) string {
|
||||
return c.ServiceURL("OS-OAUTH1", "request_token")
|
||||
}
|
||||
|
||||
func authorizeTokenURL(c *gophercloud.ServiceClient, id string) string {
|
||||
return c.ServiceURL("OS-OAUTH1", "authorize", id)
|
||||
}
|
||||
|
||||
func createAccessTokenURL(c *gophercloud.ServiceClient) string {
|
||||
return c.ServiceURL("OS-OAUTH1", "access_token")
|
||||
}
|
||||
|
||||
func userAccessTokensURL(c *gophercloud.ServiceClient, userID string) string {
|
||||
return c.ServiceURL("users", userID, "OS-OAUTH1", "access_tokens")
|
||||
}
|
||||
|
||||
func userAccessTokenURL(c *gophercloud.ServiceClient, userID string, id string) string {
|
||||
return c.ServiceURL("users", userID, "OS-OAUTH1", "access_tokens", id)
|
||||
}
|
||||
|
||||
func userAccessTokenRolesURL(c *gophercloud.ServiceClient, userID string, id string) string {
|
||||
return c.ServiceURL("users", userID, "OS-OAUTH1", "access_tokens", id, "roles")
|
||||
}
|
||||
|
||||
func userAccessTokenRoleURL(c *gophercloud.ServiceClient, userID string, id string, roleID string) string {
|
||||
return c.ServiceURL("users", userID, "OS-OAUTH1", "access_tokens", id, "roles", roleID)
|
||||
}
|
||||
|
||||
func authURL(c *gophercloud.ServiceClient) string {
|
||||
return c.ServiceURL("auth", "tokens")
|
||||
}
|
40
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go
generated
vendored
40
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go
generated
vendored
|
@ -8,6 +8,7 @@ type Scope struct {
|
|||
ProjectName string
|
||||
DomainID string
|
||||
DomainName string
|
||||
System bool
|
||||
}
|
||||
|
||||
// AuthOptionsBuilder provides the ability for extensions to add additional
|
||||
|
@ -16,6 +17,7 @@ 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)
|
||||
ToTokenV3HeadersMap(map[string]interface{}) (map[string]string, error)
|
||||
ToTokenV3ScopeMap() (map[string]interface{}, error)
|
||||
CanReauth() bool
|
||||
}
|
||||
|
@ -36,6 +38,9 @@ type AuthOptions struct {
|
|||
|
||||
Password string `json:"password,omitempty"`
|
||||
|
||||
// Passcode is used in TOTP authentication method
|
||||
Passcode string `json:"passcode,omitempty"`
|
||||
|
||||
// At most one of DomainID and DomainName must be provided if using Username
|
||||
// with Identity V3. Otherwise, either are optional.
|
||||
DomainID string `json:"-"`
|
||||
|
@ -67,6 +72,7 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
|
|||
Username: opts.Username,
|
||||
UserID: opts.UserID,
|
||||
Password: opts.Password,
|
||||
Passcode: opts.Passcode,
|
||||
DomainID: opts.DomainID,
|
||||
DomainName: opts.DomainName,
|
||||
AllowReauth: opts.AllowReauth,
|
||||
|
@ -79,7 +85,7 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
|
|||
return gophercloudAuthOpts.ToTokenV3CreateMap(scope)
|
||||
}
|
||||
|
||||
// ToTokenV3CreateMap builds a scope request body from AuthOptions.
|
||||
// ToTokenV3ScopeMap builds a scope request body from AuthOptions.
|
||||
func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
|
||||
scope := gophercloud.AuthScope(opts.Scope)
|
||||
|
||||
|
@ -93,10 +99,21 @@ func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
|
|||
}
|
||||
|
||||
func (opts *AuthOptions) CanReauth() bool {
|
||||
if opts.Passcode != "" {
|
||||
// cannot reauth using TOTP passcode
|
||||
return false
|
||||
}
|
||||
|
||||
return opts.AllowReauth
|
||||
}
|
||||
|
||||
func subjectTokenHeaders(c *gophercloud.ServiceClient, subjectToken string) map[string]string {
|
||||
// ToTokenV3HeadersMap allows AuthOptions to satisfy the AuthOptionsBuilder
|
||||
// interface in the v3 tokens package.
|
||||
func (opts *AuthOptions) ToTokenV3HeadersMap(map[string]interface{}) (map[string]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func subjectTokenHeaders(subjectToken string) map[string]string {
|
||||
return map[string]string{
|
||||
"X-Subject-Token": subjectToken,
|
||||
}
|
||||
|
@ -120,30 +137,24 @@ func Create(c *gophercloud.ServiceClient, opts AuthOptionsBuilder) (r CreateResu
|
|||
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
|
||||
}
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
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),
|
||||
MoreHeaders: subjectTokenHeaders(token),
|
||||
OkCodes: []int{200, 203},
|
||||
})
|
||||
if resp != nil {
|
||||
r.Header = resp.Header
|
||||
}
|
||||
r.Err = err
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate determines if a specified token is valid or not.
|
||||
func Validate(c *gophercloud.ServiceClient, token string) (bool, error) {
|
||||
resp, err := c.Head(tokenURL(c), &gophercloud.RequestOpts{
|
||||
MoreHeaders: subjectTokenHeaders(c, token),
|
||||
MoreHeaders: subjectTokenHeaders(token),
|
||||
OkCodes: []int{200, 204, 404},
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -155,8 +166,9 @@ func Validate(c *gophercloud.ServiceClient, token string) (bool, error) {
|
|||
|
||||
// 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),
|
||||
resp, err := c.Delete(tokenURL(c), &gophercloud.RequestOpts{
|
||||
MoreHeaders: subjectTokenHeaders(token),
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
|
16
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/results.go
generated
vendored
16
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/results.go
generated
vendored
|
@ -109,6 +109,13 @@ func (r CreateResult) ExtractTokenID() (string, error) {
|
|||
return r.Header.Get("X-Subject-Token"), r.Err
|
||||
}
|
||||
|
||||
// ExtractTokenID implements the gophercloud.AuthResult interface. The returned
|
||||
// string is the same as the ID field of the Token struct returned from
|
||||
// ExtractToken().
|
||||
func (r GetResult) ExtractTokenID() (string, error) {
|
||||
return r.Header.Get("X-Subject-Token"), r.Err
|
||||
}
|
||||
|
||||
// ExtractServiceCatalog returns the ServiceCatalog that was generated along
|
||||
// with the user's Token.
|
||||
func (r commonResult) ExtractServiceCatalog() (*ServiceCatalog, error) {
|
||||
|
@ -144,6 +151,15 @@ func (r commonResult) ExtractProject() (*Project, error) {
|
|||
return s.Project, err
|
||||
}
|
||||
|
||||
// ExtractDomain returns Domain to which User is authorized.
|
||||
func (r commonResult) ExtractDomain() (*Domain, error) {
|
||||
var s struct {
|
||||
Domain *Domain `json:"domain"`
|
||||
}
|
||||
err := r.ExtractInto(&s)
|
||||
return s.Domain, err
|
||||
}
|
||||
|
||||
// CreateResult is the response from a Create request. Use ExtractToken()
|
||||
// to interpret it as a Token, or ExtractServiceCatalog() to interpret it
|
||||
// as a service catalog.
|
||||
|
|
28
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/requests.go
generated
vendored
28
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/requests.go
generated
vendored
|
@ -206,19 +206,22 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
|
|||
r.Err = err
|
||||
return r
|
||||
}
|
||||
_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{OkCodes: []int{201}})
|
||||
resp, err := client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{OkCodes: []int{201}})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete implements image delete request.
|
||||
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
|
||||
_, r.Err = client.Delete(deleteURL(client, id), nil)
|
||||
resp, err := client.Delete(deleteURL(client, id), nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Get implements image get request.
|
||||
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
|
||||
_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
|
||||
resp, err := client.Get(getURL(client, id), &r.Body, nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -229,10 +232,11 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder
|
|||
r.Err = err
|
||||
return r
|
||||
}
|
||||
_, r.Err = client.Patch(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
resp, err := client.Patch(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
MoreHeaders: map[string]string{"Content-Type": "application/openstack-images-v2.1-json-patch"},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -335,6 +339,20 @@ func (r ReplaceImageMinDisk) ToImagePatchMap() map[string]interface{} {
|
|||
}
|
||||
}
|
||||
|
||||
// ReplaceImageMinRam represents an updated min_ram property request.
|
||||
type ReplaceImageMinRam struct {
|
||||
NewMinRam int
|
||||
}
|
||||
|
||||
// ToImagePatchMap assembles a request body based on ReplaceImageTags.
|
||||
func (r ReplaceImageMinRam) ToImagePatchMap() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"op": "replace",
|
||||
"path": "/min_ram",
|
||||
"value": r.NewMinRam,
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateOp represents a valid update operation.
|
||||
type UpdateOp string
|
||||
|
||||
|
@ -358,7 +376,7 @@ func (r UpdateImageProperty) ToImagePatchMap() map[string]interface{} {
|
|||
"path": fmt.Sprintf("/%s", r.Name),
|
||||
}
|
||||
|
||||
if r.Value != "" {
|
||||
if r.Op != RemoveOp {
|
||||
updateMap["value"] = r.Value
|
||||
}
|
||||
|
||||
|
|
40
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/results.go
generated
vendored
40
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/results.go
generated
vendored
|
@ -4,6 +4,7 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
|
@ -86,13 +87,22 @@ type Image struct {
|
|||
|
||||
// VirtualSize is the virtual size of the image
|
||||
VirtualSize int64 `json:"virtual_size"`
|
||||
|
||||
// OpenStackImageImportMethods is a slice listing the types of import
|
||||
// methods available in the cloud.
|
||||
OpenStackImageImportMethods []string `json:"-"`
|
||||
// OpenStackImageStoreIDs is a slice listing the store IDs available in
|
||||
// the cloud.
|
||||
OpenStackImageStoreIDs []string `json:"-"`
|
||||
}
|
||||
|
||||
func (r *Image) UnmarshalJSON(b []byte) error {
|
||||
type tmp Image
|
||||
var s struct {
|
||||
tmp
|
||||
SizeBytes interface{} `json:"size"`
|
||||
SizeBytes interface{} `json:"size"`
|
||||
OpenStackImageImportMethods string `json:"openstack-image-import-methods"`
|
||||
OpenStackImageStoreIDs string `json:"openstack-image-store-ids"`
|
||||
}
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
|
@ -120,9 +130,18 @@ func (r *Image) UnmarshalJSON(b []byte) error {
|
|||
if resultMap, ok := result.(map[string]interface{}); ok {
|
||||
delete(resultMap, "self")
|
||||
delete(resultMap, "size")
|
||||
delete(resultMap, "openstack-image-import-methods")
|
||||
delete(resultMap, "openstack-image-store-ids")
|
||||
r.Properties = internal.RemainingKeys(Image{}, resultMap)
|
||||
}
|
||||
|
||||
if v := strings.FieldsFunc(strings.TrimSpace(s.OpenStackImageImportMethods), splitFunc); len(v) > 0 {
|
||||
r.OpenStackImageImportMethods = v
|
||||
}
|
||||
if v := strings.FieldsFunc(strings.TrimSpace(s.OpenStackImageStoreIDs), splitFunc); len(v) > 0 {
|
||||
r.OpenStackImageStoreIDs = v
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -133,6 +152,20 @@ type commonResult struct {
|
|||
// Extract interprets any commonResult as an Image.
|
||||
func (r commonResult) Extract() (*Image, error) {
|
||||
var s *Image
|
||||
if v, ok := r.Body.(map[string]interface{}); ok {
|
||||
for k, h := range r.Header {
|
||||
if strings.ToLower(k) == "openstack-image-import-methods" {
|
||||
for _, s := range h {
|
||||
v["openstack-image-import-methods"] = s
|
||||
}
|
||||
}
|
||||
if strings.ToLower(k) == "openstack-image-store-ids" {
|
||||
for _, s := range h {
|
||||
v["openstack-image-store-ids"] = s
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
err := r.ExtractInto(&s)
|
||||
return s, err
|
||||
}
|
||||
|
@ -200,3 +233,8 @@ func ExtractImages(r pagination.Page) ([]Image, error) {
|
|||
err := (r.(ImagePage)).ExtractInto(&s)
|
||||
return s.Images, err
|
||||
}
|
||||
|
||||
// splitFunc is a helper function used to avoid a slice of empty strings.
|
||||
func splitFunc(c rune) bool {
|
||||
return c == ','
|
||||
}
|
||||
|
|
4
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/types.go
generated
vendored
4
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/types.go
generated
vendored
|
@ -35,6 +35,10 @@ const (
|
|||
// ImageStatusDeactivated denotes that access to image data is not allowed to
|
||||
// any non-admin user.
|
||||
ImageStatusDeactivated ImageStatus = "deactivated"
|
||||
|
||||
// ImageStatusImporting denotes that an import call has been made but that
|
||||
// the image is not yet ready for use.
|
||||
ImageStatusImporting ImageStatus = "importing"
|
||||
)
|
||||
|
||||
// ImageVisibility denotes an image that is fully available in Glance.
|
||||
|
|
12
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/members/requests.go
generated
vendored
12
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/members/requests.go
generated
vendored
|
@ -25,9 +25,10 @@ import (
|
|||
*/
|
||||
func Create(client *gophercloud.ServiceClient, id string, member string) (r CreateResult) {
|
||||
b := map[string]interface{}{"member": member}
|
||||
_, r.Err = client.Post(createMemberURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
resp, err := client.Post(createMemberURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -40,13 +41,15 @@ func List(client *gophercloud.ServiceClient, id string) pagination.Pager {
|
|||
|
||||
// Get image member details.
|
||||
func Get(client *gophercloud.ServiceClient, imageID string, memberID string) (r DetailsResult) {
|
||||
_, r.Err = client.Get(getMemberURL(client, imageID, memberID), &r.Body, &gophercloud.RequestOpts{OkCodes: []int{200}})
|
||||
resp, err := client.Get(getMemberURL(client, imageID, memberID), &r.Body, &gophercloud.RequestOpts{OkCodes: []int{200}})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete membership for given image. Callee should be image owner.
|
||||
func Delete(client *gophercloud.ServiceClient, imageID string, memberID string) (r DeleteResult) {
|
||||
_, r.Err = client.Delete(deleteMemberURL(client, imageID, memberID), &gophercloud.RequestOpts{OkCodes: []int{204}})
|
||||
resp, err := client.Delete(deleteMemberURL(client, imageID, memberID), &gophercloud.RequestOpts{OkCodes: []int{204}})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -75,7 +78,8 @@ func Update(client *gophercloud.ServiceClient, imageID string, memberID string,
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Put(updateMemberURL(client, imageID, memberID), b, &r.Body,
|
||||
resp, err := client.Put(updateMemberURL(client, imageID, memberID), b, &r.Body,
|
||||
&gophercloud.RequestOpts{OkCodes: []int{200}})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -116,13 +116,15 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = c.Post(rootURL(c), b, &r.Body, nil)
|
||||
resp, err := c.Post(rootURL(c), b, &r.Body, nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Get retrieves a particular floating IP resource based on its unique ID.
|
||||
func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
|
||||
_, r.Err = c.Get(resourceURL(c, id), &r.Body, nil)
|
||||
resp, err := c.Get(resourceURL(c, id), &r.Body, nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -167,9 +169,10 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
resp, err := c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -177,6 +180,7 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r
|
|||
// ensure this is what you want - you can also disassociate the IP from existing
|
||||
// internal ports.
|
||||
func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
|
||||
_, r.Err = c.Delete(resourceURL(c, id), nil)
|
||||
resp, err := c.Delete(resourceURL(c, id), nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package floatingips
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
@ -37,6 +40,11 @@ type FloatingIP struct {
|
|||
// specify a project identifier other than its own.
|
||||
TenantID string `json:"tenant_id"`
|
||||
|
||||
// UpdatedAt and CreatedAt contain ISO-8601 timestamps of when the state of
|
||||
// the floating ip last changed, and when it was created.
|
||||
UpdatedAt time.Time `json:"-"`
|
||||
CreatedAt time.Time `json:"-"`
|
||||
|
||||
// ProjectID is the project owner of the floating IP.
|
||||
ProjectID string `json:"project_id"`
|
||||
|
||||
|
@ -50,6 +58,44 @@ type FloatingIP struct {
|
|||
Tags []string `json:"tags"`
|
||||
}
|
||||
|
||||
func (r *FloatingIP) UnmarshalJSON(b []byte) error {
|
||||
type tmp FloatingIP
|
||||
|
||||
// Support for older neutron time format
|
||||
var s1 struct {
|
||||
tmp
|
||||
CreatedAt gophercloud.JSONRFC3339NoZ `json:"created_at"`
|
||||
UpdatedAt gophercloud.JSONRFC3339NoZ `json:"updated_at"`
|
||||
}
|
||||
|
||||
err := json.Unmarshal(b, &s1)
|
||||
if err == nil {
|
||||
*r = FloatingIP(s1.tmp)
|
||||
r.CreatedAt = time.Time(s1.CreatedAt)
|
||||
r.UpdatedAt = time.Time(s1.UpdatedAt)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Support for newer neutron time format
|
||||
var s2 struct {
|
||||
tmp
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
err = json.Unmarshal(b, &s2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*r = FloatingIP(s2.tmp)
|
||||
r.CreatedAt = time.Time(s2.CreatedAt)
|
||||
r.UpdatedAt = time.Time(s2.UpdatedAt)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type commonResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
|
49
vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/requests.go
generated
vendored
49
vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/requests.go
generated
vendored
|
@ -60,7 +60,8 @@ func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
|
|||
|
||||
// Get retrieves a specific network based on its unique ID.
|
||||
func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
|
||||
_, r.Err = c.Get(getURL(c, id), &r.Body, nil)
|
||||
resp, err := c.Get(getURL(c, id), &r.Body, nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -99,7 +100,8 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = c.Post(createURL(c), b, &r.Body, nil)
|
||||
resp, err := c.Post(createURL(c), b, &r.Body, nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -130,51 +132,16 @@ func Update(c *gophercloud.ServiceClient, networkID string, opts UpdateOptsBuild
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = c.Put(updateURL(c, networkID), b, &r.Body, &gophercloud.RequestOpts{
|
||||
resp, err := c.Put(updateURL(c, networkID), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200, 201},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete accepts a unique ID and deletes the network associated with it.
|
||||
func Delete(c *gophercloud.ServiceClient, networkID string) (r DeleteResult) {
|
||||
_, r.Err = c.Delete(deleteURL(c, networkID), nil)
|
||||
resp, err := c.Delete(deleteURL(c, networkID), nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// IDFromName is a convenience function that returns a network's ID, given
|
||||
// its name.
|
||||
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
|
||||
count := 0
|
||||
id := ""
|
||||
|
||||
listOpts := ListOpts{
|
||||
Name: name,
|
||||
}
|
||||
|
||||
pages, err := List(client, listOpts).AllPages()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
all, err := ExtractNetworks(pages)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, s := range all {
|
||||
if s.Name == name {
|
||||
count++
|
||||
id = s.ID
|
||||
}
|
||||
}
|
||||
|
||||
switch count {
|
||||
case 0:
|
||||
return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "network"}
|
||||
case 1:
|
||||
return id, nil
|
||||
default:
|
||||
return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "network"}
|
||||
}
|
||||
}
|
||||
|
|
46
vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/results.go
generated
vendored
46
vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/results.go
generated
vendored
|
@ -1,6 +1,9 @@
|
|||
package networks
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
@ -70,6 +73,11 @@ type Network struct {
|
|||
// TenantID is the project owner of the network.
|
||||
TenantID string `json:"tenant_id"`
|
||||
|
||||
// UpdatedAt and CreatedAt contain ISO-8601 timestamps of when the state of the
|
||||
// network last changed, and when it was created.
|
||||
UpdatedAt time.Time `json:"-"`
|
||||
CreatedAt time.Time `json:"-"`
|
||||
|
||||
// ProjectID is the project owner of the network.
|
||||
ProjectID string `json:"project_id"`
|
||||
|
||||
|
@ -84,6 +92,44 @@ type Network struct {
|
|||
Tags []string `json:"tags"`
|
||||
}
|
||||
|
||||
func (r *Network) UnmarshalJSON(b []byte) error {
|
||||
type tmp Network
|
||||
|
||||
// Support for older neutron time format
|
||||
var s1 struct {
|
||||
tmp
|
||||
CreatedAt gophercloud.JSONRFC3339NoZ `json:"created_at"`
|
||||
UpdatedAt gophercloud.JSONRFC3339NoZ `json:"updated_at"`
|
||||
}
|
||||
|
||||
err := json.Unmarshal(b, &s1)
|
||||
if err == nil {
|
||||
*r = Network(s1.tmp)
|
||||
r.CreatedAt = time.Time(s1.CreatedAt)
|
||||
r.UpdatedAt = time.Time(s1.UpdatedAt)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Support for newer neutron time format
|
||||
var s2 struct {
|
||||
tmp
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
err = json.Unmarshal(b, &s2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*r = Network(s2.tmp)
|
||||
r.CreatedAt = time.Time(s2.CreatedAt)
|
||||
r.UpdatedAt = time.Time(s2.UpdatedAt)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NetworkPage is the page returned by a pager when traversing over a
|
||||
// collection of networks.
|
||||
type NetworkPage struct {
|
||||
|
|
49
vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/requests.go
generated
vendored
49
vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/requests.go
generated
vendored
|
@ -69,7 +69,8 @@ func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
|
|||
|
||||
// Get retrieves a specific subnet based on its unique ID.
|
||||
func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
|
||||
_, r.Err = c.Get(getURL(c, id), &r.Body, nil)
|
||||
resp, err := c.Get(getURL(c, id), &r.Body, nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -160,7 +161,8 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = c.Post(createURL(c), b, &r.Body, nil)
|
||||
resp, err := c.Post(createURL(c), b, &r.Body, nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -219,51 +221,16 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r
|
|||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = c.Put(updateURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
resp, err := c.Put(updateURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200, 201},
|
||||
})
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete accepts a unique ID and deletes the subnet associated with it.
|
||||
func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
|
||||
_, r.Err = c.Delete(deleteURL(c, id), nil)
|
||||
resp, err := c.Delete(deleteURL(c, id), nil)
|
||||
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||
return
|
||||
}
|
||||
|
||||
// IDFromName is a convenience function that returns a subnet's ID,
|
||||
// given its name.
|
||||
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
|
||||
count := 0
|
||||
id := ""
|
||||
|
||||
listOpts := ListOpts{
|
||||
Name: name,
|
||||
}
|
||||
|
||||
pages, err := List(client, listOpts).AllPages()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
all, err := ExtractSubnets(pages)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, s := range all {
|
||||
if s.Name == name {
|
||||
count++
|
||||
id = s.ID
|
||||
}
|
||||
}
|
||||
|
||||
switch count {
|
||||
case 0:
|
||||
return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "subnet"}
|
||||
case 1:
|
||||
return id, nil
|
||||
default:
|
||||
return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "subnet"}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,7 +54,8 @@ func PageResultFromParsed(resp *http.Response, body interface{}) PageResult {
|
|||
// 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},
|
||||
MoreHeaders: headers,
|
||||
OkCodes: []int{200, 204, 300},
|
||||
KeepResponseBody: true,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -450,6 +450,8 @@ func BuildHeaders(opts interface{}) (map[string]string, error) {
|
|||
optsMap[tags[0]] = v.String()
|
||||
case reflect.Int:
|
||||
optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10)
|
||||
case reflect.Int64:
|
||||
optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10)
|
||||
case reflect.Bool:
|
||||
optsMap[tags[0]] = strconv.FormatBool(v.Bool())
|
||||
}
|
||||
|
|
|
@ -94,9 +94,32 @@ type ProviderClient struct {
|
|||
// reauthlock represents a set of attributes used to help in the reauthentication process.
|
||||
type reauthlock struct {
|
||||
sync.RWMutex
|
||||
reauthing bool
|
||||
reauthingErr error
|
||||
done *sync.Cond
|
||||
ongoing *reauthFuture
|
||||
}
|
||||
|
||||
// reauthFuture represents future result of the reauthentication process.
|
||||
// while done channel is not closed, reauthentication is in progress.
|
||||
// when done channel is closed, err contains the result of reauthentication.
|
||||
type reauthFuture struct {
|
||||
done chan struct{}
|
||||
err error
|
||||
}
|
||||
|
||||
func newReauthFuture() *reauthFuture {
|
||||
return &reauthFuture{
|
||||
make(chan struct{}),
|
||||
nil,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *reauthFuture) Set(err error) {
|
||||
f.err = err
|
||||
close(f.done)
|
||||
}
|
||||
|
||||
func (f *reauthFuture) Get() error {
|
||||
<-f.done
|
||||
return f.err
|
||||
}
|
||||
|
||||
// AuthenticatedHeaders returns a map of HTTP headers that are common for all
|
||||
|
@ -106,11 +129,13 @@ func (client *ProviderClient) AuthenticatedHeaders() (m map[string]string) {
|
|||
return
|
||||
}
|
||||
if client.reauthmut != nil {
|
||||
// If a Reauthenticate is in progress, wait for it to complete.
|
||||
client.reauthmut.Lock()
|
||||
for client.reauthmut.reauthing {
|
||||
client.reauthmut.done.Wait()
|
||||
}
|
||||
ongoing := client.reauthmut.ongoing
|
||||
client.reauthmut.Unlock()
|
||||
if ongoing != nil {
|
||||
_ = ongoing.Get()
|
||||
}
|
||||
}
|
||||
t := client.Token()
|
||||
if t == "" {
|
||||
|
@ -223,7 +248,7 @@ func (client *ProviderClient) SetThrowaway(v bool) {
|
|||
// this case, the reauthentication can be skipped if another thread has already
|
||||
// reauthenticated in the meantime. If no previous token is known, an empty
|
||||
// string should be passed instead to force unconditional reauthentication.
|
||||
func (client *ProviderClient) Reauthenticate(previousToken string) (err error) {
|
||||
func (client *ProviderClient) Reauthenticate(previousToken string) error {
|
||||
if client.ReauthFunc == nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -232,33 +257,36 @@ func (client *ProviderClient) Reauthenticate(previousToken string) (err error) {
|
|||
return client.ReauthFunc()
|
||||
}
|
||||
|
||||
future := newReauthFuture()
|
||||
|
||||
// Check if a Reauthenticate is in progress, or start one if not.
|
||||
client.reauthmut.Lock()
|
||||
if client.reauthmut.reauthing {
|
||||
for !client.reauthmut.reauthing {
|
||||
client.reauthmut.done.Wait()
|
||||
}
|
||||
err = client.reauthmut.reauthingErr
|
||||
client.reauthmut.Unlock()
|
||||
return err
|
||||
ongoing := client.reauthmut.ongoing
|
||||
if ongoing == nil {
|
||||
client.reauthmut.ongoing = future
|
||||
}
|
||||
client.reauthmut.Unlock()
|
||||
|
||||
client.reauthmut.Lock()
|
||||
client.reauthmut.reauthing = true
|
||||
client.reauthmut.done = sync.NewCond(client.reauthmut)
|
||||
client.reauthmut.reauthingErr = nil
|
||||
client.reauthmut.Unlock()
|
||||
// If Reauthenticate is running elsewhere, wait for its result.
|
||||
if ongoing != nil {
|
||||
return ongoing.Get()
|
||||
}
|
||||
|
||||
// Perform the actual reauthentication.
|
||||
var err error
|
||||
if previousToken == "" || client.TokenID == previousToken {
|
||||
err = client.ReauthFunc()
|
||||
} else {
|
||||
err = nil
|
||||
}
|
||||
|
||||
// Mark Reauthenticate as finished.
|
||||
client.reauthmut.Lock()
|
||||
client.reauthmut.reauthing = false
|
||||
client.reauthmut.reauthingErr = err
|
||||
client.reauthmut.done.Broadcast()
|
||||
client.reauthmut.ongoing.Set(err)
|
||||
client.reauthmut.ongoing = nil
|
||||
client.reauthmut.Unlock()
|
||||
return
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// RequestOpts customizes the behavior of the provider.Request() method.
|
||||
|
@ -283,6 +311,18 @@ type RequestOpts struct {
|
|||
// 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
|
||||
// KeepResponseBody specifies whether to keep the HTTP response body. Usually used, when the HTTP
|
||||
// response body is considered for further use. Valid when JSONResponse is nil.
|
||||
KeepResponseBody bool
|
||||
}
|
||||
|
||||
// requestState contains temporary state for a single ProviderClient.Request() call.
|
||||
type requestState struct {
|
||||
// This flag indicates if we have reauthenticated during this request because of a 401 response.
|
||||
// It ensures that we don't reauthenticate multiple times for a single request. If we
|
||||
// reauthenticate, but keep getting 401 responses with the fresh token, reauthenticating some more
|
||||
// will just get us into an infinite loop.
|
||||
hasReauthenticated bool
|
||||
}
|
||||
|
||||
var applicationJSON = "application/json"
|
||||
|
@ -290,6 +330,12 @@ 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) {
|
||||
return client.doRequest(method, url, options, &requestState{
|
||||
hasReauthenticated: false,
|
||||
})
|
||||
}
|
||||
|
||||
func (client *ProviderClient) doRequest(method, url string, options *RequestOpts, state *requestState) (*http.Response, error) {
|
||||
var body io.Reader
|
||||
var contentType *string
|
||||
|
||||
|
@ -309,6 +355,11 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts)
|
|||
contentType = &applicationJSON
|
||||
}
|
||||
|
||||
// Return an error, when "KeepResponseBody" is true and "JSONResponse" is not nil
|
||||
if options.KeepResponseBody && options.JSONResponse != nil {
|
||||
return nil, errors.New("cannot use KeepResponseBody when JSONResponse is not nil")
|
||||
}
|
||||
|
||||
if options.RawBody != nil {
|
||||
body = options.RawBody
|
||||
}
|
||||
|
@ -347,9 +398,6 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts)
|
|||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
// Set connection parameter to close the connection immediately when we've got the response
|
||||
req.Close = true
|
||||
|
||||
prereqtok := req.Header.Get("X-Auth-Token")
|
||||
|
||||
// Issue the request.
|
||||
|
@ -377,11 +425,12 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts)
|
|||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
respErr := ErrUnexpectedResponseCode{
|
||||
URL: url,
|
||||
Method: method,
|
||||
Expected: options.OkCodes,
|
||||
Actual: resp.StatusCode,
|
||||
Body: body,
|
||||
URL: url,
|
||||
Method: method,
|
||||
Expected: options.OkCodes,
|
||||
Actual: resp.StatusCode,
|
||||
Body: body,
|
||||
ResponseHeader: resp.Header,
|
||||
}
|
||||
|
||||
errType := options.ErrorContext
|
||||
|
@ -392,7 +441,7 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts)
|
|||
err = error400er.Error400(respErr)
|
||||
}
|
||||
case http.StatusUnauthorized:
|
||||
if client.ReauthFunc != nil {
|
||||
if client.ReauthFunc != nil && !state.hasReauthenticated {
|
||||
err = client.Reauthenticate(prereqtok)
|
||||
if err != nil {
|
||||
e := &ErrUnableToReauthenticate{}
|
||||
|
@ -404,7 +453,8 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts)
|
|||
seeker.Seek(0, 0)
|
||||
}
|
||||
}
|
||||
resp, err = client.Request(method, url, options)
|
||||
state.hasReauthenticated = true
|
||||
resp, err = client.doRequest(method, url, options, state)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *ErrUnexpectedResponseCode:
|
||||
|
@ -475,25 +525,40 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts)
|
|||
// Parse the response body as JSON, if requested to do so.
|
||||
if options.JSONResponse != nil {
|
||||
defer resp.Body.Close()
|
||||
// Don't decode JSON when there is no content
|
||||
if resp.StatusCode == http.StatusNoContent {
|
||||
// read till EOF, otherwise the connection will be closed and cannot be reused
|
||||
_, err = io.Copy(ioutil.Discard, resp.Body)
|
||||
return resp, err
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(options.JSONResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Close unused body to allow the HTTP connection to be reused
|
||||
if !options.KeepResponseBody && options.JSONResponse == nil {
|
||||
defer resp.Body.Close()
|
||||
// read till EOF, otherwise the connection will be closed and cannot be reused
|
||||
if _, err := io.Copy(ioutil.Discard, resp.Body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func defaultOkCodes(method string) []int {
|
||||
switch {
|
||||
case method == "GET":
|
||||
switch method {
|
||||
case "GET", "HEAD":
|
||||
return []int{200}
|
||||
case method == "POST":
|
||||
case "POST":
|
||||
return []int{201, 202}
|
||||
case method == "PUT":
|
||||
case "PUT":
|
||||
return []int{201, 202}
|
||||
case method == "PATCH":
|
||||
case "PATCH":
|
||||
return []int{200, 202, 204}
|
||||
case method == "DELETE":
|
||||
case "DELETE":
|
||||
return []int{202, 204}
|
||||
}
|
||||
|
||||
|
|
|
@ -131,6 +131,18 @@ func (r Result) extractIntoPtr(to interface{}, label string) error {
|
|||
// fields of the struct or composed extension struct
|
||||
// at the end of this method.
|
||||
toValue.Set(newSlice)
|
||||
|
||||
// jtopjian: This was put into place to resolve the issue
|
||||
// described at
|
||||
// https://github.com/gophercloud/gophercloud/issues/1963
|
||||
//
|
||||
// This probably isn't the best fix, but it appears to
|
||||
// be resolving the issue, so I'm going to implement it
|
||||
// for now.
|
||||
//
|
||||
// For future readers, this entire case statement could
|
||||
// use a review.
|
||||
return nil
|
||||
}
|
||||
}
|
||||
case reflect.Struct:
|
||||
|
|
|
@ -152,3 +152,11 @@ func (client *ServiceClient) Request(method, url string, options *RequestOpts) (
|
|||
}
|
||||
return client.ProviderClient.Request(method, url, options)
|
||||
}
|
||||
|
||||
// ParseResponse is a helper function to parse http.Response to constituents.
|
||||
func ParseResponse(resp *http.Response, err error) (io.ReadCloser, http.Header, error) {
|
||||
if resp != nil {
|
||||
return resp.Body, resp.Header, err
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
// +build !windows
|
||||
|
||||
package env
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
func Getenv(s string) string {
|
||||
return os.Getenv(s)
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
package env
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.org/x/text/encoding/charmap"
|
||||
)
|
||||
|
||||
func Getenv(s string) string {
|
||||
var st uint32
|
||||
env := os.Getenv(s)
|
||||
if windows.GetConsoleMode(windows.Handle(syscall.Stdin), &st) == nil ||
|
||||
windows.GetConsoleMode(windows.Handle(syscall.Stdout), &st) == nil ||
|
||||
windows.GetConsoleMode(windows.Handle(syscall.Stderr), &st) == nil {
|
||||
// detect windows console, should be skipped in cygwin environment
|
||||
var cm charmap.Charmap
|
||||
switch windows.GetACP() {
|
||||
case 37:
|
||||
cm = *charmap.CodePage037
|
||||
case 1047:
|
||||
cm = *charmap.CodePage1047
|
||||
case 1140:
|
||||
cm = *charmap.CodePage1140
|
||||
case 437:
|
||||
cm = *charmap.CodePage437
|
||||
case 850:
|
||||
cm = *charmap.CodePage850
|
||||
case 852:
|
||||
cm = *charmap.CodePage852
|
||||
case 855:
|
||||
cm = *charmap.CodePage855
|
||||
case 858:
|
||||
cm = *charmap.CodePage858
|
||||
case 860:
|
||||
cm = *charmap.CodePage860
|
||||
case 862:
|
||||
cm = *charmap.CodePage862
|
||||
case 863:
|
||||
cm = *charmap.CodePage863
|
||||
case 865:
|
||||
cm = *charmap.CodePage865
|
||||
case 866:
|
||||
cm = *charmap.CodePage866
|
||||
case 28591:
|
||||
cm = *charmap.ISO8859_1
|
||||
case 28592:
|
||||
cm = *charmap.ISO8859_2
|
||||
case 28593:
|
||||
cm = *charmap.ISO8859_3
|
||||
case 28594:
|
||||
cm = *charmap.ISO8859_4
|
||||
case 28595:
|
||||
cm = *charmap.ISO8859_5
|
||||
case 28596:
|
||||
cm = *charmap.ISO8859_6
|
||||
case 28597:
|
||||
cm = *charmap.ISO8859_7
|
||||
case 28598:
|
||||
cm = *charmap.ISO8859_8
|
||||
case 28599:
|
||||
cm = *charmap.ISO8859_9
|
||||
case 28600:
|
||||
cm = *charmap.ISO8859_10
|
||||
case 28603:
|
||||
cm = *charmap.ISO8859_13
|
||||
case 28604:
|
||||
cm = *charmap.ISO8859_14
|
||||
case 28605:
|
||||
cm = *charmap.ISO8859_15
|
||||
case 28606:
|
||||
cm = *charmap.ISO8859_16
|
||||
case 20866:
|
||||
cm = *charmap.KOI8R
|
||||
case 21866:
|
||||
cm = *charmap.KOI8U
|
||||
case 1250:
|
||||
cm = *charmap.Windows1250
|
||||
case 1251:
|
||||
cm = *charmap.Windows1251
|
||||
case 1252:
|
||||
cm = *charmap.Windows1252
|
||||
case 1253:
|
||||
cm = *charmap.Windows1253
|
||||
case 1254:
|
||||
cm = *charmap.Windows1254
|
||||
case 1255:
|
||||
cm = *charmap.Windows1255
|
||||
case 1256:
|
||||
cm = *charmap.Windows1256
|
||||
case 1257:
|
||||
cm = *charmap.Windows1257
|
||||
case 1258:
|
||||
cm = *charmap.Windows1258
|
||||
case 874:
|
||||
cm = *charmap.Windows874
|
||||
default:
|
||||
return env
|
||||
}
|
||||
if v, err := cm.NewEncoder().String(env); err == nil {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return env
|
||||
}
|
|
@ -7,7 +7,7 @@ See https://docs.openstack.org/os-client-config/latest for details.
|
|||
Example to Create a Provider Client From clouds.yaml
|
||||
|
||||
opts := &clientconfig.ClientOpts{
|
||||
Name: "hawaii",
|
||||
Cloud: "hawaii",
|
||||
}
|
||||
|
||||
pClient, err := clientconfig.AuthenticatedClient(opts)
|
||||
|
|
|
@ -2,12 +2,13 @@ package clientconfig
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack"
|
||||
"github.com/gophercloud/utils/env"
|
||||
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
@ -56,11 +57,62 @@ type ClientOpts struct {
|
|||
// This will override a region in clouds.yaml or can be used
|
||||
// when authenticating directly with AuthInfo.
|
||||
RegionName string
|
||||
|
||||
// EndpointType specifies whether to use the public, internal, or
|
||||
// admin endpoint of a service.
|
||||
EndpointType string
|
||||
|
||||
// HTTPClient provides the ability customize the ProviderClient's
|
||||
// internal HTTP client.
|
||||
HTTPClient *http.Client
|
||||
|
||||
// YAMLOpts provides the ability to pass a customized set
|
||||
// of options and methods for loading the YAML file.
|
||||
// It takes a YAMLOptsBuilder interface that is defined
|
||||
// in this file. This is optional and the default behavior
|
||||
// is to call the local LoadCloudsYAML functions defined
|
||||
// in this file.
|
||||
YAMLOpts YAMLOptsBuilder
|
||||
}
|
||||
|
||||
// YAMLOptsBuilder defines an interface for customization when
|
||||
// loading a clouds.yaml file.
|
||||
type YAMLOptsBuilder interface {
|
||||
LoadCloudsYAML() (map[string]Cloud, error)
|
||||
LoadSecureCloudsYAML() (map[string]Cloud, error)
|
||||
LoadPublicCloudsYAML() (map[string]Cloud, error)
|
||||
}
|
||||
|
||||
// YAMLOpts represents options and methods to load a clouds.yaml file.
|
||||
type YAMLOpts struct {
|
||||
// By default, no options are specified.
|
||||
}
|
||||
|
||||
// LoadCloudsYAML defines how to load a clouds.yaml file.
|
||||
// By default, this calls the local LoadCloudsYAML function.
|
||||
func (opts YAMLOpts) LoadCloudsYAML() (map[string]Cloud, error) {
|
||||
return LoadCloudsYAML()
|
||||
}
|
||||
|
||||
// LoadSecureCloudsYAML defines how to load a secure.yaml file.
|
||||
// By default, this calls the local LoadSecureCloudsYAML function.
|
||||
func (opts YAMLOpts) LoadSecureCloudsYAML() (map[string]Cloud, error) {
|
||||
return LoadSecureCloudsYAML()
|
||||
}
|
||||
|
||||
// LoadPublicCloudsYAML defines how to load a public-secure.yaml file.
|
||||
// By default, this calls the local LoadPublicCloudsYAML function.
|
||||
func (opts YAMLOpts) LoadPublicCloudsYAML() (map[string]Cloud, error) {
|
||||
return LoadPublicCloudsYAML()
|
||||
}
|
||||
|
||||
// LoadCloudsYAML will load a clouds.yaml file and return the full config.
|
||||
// This is called by the YAMLOpts method. Calling this function directly
|
||||
// is supported for now but has only been retained for backwards
|
||||
// compatibility from before YAMLOpts was defined. This may be removed in
|
||||
// the future.
|
||||
func LoadCloudsYAML() (map[string]Cloud, error) {
|
||||
content, err := findAndReadCloudsYAML()
|
||||
_, content, err := FindAndReadCloudsYAML()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -75,10 +127,14 @@ func LoadCloudsYAML() (map[string]Cloud, error) {
|
|||
}
|
||||
|
||||
// LoadSecureCloudsYAML will load a secure.yaml file and return the full config.
|
||||
// This is called by the YAMLOpts method. Calling this function directly
|
||||
// is supported for now but has only been retained for backwards
|
||||
// compatibility from before YAMLOpts was defined. This may be removed in
|
||||
// the future.
|
||||
func LoadSecureCloudsYAML() (map[string]Cloud, error) {
|
||||
var secureClouds Clouds
|
||||
|
||||
content, err := findAndReadSecureCloudsYAML()
|
||||
_, content, err := FindAndReadSecureCloudsYAML()
|
||||
if err != nil {
|
||||
if err.Error() == "no secure.yaml file found" {
|
||||
// secure.yaml is optional so just ignore read error
|
||||
|
@ -96,10 +152,14 @@ func LoadSecureCloudsYAML() (map[string]Cloud, error) {
|
|||
}
|
||||
|
||||
// LoadPublicCloudsYAML will load a public-clouds.yaml file and return the full config.
|
||||
// This is called by the YAMLOpts method. Calling this function directly
|
||||
// is supported for now but has only been retained for backwards
|
||||
// compatibility from before YAMLOpts was defined. This may be removed in
|
||||
// the future.
|
||||
func LoadPublicCloudsYAML() (map[string]Cloud, error) {
|
||||
var publicClouds PublicClouds
|
||||
|
||||
content, err := findAndReadPublicCloudsYAML()
|
||||
_, content, err := FindAndReadPublicCloudsYAML()
|
||||
if err != nil {
|
||||
if err.Error() == "no clouds-public.yaml file found" {
|
||||
// clouds-public.yaml is optional so just ignore read error
|
||||
|
@ -119,7 +179,13 @@ func LoadPublicCloudsYAML() (map[string]Cloud, error) {
|
|||
|
||||
// GetCloudFromYAML will return a cloud entry from a clouds.yaml file.
|
||||
func GetCloudFromYAML(opts *ClientOpts) (*Cloud, error) {
|
||||
clouds, err := LoadCloudsYAML()
|
||||
if opts.YAMLOpts == nil {
|
||||
opts.YAMLOpts = new(YAMLOpts)
|
||||
}
|
||||
|
||||
yamlOpts := opts.YAMLOpts
|
||||
|
||||
clouds, err := yamlOpts.LoadCloudsYAML()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to load clouds.yaml: %s", err)
|
||||
}
|
||||
|
@ -138,7 +204,7 @@ func GetCloudFromYAML(opts *ClientOpts) (*Cloud, error) {
|
|||
envPrefix = opts.EnvPrefix
|
||||
}
|
||||
|
||||
if v := os.Getenv(envPrefix + "CLOUD"); v != "" {
|
||||
if v := env.Getenv(envPrefix + "CLOUD"); v != "" {
|
||||
cloudName = v
|
||||
}
|
||||
|
||||
|
@ -159,32 +225,33 @@ func GetCloudFromYAML(opts *ClientOpts) (*Cloud, error) {
|
|||
}
|
||||
}
|
||||
|
||||
var cloudIsInCloudsYaml bool
|
||||
if cloud == nil {
|
||||
// not an immediate error as it might still be defined in secure.yaml
|
||||
cloudIsInCloudsYaml = false
|
||||
} else {
|
||||
cloudIsInCloudsYaml = true
|
||||
}
|
||||
if cloud != nil {
|
||||
// A profile points to a public cloud entry.
|
||||
// If one was specified, load a list of public clouds
|
||||
// and then merge the information with the current cloud data.
|
||||
profileName := defaultIfEmpty(cloud.Profile, cloud.Cloud)
|
||||
|
||||
publicClouds, err := LoadPublicCloudsYAML()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to load clouds-public.yaml: %s", err)
|
||||
}
|
||||
if profileName != "" {
|
||||
publicClouds, err := yamlOpts.LoadPublicCloudsYAML()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to load clouds-public.yaml: %s", err)
|
||||
}
|
||||
|
||||
var profileName = defaultIfEmpty(cloud.Profile, cloud.Cloud)
|
||||
if profileName != "" {
|
||||
publicCloud, ok := publicClouds[profileName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cloud %s does not exist in clouds-public.yaml", profileName)
|
||||
}
|
||||
cloud, err = mergeClouds(cloud, publicCloud)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not merge information from clouds.yaml and clouds-public.yaml for cloud %s", profileName)
|
||||
publicCloud, ok := publicClouds[profileName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cloud %s does not exist in clouds-public.yaml", profileName)
|
||||
}
|
||||
|
||||
cloud, err = mergeClouds(cloud, publicCloud)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not merge information from clouds.yaml and clouds-public.yaml for cloud %s", profileName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
secureClouds, err := LoadSecureCloudsYAML()
|
||||
// Next, load a secure clouds file and see if a cloud entry
|
||||
// can be found or merged.
|
||||
secureClouds, err := yamlOpts.LoadSecureCloudsYAML()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to load secure.yaml: %s", err)
|
||||
}
|
||||
|
@ -192,12 +259,13 @@ func GetCloudFromYAML(opts *ClientOpts) (*Cloud, error) {
|
|||
if secureClouds != nil {
|
||||
// If no entry was found in clouds.yaml, no cloud name was specified,
|
||||
// and only one secureCloud entry exists, use that as the cloud entry.
|
||||
if !cloudIsInCloudsYaml && cloudName == "" && len(secureClouds) == 1 {
|
||||
if cloud == nil && cloudName == "" && len(secureClouds) == 1 {
|
||||
for _, v := range secureClouds {
|
||||
cloud = &v
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, see if the provided cloud name exists in the secure yaml file.
|
||||
secureCloud, ok := secureClouds[cloudName]
|
||||
if !ok && cloud == nil {
|
||||
// cloud == nil serves two purposes here:
|
||||
|
@ -217,6 +285,12 @@ func GetCloudFromYAML(opts *ClientOpts) (*Cloud, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// As an extra precaution, do one final check to see if cloud is nil.
|
||||
// We shouldn't reach this point, though.
|
||||
if cloud == nil {
|
||||
return nil, fmt.Errorf("Could not find cloud %s", cloudName)
|
||||
}
|
||||
|
||||
// Default is to verify SSL API requests
|
||||
if cloud.Verify == nil {
|
||||
iTrue := true
|
||||
|
@ -227,6 +301,17 @@ func GetCloudFromYAML(opts *ClientOpts) (*Cloud, error) {
|
|||
// clouds-public.yml
|
||||
// https://github.com/openstack/openstacksdk/tree/master/openstack/config/vendors
|
||||
|
||||
// Both Interface and EndpointType are valid settings in clouds.yaml,
|
||||
// but we want to standardize on EndpointType for simplicity.
|
||||
//
|
||||
// If only Interface was set, we copy that to EndpointType to use as the setting.
|
||||
// But in all other cases, EndpointType is used and Interface is cleared.
|
||||
if cloud.Interface != "" && cloud.EndpointType == "" {
|
||||
cloud.EndpointType = cloud.Interface
|
||||
}
|
||||
|
||||
cloud.Interface = ""
|
||||
|
||||
return cloud, nil
|
||||
}
|
||||
|
||||
|
@ -260,7 +345,7 @@ func AuthOptions(opts *ClientOpts) (*gophercloud.AuthOptions, error) {
|
|||
envPrefix = opts.EnvPrefix
|
||||
}
|
||||
|
||||
if v := os.Getenv(envPrefix + "CLOUD"); v != "" {
|
||||
if v := env.Getenv(envPrefix + "CLOUD"); v != "" {
|
||||
cloudName = v
|
||||
}
|
||||
|
||||
|
@ -310,7 +395,7 @@ func determineIdentityAPI(cloud *Cloud, opts *ClientOpts) string {
|
|||
envPrefix = opts.EnvPrefix
|
||||
}
|
||||
|
||||
if v := os.Getenv(envPrefix + "IDENTITY_API_VERSION"); v != "" {
|
||||
if v := env.Getenv(envPrefix + "IDENTITY_API_VERSION"); v != "" {
|
||||
identityAPI = v
|
||||
}
|
||||
|
||||
|
@ -359,49 +444,49 @@ func v2auth(cloud *Cloud, opts *ClientOpts) (*gophercloud.AuthOptions, error) {
|
|||
}
|
||||
|
||||
if cloud.AuthInfo.AuthURL == "" {
|
||||
if v := os.Getenv(envPrefix + "AUTH_URL"); v != "" {
|
||||
if v := env.Getenv(envPrefix + "AUTH_URL"); v != "" {
|
||||
cloud.AuthInfo.AuthURL = v
|
||||
}
|
||||
}
|
||||
|
||||
if cloud.AuthInfo.Token == "" {
|
||||
if v := os.Getenv(envPrefix + "TOKEN"); v != "" {
|
||||
if v := env.Getenv(envPrefix + "TOKEN"); v != "" {
|
||||
cloud.AuthInfo.Token = v
|
||||
}
|
||||
|
||||
if v := os.Getenv(envPrefix + "AUTH_TOKEN"); v != "" {
|
||||
if v := env.Getenv(envPrefix + "AUTH_TOKEN"); v != "" {
|
||||
cloud.AuthInfo.Token = v
|
||||
}
|
||||
}
|
||||
|
||||
if cloud.AuthInfo.Username == "" {
|
||||
if v := os.Getenv(envPrefix + "USERNAME"); v != "" {
|
||||
if v := env.Getenv(envPrefix + "USERNAME"); v != "" {
|
||||
cloud.AuthInfo.Username = v
|
||||
}
|
||||
}
|
||||
|
||||
if cloud.AuthInfo.Password == "" {
|
||||
if v := os.Getenv(envPrefix + "PASSWORD"); v != "" {
|
||||
if v := env.Getenv(envPrefix + "PASSWORD"); v != "" {
|
||||
cloud.AuthInfo.Password = v
|
||||
}
|
||||
}
|
||||
|
||||
if cloud.AuthInfo.ProjectID == "" {
|
||||
if v := os.Getenv(envPrefix + "TENANT_ID"); v != "" {
|
||||
if v := env.Getenv(envPrefix + "TENANT_ID"); v != "" {
|
||||
cloud.AuthInfo.ProjectID = v
|
||||
}
|
||||
|
||||
if v := os.Getenv(envPrefix + "PROJECT_ID"); v != "" {
|
||||
if v := env.Getenv(envPrefix + "PROJECT_ID"); v != "" {
|
||||
cloud.AuthInfo.ProjectID = v
|
||||
}
|
||||
}
|
||||
|
||||
if cloud.AuthInfo.ProjectName == "" {
|
||||
if v := os.Getenv(envPrefix + "TENANT_NAME"); v != "" {
|
||||
if v := env.Getenv(envPrefix + "TENANT_NAME"); v != "" {
|
||||
cloud.AuthInfo.ProjectName = v
|
||||
}
|
||||
|
||||
if v := os.Getenv(envPrefix + "PROJECT_NAME"); v != "" {
|
||||
if v := env.Getenv(envPrefix + "PROJECT_NAME"); v != "" {
|
||||
cloud.AuthInfo.ProjectName = v
|
||||
}
|
||||
}
|
||||
|
@ -427,115 +512,115 @@ func v3auth(cloud *Cloud, opts *ClientOpts) (*gophercloud.AuthOptions, error) {
|
|||
}
|
||||
|
||||
if cloud.AuthInfo.AuthURL == "" {
|
||||
if v := os.Getenv(envPrefix + "AUTH_URL"); v != "" {
|
||||
if v := env.Getenv(envPrefix + "AUTH_URL"); v != "" {
|
||||
cloud.AuthInfo.AuthURL = v
|
||||
}
|
||||
}
|
||||
|
||||
if cloud.AuthInfo.Token == "" {
|
||||
if v := os.Getenv(envPrefix + "TOKEN"); v != "" {
|
||||
if v := env.Getenv(envPrefix + "TOKEN"); v != "" {
|
||||
cloud.AuthInfo.Token = v
|
||||
}
|
||||
|
||||
if v := os.Getenv(envPrefix + "AUTH_TOKEN"); v != "" {
|
||||
if v := env.Getenv(envPrefix + "AUTH_TOKEN"); v != "" {
|
||||
cloud.AuthInfo.Token = v
|
||||
}
|
||||
}
|
||||
|
||||
if cloud.AuthInfo.Username == "" {
|
||||
if v := os.Getenv(envPrefix + "USERNAME"); v != "" {
|
||||
if v := env.Getenv(envPrefix + "USERNAME"); v != "" {
|
||||
cloud.AuthInfo.Username = v
|
||||
}
|
||||
}
|
||||
|
||||
if cloud.AuthInfo.UserID == "" {
|
||||
if v := os.Getenv(envPrefix + "USER_ID"); v != "" {
|
||||
if v := env.Getenv(envPrefix + "USER_ID"); v != "" {
|
||||
cloud.AuthInfo.UserID = v
|
||||
}
|
||||
}
|
||||
|
||||
if cloud.AuthInfo.Password == "" {
|
||||
if v := os.Getenv(envPrefix + "PASSWORD"); v != "" {
|
||||
if v := env.Getenv(envPrefix + "PASSWORD"); v != "" {
|
||||
cloud.AuthInfo.Password = v
|
||||
}
|
||||
}
|
||||
|
||||
if cloud.AuthInfo.ProjectID == "" {
|
||||
if v := os.Getenv(envPrefix + "TENANT_ID"); v != "" {
|
||||
if v := env.Getenv(envPrefix + "TENANT_ID"); v != "" {
|
||||
cloud.AuthInfo.ProjectID = v
|
||||
}
|
||||
|
||||
if v := os.Getenv(envPrefix + "PROJECT_ID"); v != "" {
|
||||
if v := env.Getenv(envPrefix + "PROJECT_ID"); v != "" {
|
||||
cloud.AuthInfo.ProjectID = v
|
||||
}
|
||||
}
|
||||
|
||||
if cloud.AuthInfo.ProjectName == "" {
|
||||
if v := os.Getenv(envPrefix + "TENANT_NAME"); v != "" {
|
||||
if v := env.Getenv(envPrefix + "TENANT_NAME"); v != "" {
|
||||
cloud.AuthInfo.ProjectName = v
|
||||
}
|
||||
|
||||
if v := os.Getenv(envPrefix + "PROJECT_NAME"); v != "" {
|
||||
if v := env.Getenv(envPrefix + "PROJECT_NAME"); v != "" {
|
||||
cloud.AuthInfo.ProjectName = v
|
||||
}
|
||||
}
|
||||
|
||||
if cloud.AuthInfo.DomainID == "" {
|
||||
if v := os.Getenv(envPrefix + "DOMAIN_ID"); v != "" {
|
||||
if v := env.Getenv(envPrefix + "DOMAIN_ID"); v != "" {
|
||||
cloud.AuthInfo.DomainID = v
|
||||
}
|
||||
}
|
||||
|
||||
if cloud.AuthInfo.DomainName == "" {
|
||||
if v := os.Getenv(envPrefix + "DOMAIN_NAME"); v != "" {
|
||||
if v := env.Getenv(envPrefix + "DOMAIN_NAME"); v != "" {
|
||||
cloud.AuthInfo.DomainName = v
|
||||
}
|
||||
}
|
||||
|
||||
if cloud.AuthInfo.DefaultDomain == "" {
|
||||
if v := os.Getenv(envPrefix + "DEFAULT_DOMAIN"); v != "" {
|
||||
if v := env.Getenv(envPrefix + "DEFAULT_DOMAIN"); v != "" {
|
||||
cloud.AuthInfo.DefaultDomain = v
|
||||
}
|
||||
}
|
||||
|
||||
if cloud.AuthInfo.ProjectDomainID == "" {
|
||||
if v := os.Getenv(envPrefix + "PROJECT_DOMAIN_ID"); v != "" {
|
||||
if v := env.Getenv(envPrefix + "PROJECT_DOMAIN_ID"); v != "" {
|
||||
cloud.AuthInfo.ProjectDomainID = v
|
||||
}
|
||||
}
|
||||
|
||||
if cloud.AuthInfo.ProjectDomainName == "" {
|
||||
if v := os.Getenv(envPrefix + "PROJECT_DOMAIN_NAME"); v != "" {
|
||||
if v := env.Getenv(envPrefix + "PROJECT_DOMAIN_NAME"); v != "" {
|
||||
cloud.AuthInfo.ProjectDomainName = v
|
||||
}
|
||||
}
|
||||
|
||||
if cloud.AuthInfo.UserDomainID == "" {
|
||||
if v := os.Getenv(envPrefix + "USER_DOMAIN_ID"); v != "" {
|
||||
if v := env.Getenv(envPrefix + "USER_DOMAIN_ID"); v != "" {
|
||||
cloud.AuthInfo.UserDomainID = v
|
||||
}
|
||||
}
|
||||
|
||||
if cloud.AuthInfo.UserDomainName == "" {
|
||||
if v := os.Getenv(envPrefix + "USER_DOMAIN_NAME"); v != "" {
|
||||
if v := env.Getenv(envPrefix + "USER_DOMAIN_NAME"); v != "" {
|
||||
cloud.AuthInfo.UserDomainName = v
|
||||
}
|
||||
}
|
||||
|
||||
if cloud.AuthInfo.ApplicationCredentialID == "" {
|
||||
if v := os.Getenv(envPrefix + "APPLICATION_CREDENTIAL_ID"); v != "" {
|
||||
if v := env.Getenv(envPrefix + "APPLICATION_CREDENTIAL_ID"); v != "" {
|
||||
cloud.AuthInfo.ApplicationCredentialID = v
|
||||
}
|
||||
}
|
||||
|
||||
if cloud.AuthInfo.ApplicationCredentialName == "" {
|
||||
if v := os.Getenv(envPrefix + "APPLICATION_CREDENTIAL_NAME"); v != "" {
|
||||
if v := env.Getenv(envPrefix + "APPLICATION_CREDENTIAL_NAME"); v != "" {
|
||||
cloud.AuthInfo.ApplicationCredentialName = v
|
||||
}
|
||||
}
|
||||
|
||||
if cloud.AuthInfo.ApplicationCredentialSecret == "" {
|
||||
if v := os.Getenv(envPrefix + "APPLICATION_CREDENTIAL_SECRET"); v != "" {
|
||||
if v := env.Getenv(envPrefix + "APPLICATION_CREDENTIAL_SECRET"); v != "" {
|
||||
cloud.AuthInfo.ApplicationCredentialSecret = v
|
||||
}
|
||||
}
|
||||
|
@ -545,7 +630,11 @@ func v3auth(cloud *Cloud, opts *ClientOpts) (*gophercloud.AuthOptions, error) {
|
|||
scope := new(gophercloud.AuthScope)
|
||||
|
||||
// Application credentials don't support scope
|
||||
if !isApplicationCredential(cloud.AuthInfo) {
|
||||
if isApplicationCredential(cloud.AuthInfo) {
|
||||
// If Domain* is set, but UserDomain* or ProjectDomain* aren't,
|
||||
// then use Domain* as the default setting.
|
||||
cloud = setDomainIfNeeded(cloud)
|
||||
} else {
|
||||
if !isProjectScoped(cloud.AuthInfo) {
|
||||
if cloud.AuthInfo.DomainID != "" {
|
||||
scope.DomainID = cloud.AuthInfo.DomainID
|
||||
|
@ -639,7 +728,7 @@ func NewServiceClient(service string, opts *ClientOpts) (*gophercloud.ServiceCli
|
|||
envPrefix = opts.EnvPrefix
|
||||
}
|
||||
|
||||
if v := os.Getenv(envPrefix + "CLOUD"); v != "" {
|
||||
if v := env.Getenv(envPrefix + "CLOUD"); v != "" {
|
||||
cloudName = v
|
||||
}
|
||||
|
||||
|
@ -659,10 +748,15 @@ func NewServiceClient(service string, opts *ClientOpts) (*gophercloud.ServiceCli
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// If an HTTPClient was specified, use it.
|
||||
if opts.HTTPClient != nil {
|
||||
pClient.HTTPClient = *opts.HTTPClient
|
||||
}
|
||||
|
||||
// Determine the region to use.
|
||||
// First, check if the REGION_NAME environment variable is set.
|
||||
var region string
|
||||
if v := os.Getenv(envPrefix + "REGION_NAME"); v != "" {
|
||||
if v := env.Getenv(envPrefix + "REGION_NAME"); v != "" {
|
||||
region = v
|
||||
}
|
||||
|
||||
|
@ -677,8 +771,27 @@ func NewServiceClient(service string, opts *ClientOpts) (*gophercloud.ServiceCli
|
|||
region = v
|
||||
}
|
||||
|
||||
// Determine the endpoint type to use.
|
||||
// First, check if the OS_INTERFACE environment variable is set.
|
||||
var endpointType string
|
||||
if v := env.Getenv(envPrefix + "INTERFACE"); v != "" {
|
||||
endpointType = v
|
||||
}
|
||||
|
||||
// Next, check if the cloud entry sets an endpoint type.
|
||||
if v := cloud.EndpointType; v != "" {
|
||||
endpointType = v
|
||||
}
|
||||
|
||||
// Finally, see if one was specified in the ClientOpts.
|
||||
// If so, this takes precedence.
|
||||
if v := opts.EndpointType; v != "" {
|
||||
endpointType = v
|
||||
}
|
||||
|
||||
eo := gophercloud.EndpointOpts{
|
||||
Region: region,
|
||||
Region: region,
|
||||
Availability: GetEndpointType(endpointType),
|
||||
}
|
||||
|
||||
switch service {
|
||||
|
@ -688,6 +801,8 @@ func NewServiceClient(service string, opts *ClientOpts) (*gophercloud.ServiceCli
|
|||
return openstack.NewComputeV2(pClient, eo)
|
||||
case "container":
|
||||
return openstack.NewContainerV1(pClient, eo)
|
||||
case "container-infra":
|
||||
return openstack.NewContainerInfraV1(pClient, eo)
|
||||
case "database":
|
||||
return openstack.NewDBV1(pClient, eo)
|
||||
case "dns":
|
||||
|
|
|
@ -4,118 +4,124 @@ package clientconfig
|
|||
// The format of the clouds-public.yml is documented at
|
||||
// https://docs.openstack.org/python-openstackclient/latest/configuration/
|
||||
type PublicClouds struct {
|
||||
Clouds map[string]Cloud `yaml:"public-clouds"`
|
||||
Clouds map[string]Cloud `yaml:"public-clouds" json:"public-clouds"`
|
||||
}
|
||||
|
||||
// Clouds represents a collection of Cloud entries in a clouds.yaml file.
|
||||
// The format of clouds.yaml is documented at
|
||||
// https://docs.openstack.org/os-client-config/latest/user/configuration.html.
|
||||
type Clouds struct {
|
||||
Clouds map[string]Cloud `yaml:"clouds"`
|
||||
Clouds map[string]Cloud `yaml:"clouds" json:"clouds"`
|
||||
}
|
||||
|
||||
// Cloud represents an entry in a clouds.yaml/public-clouds.yaml/secure.yaml file.
|
||||
type Cloud struct {
|
||||
Cloud string `yaml:"cloud"`
|
||||
Profile string `yaml:"profile"`
|
||||
AuthInfo *AuthInfo `yaml:"auth"`
|
||||
AuthType AuthType `yaml:"auth_type"`
|
||||
RegionName string `yaml:"region_name"`
|
||||
Regions []interface{} `yaml:"regions"`
|
||||
Cloud string `yaml:"cloud,omitempty" json:"cloud,omitempty"`
|
||||
Profile string `yaml:"profile,omitempty" json:"profile,omitempty"`
|
||||
AuthInfo *AuthInfo `yaml:"auth,omitempty" json:"auth,omitempty"`
|
||||
AuthType AuthType `yaml:"auth_type,omitempty" json:"auth_type,omitempty"`
|
||||
RegionName string `yaml:"region_name,omitempty" json:"region_name,omitempty"`
|
||||
Regions []interface{} `yaml:"regions,omitempty" json:"regions,omitempty"`
|
||||
|
||||
// EndpointType and Interface both specify whether to use the public, internal,
|
||||
// or admin interface of a service. They should be considered synonymous, but
|
||||
// EndpointType will take precedence when both are specified.
|
||||
EndpointType string `yaml:"endpoint_type,omitempty" json:"endpoint_type,omitempty"`
|
||||
Interface string `yaml:"interface,omitempty" json:"interface,omitempty"`
|
||||
|
||||
// API Version overrides.
|
||||
IdentityAPIVersion string `yaml:"identity_api_version"`
|
||||
VolumeAPIVersion string `yaml:"volume_api_version"`
|
||||
IdentityAPIVersion string `yaml:"identity_api_version,omitempty" json:"identity_api_version,omitempty"`
|
||||
VolumeAPIVersion string `yaml:"volume_api_version,omitempty" json:"volume_api_version,omitempty"`
|
||||
|
||||
// Verify whether or not SSL API requests should be verified.
|
||||
Verify *bool `yaml:"verify"`
|
||||
Verify *bool `yaml:"verify,omitempty" json:"verify,omitempty"`
|
||||
|
||||
// CACertFile a path to a CA Cert bundle that can be used as part of
|
||||
// verifying SSL API requests.
|
||||
CACertFile string `yaml:"cacert"`
|
||||
CACertFile string `yaml:"cacert,omitempty" json:"cacert,omitempty"`
|
||||
|
||||
// ClientCertFile a path to a client certificate to use as part of the SSL
|
||||
// transaction.
|
||||
ClientCertFile string `yaml:"cert"`
|
||||
ClientCertFile string `yaml:"cert,omitempty" json:"cert,omitempty"`
|
||||
|
||||
// ClientKeyFile a path to a client key to use as part of the SSL
|
||||
// transaction.
|
||||
ClientKeyFile string `yaml:"key"`
|
||||
ClientKeyFile string `yaml:"key,omitempty" json:"key,omitempty"`
|
||||
}
|
||||
|
||||
// AuthInfo represents the auth section of a cloud entry or
|
||||
// auth options entered explicitly in ClientOpts.
|
||||
type AuthInfo struct {
|
||||
// AuthURL is the keystone/identity endpoint URL.
|
||||
AuthURL string `yaml:"auth_url"`
|
||||
AuthURL string `yaml:"auth_url,omitempty" json:"auth_url,omitempty"`
|
||||
|
||||
// Token is a pre-generated authentication token.
|
||||
Token string `yaml:"token"`
|
||||
Token string `yaml:"token,omitempty" json:"token,omitempty"`
|
||||
|
||||
// Username is the username of the user.
|
||||
Username string `yaml:"username"`
|
||||
Username string `yaml:"username,omitempty" json:"username,omitempty"`
|
||||
|
||||
// UserID is the unique ID of a user.
|
||||
UserID string `yaml:"user_id"`
|
||||
UserID string `yaml:"user_id,omitempty" json:"user_id,omitempty"`
|
||||
|
||||
// Password is the password of the user.
|
||||
Password string `yaml:"password"`
|
||||
Password string `yaml:"password,omitempty" json:"password,omitempty"`
|
||||
|
||||
// Application Credential ID to login with.
|
||||
ApplicationCredentialID string `yaml:"application_credential_id"`
|
||||
ApplicationCredentialID string `yaml:"application_credential_id,omitempty" json:"application_credential_id,omitempty"`
|
||||
|
||||
// Application Credential name to login with.
|
||||
ApplicationCredentialName string `yaml:"application_credential_name"`
|
||||
ApplicationCredentialName string `yaml:"application_credential_name,omitempty" json:"application_credential_name,omitempty"`
|
||||
|
||||
// Application Credential secret to login with.
|
||||
ApplicationCredentialSecret string `yaml:"application_credential_secret"`
|
||||
ApplicationCredentialSecret string `yaml:"application_credential_secret,omitempty" json:"application_credential_secret,omitempty"`
|
||||
|
||||
// ProjectName is the common/human-readable name of a project.
|
||||
// Users can be scoped to a project.
|
||||
// ProjectName on its own is not enough to ensure a unique scope. It must
|
||||
// also be combined with either a ProjectDomainName or ProjectDomainID.
|
||||
// ProjectName cannot be combined with ProjectID in a scope.
|
||||
ProjectName string `yaml:"project_name"`
|
||||
ProjectName string `yaml:"project_name,omitempty" json:"project_name,omitempty"`
|
||||
|
||||
// ProjectID is the unique ID of a project.
|
||||
// It can be used to scope a user to a specific project.
|
||||
ProjectID string `yaml:"project_id"`
|
||||
ProjectID string `yaml:"project_id,omitempty" json:"project_id,omitempty"`
|
||||
|
||||
// UserDomainName is the name of the domain where a user resides.
|
||||
// It is used to identify the source domain of a user.
|
||||
UserDomainName string `yaml:"user_domain_name"`
|
||||
UserDomainName string `yaml:"user_domain_name,omitempty" json:"user_domain_name,omitempty"`
|
||||
|
||||
// UserDomainID is the unique ID of the domain where a user resides.
|
||||
// It is used to identify the source domain of a user.
|
||||
UserDomainID string `yaml:"user_domain_id"`
|
||||
UserDomainID string `yaml:"user_domain_id,omitempty" json:"user_domain_id,omitempty"`
|
||||
|
||||
// ProjectDomainName is the name of the domain where a project resides.
|
||||
// It is used to identify the source domain of a project.
|
||||
// ProjectDomainName can be used in addition to a ProjectName when scoping
|
||||
// a user to a specific project.
|
||||
ProjectDomainName string `yaml:"project_domain_name"`
|
||||
ProjectDomainName string `yaml:"project_domain_name,omitempty" json:"project_domain_name,omitempty"`
|
||||
|
||||
// ProjectDomainID is the name of the domain where a project resides.
|
||||
// It is used to identify the source domain of a project.
|
||||
// ProjectDomainID can be used in addition to a ProjectName when scoping
|
||||
// a user to a specific project.
|
||||
ProjectDomainID string `yaml:"project_domain_id"`
|
||||
ProjectDomainID string `yaml:"project_domain_id,omitempty" json:"project_domain_id,omitempty"`
|
||||
|
||||
// DomainName is the name of a domain which can be used to identify the
|
||||
// source domain of either a user or a project.
|
||||
// If UserDomainName and ProjectDomainName are not specified, then DomainName
|
||||
// is used as a default choice.
|
||||
// It can also be used be used to specify a domain-only scope.
|
||||
DomainName string `yaml:"domain_name"`
|
||||
DomainName string `yaml:"domain_name,omitempty" json:"domain_name,omitempty"`
|
||||
|
||||
// DomainID is the unique ID of a domain which can be used to identify the
|
||||
// source domain of eitehr a user or a project.
|
||||
// If UserDomainID and ProjectDomainID are not specified, then DomainID is
|
||||
// used as a default choice.
|
||||
// It can also be used be used to specify a domain-only scope.
|
||||
DomainID string `yaml:"domain_id"`
|
||||
DomainID string `yaml:"domain_id,omitempty" json:"domain_id,omitempty"`
|
||||
|
||||
// DefaultDomain is the domain ID to fall back on if no other domain has
|
||||
// been specified and a domain is required for scope.
|
||||
DefaultDomain string `yaml:"default_domain"`
|
||||
DefaultDomain string `yaml:"default_domain,omitempty" json:"default_domain,omitempty"`
|
||||
}
|
||||
|
|
|
@ -8,6 +8,9 @@ import (
|
|||
"os/user"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/utils/env"
|
||||
)
|
||||
|
||||
// defaultIfEmpty is a helper function to make it cleaner to set default value
|
||||
|
@ -87,7 +90,7 @@ func mergeInterfaces(overridingInterface, inferiorInterface interface{}) interfa
|
|||
}
|
||||
}
|
||||
|
||||
// findAndReadCloudsYAML attempts to locate a clouds.yaml file in the following
|
||||
// FindAndReadCloudsYAML attempts to locate a clouds.yaml file in the following
|
||||
// locations:
|
||||
//
|
||||
// 1. OS_CLIENT_CONFIG_FILE
|
||||
|
@ -96,35 +99,37 @@ func mergeInterfaces(overridingInterface, inferiorInterface interface{}) interfa
|
|||
// 4. unix-specific site_config_dir (/etc/openstack/clouds.yaml)
|
||||
//
|
||||
// If found, the contents of the file is returned.
|
||||
func findAndReadCloudsYAML() ([]byte, error) {
|
||||
func FindAndReadCloudsYAML() (string, []byte, error) {
|
||||
// OS_CLIENT_CONFIG_FILE
|
||||
if v := os.Getenv("OS_CLIENT_CONFIG_FILE"); v != "" {
|
||||
if v := env.Getenv("OS_CLIENT_CONFIG_FILE"); v != "" {
|
||||
if ok := fileExists(v); ok {
|
||||
return ioutil.ReadFile(v)
|
||||
content, err := ioutil.ReadFile(v)
|
||||
return v, content, err
|
||||
}
|
||||
}
|
||||
|
||||
return findAndReadYAML("clouds.yaml")
|
||||
return FindAndReadYAML("clouds.yaml")
|
||||
}
|
||||
|
||||
func findAndReadPublicCloudsYAML() ([]byte, error) {
|
||||
return findAndReadYAML("clouds-public.yaml")
|
||||
func FindAndReadPublicCloudsYAML() (string, []byte, error) {
|
||||
return FindAndReadYAML("clouds-public.yaml")
|
||||
}
|
||||
|
||||
func findAndReadSecureCloudsYAML() ([]byte, error) {
|
||||
return findAndReadYAML("secure.yaml")
|
||||
func FindAndReadSecureCloudsYAML() (string, []byte, error) {
|
||||
return FindAndReadYAML("secure.yaml")
|
||||
}
|
||||
|
||||
func findAndReadYAML(yamlFile string) ([]byte, error) {
|
||||
func FindAndReadYAML(yamlFile string) (string, []byte, error) {
|
||||
// current directory
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to determine working directory: %s", err)
|
||||
return "", nil, fmt.Errorf("unable to determine working directory: %s", err)
|
||||
}
|
||||
|
||||
filename := filepath.Join(cwd, yamlFile)
|
||||
if ok := fileExists(filename); ok {
|
||||
return ioutil.ReadFile(filename)
|
||||
content, err := ioutil.ReadFile(filename)
|
||||
return filename, content, err
|
||||
}
|
||||
|
||||
// unix user config directory: ~/.config/openstack.
|
||||
|
@ -133,17 +138,20 @@ func findAndReadYAML(yamlFile string) ([]byte, error) {
|
|||
if homeDir != "" {
|
||||
filename := filepath.Join(homeDir, ".config/openstack/"+yamlFile)
|
||||
if ok := fileExists(filename); ok {
|
||||
return ioutil.ReadFile(filename)
|
||||
content, err := ioutil.ReadFile(filename)
|
||||
return filename, content, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unix-specific site config directory: /etc/openstack.
|
||||
if ok := fileExists("/etc/openstack/" + yamlFile); ok {
|
||||
return ioutil.ReadFile("/etc/openstack/" + yamlFile)
|
||||
filename = "/etc/openstack/" + yamlFile
|
||||
if ok := fileExists(filename); ok {
|
||||
content, err := ioutil.ReadFile(filename)
|
||||
return filename, content, err
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("no " + yamlFile + " file found")
|
||||
return "", nil, fmt.Errorf("no " + yamlFile + " file found")
|
||||
}
|
||||
|
||||
// fileExists checks for the existence of a file at a given location.
|
||||
|
@ -153,3 +161,15 @@ func fileExists(filename string) bool {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetEndpointType is a helper method to determine the endpoint type
|
||||
// requested by the user.
|
||||
func GetEndpointType(endpointType string) gophercloud.Availability {
|
||||
if endpointType == "internal" || endpointType == "internalURL" {
|
||||
return gophercloud.AvailabilityInternal
|
||||
}
|
||||
if endpointType == "admin" || endpointType == "adminURL" {
|
||||
return gophercloud.AvailabilityAdmin
|
||||
}
|
||||
return gophercloud.AvailabilityPublic
|
||||
}
|
||||
|
|
45
vendor/github.com/gophercloud/utils/openstack/compute/v2/flavors/utils.go
generated
vendored
Normal file
45
vendor/github.com/gophercloud/utils/openstack/compute/v2/flavors/utils.go
generated
vendored
Normal file
|
@ -0,0 +1,45 @@
|
|||
package flavors
|
||||
|
||||
import (
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
|
||||
)
|
||||
|
||||
// IDFromName is a convienience function that returns a flavor's ID given its
|
||||
// name.
|
||||
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
|
||||
count := 0
|
||||
id := ""
|
||||
allPages, err := flavors.ListDetail(client, nil).AllPages()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
all, err := flavors.ExtractFlavors(allPages)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, f := range all {
|
||||
if f.Name == name {
|
||||
count++
|
||||
id = f.ID
|
||||
}
|
||||
}
|
||||
|
||||
switch count {
|
||||
case 0:
|
||||
err := &gophercloud.ErrResourceNotFound{}
|
||||
err.ResourceType = "flavor"
|
||||
err.Name = name
|
||||
return "", err
|
||||
case 1:
|
||||
return id, nil
|
||||
default:
|
||||
err := &gophercloud.ErrMultipleResourcesFound{}
|
||||
err.ResourceType = "flavor"
|
||||
err.Name = name
|
||||
err.Count = count
|
||||
return "", err
|
||||
}
|
||||
}
|
|
@ -274,21 +274,24 @@ github.com/google/shlex
|
|||
github.com/google/uuid
|
||||
# github.com/googleapis/gax-go/v2 v2.0.5
|
||||
github.com/googleapis/gax-go/v2
|
||||
# github.com/gophercloud/gophercloud v0.2.0
|
||||
# github.com/gophercloud/gophercloud v0.12.0
|
||||
github.com/gophercloud/gophercloud
|
||||
github.com/gophercloud/gophercloud/internal
|
||||
github.com/gophercloud/gophercloud/openstack
|
||||
github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions
|
||||
github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes
|
||||
github.com/gophercloud/gophercloud/openstack/common/extensions
|
||||
github.com/gophercloud/gophercloud/openstack/compute/v2/extensions
|
||||
github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces
|
||||
github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume
|
||||
github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs
|
||||
github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop
|
||||
github.com/gophercloud/gophercloud/openstack/compute/v2/flavors
|
||||
github.com/gophercloud/gophercloud/openstack/compute/v2/images
|
||||
github.com/gophercloud/gophercloud/openstack/compute/v2/servers
|
||||
github.com/gophercloud/gophercloud/openstack/identity/v2/tenants
|
||||
github.com/gophercloud/gophercloud/openstack/identity/v2/tokens
|
||||
github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens
|
||||
github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1
|
||||
github.com/gophercloud/gophercloud/openstack/identity/v3/tokens
|
||||
github.com/gophercloud/gophercloud/openstack/imageservice/v2/images
|
||||
github.com/gophercloud/gophercloud/openstack/imageservice/v2/members
|
||||
|
@ -298,8 +301,10 @@ github.com/gophercloud/gophercloud/openstack/networking/v2/networks
|
|||
github.com/gophercloud/gophercloud/openstack/networking/v2/subnets
|
||||
github.com/gophercloud/gophercloud/openstack/utils
|
||||
github.com/gophercloud/gophercloud/pagination
|
||||
# github.com/gophercloud/utils v0.0.0-20190124192022-a5c25e7a53a6
|
||||
# github.com/gophercloud/utils v0.0.0-20200508015959-b0167b94122c
|
||||
github.com/gophercloud/utils/env
|
||||
github.com/gophercloud/utils/openstack/clientconfig
|
||||
github.com/gophercloud/utils/openstack/compute/v2/flavors
|
||||
# github.com/gorilla/websocket v0.0.0-20170319172727-a91eba7f9777
|
||||
github.com/gorilla/websocket
|
||||
# github.com/grpc-ecosystem/go-grpc-middleware v1.1.0
|
||||
|
|
Loading…
Reference in New Issue