diff --git a/go.mod b/go.mod index 84fa186586..aba1c9eeb0 100644 --- a/go.mod +++ b/go.mod @@ -10,8 +10,8 @@ require ( github.com/coreos/go-systemd v0.0.0-20190620071333-e64a0ec8b42a // indirect github.com/coreos/ignition v0.33.0 // indirect github.com/go-logr/logr v0.2.1 - github.com/gophercloud/gophercloud v0.6.1-0.20191025185032-6ad562af8c1f - github.com/gophercloud/utils v0.0.0-20190124231947-9c3b9f2457ef + github.com/gophercloud/gophercloud v0.14.0 + github.com/gophercloud/utils v0.0.0-20201203161420-f41c1768a042 github.com/onsi/ginkgo v1.14.1 github.com/onsi/gomega v1.10.2 github.com/openshift/api v0.0.0-20200901182017-7ac89ba6b971 diff --git a/go.sum b/go.sum index 8d6be67b67..3f9ad9f888 100644 --- a/go.sum +++ b/go.sum @@ -276,10 +276,11 @@ github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1a github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= -github.com/gophercloud/gophercloud v0.6.1-0.20191025185032-6ad562af8c1f h1:oUSh4Z++AIcYE3FClYjEqC+T/esZ2ZDaDeaEeXQ2mkE= -github.com/gophercloud/gophercloud v0.6.1-0.20191025185032-6ad562af8c1f/go.mod h1:GICNByuaEBibcjmjvI7QvYJSZEbGkcYwAR7EZK2WMqM= -github.com/gophercloud/utils v0.0.0-20190124231947-9c3b9f2457ef h1:/5kyAzeE1xcedTX3DxziRVWHJrxD+7NEdXU/4/DOjTs= -github.com/gophercloud/utils v0.0.0-20190124231947-9c3b9f2457ef/go.mod h1:wjDF8z83zTeg5eMLml5EBSlAhbF7G8DobyI1YsMuyzw= +github.com/gophercloud/gophercloud v0.6.1-0.20191122030953-d8ac278c1c9d/go.mod h1:ozGNgr9KYOVATV5jsgHl/ceCDXGuguqOZAzoQ/2vcNM= +github.com/gophercloud/gophercloud v0.14.0 h1:c2Byo+YMxhHlTJ3TPptjQ4dOQ1YknTHDJ/9zClDH+84= +github.com/gophercloud/gophercloud v0.14.0/go.mod h1:VX0Ibx85B60B5XOrZr6kaNwrmPUzcmMpwxvQ1WQIIWM= +github.com/gophercloud/utils v0.0.0-20201203161420-f41c1768a042 h1:R/+WaNsfMEc3WAljyhQVkBJ9UsyBYxbOEqYCMUx4GiM= +github.com/gophercloud/utils v0.0.0-20201203161420-f41c1768a042/go.mod h1:ehWUbLQJPqS0Ep+CxeD559hsm9pthPXadJNKwZkp43w= github.com/gorilla/mux v0.0.0-20191024121256-f395758b854c/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= @@ -290,6 +291,7 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= @@ -325,6 +327,8 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= @@ -351,6 +355,7 @@ github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= @@ -544,6 +549,7 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/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-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= @@ -634,6 +640,7 @@ golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/pkg/cloud/openstack/clients/machineservice.go b/pkg/cloud/openstack/clients/machineservice.go index f524613d03..0ade87a0ca 100644 --- a/pkg/cloud/openstack/clients/machineservice.go +++ b/pkg/cloud/openstack/clients/machineservice.go @@ -49,6 +49,7 @@ import ( "github.com/gophercloud/gophercloud/openstack/networking/v2/subnets" "github.com/gophercloud/gophercloud/pagination" "github.com/gophercloud/utils/openstack/clientconfig" + azutils "github.com/gophercloud/utils/openstack/compute/v2/availabilityzones" configclient "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1" machinev1 "github.com/openshift/machine-api-operator/pkg/apis/machine/v1beta1" "github.com/openshift/machine-api-operator/pkg/util" @@ -915,6 +916,23 @@ func (is *InstanceService) DoesImageExist(imageName string) error { return nil } +// DoesAvailabilityZoneExist return an error if AZ with the given name doesn't exist, and nil otherwise +func (is *InstanceService) DoesAvailabilityZoneExist(azName string) error { + zones, err := azutils.ListAvailableAvailabilityZones(is.computeClient) + if err != nil { + return err + } + if len(zones) == 0 { + return fmt.Errorf("could not find an available compute availability zone") + } + for _, zoneName := range zones { + if zoneName == azName { + return nil + } + } + return fmt.Errorf("could not find compute availability zone: %s", azName) +} + func (is *InstanceService) GetInstance(resourceId string) (instance *Instance, err error) { if resourceId == "" { return nil, fmt.Errorf("ResourceId should be specified to get detail.") diff --git a/pkg/cloud/openstack/machine/actuator.go b/pkg/cloud/openstack/machine/actuator.go index 241ca49152..6ad791a134 100644 --- a/pkg/cloud/openstack/machine/actuator.go +++ b/pkg/cloud/openstack/machine/actuator.go @@ -711,5 +711,11 @@ func (oc *OpenstackClient) validateMachine(machine *machinev1.Machine) error { return err } + // Validate that Availability Zone exists + err = machineService.DoesAvailabilityZoneExist(machineSpec.AvailabilityZone) + if err != nil { + return err + } + return nil } diff --git a/vendor/github.com/gophercloud/gophercloud/.travis.yml b/vendor/github.com/gophercloud/gophercloud/.travis.yml index 31f80f8dbb..a6eb99c4a9 100644 --- a/vendor/github.com/gophercloud/gophercloud/.travis.yml +++ b/vendor/github.com/gophercloud/gophercloud/.travis.yml @@ -7,9 +7,9 @@ install: - GO111MODULE=off go get github.com/mattn/goveralls - GO111MODULE=off go get golang.org/x/tools/cmd/goimports go: -- "1.11" -- "1.12" - "1.13" +- "1.14" +- "1.15" - "tip" env: global: diff --git a/vendor/github.com/gophercloud/gophercloud/.zuul.yaml b/vendor/github.com/gophercloud/gophercloud/.zuul.yaml index 135e3b203a..5da8982915 100644 --- a/vendor/github.com/gophercloud/gophercloud/.zuul.yaml +++ b/vendor/github.com/gophercloud/gophercloud/.zuul.yaml @@ -12,6 +12,8 @@ description: | Run gophercloud acceptance test on master branch run: .zuul/playbooks/gophercloud-acceptance-test/run.yaml + timeout: 18000 # 5 hours + nodeset: ubuntu-bionic - job: name: gophercloud-acceptance-test-ironic @@ -19,6 +21,25 @@ description: | Run gophercloud ironic acceptance test on master branch run: .zuul/playbooks/gophercloud-acceptance-test-ironic/run.yaml + nodeset: ubuntu-bionic + +- job: + name: gophercloud-acceptance-test-ussuri + parent: gophercloud-acceptance-test + description: | + Run gophercloud acceptance test on ussuri branch + vars: + global_env: + OS_BRANCH: stable/ussuri + +- job: + name: gophercloud-acceptance-test-train + parent: gophercloud-acceptance-test + description: | + Run gophercloud acceptance test on train branch + vars: + global_env: + OS_BRANCH: stable/train - job: name: gophercloud-acceptance-test-stein @@ -43,15 +64,19 @@ parent: gophercloud-acceptance-test description: | Run gophercloud acceptance test on queens branch + nodeset: ubuntu-xenial vars: global_env: OS_BRANCH: stable/queens +# NOTE: A Pike-based devstack environment is currently +# not building correctly. This might be a temporary issue. - job: name: gophercloud-acceptance-test-pike parent: gophercloud-acceptance-test description: | Run gophercloud acceptance test on pike branch + nodeset: ubuntu-xenial vars: global_env: OS_BRANCH: stable/pike @@ -61,29 +86,23 @@ parent: gophercloud-acceptance-test description: | Run gophercloud acceptance test on ocata branch + nodeset: ubuntu-xenial vars: global_env: OS_BRANCH: stable/ocata +# NOTE: A Newton-based devstack environment is currently +# not building correctly. This might be a temporary issue. - job: name: gophercloud-acceptance-test-newton parent: gophercloud-acceptance-test description: | Run gophercloud acceptance test on newton branch + nodeset: ubuntu-xenial vars: global_env: OS_BRANCH: stable/newton -- job: - name: gophercloud-acceptance-test-mitaka - parent: gophercloud-acceptance-test - description: | - Run gophercloud acceptance test on mitaka branch - vars: - global_env: - OS_BRANCH: stable/mitaka - nodeset: ubuntu-trusty - - project: name: gophercloud/gophercloud check: @@ -91,9 +110,6 @@ - gophercloud-unittest - gophercloud-acceptance-test - gophercloud-acceptance-test-ironic - recheck-mitaka: - jobs: - - gophercloud-acceptance-test-mitaka recheck-newton: jobs: - gophercloud-acceptance-test-newton @@ -112,3 +128,9 @@ recheck-stein: jobs: - gophercloud-acceptance-test-stein + recheck-train: + jobs: + - gophercloud-acceptance-test-train + recheck-ussuri: + jobs: + - gophercloud-acceptance-test-ussuri diff --git a/vendor/github.com/gophercloud/gophercloud/CHANGELOG.md b/vendor/github.com/gophercloud/gophercloud/CHANGELOG.md index e2e4c82da8..b38b101688 100644 --- a/vendor/github.com/gophercloud/gophercloud/CHANGELOG.md +++ b/vendor/github.com/gophercloud/gophercloud/CHANGELOG.md @@ -1,10 +1,296 @@ -## 0.7.0 (Unreleased) +## 0.15.0 (Unreleased) + +## 0.14.0 (November 11, 2020) + +IMPROVEMENTS + +* Added `identity/v3/endpoints.Endpoint.Enabled` [GH-2030](https://github.com/gophercloud/gophercloud/pull/2030) +* Added `containerinfra/v1/clusters.Upgrade` [GH-2032](https://github.com/gophercloud/gophercloud/pull/2032) +* Added `compute/apiversions.List` [GH-2037](https://github.com/gophercloud/gophercloud/pull/2037) +* Added `compute/apiversions.Get` [GH-2037](https://github.com/gophercloud/gophercloud/pull/2037) +* Added `compute/v2/servers.ListOpts.IP` [GH-2038](https://github.com/gophercloud/gophercloud/pull/2038) +* Added `compute/v2/servers.ListOpts.IP6` [GH-2038](https://github.com/gophercloud/gophercloud/pull/2038) +* Added `compute/v2/servers.ListOpts.UserID` [GH-2038](https://github.com/gophercloud/gophercloud/pull/2038) +* Added `dns/v2/transfer/accept.List` [GH-2041](https://github.com/gophercloud/gophercloud/pull/2041) +* Added `dns/v2/transfer/accept.Get` [GH-2041](https://github.com/gophercloud/gophercloud/pull/2041) +* Added `dns/v2/transfer/accept.Create` [GH-2041](https://github.com/gophercloud/gophercloud/pull/2041) +* Added `dns/v2/transfer/requests.List` [GH-2041](https://github.com/gophercloud/gophercloud/pull/2041) +* Added `dns/v2/transfer/requests.Get` [GH-2041](https://github.com/gophercloud/gophercloud/pull/2041) +* Added `dns/v2/transfer/requests.Update` [GH-2041](https://github.com/gophercloud/gophercloud/pull/2041) +* Added `dns/v2/transfer/requests.Delete` [GH-2041](https://github.com/gophercloud/gophercloud/pull/2041) +* Added `baremetal/v1/nodes.RescueWait` [GH-2052](https://github.com/gophercloud/gophercloud/pull/2052) +* Added `baremetal/v1/nodes.Unrescuing` [GH-2052](https://github.com/gophercloud/gophercloud/pull/2052) +* Added `networking/v2/extensions/fwaas_v2/groups.List` [GH-2050](https://github.com/gophercloud/gophercloud/pull/2050) +* Added `networking/v2/extensions/fwaas_v2/groups.Get` [GH-2050](https://github.com/gophercloud/gophercloud/pull/2050) +* Added `networking/v2/extensions/fwaas_v2/groups.Create` [GH-2050](https://github.com/gophercloud/gophercloud/pull/2050) +* Added `networking/v2/extensions/fwaas_v2/groups.Update` [GH-2050](https://github.com/gophercloud/gophercloud/pull/2050) +* Added `networking/v2/extensions/fwaas_v2/groups.Delete` [GH-2050](https://github.com/gophercloud/gophercloud/pull/2050) + +BUG FIXES + +* Changed `networking/v2/extensions/layer3/routers.Routes` from `[]Route` to `*[]Route` [GH-2043](https://github.com/gophercloud/gophercloud/pull/2043) + +## 0.13.0 (September 27, 2020) + +IMPROVEMENTS + +* Added `ProtocolTerminatedHTTPS` as a valid listener protocol to `loadbalancer/v2/listeners` [GH-1992](https://github.com/gophercloud/gophercloud/pull/1992) +* Added `objectstorage/v1/objects.CreateTempURLOpts.Timestamp` [GH-1994](https://github.com/gophercloud/gophercloud/pull/1994) +* Added `compute/v2/extensions/schedulerhints.SchedulerHints.DifferentCell` [GH-2012](https://github.com/gophercloud/gophercloud/pull/2012) +* Added `loadbalancer/v2/quotas.Get` [GH-2010](https://github.com/gophercloud/gophercloud/pull/2010) +* Added `messaging/v2/queues.CreateOpts.EnableEncryptMessages` [GH-2016](https://github.com/gophercloud/gophercloud/pull/2016) +* Added `messaging/v2/queues.ListOpts.Name` [GH-2018](https://github.com/gophercloud/gophercloud/pull/2018) +* Added `messaging/v2/queues.ListOpts.WithCount` [GH-2018](https://github.com/gophercloud/gophercloud/pull/2018) +* Added `loadbalancer/v2/quotas.Update` [GH-2023](https://github.com/gophercloud/gophercloud/pull/2023) +* Added `loadbalancer/v2/loadbalancers.ListOpts.AvailabilityZone` [GH-2026](https://github.com/gophercloud/gophercloud/pull/2026) +* Added `loadbalancer/v2/loadbalancers.CreateOpts.AvailabilityZone` [GH-2026](https://github.com/gophercloud/gophercloud/pull/2026) +* Added `loadbalancer/v2/loadbalancers.LoadBalancer.AvailabilityZone` [GH-2026](https://github.com/gophercloud/gophercloud/pull/2026) +* Added `networking/v2/extensions/layer3/routers.ListL3Agents` [GH-2025](https://github.com/gophercloud/gophercloud/pull/2025) + +BUG FIXES + +* Fixed URL escaping in `objectstorage/v1/objects.CreateTempURL` [GH-1994](https://github.com/gophercloud/gophercloud/pull/1994) +* Remove unused `ServiceClient` from `compute/v2/servers.CreateOpts` [GH-2004](https://github.com/gophercloud/gophercloud/pull/2004) +* Changed `objectstorage/v1/objects.CreateOpts.DeleteAfter` from `int` to `int64` [GH-2014](https://github.com/gophercloud/gophercloud/pull/2014) +* Changed `objectstorage/v1/objects.CreateOpts.DeleteAt` from `int` to `int64` [GH-2014](https://github.com/gophercloud/gophercloud/pull/2014) +* Changed `objectstorage/v1/objects.UpdateOpts.DeleteAfter` from `int` to `int64` [GH-2014](https://github.com/gophercloud/gophercloud/pull/2014) +* Changed `objectstorage/v1/objects.UpdateOpts.DeleteAt` from `int` to `int64` [GH-2014](https://github.com/gophercloud/gophercloud/pull/2014) + + +## 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) +* Changed `baremetalintrospection/v1/introspection.RootDiskType.Size` from `int` to `int64` [GH-1988](https://github.com/gophercloud/gophercloud/pull/1988) + +## 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](https://github.com/gophercloud/gophercloud/pull/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) diff --git a/vendor/github.com/gophercloud/gophercloud/README.md b/vendor/github.com/gophercloud/gophercloud/README.md index ad29041d9b..95539563ac 100644 --- a/vendor/github.com/gophercloud/gophercloud/README.md +++ b/vendor/github.com/gophercloud/gophercloud/README.md @@ -60,6 +60,13 @@ prompted for your password. ### Authentication +> NOTE: It is now recommended to use the `clientconfig` package found at +> https://github.com/gophercloud/utils/tree/master/openstack/clientconfig +> for all authentication purposes. +> +> The below documentation is still relevant. clientconfig simply implements +> the below and presents it in an easier and more flexible way. + Once you have access to your credentials, you can begin plugging them into Gophercloud. The next step is authentication, and this is handled by a base "Provider" struct. To get one, you can either pass in your credentials diff --git a/vendor/github.com/gophercloud/gophercloud/auth_options.go b/vendor/github.com/gophercloud/gophercloud/auth_options.go index 5ffa8d1e0a..4f301305e6 100644 --- a/vendor/github.com/gophercloud/gophercloud/auth_options.go +++ b/vendor/github.com/gophercloud/gophercloud/auth_options.go @@ -45,6 +45,9 @@ type AuthOptions struct { Password string `json:"password,omitempty"` + // Passcode is used in TOTP authentication method + Passcode string `json:"passcode,omitempty"` + // At most one of DomainID and DomainName must be provided if using Username // with Identity V3. Otherwise, either are optional. DomainID string `json:"-"` @@ -98,6 +101,7 @@ type AuthScope struct { ProjectName string DomainID string DomainName string + System bool } // ToTokenV2CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder @@ -133,6 +137,8 @@ func (opts AuthOptions) ToTokenV2CreateMap() (map[string]interface{}, error) { return map[string]interface{}{"auth": authMap}, nil } +// ToTokenV3CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder +// interface in the v3 tokens package func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) { type domainReq struct { ID *string `json:"id,omitempty"` @@ -148,7 +154,8 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s type userReq struct { ID *string `json:"id,omitempty"` Name *string `json:"name,omitempty"` - Password string `json:"password,omitempty"` + Password *string `json:"password,omitempty"` + Passcode *string `json:"passcode,omitempty"` Domain *domainReq `json:"domain,omitempty"` } @@ -167,11 +174,16 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s Secret *string `json:"secret,omitempty"` } + type totpReq struct { + User *userReq `json:"user,omitempty"` + } + type identityReq struct { Methods []string `json:"methods"` Password *passwordReq `json:"password,omitempty"` Token *tokenReq `json:"token,omitempty"` ApplicationCredential *applicationCredentialReq `json:"application_credential,omitempty"` + TOTP *totpReq `json:"totp,omitempty"` } type authReq struct { @@ -186,7 +198,7 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s // if insufficient or incompatible information is present. var req request - if opts.Password == "" { + if opts.Password == "" && opts.Passcode == "" { if opts.TokenID != "" { // Because we aren't using password authentication, it's an error to also provide any of the user-based authentication // parameters. @@ -274,7 +286,14 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s } } else { // Password authentication. - req.Auth.Identity.Methods = []string{"password"} + if opts.Password != "" { + req.Auth.Identity.Methods = append(req.Auth.Identity.Methods, "password") + } + + // TOTP authentication. + if opts.Passcode != "" { + req.Auth.Identity.Methods = append(req.Auth.Identity.Methods, "totp") + } // At least one of Username and UserID must be specified. if opts.Username == "" && opts.UserID == "" { @@ -298,23 +317,46 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s } // Configure the request for Username and Password authentication with a DomainID. - req.Auth.Identity.Password = &passwordReq{ - User: userReq{ - Name: &opts.Username, - Password: opts.Password, - Domain: &domainReq{ID: &opts.DomainID}, - }, + if opts.Password != "" { + req.Auth.Identity.Password = &passwordReq{ + User: userReq{ + Name: &opts.Username, + Password: &opts.Password, + Domain: &domainReq{ID: &opts.DomainID}, + }, + } + } + if opts.Passcode != "" { + req.Auth.Identity.TOTP = &totpReq{ + User: &userReq{ + Name: &opts.Username, + Passcode: &opts.Passcode, + Domain: &domainReq{ID: &opts.DomainID}, + }, + } } } if opts.DomainName != "" { // Configure the request for Username and Password authentication with a DomainName. - req.Auth.Identity.Password = &passwordReq{ - User: userReq{ - Name: &opts.Username, - Password: opts.Password, - Domain: &domainReq{Name: &opts.DomainName}, - }, + if opts.Password != "" { + req.Auth.Identity.Password = &passwordReq{ + User: userReq{ + Name: &opts.Username, + Password: &opts.Password, + Domain: &domainReq{Name: &opts.DomainName}, + }, + } + } + + if opts.Passcode != "" { + req.Auth.Identity.TOTP = &totpReq{ + User: &userReq{ + Name: &opts.Username, + Passcode: &opts.Passcode, + Domain: &domainReq{Name: &opts.DomainName}, + }, + } } } } @@ -329,8 +371,22 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s } // Configure the request for UserID and Password authentication. - req.Auth.Identity.Password = &passwordReq{ - User: userReq{ID: &opts.UserID, Password: opts.Password}, + if opts.Password != "" { + req.Auth.Identity.Password = &passwordReq{ + User: userReq{ + ID: &opts.UserID, + Password: &opts.Password, + }, + } + } + + if opts.Passcode != "" { + req.Auth.Identity.TOTP = &totpReq{ + User: &userReq{ + ID: &opts.UserID, + Passcode: &opts.Passcode, + }, + } } } } @@ -347,6 +403,8 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s return b, nil } +// ToTokenV3ScopeMap builds a scope from AuthOptions and satisfies interface in +// the v3 tokens package. func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) { // For backwards compatibility. // If AuthOptions.Scope was not set, try to determine it. @@ -364,6 +422,14 @@ func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) { } } + if opts.Scope.System { + return map[string]interface{}{ + "system": map[string]interface{}{ + "all": true, + }, + }, nil + } + if opts.Scope.ProjectName != "" { // ProjectName provided: either DomainID or DomainName must also be supplied. // ProjectID may not be supplied. @@ -433,5 +499,16 @@ func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) { } func (opts AuthOptions) CanReauth() bool { + if opts.Passcode != "" { + // cannot reauth using TOTP passcode + return false + } + return opts.AllowReauth } + +// ToTokenV3HeadersMap allows AuthOptions to satisfy the AuthOptionsBuilder +// interface in the v3 tokens package. +func (opts *AuthOptions) ToTokenV3HeadersMap(map[string]interface{}) (map[string]string, error) { + return nil, nil +} diff --git a/vendor/github.com/gophercloud/gophercloud/errors.go b/vendor/github.com/gophercloud/gophercloud/errors.go index 0bcb3af7f0..77cabf6a92 100644 --- a/vendor/github.com/gophercloud/gophercloud/errors.go +++ b/vendor/github.com/gophercloud/gophercloud/errors.go @@ -2,6 +2,7 @@ package gophercloud import ( "fmt" + "net/http" "strings" ) @@ -77,11 +78,12 @@ func (e ErrMissingAnyoneOfEnvironmentVariables) Error() string { // those listed in OkCodes is encountered. type ErrUnexpectedResponseCode struct { BaseError - URL string - Method string - Expected []int - Actual int - Body []byte + URL string + Method string + Expected []int + Actual int + Body []byte + ResponseHeader http.Header } func (e ErrUnexpectedResponseCode) Error() string { @@ -92,6 +94,23 @@ func (e ErrUnexpectedResponseCode) Error() string { return e.choseErrString() } +// GetStatusCode returns the actual status code of the error. +func (e ErrUnexpectedResponseCode) GetStatusCode() int { + return e.Actual +} + +// StatusCodeError is a convenience interface to easily allow access to the +// status code field of the various ErrDefault* types. +// +// By using this interface, you only have to make a single type cast of +// the returned error to err.(StatusCodeError) and then call GetStatusCode() +// instead of having a large switch statement checking for each of the +// ErrDefault* types. +type StatusCodeError interface { + Error() string + GetStatusCode() int +} + // ErrDefault400 is the default error type returned on a 400 HTTP response code. type ErrDefault400 struct { ErrUnexpectedResponseCode diff --git a/vendor/github.com/gophercloud/gophercloud/go.mod b/vendor/github.com/gophercloud/gophercloud/go.mod index 1eebf17ed7..64e2a0fb48 100644 --- a/vendor/github.com/gophercloud/gophercloud/go.mod +++ b/vendor/github.com/gophercloud/gophercloud/go.mod @@ -1,7 +1,11 @@ module github.com/gophercloud/gophercloud +go 1.13 + require ( - golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 - gopkg.in/yaml.v2 v2.2.4 + github.com/kr/pretty v0.2.1 // indirect + golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e + golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 // indirect + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect + gopkg.in/yaml.v2 v2.3.0 ) - diff --git a/vendor/github.com/gophercloud/gophercloud/go.sum b/vendor/github.com/gophercloud/gophercloud/go.sum index 27dc9b3055..311ab0449d 100644 --- a/vendor/github.com/gophercloud/gophercloud/go.sum +++ b/vendor/github.com/gophercloud/gophercloud/go.sum @@ -1,8 +1,19 @@ -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= +golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e h1:egKlR8l7Nu9vHGWbcUV8lqR4987UfUbBd7GbhqGzNYU= +golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU= +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= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/auth_env.go b/vendor/github.com/gophercloud/gophercloud/openstack/auth_env.go index 0e8d90ff82..c801de5553 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/auth_env.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/auth_env.go @@ -38,6 +38,7 @@ func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) { username := os.Getenv("OS_USERNAME") userID := os.Getenv("OS_USERID") password := os.Getenv("OS_PASSWORD") + passcode := os.Getenv("OS_PASSCODE") tenantID := os.Getenv("OS_TENANT_ID") tenantName := os.Getenv("OS_TENANT_NAME") domainID := os.Getenv("OS_DOMAIN_ID") @@ -73,8 +74,9 @@ func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) { } } - if password == "" && applicationCredentialID == "" && applicationCredentialName == "" { + if password == "" && passcode == "" && applicationCredentialID == "" && applicationCredentialName == "" { err := gophercloud.ErrMissingEnvironmentVariable{ + // silently ignore TOTP passcode warning, since it is not a common auth method EnvironmentVariable: "OS_PASSWORD", } return nilOptions, err @@ -112,6 +114,7 @@ func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) { UserID: userID, Username: username, Password: password, + Passcode: passcode, TenantID: tenantID, TenantName: tenantName, DomainID: domainID, diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/doc.go index 307b8b12d2..0b834852dd 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/doc.go @@ -1,5 +1,23 @@ -// Package volumes provides information and interaction with volumes in the -// OpenStack Block Storage service. A volume is a detachable block storage -// device, akin to a USB hard drive. It can only be attached to one instance at -// a time. +/* +Package volumes provides information and interaction with volumes in the +OpenStack Block Storage service. A volume is a detachable block storage +device, akin to a USB hard drive. It can only be attached to one instance at +a time. + +Example to create a Volume from a Backup + + backupID := "20c792f0-bb03-434f-b653-06ef238e337e" + options := volumes.CreateOpts{ + Name: "vol-001", + BackupID: &backupID, + } + + client.Microversion = "3.47" + volume, err := volumes.Create(client, options).Extract() + if err != nil { + panic(err) + } + + fmt.Println(volume) +*/ package volumes diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/requests.go index 25f70b27c1..f6063c5954 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/requests.go @@ -16,7 +16,7 @@ type CreateOptsBuilder interface { // see the Volume object. type CreateOpts struct { // The size of the volume, in GB - Size int `json:"size" required:"true"` + Size int `json:"size,omitempty"` // The availability zone AvailabilityZone string `json:"availability_zone,omitempty"` // ConsistencyGroupID is the ID of a consistency group @@ -36,6 +36,9 @@ type CreateOpts struct { // The ID of the image from which you want to create the volume. // Required to create a bootable volume. ImageID string `json:"imageRef,omitempty"` + // Specifies the backup ID, from which you want to create the volume. + // Create a volume from a backup is supported since 3.47 microversion + BackupID string `json:"backup_id,omitempty"` // The associated volume type VolumeType string `json:"volume_type,omitempty"` // Multiattach denotes if the volume is multi-attach capable. @@ -57,9 +60,10 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create r.Err = err return } - _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{202}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -93,14 +97,16 @@ func Delete(client *gophercloud.ServiceClient, id string, opts DeleteOptsBuilder } url += query } - _, r.Err = client.Delete(url, nil) + resp, err := client.Delete(url, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Get retrieves the Volume with the provided ID. To extract the Volume object // from the response, call the Extract method on the GetResult. func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { - _, r.Err = client.Get(getURL(client, id), &r.Body, nil) + resp, err := client.Get(getURL(client, id), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -194,44 +200,9 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder r.Err = err return } - _, r.Err = client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } - -// IDFromName is a convienience function that returns a server's ID given its name. -func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { - count := 0 - id := "" - - listOpts := ListOpts{ - Name: name, - } - - pages, err := List(client, listOpts).AllPages() - if err != nil { - return "", err - } - - all, err := ExtractVolumes(pages) - if err != nil { - return "", err - } - - for _, s := range all { - if s.Name == name { - count++ - id = s.ID - } - } - - switch count { - case 0: - return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "volume"} - case 1: - return id, nil - default: - return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "volume"} - } -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/results.go index 3a33b5864b..6f46685b6e 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes/results.go @@ -63,6 +63,9 @@ type Volume struct { SnapshotID string `json:"snapshot_id"` // The ID of another block storage volume from which the current volume was created SourceVolID string `json:"source_volid"` + // The backup ID, from which the volume was restored + // This field is supported since 3.47 microversion + BackupID *string `json:"backup_id"` // Arbitrary key-value pairs defined by the user. Metadata map[string]string `json:"metadata"` // UserID is the id of the user who created the volume. diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/client.go b/vendor/github.com/gophercloud/gophercloud/openstack/client.go index e77584157f..655a9f6b91 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/client.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/client.go @@ -3,9 +3,12 @@ package openstack import ( "fmt" "reflect" + "strings" "github.com/gophercloud/gophercloud" tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens" + "github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens" + "github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1" tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens" "github.com/gophercloud/gophercloud/openstack/utils" ) @@ -67,7 +70,7 @@ Example: ao, err := openstack.AuthOptionsFromEnv() provider, err := openstack.AuthenticatedClient(ao) - client, err := openstack.NewNetworkV2(client, gophercloud.EndpointOpts{ + client, err := openstack.NewNetworkV2(provider, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) */ @@ -223,7 +226,15 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.Au return err } } else { - result := tokens3.Create(v3Client, opts) + 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 { @@ -254,6 +265,14 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.Au o := *ot o.AllowReauth = false tao = &o + case *ec2tokens.AuthOptions: + o := *ot + o.AllowReauth = false + tao = &o + case *oauth1.AuthOptions: + o := *ot + o.AllowReauth = false + tao = &o default: tao = opts } @@ -432,7 +451,11 @@ func NewImageServiceV2(client *gophercloud.ProviderClient, eo gophercloud.Endpoi // load balancer service. func NewLoadBalancerV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { sc, err := initClientOpts(client, eo, "load-balancer") - sc.ResourceBase = sc.Endpoint + "v2.0/" + + // Fixes edge case having an OpenStack lb endpoint with trailing version number. + endpoint := strings.Replace(sc.Endpoint, "v2.0/", "", -1) + + sc.ResourceBase = endpoint + "v2.0/" return sc, err } @@ -473,3 +496,8 @@ func NewContainerInfraV1(client *gophercloud.ProviderClient, eo gophercloud.Endp func NewWorkflowV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { return initClientOpts(client, eo, "workflowv2") } + +// NewPlacementV1 creates a ServiceClient that may be used with the placement package. +func NewPlacementV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(client, eo, "placement") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/requests.go index 46b7d60cd6..bb301c7a12 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/common/extensions/requests.go @@ -7,7 +7,8 @@ import ( // Get retrieves information for a specific extension using its alias. func Get(c *gophercloud.ServiceClient, alias string) (r GetResult) { - _, r.Err = c.Get(ExtensionURL(c, alias), &r.Body, nil) + resp, err := c.Get(ExtensionURL(c, alias), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/requests.go index 874f7a61ec..9748ce4ac1 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces/requests.go @@ -14,9 +14,10 @@ func List(client *gophercloud.ServiceClient, serverID string) pagination.Pager { // Get requests details on a single interface attachment by the server and port IDs. func Get(client *gophercloud.ServiceClient, serverID, portID string) (r GetResult) { - _, r.Err = client.Get(getInterfaceURL(client, serverID, portID), &r.Body, &gophercloud.RequestOpts{ + resp, err := client.Get(getInterfaceURL(client, serverID, portID), &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -58,15 +59,17 @@ func Create(client *gophercloud.ServiceClient, serverID string, opts CreateOptsB r.Err = err return } - _, r.Err = client.Post(createInterfaceURL(client, serverID), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := client.Post(createInterfaceURL(client, serverID), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Delete makes a request against the nova API to detach a single interface from the server. // It needs server and port IDs to make a such request. func Delete(client *gophercloud.ServiceClient, serverID, portID string) (r DeleteResult) { - _, r.Err = client.Delete(deleteInterfaceURL(client, serverID, portID), nil) + resp, err := client.Delete(deleteInterfaceURL(client, serverID, portID), nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones/doc.go new file mode 100644 index 0000000000..29b554d213 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones/doc.go @@ -0,0 +1,61 @@ +/* +Package availabilityzones provides the ability to get lists and detailed +availability zone information and to extend a server result with +availability zone information. + +Example of Extend server result with Availability Zone Information: + + type ServerWithAZ struct { + servers.Server + availabilityzones.ServerAvailabilityZoneExt + } + + var allServers []ServerWithAZ + + allPages, err := servers.List(client, nil).AllPages() + if err != nil { + panic("Unable to retrieve servers: %s", err) + } + + err = servers.ExtractServersInto(allPages, &allServers) + if err != nil { + panic("Unable to extract servers: %s", err) + } + + for _, server := range allServers { + fmt.Println(server.AvailabilityZone) + } + +Example of Get Availability Zone Information + + allPages, err := availabilityzones.List(computeClient).AllPages() + if err != nil { + panic(err) + } + + availabilityZoneInfo, err := availabilityzones.ExtractAvailabilityZones(allPages) + if err != nil { + panic(err) + } + + for _, zoneInfo := range availabilityZoneInfo { + fmt.Printf("%+v\n", zoneInfo) + } + +Example of Get Detailed Availability Zone Information + + allPages, err := availabilityzones.ListDetail(computeClient).AllPages() + if err != nil { + panic(err) + } + + availabilityZoneInfo, err := availabilityzones.ExtractAvailabilityZones(allPages) + if err != nil { + panic(err) + } + + for _, zoneInfo := range availabilityZoneInfo { + fmt.Printf("%+v\n", zoneInfo) + } +*/ +package availabilityzones diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones/requests.go new file mode 100644 index 0000000000..f9a2e86e03 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones/requests.go @@ -0,0 +1,20 @@ +package availabilityzones + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// List will return the existing availability zones. +func List(client *gophercloud.ServiceClient) pagination.Pager { + return pagination.NewPager(client, listURL(client), func(r pagination.PageResult) pagination.Page { + return AvailabilityZonePage{pagination.SinglePageBase(r)} + }) +} + +// ListDetail will return the existing availability zones with detailed information. +func ListDetail(client *gophercloud.ServiceClient) pagination.Pager { + return pagination.NewPager(client, listDetailURL(client), func(r pagination.PageResult) pagination.Page { + return AvailabilityZonePage{pagination.SinglePageBase(r)} + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones/results.go new file mode 100644 index 0000000000..d48a0ea858 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones/results.go @@ -0,0 +1,76 @@ +package availabilityzones + +import ( + "encoding/json" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ServerAvailabilityZoneExt is an extension to the base Server object. +type ServerAvailabilityZoneExt struct { + // AvailabilityZone is the availabilty zone the server is in. + AvailabilityZone string `json:"OS-EXT-AZ:availability_zone"` +} + +// ServiceState represents the state of a service in an AvailabilityZone. +type ServiceState struct { + Active bool `json:"active"` + Available bool `json:"available"` + UpdatedAt time.Time `json:"-"` +} + +// UnmarshalJSON to override default +func (r *ServiceState) UnmarshalJSON(b []byte) error { + type tmp ServiceState + var s struct { + tmp + UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = ServiceState(s.tmp) + + r.UpdatedAt = time.Time(s.UpdatedAt) + + return nil +} + +// Services is a map of services contained in an AvailabilityZone. +type Services map[string]ServiceState + +// Hosts is map of hosts/nodes contained in an AvailabilityZone. +// Each host can have multiple services. +type Hosts map[string]Services + +// ZoneState represents the current state of the availability zone. +type ZoneState struct { + // Returns true if the availability zone is available + Available bool `json:"available"` +} + +// AvailabilityZone contains all the information associated with an OpenStack +// AvailabilityZone. +type AvailabilityZone struct { + Hosts Hosts `json:"hosts"` + // The availability zone name + ZoneName string `json:"zoneName"` + ZoneState ZoneState `json:"zoneState"` +} + +type AvailabilityZonePage struct { + pagination.SinglePageBase +} + +// ExtractAvailabilityZones returns a slice of AvailabilityZones contained in a +// single page of results. +func ExtractAvailabilityZones(r pagination.Page) ([]AvailabilityZone, error) { + var s struct { + AvailabilityZoneInfo []AvailabilityZone `json:"availabilityZoneInfo"` + } + err := (r.(AvailabilityZonePage)).ExtractInto(&s) + return s.AvailabilityZoneInfo, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones/urls.go new file mode 100644 index 0000000000..9d99ec74b7 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones/urls.go @@ -0,0 +1,11 @@ +package availabilityzones + +import "github.com/gophercloud/gophercloud" + +func listURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("os-availability-zone") +} + +func listDetailURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("os-availability-zone", "detail") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/requests.go index d2346d4b42..096d8be7ef 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/requests.go @@ -125,8 +125,9 @@ func Create(client *gophercloud.ServiceClient, opts servers.CreateOptsBuilder) ( r.Err = err return } - _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200, 202}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/urls.go index dc007eadf8..e74422d0c1 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/urls.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume/urls.go @@ -3,5 +3,5 @@ package bootfromvolume import "github.com/gophercloud/gophercloud" func createURL(c *gophercloud.ServiceClient) string { - return c.ServiceURL("os-volumes_boot") + return c.ServiceURL("servers") } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips/requests.go index a922639dec..0d8104d24b 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips/requests.go @@ -36,21 +36,24 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create r.Err = err return } - _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Get returns data about a previously created Floating IP. func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { - _, r.Err = client.Get(getURL(client, id), &r.Body, nil) + resp, err := client.Get(getURL(client, id), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Delete requests the deletion of a previous allocated Floating IP. 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 } @@ -81,7 +84,8 @@ func AssociateInstance(client *gophercloud.ServiceClient, serverID string, opts r.Err = err return } - _, r.Err = client.Post(associateURL(client, serverID), b, nil, nil) + resp, err := client.Post(associateURL(client, serverID), b, nil, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -109,6 +113,7 @@ func DisassociateInstance(client *gophercloud.ServiceClient, serverID string, op r.Err = err return } - _, r.Err = client.Post(disassociateURL(client, serverID), b, nil, nil) + resp, err := client.Post(disassociateURL(client, serverID), b, nil, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs/requests.go index b72807770e..39056fdc70 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs/requests.go @@ -67,20 +67,23 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create r.Err = err return } - _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200, 201}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Get returns public data about a previously uploaded KeyPair. func Get(client *gophercloud.ServiceClient, name string) (r GetResult) { - _, r.Err = client.Get(getURL(client, name), &r.Body, nil) + resp, err := client.Get(getURL(client, name), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Delete requests the deletion of a previous stored KeyPair from the server. func Delete(client *gophercloud.ServiceClient, name string) (r DeleteResult) { - _, r.Err = client.Delete(deleteURL(client, name), nil) + resp, err := client.Delete(deleteURL(client, name), nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints/requests.go index 963934507f..9a6324cf1d 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints/requests.go @@ -31,6 +31,9 @@ type SchedulerHints struct { // TargetCell specifies a cell name where the instance will be placed. TargetCell string `json:"target_cell,omitempty"` + // DifferentCell specifies cells names where an instance should not be placed. + DifferentCell []string `json:"different_cell,omitempty"` + // BuildNearHostIP specifies a subnet of compute nodes to host the instance. BuildNearHostIP string @@ -124,6 +127,10 @@ func (opts SchedulerHints) ToServerSchedulerHintsCreateMap() (map[string]interfa sh["target_cell"] = opts.TargetCell } + if len(opts.DifferentCell) > 0 { + sh["different_cell"] = opts.DifferentCell + } + if opts.BuildNearHostIP != "" { if _, _, err := net.ParseCIDR(opts.BuildNearHostIP); err != nil { err := gophercloud.ErrInvalidInput{} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/doc.go index 7811d32ecf..936674b051 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/doc.go @@ -47,16 +47,6 @@ Example to Create a Server Group with additional microversion 2.64 fields panic(err) } - policy, err := servergroups.ExtractPolicy(result.Result) - if err != nil { - panic(err) - } - - rules, err := servergroups.ExtractRules(result.Result) - if err != nil { - panic(err) - } - Example to Delete a Server Group sgID := "7a6f29ad-e34d-4368-951a-58a08f11cfb7" @@ -64,23 +54,5 @@ Example to Delete a Server Group if err != nil { panic(err) } - -Example to get additional fields with microversion 2.64 or later - - computeClient.Microversion = "2.64" - result := servergroups.Get(computeClient, "616fb98f-46ca-475e-917e-2563e5a8cd19") - - policy, err := servergroups.ExtractPolicy(result.Result) - if err != nil { - panic(err) - } - fmt.Printf("Policy: %s\n", policy) - - rules, err := servergroups.ExtractRules(result.Result) - if err != nil { - panic(err) - } - fmt.Printf("Max server per host: %s\n", rules.MaxServerPerHost) - */ package servergroups diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/microversions.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/microversions.go deleted file mode 100644 index 4899d9c572..0000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/microversions.go +++ /dev/null @@ -1,32 +0,0 @@ -package servergroups - -import "github.com/gophercloud/gophercloud" - -// ExtractPolicy will extract the policy attribute. -// This requires the client to be set to microversion 2.64 or later. -func ExtractPolicy(r gophercloud.Result) (string, error) { - var s struct { - Policy string `json:"policy"` - } - err := r.ExtractIntoStructPtr(&s, "server_group") - - return s.Policy, err -} - -// ExtractRules will extract the rules attribute. -// This requires the client to be set to microversion 2.64 or later. -func ExtractRules(r gophercloud.Result) (Rules, error) { - var s struct { - Rules Rules `json:"rules"` - } - err := r.ExtractIntoStructPtr(&s, "server_group") - - return s.Rules, err -} - -// Rules represents set of rules for a policy. -type Rules struct { - // MaxServerPerHost specifies how many servers can reside on a single compute host. - // It can be used only with the "anti-affinity" policy. - MaxServerPerHost int `json:"max_server_per_host,omitempty"` -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/requests.go index 5740afaf05..7a877718d9 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/requests.go @@ -48,20 +48,23 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create r.Err = err return } - _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Get returns data about a previously created ServerGroup. func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { - _, r.Err = client.Get(getURL(client, id), &r.Body, nil) + resp, err := client.Get(getURL(client, id), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Delete requests the deletion of a previously allocated ServerGroup. 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 } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/results.go index fd6f2c6c76..de41f12304 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/results.go @@ -32,6 +32,22 @@ type ServerGroup struct { // Metadata includes a list of all user-specified key-value pairs attached // to the Server Group. Metadata map[string]interface{} + + // Policy is the policy of a server group. + // This requires microversion 2.64 or later. + Policy *string `json:"policy"` + + // Rules are the rules of the server group. + // This requires microversion 2.64 or later. + Rules *Rules `json:"rules"` +} + +// Rules represents set of rules for a policy. +// This requires microversion 2.64 or later. +type Rules struct { + // MaxServerPerHost specifies how many servers can reside on a single compute host. + // It can be used only with the "anti-affinity" policy. + MaxServerPerHost int `json:"max_server_per_host"` } // ServerGroupPage stores a single page of all ServerGroups results from a diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/requests.go index 753024a18b..1b7acd0a7e 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/requests.go @@ -142,22 +142,25 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create r.Err = err return } - _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200, 201}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Get retrieves details of a single flavor. Use Extract to convert its // result into a Flavor. func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { - _, r.Err = client.Get(getURL(client, id), &r.Body, nil) + resp, err := client.Get(getURL(client, id), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Delete deletes the specified flavor ID. func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { - _, r.Err = client.Delete(deleteURL(client, id), nil) + resp, err := client.Delete(deleteURL(client, id), nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -194,9 +197,10 @@ func AddAccess(client *gophercloud.ServiceClient, id string, opts AddAccessOptsB r.Err = err return } - _, r.Err = client.Post(accessActionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := client.Post(accessActionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -224,20 +228,23 @@ func RemoveAccess(client *gophercloud.ServiceClient, id string, opts RemoveAcces r.Err = err return } - _, r.Err = client.Post(accessActionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := client.Post(accessActionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // ExtraSpecs requests all the extra-specs for the given flavor ID. func ListExtraSpecs(client *gophercloud.ServiceClient, flavorID string) (r ListExtraSpecsResult) { - _, r.Err = client.Get(extraSpecsListURL(client, flavorID), &r.Body, nil) + resp, err := client.Get(extraSpecsListURL(client, flavorID), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } func GetExtraSpec(client *gophercloud.ServiceClient, flavorID string, key string) (r GetExtraSpecResult) { - _, r.Err = client.Get(extraSpecsGetURL(client, flavorID, key), &r.Body, nil) + resp, err := client.Get(extraSpecsGetURL(client, flavorID, key), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -264,9 +271,10 @@ func CreateExtraSpecs(client *gophercloud.ServiceClient, flavorID string, opts C r.Err = err return } - _, r.Err = client.Post(extraSpecsCreateURL(client, flavorID), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := client.Post(extraSpecsCreateURL(client, flavorID), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -302,56 +310,19 @@ func UpdateExtraSpec(client *gophercloud.ServiceClient, flavorID string, opts Up r.Err = err return } - _, r.Err = client.Put(extraSpecUpdateURL(client, flavorID, key), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := client.Put(extraSpecUpdateURL(client, flavorID, key), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // DeleteExtraSpec will delete the key-value pair with the given key for the given // flavor ID. func DeleteExtraSpec(client *gophercloud.ServiceClient, flavorID, key string) (r DeleteExtraSpecResult) { - _, r.Err = client.Delete(extraSpecDeleteURL(client, flavorID, key), &gophercloud.RequestOpts{ + resp, err := client.Delete(extraSpecDeleteURL(client, flavorID, key), &gophercloud.RequestOpts{ OkCodes: []int{200}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } - -// IDFromName is a convienience function that returns a flavor's ID given its -// name. -func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { - count := 0 - id := "" - allPages, err := ListDetail(client, nil).AllPages() - if err != nil { - return "", err - } - - all, err := ExtractFlavors(allPages) - if err != nil { - return "", err - } - - for _, f := range all { - if f.Name == name { - count++ - id = f.ID - } - } - - switch count { - case 0: - err := &gophercloud.ErrResourceNotFound{} - err.ResourceType = "flavor" - err.Name = name - return "", err - case 1: - return id, nil - default: - err := &gophercloud.ErrMultipleResourcesFound{} - err.ResourceType = "flavor" - err.Name = name - err.Count = count - return "", err - } -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/doc.go deleted file mode 100644 index 22410a79a2..0000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/doc.go +++ /dev/null @@ -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 diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/requests.go deleted file mode 100644 index 558b481b9e..0000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/requests.go +++ /dev/null @@ -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 - } -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/results.go deleted file mode 100644 index 70d1018c72..0000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/results.go +++ /dev/null @@ -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 -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/urls.go deleted file mode 100644 index 57787fb725..0000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/urls.go +++ /dev/null @@ -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) -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/doc.go index 3926a382c3..3b0ab78362 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/doc.go @@ -111,30 +111,5 @@ Example to Snapshot a Server if err != nil { panic(err) } - -Example of Extend server result with Tags: - - client.Microversion = "2.26" - - type ServerWithTags struct { - servers.Server - servers.TagsExt - } - - var allServers []ServerWithTags - - allPages, err := servers.List(client, nil).AllPages() - if err != nil { - log.Fatal(err) - } - - err = servers.ExtractServersInto(allPages, &allServers) - if err != nil { - log.Fatal(err) - } - - for _, server := range allServers { - fmt.Println(server.Tags) - } */ package servers diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/microversions.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/microversions.go deleted file mode 100644 index b5d4be7506..0000000000 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/microversions.go +++ /dev/null @@ -1,29 +0,0 @@ -package servers - -// TagsExt is an extension to the base Server struct. -// Use this in combination with the Server struct to create -// a composed struct in order to extract a slice of servers -// with the Tag field. -// -// This requires the client to be set to microversion 2.26 or later. -// -// To interact with a server's tags directly, see the -// openstack/compute/v2/extensions/tags package. -type TagsExt struct { - // Tags contains a list of server tags. - Tags []string `json:"tags"` -} - -// ExtractTags will extract the tags of a server. -// -// This requires the client to be set to microversion 2.26 or later. -// -// To interact with a server's tags directly, see the -// openstack/compute/v2/extensions/tags package. -func (r serverResult) ExtractTags() ([]string, error) { - var s struct { - Tags []string `json:"tags"` - } - err := r.ExtractInto(&s) - return s.Tags, err -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/requests.go index 75103b2eb5..4e6042409e 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/requests.go @@ -3,10 +3,9 @@ package servers import ( "encoding/base64" "encoding/json" + "fmt" "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors" - "github.com/gophercloud/gophercloud/openstack/compute/v2/images" "github.com/gophercloud/gophercloud/pagination" ) @@ -30,6 +29,14 @@ type ListOpts struct { // Flavor is the name of the flavor in URL format. Flavor string `q:"flavor"` + // IP is a regular expression to match the IPv4 address of the server. + IP string `q:"ip"` + + // This requires the client to be set to microversion 2.5 or later, unless + // the user is an admin. + // IP is a regular expression to match the IPv6 address of the server. + IP6 string `q:"ip6"` + // Name of the server as a string; can be queried with regular expressions. // Realize that ?name=bob returns both bob and bobb. If you need to match bob // only, you can use a regular expression matching the syntax of the @@ -56,6 +63,11 @@ type ListOpts struct { // Setting "AllTenants = true" is required. TenantID string `q:"tenant_id"` + // This requires the client to be set to microversion 2.83 or later, unless + // the user is an admin. + // UserID lists servers for a particular user. + UserID string `q:"user_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"` @@ -147,24 +159,14 @@ type CreateOpts struct { // Name is the name to assign to the newly launched server. Name string `json:"name" required:"true"` - // ImageRef [optional; required if ImageName is not provided] is the ID or - // full URL to the image that contains the server's OS and initial state. + // ImageRef is the ID or full URL to the image that contains the + // server's OS and initial state. // Also optional if using the boot-from-volume extension. ImageRef string `json:"imageRef"` - // ImageName [optional; required if ImageRef is not provided] is the name of - // the image that contains the server's OS and initial state. - // Also optional if using the boot-from-volume extension. - ImageName string `json:"-"` - - // FlavorRef [optional; required if FlavorName is not provided] is the ID or - // full URL to the flavor that describes the server's specs. + // FlavorRef is the ID or full URL to the flavor that describes the server's specs. FlavorRef string `json:"flavorRef"` - // FlavorName [optional; required if FlavorRef is not provided] is the name of - // the flavor that describes the server's specs. - FlavorName string `json:"-"` - // SecurityGroups lists the names of the security groups to which this server // should belong. SecurityGroups []string `json:"-"` @@ -179,7 +181,9 @@ type CreateOpts struct { // Networks dictates how this server will be attached to available networks. // By default, the server will be attached to all isolated networks for the // tenant. - Networks []Network `json:"-"` + // Starting with microversion 2.37 networks can also be an "auto" or "none" + // string. + Networks interface{} `json:"-"` // Metadata contains key-value pairs (up to 255 bytes each) to attach to the // server. @@ -208,10 +212,6 @@ type CreateOpts struct { // Max specifies Maximum number of servers to launch. Max int `json:"max_count,omitempty"` - // ServiceClient will allow calls to be made to retrieve an image or - // flavor ID by name. - ServiceClient *gophercloud.ServiceClient `json:"-"` - // Tags allows a server to be tagged with single-word metadata. // Requires microversion 2.52 or later. Tags []string `json:"tags,omitempty"` @@ -220,8 +220,6 @@ type CreateOpts struct { // ToServerCreateMap assembles a request body based on the contents of a // CreateOpts. func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) { - sc := opts.ServiceClient - opts.ServiceClient = nil b, err := gophercloud.BuildRequestBody(opts, "") if err != nil { return nil, err @@ -245,57 +243,30 @@ func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) { b["security_groups"] = securityGroups } - if len(opts.Networks) > 0 { - networks := make([]map[string]interface{}, len(opts.Networks)) - for i, net := range opts.Networks { - networks[i] = make(map[string]interface{}) - if net.UUID != "" { - networks[i]["uuid"] = net.UUID - } - if net.Port != "" { - networks[i]["port"] = net.Port - } - if net.FixedIP != "" { - networks[i]["fixed_ip"] = net.FixedIP - } - } - b["networks"] = networks - } - - // If ImageRef isn't provided, check if ImageName was provided to ascertain - // the image ID. - if opts.ImageRef == "" { - if opts.ImageName != "" { - if sc == nil { - err := ErrNoClientProvidedForIDByName{} - err.Argument = "ServiceClient" - return nil, err - } - imageID, err := images.IDFromName(sc, opts.ImageName) - if err != nil { - return nil, err + switch v := opts.Networks.(type) { + case []Network: + if len(v) > 0 { + networks := make([]map[string]interface{}, len(v)) + for i, net := range v { + networks[i] = make(map[string]interface{}) + if net.UUID != "" { + networks[i]["uuid"] = net.UUID + } + if net.Port != "" { + networks[i]["port"] = net.Port + } + if net.FixedIP != "" { + networks[i]["fixed_ip"] = net.FixedIP + } } - b["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 + b["networks"] = networks } - if sc == nil { - err := ErrNoClientProvidedForIDByName{} - err.Argument = "ServiceClient" - return nil, err - } - flavorID, err := flavors.IDFromName(sc, opts.FlavorName) - if err != nil { - return nil, err + case string: + if v == "auto" || v == "none" { + b["networks"] = v + } else { + return nil, fmt.Errorf(`networks must be a slice of Network struct or a string with "auto" or "none" values, current value is %q`, v) } - b["flavorRef"] = flavorID } if opts.Min != 0 { @@ -316,28 +287,32 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create r.Err = err return } - _, r.Err = client.Post(listURL(client), reqBody, &r.Body, nil) + resp, err := client.Post(listURL(client), reqBody, &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Delete requests that a server previously provisioned be removed from your // account. func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { - _, r.Err = client.Delete(deleteURL(client, id), nil) + resp, err := client.Delete(deleteURL(client, id), nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // ForceDelete forces the deletion of a server. func ForceDelete(client *gophercloud.ServiceClient, id string) (r ActionResult) { - _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"forceDelete": ""}, nil, nil) + resp, err := client.Post(actionURL(client, id), map[string]interface{}{"forceDelete": ""}, nil, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Get requests details on a single server, by ID. func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { - _, r.Err = client.Get(getURL(client, id), &r.Body, &gophercloud.RequestOpts{ + resp, err := client.Get(getURL(client, id), &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200, 203}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -374,9 +349,10 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder r.Err = err return } - _, r.Err = client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -388,7 +364,8 @@ func ChangeAdminPassword(client *gophercloud.ServiceClient, id, newPassword stri "adminPass": newPassword, }, } - _, r.Err = client.Post(actionURL(client, id), b, nil, nil) + resp, err := client.Post(actionURL(client, id), b, nil, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -429,7 +406,7 @@ func (opts RebootOpts) ToServerRebootMap() (map[string]interface{}, error) { HardReboot (aka PowerCycle) starts the server instance by physically cutting power to the machine, or if a VM, terminating it at the hypervisor level. It's done. Caput. Full stop. - Then, after a brief while, power is rtored or the VM instance restarted. + Then, after a brief while, power is restored or the VM instance restarted. SoftReboot (aka OSReboot) simply tells the OS to restart under its own procedure. @@ -442,7 +419,8 @@ func Reboot(client *gophercloud.ServiceClient, id string, opts RebootOptsBuilder r.Err = err return } - _, r.Err = client.Post(actionURL(client, id), b, nil, nil) + resp, err := client.Post(actionURL(client, id), b, nil, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -458,11 +436,8 @@ type RebuildOpts struct { // AdminPass is the server's admin password AdminPass string `json:"adminPass,omitempty"` - // ImageID is the ID of the image you want your server to be provisioned on. - ImageID string `json:"imageRef"` - - // ImageName is readable name of an image. - ImageName string `json:"-"` + // ImageRef is the ID of the image you want your server to be provisioned on. + ImageRef string `json:"imageRef"` // Name to set the server to Name string `json:"name,omitempty"` @@ -480,10 +455,6 @@ type RebuildOpts struct { // Personality [optional] includes files to inject into the server at launch. // Rebuild will base64-encode file contents for you. Personality Personality `json:"personality,omitempty"` - - // ServiceClient will allow calls to be made to retrieve an image or - // flavor ID by name. - ServiceClient *gophercloud.ServiceClient `json:"-"` } // ToServerRebuildMap formats a RebuildOpts struct into a map for use in JSON @@ -493,23 +464,6 @@ func (opts RebuildOpts) ToServerRebuildMap() (map[string]interface{}, error) { return nil, err } - // If ImageRef isn't provided, check if ImageName was provided to ascertain - // the image ID. - if opts.ImageID == "" { - if opts.ImageName != "" { - if opts.ServiceClient == nil { - err := ErrNoClientProvidedForIDByName{} - err.Argument = "ServiceClient" - return nil, err - } - imageID, err := images.IDFromName(opts.ServiceClient, opts.ImageName) - if err != nil { - return nil, err - } - b["imageRef"] = imageID - } - } - return map[string]interface{}{"rebuild": b}, nil } @@ -521,7 +475,8 @@ func Rebuild(client *gophercloud.ServiceClient, id string, opts RebuildOptsBuild r.Err = err return } - _, r.Err = client.Post(actionURL(client, id), b, &r.Body, nil) + resp, err := client.Post(actionURL(client, id), b, &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -559,23 +514,26 @@ func Resize(client *gophercloud.ServiceClient, id string, opts ResizeOptsBuilder r.Err = err return } - _, r.Err = client.Post(actionURL(client, id), b, nil, nil) + resp, err := client.Post(actionURL(client, id), b, nil, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // ConfirmResize confirms a previous resize operation on a server. // See Resize() for more details. func ConfirmResize(client *gophercloud.ServiceClient, id string) (r ActionResult) { - _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"confirmResize": nil}, nil, &gophercloud.RequestOpts{ + resp, err := client.Post(actionURL(client, id), map[string]interface{}{"confirmResize": nil}, nil, &gophercloud.RequestOpts{ OkCodes: []int{201, 202, 204}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // RevertResize cancels a previous resize operation on a server. // See Resize() for more details. func RevertResize(client *gophercloud.ServiceClient, id string) (r ActionResult) { - _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"revertResize": nil}, nil, nil) + resp, err := client.Post(actionURL(client, id), map[string]interface{}{"revertResize": nil}, nil, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -611,15 +569,17 @@ func ResetMetadata(client *gophercloud.ServiceClient, id string, opts ResetMetad r.Err = err return } - _, r.Err = client.Put(metadataURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := client.Put(metadataURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Metadata requests all the metadata for the given server ID. func Metadata(client *gophercloud.ServiceClient, id string) (r GetMetadataResult) { - _, r.Err = client.Get(metadataURL(client, id), &r.Body, nil) + resp, err := client.Get(metadataURL(client, id), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -638,9 +598,10 @@ func UpdateMetadata(client *gophercloud.ServiceClient, id string, opts UpdateMet r.Err = err return } - _, r.Err = client.Post(metadataURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := client.Post(metadataURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -678,23 +639,26 @@ func CreateMetadatum(client *gophercloud.ServiceClient, id string, opts Metadatu r.Err = err return } - _, r.Err = client.Put(metadatumURL(client, id, key), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := client.Put(metadatumURL(client, id, key), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Metadatum requests the key-value pair with the given key for the given // server ID. func Metadatum(client *gophercloud.ServiceClient, id, key string) (r GetMetadatumResult) { - _, r.Err = client.Get(metadatumURL(client, id, key), &r.Body, nil) + resp, err := client.Get(metadatumURL(client, id, key), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // DeleteMetadatum will delete the key-value pair with the given key for the // given server ID. func DeleteMetadatum(client *gophercloud.ServiceClient, id, key string) (r DeleteMetadatumResult) { - _, r.Err = client.Delete(metadatumURL(client, id, key), nil) + resp, err := client.Delete(metadatumURL(client, id, key), nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -747,52 +711,15 @@ func CreateImage(client *gophercloud.ServiceClient, id string, opts CreateImageO resp, err := client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{ OkCodes: []int{202}, }) - r.Err = err - r.Header = resp.Header + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } -// IDFromName is a convienience function that returns a server's ID given its -// name. -func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { - count := 0 - id := "" - - listOpts := ListOpts{ - Name: name, - } - - allPages, err := List(client, listOpts).AllPages() - if err != nil { - return "", err - } - - all, err := ExtractServers(allPages) - if err != nil { - return "", err - } - - for _, f := range all { - if f.Name == name { - count++ - id = f.ID - } - } - - switch count { - case 0: - return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "server"} - case 1: - return id, nil - default: - return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "server"} - } -} - // GetPassword makes a request against the nova API to get the encrypted // administrative password. func GetPassword(client *gophercloud.ServiceClient, serverId string) (r GetPasswordResult) { - _, r.Err = client.Get(passwordURL(client, serverId), &r.Body, nil) + resp, err := client.Get(passwordURL(client, serverId), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -821,8 +748,9 @@ func ShowConsoleOutput(client *gophercloud.ServiceClient, id string, opts ShowCo r.Err = err return } - _, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/results.go index cec633e77a..b3028be5d5 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/results.go @@ -217,6 +217,10 @@ type Server struct { // Fault contains failure information about a server. Fault Fault `json:"fault"` + + // Tags is a slice/list of string tags in a server. + // The requires microversion 2.26 or later. + Tags *[]string `json:"tags"` } type AttachedVolume struct { diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/doc.go index cedf1f4d3a..af4bd512bf 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/doc.go @@ -7,7 +7,7 @@ Example of Creating a Service Client ao, err := openstack.AuthOptionsFromEnv() provider, err := openstack.AuthenticatedClient(ao) - client, err := openstack.NewNetworkV2(client, gophercloud.EndpointOpts{ + client, err := openstack.NewNetworkV2(provider, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) */ diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/endpoint_location.go b/vendor/github.com/gophercloud/gophercloud/openstack/endpoint_location.go index 12c8aebcf7..509700790e 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/endpoint_location.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/endpoint_location.go @@ -29,11 +29,12 @@ func V2EndpointURL(catalog *tokens2.ServiceCatalog, opts gophercloud.EndpointOpt } } - // Report an error if the options were ambiguous. + // If multiple endpoints were found, use the first result + // and disregard the other endpoints. + // + // This behavior matches the Python library. See GH-1764. if len(endpoints) > 1 { - err := &ErrMultipleMatchingEndpointsV2{} - err.Endpoints = endpoints - return "", err + endpoints = endpoints[0:1] } // Extract the appropriate URL from the matching Endpoint. @@ -91,9 +92,12 @@ func V3EndpointURL(catalog *tokens3.ServiceCatalog, opts gophercloud.EndpointOpt } } - // Report an error if the options were ambiguous. + // If multiple endpoints were found, use the first result + // and disregard the other endpoints. + // + // This behavior matches the Python library. See GH-1764. if len(endpoints) > 1 { - return "", ErrMultipleMatchingEndpointsV3{Endpoints: endpoints} + endpoints = endpoints[0:1] } // Extract the URL from the matching Endpoint. diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/errors.go b/vendor/github.com/gophercloud/gophercloud/openstack/errors.go index df410b1c61..cba6ae5f00 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/errors.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/errors.go @@ -4,8 +4,6 @@ import ( "fmt" "github.com/gophercloud/gophercloud" - tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens" - tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens" ) // ErrEndpointNotFound is the error when no suitable endpoint can be found @@ -24,28 +22,6 @@ func (e ErrInvalidAvailabilityProvided) Error() string { return fmt.Sprintf("Unexpected availability in endpoint query: %s", e.Value) } -// ErrMultipleMatchingEndpointsV2 is the error when more than one endpoint -// for the given options is found in the v2 catalog -type ErrMultipleMatchingEndpointsV2 struct { - gophercloud.BaseError - Endpoints []tokens2.Endpoint -} - -func (e ErrMultipleMatchingEndpointsV2) Error() string { - return fmt.Sprintf("Discovered %d matching endpoints: %#v", len(e.Endpoints), e.Endpoints) -} - -// ErrMultipleMatchingEndpointsV3 is the error when more than one endpoint -// for the given options is found in the v3 catalog -type ErrMultipleMatchingEndpointsV3 struct { - gophercloud.BaseError - Endpoints []tokens3.Endpoint -} - -func (e ErrMultipleMatchingEndpointsV3) Error() string { - return fmt.Sprintf("Discovered %d matching endpoints: %#v", len(e.Endpoints), e.Endpoints) -} - // ErrNoAuthURL is the error when the OS_AUTH_URL environment variable is not // found type ErrNoAuthURL struct{ gophercloud.ErrInvalidInput } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/doc.go index 45623369e1..348dd20839 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/doc.go @@ -8,7 +8,7 @@ for more information. Example to List Tenants - listOpts := tenants.ListOpts{ + listOpts := &tenants.ListOpts{ Limit: 2, } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/requests.go index f21a58f10c..f16df38e5e 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/requests.go @@ -60,15 +60,17 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create r.Err = err return } - _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200, 201}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Get requests details on a single tenant by ID. func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { - _, r.Err = client.Get(getURL(client, id), &r.Body, nil) + resp, err := client.Get(getURL(client, id), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -103,14 +105,16 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder r.Err = err return } - _, r.Err = client.Put(updateURL(client, id), &b, &r.Body, &gophercloud.RequestOpts{ + resp, err := client.Put(updateURL(client, id), &b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Delete is the operation responsible for permanently deleting a tenant. func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { - _, r.Err = client.Delete(deleteURL(client, id), nil) + resp, err := client.Delete(deleteURL(client, id), nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/requests.go index ab32368cc6..2b64f108cb 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/requests.go @@ -87,17 +87,19 @@ func Create(client *gophercloud.ServiceClient, auth AuthOptionsBuilder) (r Creat r.Err = err return } - _, r.Err = client.Post(CreateURL(client), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := client.Post(CreateURL(client), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200, 203}, MoreHeaders: map[string]string{"X-Auth-Token": ""}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Get validates and retrieves information for user's token. func Get(client *gophercloud.ServiceClient, token string) (r GetResult) { - _, r.Err = client.Get(GetURL(client, token), &r.Body, &gophercloud.RequestOpts{ + resp, err := client.Get(GetURL(client, token), &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200, 203}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens/doc.go new file mode 100644 index 0000000000..1f6f807fe0 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens/doc.go @@ -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 diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens/requests.go new file mode 100644 index 0000000000..32ba0e621d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens/requests.go @@ -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) + } + } + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens/urls.go new file mode 100644 index 0000000000..84b33b282e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens/urls.go @@ -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") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1/doc.go new file mode 100644 index 0000000000..c5b0831ca1 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1/doc.go @@ -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 diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1/requests.go new file mode 100644 index 0000000000..028b5a45bd --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1/requests.go @@ -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, ", ") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1/results.go new file mode 100644 index 0000000000..a67f9381d6 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1/results.go @@ -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"` +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1/urls.go new file mode 100644 index 0000000000..9b51d53b31 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1/urls.go @@ -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") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go index e4d766b232..d8c455d160 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go @@ -8,6 +8,7 @@ type Scope struct { ProjectName string DomainID string DomainName string + System bool } // AuthOptionsBuilder provides the ability for extensions to add additional @@ -16,6 +17,7 @@ type AuthOptionsBuilder interface { // ToTokenV3CreateMap assembles the Create request body, returning an error // if parameters are missing or inconsistent. ToTokenV3CreateMap(map[string]interface{}) (map[string]interface{}, error) + ToTokenV3HeadersMap(map[string]interface{}) (map[string]string, error) ToTokenV3ScopeMap() (map[string]interface{}, error) CanReauth() bool } @@ -36,6 +38,9 @@ type AuthOptions struct { Password string `json:"password,omitempty"` + // Passcode is used in TOTP authentication method + Passcode string `json:"passcode,omitempty"` + // At most one of DomainID and DomainName must be provided if using Username // with Identity V3. Otherwise, either are optional. DomainID string `json:"-"` @@ -67,6 +72,7 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s Username: opts.Username, UserID: opts.UserID, Password: opts.Password, + Passcode: opts.Passcode, DomainID: opts.DomainID, DomainName: opts.DomainName, AllowReauth: opts.AllowReauth, @@ -79,7 +85,7 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s return gophercloudAuthOpts.ToTokenV3CreateMap(scope) } -// ToTokenV3CreateMap builds a scope request body from AuthOptions. +// ToTokenV3ScopeMap builds a scope request body from AuthOptions. func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) { scope := gophercloud.AuthScope(opts.Scope) @@ -93,10 +99,21 @@ func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) { } func (opts *AuthOptions) CanReauth() bool { + if opts.Passcode != "" { + // cannot reauth using TOTP passcode + return false + } + return opts.AllowReauth } -func subjectTokenHeaders(c *gophercloud.ServiceClient, subjectToken string) map[string]string { +// ToTokenV3HeadersMap allows AuthOptions to satisfy the AuthOptionsBuilder +// interface in the v3 tokens package. +func (opts *AuthOptions) ToTokenV3HeadersMap(map[string]interface{}) (map[string]string, error) { + return nil, nil +} + +func subjectTokenHeaders(subjectToken string) map[string]string { return map[string]string{ "X-Subject-Token": subjectToken, } @@ -120,30 +137,24 @@ func Create(c *gophercloud.ServiceClient, opts AuthOptionsBuilder) (r CreateResu resp, err := c.Post(tokenURL(c), b, &r.Body, &gophercloud.RequestOpts{ MoreHeaders: map[string]string{"X-Auth-Token": ""}, }) - r.Err = err - if resp != nil { - r.Header = resp.Header - } + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Get validates and retrieves information about another token. func Get(c *gophercloud.ServiceClient, token string) (r GetResult) { resp, err := c.Get(tokenURL(c), &r.Body, &gophercloud.RequestOpts{ - MoreHeaders: subjectTokenHeaders(c, token), + MoreHeaders: subjectTokenHeaders(token), OkCodes: []int{200, 203}, }) - if resp != nil { - r.Header = resp.Header - } - r.Err = err + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Validate determines if a specified token is valid or not. func Validate(c *gophercloud.ServiceClient, token string) (bool, error) { resp, err := c.Head(tokenURL(c), &gophercloud.RequestOpts{ - MoreHeaders: subjectTokenHeaders(c, token), + MoreHeaders: subjectTokenHeaders(token), OkCodes: []int{200, 204, 404}, }) if err != nil { @@ -155,8 +166,9 @@ func Validate(c *gophercloud.ServiceClient, token string) (bool, error) { // Revoke immediately makes specified token invalid. func Revoke(c *gophercloud.ServiceClient, token string) (r RevokeResult) { - _, r.Err = c.Delete(tokenURL(c), &gophercloud.RequestOpts{ - MoreHeaders: subjectTokenHeaders(c, token), + resp, err := c.Delete(tokenURL(c), &gophercloud.RequestOpts{ + MoreHeaders: subjectTokenHeaders(token), }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/requests.go index 4e487ea9e6..f0cd5cbde8 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/requests.go @@ -206,19 +206,22 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create r.Err = err return r } - _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{OkCodes: []int{201}}) + resp, err := client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{OkCodes: []int{201}}) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Delete implements image delete request. func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { - _, r.Err = client.Delete(deleteURL(client, id), nil) + resp, err := client.Delete(deleteURL(client, id), nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Get implements image get request. func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { - _, r.Err = client.Get(getURL(client, id), &r.Body, nil) + resp, err := client.Get(getURL(client, id), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -229,10 +232,11 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder r.Err = err return r } - _, r.Err = client.Patch(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := client.Patch(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, MoreHeaders: map[string]string{"Content-Type": "application/openstack-images-v2.1-json-patch"}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -335,6 +339,20 @@ func (r ReplaceImageMinDisk) ToImagePatchMap() map[string]interface{} { } } +// ReplaceImageMinRam represents an updated min_ram property request. +type ReplaceImageMinRam struct { + NewMinRam int +} + +// ToImagePatchMap assembles a request body based on ReplaceImageTags. +func (r ReplaceImageMinRam) ToImagePatchMap() map[string]interface{} { + return map[string]interface{}{ + "op": "replace", + "path": "/min_ram", + "value": r.NewMinRam, + } +} + // UpdateOp represents a valid update operation. type UpdateOp string @@ -358,7 +376,7 @@ func (r UpdateImageProperty) ToImagePatchMap() map[string]interface{} { "path": fmt.Sprintf("/%s", r.Name), } - if r.Value != "" { + if r.Op != RemoveOp { updateMap["value"] = r.Value } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/results.go index 676181e1f4..f445cc38fc 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/results.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "reflect" + "strings" "time" "github.com/gophercloud/gophercloud" @@ -86,13 +87,22 @@ type Image struct { // VirtualSize is the virtual size of the image VirtualSize int64 `json:"virtual_size"` + + // OpenStackImageImportMethods is a slice listing the types of import + // methods available in the cloud. + OpenStackImageImportMethods []string `json:"-"` + // OpenStackImageStoreIDs is a slice listing the store IDs available in + // the cloud. + OpenStackImageStoreIDs []string `json:"-"` } func (r *Image) UnmarshalJSON(b []byte) error { type tmp Image var s struct { tmp - SizeBytes interface{} `json:"size"` + SizeBytes interface{} `json:"size"` + OpenStackImageImportMethods string `json:"openstack-image-import-methods"` + OpenStackImageStoreIDs string `json:"openstack-image-store-ids"` } err := json.Unmarshal(b, &s) if err != nil { @@ -120,9 +130,18 @@ func (r *Image) UnmarshalJSON(b []byte) error { if resultMap, ok := result.(map[string]interface{}); ok { delete(resultMap, "self") delete(resultMap, "size") + delete(resultMap, "openstack-image-import-methods") + delete(resultMap, "openstack-image-store-ids") r.Properties = internal.RemainingKeys(Image{}, resultMap) } + if v := strings.FieldsFunc(strings.TrimSpace(s.OpenStackImageImportMethods), splitFunc); len(v) > 0 { + r.OpenStackImageImportMethods = v + } + if v := strings.FieldsFunc(strings.TrimSpace(s.OpenStackImageStoreIDs), splitFunc); len(v) > 0 { + r.OpenStackImageStoreIDs = v + } + return err } @@ -133,6 +152,20 @@ type commonResult struct { // Extract interprets any commonResult as an Image. func (r commonResult) Extract() (*Image, error) { var s *Image + if v, ok := r.Body.(map[string]interface{}); ok { + for k, h := range r.Header { + if strings.ToLower(k) == "openstack-image-import-methods" { + for _, s := range h { + v["openstack-image-import-methods"] = s + } + } + if strings.ToLower(k) == "openstack-image-store-ids" { + for _, s := range h { + v["openstack-image-store-ids"] = s + } + } + } + } err := r.ExtractInto(&s) return s, err } @@ -200,3 +233,8 @@ func ExtractImages(r pagination.Page) ([]Image, error) { err := (r.(ImagePage)).ExtractInto(&s) return s.Images, err } + +// splitFunc is a helper function used to avoid a slice of empty strings. +func splitFunc(c rune) bool { + return c == ',' +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/requests.go index a08bccbb68..6d49aeaf6c 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags/requests.go @@ -29,53 +29,59 @@ func ReplaceAll(client *gophercloud.ServiceClient, resourceType string, resource r.Err = err return } - _, r.Err = client.Put(url, &b, &r.Body, &gophercloud.RequestOpts{ + resp, err := client.Put(url, &b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // List all tags on a resource func List(client *gophercloud.ServiceClient, resourceType string, resourceID string) (r ListResult) { url := listURL(client, resourceType, resourceID) - _, r.Err = client.Get(url, &r.Body, &gophercloud.RequestOpts{ + resp, err := client.Get(url, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // DeleteAll deletes all tags on a resource func DeleteAll(client *gophercloud.ServiceClient, resourceType string, resourceID string) (r DeleteResult) { url := deleteAllURL(client, resourceType, resourceID) - _, r.Err = client.Delete(url, &gophercloud.RequestOpts{ + resp, err := client.Delete(url, &gophercloud.RequestOpts{ OkCodes: []int{204}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Add a tag on a resource func Add(client *gophercloud.ServiceClient, resourceType string, resourceID string, tag string) (r AddResult) { url := addURL(client, resourceType, resourceID, tag) - _, r.Err = client.Put(url, nil, nil, &gophercloud.RequestOpts{ + resp, err := client.Put(url, nil, nil, &gophercloud.RequestOpts{ OkCodes: []int{201}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Delete a tag on a resource func Delete(client *gophercloud.ServiceClient, resourceType string, resourceID string, tag string) (r DeleteResult) { url := deleteURL(client, resourceType, resourceID, tag) - _, r.Err = client.Delete(url, &gophercloud.RequestOpts{ + resp, err := client.Delete(url, &gophercloud.RequestOpts{ OkCodes: []int{204}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Confirm if a tag exists on a resource func Confirm(client *gophercloud.ServiceClient, resourceType string, resourceID string, tag string) (r ConfirmResult) { url := confirmURL(client, resourceType, resourceID, tag) - _, r.Err = client.Get(url, nil, &gophercloud.RequestOpts{ + resp, err := client.Get(url, nil, &gophercloud.RequestOpts{ OkCodes: []int{204}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/doc.go index 6ede7f5e17..fb20e2ef54 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/doc.go @@ -48,7 +48,20 @@ Example to Update a Router updateOpts := routers.UpdateOpts{ Name: "new_name", - Routes: routes, + Routes: &routes, + } + + router, err := routers.Update(networkClient, routerID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update just the Router name, keeping everything else as-is + + routerID := "4e8e5957-649f-477b-9e5b-f1f75b21c03c" + + updateOpts := routers.UpdateOpts{ + Name: "new_name", } router, err := routers.Update(networkClient, routerID, updateOpts).Extract() @@ -63,7 +76,7 @@ Example to Remove all Routes from a Router routes := []routers.Route{} updateOpts := routers.UpdateOpts{ - Routes: routes, + Routes: &routes, } router, err := routers.Update(networkClient, routerID, updateOpts).Extract() @@ -104,5 +117,23 @@ Example to Remove an Interface from a Router if err != nil { panic(err) } + +Example to List an L3 agents for a Router + + routerID := "4e8e5957-649f-477b-9e5b-f1f75b21c03c" + + allPages, err := routers.ListL3Agents(networkClient, routerID).AllPages() + if err != nil { + panic(err) + } + + allL3Agents, err := routers.ExtractL3Agents(allPages) + if err != nil { + panic(err) + } + + for _, agent := range allL3Agents { + fmt.Printf("%+v\n", agent) + } */ package routers diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/requests.go index cf499f9873..81665acef4 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/requests.go @@ -84,13 +84,15 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul r.Err = err return } - _, r.Err = c.Post(rootURL(c), b, &r.Body, nil) + resp, err := c.Post(rootURL(c), b, &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Get retrieves a particular router based on its unique ID. func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { - _, r.Err = c.Get(resourceURL(c, id), &r.Body, nil) + resp, err := c.Get(resourceURL(c, id), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -107,7 +109,7 @@ type UpdateOpts struct { AdminStateUp *bool `json:"admin_state_up,omitempty"` Distributed *bool `json:"distributed,omitempty"` GatewayInfo *GatewayInfo `json:"external_gateway_info,omitempty"` - Routes []Route `json:"routes"` + Routes *[]Route `json:"routes,omitempty"` } // ToRouterUpdateMap builds an update body based on UpdateOpts. @@ -126,15 +128,17 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r r.Err = err return } - _, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Delete will permanently delete a particular router based on its unique ID. 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 } @@ -182,9 +186,10 @@ func AddInterface(c *gophercloud.ServiceClient, id string, opts AddInterfaceOpts r.Err = err return } - _, r.Err = c.Put(addInterfaceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := c.Put(addInterfaceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -226,8 +231,16 @@ func RemoveInterface(c *gophercloud.ServiceClient, id string, opts RemoveInterfa r.Err = err return } - _, r.Err = c.Put(removeInterfaceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := c.Put(removeInterfaceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } + +// ListL3Agents returns a list of l3-agents scheduled for a specific router. +func ListL3Agents(c *gophercloud.ServiceClient, id string) (result pagination.Pager) { + return pagination.NewPager(c, listl3AgentsURL(c, id), func(r pagination.PageResult) pagination.Page { + return ListL3AgentsPage{pagination.SinglePageBase(r)} + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/results.go index 857e1947e1..a6c93cfd0a 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/results.go @@ -1,6 +1,9 @@ package routers import ( + "encoding/json" + "time" + "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/pagination" ) @@ -179,3 +182,96 @@ func (r InterfaceResult) Extract() (*InterfaceInfo, error) { err := r.ExtractInto(&s) return &s, err } + +// L3Agent represents a Neutron agent for routers. +type L3Agent struct { + // ID is the id of the agent. + ID string `json:"id"` + + // AdminStateUp is an administrative state of the agent. + AdminStateUp bool `json:"admin_state_up"` + + // AgentType is a type of the agent. + AgentType string `json:"agent_type"` + + // Alive indicates whether agent is alive or not. + Alive bool `json:"alive"` + + // ResourcesSynced indicates whether agent is synced or not. + // Not all agent types track resources via Placement. + ResourcesSynced bool `json:"resources_synced"` + + // AvailabilityZone is a zone of the agent. + AvailabilityZone string `json:"availability_zone"` + + // Binary is an executable binary of the agent. + Binary string `json:"binary"` + + // Configurations is a configuration specific key/value pairs that are + // determined by the agent binary and type. + Configurations map[string]interface{} `json:"configurations"` + + // CreatedAt is a creation timestamp. + CreatedAt time.Time `json:"-"` + + // StartedAt is a starting timestamp. + StartedAt time.Time `json:"-"` + + // HeartbeatTimestamp is a last heartbeat timestamp. + HeartbeatTimestamp time.Time `json:"-"` + + // Description contains agent description. + Description string `json:"description"` + + // Host is a hostname of the agent system. + Host string `json:"host"` + + // Topic contains name of AMQP topic. + Topic string `json:"topic"` + + // HAState is a ha state of agent(active/standby) for router + HAState string `json:"ha_state"` + + // ResourceVersions is a list agent known objects and version numbers + ResourceVersions map[string]interface{} `json:"resource_versions"` +} + +// UnmarshalJSON helps to convert the timestamps into the time.Time type. +func (r *L3Agent) UnmarshalJSON(b []byte) error { + type tmp L3Agent + var s struct { + tmp + CreatedAt gophercloud.JSONRFC3339ZNoTNoZ `json:"created_at"` + StartedAt gophercloud.JSONRFC3339ZNoTNoZ `json:"started_at"` + HeartbeatTimestamp gophercloud.JSONRFC3339ZNoTNoZ `json:"heartbeat_timestamp"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = L3Agent(s.tmp) + + r.CreatedAt = time.Time(s.CreatedAt) + r.StartedAt = time.Time(s.StartedAt) + r.HeartbeatTimestamp = time.Time(s.HeartbeatTimestamp) + + return nil +} + +type ListL3AgentsPage struct { + pagination.SinglePageBase +} + +func (r ListL3AgentsPage) IsEmpty() (bool, error) { + v, err := ExtractL3Agents(r) + return len(v) == 0, err +} + +func ExtractL3Agents(r pagination.Page) ([]L3Agent, error) { + var s struct { + L3Agents []L3Agent `json:"agents"` + } + + err := (r.(ListL3AgentsPage)).ExtractInto(&s) + return s.L3Agents, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/urls.go index f9e9da3211..7b30f9033c 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/urls.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/urls.go @@ -19,3 +19,7 @@ func addInterfaceURL(c *gophercloud.ServiceClient, id string) string { func removeInterfaceURL(c *gophercloud.ServiceClient, id string) string { return c.ServiceURL(resourcePath, id, "remove_router_interface") } + +func listl3AgentsURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(resourcePath, id, "l3-agents") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/requests.go index a22cd306e8..566a730eaf 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups/requests.go @@ -76,7 +76,8 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul r.Err = err return } - _, r.Err = c.Post(rootURL(c), b, &r.Body, nil) + resp, err := c.Post(rootURL(c), b, &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -109,58 +110,24 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r return } - _, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Get retrieves a particular security group based on its unique ID. func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { - _, r.Err = c.Get(resourceURL(c, id), &r.Body, nil) + resp, err := c.Get(resourceURL(c, id), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Delete will permanently delete a particular security group based on its // unique ID. 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 } - -// IDFromName is a convenience function that returns a security group'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 := ExtractGroups(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: "security group"} - case 1: - return id, nil - default: - return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "security group"} - } -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/requests.go index c7741ffcd2..544c24d7f3 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules/requests.go @@ -141,19 +141,22 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul r.Err = err return } - _, r.Err = c.Post(rootURL(c), b, &r.Body, nil) + resp, err := c.Post(rootURL(c), b, &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Get retrieves a particular security group rule based on its unique ID. func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { - _, r.Err = c.Get(resourceURL(c, id), &r.Body, nil) + resp, err := c.Get(resourceURL(c, id), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Delete will permanently delete a particular security group rule based on its // unique ID. 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 } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/doc.go index 82496ffd0b..879e00070e 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/doc.go @@ -14,7 +14,7 @@ Example of a new empty Trunk creation PortID: "a6f0560c-b7a8-401f-bf6e-d0a5c851ae10", } - trunk, err := trunks.Create(networkClient, trunkOpts).Extract() + trunk, err := trunks.Create(networkClient, createOpts).Extract() if err != nil { panic(err) } @@ -42,7 +42,7 @@ Example of a new Trunk creation with 2 subports }, } - trunk, err := trunks.Create(client, trunkOpts).Extract() + trunk, err := trunks.Create(client, createOpts).Extract() if err != nil { panic(err) } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/requests.go index 447a0d4113..d9037b141d 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks/requests.go @@ -37,13 +37,15 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul return } - _, r.Err = c.Post(createURL(c), body, &r.Body, nil) + resp, err := c.Post(createURL(c), body, &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Delete accepts a unique ID and deletes the trunk associated with it. func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { - _, r.Err = c.Delete(deleteURL(c, id), nil) + resp, err := c.Delete(deleteURL(c, id), nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -105,7 +107,8 @@ func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { // Get retrieves a specific trunk based on its unique ID. func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { - _, r.Err = c.Get(getURL(c, id), &r.Body, nil) + resp, err := c.Get(getURL(c, id), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -129,16 +132,18 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r r.Err = err return } - _, r.Err = c.Put(updateURL(c, id), body, &r.Body, &gophercloud.RequestOpts{ + resp, err := c.Put(updateURL(c, id), body, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } func GetSubports(c *gophercloud.ServiceClient, id string) (r GetSubportsResult) { - _, r.Err = c.Get(getSubportsURL(c, id), &r.Body, &gophercloud.RequestOpts{ + resp, err := c.Get(getSubportsURL(c, id), &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -160,9 +165,10 @@ func AddSubports(c *gophercloud.ServiceClient, id string, opts AddSubportsOptsBu r.Err = err return } - _, r.Err = c.Put(addSubportsURL(c, id), body, &r.Body, &gophercloud.RequestOpts{ + resp, err := c.Put(addSubportsURL(c, id), body, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -188,8 +194,9 @@ func RemoveSubports(c *gophercloud.ServiceClient, id string, opts RemoveSubports r.Err = err return } - _, r.Err = c.Put(removeSubportsURL(c, id), body, &r.Body, &gophercloud.RequestOpts{ + resp, err := c.Put(removeSubportsURL(c, id), body, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/requests.go index 8006c48167..7a28ba0d19 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/networks/requests.go @@ -60,7 +60,8 @@ func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { // Get retrieves a specific network based on its unique ID. func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { - _, r.Err = c.Get(getURL(c, id), &r.Body, nil) + resp, err := c.Get(getURL(c, id), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -99,7 +100,8 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul r.Err = err return } - _, r.Err = c.Post(createURL(c), b, &r.Body, nil) + resp, err := c.Post(createURL(c), b, &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -130,51 +132,16 @@ func Update(c *gophercloud.ServiceClient, networkID string, opts UpdateOptsBuild r.Err = err return } - _, r.Err = c.Put(updateURL(c, networkID), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := c.Put(updateURL(c, networkID), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200, 201}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Delete accepts a unique ID and deletes the network associated with it. func Delete(c *gophercloud.ServiceClient, networkID string) (r DeleteResult) { - _, r.Err = c.Delete(deleteURL(c, networkID), nil) + resp, err := c.Delete(deleteURL(c, networkID), nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } - -// IDFromName is a convenience function that returns a network's ID, given -// its name. -func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { - count := 0 - id := "" - - listOpts := ListOpts{ - Name: name, - } - - pages, err := List(client, listOpts).AllPages() - if err != nil { - return "", err - } - - all, err := ExtractNetworks(pages) - if err != nil { - return "", err - } - - for _, s := range all { - if s.Name == name { - count++ - id = s.ID - } - } - - switch count { - case 0: - return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "network"} - case 1: - return id, nil - default: - return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "network"} - } -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/requests.go index f5f7d761ce..4dc1600726 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/ports/requests.go @@ -1,6 +1,10 @@ package ports import ( + "fmt" + "net/url" + "strings" + "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/pagination" ) @@ -36,11 +40,37 @@ type ListOpts struct { TagsAny string `q:"tags-any"` NotTags string `q:"not-tags"` NotTagsAny string `q:"not-tags-any"` + FixedIPs []FixedIPOpts +} + +type FixedIPOpts struct { + IPAddress string + IPAddressSubstr string + SubnetID string +} + +func (f FixedIPOpts) String() string { + var res []string + if f.IPAddress != "" { + res = append(res, fmt.Sprintf("ip_address=%s", f.IPAddress)) + } + if f.IPAddressSubstr != "" { + res = append(res, fmt.Sprintf("ip_address_substr=%s", f.IPAddressSubstr)) + } + if f.SubnetID != "" { + res = append(res, fmt.Sprintf("subnet_id=%s", f.SubnetID)) + } + return strings.Join(res, ",") } // ToPortListQuery formats a ListOpts into a query string. func (opts ListOpts) ToPortListQuery() (string, error) { q, err := gophercloud.BuildQueryString(opts) + params := q.Query() + for _, fixedIP := range opts.FixedIPs { + params.Add("fixed_ips", fixedIP.String()) + } + q = &url.URL{RawQuery: params.Encode()} return q.String(), err } @@ -67,7 +97,8 @@ func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { // Get retrieves a specific port based on its unique ID. func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { - _, r.Err = c.Get(getURL(c, id), &r.Body, nil) + resp, err := c.Get(getURL(c, id), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -106,7 +137,8 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul r.Err = err return } - _, r.Err = c.Post(createURL(c), b, &r.Body, nil) + resp, err := c.Post(createURL(c), b, &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -141,51 +173,16 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r r.Err = err return } - _, r.Err = c.Put(updateURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := c.Put(updateURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200, 201}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Delete accepts a unique ID and deletes the port associated with it. func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { - _, r.Err = c.Delete(deleteURL(c, id), nil) + resp, err := c.Delete(deleteURL(c, id), nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } - -// IDFromName is a convenience function that returns a port'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 := ExtractPorts(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: "port"} - case 1: - return id, nil - default: - return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "port"} - } -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/requests.go index 3e56bf389a..94a5b6b1aa 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/subnets/requests.go @@ -69,7 +69,8 @@ func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { // Get retrieves a specific subnet based on its unique ID. func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { - _, r.Err = c.Get(getURL(c, id), &r.Body, nil) + resp, err := c.Get(getURL(c, id), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -160,7 +161,8 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul r.Err = err return } - _, r.Err = c.Post(createURL(c), b, &r.Body, nil) + resp, err := c.Post(createURL(c), b, &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } @@ -219,51 +221,16 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r r.Err = err return } - _, r.Err = c.Put(updateURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := c.Put(updateURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200, 201}, }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } // Delete accepts a unique ID and deletes the subnet associated with it. func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { - _, r.Err = c.Delete(deleteURL(c, id), nil) + resp, err := c.Delete(deleteURL(c, id), nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } - -// IDFromName is a convenience function that returns a subnet's ID, -// given its name. -func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { - count := 0 - id := "" - - listOpts := ListOpts{ - Name: name, - } - - pages, err := List(client, listOpts).AllPages() - if err != nil { - return "", err - } - - all, err := ExtractSubnets(pages) - if err != nil { - return "", err - } - - for _, s := range all { - if s.Name == name { - count++ - id = s.ID - } - } - - switch count { - case 0: - return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "subnet"} - case 1: - return id, nil - default: - return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "subnet"} - } -} diff --git a/vendor/github.com/gophercloud/gophercloud/pagination/http.go b/vendor/github.com/gophercloud/gophercloud/pagination/http.go index 757295c423..df3503159a 100644 --- a/vendor/github.com/gophercloud/gophercloud/pagination/http.go +++ b/vendor/github.com/gophercloud/gophercloud/pagination/http.go @@ -54,7 +54,8 @@ func PageResultFromParsed(resp *http.Response, body interface{}) PageResult { // Request performs an HTTP request and extracts the http.Response from the result. func Request(client *gophercloud.ServiceClient, headers map[string]string, url string) (*http.Response, error) { return client.Get(url, nil, &gophercloud.RequestOpts{ - MoreHeaders: headers, - OkCodes: []int{200, 204, 300}, + MoreHeaders: headers, + OkCodes: []int{200, 204, 300}, + KeepResponseBody: true, }) } diff --git a/vendor/github.com/gophercloud/gophercloud/params.go b/vendor/github.com/gophercloud/gophercloud/params.go index b9986660cb..219c020a24 100644 --- a/vendor/github.com/gophercloud/gophercloud/params.go +++ b/vendor/github.com/gophercloud/gophercloud/params.go @@ -450,6 +450,8 @@ func BuildHeaders(opts interface{}) (map[string]string, error) { optsMap[tags[0]] = v.String() case reflect.Int: optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10) + case reflect.Int64: + optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10) case reflect.Bool: optsMap[tags[0]] = strconv.FormatBool(v.Bool()) } diff --git a/vendor/github.com/gophercloud/gophercloud/provider_client.go b/vendor/github.com/gophercloud/gophercloud/provider_client.go index 885bf07a7b..53b3ecf27f 100644 --- a/vendor/github.com/gophercloud/gophercloud/provider_client.go +++ b/vendor/github.com/gophercloud/gophercloud/provider_client.go @@ -94,10 +94,32 @@ type ProviderClient struct { // reauthlock represents a set of attributes used to help in the reauthentication process. type reauthlock struct { sync.RWMutex - // This channel is non-nil during reauthentication. It can be used to ask the - // goroutine doing Reauthenticate() for its result. Look at the implementation - // of Reauthenticate() for details. - ongoing chan<- (chan<- error) + ongoing *reauthFuture +} + +// reauthFuture represents future result of the reauthentication process. +// while done channel is not closed, reauthentication is in progress. +// when done channel is closed, err contains the result of reauthentication. +type reauthFuture struct { + done chan struct{} + err error +} + +func newReauthFuture() *reauthFuture { + return &reauthFuture{ + make(chan struct{}), + nil, + } +} + +func (f *reauthFuture) Set(err error) { + f.err = err + close(f.done) +} + +func (f *reauthFuture) Get() error { + <-f.done + return f.err } // AuthenticatedHeaders returns a map of HTTP headers that are common for all @@ -112,9 +134,7 @@ func (client *ProviderClient) AuthenticatedHeaders() (m map[string]string) { ongoing := client.reauthmut.ongoing client.reauthmut.Unlock() if ongoing != nil { - responseChannel := make(chan error) - ongoing <- responseChannel - _ = <-responseChannel + _ = ongoing.Get() } } t := client.Token() @@ -237,21 +257,19 @@ func (client *ProviderClient) Reauthenticate(previousToken string) error { return client.ReauthFunc() } - messages := make(chan (chan<- error)) + future := newReauthFuture() // Check if a Reauthenticate is in progress, or start one if not. client.reauthmut.Lock() ongoing := client.reauthmut.ongoing if ongoing == nil { - client.reauthmut.ongoing = messages + client.reauthmut.ongoing = future } client.reauthmut.Unlock() // If Reauthenticate is running elsewhere, wait for its result. if ongoing != nil { - responseChannel := make(chan error) - ongoing <- responseChannel - return <-responseChannel + return ongoing.Get() } // Perform the actual reauthentication. @@ -264,22 +282,10 @@ func (client *ProviderClient) Reauthenticate(previousToken string) error { // Mark Reauthenticate as finished. client.reauthmut.Lock() + client.reauthmut.ongoing.Set(err) client.reauthmut.ongoing = nil client.reauthmut.Unlock() - // Report result to all other interested goroutines. - // - // This happens in a separate goroutine because another goroutine might have - // acquired a copy of `client.reauthmut.ongoing` before we cleared it, but not - // have come around to sending its request. By answering in a goroutine, we - // can have that goroutine linger until all responseChannels have been sent. - // When GC has collected all sendings ends of the channel, our receiving end - // will be closed and the goroutine will end. - go func() { - for responseChannel := range messages { - responseChannel <- err - } - }() return err } @@ -305,6 +311,9 @@ type RequestOpts struct { // ErrorContext specifies the resource error type to return if an error is encountered. // This lets resources override default error messages based on the response status code. ErrorContext error + // KeepResponseBody specifies whether to keep the HTTP response body. Usually used, when the HTTP + // response body is considered for further use. Valid when JSONResponse is nil. + KeepResponseBody bool } // requestState contains temporary state for a single ProviderClient.Request() call. @@ -346,6 +355,11 @@ func (client *ProviderClient) doRequest(method, url string, options *RequestOpts contentType = &applicationJSON } + // Return an error, when "KeepResponseBody" is true and "JSONResponse" is not nil + if options.KeepResponseBody && options.JSONResponse != nil { + return nil, errors.New("cannot use KeepResponseBody when JSONResponse is not nil") + } + if options.RawBody != nil { body = options.RawBody } @@ -384,9 +398,6 @@ func (client *ProviderClient) doRequest(method, url string, options *RequestOpts req.Header.Set(k, v) } - // Set connection parameter to close the connection immediately when we've got the response - req.Close = true - prereqtok := req.Header.Get("X-Auth-Token") // Issue the request. @@ -414,11 +425,12 @@ func (client *ProviderClient) doRequest(method, url string, options *RequestOpts body, _ := ioutil.ReadAll(resp.Body) resp.Body.Close() respErr := ErrUnexpectedResponseCode{ - URL: url, - Method: method, - Expected: options.OkCodes, - Actual: resp.StatusCode, - Body: body, + URL: url, + Method: method, + Expected: options.OkCodes, + Actual: resp.StatusCode, + Body: body, + ResponseHeader: resp.Header, } errType := options.ErrorContext @@ -513,25 +525,40 @@ func (client *ProviderClient) doRequest(method, url string, options *RequestOpts // Parse the response body as JSON, if requested to do so. if options.JSONResponse != nil { defer resp.Body.Close() + // Don't decode JSON when there is no content + if resp.StatusCode == http.StatusNoContent { + // read till EOF, otherwise the connection will be closed and cannot be reused + _, err = io.Copy(ioutil.Discard, resp.Body) + return resp, err + } if err := json.NewDecoder(resp.Body).Decode(options.JSONResponse); err != nil { return nil, err } } + // Close unused body to allow the HTTP connection to be reused + if !options.KeepResponseBody && options.JSONResponse == nil { + defer resp.Body.Close() + // read till EOF, otherwise the connection will be closed and cannot be reused + if _, err := io.Copy(ioutil.Discard, resp.Body); err != nil { + return nil, err + } + } + return resp, nil } func defaultOkCodes(method string) []int { - switch { - case method == "GET": + switch method { + case "GET", "HEAD": return []int{200} - case method == "POST": + case "POST": return []int{201, 202} - case method == "PUT": + case "PUT": return []int{201, 202} - case method == "PATCH": + case "PATCH": return []int{200, 202, 204} - case method == "DELETE": + case "DELETE": return []int{202, 204} } diff --git a/vendor/github.com/gophercloud/gophercloud/results.go b/vendor/github.com/gophercloud/gophercloud/results.go index 94a16bff0b..1b608103b7 100644 --- a/vendor/github.com/gophercloud/gophercloud/results.go +++ b/vendor/github.com/gophercloud/gophercloud/results.go @@ -131,6 +131,18 @@ func (r Result) extractIntoPtr(to interface{}, label string) error { // fields of the struct or composed extension struct // at the end of this method. toValue.Set(newSlice) + + // jtopjian: This was put into place to resolve the issue + // described at + // https://github.com/gophercloud/gophercloud/issues/1963 + // + // This probably isn't the best fix, but it appears to + // be resolving the issue, so I'm going to implement it + // for now. + // + // For future readers, this entire case statement could + // use a review. + return nil } } case reflect.Struct: diff --git a/vendor/github.com/gophercloud/gophercloud/service_client.go b/vendor/github.com/gophercloud/gophercloud/service_client.go index f222f05a66..dd54abe30e 100644 --- a/vendor/github.com/gophercloud/gophercloud/service_client.go +++ b/vendor/github.com/gophercloud/gophercloud/service_client.go @@ -152,3 +152,11 @@ func (client *ServiceClient) Request(method, url string, options *RequestOpts) ( } return client.ProviderClient.Request(method, url, options) } + +// ParseResponse is a helper function to parse http.Response to constituents. +func ParseResponse(resp *http.Response, err error) (io.ReadCloser, http.Header, error) { + if resp != nil { + return resp.Body, resp.Header, err + } + return nil, nil, err +} diff --git a/vendor/github.com/gophercloud/utils/env/env.go b/vendor/github.com/gophercloud/utils/env/env.go new file mode 100644 index 0000000000..4747ed67c4 --- /dev/null +++ b/vendor/github.com/gophercloud/utils/env/env.go @@ -0,0 +1,11 @@ +// +build !windows + +package env + +import ( + "os" +) + +func Getenv(s string) string { + return os.Getenv(s) +} diff --git a/vendor/github.com/gophercloud/utils/env/env_windows.go b/vendor/github.com/gophercloud/utils/env/env_windows.go new file mode 100644 index 0000000000..7cf80ca1da --- /dev/null +++ b/vendor/github.com/gophercloud/utils/env/env_windows.go @@ -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 +} diff --git a/vendor/github.com/gophercloud/utils/gnocchi/client.go b/vendor/github.com/gophercloud/utils/gnocchi/client.go new file mode 100644 index 0000000000..1f73d3932b --- /dev/null +++ b/vendor/github.com/gophercloud/utils/gnocchi/client.go @@ -0,0 +1,25 @@ +package gnocchi + +import ( + "github.com/gophercloud/gophercloud" +) + +func initClientOpts(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts, clientType string) (*gophercloud.ServiceClient, error) { + sc := new(gophercloud.ServiceClient) + eo.ApplyDefaults(clientType) + url, err := client.EndpointLocator(eo) + if err != nil { + return sc, err + } + sc.ProviderClient = client + sc.Endpoint = url + sc.Type = clientType + return sc, nil +} + +// NewGnocchiV1 creates a ServiceClient that may be used with the v1 Gnocchi package. +func NewGnocchiV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + sc, err := initClientOpts(client, eo, "metric") + sc.ResourceBase = sc.Endpoint + "v1/" + return sc, err +} diff --git a/vendor/github.com/gophercloud/utils/gnocchi/results.go b/vendor/github.com/gophercloud/utils/gnocchi/results.go new file mode 100644 index 0000000000..f08095d3fe --- /dev/null +++ b/vendor/github.com/gophercloud/utils/gnocchi/results.go @@ -0,0 +1,37 @@ +package gnocchi + +import ( + "bytes" + "encoding/json" + "time" +) + +// RFC3339NanoTimezone describes a common timestamp format used by Gnocchi API responses. +const RFC3339NanoTimezone = "2006-01-02T15:04:05.999999+00:00" + +// RFC3339NanoNoTimezone describes a common timestamp format that can be used for Gnocchi requests +// with time ranges. +const RFC3339NanoNoTimezone = "2006-01-02T15:04:05.999999" + +// JSONRFC3339NanoTimezone is a type for Gnocchi responses timestamps with a timezone offset. +type JSONRFC3339NanoTimezone time.Time + +// UnmarshalJSON helps to unmarshal timestamps from Gnocchi responses to the +// JSONRFC3339NanoTimezone type. +func (jt *JSONRFC3339NanoTimezone) UnmarshalJSON(data []byte) error { + b := bytes.NewBuffer(data) + dec := json.NewDecoder(b) + var s string + if err := dec.Decode(&s); err != nil { + return err + } + if s == "" { + return nil + } + t, err := time.Parse(RFC3339NanoTimezone, s) + if err != nil { + return err + } + *jt = JSONRFC3339NanoTimezone(t) + return nil +} diff --git a/vendor/github.com/gophercloud/utils/internal/pkg.go b/vendor/github.com/gophercloud/utils/internal/pkg.go new file mode 100644 index 0000000000..5bf0569ce8 --- /dev/null +++ b/vendor/github.com/gophercloud/utils/internal/pkg.go @@ -0,0 +1 @@ +package internal diff --git a/vendor/github.com/gophercloud/utils/internal/util.go b/vendor/github.com/gophercloud/utils/internal/util.go new file mode 100644 index 0000000000..debaa56cae --- /dev/null +++ b/vendor/github.com/gophercloud/utils/internal/util.go @@ -0,0 +1,108 @@ +package internal + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "io/ioutil" + "os" + "reflect" + "strings" + + "github.com/mitchellh/go-homedir" +) + +// RemainingKeys will inspect a struct and compare it to a map. Any struct +// field that does not have a JSON tag that matches a key in the map or +// a matching lower-case field in the map will be returned as an extra. +// +// This is useful for determining the extra fields returned in response bodies +// for resources that can contain an arbitrary or dynamic number of fields. +func RemainingKeys(s interface{}, m map[string]interface{}) (extras map[string]interface{}) { + extras = make(map[string]interface{}) + for k, v := range m { + extras[k] = v + } + + valueOf := reflect.ValueOf(s) + typeOf := reflect.TypeOf(s) + for i := 0; i < valueOf.NumField(); i++ { + field := typeOf.Field(i) + + lowerField := strings.ToLower(field.Name) + delete(extras, lowerField) + + if tagValue := field.Tag.Get("json"); tagValue != "" && tagValue != "-" { + delete(extras, tagValue) + } + } + + return +} + +// PrepareTLSConfig generates TLS config based on the specifed parameters +func PrepareTLSConfig(caCertFile, clientCertFile, clientKeyFile string, insecure *bool) (*tls.Config, error) { + config := &tls.Config{} + if caCertFile != "" { + caCert, _, err := pathOrContents(caCertFile) + if err != nil { + return nil, fmt.Errorf("Error reading CA Cert: %s", err) + } + + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caCert) + config.RootCAs = caCertPool + } + + if insecure == nil { + config.InsecureSkipVerify = false + } else { + config.InsecureSkipVerify = *insecure + } + + if clientCertFile != "" && clientKeyFile != "" { + clientCert, _, err := pathOrContents(clientCertFile) + if err != nil { + return nil, fmt.Errorf("Error reading Client Cert: %s", err) + } + clientKey, _, err := pathOrContents(clientKeyFile) + if err != nil { + return nil, fmt.Errorf("Error reading Client Key: %s", err) + } + + cert, err := tls.X509KeyPair(clientCert, clientKey) + if err != nil { + return nil, err + } + + config.Certificates = []tls.Certificate{cert} + config.BuildNameToCertificate() + } + + return config, nil +} + +func pathOrContents(poc string) ([]byte, bool, error) { + if len(poc) == 0 { + return nil, false, nil + } + + path := poc + if path[0] == '~' { + var err error + path, err = homedir.Expand(path) + if err != nil { + return []byte(path), true, err + } + } + + if _, err := os.Stat(path); err == nil { + contents, err := ioutil.ReadFile(path) + if err != nil { + return contents, true, err + } + return contents, true, nil + } + + return []byte(poc), false, nil +} diff --git a/vendor/github.com/gophercloud/utils/openstack/clientconfig/doc.go b/vendor/github.com/gophercloud/utils/openstack/clientconfig/doc.go index 1f3be2128c..a68ccda5c4 100644 --- a/vendor/github.com/gophercloud/utils/openstack/clientconfig/doc.go +++ b/vendor/github.com/gophercloud/utils/openstack/clientconfig/doc.go @@ -7,7 +7,7 @@ See https://docs.openstack.org/os-client-config/latest for details. Example to Create a Provider Client From clouds.yaml opts := &clientconfig.ClientOpts{ - Name: "hawaii", + Cloud: "hawaii", } pClient, err := clientconfig.AuthenticatedClient(opts) diff --git a/vendor/github.com/gophercloud/utils/openstack/clientconfig/requests.go b/vendor/github.com/gophercloud/utils/openstack/clientconfig/requests.go index 007939013b..0aff5965fd 100644 --- a/vendor/github.com/gophercloud/utils/openstack/clientconfig/requests.go +++ b/vendor/github.com/gophercloud/utils/openstack/clientconfig/requests.go @@ -2,12 +2,15 @@ package clientconfig import ( "fmt" - "os" + "net/http" "reflect" "strings" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack" + "github.com/gophercloud/utils/env" + "github.com/gophercloud/utils/gnocchi" + "github.com/gophercloud/utils/internal" yaml "gopkg.in/yaml.v2" ) @@ -56,11 +59,62 @@ type ClientOpts struct { // This will override a region in clouds.yaml or can be used // when authenticating directly with AuthInfo. RegionName string + + // EndpointType specifies whether to use the public, internal, or + // admin endpoint of a service. + EndpointType string + + // HTTPClient provides the ability customize the ProviderClient's + // internal HTTP client. + HTTPClient *http.Client + + // YAMLOpts provides the ability to pass a customized set + // of options and methods for loading the YAML file. + // It takes a YAMLOptsBuilder interface that is defined + // in this file. This is optional and the default behavior + // is to call the local LoadCloudsYAML functions defined + // in this file. + YAMLOpts YAMLOptsBuilder +} + +// YAMLOptsBuilder defines an interface for customization when +// loading a clouds.yaml file. +type YAMLOptsBuilder interface { + LoadCloudsYAML() (map[string]Cloud, error) + LoadSecureCloudsYAML() (map[string]Cloud, error) + LoadPublicCloudsYAML() (map[string]Cloud, error) +} + +// YAMLOpts represents options and methods to load a clouds.yaml file. +type YAMLOpts struct { + // By default, no options are specified. +} + +// LoadCloudsYAML defines how to load a clouds.yaml file. +// By default, this calls the local LoadCloudsYAML function. +func (opts YAMLOpts) LoadCloudsYAML() (map[string]Cloud, error) { + return LoadCloudsYAML() +} + +// LoadSecureCloudsYAML defines how to load a secure.yaml file. +// By default, this calls the local LoadSecureCloudsYAML function. +func (opts YAMLOpts) LoadSecureCloudsYAML() (map[string]Cloud, error) { + return LoadSecureCloudsYAML() +} + +// LoadPublicCloudsYAML defines how to load a public-secure.yaml file. +// By default, this calls the local LoadPublicCloudsYAML function. +func (opts YAMLOpts) LoadPublicCloudsYAML() (map[string]Cloud, error) { + return LoadPublicCloudsYAML() } // LoadCloudsYAML will load a clouds.yaml file and return the full config. +// This is called by the YAMLOpts method. Calling this function directly +// is supported for now but has only been retained for backwards +// compatibility from before YAMLOpts was defined. This may be removed in +// the future. func LoadCloudsYAML() (map[string]Cloud, error) { - content, err := findAndReadCloudsYAML() + _, content, err := FindAndReadCloudsYAML() if err != nil { return nil, err } @@ -75,10 +129,14 @@ func LoadCloudsYAML() (map[string]Cloud, error) { } // LoadSecureCloudsYAML will load a secure.yaml file and return the full config. +// This is called by the YAMLOpts method. Calling this function directly +// is supported for now but has only been retained for backwards +// compatibility from before YAMLOpts was defined. This may be removed in +// the future. func LoadSecureCloudsYAML() (map[string]Cloud, error) { var secureClouds Clouds - content, err := findAndReadSecureCloudsYAML() + _, content, err := FindAndReadSecureCloudsYAML() if err != nil { if err.Error() == "no secure.yaml file found" { // secure.yaml is optional so just ignore read error @@ -96,10 +154,14 @@ func LoadSecureCloudsYAML() (map[string]Cloud, error) { } // LoadPublicCloudsYAML will load a public-clouds.yaml file and return the full config. +// This is called by the YAMLOpts method. Calling this function directly +// is supported for now but has only been retained for backwards +// compatibility from before YAMLOpts was defined. This may be removed in +// the future. func LoadPublicCloudsYAML() (map[string]Cloud, error) { var publicClouds PublicClouds - content, err := findAndReadPublicCloudsYAML() + _, content, err := FindAndReadPublicCloudsYAML() if err != nil { if err.Error() == "no clouds-public.yaml file found" { // clouds-public.yaml is optional so just ignore read error @@ -119,7 +181,13 @@ func LoadPublicCloudsYAML() (map[string]Cloud, error) { // GetCloudFromYAML will return a cloud entry from a clouds.yaml file. func GetCloudFromYAML(opts *ClientOpts) (*Cloud, error) { - clouds, err := LoadCloudsYAML() + if opts.YAMLOpts == nil { + opts.YAMLOpts = new(YAMLOpts) + } + + yamlOpts := opts.YAMLOpts + + clouds, err := yamlOpts.LoadCloudsYAML() if err != nil { return nil, fmt.Errorf("unable to load clouds.yaml: %s", err) } @@ -138,7 +206,7 @@ func GetCloudFromYAML(opts *ClientOpts) (*Cloud, error) { envPrefix = opts.EnvPrefix } - if v := os.Getenv(envPrefix + "CLOUD"); v != "" { + if v := env.Getenv(envPrefix + "CLOUD"); v != "" { cloudName = v } @@ -159,32 +227,33 @@ func GetCloudFromYAML(opts *ClientOpts) (*Cloud, error) { } } - var cloudIsInCloudsYaml bool - if cloud == nil { - // not an immediate error as it might still be defined in secure.yaml - cloudIsInCloudsYaml = false - } else { - cloudIsInCloudsYaml = true - } + if cloud != nil { + // A profile points to a public cloud entry. + // If one was specified, load a list of public clouds + // and then merge the information with the current cloud data. + profileName := defaultIfEmpty(cloud.Profile, cloud.Cloud) - publicClouds, err := LoadPublicCloudsYAML() - if err != nil { - return nil, fmt.Errorf("unable to load clouds-public.yaml: %s", err) - } + if profileName != "" { + publicClouds, err := yamlOpts.LoadPublicCloudsYAML() + if err != nil { + return nil, fmt.Errorf("unable to load clouds-public.yaml: %s", err) + } - var profileName = defaultIfEmpty(cloud.Profile, cloud.Cloud) - if profileName != "" { - publicCloud, ok := publicClouds[profileName] - if !ok { - return nil, fmt.Errorf("cloud %s does not exist in clouds-public.yaml", profileName) - } - cloud, err = mergeClouds(cloud, publicCloud) - if err != nil { - return nil, fmt.Errorf("Could not merge information from clouds.yaml and clouds-public.yaml for cloud %s", profileName) + publicCloud, ok := publicClouds[profileName] + if !ok { + return nil, fmt.Errorf("cloud %s does not exist in clouds-public.yaml", profileName) + } + + cloud, err = mergeClouds(cloud, publicCloud) + if err != nil { + return nil, fmt.Errorf("Could not merge information from clouds.yaml and clouds-public.yaml for cloud %s", profileName) + } } } - secureClouds, err := LoadSecureCloudsYAML() + // Next, load a secure clouds file and see if a cloud entry + // can be found or merged. + secureClouds, err := yamlOpts.LoadSecureCloudsYAML() if err != nil { return nil, fmt.Errorf("unable to load secure.yaml: %s", err) } @@ -192,12 +261,13 @@ func GetCloudFromYAML(opts *ClientOpts) (*Cloud, error) { if secureClouds != nil { // If no entry was found in clouds.yaml, no cloud name was specified, // and only one secureCloud entry exists, use that as the cloud entry. - if !cloudIsInCloudsYaml && cloudName == "" && len(secureClouds) == 1 { + if cloud == nil && cloudName == "" && len(secureClouds) == 1 { for _, v := range secureClouds { cloud = &v } } + // Otherwise, see if the provided cloud name exists in the secure yaml file. secureCloud, ok := secureClouds[cloudName] if !ok && cloud == nil { // cloud == nil serves two purposes here: @@ -217,6 +287,12 @@ func GetCloudFromYAML(opts *ClientOpts) (*Cloud, error) { } } + // As an extra precaution, do one final check to see if cloud is nil. + // We shouldn't reach this point, though. + if cloud == nil { + return nil, fmt.Errorf("Could not find cloud %s", cloudName) + } + // Default is to verify SSL API requests if cloud.Verify == nil { iTrue := true @@ -227,6 +303,17 @@ func GetCloudFromYAML(opts *ClientOpts) (*Cloud, error) { // clouds-public.yml // https://github.com/openstack/openstacksdk/tree/master/openstack/config/vendors + // Both Interface and EndpointType are valid settings in clouds.yaml, + // but we want to standardize on EndpointType for simplicity. + // + // If only Interface was set, we copy that to EndpointType to use as the setting. + // But in all other cases, EndpointType is used and Interface is cleared. + if cloud.Interface != "" && cloud.EndpointType == "" { + cloud.EndpointType = cloud.Interface + } + + cloud.Interface = "" + return cloud, nil } @@ -260,7 +347,7 @@ func AuthOptions(opts *ClientOpts) (*gophercloud.AuthOptions, error) { envPrefix = opts.EnvPrefix } - if v := os.Getenv(envPrefix + "CLOUD"); v != "" { + if v := env.Getenv(envPrefix + "CLOUD"); v != "" { cloudName = v } @@ -310,7 +397,7 @@ func determineIdentityAPI(cloud *Cloud, opts *ClientOpts) string { envPrefix = opts.EnvPrefix } - if v := os.Getenv(envPrefix + "IDENTITY_API_VERSION"); v != "" { + if v := env.Getenv(envPrefix + "IDENTITY_API_VERSION"); v != "" { identityAPI = v } @@ -359,49 +446,49 @@ func v2auth(cloud *Cloud, opts *ClientOpts) (*gophercloud.AuthOptions, error) { } if cloud.AuthInfo.AuthURL == "" { - if v := os.Getenv(envPrefix + "AUTH_URL"); v != "" { + if v := env.Getenv(envPrefix + "AUTH_URL"); v != "" { cloud.AuthInfo.AuthURL = v } } if cloud.AuthInfo.Token == "" { - if v := os.Getenv(envPrefix + "TOKEN"); v != "" { + if v := env.Getenv(envPrefix + "TOKEN"); v != "" { cloud.AuthInfo.Token = v } - if v := os.Getenv(envPrefix + "AUTH_TOKEN"); v != "" { + if v := env.Getenv(envPrefix + "AUTH_TOKEN"); v != "" { cloud.AuthInfo.Token = v } } if cloud.AuthInfo.Username == "" { - if v := os.Getenv(envPrefix + "USERNAME"); v != "" { + if v := env.Getenv(envPrefix + "USERNAME"); v != "" { cloud.AuthInfo.Username = v } } if cloud.AuthInfo.Password == "" { - if v := os.Getenv(envPrefix + "PASSWORD"); v != "" { + if v := env.Getenv(envPrefix + "PASSWORD"); v != "" { cloud.AuthInfo.Password = v } } if cloud.AuthInfo.ProjectID == "" { - if v := os.Getenv(envPrefix + "TENANT_ID"); v != "" { + if v := env.Getenv(envPrefix + "TENANT_ID"); v != "" { cloud.AuthInfo.ProjectID = v } - if v := os.Getenv(envPrefix + "PROJECT_ID"); v != "" { + if v := env.Getenv(envPrefix + "PROJECT_ID"); v != "" { cloud.AuthInfo.ProjectID = v } } if cloud.AuthInfo.ProjectName == "" { - if v := os.Getenv(envPrefix + "TENANT_NAME"); v != "" { + if v := env.Getenv(envPrefix + "TENANT_NAME"); v != "" { cloud.AuthInfo.ProjectName = v } - if v := os.Getenv(envPrefix + "PROJECT_NAME"); v != "" { + if v := env.Getenv(envPrefix + "PROJECT_NAME"); v != "" { cloud.AuthInfo.ProjectName = v } } @@ -427,115 +514,115 @@ func v3auth(cloud *Cloud, opts *ClientOpts) (*gophercloud.AuthOptions, error) { } if cloud.AuthInfo.AuthURL == "" { - if v := os.Getenv(envPrefix + "AUTH_URL"); v != "" { + if v := env.Getenv(envPrefix + "AUTH_URL"); v != "" { cloud.AuthInfo.AuthURL = v } } if cloud.AuthInfo.Token == "" { - if v := os.Getenv(envPrefix + "TOKEN"); v != "" { + if v := env.Getenv(envPrefix + "TOKEN"); v != "" { cloud.AuthInfo.Token = v } - if v := os.Getenv(envPrefix + "AUTH_TOKEN"); v != "" { + if v := env.Getenv(envPrefix + "AUTH_TOKEN"); v != "" { cloud.AuthInfo.Token = v } } if cloud.AuthInfo.Username == "" { - if v := os.Getenv(envPrefix + "USERNAME"); v != "" { + if v := env.Getenv(envPrefix + "USERNAME"); v != "" { cloud.AuthInfo.Username = v } } if cloud.AuthInfo.UserID == "" { - if v := os.Getenv(envPrefix + "USER_ID"); v != "" { + if v := env.Getenv(envPrefix + "USER_ID"); v != "" { cloud.AuthInfo.UserID = v } } if cloud.AuthInfo.Password == "" { - if v := os.Getenv(envPrefix + "PASSWORD"); v != "" { + if v := env.Getenv(envPrefix + "PASSWORD"); v != "" { cloud.AuthInfo.Password = v } } if cloud.AuthInfo.ProjectID == "" { - if v := os.Getenv(envPrefix + "TENANT_ID"); v != "" { + if v := env.Getenv(envPrefix + "TENANT_ID"); v != "" { cloud.AuthInfo.ProjectID = v } - if v := os.Getenv(envPrefix + "PROJECT_ID"); v != "" { + if v := env.Getenv(envPrefix + "PROJECT_ID"); v != "" { cloud.AuthInfo.ProjectID = v } } if cloud.AuthInfo.ProjectName == "" { - if v := os.Getenv(envPrefix + "TENANT_NAME"); v != "" { + if v := env.Getenv(envPrefix + "TENANT_NAME"); v != "" { cloud.AuthInfo.ProjectName = v } - if v := os.Getenv(envPrefix + "PROJECT_NAME"); v != "" { + if v := env.Getenv(envPrefix + "PROJECT_NAME"); v != "" { cloud.AuthInfo.ProjectName = v } } if cloud.AuthInfo.DomainID == "" { - if v := os.Getenv(envPrefix + "DOMAIN_ID"); v != "" { + if v := env.Getenv(envPrefix + "DOMAIN_ID"); v != "" { cloud.AuthInfo.DomainID = v } } if cloud.AuthInfo.DomainName == "" { - if v := os.Getenv(envPrefix + "DOMAIN_NAME"); v != "" { + if v := env.Getenv(envPrefix + "DOMAIN_NAME"); v != "" { cloud.AuthInfo.DomainName = v } } if cloud.AuthInfo.DefaultDomain == "" { - if v := os.Getenv(envPrefix + "DEFAULT_DOMAIN"); v != "" { + if v := env.Getenv(envPrefix + "DEFAULT_DOMAIN"); v != "" { cloud.AuthInfo.DefaultDomain = v } } if cloud.AuthInfo.ProjectDomainID == "" { - if v := os.Getenv(envPrefix + "PROJECT_DOMAIN_ID"); v != "" { + if v := env.Getenv(envPrefix + "PROJECT_DOMAIN_ID"); v != "" { cloud.AuthInfo.ProjectDomainID = v } } if cloud.AuthInfo.ProjectDomainName == "" { - if v := os.Getenv(envPrefix + "PROJECT_DOMAIN_NAME"); v != "" { + if v := env.Getenv(envPrefix + "PROJECT_DOMAIN_NAME"); v != "" { cloud.AuthInfo.ProjectDomainName = v } } if cloud.AuthInfo.UserDomainID == "" { - if v := os.Getenv(envPrefix + "USER_DOMAIN_ID"); v != "" { + if v := env.Getenv(envPrefix + "USER_DOMAIN_ID"); v != "" { cloud.AuthInfo.UserDomainID = v } } if cloud.AuthInfo.UserDomainName == "" { - if v := os.Getenv(envPrefix + "USER_DOMAIN_NAME"); v != "" { + if v := env.Getenv(envPrefix + "USER_DOMAIN_NAME"); v != "" { cloud.AuthInfo.UserDomainName = v } } if cloud.AuthInfo.ApplicationCredentialID == "" { - if v := os.Getenv(envPrefix + "APPLICATION_CREDENTIAL_ID"); v != "" { + if v := env.Getenv(envPrefix + "APPLICATION_CREDENTIAL_ID"); v != "" { cloud.AuthInfo.ApplicationCredentialID = v } } if cloud.AuthInfo.ApplicationCredentialName == "" { - if v := os.Getenv(envPrefix + "APPLICATION_CREDENTIAL_NAME"); v != "" { + if v := env.Getenv(envPrefix + "APPLICATION_CREDENTIAL_NAME"); v != "" { cloud.AuthInfo.ApplicationCredentialName = v } } if cloud.AuthInfo.ApplicationCredentialSecret == "" { - if v := os.Getenv(envPrefix + "APPLICATION_CREDENTIAL_SECRET"); v != "" { + if v := env.Getenv(envPrefix + "APPLICATION_CREDENTIAL_SECRET"); v != "" { cloud.AuthInfo.ApplicationCredentialSecret = v } } @@ -545,7 +632,11 @@ func v3auth(cloud *Cloud, opts *ClientOpts) (*gophercloud.AuthOptions, error) { scope := new(gophercloud.AuthScope) // Application credentials don't support scope - if !isApplicationCredential(cloud.AuthInfo) { + if isApplicationCredential(cloud.AuthInfo) { + // If Domain* is set, but UserDomain* or ProjectDomain* aren't, + // then use Domain* as the default setting. + cloud = setDomainIfNeeded(cloud) + } else { if !isProjectScoped(cloud.AuthInfo) { if cloud.AuthInfo.DomainID != "" { scope.DomainID = cloud.AuthInfo.DomainID @@ -639,7 +730,7 @@ func NewServiceClient(service string, opts *ClientOpts) (*gophercloud.ServiceCli envPrefix = opts.EnvPrefix } - if v := os.Getenv(envPrefix + "CLOUD"); v != "" { + if v := env.Getenv(envPrefix + "CLOUD"); v != "" { cloudName = v } @@ -659,10 +750,68 @@ func NewServiceClient(service string, opts *ClientOpts) (*gophercloud.ServiceCli return nil, err } + // Check if a custom CA cert was provided. + // First, check if the CACERT environment variable is set. + var caCertPath string + if v := env.Getenv(envPrefix + "CACERT"); v != "" { + caCertPath = v + } + // Next, check if the cloud entry sets a CA cert. + if v := cloud.CACertFile; v != "" { + caCertPath = v + } + + // Check if a custom client cert was provided. + // First, check if the CERT environment variable is set. + var clientCertPath string + if v := env.Getenv(envPrefix + "CERT"); v != "" { + clientCertPath = v + } + // Next, check if the cloud entry sets a client cert. + if v := cloud.ClientCertFile; v != "" { + clientCertPath = v + } + + // Check if a custom client key was provided. + // First, check if the KEY environment variable is set. + var clientKeyPath string + if v := env.Getenv(envPrefix + "KEY"); v != "" { + clientKeyPath = v + } + // Next, check if the cloud entry sets a client key. + if v := cloud.ClientKeyFile; v != "" { + clientKeyPath = v + } + + // Define whether or not SSL API requests should be verified. + var insecurePtr *bool + if cloud.Verify != nil { + // Here we take the boolean pointer negation. + insecure := !*cloud.Verify + insecurePtr = &insecure + } + + tlsConfig, err := internal.PrepareTLSConfig(caCertPath, clientCertPath, clientKeyPath, insecurePtr) + if err != nil { + return nil, err + } + + // If an HTTPClient was specified, use it. + if opts.HTTPClient != nil { + pClient.HTTPClient = *opts.HTTPClient + } else { + // Otherwise create a new HTTP client with the generated TLS config. + pClient.HTTPClient = http.Client{ + Transport: &http.Transport{ + TLSClientConfig: tlsConfig, + }, + } + } + // Determine the region to use. // First, check if the REGION_NAME environment variable is set. var region string - if v := os.Getenv(envPrefix + "REGION_NAME"); v != "" { + if v := env.Getenv(envPrefix + "REGION_NAME"); v != "" { region = v } @@ -677,8 +826,27 @@ func NewServiceClient(service string, opts *ClientOpts) (*gophercloud.ServiceCli region = v } + // Determine the endpoint type to use. + // First, check if the OS_INTERFACE environment variable is set. + var endpointType string + if v := env.Getenv(envPrefix + "INTERFACE"); v != "" { + endpointType = v + } + + // Next, check if the cloud entry sets an endpoint type. + if v := cloud.EndpointType; v != "" { + endpointType = v + } + + // Finally, see if one was specified in the ClientOpts. + // If so, this takes precedence. + if v := opts.EndpointType; v != "" { + endpointType = v + } + eo := gophercloud.EndpointOpts{ - Region: region, + Region: region, + Availability: GetEndpointType(endpointType), } switch service { @@ -688,10 +856,14 @@ func NewServiceClient(service string, opts *ClientOpts) (*gophercloud.ServiceCli return openstack.NewComputeV2(pClient, eo) case "container": return openstack.NewContainerV1(pClient, eo) + case "container-infra": + return openstack.NewContainerInfraV1(pClient, eo) case "database": return openstack.NewDBV1(pClient, eo) case "dns": return openstack.NewDNSV2(pClient, eo) + case "gnocchi": + return gnocchi.NewGnocchiV1(pClient, eo) case "identity": identityVersion := "3" if v := cloud.IdentityAPIVersion; v != "" { diff --git a/vendor/github.com/gophercloud/utils/openstack/clientconfig/results.go b/vendor/github.com/gophercloud/utils/openstack/clientconfig/results.go index 04f1ad3bd0..3ffae32783 100644 --- a/vendor/github.com/gophercloud/utils/openstack/clientconfig/results.go +++ b/vendor/github.com/gophercloud/utils/openstack/clientconfig/results.go @@ -16,106 +16,112 @@ type Clouds struct { // Cloud represents an entry in a clouds.yaml/public-clouds.yaml/secure.yaml file. type Cloud struct { - Cloud string `yaml:"cloud" json:"cloud"` - Profile string `yaml:"profile" json:"profile"` - AuthInfo *AuthInfo `yaml:"auth" json:"auth"` - AuthType AuthType `yaml:"auth_type" json:"auth_type"` - RegionName string `yaml:"region_name" json:"region_name"` - Regions []interface{} `yaml:"regions" json:"regions"` + Cloud string `yaml:"cloud,omitempty" json:"cloud,omitempty"` + Profile string `yaml:"profile,omitempty" json:"profile,omitempty"` + AuthInfo *AuthInfo `yaml:"auth,omitempty" json:"auth,omitempty"` + AuthType AuthType `yaml:"auth_type,omitempty" json:"auth_type,omitempty"` + RegionName string `yaml:"region_name,omitempty" json:"region_name,omitempty"` + Regions []interface{} `yaml:"regions,omitempty" json:"regions,omitempty"` + + // EndpointType and Interface both specify whether to use the public, internal, + // or admin interface of a service. They should be considered synonymous, but + // EndpointType will take precedence when both are specified. + EndpointType string `yaml:"endpoint_type,omitempty" json:"endpoint_type,omitempty"` + Interface string `yaml:"interface,omitempty" json:"interface,omitempty"` // API Version overrides. - IdentityAPIVersion string `yaml:"identity_api_version" json:"identity_api_version"` - VolumeAPIVersion string `yaml:"volume_api_version" json:"volume_api_version"` + IdentityAPIVersion string `yaml:"identity_api_version,omitempty" json:"identity_api_version,omitempty"` + VolumeAPIVersion string `yaml:"volume_api_version,omitempty" json:"volume_api_version,omitempty"` // Verify whether or not SSL API requests should be verified. - Verify *bool `yaml:"verify" json:"verify"` + Verify *bool `yaml:"verify,omitempty" json:"verify,omitempty"` // CACertFile a path to a CA Cert bundle that can be used as part of // verifying SSL API requests. - CACertFile string `yaml:"cacert" json:"cacert"` + CACertFile string `yaml:"cacert,omitempty" json:"cacert,omitempty"` // ClientCertFile a path to a client certificate to use as part of the SSL // transaction. - ClientCertFile string `yaml:"cert" json:"cert"` + ClientCertFile string `yaml:"cert,omitempty" json:"cert,omitempty"` // ClientKeyFile a path to a client key to use as part of the SSL // transaction. - ClientKeyFile string `yaml:"key" json:"key"` + ClientKeyFile string `yaml:"key,omitempty" json:"key,omitempty"` } // AuthInfo represents the auth section of a cloud entry or // auth options entered explicitly in ClientOpts. type AuthInfo struct { // AuthURL is the keystone/identity endpoint URL. - AuthURL string `yaml:"auth_url" json:"auth_url"` + AuthURL string `yaml:"auth_url,omitempty" json:"auth_url,omitempty"` // Token is a pre-generated authentication token. - Token string `yaml:"token" json:"token"` + Token string `yaml:"token,omitempty" json:"token,omitempty"` // Username is the username of the user. - Username string `yaml:"username" json:"username"` + Username string `yaml:"username,omitempty" json:"username,omitempty"` // UserID is the unique ID of a user. - UserID string `yaml:"user_id" json:"user_id"` + UserID string `yaml:"user_id,omitempty" json:"user_id,omitempty"` // Password is the password of the user. - Password string `yaml:"password" json:"password"` + Password string `yaml:"password,omitempty" json:"password,omitempty"` // Application Credential ID to login with. - ApplicationCredentialID string `yaml:"application_credential_id"` + ApplicationCredentialID string `yaml:"application_credential_id,omitempty" json:"application_credential_id,omitempty"` // Application Credential name to login with. - ApplicationCredentialName string `yaml:"application_credential_name"` + ApplicationCredentialName string `yaml:"application_credential_name,omitempty" json:"application_credential_name,omitempty"` // Application Credential secret to login with. - ApplicationCredentialSecret string `yaml:"application_credential_secret"` + ApplicationCredentialSecret string `yaml:"application_credential_secret,omitempty" json:"application_credential_secret,omitempty"` // ProjectName is the common/human-readable name of a project. // Users can be scoped to a project. // ProjectName on its own is not enough to ensure a unique scope. It must // also be combined with either a ProjectDomainName or ProjectDomainID. // ProjectName cannot be combined with ProjectID in a scope. - ProjectName string `yaml:"project_name" json:"project_name"` + ProjectName string `yaml:"project_name,omitempty" json:"project_name,omitempty"` // ProjectID is the unique ID of a project. // It can be used to scope a user to a specific project. - ProjectID string `yaml:"project_id" json:"project_id"` + ProjectID string `yaml:"project_id,omitempty" json:"project_id,omitempty"` // UserDomainName is the name of the domain where a user resides. // It is used to identify the source domain of a user. - UserDomainName string `yaml:"user_domain_name" json:"user_domain_name"` + UserDomainName string `yaml:"user_domain_name,omitempty" json:"user_domain_name,omitempty"` // UserDomainID is the unique ID of the domain where a user resides. // It is used to identify the source domain of a user. - UserDomainID string `yaml:"user_domain_id" json:"user_domain_id"` + UserDomainID string `yaml:"user_domain_id,omitempty" json:"user_domain_id,omitempty"` // ProjectDomainName is the name of the domain where a project resides. // It is used to identify the source domain of a project. // ProjectDomainName can be used in addition to a ProjectName when scoping // a user to a specific project. - ProjectDomainName string `yaml:"project_domain_name" json:"project_domain_name"` + ProjectDomainName string `yaml:"project_domain_name,omitempty" json:"project_domain_name,omitempty"` // ProjectDomainID is the name of the domain where a project resides. // It is used to identify the source domain of a project. // ProjectDomainID can be used in addition to a ProjectName when scoping // a user to a specific project. - ProjectDomainID string `yaml:"project_domain_id" json:"project_domain_id"` + ProjectDomainID string `yaml:"project_domain_id,omitempty" json:"project_domain_id,omitempty"` // DomainName is the name of a domain which can be used to identify the // source domain of either a user or a project. // If UserDomainName and ProjectDomainName are not specified, then DomainName // is used as a default choice. // It can also be used be used to specify a domain-only scope. - DomainName string `yaml:"domain_name" json:"domain_name"` + DomainName string `yaml:"domain_name,omitempty" json:"domain_name,omitempty"` // DomainID is the unique ID of a domain which can be used to identify the // source domain of eitehr a user or a project. // If UserDomainID and ProjectDomainID are not specified, then DomainID is // used as a default choice. // It can also be used be used to specify a domain-only scope. - DomainID string `yaml:"domain_id" json:"domain_id"` + DomainID string `yaml:"domain_id,omitempty" json:"domain_id,omitempty"` // DefaultDomain is the domain ID to fall back on if no other domain has // been specified and a domain is required for scope. - DefaultDomain string `yaml:"default_domain" json:"default_domain"` + DefaultDomain string `yaml:"default_domain,omitempty" json:"default_domain,omitempty"` } diff --git a/vendor/github.com/gophercloud/utils/openstack/clientconfig/utils.go b/vendor/github.com/gophercloud/utils/openstack/clientconfig/utils.go index 884e5644e2..bc2adf4fc1 100644 --- a/vendor/github.com/gophercloud/utils/openstack/clientconfig/utils.go +++ b/vendor/github.com/gophercloud/utils/openstack/clientconfig/utils.go @@ -8,6 +8,9 @@ import ( "os/user" "path/filepath" "reflect" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/utils/env" ) // defaultIfEmpty is a helper function to make it cleaner to set default value @@ -87,7 +90,7 @@ func mergeInterfaces(overridingInterface, inferiorInterface interface{}) interfa } } -// findAndReadCloudsYAML attempts to locate a clouds.yaml file in the following +// FindAndReadCloudsYAML attempts to locate a clouds.yaml file in the following // locations: // // 1. OS_CLIENT_CONFIG_FILE @@ -96,35 +99,37 @@ func mergeInterfaces(overridingInterface, inferiorInterface interface{}) interfa // 4. unix-specific site_config_dir (/etc/openstack/clouds.yaml) // // If found, the contents of the file is returned. -func findAndReadCloudsYAML() ([]byte, error) { +func FindAndReadCloudsYAML() (string, []byte, error) { // OS_CLIENT_CONFIG_FILE - if v := os.Getenv("OS_CLIENT_CONFIG_FILE"); v != "" { + if v := env.Getenv("OS_CLIENT_CONFIG_FILE"); v != "" { if ok := fileExists(v); ok { - return ioutil.ReadFile(v) + content, err := ioutil.ReadFile(v) + return v, content, err } } - return findAndReadYAML("clouds.yaml") + return FindAndReadYAML("clouds.yaml") } -func findAndReadPublicCloudsYAML() ([]byte, error) { - return findAndReadYAML("clouds-public.yaml") +func FindAndReadPublicCloudsYAML() (string, []byte, error) { + return FindAndReadYAML("clouds-public.yaml") } -func findAndReadSecureCloudsYAML() ([]byte, error) { - return findAndReadYAML("secure.yaml") +func FindAndReadSecureCloudsYAML() (string, []byte, error) { + return FindAndReadYAML("secure.yaml") } -func findAndReadYAML(yamlFile string) ([]byte, error) { +func FindAndReadYAML(yamlFile string) (string, []byte, error) { // current directory cwd, err := os.Getwd() if err != nil { - return nil, fmt.Errorf("unable to determine working directory: %s", err) + return "", nil, fmt.Errorf("unable to determine working directory: %s", err) } filename := filepath.Join(cwd, yamlFile) if ok := fileExists(filename); ok { - return ioutil.ReadFile(filename) + content, err := ioutil.ReadFile(filename) + return filename, content, err } // unix user config directory: ~/.config/openstack. @@ -133,17 +138,20 @@ func findAndReadYAML(yamlFile string) ([]byte, error) { if homeDir != "" { filename := filepath.Join(homeDir, ".config/openstack/"+yamlFile) if ok := fileExists(filename); ok { - return ioutil.ReadFile(filename) + content, err := ioutil.ReadFile(filename) + return filename, content, err } } } // unix-specific site config directory: /etc/openstack. - if ok := fileExists("/etc/openstack/" + yamlFile); ok { - return ioutil.ReadFile("/etc/openstack/" + yamlFile) + filename = "/etc/openstack/" + yamlFile + if ok := fileExists(filename); ok { + content, err := ioutil.ReadFile(filename) + return filename, content, err } - return nil, fmt.Errorf("no " + yamlFile + " file found") + return "", nil, fmt.Errorf("no " + yamlFile + " file found") } // fileExists checks for the existence of a file at a given location. @@ -153,3 +161,15 @@ func fileExists(filename string) bool { } return false } + +// GetEndpointType is a helper method to determine the endpoint type +// requested by the user. +func GetEndpointType(endpointType string) gophercloud.Availability { + if endpointType == "internal" || endpointType == "internalURL" { + return gophercloud.AvailabilityInternal + } + if endpointType == "admin" || endpointType == "adminURL" { + return gophercloud.AvailabilityAdmin + } + return gophercloud.AvailabilityPublic +} diff --git a/vendor/github.com/gophercloud/utils/openstack/compute/v2/availabilityzones/utils.go b/vendor/github.com/gophercloud/utils/openstack/compute/v2/availabilityzones/utils.go new file mode 100644 index 0000000000..a7cb940177 --- /dev/null +++ b/vendor/github.com/gophercloud/utils/openstack/compute/v2/availabilityzones/utils.go @@ -0,0 +1,27 @@ +package availabilityzones + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones" +) + +// ListAvailableAvailabilityZones is a convenience function that return a slice of available Availability Zones. +func ListAvailableAvailabilityZones(client *gophercloud.ServiceClient) ([]string, error) { + var ret []string + allPages, err := availabilityzones.List(client).AllPages() + if err != nil { + return ret, err + } + + availabilityZoneInfo, err := availabilityzones.ExtractAvailabilityZones(allPages) + if err != nil { + return ret, err + } + + for _, zoneInfo := range availabilityZoneInfo { + if zoneInfo.ZoneState.Available { + ret = append(ret, zoneInfo.ZoneName) + } + } + return ret, nil +} diff --git a/vendor/github.com/mitchellh/go-homedir/LICENSE b/vendor/github.com/mitchellh/go-homedir/LICENSE new file mode 100644 index 0000000000..f9c841a51e --- /dev/null +++ b/vendor/github.com/mitchellh/go-homedir/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013 Mitchell Hashimoto + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/mitchellh/go-homedir/README.md b/vendor/github.com/mitchellh/go-homedir/README.md new file mode 100644 index 0000000000..d70706d5b3 --- /dev/null +++ b/vendor/github.com/mitchellh/go-homedir/README.md @@ -0,0 +1,14 @@ +# go-homedir + +This is a Go library for detecting the user's home directory without +the use of cgo, so the library can be used in cross-compilation environments. + +Usage is incredibly simple, just call `homedir.Dir()` to get the home directory +for a user, and `homedir.Expand()` to expand the `~` in a path to the home +directory. + +**Why not just use `os/user`?** The built-in `os/user` package requires +cgo on Darwin systems. This means that any Go code that uses that package +cannot cross compile. But 99% of the time the use for `os/user` is just to +retrieve the home directory, which we can do for the current user without +cgo. This library does that, enabling cross-compilation. diff --git a/vendor/github.com/mitchellh/go-homedir/go.mod b/vendor/github.com/mitchellh/go-homedir/go.mod new file mode 100644 index 0000000000..7efa09a043 --- /dev/null +++ b/vendor/github.com/mitchellh/go-homedir/go.mod @@ -0,0 +1 @@ +module github.com/mitchellh/go-homedir diff --git a/vendor/github.com/mitchellh/go-homedir/homedir.go b/vendor/github.com/mitchellh/go-homedir/homedir.go new file mode 100644 index 0000000000..25378537ea --- /dev/null +++ b/vendor/github.com/mitchellh/go-homedir/homedir.go @@ -0,0 +1,167 @@ +package homedir + +import ( + "bytes" + "errors" + "os" + "os/exec" + "path/filepath" + "runtime" + "strconv" + "strings" + "sync" +) + +// DisableCache will disable caching of the home directory. Caching is enabled +// by default. +var DisableCache bool + +var homedirCache string +var cacheLock sync.RWMutex + +// Dir returns the home directory for the executing user. +// +// This uses an OS-specific method for discovering the home directory. +// An error is returned if a home directory cannot be detected. +func Dir() (string, error) { + if !DisableCache { + cacheLock.RLock() + cached := homedirCache + cacheLock.RUnlock() + if cached != "" { + return cached, nil + } + } + + cacheLock.Lock() + defer cacheLock.Unlock() + + var result string + var err error + if runtime.GOOS == "windows" { + result, err = dirWindows() + } else { + // Unix-like system, so just assume Unix + result, err = dirUnix() + } + + if err != nil { + return "", err + } + homedirCache = result + return result, nil +} + +// Expand expands the path to include the home directory if the path +// is prefixed with `~`. If it isn't prefixed with `~`, the path is +// returned as-is. +func Expand(path string) (string, error) { + if len(path) == 0 { + return path, nil + } + + if path[0] != '~' { + return path, nil + } + + if len(path) > 1 && path[1] != '/' && path[1] != '\\' { + return "", errors.New("cannot expand user-specific home dir") + } + + dir, err := Dir() + if err != nil { + return "", err + } + + return filepath.Join(dir, path[1:]), nil +} + +// Reset clears the cache, forcing the next call to Dir to re-detect +// the home directory. This generally never has to be called, but can be +// useful in tests if you're modifying the home directory via the HOME +// env var or something. +func Reset() { + cacheLock.Lock() + defer cacheLock.Unlock() + homedirCache = "" +} + +func dirUnix() (string, error) { + homeEnv := "HOME" + if runtime.GOOS == "plan9" { + // On plan9, env vars are lowercase. + homeEnv = "home" + } + + // First prefer the HOME environmental variable + if home := os.Getenv(homeEnv); home != "" { + return home, nil + } + + var stdout bytes.Buffer + + // If that fails, try OS specific commands + if runtime.GOOS == "darwin" { + cmd := exec.Command("sh", "-c", `dscl -q . -read /Users/"$(whoami)" NFSHomeDirectory | sed 's/^[^ ]*: //'`) + cmd.Stdout = &stdout + if err := cmd.Run(); err == nil { + result := strings.TrimSpace(stdout.String()) + if result != "" { + return result, nil + } + } + } else { + cmd := exec.Command("getent", "passwd", strconv.Itoa(os.Getuid())) + cmd.Stdout = &stdout + if err := cmd.Run(); err != nil { + // If the error is ErrNotFound, we ignore it. Otherwise, return it. + if err != exec.ErrNotFound { + return "", err + } + } else { + if passwd := strings.TrimSpace(stdout.String()); passwd != "" { + // username:password:uid:gid:gecos:home:shell + passwdParts := strings.SplitN(passwd, ":", 7) + if len(passwdParts) > 5 { + return passwdParts[5], nil + } + } + } + } + + // If all else fails, try the shell + stdout.Reset() + cmd := exec.Command("sh", "-c", "cd && pwd") + cmd.Stdout = &stdout + if err := cmd.Run(); err != nil { + return "", err + } + + result := strings.TrimSpace(stdout.String()) + if result == "" { + return "", errors.New("blank output when reading home directory") + } + + return result, nil +} + +func dirWindows() (string, error) { + // First prefer the HOME environmental variable + if home := os.Getenv("HOME"); home != "" { + return home, nil + } + + // Prefer standard environment variable USERPROFILE + if home := os.Getenv("USERPROFILE"); home != "" { + return home, nil + } + + drive := os.Getenv("HOMEDRIVE") + path := os.Getenv("HOMEPATH") + home := drive + path + if drive == "" || path == "" { + return "", errors.New("HOMEDRIVE, HOMEPATH, or USERPROFILE are blank") + } + + return home, nil +} diff --git a/vendor/github.com/nxadm/tail/.gitignore b/vendor/github.com/nxadm/tail/.gitignore new file mode 100644 index 0000000000..fa81aa93a0 --- /dev/null +++ b/vendor/github.com/nxadm/tail/.gitignore @@ -0,0 +1,2 @@ +.idea/ +.test/ \ No newline at end of file diff --git a/vendor/github.com/onsi/ginkgo/.gitignore b/vendor/github.com/onsi/ginkgo/.gitignore new file mode 100644 index 0000000000..b9f9659d29 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +TODO +tmp/**/* +*.coverprofile +.vscode +.idea/ +*.log diff --git a/vendor/github.com/onsi/gomega/.gitignore b/vendor/github.com/onsi/gomega/.gitignore new file mode 100644 index 0000000000..720c13cba8 --- /dev/null +++ b/vendor/github.com/onsi/gomega/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +*.test +. +.idea +gomega.iml diff --git a/vendor/modules.txt b/vendor/modules.txt index 9c659b2f05..643eb84279 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -90,23 +90,25 @@ github.com/google/uuid github.com/googleapis/gnostic/compiler github.com/googleapis/gnostic/extensions github.com/googleapis/gnostic/openapiv2 -# github.com/gophercloud/gophercloud v0.6.1-0.20191025185032-6ad562af8c1f +# github.com/gophercloud/gophercloud v0.14.0 github.com/gophercloud/gophercloud github.com/gophercloud/gophercloud/internal github.com/gophercloud/gophercloud/openstack github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes github.com/gophercloud/gophercloud/openstack/common/extensions github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces +github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups github.com/gophercloud/gophercloud/openstack/compute/v2/flavors -github.com/gophercloud/gophercloud/openstack/compute/v2/images github.com/gophercloud/gophercloud/openstack/compute/v2/servers github.com/gophercloud/gophercloud/openstack/identity/v2/tenants github.com/gophercloud/gophercloud/openstack/identity/v2/tokens +github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens +github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1 github.com/gophercloud/gophercloud/openstack/identity/v3/tokens github.com/gophercloud/gophercloud/openstack/imageservice/v2/images github.com/gophercloud/gophercloud/openstack/networking/v2/extensions @@ -120,8 +122,12 @@ github.com/gophercloud/gophercloud/openstack/networking/v2/ports github.com/gophercloud/gophercloud/openstack/networking/v2/subnets github.com/gophercloud/gophercloud/openstack/utils github.com/gophercloud/gophercloud/pagination -# github.com/gophercloud/utils v0.0.0-20190124231947-9c3b9f2457ef +# github.com/gophercloud/utils v0.0.0-20201203161420-f41c1768a042 +github.com/gophercloud/utils/env +github.com/gophercloud/utils/gnocchi +github.com/gophercloud/utils/internal github.com/gophercloud/utils/openstack/clientconfig +github.com/gophercloud/utils/openstack/compute/v2/availabilityzones # github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 github.com/gregjones/httpcache github.com/gregjones/httpcache/diskcache @@ -144,6 +150,8 @@ github.com/mailru/easyjson/jlexer github.com/mailru/easyjson/jwriter # github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 github.com/matttproud/golang_protobuf_extensions/pbutil +# github.com/mitchellh/go-homedir v1.1.0 +github.com/mitchellh/go-homedir # github.com/mitchellh/go-wordwrap v1.0.0 github.com/mitchellh/go-wordwrap # github.com/moby/term v0.0.0-20200312100748-672ec06f55cd diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/internal/testing/integration/.gitignore b/vendor/sigs.k8s.io/controller-runtime/pkg/internal/testing/integration/.gitignore new file mode 100644 index 0000000000..16308b38c4 --- /dev/null +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/internal/testing/integration/.gitignore @@ -0,0 +1 @@ +assets/bin