Bump gophercloud to latest version
This commit is contained in:
parent
1400662db7
commit
4fe9a92058
|
@ -6,6 +6,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
|
"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/helper/multistep"
|
||||||
"github.com/hashicorp/packer/packer"
|
"github.com/hashicorp/packer/packer"
|
||||||
)
|
)
|
||||||
|
@ -37,7 +38,7 @@ func (s *StepLoadFlavor) Run(ctx context.Context, state multistep.StateBag) mult
|
||||||
geterr := err
|
geterr := err
|
||||||
|
|
||||||
log.Printf("[INFO] Loading flavor by name: %s", s.Flavor)
|
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 {
|
if err != nil {
|
||||||
log.Printf("[ERROR] Failed to find flavor by name: %s", err)
|
log.Printf("[ERROR] Failed to find flavor by name: %s", err)
|
||||||
err = fmt.Errorf(
|
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/go-querystring v1.0.0 // indirect
|
||||||
github.com/google/shlex v0.0.0-20150127133951-6f45313302b9
|
github.com/google/shlex v0.0.0-20150127133951-6f45313302b9
|
||||||
github.com/google/uuid v1.1.1
|
github.com/google/uuid v1.1.1
|
||||||
github.com/gophercloud/gophercloud v0.2.0
|
github.com/gophercloud/gophercloud v0.12.0
|
||||||
github.com/gophercloud/utils v0.0.0-20190124192022-a5c25e7a53a6
|
github.com/gophercloud/utils v0.0.0-20200508015959-b0167b94122c
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
|
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
|
||||||
github.com/gorilla/websocket v0.0.0-20170319172727-a91eba7f9777 // indirect
|
github.com/gorilla/websocket v0.0.0-20170319172727-a91eba7f9777 // indirect
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.1.0
|
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.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 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
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.6.1-0.20191122030953-d8ac278c1c9d/go.mod h1:ozGNgr9KYOVATV5jsgHl/ceCDXGuguqOZAzoQ/2vcNM=
|
||||||
github.com/gophercloud/gophercloud v0.2.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
|
github.com/gophercloud/gophercloud v0.12.0 h1:mZrie07npp6ODiwHZolTicr5jV8Ogn43AvAsSMm6Ork=
|
||||||
github.com/gophercloud/utils v0.0.0-20190124192022-a5c25e7a53a6 h1:Cw/B8Bu7Rryomxf7bjc8zNfIyLgjxsDd91n0eGRWpuo=
|
github.com/gophercloud/gophercloud v0.12.0/go.mod h1:gmC5oQqMDOMO1t1gq5DquX/yAU808e/4mzjjDA76+Ss=
|
||||||
github.com/gophercloud/utils v0.0.0-20190124192022-a5c25e7a53a6/go.mod h1:wjDF8z83zTeg5eMLml5EBSlAhbF7G8DobyI1YsMuyzw=
|
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 h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
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=
|
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-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 h1:KYQXGkl6vs02hK7pK4eIbw0NpNPedieTSTEiJ//bwGs=
|
||||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
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-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 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
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-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-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-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-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 h1:dSChiwOTvzwbHFTMq2l6uRardHH7/E6SqEkqccinS/o=
|
||||||
golang.org/x/crypto v0.0.0-20200422194213-44a606286825/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
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-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-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-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-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-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=
|
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-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-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-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-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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
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-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-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-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-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-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/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-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-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-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-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-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/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/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.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.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.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
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 github.com/mattn/goveralls
|
||||||
- GO111MODULE=off go get golang.org/x/tools/cmd/goimports
|
- GO111MODULE=off go get golang.org/x/tools/cmd/goimports
|
||||||
go:
|
go:
|
||||||
- "1.10"
|
|
||||||
- "1.11"
|
- "1.11"
|
||||||
- "1.12"
|
- "1.12"
|
||||||
|
- "1.13"
|
||||||
- "tip"
|
- "tip"
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
description: |
|
description: |
|
||||||
Run gophercloud acceptance test on master branch
|
Run gophercloud acceptance test on master branch
|
||||||
run: .zuul/playbooks/gophercloud-acceptance-test/run.yaml
|
run: .zuul/playbooks/gophercloud-acceptance-test/run.yaml
|
||||||
|
nodeset: ubuntu-bionic
|
||||||
|
|
||||||
- job:
|
- job:
|
||||||
name: gophercloud-acceptance-test-ironic
|
name: gophercloud-acceptance-test-ironic
|
||||||
|
@ -19,6 +20,7 @@
|
||||||
description: |
|
description: |
|
||||||
Run gophercloud ironic acceptance test on master branch
|
Run gophercloud ironic acceptance test on master branch
|
||||||
run: .zuul/playbooks/gophercloud-acceptance-test-ironic/run.yaml
|
run: .zuul/playbooks/gophercloud-acceptance-test-ironic/run.yaml
|
||||||
|
nodeset: ubuntu-bionic
|
||||||
|
|
||||||
- job:
|
- job:
|
||||||
name: gophercloud-acceptance-test-stein
|
name: gophercloud-acceptance-test-stein
|
||||||
|
@ -34,6 +36,7 @@
|
||||||
parent: gophercloud-acceptance-test
|
parent: gophercloud-acceptance-test
|
||||||
description: |
|
description: |
|
||||||
Run gophercloud acceptance test on rocky branch
|
Run gophercloud acceptance test on rocky branch
|
||||||
|
nodeset: ubuntu-xenial
|
||||||
vars:
|
vars:
|
||||||
global_env:
|
global_env:
|
||||||
OS_BRANCH: stable/rocky
|
OS_BRANCH: stable/rocky
|
||||||
|
@ -43,6 +46,7 @@
|
||||||
parent: gophercloud-acceptance-test
|
parent: gophercloud-acceptance-test
|
||||||
description: |
|
description: |
|
||||||
Run gophercloud acceptance test on queens branch
|
Run gophercloud acceptance test on queens branch
|
||||||
|
nodeset: ubuntu-xenial
|
||||||
vars:
|
vars:
|
||||||
global_env:
|
global_env:
|
||||||
OS_BRANCH: stable/queens
|
OS_BRANCH: stable/queens
|
||||||
|
@ -52,6 +56,7 @@
|
||||||
parent: gophercloud-acceptance-test
|
parent: gophercloud-acceptance-test
|
||||||
description: |
|
description: |
|
||||||
Run gophercloud acceptance test on pike branch
|
Run gophercloud acceptance test on pike branch
|
||||||
|
nodeset: ubuntu-xenial
|
||||||
vars:
|
vars:
|
||||||
global_env:
|
global_env:
|
||||||
OS_BRANCH: stable/pike
|
OS_BRANCH: stable/pike
|
||||||
|
@ -61,6 +66,7 @@
|
||||||
parent: gophercloud-acceptance-test
|
parent: gophercloud-acceptance-test
|
||||||
description: |
|
description: |
|
||||||
Run gophercloud acceptance test on ocata branch
|
Run gophercloud acceptance test on ocata branch
|
||||||
|
nodeset: ubuntu-xenial
|
||||||
vars:
|
vars:
|
||||||
global_env:
|
global_env:
|
||||||
OS_BRANCH: stable/ocata
|
OS_BRANCH: stable/ocata
|
||||||
|
@ -70,20 +76,11 @@
|
||||||
parent: gophercloud-acceptance-test
|
parent: gophercloud-acceptance-test
|
||||||
description: |
|
description: |
|
||||||
Run gophercloud acceptance test on newton branch
|
Run gophercloud acceptance test on newton branch
|
||||||
|
nodeset: ubuntu-xenial
|
||||||
vars:
|
vars:
|
||||||
global_env:
|
global_env:
|
||||||
OS_BRANCH: stable/newton
|
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:
|
- project:
|
||||||
name: gophercloud/gophercloud
|
name: gophercloud/gophercloud
|
||||||
check:
|
check:
|
||||||
|
@ -91,9 +88,6 @@
|
||||||
- gophercloud-unittest
|
- gophercloud-unittest
|
||||||
- gophercloud-acceptance-test
|
- gophercloud-acceptance-test
|
||||||
- gophercloud-acceptance-test-ironic
|
- gophercloud-acceptance-test-ironic
|
||||||
recheck-mitaka:
|
|
||||||
jobs:
|
|
||||||
- gophercloud-acceptance-test-mitaka
|
|
||||||
recheck-newton:
|
recheck-newton:
|
||||||
jobs:
|
jobs:
|
||||||
- gophercloud-acceptance-test-newton
|
- 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)
|
## 0.2.0 (June 17, 2019)
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,9 @@ type AuthOptions struct {
|
||||||
|
|
||||||
Password string `json:"password,omitempty"`
|
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
|
// At most one of DomainID and DomainName must be provided if using Username
|
||||||
// with Identity V3. Otherwise, either are optional.
|
// with Identity V3. Otherwise, either are optional.
|
||||||
DomainID string `json:"-"`
|
DomainID string `json:"-"`
|
||||||
|
@ -98,6 +101,7 @@ type AuthScope struct {
|
||||||
ProjectName string
|
ProjectName string
|
||||||
DomainID string
|
DomainID string
|
||||||
DomainName string
|
DomainName string
|
||||||
|
System bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToTokenV2CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder
|
// 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
|
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) {
|
func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) {
|
||||||
type domainReq struct {
|
type domainReq struct {
|
||||||
ID *string `json:"id,omitempty"`
|
ID *string `json:"id,omitempty"`
|
||||||
|
@ -148,7 +154,8 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
|
||||||
type userReq struct {
|
type userReq struct {
|
||||||
ID *string `json:"id,omitempty"`
|
ID *string `json:"id,omitempty"`
|
||||||
Name *string `json:"name,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"`
|
Domain *domainReq `json:"domain,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,11 +174,16 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
|
||||||
Secret *string `json:"secret,omitempty"`
|
Secret *string `json:"secret,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type totpReq struct {
|
||||||
|
User *userReq `json:"user,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type identityReq struct {
|
type identityReq struct {
|
||||||
Methods []string `json:"methods"`
|
Methods []string `json:"methods"`
|
||||||
Password *passwordReq `json:"password,omitempty"`
|
Password *passwordReq `json:"password,omitempty"`
|
||||||
Token *tokenReq `json:"token,omitempty"`
|
Token *tokenReq `json:"token,omitempty"`
|
||||||
ApplicationCredential *applicationCredentialReq `json:"application_credential,omitempty"`
|
ApplicationCredential *applicationCredentialReq `json:"application_credential,omitempty"`
|
||||||
|
TOTP *totpReq `json:"totp,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type authReq struct {
|
type authReq struct {
|
||||||
|
@ -186,7 +198,7 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
|
||||||
// if insufficient or incompatible information is present.
|
// if insufficient or incompatible information is present.
|
||||||
var req request
|
var req request
|
||||||
|
|
||||||
if opts.Password == "" {
|
if opts.Password == "" && opts.Passcode == "" {
|
||||||
if opts.TokenID != "" {
|
if opts.TokenID != "" {
|
||||||
// Because we aren't using password authentication, it's an error to also provide any of the user-based authentication
|
// Because we aren't using password authentication, it's an error to also provide any of the user-based authentication
|
||||||
// parameters.
|
// parameters.
|
||||||
|
@ -274,7 +286,14 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Password authentication.
|
// 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.
|
// At least one of Username and UserID must be specified.
|
||||||
if opts.Username == "" && opts.UserID == "" {
|
if opts.Username == "" && opts.UserID == "" {
|
||||||
|
@ -298,25 +317,48 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure the request for Username and Password authentication with a DomainID.
|
// Configure the request for Username and Password authentication with a DomainID.
|
||||||
|
if opts.Password != "" {
|
||||||
req.Auth.Identity.Password = &passwordReq{
|
req.Auth.Identity.Password = &passwordReq{
|
||||||
User: userReq{
|
User: userReq{
|
||||||
Name: &opts.Username,
|
Name: &opts.Username,
|
||||||
Password: opts.Password,
|
Password: &opts.Password,
|
||||||
Domain: &domainReq{ID: &opts.DomainID},
|
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 != "" {
|
if opts.DomainName != "" {
|
||||||
// Configure the request for Username and Password authentication with a DomainName.
|
// Configure the request for Username and Password authentication with a DomainName.
|
||||||
|
if opts.Password != "" {
|
||||||
req.Auth.Identity.Password = &passwordReq{
|
req.Auth.Identity.Password = &passwordReq{
|
||||||
User: userReq{
|
User: userReq{
|
||||||
Name: &opts.Username,
|
Name: &opts.Username,
|
||||||
Password: opts.Password,
|
Password: &opts.Password,
|
||||||
Domain: &domainReq{Name: &opts.DomainName},
|
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},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.UserID != "" {
|
if opts.UserID != "" {
|
||||||
|
@ -329,8 +371,22 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure the request for UserID and Password authentication.
|
// Configure the request for UserID and Password authentication.
|
||||||
|
if opts.Password != "" {
|
||||||
req.Auth.Identity.Password = &passwordReq{
|
req.Auth.Identity.Password = &passwordReq{
|
||||||
User: userReq{ID: &opts.UserID, Password: opts.Password},
|
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
|
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) {
|
func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
|
||||||
// For backwards compatibility.
|
// For backwards compatibility.
|
||||||
// If AuthOptions.Scope was not set, try to determine it.
|
// 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 != "" {
|
if opts.Scope.ProjectName != "" {
|
||||||
// ProjectName provided: either DomainID or DomainName must also be supplied.
|
// ProjectName provided: either DomainID or DomainName must also be supplied.
|
||||||
// ProjectID may not be supplied.
|
// ProjectID may not be supplied.
|
||||||
|
@ -433,5 +499,16 @@ func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opts AuthOptions) CanReauth() bool {
|
func (opts AuthOptions) CanReauth() bool {
|
||||||
|
if opts.Passcode != "" {
|
||||||
|
// cannot reauth using TOTP passcode
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
return opts.AllowReauth
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -82,6 +83,7 @@ type ErrUnexpectedResponseCode struct {
|
||||||
Expected []int
|
Expected []int
|
||||||
Actual int
|
Actual int
|
||||||
Body []byte
|
Body []byte
|
||||||
|
ResponseHeader http.Header
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e ErrUnexpectedResponseCode) Error() string {
|
func (e ErrUnexpectedResponseCode) Error() string {
|
||||||
|
@ -92,6 +94,23 @@ func (e ErrUnexpectedResponseCode) Error() string {
|
||||||
return e.choseErrString()
|
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.
|
// ErrDefault400 is the default error type returned on a 400 HTTP response code.
|
||||||
type ErrDefault400 struct {
|
type ErrDefault400 struct {
|
||||||
ErrUnexpectedResponseCode
|
ErrUnexpectedResponseCode
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
module github.com/gophercloud/gophercloud
|
module github.com/gophercloud/gophercloud
|
||||||
|
|
||||||
require (
|
require (
|
||||||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67
|
golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e
|
||||||
golang.org/x/sys v0.0.0-20190209173611-3b5209105503 // indirect
|
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933 // indirect
|
||||||
gopkg.in/yaml.v2 v2.2.2
|
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-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/sys v0.0.0-20190209173611-3b5209105503 h1:5SvYFrOM3W8Mexn9/oA44Ji7vhXAZQ9hiP+1Q/DMrWg=
|
golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e h1:egKlR8l7Nu9vHGWbcUV8lqR4987UfUbBd7GbhqGzNYU=
|
||||||
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
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/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
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")
|
username := os.Getenv("OS_USERNAME")
|
||||||
userID := os.Getenv("OS_USERID")
|
userID := os.Getenv("OS_USERID")
|
||||||
password := os.Getenv("OS_PASSWORD")
|
password := os.Getenv("OS_PASSWORD")
|
||||||
|
passcode := os.Getenv("OS_PASSCODE")
|
||||||
tenantID := os.Getenv("OS_TENANT_ID")
|
tenantID := os.Getenv("OS_TENANT_ID")
|
||||||
tenantName := os.Getenv("OS_TENANT_NAME")
|
tenantName := os.Getenv("OS_TENANT_NAME")
|
||||||
domainID := os.Getenv("OS_DOMAIN_ID")
|
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{
|
err := gophercloud.ErrMissingEnvironmentVariable{
|
||||||
|
// silently ignore TOTP passcode warning, since it is not a common auth method
|
||||||
EnvironmentVariable: "OS_PASSWORD",
|
EnvironmentVariable: "OS_PASSWORD",
|
||||||
}
|
}
|
||||||
return nilOptions, err
|
return nilOptions, err
|
||||||
|
@ -112,6 +114,7 @@ func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) {
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
Username: username,
|
Username: username,
|
||||||
Password: password,
|
Password: password,
|
||||||
|
Passcode: passcode,
|
||||||
TenantID: tenantID,
|
TenantID: tenantID,
|
||||||
TenantName: tenantName,
|
TenantName: tenantName,
|
||||||
DomainID: domainID,
|
DomainID: domainID,
|
||||||
|
|
|
@ -82,5 +82,16 @@ Example of Initializing a Volume Connection
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
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
|
package volumeactions
|
||||||
|
|
|
@ -47,18 +47,20 @@ func Attach(client *gophercloud.ServiceClient, id string, opts AttachOptsBuilder
|
||||||
r.Err = err
|
r.Err = err
|
||||||
return
|
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},
|
OkCodes: []int{202},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// BeginDetach will mark the volume as detaching.
|
// BeginDetach will mark the volume as detaching.
|
||||||
func BeginDetaching(client *gophercloud.ServiceClient, id string) (r BeginDetachingResult) {
|
func BeginDetaching(client *gophercloud.ServiceClient, id string) (r BeginDetachingResult) {
|
||||||
b := map[string]interface{}{"os-begin_detaching": make(map[string]interface{})}
|
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},
|
OkCodes: []int{202},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,27 +89,30 @@ func Detach(client *gophercloud.ServiceClient, id string, opts DetachOptsBuilder
|
||||||
r.Err = err
|
r.Err = err
|
||||||
return
|
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},
|
OkCodes: []int{202},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reserve will reserve a volume based on volume ID.
|
// Reserve will reserve a volume based on volume ID.
|
||||||
func Reserve(client *gophercloud.ServiceClient, id string) (r ReserveResult) {
|
func Reserve(client *gophercloud.ServiceClient, id string) (r ReserveResult) {
|
||||||
b := map[string]interface{}{"os-reserve": make(map[string]interface{})}
|
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},
|
OkCodes: []int{200, 201, 202},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unreserve will unreserve a volume based on volume ID.
|
// Unreserve will unreserve a volume based on volume ID.
|
||||||
func Unreserve(client *gophercloud.ServiceClient, id string) (r UnreserveResult) {
|
func Unreserve(client *gophercloud.ServiceClient, id string) (r UnreserveResult) {
|
||||||
b := map[string]interface{}{"os-unreserve": make(map[string]interface{})}
|
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},
|
OkCodes: []int{200, 201, 202},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,9 +150,10 @@ func InitializeConnection(client *gophercloud.ServiceClient, id string, opts Ini
|
||||||
r.Err = err
|
r.Err = err
|
||||||
return
|
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},
|
OkCodes: []int{200, 201, 202},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,9 +189,10 @@ func TerminateConnection(client *gophercloud.ServiceClient, id string, opts Term
|
||||||
r.Err = err
|
r.Err = err
|
||||||
return
|
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},
|
OkCodes: []int{202},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,9 +223,10 @@ func ExtendSize(client *gophercloud.ServiceClient, id string, opts ExtendSizeOpt
|
||||||
r.Err = err
|
r.Err = err
|
||||||
return
|
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},
|
OkCodes: []int{202},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,6 +249,14 @@ type UploadImageOpts struct {
|
||||||
|
|
||||||
// Force image creation, usable if volume attached to instance.
|
// Force image creation, usable if volume attached to instance.
|
||||||
Force bool `json:"force,omitempty"`
|
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
|
// 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
|
r.Err = err
|
||||||
return
|
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},
|
OkCodes: []int{202},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForceDelete will delete the volume regardless of state.
|
// ForceDelete will delete the volume regardless of state.
|
||||||
func ForceDelete(client *gophercloud.ServiceClient, id string) (r ForceDeleteResult) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,8 +311,35 @@ func SetImageMetadata(client *gophercloud.ServiceClient, id string, opts ImageMe
|
||||||
r.Err = err
|
r.Err = err
|
||||||
return
|
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},
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,12 @@ type SetImageMetadataResult struct {
|
||||||
gophercloud.ErrResult
|
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.
|
// ReserveResult contains the response body and error from a Reserve request.
|
||||||
type ReserveResult struct {
|
type ReserveResult struct {
|
||||||
gophercloud.ErrResult
|
gophercloud.ErrResult
|
||||||
|
@ -157,6 +163,14 @@ type VolumeImage struct {
|
||||||
// Current status of the volume.
|
// Current status of the volume.
|
||||||
Status string `json:"status"`
|
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.
|
// The date when this volume was last updated.
|
||||||
UpdatedAt time.Time `json:"-"`
|
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
|
Package volumes provides information and interaction with volumes in the
|
||||||
// device, akin to a USB hard drive. It can only be attached to one instance at
|
OpenStack Block Storage service. A volume is a detachable block storage
|
||||||
// a time.
|
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
|
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.
|
// see the Volume object.
|
||||||
type CreateOpts struct {
|
type CreateOpts struct {
|
||||||
// The size of the volume, in GB
|
// The size of the volume, in GB
|
||||||
Size int `json:"size" required:"true"`
|
Size int `json:"size,omitempty"`
|
||||||
// The availability zone
|
// The availability zone
|
||||||
AvailabilityZone string `json:"availability_zone,omitempty"`
|
AvailabilityZone string `json:"availability_zone,omitempty"`
|
||||||
// ConsistencyGroupID is the ID of a consistency group
|
// 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.
|
// The ID of the image from which you want to create the volume.
|
||||||
// Required to create a bootable volume.
|
// Required to create a bootable volume.
|
||||||
ImageID string `json:"imageRef,omitempty"`
|
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
|
// The associated volume type
|
||||||
VolumeType string `json:"volume_type,omitempty"`
|
VolumeType string `json:"volume_type,omitempty"`
|
||||||
// Multiattach denotes if the volume is multi-attach capable.
|
// 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
|
r.Err = err
|
||||||
return
|
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},
|
OkCodes: []int{202},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,14 +97,16 @@ func Delete(client *gophercloud.ServiceClient, id string, opts DeleteOptsBuilder
|
||||||
}
|
}
|
||||||
url += query
|
url += query
|
||||||
}
|
}
|
||||||
_, r.Err = client.Delete(url, nil)
|
resp, err := client.Delete(url, nil)
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get retrieves the Volume with the provided ID. To extract the Volume object
|
// Get retrieves the Volume with the provided ID. To extract the Volume object
|
||||||
// from the response, call the Extract method on the GetResult.
|
// from the response, call the Extract method on the GetResult.
|
||||||
func Get(client *gophercloud.ServiceClient, id string) (r 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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,44 +200,9 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder
|
||||||
r.Err = err
|
r.Err = err
|
||||||
return
|
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},
|
OkCodes: []int{200},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
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"`
|
SnapshotID string `json:"snapshot_id"`
|
||||||
// The ID of another block storage volume from which the current volume was created
|
// The ID of another block storage volume from which the current volume was created
|
||||||
SourceVolID string `json:"source_volid"`
|
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.
|
// Arbitrary key-value pairs defined by the user.
|
||||||
Metadata map[string]string `json:"metadata"`
|
Metadata map[string]string `json:"metadata"`
|
||||||
// UserID is the id of the user who created the volume.
|
// UserID is the id of the user who created the volume.
|
||||||
|
|
|
@ -3,9 +3,12 @@ package openstack
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gophercloud/gophercloud"
|
"github.com/gophercloud/gophercloud"
|
||||||
tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
|
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"
|
tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
|
||||||
"github.com/gophercloud/gophercloud/openstack/utils"
|
"github.com/gophercloud/gophercloud/openstack/utils"
|
||||||
)
|
)
|
||||||
|
@ -67,7 +70,7 @@ Example:
|
||||||
|
|
||||||
ao, err := openstack.AuthOptionsFromEnv()
|
ao, err := openstack.AuthOptionsFromEnv()
|
||||||
provider, err := openstack.AuthenticatedClient(ao)
|
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"),
|
Region: os.Getenv("OS_REGION_NAME"),
|
||||||
})
|
})
|
||||||
*/
|
*/
|
||||||
|
@ -187,17 +190,62 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.Au
|
||||||
v3Client.Endpoint = endpoint
|
v3Client.Endpoint = endpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
result := tokens3.Create(v3Client, opts)
|
var catalog *tokens3.ServiceCatalog
|
||||||
|
|
||||||
|
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{})
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
err = client.SetTokenAndAuthResult(result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
catalog, err := result.ExtractServiceCatalog()
|
catalog, err = result.ExtractServiceCatalog()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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() {
|
if opts.CanReauth() {
|
||||||
// here we're creating a throw-away client (tac). it's a copy of the user's provider client, but
|
// here we're creating a throw-away client (tac). it's a copy of the user's provider client, but
|
||||||
|
@ -217,6 +265,14 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.Au
|
||||||
o := *ot
|
o := *ot
|
||||||
o.AllowReauth = false
|
o.AllowReauth = false
|
||||||
tao = &o
|
tao = &o
|
||||||
|
case *ec2tokens.AuthOptions:
|
||||||
|
o := *ot
|
||||||
|
o.AllowReauth = false
|
||||||
|
tao = &o
|
||||||
|
case *oauth1.AuthOptions:
|
||||||
|
o := *ot
|
||||||
|
o.AllowReauth = false
|
||||||
|
tao = &o
|
||||||
default:
|
default:
|
||||||
tao = opts
|
tao = opts
|
||||||
}
|
}
|
||||||
|
@ -395,7 +451,11 @@ func NewImageServiceV2(client *gophercloud.ProviderClient, eo gophercloud.Endpoi
|
||||||
// load balancer service.
|
// load balancer service.
|
||||||
func NewLoadBalancerV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
func NewLoadBalancerV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
sc, err := initClientOpts(client, eo, "load-balancer")
|
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
|
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) {
|
func NewWorkflowV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||||
return initClientOpts(client, eo, "workflowv2")
|
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.
|
// Get requests details on a single interface attachment by the server and port IDs.
|
||||||
func Get(client *gophercloud.ServiceClient, serverID, portID string) (r GetResult) {
|
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},
|
OkCodes: []int{200},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,15 +59,17 @@ func Create(client *gophercloud.ServiceClient, serverID string, opts CreateOptsB
|
||||||
r.Err = err
|
r.Err = err
|
||||||
return
|
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},
|
OkCodes: []int{200},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete makes a request against the nova API to detach a single interface from the server.
|
// 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.
|
// It needs server and port IDs to make a such request.
|
||||||
func Delete(client *gophercloud.ServiceClient, serverID, portID string) (r DeleteResult) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,6 +75,10 @@ type BlockDevice struct {
|
||||||
// DiskBus is the bus type of the block devices.
|
// DiskBus is the bus type of the block devices.
|
||||||
// Examples of this are ide, usb, virtio, scsi, etc.
|
// Examples of this are ide, usb, virtio, scsi, etc.
|
||||||
DiskBus string `json:"disk_bus,omitempty"`
|
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
|
// 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
|
r.Err = err
|
||||||
return
|
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},
|
OkCodes: []int{200, 202},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,5 +3,5 @@ package bootfromvolume
|
||||||
import "github.com/gophercloud/gophercloud"
|
import "github.com/gophercloud/gophercloud"
|
||||||
|
|
||||||
func createURL(c *gophercloud.ServiceClient) string {
|
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
|
r.Err = err
|
||||||
return
|
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},
|
OkCodes: []int{200, 201},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns public data about a previously uploaded KeyPair.
|
// Get returns public data about a previously uploaded KeyPair.
|
||||||
func Get(client *gophercloud.ServiceClient, name string) (r GetResult) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete requests the deletion of a previous stored KeyPair from the server.
|
// Delete requests the deletion of a previous stored KeyPair from the server.
|
||||||
func Delete(client *gophercloud.ServiceClient, name string) (r DeleteResult) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
package startstop
|
package startstop
|
||||||
|
|
||||||
import "github.com/gophercloud/gophercloud"
|
import (
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
func actionURL(client *gophercloud.ServiceClient, id string) string {
|
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions"
|
||||||
return client.ServiceURL("servers", id, "action")
|
)
|
||||||
}
|
|
||||||
|
|
||||||
// Start is the operation responsible for starting a Compute server.
|
// Start is the operation responsible for starting a Compute server.
|
||||||
func Start(client *gophercloud.ServiceClient, id string) (r StartResult) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop is the operation responsible for stopping a Compute server.
|
// Stop is the operation responsible for stopping a Compute server.
|
||||||
func Stop(client *gophercloud.ServiceClient, id string) (r StopResult) {
|
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
|
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
|
r.Err = err
|
||||||
return
|
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},
|
OkCodes: []int{200, 201},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get retrieves details of a single flavor. Use Extract to convert its
|
// Get retrieves details of a single flavor. Use Extract to convert its
|
||||||
// result into a Flavor.
|
// result into a Flavor.
|
||||||
func Get(client *gophercloud.ServiceClient, id string) (r 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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete deletes the specified flavor ID.
|
// Delete deletes the specified flavor ID.
|
||||||
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,9 +197,10 @@ func AddAccess(client *gophercloud.ServiceClient, id string, opts AddAccessOptsB
|
||||||
r.Err = err
|
r.Err = err
|
||||||
return
|
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},
|
OkCodes: []int{200},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,20 +228,23 @@ func RemoveAccess(client *gophercloud.ServiceClient, id string, opts RemoveAcces
|
||||||
r.Err = err
|
r.Err = err
|
||||||
return
|
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},
|
OkCodes: []int{200},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtraSpecs requests all the extra-specs for the given flavor ID.
|
// ExtraSpecs requests all the extra-specs for the given flavor ID.
|
||||||
func ListExtraSpecs(client *gophercloud.ServiceClient, flavorID string) (r ListExtraSpecsResult) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetExtraSpec(client *gophercloud.ServiceClient, flavorID string, key string) (r GetExtraSpecResult) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,9 +271,10 @@ func CreateExtraSpecs(client *gophercloud.ServiceClient, flavorID string, opts C
|
||||||
r.Err = err
|
r.Err = err
|
||||||
return
|
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},
|
OkCodes: []int{200},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,56 +310,19 @@ func UpdateExtraSpec(client *gophercloud.ServiceClient, flavorID string, opts Up
|
||||||
r.Err = err
|
r.Err = err
|
||||||
return
|
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},
|
OkCodes: []int{200},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteExtraSpec will delete the key-value pair with the given key for the given
|
// DeleteExtraSpec will delete the key-value pair with the given key for the given
|
||||||
// flavor ID.
|
// flavor ID.
|
||||||
func DeleteExtraSpec(client *gophercloud.ServiceClient, flavorID, key string) (r DeleteExtraSpecResult) {
|
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},
|
OkCodes: []int{200},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
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
|
|
||||||
}
|
|
210
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/requests.go
generated
vendored
210
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/requests.go
generated
vendored
|
@ -3,10 +3,9 @@ package servers
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/gophercloud/gophercloud"
|
"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"
|
"github.com/gophercloud/gophercloud/pagination"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -55,6 +54,22 @@ type ListOpts struct {
|
||||||
// TenantID lists servers for a particular tenant.
|
// TenantID lists servers for a particular tenant.
|
||||||
// Setting "AllTenants = true" is required.
|
// Setting "AllTenants = true" is required.
|
||||||
TenantID string `q:"tenant_id"`
|
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.
|
// 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 is the name to assign to the newly launched server.
|
||||||
Name string `json:"name" required:"true"`
|
Name string `json:"name" required:"true"`
|
||||||
|
|
||||||
// ImageRef [optional; required if ImageName is not provided] is the ID or
|
// ImageRef is the ID or full URL to the image that contains the
|
||||||
// full URL to the image that contains the server's OS and initial state.
|
// server's OS and initial state.
|
||||||
// Also optional if using the boot-from-volume extension.
|
// Also optional if using the boot-from-volume extension.
|
||||||
ImageRef string `json:"imageRef"`
|
ImageRef string `json:"imageRef"`
|
||||||
|
|
||||||
// ImageName [optional; required if ImageRef is not provided] is the name of
|
// FlavorRef is the ID or full URL to the flavor that describes the server's specs.
|
||||||
// the image that contains the server's OS and initial state.
|
|
||||||
// Also optional if using the boot-from-volume extension.
|
|
||||||
ImageName string `json:"-"`
|
|
||||||
|
|
||||||
// FlavorRef [optional; required if FlavorName is not provided] is the ID or
|
|
||||||
// full URL to the flavor that describes the server's specs.
|
|
||||||
FlavorRef string `json:"flavorRef"`
|
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
|
// SecurityGroups lists the names of the security groups to which this server
|
||||||
// should belong.
|
// should belong.
|
||||||
SecurityGroups []string `json:"-"`
|
SecurityGroups []string `json:"-"`
|
||||||
|
@ -163,7 +168,9 @@ type CreateOpts struct {
|
||||||
// Networks dictates how this server will be attached to available networks.
|
// Networks dictates how this server will be attached to available networks.
|
||||||
// By default, the server will be attached to all isolated networks for the
|
// By default, the server will be attached to all isolated networks for the
|
||||||
// tenant.
|
// 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
|
// Metadata contains key-value pairs (up to 255 bytes each) to attach to the
|
||||||
// server.
|
// server.
|
||||||
|
@ -204,7 +211,6 @@ type CreateOpts struct {
|
||||||
// ToServerCreateMap assembles a request body based on the contents of a
|
// ToServerCreateMap assembles a request body based on the contents of a
|
||||||
// CreateOpts.
|
// CreateOpts.
|
||||||
func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) {
|
func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) {
|
||||||
sc := opts.ServiceClient
|
|
||||||
opts.ServiceClient = nil
|
opts.ServiceClient = nil
|
||||||
b, err := gophercloud.BuildRequestBody(opts, "")
|
b, err := gophercloud.BuildRequestBody(opts, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -229,9 +235,11 @@ func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) {
|
||||||
b["security_groups"] = securityGroups
|
b["security_groups"] = securityGroups
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(opts.Networks) > 0 {
|
switch v := opts.Networks.(type) {
|
||||||
networks := make([]map[string]interface{}, len(opts.Networks))
|
case []Network:
|
||||||
for i, net := range opts.Networks {
|
if len(v) > 0 {
|
||||||
|
networks := make([]map[string]interface{}, len(v))
|
||||||
|
for i, net := range v {
|
||||||
networks[i] = make(map[string]interface{})
|
networks[i] = make(map[string]interface{})
|
||||||
if net.UUID != "" {
|
if net.UUID != "" {
|
||||||
networks[i]["uuid"] = net.UUID
|
networks[i]["uuid"] = net.UUID
|
||||||
|
@ -245,41 +253,12 @@ func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) {
|
||||||
}
|
}
|
||||||
b["networks"] = networks
|
b["networks"] = networks
|
||||||
}
|
}
|
||||||
|
case string:
|
||||||
// If ImageRef isn't provided, check if ImageName was provided to ascertain
|
if v == "auto" || v == "none" {
|
||||||
// the image ID.
|
b["networks"] = v
|
||||||
if opts.ImageRef == "" {
|
} else {
|
||||||
if opts.ImageName != "" {
|
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 sc == nil {
|
|
||||||
err := ErrNoClientProvidedForIDByName{}
|
|
||||||
err.Argument = "ServiceClient"
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
imageID, err := images.IDFromName(sc, opts.ImageName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b["imageRef"] = imageID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If FlavorRef isn't provided, use FlavorName to ascertain the flavor ID.
|
|
||||||
if opts.FlavorRef == "" {
|
|
||||||
if opts.FlavorName == "" {
|
|
||||||
err := ErrNeitherFlavorIDNorFlavorNameProvided{}
|
|
||||||
err.Argument = "FlavorRef/FlavorName"
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if sc == nil {
|
|
||||||
err := ErrNoClientProvidedForIDByName{}
|
|
||||||
err.Argument = "ServiceClient"
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
flavorID, err := flavors.IDFromName(sc, opts.FlavorName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b["flavorRef"] = flavorID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.Min != 0 {
|
if opts.Min != 0 {
|
||||||
|
@ -300,28 +279,32 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
|
||||||
r.Err = err
|
r.Err = err
|
||||||
return
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete requests that a server previously provisioned be removed from your
|
// Delete requests that a server previously provisioned be removed from your
|
||||||
// account.
|
// account.
|
||||||
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForceDelete forces the deletion of a server.
|
// ForceDelete forces the deletion of a server.
|
||||||
func ForceDelete(client *gophercloud.ServiceClient, id string) (r ActionResult) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get requests details on a single server, by ID.
|
// Get requests details on a single server, by ID.
|
||||||
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
|
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},
|
OkCodes: []int{200, 203},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -358,9 +341,10 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder
|
||||||
r.Err = err
|
r.Err = err
|
||||||
return
|
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},
|
OkCodes: []int{200},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -372,7 +356,8 @@ func ChangeAdminPassword(client *gophercloud.ServiceClient, id, newPassword stri
|
||||||
"adminPass": newPassword,
|
"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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -413,7 +398,7 @@ func (opts RebootOpts) ToServerRebootMap() (map[string]interface{}, error) {
|
||||||
HardReboot (aka PowerCycle) starts the server instance by physically cutting
|
HardReboot (aka PowerCycle) starts the server instance by physically cutting
|
||||||
power to the machine, or if a VM, terminating it at the hypervisor level.
|
power to the machine, or if a VM, terminating it at the hypervisor level.
|
||||||
It's done. Caput. Full stop.
|
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
|
SoftReboot (aka OSReboot) simply tells the OS to restart under its own
|
||||||
procedure.
|
procedure.
|
||||||
|
@ -426,7 +411,8 @@ func Reboot(client *gophercloud.ServiceClient, id string, opts RebootOptsBuilder
|
||||||
r.Err = err
|
r.Err = err
|
||||||
return
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -442,11 +428,8 @@ type RebuildOpts struct {
|
||||||
// AdminPass is the server's admin password
|
// AdminPass is the server's admin password
|
||||||
AdminPass string `json:"adminPass,omitempty"`
|
AdminPass string `json:"adminPass,omitempty"`
|
||||||
|
|
||||||
// ImageID is the ID of the image you want your server to be provisioned on.
|
// ImageRef is the ID of the image you want your server to be provisioned on.
|
||||||
ImageID string `json:"imageRef"`
|
ImageRef string `json:"imageRef"`
|
||||||
|
|
||||||
// ImageName is readable name of an image.
|
|
||||||
ImageName string `json:"-"`
|
|
||||||
|
|
||||||
// Name to set the server to
|
// Name to set the server to
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
|
@ -477,23 +460,6 @@ func (opts RebuildOpts) ToServerRebuildMap() (map[string]interface{}, error) {
|
||||||
return nil, err
|
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
|
return map[string]interface{}{"rebuild": b}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -505,7 +471,8 @@ func Rebuild(client *gophercloud.ServiceClient, id string, opts RebuildOptsBuild
|
||||||
r.Err = err
|
r.Err = err
|
||||||
return
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -543,23 +510,26 @@ func Resize(client *gophercloud.ServiceClient, id string, opts ResizeOptsBuilder
|
||||||
r.Err = err
|
r.Err = err
|
||||||
return
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfirmResize confirms a previous resize operation on a server.
|
// ConfirmResize confirms a previous resize operation on a server.
|
||||||
// See Resize() for more details.
|
// See Resize() for more details.
|
||||||
func ConfirmResize(client *gophercloud.ServiceClient, id string) (r ActionResult) {
|
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},
|
OkCodes: []int{201, 202, 204},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// RevertResize cancels a previous resize operation on a server.
|
// RevertResize cancels a previous resize operation on a server.
|
||||||
// See Resize() for more details.
|
// See Resize() for more details.
|
||||||
func RevertResize(client *gophercloud.ServiceClient, id string) (r ActionResult) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -595,15 +565,17 @@ func ResetMetadata(client *gophercloud.ServiceClient, id string, opts ResetMetad
|
||||||
r.Err = err
|
r.Err = err
|
||||||
return
|
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},
|
OkCodes: []int{200},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metadata requests all the metadata for the given server ID.
|
// Metadata requests all the metadata for the given server ID.
|
||||||
func Metadata(client *gophercloud.ServiceClient, id string) (r GetMetadataResult) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -622,9 +594,10 @@ func UpdateMetadata(client *gophercloud.ServiceClient, id string, opts UpdateMet
|
||||||
r.Err = err
|
r.Err = err
|
||||||
return
|
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},
|
OkCodes: []int{200},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -662,23 +635,26 @@ func CreateMetadatum(client *gophercloud.ServiceClient, id string, opts Metadatu
|
||||||
r.Err = err
|
r.Err = err
|
||||||
return
|
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},
|
OkCodes: []int{200},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metadatum requests the key-value pair with the given key for the given
|
// Metadatum requests the key-value pair with the given key for the given
|
||||||
// server ID.
|
// server ID.
|
||||||
func Metadatum(client *gophercloud.ServiceClient, id, key string) (r GetMetadatumResult) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteMetadatum will delete the key-value pair with the given key for the
|
// DeleteMetadatum will delete the key-value pair with the given key for the
|
||||||
// given server ID.
|
// given server ID.
|
||||||
func DeleteMetadatum(client *gophercloud.ServiceClient, id, key string) (r DeleteMetadatumResult) {
|
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
|
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{
|
resp, err := client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
|
||||||
OkCodes: []int{202},
|
OkCodes: []int{202},
|
||||||
})
|
})
|
||||||
r.Err = err
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
r.Header = resp.Header
|
|
||||||
return
|
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
|
// GetPassword makes a request against the nova API to get the encrypted
|
||||||
// administrative password.
|
// administrative password.
|
||||||
func GetPassword(client *gophercloud.ServiceClient, serverId string) (r GetPasswordResult) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -805,8 +744,9 @@ func ShowConsoleOutput(client *gophercloud.ServiceClient, id string, opts ShowCo
|
||||||
r.Err = err
|
r.Err = err
|
||||||
return
|
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},
|
OkCodes: []int{200},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
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.
|
// to it.
|
||||||
SecurityGroups []map[string]interface{} `json:"security_groups"`
|
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 contains failure information about a server.
|
||||||
Fault Fault `json:"fault"`
|
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 {
|
type Fault struct {
|
||||||
|
|
|
@ -7,7 +7,7 @@ Example of Creating a Service Client
|
||||||
|
|
||||||
ao, err := openstack.AuthOptionsFromEnv()
|
ao, err := openstack.AuthOptionsFromEnv()
|
||||||
provider, err := openstack.AuthenticatedClient(ao)
|
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"),
|
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 {
|
if len(endpoints) > 1 {
|
||||||
err := &ErrMultipleMatchingEndpointsV2{}
|
endpoints = endpoints[0:1]
|
||||||
err.Endpoints = endpoints
|
|
||||||
return "", err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract the appropriate URL from the matching Endpoint.
|
// 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 {
|
if len(endpoints) > 1 {
|
||||||
return "", ErrMultipleMatchingEndpointsV3{Endpoints: endpoints}
|
endpoints = endpoints[0:1]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract the URL from the matching Endpoint.
|
// Extract the URL from the matching Endpoint.
|
||||||
|
|
|
@ -4,8 +4,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/gophercloud/gophercloud"
|
"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
|
// 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)
|
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
|
// ErrNoAuthURL is the error when the OS_AUTH_URL environment variable is not
|
||||||
// found
|
// found
|
||||||
type ErrNoAuthURL struct{ gophercloud.ErrInvalidInput }
|
type ErrNoAuthURL struct{ gophercloud.ErrInvalidInput }
|
||||||
|
|
|
@ -8,7 +8,7 @@ for more information.
|
||||||
|
|
||||||
Example to List Tenants
|
Example to List Tenants
|
||||||
|
|
||||||
listOpts := tenants.ListOpts{
|
listOpts := &tenants.ListOpts{
|
||||||
Limit: 2,
|
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
|
r.Err = err
|
||||||
return
|
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},
|
OkCodes: []int{200, 201},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get requests details on a single tenant by ID.
|
// Get requests details on a single tenant by ID.
|
||||||
func Get(client *gophercloud.ServiceClient, id string) (r 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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,14 +105,16 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder
|
||||||
r.Err = err
|
r.Err = err
|
||||||
return
|
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},
|
OkCodes: []int{200},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete is the operation responsible for permanently deleting a tenant.
|
// Delete is the operation responsible for permanently deleting a tenant.
|
||||||
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
|
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
|
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
|
r.Err = err
|
||||||
return
|
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},
|
OkCodes: []int{200, 203},
|
||||||
MoreHeaders: map[string]string{"X-Auth-Token": ""},
|
MoreHeaders: map[string]string{"X-Auth-Token": ""},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get validates and retrieves information for user's token.
|
// Get validates and retrieves information for user's token.
|
||||||
func Get(client *gophercloud.ServiceClient, token string) (r GetResult) {
|
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},
|
OkCodes: []int{200, 203},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
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
|
ProjectName string
|
||||||
DomainID string
|
DomainID string
|
||||||
DomainName string
|
DomainName string
|
||||||
|
System bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthOptionsBuilder provides the ability for extensions to add additional
|
// 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
|
// ToTokenV3CreateMap assembles the Create request body, returning an error
|
||||||
// if parameters are missing or inconsistent.
|
// if parameters are missing or inconsistent.
|
||||||
ToTokenV3CreateMap(map[string]interface{}) (map[string]interface{}, error)
|
ToTokenV3CreateMap(map[string]interface{}) (map[string]interface{}, error)
|
||||||
|
ToTokenV3HeadersMap(map[string]interface{}) (map[string]string, error)
|
||||||
ToTokenV3ScopeMap() (map[string]interface{}, error)
|
ToTokenV3ScopeMap() (map[string]interface{}, error)
|
||||||
CanReauth() bool
|
CanReauth() bool
|
||||||
}
|
}
|
||||||
|
@ -36,6 +38,9 @@ type AuthOptions struct {
|
||||||
|
|
||||||
Password string `json:"password,omitempty"`
|
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
|
// At most one of DomainID and DomainName must be provided if using Username
|
||||||
// with Identity V3. Otherwise, either are optional.
|
// with Identity V3. Otherwise, either are optional.
|
||||||
DomainID string `json:"-"`
|
DomainID string `json:"-"`
|
||||||
|
@ -67,6 +72,7 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
|
||||||
Username: opts.Username,
|
Username: opts.Username,
|
||||||
UserID: opts.UserID,
|
UserID: opts.UserID,
|
||||||
Password: opts.Password,
|
Password: opts.Password,
|
||||||
|
Passcode: opts.Passcode,
|
||||||
DomainID: opts.DomainID,
|
DomainID: opts.DomainID,
|
||||||
DomainName: opts.DomainName,
|
DomainName: opts.DomainName,
|
||||||
AllowReauth: opts.AllowReauth,
|
AllowReauth: opts.AllowReauth,
|
||||||
|
@ -79,7 +85,7 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
|
||||||
return gophercloudAuthOpts.ToTokenV3CreateMap(scope)
|
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) {
|
func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
|
||||||
scope := gophercloud.AuthScope(opts.Scope)
|
scope := gophercloud.AuthScope(opts.Scope)
|
||||||
|
|
||||||
|
@ -93,10 +99,21 @@ func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opts *AuthOptions) CanReauth() bool {
|
func (opts *AuthOptions) CanReauth() bool {
|
||||||
|
if opts.Passcode != "" {
|
||||||
|
// cannot reauth using TOTP passcode
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
return opts.AllowReauth
|
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{
|
return map[string]string{
|
||||||
"X-Subject-Token": subjectToken,
|
"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{
|
resp, err := c.Post(tokenURL(c), b, &r.Body, &gophercloud.RequestOpts{
|
||||||
MoreHeaders: map[string]string{"X-Auth-Token": ""},
|
MoreHeaders: map[string]string{"X-Auth-Token": ""},
|
||||||
})
|
})
|
||||||
r.Err = err
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
if resp != nil {
|
|
||||||
r.Header = resp.Header
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get validates and retrieves information about another token.
|
// Get validates and retrieves information about another token.
|
||||||
func Get(c *gophercloud.ServiceClient, token string) (r GetResult) {
|
func Get(c *gophercloud.ServiceClient, token string) (r GetResult) {
|
||||||
resp, err := c.Get(tokenURL(c), &r.Body, &gophercloud.RequestOpts{
|
resp, err := c.Get(tokenURL(c), &r.Body, &gophercloud.RequestOpts{
|
||||||
MoreHeaders: subjectTokenHeaders(c, token),
|
MoreHeaders: subjectTokenHeaders(token),
|
||||||
OkCodes: []int{200, 203},
|
OkCodes: []int{200, 203},
|
||||||
})
|
})
|
||||||
if resp != nil {
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
r.Header = resp.Header
|
|
||||||
}
|
|
||||||
r.Err = err
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate determines if a specified token is valid or not.
|
// Validate determines if a specified token is valid or not.
|
||||||
func Validate(c *gophercloud.ServiceClient, token string) (bool, error) {
|
func Validate(c *gophercloud.ServiceClient, token string) (bool, error) {
|
||||||
resp, err := c.Head(tokenURL(c), &gophercloud.RequestOpts{
|
resp, err := c.Head(tokenURL(c), &gophercloud.RequestOpts{
|
||||||
MoreHeaders: subjectTokenHeaders(c, token),
|
MoreHeaders: subjectTokenHeaders(token),
|
||||||
OkCodes: []int{200, 204, 404},
|
OkCodes: []int{200, 204, 404},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -155,8 +166,9 @@ func Validate(c *gophercloud.ServiceClient, token string) (bool, error) {
|
||||||
|
|
||||||
// Revoke immediately makes specified token invalid.
|
// Revoke immediately makes specified token invalid.
|
||||||
func Revoke(c *gophercloud.ServiceClient, token string) (r RevokeResult) {
|
func Revoke(c *gophercloud.ServiceClient, token string) (r RevokeResult) {
|
||||||
_, r.Err = c.Delete(tokenURL(c), &gophercloud.RequestOpts{
|
resp, err := c.Delete(tokenURL(c), &gophercloud.RequestOpts{
|
||||||
MoreHeaders: subjectTokenHeaders(c, token),
|
MoreHeaders: subjectTokenHeaders(token),
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
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
|
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
|
// ExtractServiceCatalog returns the ServiceCatalog that was generated along
|
||||||
// with the user's Token.
|
// with the user's Token.
|
||||||
func (r commonResult) ExtractServiceCatalog() (*ServiceCatalog, error) {
|
func (r commonResult) ExtractServiceCatalog() (*ServiceCatalog, error) {
|
||||||
|
@ -144,6 +151,15 @@ func (r commonResult) ExtractProject() (*Project, error) {
|
||||||
return s.Project, err
|
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()
|
// CreateResult is the response from a Create request. Use ExtractToken()
|
||||||
// to interpret it as a Token, or ExtractServiceCatalog() to interpret it
|
// to interpret it as a Token, or ExtractServiceCatalog() to interpret it
|
||||||
// as a service catalog.
|
// 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
|
r.Err = err
|
||||||
return r
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete implements image delete request.
|
// Delete implements image delete request.
|
||||||
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get implements image get request.
|
// Get implements image get request.
|
||||||
func Get(client *gophercloud.ServiceClient, id string) (r 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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,10 +232,11 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder
|
||||||
r.Err = err
|
r.Err = err
|
||||||
return r
|
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},
|
OkCodes: []int{200},
|
||||||
MoreHeaders: map[string]string{"Content-Type": "application/openstack-images-v2.1-json-patch"},
|
MoreHeaders: map[string]string{"Content-Type": "application/openstack-images-v2.1-json-patch"},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
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.
|
// UpdateOp represents a valid update operation.
|
||||||
type UpdateOp string
|
type UpdateOp string
|
||||||
|
|
||||||
|
@ -358,7 +376,7 @@ func (r UpdateImageProperty) ToImagePatchMap() map[string]interface{} {
|
||||||
"path": fmt.Sprintf("/%s", r.Name),
|
"path": fmt.Sprintf("/%s", r.Name),
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Value != "" {
|
if r.Op != RemoveOp {
|
||||||
updateMap["value"] = r.Value
|
updateMap["value"] = r.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
38
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/results.go
generated
vendored
38
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/results.go
generated
vendored
|
@ -4,6 +4,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gophercloud/gophercloud"
|
"github.com/gophercloud/gophercloud"
|
||||||
|
@ -86,6 +87,13 @@ type Image struct {
|
||||||
|
|
||||||
// VirtualSize is the virtual size of the image
|
// VirtualSize is the virtual size of the image
|
||||||
VirtualSize int64 `json:"virtual_size"`
|
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 {
|
func (r *Image) UnmarshalJSON(b []byte) error {
|
||||||
|
@ -93,6 +101,8 @@ func (r *Image) UnmarshalJSON(b []byte) error {
|
||||||
var s struct {
|
var s struct {
|
||||||
tmp
|
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)
|
err := json.Unmarshal(b, &s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -120,9 +130,18 @@ func (r *Image) UnmarshalJSON(b []byte) error {
|
||||||
if resultMap, ok := result.(map[string]interface{}); ok {
|
if resultMap, ok := result.(map[string]interface{}); ok {
|
||||||
delete(resultMap, "self")
|
delete(resultMap, "self")
|
||||||
delete(resultMap, "size")
|
delete(resultMap, "size")
|
||||||
|
delete(resultMap, "openstack-image-import-methods")
|
||||||
|
delete(resultMap, "openstack-image-store-ids")
|
||||||
r.Properties = internal.RemainingKeys(Image{}, resultMap)
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,6 +152,20 @@ type commonResult struct {
|
||||||
// Extract interprets any commonResult as an Image.
|
// Extract interprets any commonResult as an Image.
|
||||||
func (r commonResult) Extract() (*Image, error) {
|
func (r commonResult) Extract() (*Image, error) {
|
||||||
var s *Image
|
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)
|
err := r.ExtractInto(&s)
|
||||||
return s, err
|
return s, err
|
||||||
}
|
}
|
||||||
|
@ -200,3 +233,8 @@ func ExtractImages(r pagination.Page) ([]Image, error) {
|
||||||
err := (r.(ImagePage)).ExtractInto(&s)
|
err := (r.(ImagePage)).ExtractInto(&s)
|
||||||
return s.Images, err
|
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
|
// ImageStatusDeactivated denotes that access to image data is not allowed to
|
||||||
// any non-admin user.
|
// any non-admin user.
|
||||||
ImageStatusDeactivated ImageStatus = "deactivated"
|
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.
|
// 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) {
|
func Create(client *gophercloud.ServiceClient, id string, member string) (r CreateResult) {
|
||||||
b := map[string]interface{}{"member": member}
|
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},
|
OkCodes: []int{200},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,13 +41,15 @@ func List(client *gophercloud.ServiceClient, id string) pagination.Pager {
|
||||||
|
|
||||||
// Get image member details.
|
// Get image member details.
|
||||||
func Get(client *gophercloud.ServiceClient, imageID string, memberID string) (r DetailsResult) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete membership for given image. Callee should be image owner.
|
// Delete membership for given image. Callee should be image owner.
|
||||||
func Delete(client *gophercloud.ServiceClient, imageID string, memberID string) (r DeleteResult) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +78,8 @@ func Update(client *gophercloud.ServiceClient, imageID string, memberID string,
|
||||||
r.Err = err
|
r.Err = err
|
||||||
return
|
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}})
|
&gophercloud.RequestOpts{OkCodes: []int{200}})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,13 +116,15 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul
|
||||||
r.Err = err
|
r.Err = err
|
||||||
return
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get retrieves a particular floating IP resource based on its unique ID.
|
// Get retrieves a particular floating IP resource based on its unique ID.
|
||||||
func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,9 +169,10 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r
|
||||||
r.Err = err
|
r.Err = err
|
||||||
return
|
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},
|
OkCodes: []int{200},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
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
|
// ensure this is what you want - you can also disassociate the IP from existing
|
||||||
// internal ports.
|
// internal ports.
|
||||||
func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package floatingips
|
package floatingips
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gophercloud/gophercloud"
|
"github.com/gophercloud/gophercloud"
|
||||||
"github.com/gophercloud/gophercloud/pagination"
|
"github.com/gophercloud/gophercloud/pagination"
|
||||||
)
|
)
|
||||||
|
@ -37,6 +40,11 @@ type FloatingIP struct {
|
||||||
// specify a project identifier other than its own.
|
// specify a project identifier other than its own.
|
||||||
TenantID string `json:"tenant_id"`
|
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 is the project owner of the floating IP.
|
||||||
ProjectID string `json:"project_id"`
|
ProjectID string `json:"project_id"`
|
||||||
|
|
||||||
|
@ -50,6 +58,44 @@ type FloatingIP struct {
|
||||||
Tags []string `json:"tags"`
|
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 {
|
type commonResult struct {
|
||||||
gophercloud.Result
|
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.
|
// Get retrieves a specific network based on its unique ID.
|
||||||
func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +100,8 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul
|
||||||
r.Err = err
|
r.Err = err
|
||||||
return
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,51 +132,16 @@ func Update(c *gophercloud.ServiceClient, networkID string, opts UpdateOptsBuild
|
||||||
r.Err = err
|
r.Err = err
|
||||||
return
|
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},
|
OkCodes: []int{200, 201},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete accepts a unique ID and deletes the network associated with it.
|
// Delete accepts a unique ID and deletes the network associated with it.
|
||||||
func Delete(c *gophercloud.ServiceClient, networkID string) (r DeleteResult) {
|
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
|
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
|
package networks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gophercloud/gophercloud"
|
"github.com/gophercloud/gophercloud"
|
||||||
"github.com/gophercloud/gophercloud/pagination"
|
"github.com/gophercloud/gophercloud/pagination"
|
||||||
)
|
)
|
||||||
|
@ -70,6 +73,11 @@ type Network struct {
|
||||||
// TenantID is the project owner of the network.
|
// TenantID is the project owner of the network.
|
||||||
TenantID string `json:"tenant_id"`
|
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 is the project owner of the network.
|
||||||
ProjectID string `json:"project_id"`
|
ProjectID string `json:"project_id"`
|
||||||
|
|
||||||
|
@ -84,6 +92,44 @@ type Network struct {
|
||||||
Tags []string `json:"tags"`
|
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
|
// NetworkPage is the page returned by a pager when traversing over a
|
||||||
// collection of networks.
|
// collection of networks.
|
||||||
type NetworkPage struct {
|
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.
|
// Get retrieves a specific subnet based on its unique ID.
|
||||||
func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,7 +161,8 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul
|
||||||
r.Err = err
|
r.Err = err
|
||||||
return
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,51 +221,16 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r
|
||||||
r.Err = err
|
r.Err = err
|
||||||
return
|
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},
|
OkCodes: []int{200, 201},
|
||||||
})
|
})
|
||||||
|
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete accepts a unique ID and deletes the subnet associated with it.
|
// Delete accepts a unique ID and deletes the subnet associated with it.
|
||||||
func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
|
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
|
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"}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -56,5 +56,6 @@ func Request(client *gophercloud.ServiceClient, headers map[string]string, url s
|
||||||
return client.Get(url, nil, &gophercloud.RequestOpts{
|
return client.Get(url, nil, &gophercloud.RequestOpts{
|
||||||
MoreHeaders: headers,
|
MoreHeaders: headers,
|
||||||
OkCodes: []int{200, 204, 300},
|
OkCodes: []int{200, 204, 300},
|
||||||
|
KeepResponseBody: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -450,6 +450,8 @@ func BuildHeaders(opts interface{}) (map[string]string, error) {
|
||||||
optsMap[tags[0]] = v.String()
|
optsMap[tags[0]] = v.String()
|
||||||
case reflect.Int:
|
case reflect.Int:
|
||||||
optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10)
|
optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10)
|
||||||
|
case reflect.Int64:
|
||||||
|
optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10)
|
||||||
case reflect.Bool:
|
case reflect.Bool:
|
||||||
optsMap[tags[0]] = strconv.FormatBool(v.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.
|
// reauthlock represents a set of attributes used to help in the reauthentication process.
|
||||||
type reauthlock struct {
|
type reauthlock struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
reauthing bool
|
ongoing *reauthFuture
|
||||||
reauthingErr error
|
}
|
||||||
done *sync.Cond
|
|
||||||
|
// 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
|
// 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
|
return
|
||||||
}
|
}
|
||||||
if client.reauthmut != nil {
|
if client.reauthmut != nil {
|
||||||
|
// If a Reauthenticate is in progress, wait for it to complete.
|
||||||
client.reauthmut.Lock()
|
client.reauthmut.Lock()
|
||||||
for client.reauthmut.reauthing {
|
ongoing := client.reauthmut.ongoing
|
||||||
client.reauthmut.done.Wait()
|
|
||||||
}
|
|
||||||
client.reauthmut.Unlock()
|
client.reauthmut.Unlock()
|
||||||
|
if ongoing != nil {
|
||||||
|
_ = ongoing.Get()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
t := client.Token()
|
t := client.Token()
|
||||||
if t == "" {
|
if t == "" {
|
||||||
|
@ -223,7 +248,7 @@ func (client *ProviderClient) SetThrowaway(v bool) {
|
||||||
// this case, the reauthentication can be skipped if another thread has already
|
// this case, the reauthentication can be skipped if another thread has already
|
||||||
// reauthenticated in the meantime. If no previous token is known, an empty
|
// reauthenticated in the meantime. If no previous token is known, an empty
|
||||||
// string should be passed instead to force unconditional reauthentication.
|
// 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 {
|
if client.ReauthFunc == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -232,33 +257,36 @@ func (client *ProviderClient) Reauthenticate(previousToken string) (err error) {
|
||||||
return client.ReauthFunc()
|
return client.ReauthFunc()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
future := newReauthFuture()
|
||||||
|
|
||||||
|
// Check if a Reauthenticate is in progress, or start one if not.
|
||||||
client.reauthmut.Lock()
|
client.reauthmut.Lock()
|
||||||
if client.reauthmut.reauthing {
|
ongoing := client.reauthmut.ongoing
|
||||||
for !client.reauthmut.reauthing {
|
if ongoing == nil {
|
||||||
client.reauthmut.done.Wait()
|
client.reauthmut.ongoing = future
|
||||||
}
|
|
||||||
err = client.reauthmut.reauthingErr
|
|
||||||
client.reauthmut.Unlock()
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
client.reauthmut.Unlock()
|
client.reauthmut.Unlock()
|
||||||
|
|
||||||
client.reauthmut.Lock()
|
// If Reauthenticate is running elsewhere, wait for its result.
|
||||||
client.reauthmut.reauthing = true
|
if ongoing != nil {
|
||||||
client.reauthmut.done = sync.NewCond(client.reauthmut)
|
return ongoing.Get()
|
||||||
client.reauthmut.reauthingErr = nil
|
}
|
||||||
client.reauthmut.Unlock()
|
|
||||||
|
|
||||||
|
// Perform the actual reauthentication.
|
||||||
|
var err error
|
||||||
if previousToken == "" || client.TokenID == previousToken {
|
if previousToken == "" || client.TokenID == previousToken {
|
||||||
err = client.ReauthFunc()
|
err = client.ReauthFunc()
|
||||||
|
} else {
|
||||||
|
err = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mark Reauthenticate as finished.
|
||||||
client.reauthmut.Lock()
|
client.reauthmut.Lock()
|
||||||
client.reauthmut.reauthing = false
|
client.reauthmut.ongoing.Set(err)
|
||||||
client.reauthmut.reauthingErr = err
|
client.reauthmut.ongoing = nil
|
||||||
client.reauthmut.done.Broadcast()
|
|
||||||
client.reauthmut.Unlock()
|
client.reauthmut.Unlock()
|
||||||
return
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestOpts customizes the behavior of the provider.Request() method.
|
// 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.
|
// 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.
|
// This lets resources override default error messages based on the response status code.
|
||||||
ErrorContext error
|
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"
|
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
|
// Request performs an HTTP request using the ProviderClient's current HTTPClient. An authentication
|
||||||
// header will automatically be provided.
|
// header will automatically be provided.
|
||||||
func (client *ProviderClient) Request(method, url string, options *RequestOpts) (*http.Response, error) {
|
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 body io.Reader
|
||||||
var contentType *string
|
var contentType *string
|
||||||
|
|
||||||
|
@ -309,6 +355,11 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts)
|
||||||
contentType = &applicationJSON
|
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 {
|
if options.RawBody != nil {
|
||||||
body = options.RawBody
|
body = options.RawBody
|
||||||
}
|
}
|
||||||
|
@ -347,9 +398,6 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts)
|
||||||
req.Header.Set(k, v)
|
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")
|
prereqtok := req.Header.Get("X-Auth-Token")
|
||||||
|
|
||||||
// Issue the request.
|
// Issue the request.
|
||||||
|
@ -382,6 +430,7 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts)
|
||||||
Expected: options.OkCodes,
|
Expected: options.OkCodes,
|
||||||
Actual: resp.StatusCode,
|
Actual: resp.StatusCode,
|
||||||
Body: body,
|
Body: body,
|
||||||
|
ResponseHeader: resp.Header,
|
||||||
}
|
}
|
||||||
|
|
||||||
errType := options.ErrorContext
|
errType := options.ErrorContext
|
||||||
|
@ -392,7 +441,7 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts)
|
||||||
err = error400er.Error400(respErr)
|
err = error400er.Error400(respErr)
|
||||||
}
|
}
|
||||||
case http.StatusUnauthorized:
|
case http.StatusUnauthorized:
|
||||||
if client.ReauthFunc != nil {
|
if client.ReauthFunc != nil && !state.hasReauthenticated {
|
||||||
err = client.Reauthenticate(prereqtok)
|
err = client.Reauthenticate(prereqtok)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
e := &ErrUnableToReauthenticate{}
|
e := &ErrUnableToReauthenticate{}
|
||||||
|
@ -404,7 +453,8 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts)
|
||||||
seeker.Seek(0, 0)
|
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 {
|
if err != nil {
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
case *ErrUnexpectedResponseCode:
|
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.
|
// Parse the response body as JSON, if requested to do so.
|
||||||
if options.JSONResponse != nil {
|
if options.JSONResponse != nil {
|
||||||
defer resp.Body.Close()
|
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 {
|
if err := json.NewDecoder(resp.Body).Decode(options.JSONResponse); err != nil {
|
||||||
return nil, err
|
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
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultOkCodes(method string) []int {
|
func defaultOkCodes(method string) []int {
|
||||||
switch {
|
switch method {
|
||||||
case method == "GET":
|
case "GET", "HEAD":
|
||||||
return []int{200}
|
return []int{200}
|
||||||
case method == "POST":
|
case "POST":
|
||||||
return []int{201, 202}
|
return []int{201, 202}
|
||||||
case method == "PUT":
|
case "PUT":
|
||||||
return []int{201, 202}
|
return []int{201, 202}
|
||||||
case method == "PATCH":
|
case "PATCH":
|
||||||
return []int{200, 202, 204}
|
return []int{200, 202, 204}
|
||||||
case method == "DELETE":
|
case "DELETE":
|
||||||
return []int{202, 204}
|
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
|
// fields of the struct or composed extension struct
|
||||||
// at the end of this method.
|
// at the end of this method.
|
||||||
toValue.Set(newSlice)
|
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:
|
case reflect.Struct:
|
||||||
|
|
|
@ -152,3 +152,11 @@ func (client *ServiceClient) Request(method, url string, options *RequestOpts) (
|
||||||
}
|
}
|
||||||
return client.ProviderClient.Request(method, url, options)
|
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
|
Example to Create a Provider Client From clouds.yaml
|
||||||
|
|
||||||
opts := &clientconfig.ClientOpts{
|
opts := &clientconfig.ClientOpts{
|
||||||
Name: "hawaii",
|
Cloud: "hawaii",
|
||||||
}
|
}
|
||||||
|
|
||||||
pClient, err := clientconfig.AuthenticatedClient(opts)
|
pClient, err := clientconfig.AuthenticatedClient(opts)
|
||||||
|
|
|
@ -2,12 +2,13 @@ package clientconfig
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gophercloud/gophercloud"
|
"github.com/gophercloud/gophercloud"
|
||||||
"github.com/gophercloud/gophercloud/openstack"
|
"github.com/gophercloud/gophercloud/openstack"
|
||||||
|
"github.com/gophercloud/utils/env"
|
||||||
|
|
||||||
yaml "gopkg.in/yaml.v2"
|
yaml "gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
@ -56,11 +57,62 @@ type ClientOpts struct {
|
||||||
// This will override a region in clouds.yaml or can be used
|
// This will override a region in clouds.yaml or can be used
|
||||||
// when authenticating directly with AuthInfo.
|
// when authenticating directly with AuthInfo.
|
||||||
RegionName string
|
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.
|
// 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) {
|
func LoadCloudsYAML() (map[string]Cloud, error) {
|
||||||
content, err := findAndReadCloudsYAML()
|
_, content, err := FindAndReadCloudsYAML()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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.
|
// 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) {
|
func LoadSecureCloudsYAML() (map[string]Cloud, error) {
|
||||||
var secureClouds Clouds
|
var secureClouds Clouds
|
||||||
|
|
||||||
content, err := findAndReadSecureCloudsYAML()
|
_, content, err := FindAndReadSecureCloudsYAML()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "no secure.yaml file found" {
|
if err.Error() == "no secure.yaml file found" {
|
||||||
// secure.yaml is optional so just ignore read error
|
// 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.
|
// 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) {
|
func LoadPublicCloudsYAML() (map[string]Cloud, error) {
|
||||||
var publicClouds PublicClouds
|
var publicClouds PublicClouds
|
||||||
|
|
||||||
content, err := findAndReadPublicCloudsYAML()
|
_, content, err := FindAndReadPublicCloudsYAML()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "no clouds-public.yaml file found" {
|
if err.Error() == "no clouds-public.yaml file found" {
|
||||||
// clouds-public.yaml is optional so just ignore read error
|
// 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.
|
// GetCloudFromYAML will return a cloud entry from a clouds.yaml file.
|
||||||
func GetCloudFromYAML(opts *ClientOpts) (*Cloud, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to load clouds.yaml: %s", err)
|
return nil, fmt.Errorf("unable to load clouds.yaml: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -138,7 +204,7 @@ func GetCloudFromYAML(opts *ClientOpts) (*Cloud, error) {
|
||||||
envPrefix = opts.EnvPrefix
|
envPrefix = opts.EnvPrefix
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(envPrefix + "CLOUD"); v != "" {
|
if v := env.Getenv(envPrefix + "CLOUD"); v != "" {
|
||||||
cloudName = v
|
cloudName = v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,32 +225,33 @@ func GetCloudFromYAML(opts *ClientOpts) (*Cloud, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var cloudIsInCloudsYaml bool
|
if cloud != nil {
|
||||||
if cloud == nil {
|
// A profile points to a public cloud entry.
|
||||||
// not an immediate error as it might still be defined in secure.yaml
|
// If one was specified, load a list of public clouds
|
||||||
cloudIsInCloudsYaml = false
|
// and then merge the information with the current cloud data.
|
||||||
} else {
|
profileName := defaultIfEmpty(cloud.Profile, cloud.Cloud)
|
||||||
cloudIsInCloudsYaml = true
|
|
||||||
}
|
|
||||||
|
|
||||||
publicClouds, err := LoadPublicCloudsYAML()
|
if profileName != "" {
|
||||||
|
publicClouds, err := yamlOpts.LoadPublicCloudsYAML()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to load clouds-public.yaml: %s", err)
|
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]
|
publicCloud, ok := publicClouds[profileName]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("cloud %s does not exist in clouds-public.yaml", profileName)
|
return nil, fmt.Errorf("cloud %s does not exist in clouds-public.yaml", profileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
cloud, err = mergeClouds(cloud, publicCloud)
|
cloud, err = mergeClouds(cloud, publicCloud)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Could not merge information from clouds.yaml and clouds-public.yaml for cloud %s", profileName)
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to load secure.yaml: %s", err)
|
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 secureClouds != nil {
|
||||||
// If no entry was found in clouds.yaml, no cloud name was specified,
|
// 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.
|
// 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 {
|
for _, v := range secureClouds {
|
||||||
cloud = &v
|
cloud = &v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Otherwise, see if the provided cloud name exists in the secure yaml file.
|
||||||
secureCloud, ok := secureClouds[cloudName]
|
secureCloud, ok := secureClouds[cloudName]
|
||||||
if !ok && cloud == nil {
|
if !ok && cloud == nil {
|
||||||
// cloud == nil serves two purposes here:
|
// 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
|
// Default is to verify SSL API requests
|
||||||
if cloud.Verify == nil {
|
if cloud.Verify == nil {
|
||||||
iTrue := true
|
iTrue := true
|
||||||
|
@ -227,6 +301,17 @@ func GetCloudFromYAML(opts *ClientOpts) (*Cloud, error) {
|
||||||
// clouds-public.yml
|
// clouds-public.yml
|
||||||
// https://github.com/openstack/openstacksdk/tree/master/openstack/config/vendors
|
// 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
|
return cloud, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,7 +345,7 @@ func AuthOptions(opts *ClientOpts) (*gophercloud.AuthOptions, error) {
|
||||||
envPrefix = opts.EnvPrefix
|
envPrefix = opts.EnvPrefix
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(envPrefix + "CLOUD"); v != "" {
|
if v := env.Getenv(envPrefix + "CLOUD"); v != "" {
|
||||||
cloudName = v
|
cloudName = v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -310,7 +395,7 @@ func determineIdentityAPI(cloud *Cloud, opts *ClientOpts) string {
|
||||||
envPrefix = opts.EnvPrefix
|
envPrefix = opts.EnvPrefix
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(envPrefix + "IDENTITY_API_VERSION"); v != "" {
|
if v := env.Getenv(envPrefix + "IDENTITY_API_VERSION"); v != "" {
|
||||||
identityAPI = v
|
identityAPI = v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -359,49 +444,49 @@ func v2auth(cloud *Cloud, opts *ClientOpts) (*gophercloud.AuthOptions, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if cloud.AuthInfo.AuthURL == "" {
|
if cloud.AuthInfo.AuthURL == "" {
|
||||||
if v := os.Getenv(envPrefix + "AUTH_URL"); v != "" {
|
if v := env.Getenv(envPrefix + "AUTH_URL"); v != "" {
|
||||||
cloud.AuthInfo.AuthURL = v
|
cloud.AuthInfo.AuthURL = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cloud.AuthInfo.Token == "" {
|
if cloud.AuthInfo.Token == "" {
|
||||||
if v := os.Getenv(envPrefix + "TOKEN"); v != "" {
|
if v := env.Getenv(envPrefix + "TOKEN"); v != "" {
|
||||||
cloud.AuthInfo.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
|
cloud.AuthInfo.Token = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cloud.AuthInfo.Username == "" {
|
if cloud.AuthInfo.Username == "" {
|
||||||
if v := os.Getenv(envPrefix + "USERNAME"); v != "" {
|
if v := env.Getenv(envPrefix + "USERNAME"); v != "" {
|
||||||
cloud.AuthInfo.Username = v
|
cloud.AuthInfo.Username = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cloud.AuthInfo.Password == "" {
|
if cloud.AuthInfo.Password == "" {
|
||||||
if v := os.Getenv(envPrefix + "PASSWORD"); v != "" {
|
if v := env.Getenv(envPrefix + "PASSWORD"); v != "" {
|
||||||
cloud.AuthInfo.Password = v
|
cloud.AuthInfo.Password = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cloud.AuthInfo.ProjectID == "" {
|
if cloud.AuthInfo.ProjectID == "" {
|
||||||
if v := os.Getenv(envPrefix + "TENANT_ID"); v != "" {
|
if v := env.Getenv(envPrefix + "TENANT_ID"); v != "" {
|
||||||
cloud.AuthInfo.ProjectID = 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
|
cloud.AuthInfo.ProjectID = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cloud.AuthInfo.ProjectName == "" {
|
if cloud.AuthInfo.ProjectName == "" {
|
||||||
if v := os.Getenv(envPrefix + "TENANT_NAME"); v != "" {
|
if v := env.Getenv(envPrefix + "TENANT_NAME"); v != "" {
|
||||||
cloud.AuthInfo.ProjectName = 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
|
cloud.AuthInfo.ProjectName = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -427,115 +512,115 @@ func v3auth(cloud *Cloud, opts *ClientOpts) (*gophercloud.AuthOptions, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if cloud.AuthInfo.AuthURL == "" {
|
if cloud.AuthInfo.AuthURL == "" {
|
||||||
if v := os.Getenv(envPrefix + "AUTH_URL"); v != "" {
|
if v := env.Getenv(envPrefix + "AUTH_URL"); v != "" {
|
||||||
cloud.AuthInfo.AuthURL = v
|
cloud.AuthInfo.AuthURL = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cloud.AuthInfo.Token == "" {
|
if cloud.AuthInfo.Token == "" {
|
||||||
if v := os.Getenv(envPrefix + "TOKEN"); v != "" {
|
if v := env.Getenv(envPrefix + "TOKEN"); v != "" {
|
||||||
cloud.AuthInfo.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
|
cloud.AuthInfo.Token = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cloud.AuthInfo.Username == "" {
|
if cloud.AuthInfo.Username == "" {
|
||||||
if v := os.Getenv(envPrefix + "USERNAME"); v != "" {
|
if v := env.Getenv(envPrefix + "USERNAME"); v != "" {
|
||||||
cloud.AuthInfo.Username = v
|
cloud.AuthInfo.Username = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cloud.AuthInfo.UserID == "" {
|
if cloud.AuthInfo.UserID == "" {
|
||||||
if v := os.Getenv(envPrefix + "USER_ID"); v != "" {
|
if v := env.Getenv(envPrefix + "USER_ID"); v != "" {
|
||||||
cloud.AuthInfo.UserID = v
|
cloud.AuthInfo.UserID = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cloud.AuthInfo.Password == "" {
|
if cloud.AuthInfo.Password == "" {
|
||||||
if v := os.Getenv(envPrefix + "PASSWORD"); v != "" {
|
if v := env.Getenv(envPrefix + "PASSWORD"); v != "" {
|
||||||
cloud.AuthInfo.Password = v
|
cloud.AuthInfo.Password = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cloud.AuthInfo.ProjectID == "" {
|
if cloud.AuthInfo.ProjectID == "" {
|
||||||
if v := os.Getenv(envPrefix + "TENANT_ID"); v != "" {
|
if v := env.Getenv(envPrefix + "TENANT_ID"); v != "" {
|
||||||
cloud.AuthInfo.ProjectID = 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
|
cloud.AuthInfo.ProjectID = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cloud.AuthInfo.ProjectName == "" {
|
if cloud.AuthInfo.ProjectName == "" {
|
||||||
if v := os.Getenv(envPrefix + "TENANT_NAME"); v != "" {
|
if v := env.Getenv(envPrefix + "TENANT_NAME"); v != "" {
|
||||||
cloud.AuthInfo.ProjectName = 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
|
cloud.AuthInfo.ProjectName = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cloud.AuthInfo.DomainID == "" {
|
if cloud.AuthInfo.DomainID == "" {
|
||||||
if v := os.Getenv(envPrefix + "DOMAIN_ID"); v != "" {
|
if v := env.Getenv(envPrefix + "DOMAIN_ID"); v != "" {
|
||||||
cloud.AuthInfo.DomainID = v
|
cloud.AuthInfo.DomainID = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cloud.AuthInfo.DomainName == "" {
|
if cloud.AuthInfo.DomainName == "" {
|
||||||
if v := os.Getenv(envPrefix + "DOMAIN_NAME"); v != "" {
|
if v := env.Getenv(envPrefix + "DOMAIN_NAME"); v != "" {
|
||||||
cloud.AuthInfo.DomainName = v
|
cloud.AuthInfo.DomainName = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cloud.AuthInfo.DefaultDomain == "" {
|
if cloud.AuthInfo.DefaultDomain == "" {
|
||||||
if v := os.Getenv(envPrefix + "DEFAULT_DOMAIN"); v != "" {
|
if v := env.Getenv(envPrefix + "DEFAULT_DOMAIN"); v != "" {
|
||||||
cloud.AuthInfo.DefaultDomain = v
|
cloud.AuthInfo.DefaultDomain = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cloud.AuthInfo.ProjectDomainID == "" {
|
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
|
cloud.AuthInfo.ProjectDomainID = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cloud.AuthInfo.ProjectDomainName == "" {
|
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
|
cloud.AuthInfo.ProjectDomainName = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cloud.AuthInfo.UserDomainID == "" {
|
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
|
cloud.AuthInfo.UserDomainID = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cloud.AuthInfo.UserDomainName == "" {
|
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
|
cloud.AuthInfo.UserDomainName = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cloud.AuthInfo.ApplicationCredentialID == "" {
|
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
|
cloud.AuthInfo.ApplicationCredentialID = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cloud.AuthInfo.ApplicationCredentialName == "" {
|
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
|
cloud.AuthInfo.ApplicationCredentialName = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cloud.AuthInfo.ApplicationCredentialSecret == "" {
|
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
|
cloud.AuthInfo.ApplicationCredentialSecret = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -545,7 +630,11 @@ func v3auth(cloud *Cloud, opts *ClientOpts) (*gophercloud.AuthOptions, error) {
|
||||||
scope := new(gophercloud.AuthScope)
|
scope := new(gophercloud.AuthScope)
|
||||||
|
|
||||||
// Application credentials don't support scope
|
// 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 !isProjectScoped(cloud.AuthInfo) {
|
||||||
if cloud.AuthInfo.DomainID != "" {
|
if cloud.AuthInfo.DomainID != "" {
|
||||||
scope.DomainID = cloud.AuthInfo.DomainID
|
scope.DomainID = cloud.AuthInfo.DomainID
|
||||||
|
@ -639,7 +728,7 @@ func NewServiceClient(service string, opts *ClientOpts) (*gophercloud.ServiceCli
|
||||||
envPrefix = opts.EnvPrefix
|
envPrefix = opts.EnvPrefix
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(envPrefix + "CLOUD"); v != "" {
|
if v := env.Getenv(envPrefix + "CLOUD"); v != "" {
|
||||||
cloudName = v
|
cloudName = v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -659,10 +748,15 @@ func NewServiceClient(service string, opts *ClientOpts) (*gophercloud.ServiceCli
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If an HTTPClient was specified, use it.
|
||||||
|
if opts.HTTPClient != nil {
|
||||||
|
pClient.HTTPClient = *opts.HTTPClient
|
||||||
|
}
|
||||||
|
|
||||||
// Determine the region to use.
|
// Determine the region to use.
|
||||||
// First, check if the REGION_NAME environment variable is set.
|
// First, check if the REGION_NAME environment variable is set.
|
||||||
var region string
|
var region string
|
||||||
if v := os.Getenv(envPrefix + "REGION_NAME"); v != "" {
|
if v := env.Getenv(envPrefix + "REGION_NAME"); v != "" {
|
||||||
region = v
|
region = v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -677,8 +771,27 @@ func NewServiceClient(service string, opts *ClientOpts) (*gophercloud.ServiceCli
|
||||||
region = v
|
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{
|
eo := gophercloud.EndpointOpts{
|
||||||
Region: region,
|
Region: region,
|
||||||
|
Availability: GetEndpointType(endpointType),
|
||||||
}
|
}
|
||||||
|
|
||||||
switch service {
|
switch service {
|
||||||
|
@ -688,6 +801,8 @@ func NewServiceClient(service string, opts *ClientOpts) (*gophercloud.ServiceCli
|
||||||
return openstack.NewComputeV2(pClient, eo)
|
return openstack.NewComputeV2(pClient, eo)
|
||||||
case "container":
|
case "container":
|
||||||
return openstack.NewContainerV1(pClient, eo)
|
return openstack.NewContainerV1(pClient, eo)
|
||||||
|
case "container-infra":
|
||||||
|
return openstack.NewContainerInfraV1(pClient, eo)
|
||||||
case "database":
|
case "database":
|
||||||
return openstack.NewDBV1(pClient, eo)
|
return openstack.NewDBV1(pClient, eo)
|
||||||
case "dns":
|
case "dns":
|
||||||
|
|
|
@ -4,118 +4,124 @@ package clientconfig
|
||||||
// The format of the clouds-public.yml is documented at
|
// The format of the clouds-public.yml is documented at
|
||||||
// https://docs.openstack.org/python-openstackclient/latest/configuration/
|
// https://docs.openstack.org/python-openstackclient/latest/configuration/
|
||||||
type PublicClouds struct {
|
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.
|
// Clouds represents a collection of Cloud entries in a clouds.yaml file.
|
||||||
// The format of clouds.yaml is documented at
|
// The format of clouds.yaml is documented at
|
||||||
// https://docs.openstack.org/os-client-config/latest/user/configuration.html.
|
// https://docs.openstack.org/os-client-config/latest/user/configuration.html.
|
||||||
type Clouds struct {
|
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.
|
// Cloud represents an entry in a clouds.yaml/public-clouds.yaml/secure.yaml file.
|
||||||
type Cloud struct {
|
type Cloud struct {
|
||||||
Cloud string `yaml:"cloud"`
|
Cloud string `yaml:"cloud,omitempty" json:"cloud,omitempty"`
|
||||||
Profile string `yaml:"profile"`
|
Profile string `yaml:"profile,omitempty" json:"profile,omitempty"`
|
||||||
AuthInfo *AuthInfo `yaml:"auth"`
|
AuthInfo *AuthInfo `yaml:"auth,omitempty" json:"auth,omitempty"`
|
||||||
AuthType AuthType `yaml:"auth_type"`
|
AuthType AuthType `yaml:"auth_type,omitempty" json:"auth_type,omitempty"`
|
||||||
RegionName string `yaml:"region_name"`
|
RegionName string `yaml:"region_name,omitempty" json:"region_name,omitempty"`
|
||||||
Regions []interface{} `yaml:"regions"`
|
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.
|
// API Version overrides.
|
||||||
IdentityAPIVersion string `yaml:"identity_api_version"`
|
IdentityAPIVersion string `yaml:"identity_api_version,omitempty" json:"identity_api_version,omitempty"`
|
||||||
VolumeAPIVersion string `yaml:"volume_api_version"`
|
VolumeAPIVersion string `yaml:"volume_api_version,omitempty" json:"volume_api_version,omitempty"`
|
||||||
|
|
||||||
// Verify whether or not SSL API requests should be verified.
|
// 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
|
// CACertFile a path to a CA Cert bundle that can be used as part of
|
||||||
// verifying SSL API requests.
|
// 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
|
// ClientCertFile a path to a client certificate to use as part of the SSL
|
||||||
// transaction.
|
// 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
|
// ClientKeyFile a path to a client key to use as part of the SSL
|
||||||
// transaction.
|
// transaction.
|
||||||
ClientKeyFile string `yaml:"key"`
|
ClientKeyFile string `yaml:"key,omitempty" json:"key,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthInfo represents the auth section of a cloud entry or
|
// AuthInfo represents the auth section of a cloud entry or
|
||||||
// auth options entered explicitly in ClientOpts.
|
// auth options entered explicitly in ClientOpts.
|
||||||
type AuthInfo struct {
|
type AuthInfo struct {
|
||||||
// AuthURL is the keystone/identity endpoint URL.
|
// 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 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 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 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 is the password of the user.
|
||||||
Password string `yaml:"password"`
|
Password string `yaml:"password,omitempty" json:"password,omitempty"`
|
||||||
|
|
||||||
// Application Credential ID to login with.
|
// 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.
|
// 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.
|
// 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.
|
// ProjectName is the common/human-readable name of a project.
|
||||||
// Users can be scoped to a project.
|
// Users can be scoped to a project.
|
||||||
// ProjectName on its own is not enough to ensure a unique scope. It must
|
// ProjectName on its own is not enough to ensure a unique scope. It must
|
||||||
// also be combined with either a ProjectDomainName or ProjectDomainID.
|
// also be combined with either a ProjectDomainName or ProjectDomainID.
|
||||||
// ProjectName cannot be combined with ProjectID in a scope.
|
// 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.
|
// ProjectID is the unique ID of a project.
|
||||||
// It can be used to scope a user to a specific 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.
|
// UserDomainName is the name of the domain where a user resides.
|
||||||
// It is used to identify the source domain of a user.
|
// 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.
|
// UserDomainID is the unique ID of the domain where a user resides.
|
||||||
// It is used to identify the source domain of a user.
|
// 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.
|
// ProjectDomainName is the name of the domain where a project resides.
|
||||||
// It is used to identify the source domain of a project.
|
// It is used to identify the source domain of a project.
|
||||||
// ProjectDomainName can be used in addition to a ProjectName when scoping
|
// ProjectDomainName can be used in addition to a ProjectName when scoping
|
||||||
// a user to a specific project.
|
// 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.
|
// ProjectDomainID is the name of the domain where a project resides.
|
||||||
// It is used to identify the source domain of a project.
|
// It is used to identify the source domain of a project.
|
||||||
// ProjectDomainID can be used in addition to a ProjectName when scoping
|
// ProjectDomainID can be used in addition to a ProjectName when scoping
|
||||||
// a user to a specific project.
|
// 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
|
// DomainName is the name of a domain which can be used to identify the
|
||||||
// source domain of either a user or a project.
|
// source domain of either a user or a project.
|
||||||
// If UserDomainName and ProjectDomainName are not specified, then DomainName
|
// If UserDomainName and ProjectDomainName are not specified, then DomainName
|
||||||
// is used as a default choice.
|
// is used as a default choice.
|
||||||
// It can also be used be used to specify a domain-only scope.
|
// 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
|
// DomainID is the unique ID of a domain which can be used to identify the
|
||||||
// source domain of eitehr a user or a project.
|
// source domain of eitehr a user or a project.
|
||||||
// If UserDomainID and ProjectDomainID are not specified, then DomainID is
|
// If UserDomainID and ProjectDomainID are not specified, then DomainID is
|
||||||
// used as a default choice.
|
// used as a default choice.
|
||||||
// It can also be used be used to specify a domain-only scope.
|
// 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
|
// DefaultDomain is the domain ID to fall back on if no other domain has
|
||||||
// been specified and a domain is required for scope.
|
// 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"
|
"os/user"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
"github.com/gophercloud/utils/env"
|
||||||
)
|
)
|
||||||
|
|
||||||
// defaultIfEmpty is a helper function to make it cleaner to set default value
|
// 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:
|
// locations:
|
||||||
//
|
//
|
||||||
// 1. OS_CLIENT_CONFIG_FILE
|
// 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)
|
// 4. unix-specific site_config_dir (/etc/openstack/clouds.yaml)
|
||||||
//
|
//
|
||||||
// If found, the contents of the file is returned.
|
// If found, the contents of the file is returned.
|
||||||
func findAndReadCloudsYAML() ([]byte, error) {
|
func FindAndReadCloudsYAML() (string, []byte, error) {
|
||||||
// OS_CLIENT_CONFIG_FILE
|
// 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 {
|
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) {
|
func FindAndReadPublicCloudsYAML() (string, []byte, error) {
|
||||||
return findAndReadYAML("clouds-public.yaml")
|
return FindAndReadYAML("clouds-public.yaml")
|
||||||
}
|
}
|
||||||
|
|
||||||
func findAndReadSecureCloudsYAML() ([]byte, error) {
|
func FindAndReadSecureCloudsYAML() (string, []byte, error) {
|
||||||
return findAndReadYAML("secure.yaml")
|
return FindAndReadYAML("secure.yaml")
|
||||||
}
|
}
|
||||||
|
|
||||||
func findAndReadYAML(yamlFile string) ([]byte, error) {
|
func FindAndReadYAML(yamlFile string) (string, []byte, error) {
|
||||||
// current directory
|
// current directory
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
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)
|
filename := filepath.Join(cwd, yamlFile)
|
||||||
if ok := fileExists(filename); ok {
|
if ok := fileExists(filename); ok {
|
||||||
return ioutil.ReadFile(filename)
|
content, err := ioutil.ReadFile(filename)
|
||||||
|
return filename, content, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// unix user config directory: ~/.config/openstack.
|
// unix user config directory: ~/.config/openstack.
|
||||||
|
@ -133,17 +138,20 @@ func findAndReadYAML(yamlFile string) ([]byte, error) {
|
||||||
if homeDir != "" {
|
if homeDir != "" {
|
||||||
filename := filepath.Join(homeDir, ".config/openstack/"+yamlFile)
|
filename := filepath.Join(homeDir, ".config/openstack/"+yamlFile)
|
||||||
if ok := fileExists(filename); ok {
|
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.
|
// unix-specific site config directory: /etc/openstack.
|
||||||
if ok := fileExists("/etc/openstack/" + yamlFile); ok {
|
filename = "/etc/openstack/" + yamlFile
|
||||||
return ioutil.ReadFile("/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.
|
// fileExists checks for the existence of a file at a given location.
|
||||||
|
@ -153,3 +161,15 @@ func fileExists(filename string) bool {
|
||||||
}
|
}
|
||||||
return false
|
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/google/uuid
|
||||||
# github.com/googleapis/gax-go/v2 v2.0.5
|
# github.com/googleapis/gax-go/v2 v2.0.5
|
||||||
github.com/googleapis/gax-go/v2
|
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
|
||||||
github.com/gophercloud/gophercloud/internal
|
github.com/gophercloud/gophercloud/internal
|
||||||
github.com/gophercloud/gophercloud/openstack
|
github.com/gophercloud/gophercloud/openstack
|
||||||
github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions
|
github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions
|
||||||
github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes
|
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/attachinterfaces
|
||||||
github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume
|
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/keypairs
|
||||||
github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop
|
github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop
|
||||||
github.com/gophercloud/gophercloud/openstack/compute/v2/flavors
|
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/compute/v2/servers
|
||||||
github.com/gophercloud/gophercloud/openstack/identity/v2/tenants
|
github.com/gophercloud/gophercloud/openstack/identity/v2/tenants
|
||||||
github.com/gophercloud/gophercloud/openstack/identity/v2/tokens
|
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/identity/v3/tokens
|
||||||
github.com/gophercloud/gophercloud/openstack/imageservice/v2/images
|
github.com/gophercloud/gophercloud/openstack/imageservice/v2/images
|
||||||
github.com/gophercloud/gophercloud/openstack/imageservice/v2/members
|
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/networking/v2/subnets
|
||||||
github.com/gophercloud/gophercloud/openstack/utils
|
github.com/gophercloud/gophercloud/openstack/utils
|
||||||
github.com/gophercloud/gophercloud/pagination
|
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/clientconfig
|
||||||
|
github.com/gophercloud/utils/openstack/compute/v2/flavors
|
||||||
# github.com/gorilla/websocket v0.0.0-20170319172727-a91eba7f9777
|
# github.com/gorilla/websocket v0.0.0-20170319172727-a91eba7f9777
|
||||||
github.com/gorilla/websocket
|
github.com/gorilla/websocket
|
||||||
# github.com/grpc-ecosystem/go-grpc-middleware v1.1.0
|
# github.com/grpc-ecosystem/go-grpc-middleware v1.1.0
|
||||||
|
|
Loading…
Reference in New Issue