From b4ce23f69c58a4f37759c6141c9b228fd22d8162 Mon Sep 17 00:00:00 2001 From: Justin SB Date: Mon, 13 May 2019 15:16:14 -0700 Subject: [PATCH] Update kops dependency In particular, we are picking up the fix for https://github.com/kubernetes/kops/issues/6098 --- etcd-manager/Gopkg.lock | 11 +- etcd-manager/Gopkg.toml | 6 +- .../conformance_proto/BUILD.bazel | 2 +- .../gophercloud/gophercloud/.gitignore | 1 + .../gophercloud/gophercloud/.travis.yml | 17 +- .../gophercloud/gophercloud/.zuul.yaml | 86 +++++++++ .../gophercloud/gophercloud/BUILD.bazel | 1 + .../github.com/gophercloud/gophercloud/FAQ.md | 148 --------------- .../gophercloud/gophercloud/MIGRATING.md | 32 ---- .../gophercloud/gophercloud/README.md | 8 +- .../gophercloud/gophercloud/STYLEGUIDE.md | 79 -------- .../gophercloud/gophercloud/auth_options.go | 159 ++++++++++++---- .../gophercloud/gophercloud/auth_result.go | 52 +++++ .../github.com/gophercloud/gophercloud/doc.go | 2 +- .../gophercloud/gophercloud/errors.go | 63 ++++++- .../github.com/gophercloud/gophercloud/go.mod | 7 + .../github.com/gophercloud/gophercloud/go.sum | 8 + .../gophercloud/openstack/auth_env.go | 61 ++++-- .../gophercloud/openstack/client.go | 88 ++++++--- .../openstack/endpoint_location.go | 2 +- .../openstack/identity/v2/tenants/requests.go | 2 +- .../openstack/identity/v2/tokens/results.go | 15 ++ .../openstack/identity/v3/tokens/requests.go | 96 +++------- .../openstack/identity/v3/tokens/results.go | 8 + .../objectstorage/v1/accounts/requests.go | 2 +- .../objectstorage/v1/accounts/results.go | 13 ++ .../objectstorage/v1/containers/doc.go | 4 + .../objectstorage/v1/containers/requests.go | 39 +++- .../objectstorage/v1/containers/results.go | 1 + .../openstack/objectstorage/v1/objects/doc.go | 4 + .../objectstorage/v1/objects/requests.go | 66 +++++-- .../objectstorage/v1/objects/results.go | 59 ++++-- .../gophercloud/openstack/utils/BUILD.bazel | 5 +- .../openstack/utils/base_endpoint.go | 28 +++ .../gophercloud/pagination/pager.go | 32 +++- .../gophercloud/gophercloud/params.go | 28 ++- .../gophercloud/provider_client.go | 177 +++++++++++++++--- .../gophercloud/gophercloud/results.go | 88 +++++++-- .../gophercloud/gophercloud/service_client.go | 28 +++ .../vendor/k8s.io/kops/pkg/try/BUILD.bazel | 10 + .../vendor/k8s.io/kops/pkg/try/files.go | 37 ++++ .../k8s.io/kops/util/pkg/hashing/BUILD.bazel | 5 +- .../k8s.io/kops/util/pkg/hashing/hash.go | 4 +- .../k8s.io/kops/util/pkg/vfs/BUILD.bazel | 2 + .../k8s.io/kops/util/pkg/vfs/context.go | 2 +- .../vendor/k8s.io/kops/util/pkg/vfs/fs.go | 3 +- .../vendor/k8s.io/kops/util/pkg/vfs/gsfs.go | 3 +- .../k8s.io/kops/util/pkg/vfs/s3context.go | 143 +++++++++++--- .../vendor/k8s.io/kops/util/pkg/vfs/s3fs.go | 5 +- .../k8s.io/kops/util/pkg/vfs/swiftfs.go | 74 +++++++- 50 files changed, 1256 insertions(+), 560 deletions(-) delete mode 100644 etcd-manager/vendor/github.com/gophercloud/gophercloud/FAQ.md delete mode 100644 etcd-manager/vendor/github.com/gophercloud/gophercloud/MIGRATING.md delete mode 100644 etcd-manager/vendor/github.com/gophercloud/gophercloud/STYLEGUIDE.md create mode 100644 etcd-manager/vendor/github.com/gophercloud/gophercloud/auth_result.go create mode 100644 etcd-manager/vendor/github.com/gophercloud/gophercloud/go.mod create mode 100644 etcd-manager/vendor/github.com/gophercloud/gophercloud/go.sum create mode 100644 etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/utils/base_endpoint.go create mode 100644 etcd-manager/vendor/k8s.io/kops/pkg/try/BUILD.bazel create mode 100644 etcd-manager/vendor/k8s.io/kops/pkg/try/files.go diff --git a/etcd-manager/Gopkg.lock b/etcd-manager/Gopkg.lock index 558dfb821..7e589e120 100644 --- a/etcd-manager/Gopkg.lock +++ b/etcd-manager/Gopkg.lock @@ -125,8 +125,7 @@ revision = "925541529c1fa6821df4e44ce2723319eb2be768" [[projects]] - branch = "master" - digest = "1:ba8796b3d336ef3cb61583a8b03a6e5361deed8c7b7cc1c89c32d71fe401e9c1" + digest = "1:904273e755c15a07aeffa1fa4ca233f7e37c10982e23db1399bc08c14ae180ba" name = "github.com/gophercloud/gophercloud" packages = [ ".", @@ -141,7 +140,7 @@ "pagination", ] pruneopts = "UT" - revision = "4a3f5ae58624b68283375060dad06a214b05a32b" + revision = "dcc6e84aef1b6713accea7d7d7252ad2bc0e7034" [[projects]] digest = "1:e22af8c7518e1eab6f2eab2b7d7558927f816262586cd6ed9f349c97a6c285c4" @@ -373,15 +372,15 @@ version = "v0.1.0" [[projects]] - digest = "1:8a335574b2ecf993b2520eed2035e5f9d6ed562d2d0c659f0ca510686fc239e2" + digest = "1:5150b7e30f3bd379c9329339a3da210a948e6fafb861cb68d904eec96d3d5e3c" name = "k8s.io/kops" packages = [ + "pkg/try", "util/pkg/hashing", "util/pkg/vfs", ] pruneopts = "UT" - revision = "8b52ea6d1cac6913cb2fa8edda6e054a6fdc5f27" - version = "1.10.0" + revision = "24206f04f5eb8e1565b3e7edfbf40f84d5511e20" [[projects]] digest = "1:54f05d2b09982c6ce153e2157eef013d98133cfea437a992a1a40d702a8770e4" diff --git a/etcd-manager/Gopkg.toml b/etcd-manager/Gopkg.toml index f3a6264af..0a72d5b87 100644 --- a/etcd-manager/Gopkg.toml +++ b/etcd-manager/Gopkg.toml @@ -29,8 +29,12 @@ unused-packages = true [[constraint]] name = "k8s.io/kops" - version = "v1.10.0" + revision = "24206f04f5eb8e1565b3e7edfbf40f84d5511e20" # shortly before 1.12.0 [[constraint]] name = "k8s.io/kubernetes" version = "v1.12.1" + +[[override]] + name = "github.com/gophercloud/gophercloud" + revision = "dcc6e84aef1b6713accea7d7d7252ad2bc0e7034" diff --git a/etcd-manager/vendor/github.com/golang/protobuf/_conformance/conformance_proto/BUILD.bazel b/etcd-manager/vendor/github.com/golang/protobuf/_conformance/conformance_proto/BUILD.bazel index e5b329db4..cd3eb8073 100644 --- a/etcd-manager/vendor/github.com/golang/protobuf/_conformance/conformance_proto/BUILD.bazel +++ b/etcd-manager/vendor/github.com/golang/protobuf/_conformance/conformance_proto/BUILD.bazel @@ -13,6 +13,6 @@ go_library( "//vendor/github.com/golang/protobuf/ptypes/struct:go_default_library", "//vendor/github.com/golang/protobuf/ptypes/timestamp:go_default_library", "//vendor/github.com/golang/protobuf/ptypes/wrappers:go_default_library", - "@org_golang_google_genproto//protobuf:go_default_library", + "//vendor/google.golang.org/genproto/protobuf:go_default_library", ], ) diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/.gitignore b/etcd-manager/vendor/github.com/gophercloud/gophercloud/.gitignore index df9048a01..dd91ed205 100644 --- a/etcd-manager/vendor/github.com/gophercloud/gophercloud/.gitignore +++ b/etcd-manager/vendor/github.com/gophercloud/gophercloud/.gitignore @@ -1,2 +1,3 @@ **/*.swp .idea +.vscode diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/.travis.yml b/etcd-manager/vendor/github.com/gophercloud/gophercloud/.travis.yml index 59c419495..0955f170e 100644 --- a/etcd-manager/vendor/github.com/gophercloud/gophercloud/.travis.yml +++ b/etcd-manager/vendor/github.com/gophercloud/gophercloud/.travis.yml @@ -1,21 +1,24 @@ language: go sudo: false install: -- go get golang.org/x/crypto/ssh -- go get -v -tags 'fixtures acceptance' ./... -- go get github.com/wadey/gocovmerge -- go get github.com/mattn/goveralls -- go get golang.org/x/tools/cmd/goimports +- GO111MODULE=off go get golang.org/x/crypto/ssh +- GO111MODULE=off go get -v -tags 'fixtures acceptance' ./... +- GO111MODULE=off go get github.com/wadey/gocovmerge +- GO111MODULE=off go get github.com/mattn/goveralls +- GO111MODULE=off go get golang.org/x/tools/cmd/goimports go: -- 1.8 -- tip +- "1.10" +- "1.11" +- "tip" env: global: - secure: "xSQsAG5wlL9emjbCdxzz/hYQsSpJ/bABO1kkbwMSISVcJ3Nk0u4ywF+LS4bgeOnwPfmFvNTOqVDu3RwEvMeWXSI76t1piCPcObutb2faKLVD/hLoAS76gYX+Z8yGWGHrSB7Do5vTPj1ERe2UljdrnsSeOXzoDwFxYRaZLX4bBOB4AyoGvRniil5QXPATiA1tsWX1VMicj8a4F8X+xeESzjt1Q5Iy31e7vkptu71bhvXCaoo5QhYwT+pLR9dN0S1b7Ro0KVvkRefmr1lUOSYd2e74h6Lc34tC1h3uYZCS4h47t7v5cOXvMNxinEj2C51RvbjvZI1RLVdkuAEJD1Iz4+Ote46nXbZ//6XRZMZz/YxQ13l7ux1PFjgEB6HAapmF5Xd8PRsgeTU9LRJxpiTJ3P5QJ3leS1va8qnziM5kYipj/Rn+V8g2ad/rgkRox9LSiR9VYZD2Pe45YCb1mTKSl2aIJnV7nkOqsShY5LNB4JZSg7xIffA+9YVDktw8dJlATjZqt7WvJJ49g6A61mIUV4C15q2JPGKTkZzDiG81NtmS7hFa7k0yaE2ELgYocbcuyUcAahhxntYTC0i23nJmEHVNiZmBO3u7EgpWe4KGVfumU+lt12tIn5b3dZRBBUk3QakKKozSK1QPHGpk/AZGrhu7H6l8to6IICKWtDcyMPQ=" + - GO111MODULE=on before_script: - go vet ./... script: - ./script/coverage +- ./script/unittest - ./script/format after_success: - $HOME/gopath/bin/goveralls -service=travis-ci -coverprofile=cover.out diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/.zuul.yaml b/etcd-manager/vendor/github.com/gophercloud/gophercloud/.zuul.yaml index c259d03e1..8c31ea160 100644 --- a/etcd-manager/vendor/github.com/gophercloud/gophercloud/.zuul.yaml +++ b/etcd-manager/vendor/github.com/gophercloud/gophercloud/.zuul.yaml @@ -1,3 +1,73 @@ +- job: + name: gophercloud-unittest + parent: golang-test + description: | + Run gophercloud unit test + run: .zuul/playbooks/gophercloud-unittest/run.yaml + nodeset: ubuntu-xenial-ut + +- job: + name: gophercloud-acceptance-test + parent: golang-test + description: | + Run gophercloud acceptance test on master branch + run: .zuul/playbooks/gophercloud-acceptance-test/run.yaml + +- job: + name: gophercloud-acceptance-test-queens + parent: gophercloud-acceptance-test + description: | + Run gophercloud acceptance test on queens branch + vars: + global_env: + OS_BRANCH: stable/queens + +- job: + name: gophercloud-acceptance-test-rocky + parent: gophercloud-acceptance-test + description: | + Run gophercloud acceptance test on rocky branch + vars: + global_env: + OS_BRANCH: stable/rocky + +- job: + name: gophercloud-acceptance-test-pike + parent: gophercloud-acceptance-test + description: | + Run gophercloud acceptance test on pike branch + vars: + global_env: + OS_BRANCH: stable/pike + +- job: + name: gophercloud-acceptance-test-ocata + parent: gophercloud-acceptance-test + description: | + Run gophercloud acceptance test on ocata branch + vars: + global_env: + OS_BRANCH: stable/ocata + +- job: + name: gophercloud-acceptance-test-newton + parent: gophercloud-acceptance-test + description: | + Run gophercloud acceptance test on newton branch + 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: @@ -7,6 +77,22 @@ recheck-mitaka: jobs: - gophercloud-acceptance-test-mitaka + recheck-newton: + jobs: + - gophercloud-acceptance-test-newton + recheck-ocata: + jobs: + - gophercloud-acceptance-test-ocata recheck-pike: jobs: - gophercloud-acceptance-test-pike + recheck-queens: + jobs: + - gophercloud-acceptance-test-queens + recheck-rocky: + jobs: + - gophercloud-acceptance-test-rocky + periodic: + jobs: + - gophercloud-unittest + - gophercloud-acceptance-test diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/BUILD.bazel b/etcd-manager/vendor/github.com/gophercloud/gophercloud/BUILD.bazel index 2749648fa..61c5c3f72 100644 --- a/etcd-manager/vendor/github.com/gophercloud/gophercloud/BUILD.bazel +++ b/etcd-manager/vendor/github.com/gophercloud/gophercloud/BUILD.bazel @@ -4,6 +4,7 @@ go_library( name = "go_default_library", srcs = [ "auth_options.go", + "auth_result.go", "doc.go", "endpoint_search.go", "errors.go", diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/FAQ.md b/etcd-manager/vendor/github.com/gophercloud/gophercloud/FAQ.md deleted file mode 100644 index 88a366a28..000000000 --- a/etcd-manager/vendor/github.com/gophercloud/gophercloud/FAQ.md +++ /dev/null @@ -1,148 +0,0 @@ -# Tips - -## Implementing default logging and re-authentication attempts - -You can implement custom logging and/or limit re-auth attempts by creating a custom HTTP client -like the following and setting it as the provider client's HTTP Client (via the -`gophercloud.ProviderClient.HTTPClient` field): - -```go -//... - -// LogRoundTripper satisfies the http.RoundTripper interface and is used to -// customize the default Gophercloud RoundTripper to allow for logging. -type LogRoundTripper struct { - rt http.RoundTripper - numReauthAttempts int -} - -// newHTTPClient return a custom HTTP client that allows for logging relevant -// information before and after the HTTP request. -func newHTTPClient() http.Client { - return http.Client{ - Transport: &LogRoundTripper{ - rt: http.DefaultTransport, - }, - } -} - -// RoundTrip performs a round-trip HTTP request and logs relevant information about it. -func (lrt *LogRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) { - glog.Infof("Request URL: %s\n", request.URL) - - response, err := lrt.rt.RoundTrip(request) - if response == nil { - return nil, err - } - - if response.StatusCode == http.StatusUnauthorized { - if lrt.numReauthAttempts == 3 { - return response, fmt.Errorf("Tried to re-authenticate 3 times with no success.") - } - lrt.numReauthAttempts++ - } - - glog.Debugf("Response Status: %s\n", response.Status) - - return response, nil -} - -endpoint := "https://127.0.0.1/auth" -pc := openstack.NewClient(endpoint) -pc.HTTPClient = newHTTPClient() - -//... -``` - - -## Implementing custom objects - -OpenStack request/response objects may differ among variable names or types. - -### Custom request objects - -To pass custom options to a request, implement the desired `OptsBuilder` interface. For -example, to pass in - -```go -type MyCreateServerOpts struct { - Name string - Size int -} -``` - -to `servers.Create`, simply implement the `servers.CreateOptsBuilder` interface: - -```go -func (o MyCreateServeropts) ToServerCreateMap() (map[string]interface{}, error) { - return map[string]interface{}{ - "name": o.Name, - "size": o.Size, - }, nil -} -``` - -create an instance of your custom options object, and pass it to `servers.Create`: - -```go -// ... -myOpts := MyCreateServerOpts{ - Name: "s1", - Size: "100", -} -server, err := servers.Create(computeClient, myOpts).Extract() -// ... -``` - -### Custom response objects - -Some OpenStack services have extensions. Extensions that are supported in Gophercloud can be -combined to create a custom object: - -```go -// ... -type MyVolume struct { - volumes.Volume - tenantattr.VolumeExt -} - -var v struct { - MyVolume `json:"volume"` -} - -err := volumes.Get(client, volID).ExtractInto(&v) -// ... -``` - -## Overriding default `UnmarshalJSON` method - -For some response objects, a field may be a custom type or may be allowed to take on -different types. In these cases, overriding the default `UnmarshalJSON` method may be -necessary. To do this, declare the JSON `struct` field tag as "-" and create an `UnmarshalJSON` -method on the type: - -```go -// ... -type MyVolume struct { - ID string `json: "id"` - TimeCreated time.Time `json: "-"` -} - -func (r *MyVolume) UnmarshalJSON(b []byte) error { - type tmp MyVolume - var s struct { - tmp - TimeCreated gophercloud.JSONRFC3339MilliNoZ `json:"created_at"` - } - err := json.Unmarshal(b, &s) - if err != nil { - return err - } - *r = Volume(s.tmp) - - r.TimeCreated = time.Time(s.CreatedAt) - - return err -} -// ... -``` diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/MIGRATING.md b/etcd-manager/vendor/github.com/gophercloud/gophercloud/MIGRATING.md deleted file mode 100644 index aa383c9cc..000000000 --- a/etcd-manager/vendor/github.com/gophercloud/gophercloud/MIGRATING.md +++ /dev/null @@ -1,32 +0,0 @@ -# Compute - -## Floating IPs - -* `github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingip` is now `github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips` -* `floatingips.Associate` and `floatingips.Disassociate` have been removed. -* `floatingips.DisassociateOpts` is now required to disassociate a Floating IP. - -## Security Groups - -* `secgroups.AddServerToGroup` is now `secgroups.AddServer`. -* `secgroups.RemoveServerFromGroup` is now `secgroups.RemoveServer`. - -## Servers - -* `servers.Reboot` now requires a `servers.RebootOpts` struct: - - ```golang - rebootOpts := &servers.RebootOpts{ - Type: servers.SoftReboot, - } - res := servers.Reboot(client, server.ID, rebootOpts) - ``` - -# Identity - -## V3 - -### Tokens - -* `Token.ExpiresAt` is now of type `gophercloud.JSONRFC3339Milli` instead of - `time.Time` diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/README.md b/etcd-manager/vendor/github.com/gophercloud/gophercloud/README.md index bb218c3fe..ad29041d9 100644 --- a/etcd-manager/vendor/github.com/gophercloud/gophercloud/README.md +++ b/etcd-manager/vendor/github.com/gophercloud/gophercloud/README.md @@ -127,7 +127,7 @@ new resource in the `server` variable (a ## Advanced Usage -Have a look at the [FAQ](./FAQ.md) for some tips on customizing the way Gophercloud works. +Have a look at the [FAQ](./docs/FAQ.md) for some tips on customizing the way Gophercloud works. ## Backwards-Compatibility Guarantees @@ -140,7 +140,7 @@ See the [contributing guide](./.github/CONTRIBUTING.md). ## Help and feedback If you're struggling with something or have spotted a potential bug, feel free -to submit an issue to our [bug tracker](/issues). +to submit an issue to our [bug tracker](https://github.com/gophercloud/gophercloud/issues). ## Thank You @@ -148,12 +148,12 @@ We'd like to extend special thanks and appreciation to the following: ### OpenLab - + OpenLab is providing a full CI environment to test each PR and merge for a variety of OpenStack releases. ### VEXXHOST - + VEXXHOST is providing their services to assist with the development and testing of Gophercloud. diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/STYLEGUIDE.md b/etcd-manager/vendor/github.com/gophercloud/gophercloud/STYLEGUIDE.md deleted file mode 100644 index 22a290094..000000000 --- a/etcd-manager/vendor/github.com/gophercloud/gophercloud/STYLEGUIDE.md +++ /dev/null @@ -1,79 +0,0 @@ - -## On Pull Requests - -- Please make sure to read our [contributing guide](/.github/CONTRIBUTING.md). - -- Before you start a PR there needs to be a Github issue and a discussion about it - on that issue with a core contributor, even if it's just a 'SGTM'. - -- A PR's description must reference the issue it closes with a `For ` (e.g. For #293). - -- A PR's description must contain link(s) to the line(s) in the OpenStack - source code (on Github) that prove(s) the PR code to be valid. Links to documentation - are not good enough. The link(s) should be to a non-`master` branch. For example, - a pull request implementing the creation of a Neutron v2 subnet might put the - following link in the description: - - https://github.com/openstack/neutron/blob/stable/mitaka/neutron/api/v2/attributes.py#L749 - - From that link, a reviewer (or user) can verify the fields in the request/response - objects in the PR. - -- A PR that is in-progress should have `[wip]` in front of the PR's title. When - ready for review, remove the `[wip]` and ping a core contributor with an `@`. - -- Forcing PRs to be small can have the effect of users submitting PRs in a hierarchical chain, with - one depending on the next. If a PR depends on another one, it should have a [Pending #PRNUM] - prefix in the PR title. In addition, it will be the PR submitter's responsibility to remove the - [Pending #PRNUM] tag once the PR has been updated with the merged, dependent PR. That will - let reviewers know it is ready to review. - -- A PR should be small. Even if you intend on implementing an entire - service, a PR should only be one route of that service - (e.g. create server or get server, but not both). - -- Unless explicitly asked, do not squash commits in the middle of a review; only - append. It makes it difficult for the reviewer to see what's changed from one - review to the next. - -- See [#583](https://github.com/gophercloud/gophercloud/issues/583) as an example of a - well-formatted issue which contains all relevant information we need to review and approve. - -## On Code - -- In re design: follow as closely as is reasonable the code already in the library. - Most operations (e.g. create, delete) admit the same design. - -- Unit tests and acceptance (integration) tests must be written to cover each PR. - Tests for operations with several options (e.g. list, create) should include all - the options in the tests. This will allow users to verify an operation on their - own infrastructure and see an example of usage. - -- If in doubt, ask in-line on the PR. - -### File Structure - -- The following should be used in most cases: - - - `requests.go`: contains all the functions that make HTTP requests and the - types associated with the HTTP request (parameters for URL, body, etc) - - `results.go`: contains all the response objects and their methods - - `urls.go`: contains the endpoints to which the requests are made - -### Naming - -- For methods on a type in `results.go`, the receiver should be named `r` and the - variable into which it will be unmarshalled `s`. - -- Functions in `requests.go`, with the exception of functions that return a - `pagination.Pager`, should be named returns of the name `r`. - -- Functions in `requests.go` that accept request bodies should accept as their - last parameter an `interface` named `OptsBuilder` (eg `CreateOptsBuilder`). - This `interface` should have at the least a method named `ToMap` - (eg `ToPortCreateMap`). - -- Functions in `requests.go` that accept query strings should accept as their - last parameter an `interface` named `OptsBuilder` (eg `ListOptsBuilder`). - This `interface` should have at the least a method named `ToQuery` - (eg `ToServerListQuery`). diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/auth_options.go b/etcd-manager/vendor/github.com/gophercloud/gophercloud/auth_options.go index 421147002..5ffa8d1e0 100644 --- a/etcd-manager/vendor/github.com/gophercloud/gophercloud/auth_options.go +++ b/etcd-manager/vendor/github.com/gophercloud/gophercloud/auth_options.go @@ -81,6 +81,23 @@ type AuthOptions struct { // TokenID allows users to authenticate (possibly as another user) with an // authentication token ID. TokenID string `json:"-"` + + // Scope determines the scoping of the authentication request. + Scope *AuthScope `json:"-"` + + // Authentication through Application Credentials requires supplying name, project and secret + // For project we can use TenantID + ApplicationCredentialID string `json:"-"` + ApplicationCredentialName string `json:"-"` + ApplicationCredentialSecret string `json:"-"` +} + +// AuthScope allows a created token to be limited to a specific domain or project. +type AuthScope struct { + ProjectID string + ProjectName string + DomainID string + DomainName string } // ToTokenV2CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder @@ -131,7 +148,7 @@ 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"` + Password string `json:"password,omitempty"` Domain *domainReq `json:"domain,omitempty"` } @@ -143,10 +160,18 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s ID string `json:"id"` } + type applicationCredentialReq struct { + ID *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + User *userReq `json:"user,omitempty"` + Secret *string `json:"secret,omitempty"` + } + type identityReq struct { - Methods []string `json:"methods"` - Password *passwordReq `json:"password,omitempty"` - Token *tokenReq `json:"token,omitempty"` + Methods []string `json:"methods"` + Password *passwordReq `json:"password,omitempty"` + Token *tokenReq `json:"token,omitempty"` + ApplicationCredential *applicationCredentialReq `json:"application_credential,omitempty"` } type authReq struct { @@ -183,8 +208,68 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s req.Auth.Identity.Token = &tokenReq{ ID: opts.TokenID, } + + } else if opts.ApplicationCredentialID != "" { + // Configure the request for ApplicationCredentialID authentication. + // https://github.com/openstack/keystoneauth/blob/stable/rocky/keystoneauth1/identity/v3/application_credential.py#L48-L67 + // There are three kinds of possible application_credential requests + // 1. application_credential id + secret + // 2. application_credential name + secret + user_id + // 3. application_credential name + secret + username + domain_id / domain_name + if opts.ApplicationCredentialSecret == "" { + return nil, ErrAppCredMissingSecret{} + } + req.Auth.Identity.Methods = []string{"application_credential"} + req.Auth.Identity.ApplicationCredential = &applicationCredentialReq{ + ID: &opts.ApplicationCredentialID, + Secret: &opts.ApplicationCredentialSecret, + } + } else if opts.ApplicationCredentialName != "" { + if opts.ApplicationCredentialSecret == "" { + return nil, ErrAppCredMissingSecret{} + } + + var userRequest *userReq + + if opts.UserID != "" { + // UserID could be used without the domain information + userRequest = &userReq{ + ID: &opts.UserID, + } + } + + if userRequest == nil && opts.Username == "" { + // Make sure that Username or UserID are provided + return nil, ErrUsernameOrUserID{} + } + + if userRequest == nil && opts.DomainID != "" { + userRequest = &userReq{ + Name: &opts.Username, + Domain: &domainReq{ID: &opts.DomainID}, + } + } + + if userRequest == nil && opts.DomainName != "" { + userRequest = &userReq{ + Name: &opts.Username, + Domain: &domainReq{Name: &opts.DomainName}, + } + } + + // Make sure that DomainID or DomainName are provided among Username + if userRequest == nil { + return nil, ErrDomainIDOrDomainName{} + } + + req.Auth.Identity.Methods = []string{"application_credential"} + req.Auth.Identity.ApplicationCredential = &applicationCredentialReq{ + Name: &opts.ApplicationCredentialName, + User: userRequest, + Secret: &opts.ApplicationCredentialSecret, + } } else { - // If no password or token ID are available, authentication can't continue. + // If no password or token ID or ApplicationCredential are available, authentication can't continue. return nil, ErrMissingPassword{} } } else { @@ -263,85 +348,83 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s } func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) { - - var scope struct { - ProjectID string - ProjectName string - DomainID string - DomainName string - } - - if opts.TenantID != "" { - scope.ProjectID = opts.TenantID - } else { - if opts.TenantName != "" { - scope.ProjectName = opts.TenantName - scope.DomainID = opts.DomainID - scope.DomainName = opts.DomainName + // For backwards compatibility. + // If AuthOptions.Scope was not set, try to determine it. + // This works well for common scenarios. + if opts.Scope == nil { + opts.Scope = new(AuthScope) + if opts.TenantID != "" { + opts.Scope.ProjectID = opts.TenantID + } else { + if opts.TenantName != "" { + opts.Scope.ProjectName = opts.TenantName + opts.Scope.DomainID = opts.DomainID + opts.Scope.DomainName = opts.DomainName + } } } - if scope.ProjectName != "" { + if opts.Scope.ProjectName != "" { // ProjectName provided: either DomainID or DomainName must also be supplied. // ProjectID may not be supplied. - if scope.DomainID == "" && scope.DomainName == "" { + if opts.Scope.DomainID == "" && opts.Scope.DomainName == "" { return nil, ErrScopeDomainIDOrDomainName{} } - if scope.ProjectID != "" { + if opts.Scope.ProjectID != "" { return nil, ErrScopeProjectIDOrProjectName{} } - if scope.DomainID != "" { + if opts.Scope.DomainID != "" { // ProjectName + DomainID return map[string]interface{}{ "project": map[string]interface{}{ - "name": &scope.ProjectName, - "domain": map[string]interface{}{"id": &scope.DomainID}, + "name": &opts.Scope.ProjectName, + "domain": map[string]interface{}{"id": &opts.Scope.DomainID}, }, }, nil } - if scope.DomainName != "" { + if opts.Scope.DomainName != "" { // ProjectName + DomainName return map[string]interface{}{ "project": map[string]interface{}{ - "name": &scope.ProjectName, - "domain": map[string]interface{}{"name": &scope.DomainName}, + "name": &opts.Scope.ProjectName, + "domain": map[string]interface{}{"name": &opts.Scope.DomainName}, }, }, nil } - } else if scope.ProjectID != "" { + } else if opts.Scope.ProjectID != "" { // ProjectID provided. ProjectName, DomainID, and DomainName may not be provided. - if scope.DomainID != "" { + if opts.Scope.DomainID != "" { return nil, ErrScopeProjectIDAlone{} } - if scope.DomainName != "" { + if opts.Scope.DomainName != "" { return nil, ErrScopeProjectIDAlone{} } // ProjectID return map[string]interface{}{ "project": map[string]interface{}{ - "id": &scope.ProjectID, + "id": &opts.Scope.ProjectID, }, }, nil - } else if scope.DomainID != "" { + } else if opts.Scope.DomainID != "" { // DomainID provided. ProjectID, ProjectName, and DomainName may not be provided. - if scope.DomainName != "" { + if opts.Scope.DomainName != "" { return nil, ErrScopeDomainIDOrDomainName{} } // DomainID return map[string]interface{}{ "domain": map[string]interface{}{ - "id": &scope.DomainID, + "id": &opts.Scope.DomainID, }, }, nil - } else if scope.DomainName != "" { + } else if opts.Scope.DomainName != "" { // DomainName return map[string]interface{}{ "domain": map[string]interface{}{ - "name": &scope.DomainName, + "name": &opts.Scope.DomainName, }, }, nil } diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/auth_result.go b/etcd-manager/vendor/github.com/gophercloud/gophercloud/auth_result.go new file mode 100644 index 000000000..2e4699b97 --- /dev/null +++ b/etcd-manager/vendor/github.com/gophercloud/gophercloud/auth_result.go @@ -0,0 +1,52 @@ +package gophercloud + +/* +AuthResult is the result from the request that was used to obtain a provider +client's Keystone token. It is returned from ProviderClient.GetAuthResult(). + +The following types satisfy this interface: + + github.com/gophercloud/gophercloud/openstack/identity/v2/tokens.CreateResult + github.com/gophercloud/gophercloud/openstack/identity/v3/tokens.CreateResult + +Usage example: + + import ( + "github.com/gophercloud/gophercloud" + tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens" + tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens" + ) + + func GetAuthenticatedUserID(providerClient *gophercloud.ProviderClient) (string, error) { + r := providerClient.GetAuthResult() + if r == nil { + //ProviderClient did not use openstack.Authenticate(), e.g. because token + //was set manually with ProviderClient.SetToken() + return "", errors.New("no AuthResult available") + } + switch r := r.(type) { + case tokens2.CreateResult: + u, err := r.ExtractUser() + if err != nil { + return "", err + } + return u.ID, nil + case tokens3.CreateResult: + u, err := r.ExtractUser() + if err != nil { + return "", err + } + return u.ID, nil + default: + panic(fmt.Sprintf("got unexpected AuthResult type %t", r)) + } + } + +Both implementing types share a lot of methods by name, like ExtractUser() in +this example. But those methods cannot be part of the AuthResult interface +because the return types are different (in this case, type tokens2.User vs. +type tokens3.User). +*/ +type AuthResult interface { + ExtractTokenID() (string, error) +} diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/doc.go b/etcd-manager/vendor/github.com/gophercloud/gophercloud/doc.go index 30067aa35..131cc8e30 100644 --- a/etcd-manager/vendor/github.com/gophercloud/gophercloud/doc.go +++ b/etcd-manager/vendor/github.com/gophercloud/gophercloud/doc.go @@ -41,7 +41,7 @@ pass in the parent provider, like so: opts := gophercloud.EndpointOpts{Region: "RegionOne"} - client := openstack.NewComputeV2(provider, opts) + client, err := openstack.NewComputeV2(provider, opts) Resources diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/errors.go b/etcd-manager/vendor/github.com/gophercloud/gophercloud/errors.go index 88fd2ac67..4bf102468 100644 --- a/etcd-manager/vendor/github.com/gophercloud/gophercloud/errors.go +++ b/etcd-manager/vendor/github.com/gophercloud/gophercloud/errors.go @@ -1,6 +1,9 @@ package gophercloud -import "fmt" +import ( + "fmt" + "strings" +) // BaseError is an error type that all other error types embed. type BaseError struct { @@ -43,6 +46,33 @@ func (e ErrInvalidInput) Error() string { return e.choseErrString() } +// ErrMissingEnvironmentVariable is the error when environment variable is required +// in a particular situation but not provided by the user +type ErrMissingEnvironmentVariable struct { + BaseError + EnvironmentVariable string +} + +func (e ErrMissingEnvironmentVariable) Error() string { + e.DefaultErrString = fmt.Sprintf("Missing environment variable [%s]", e.EnvironmentVariable) + return e.choseErrString() +} + +// ErrMissingAnyoneOfEnvironmentVariables is the error when anyone of the environment variables +// is required in a particular situation but not provided by the user +type ErrMissingAnyoneOfEnvironmentVariables struct { + BaseError + EnvironmentVariables []string +} + +func (e ErrMissingAnyoneOfEnvironmentVariables) Error() string { + e.DefaultErrString = fmt.Sprintf( + "Missing one of the following environment variables [%s]", + strings.Join(e.EnvironmentVariables, ", "), + ) + return e.choseErrString() +} + // ErrUnexpectedResponseCode is returned by the Request method when a response code other than // those listed in OkCodes is encountered. type ErrUnexpectedResponseCode struct { @@ -72,6 +102,11 @@ type ErrDefault401 struct { ErrUnexpectedResponseCode } +// ErrDefault403 is the default error type returned on a 403 HTTP response code. +type ErrDefault403 struct { + ErrUnexpectedResponseCode +} + // ErrDefault404 is the default error type returned on a 404 HTTP response code. type ErrDefault404 struct { ErrUnexpectedResponseCode @@ -103,11 +138,22 @@ type ErrDefault503 struct { } func (e ErrDefault400) Error() string { - return "Invalid request due to incorrect syntax or missing required parameters." + e.DefaultErrString = fmt.Sprintf( + "Bad request with: [%s %s], error message: %s", + e.Method, e.URL, e.Body, + ) + return e.choseErrString() } func (e ErrDefault401) Error() string { return "Authentication failed" } +func (e ErrDefault403) Error() string { + e.DefaultErrString = fmt.Sprintf( + "Request forbidden: [%s %s], error message: %s", + e.Method, e.URL, e.Body, + ) + return e.choseErrString() +} func (e ErrDefault404) Error() string { return "Resource not found" } @@ -141,6 +187,12 @@ type Err401er interface { Error401(ErrUnexpectedResponseCode) error } +// Err403er is the interface resource error types implement to override the error message +// from a 403 error. +type Err403er interface { + Error403(ErrUnexpectedResponseCode) error +} + // Err404er is the interface resource error types implement to override the error message // from a 404 error. type Err404er interface { @@ -399,3 +451,10 @@ type ErrScopeEmpty struct{ BaseError } func (e ErrScopeEmpty) Error() string { return "You must provide either a Project or Domain in a Scope" } + +// ErrAppCredMissingSecret indicates that no Application Credential Secret was provided with Application Credential ID or Name +type ErrAppCredMissingSecret struct{ BaseError } + +func (e ErrAppCredMissingSecret) Error() string { + return "You must provide an Application Credential Secret" +} diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/go.mod b/etcd-manager/vendor/github.com/gophercloud/gophercloud/go.mod new file mode 100644 index 000000000..d1ee3b472 --- /dev/null +++ b/etcd-manager/vendor/github.com/gophercloud/gophercloud/go.mod @@ -0,0 +1,7 @@ +module github.com/gophercloud/gophercloud + +require ( + golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67 + golang.org/x/sys v0.0.0-20190209173611-3b5209105503 // indirect + gopkg.in/yaml.v2 v2.2.2 +) diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/go.sum b/etcd-manager/vendor/github.com/gophercloud/gophercloud/go.sum new file mode 100644 index 000000000..33cb0be8a --- /dev/null +++ b/etcd-manager/vendor/github.com/gophercloud/gophercloud/go.sum @@ -0,0 +1,8 @@ +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67 h1:ng3VDlRp5/DHpSWl02R4rM9I+8M2rhmsuLwAMmkLQWE= +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/sys v0.0.0-20190209173611-3b5209105503 h1:5SvYFrOM3W8Mexn9/oA44Ji7vhXAZQ9hiP+1Q/DMrWg= +golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/auth_env.go b/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/auth_env.go index b5482ba8c..0bb1f4837 100644 --- a/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/auth_env.go +++ b/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/auth_env.go @@ -38,6 +38,9 @@ func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) { tenantName := os.Getenv("OS_TENANT_NAME") domainID := os.Getenv("OS_DOMAIN_ID") domainName := os.Getenv("OS_DOMAIN_NAME") + applicationCredentialID := os.Getenv("OS_APPLICATION_CREDENTIAL_ID") + applicationCredentialName := os.Getenv("OS_APPLICATION_CREDENTIAL_NAME") + applicationCredentialSecret := os.Getenv("OS_APPLICATION_CREDENTIAL_SECRET") // If OS_PROJECT_ID is set, overwrite tenantID with the value. if v := os.Getenv("OS_PROJECT_ID"); v != "" { @@ -50,29 +53,61 @@ func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) { } if authURL == "" { - err := gophercloud.ErrMissingInput{Argument: "authURL"} + err := gophercloud.ErrMissingEnvironmentVariable{ + EnvironmentVariable: "OS_AUTH_URL", + } return nilOptions, err } - if username == "" && userID == "" { - err := gophercloud.ErrMissingInput{Argument: "username"} + if userID == "" && username == "" { + // Empty username and userID could be ignored, when applicationCredentialID and applicationCredentialSecret are set + if applicationCredentialID == "" && applicationCredentialSecret == "" { + err := gophercloud.ErrMissingAnyoneOfEnvironmentVariables{ + EnvironmentVariables: []string{"OS_USERID", "OS_USERNAME"}, + } + return nilOptions, err + } + } + + if password == "" && applicationCredentialID == "" && applicationCredentialName == "" { + err := gophercloud.ErrMissingEnvironmentVariable{ + EnvironmentVariable: "OS_PASSWORD", + } return nilOptions, err } - if password == "" { - err := gophercloud.ErrMissingInput{Argument: "password"} + if (applicationCredentialID != "" || applicationCredentialName != "") && applicationCredentialSecret == "" { + err := gophercloud.ErrMissingEnvironmentVariable{ + EnvironmentVariable: "OS_APPLICATION_CREDENTIAL_SECRET", + } return nilOptions, err } + if applicationCredentialID == "" && applicationCredentialName != "" && applicationCredentialSecret != "" { + if userID == "" && username == "" { + return nilOptions, gophercloud.ErrMissingAnyoneOfEnvironmentVariables{ + EnvironmentVariables: []string{"OS_USERID", "OS_USERNAME"}, + } + } + if username != "" && domainID == "" && domainName == "" { + return nilOptions, gophercloud.ErrMissingAnyoneOfEnvironmentVariables{ + EnvironmentVariables: []string{"OS_DOMAIN_ID", "OS_DOMAIN_NAME"}, + } + } + } + ao := gophercloud.AuthOptions{ - IdentityEndpoint: authURL, - UserID: userID, - Username: username, - Password: password, - TenantID: tenantID, - TenantName: tenantName, - DomainID: domainID, - DomainName: domainName, + IdentityEndpoint: authURL, + UserID: userID, + Username: username, + Password: password, + TenantID: tenantID, + TenantName: tenantName, + DomainID: domainID, + DomainName: domainName, + ApplicationCredentialID: applicationCredentialID, + ApplicationCredentialName: applicationCredentialName, + ApplicationCredentialSecret: applicationCredentialSecret, } return ao, nil diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/client.go b/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/client.go index 5a52e5791..064a70dbe 100644 --- a/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/client.go +++ b/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/client.go @@ -2,10 +2,7 @@ package openstack import ( "fmt" - "net/url" "reflect" - "regexp" - "strings" "github.com/gophercloud/gophercloud" tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens" @@ -38,21 +35,11 @@ A basic example of using this would be: client, err := openstack.NewIdentityV3(provider, gophercloud.EndpointOpts{}) */ func NewClient(endpoint string) (*gophercloud.ProviderClient, error) { - u, err := url.Parse(endpoint) + base, err := utils.BaseEndpoint(endpoint) if err != nil { return nil, err } - u.RawQuery, u.Fragment = "", "" - - var base string - versionRe := regexp.MustCompile("v[0-9.]+/?") - if version := versionRe.FindString(u.Path); version != "" { - base = strings.Replace(u.String(), version, "", -1) - } else { - base = u.String() - } - endpoint = gophercloud.NormalizeURL(endpoint) base = gophercloud.NormalizeURL(base) @@ -148,7 +135,7 @@ func v2auth(client *gophercloud.ProviderClient, endpoint string, options gopherc result := tokens2.Create(v2Client, v2Opts) - token, err := result.ExtractToken() + err = client.SetTokenAndAuthResult(result) if err != nil { return err } @@ -163,8 +150,9 @@ func v2auth(client *gophercloud.ProviderClient, endpoint string, options gopherc // with the token and reauth func zeroed out. combined with setting `AllowReauth` to `false`, // this should retry authentication only once tac := *client + tac.SetThrowaway(true) tac.ReauthFunc = nil - tac.TokenID = "" + tac.SetTokenAndAuthResult(nil) tao := options tao.AllowReauth = false client.ReauthFunc = func() error { @@ -172,11 +160,10 @@ func v2auth(client *gophercloud.ProviderClient, endpoint string, options gopherc if err != nil { return err } - client.TokenID = tac.TokenID + client.CopyTokenFrom(&tac) return nil } } - client.TokenID = token.ID client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) { return V2EndpointURL(catalog, opts) } @@ -202,7 +189,7 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.Au result := tokens3.Create(v3Client, opts) - token, err := result.ExtractToken() + err = client.SetTokenAndAuthResult(result) if err != nil { return err } @@ -212,15 +199,14 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.Au return err } - client.TokenID = token.ID - if opts.CanReauth() { // here we're creating a throw-away client (tac). it's a copy of the user's provider client, but // with the token and reauth func zeroed out. combined with setting `AllowReauth` to `false`, // this should retry authentication only once tac := *client + tac.SetThrowaway(true) tac.ReauthFunc = nil - tac.TokenID = "" + tac.SetTokenAndAuthResult(nil) var tao tokens3.AuthOptionsBuilder switch ot := opts.(type) { case *gophercloud.AuthOptions: @@ -239,7 +225,7 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.Au if err != nil { return err } - client.TokenID = tac.TokenID + client.CopyTokenFrom(&tac) return nil } } @@ -287,11 +273,17 @@ func NewIdentityV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOp // Ensure endpoint still has a suffix of v3. // This is because EndpointLocator might have found a versionless - // endpoint and requests will fail unless targeted at /v3. - if !strings.HasSuffix(endpoint, "v3/") { - endpoint = endpoint + "v3/" + // endpoint or the published endpoint is still /v2.0. In both + // cases, we need to fix the endpoint to point to /v3. + base, err := utils.BaseEndpoint(endpoint) + if err != nil { + return nil, err } + base = gophercloud.NormalizeURL(base) + + endpoint = base + "v3/" + return &gophercloud.ServiceClient{ ProviderClient: client, Endpoint: endpoint, @@ -312,6 +304,12 @@ func initClientOpts(client *gophercloud.ProviderClient, eo gophercloud.EndpointO return sc, nil } +// NewBareMetalV1 creates a ServiceClient that may be used with the v1 +// bare metal package. +func NewBareMetalV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(client, eo, "baremetal") +} + // NewObjectStorageV1 creates a ServiceClient that may be used with the v1 // object storage package. func NewObjectStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { @@ -394,3 +392,41 @@ func NewLoadBalancerV2(client *gophercloud.ProviderClient, eo gophercloud.Endpoi sc.ResourceBase = sc.Endpoint + "v2.0/" return sc, err } + +// NewClusteringV1 creates a ServiceClient that may be used with the v1 clustering +// package. +func NewClusteringV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(client, eo, "clustering") +} + +// NewMessagingV2 creates a ServiceClient that may be used with the v2 messaging +// service. +func NewMessagingV2(client *gophercloud.ProviderClient, clientID string, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + sc, err := initClientOpts(client, eo, "messaging") + sc.MoreHeaders = map[string]string{"Client-ID": clientID} + return sc, err +} + +// NewContainerV1 creates a ServiceClient that may be used with v1 container package +func NewContainerV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(client, eo, "container") +} + +// NewKeyManagerV1 creates a ServiceClient that may be used with the v1 key +// manager service. +func NewKeyManagerV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + sc, err := initClientOpts(client, eo, "key-manager") + sc.ResourceBase = sc.Endpoint + "v1/" + return sc, err +} + +// NewContainerInfraV1 creates a ServiceClient that may be used with the v1 container infra management +// package. +func NewContainerInfraV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(client, eo, "container-infra") +} + +// NewWorkflowV2 creates a ServiceClient that may be used with the v2 workflow management package. +func NewWorkflowV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(client, eo, "workflowv2") +} diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/endpoint_location.go b/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/endpoint_location.go index 070ea7cbe..12c8aebcf 100644 --- a/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/endpoint_location.go +++ b/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/endpoint_location.go @@ -84,7 +84,7 @@ func V3EndpointURL(catalog *tokens3.ServiceCatalog, opts gophercloud.EndpointOpt return "", err } if (opts.Availability == gophercloud.Availability(endpoint.Interface)) && - (opts.Region == "" || endpoint.Region == opts.Region) { + (opts.Region == "" || endpoint.Region == opts.Region || endpoint.RegionID == opts.Region) { endpoints = append(endpoints, endpoint) } } diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/requests.go b/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/requests.go index 60f58c8ce..f21a58f10 100644 --- a/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/requests.go +++ b/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/requests.go @@ -85,7 +85,7 @@ type UpdateOpts struct { Name string `json:"name,omitempty"` // Description is the description of the tenant. - Description string `json:"description,omitempty"` + Description *string `json:"description,omitempty"` // Enabled sets the tenant status to enabled or disabled. Enabled *bool `json:"enabled,omitempty"` diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/results.go b/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/results.go index b11326772..ee5da37f4 100644 --- a/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/results.go +++ b/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/results.go @@ -135,6 +135,21 @@ func (r CreateResult) ExtractToken() (*Token, error) { }, nil } +// ExtractTokenID implements the gophercloud.AuthResult interface. The returned +// string is the same as the ID field of the Token struct returned from +// ExtractToken(). +func (r CreateResult) ExtractTokenID() (string, error) { + var s struct { + Access struct { + Token struct { + ID string `json:"id"` + } `json:"token"` + } `json:"access"` + } + err := r.ExtractInto(&s) + return s.Access.Token.ID, err +} + // ExtractServiceCatalog returns the ServiceCatalog that was generated along // with the user's Token. func (r CreateResult) ExtractServiceCatalog() (*ServiceCatalog, error) { diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go b/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go index ca35851e4..2d20fa6f4 100644 --- a/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go +++ b/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go @@ -52,19 +52,28 @@ type AuthOptions struct { // authentication token ID. TokenID string `json:"-"` + // Authentication through Application Credentials requires supplying name, project and secret + // For project we can use TenantID + ApplicationCredentialID string `json:"-"` + ApplicationCredentialName string `json:"-"` + ApplicationCredentialSecret string `json:"-"` + Scope Scope `json:"-"` } // ToTokenV3CreateMap builds a request body from AuthOptions. func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) { gophercloudAuthOpts := gophercloud.AuthOptions{ - Username: opts.Username, - UserID: opts.UserID, - Password: opts.Password, - DomainID: opts.DomainID, - DomainName: opts.DomainName, - AllowReauth: opts.AllowReauth, - TokenID: opts.TokenID, + Username: opts.Username, + UserID: opts.UserID, + Password: opts.Password, + DomainID: opts.DomainID, + DomainName: opts.DomainName, + AllowReauth: opts.AllowReauth, + TokenID: opts.TokenID, + ApplicationCredentialID: opts.ApplicationCredentialID, + ApplicationCredentialName: opts.ApplicationCredentialName, + ApplicationCredentialSecret: opts.ApplicationCredentialSecret, } return gophercloudAuthOpts.ToTokenV3CreateMap(scope) @@ -72,72 +81,15 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s // ToTokenV3CreateMap builds a scope request body from AuthOptions. func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) { - if opts.Scope.ProjectName != "" { - // ProjectName provided: either DomainID or DomainName must also be supplied. - // ProjectID may not be supplied. - if opts.Scope.DomainID == "" && opts.Scope.DomainName == "" { - return nil, gophercloud.ErrScopeDomainIDOrDomainName{} - } - if opts.Scope.ProjectID != "" { - return nil, gophercloud.ErrScopeProjectIDOrProjectName{} - } - - if opts.Scope.DomainID != "" { - // ProjectName + DomainID - return map[string]interface{}{ - "project": map[string]interface{}{ - "name": &opts.Scope.ProjectName, - "domain": map[string]interface{}{"id": &opts.Scope.DomainID}, - }, - }, nil - } - - if opts.Scope.DomainName != "" { - // ProjectName + DomainName - return map[string]interface{}{ - "project": map[string]interface{}{ - "name": &opts.Scope.ProjectName, - "domain": map[string]interface{}{"name": &opts.Scope.DomainName}, - }, - }, nil - } - } else if opts.Scope.ProjectID != "" { - // ProjectID provided. ProjectName, DomainID, and DomainName may not be provided. - if opts.Scope.DomainID != "" { - return nil, gophercloud.ErrScopeProjectIDAlone{} - } - if opts.Scope.DomainName != "" { - return nil, gophercloud.ErrScopeProjectIDAlone{} - } - - // ProjectID - return map[string]interface{}{ - "project": map[string]interface{}{ - "id": &opts.Scope.ProjectID, - }, - }, nil - } else if opts.Scope.DomainID != "" { - // DomainID provided. ProjectID, ProjectName, and DomainName may not be provided. - if opts.Scope.DomainName != "" { - return nil, gophercloud.ErrScopeDomainIDOrDomainName{} - } - - // DomainID - return map[string]interface{}{ - "domain": map[string]interface{}{ - "id": &opts.Scope.DomainID, - }, - }, nil - } else if opts.Scope.DomainName != "" { - // DomainName - return map[string]interface{}{ - "domain": map[string]interface{}{ - "name": &opts.Scope.DomainName, - }, - }, nil + scope := gophercloud.AuthScope(opts.Scope) + + gophercloudAuthOpts := gophercloud.AuthOptions{ + Scope: &scope, + DomainID: opts.DomainID, + DomainName: opts.DomainName, } - return nil, nil + return gophercloudAuthOpts.ToTokenV3ScopeMap() } func (opts *AuthOptions) CanReauth() bool { @@ -190,7 +142,7 @@ func Get(c *gophercloud.ServiceClient, token string) (r GetResult) { // Validate determines if a specified token is valid or not. func Validate(c *gophercloud.ServiceClient, token string) (bool, error) { - resp, err := c.Request("HEAD", tokenURL(c), &gophercloud.RequestOpts{ + resp, err := c.Head(tokenURL(c), &gophercloud.RequestOpts{ MoreHeaders: subjectTokenHeaders(c, token), OkCodes: []int{200, 204, 404}, }) diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/results.go b/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/results.go index 6e78d1cbd..6f26c96bc 100644 --- a/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/results.go +++ b/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/results.go @@ -13,6 +13,7 @@ import ( type Endpoint struct { ID string `json:"id"` Region string `json:"region"` + RegionID string `json:"region_id"` Interface string `json:"interface"` URL string `json:"url"` } @@ -101,6 +102,13 @@ func (r commonResult) ExtractToken() (*Token, error) { return &s, err } +// ExtractTokenID implements the gophercloud.AuthResult interface. The returned +// string is the same as the ID field of the Token struct returned from +// ExtractToken(). +func (r CreateResult) ExtractTokenID() (string, error) { + return r.Header.Get("X-Subject-Token"), r.Err +} + // ExtractServiceCatalog returns the ServiceCatalog that was generated along // with the user's Token. func (r commonResult) ExtractServiceCatalog() (*ServiceCatalog, error) { diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/requests.go b/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/requests.go index df2158785..452a331c7 100644 --- a/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/requests.go +++ b/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/requests.go @@ -35,7 +35,7 @@ func Get(c *gophercloud.ServiceClient, opts GetOptsBuilder) (r GetResult) { h[k] = v } } - resp, err := c.Request("HEAD", getURL(c), &gophercloud.RequestOpts{ + resp, err := c.Head(getURL(c), &gophercloud.RequestOpts{ MoreHeaders: h, OkCodes: []int{204}, }) diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/results.go b/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/results.go index bf5dc846f..10661e671 100644 --- a/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/results.go +++ b/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/results.go @@ -63,6 +63,7 @@ func (r UpdateResult) Extract() (*UpdateHeader, error) { // GetHeader represents the headers returned in the response from a Get request. type GetHeader struct { BytesUsed int64 `json:"-"` + QuotaBytes *int64 `json:"-"` ContainerCount int64 `json:"-"` ContentLength int64 `json:"-"` ObjectCount int64 `json:"-"` @@ -78,6 +79,7 @@ func (r *GetHeader) UnmarshalJSON(b []byte) error { var s struct { tmp BytesUsed string `json:"X-Account-Bytes-Used"` + QuotaBytes string `json:"X-Account-Meta-Quota-Bytes"` ContentLength string `json:"Content-Length"` ContainerCount string `json:"X-Account-Container-Count"` ObjectCount string `json:"X-Account-Object-Count"` @@ -100,6 +102,17 @@ func (r *GetHeader) UnmarshalJSON(b []byte) error { } } + switch s.QuotaBytes { + case "": + r.QuotaBytes = nil + default: + v, err := strconv.ParseInt(s.QuotaBytes, 10, 64) + if err != nil { + return err + } + r.QuotaBytes = &v + } + switch s.ContentLength { case "": r.ContentLength = 0 diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/doc.go b/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/doc.go index 1ac8504de..9e5f66419 100644 --- a/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/doc.go +++ b/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/doc.go @@ -7,6 +7,10 @@ containers represents two different objects. In addition to containing objects, you can also use the container to control access to objects by using an access control list (ACL). +Note: When referencing the Object Storage API docs, some of the API actions +are listed under "accounts" rather than "containers". This was an intentional +design in Gophercloud to make some container actions feel more natural. + Example to List Containers listOpts := containers.ListOpts{ diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/requests.go b/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/requests.go index ecb76075b..89aa99683 100644 --- a/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/requests.go +++ b/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/requests.go @@ -74,6 +74,7 @@ type CreateOpts struct { DetectContentType bool `h:"X-Detect-Content-Type"` IfNoneMatch string `h:"If-None-Match"` VersionsLocation string `h:"X-Versions-Location"` + HistoryLocation string `h:"X-History-Location"` } // ToContainerCreateMap formats a CreateOpts into a map of headers. @@ -107,6 +108,7 @@ func Create(c *gophercloud.ServiceClient, containerName string, opts CreateOptsB }) if resp != nil { r.Header = resp.Header + resp.Body.Close() } r.Err = err return @@ -136,6 +138,8 @@ type UpdateOpts struct { DetectContentType bool `h:"X-Detect-Content-Type"` RemoveVersionsLocation string `h:"X-Remove-Versions-Location"` VersionsLocation string `h:"X-Versions-Location"` + RemoveHistoryLocation string `h:"X-Remove-History-Location"` + HistoryLocation string `h:"X-History-Location"` } // ToContainerUpdateMap formats a UpdateOpts into a map of headers. @@ -176,12 +180,41 @@ func Update(c *gophercloud.ServiceClient, containerName string, opts UpdateOptsB return } +// GetOptsBuilder allows extensions to add additional parameters to the Get +// request. +type GetOptsBuilder interface { + ToContainerGetMap() (map[string]string, error) +} + +// GetOpts is a structure that holds options for listing containers. +type GetOpts struct { + Newest bool `h:"X-Newest"` +} + +// ToContainerGetMap formats a GetOpts into a map of headers. +func (opts GetOpts) ToContainerGetMap() (map[string]string, error) { + return gophercloud.BuildHeaders(opts) +} + // Get is a function that retrieves the metadata of a container. To extract just // the custom metadata, pass the GetResult response to the ExtractMetadata // function. -func Get(c *gophercloud.ServiceClient, containerName string) (r GetResult) { - resp, err := c.Request("HEAD", getURL(c, containerName), &gophercloud.RequestOpts{ - OkCodes: []int{200, 204}, +func Get(c *gophercloud.ServiceClient, containerName string, opts GetOptsBuilder) (r GetResult) { + h := make(map[string]string) + if opts != nil { + headers, err := opts.ToContainerGetMap() + if err != nil { + r.Err = err + return + } + + for k, v := range headers { + h[k] = v + } + } + resp, err := c.Head(getURL(c, containerName), &gophercloud.RequestOpts{ + MoreHeaders: h, + OkCodes: []int{200, 204}, }) if resp != nil { r.Header = resp.Header diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/results.go b/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/results.go index e8c78880a..cce2190ff 100644 --- a/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/results.go +++ b/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/results.go @@ -100,6 +100,7 @@ type GetHeader struct { Read []string `json:"-"` TransID string `json:"X-Trans-Id"` VersionsLocation string `json:"X-Versions-Location"` + HistoryLocation string `json:"X-History-Location"` Write []string `json:"-"` StoragePolicy string `json:"X-Storage-Policy"` } diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/doc.go b/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/doc.go index 1e02430fb..e9b4b8a9f 100644 --- a/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/doc.go +++ b/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/doc.go @@ -4,6 +4,10 @@ object resources. An object is a resource that represents and contains data - such as documents, images, and so on. You can also store custom metadata with an object. +Note: When referencing the Object Storage API docs, some of the API actions +are listed under "containers" rather than "objects". This was an intentional +design in Gophercloud to make some object actions feel more natural. + Example to List Objects containerName := "my_container" diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/requests.go b/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/requests.go index f67bfd159..7325cd7d0 100644 --- a/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/requests.go +++ b/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/requests.go @@ -7,6 +7,7 @@ import ( "crypto/sha1" "fmt" "io" + "io/ioutil" "strings" "time" @@ -85,6 +86,7 @@ type DownloadOpts struct { IfModifiedSince time.Time `h:"If-Modified-Since"` IfNoneMatch string `h:"If-None-Match"` IfUnmodifiedSince time.Time `h:"If-Unmodified-Since"` + Newest bool `h:"X-Newest"` Range string `h:"Range"` Expires string `q:"expires"` MultipartManifest string `q:"multipart-manifest"` @@ -125,7 +127,7 @@ func Download(c *gophercloud.ServiceClient, containerName, objectName string, op resp, err := c.Get(url, nil, &gophercloud.RequestOpts{ MoreHeaders: h, - OkCodes: []int{200, 304}, + OkCodes: []int{200, 206, 304}, }) if resp != nil { r.Header = resp.Header @@ -145,6 +147,7 @@ type CreateOptsBuilder interface { type CreateOpts struct { Content io.Reader Metadata map[string]string + NoETag bool CacheControl string `h:"Cache-Control"` ContentDisposition string `h:"Content-Disposition"` ContentEncoding string `h:"Content-Encoding"` @@ -179,16 +182,37 @@ func (opts CreateOpts) ToObjectCreateParams() (io.Reader, map[string]string, str h["X-Object-Meta-"+k] = v } + if opts.NoETag { + delete(h, "etag") + return opts.Content, h, q.String(), nil + } + + if h["ETag"] != "" { + return opts.Content, h, q.String(), nil + } + + // When we're dealing with big files an io.ReadSeeker allows us to efficiently calculate + // the md5 sum. An io.Reader is only readable once which means we have to copy the entire + // file content into memory first. + readSeeker, isReadSeeker := opts.Content.(io.ReadSeeker) + if !isReadSeeker { + data, err := ioutil.ReadAll(opts.Content) + if err != nil { + return nil, nil, "", err + } + readSeeker = bytes.NewReader(data) + } + hash := md5.New() - buf := bytes.NewBuffer([]byte{}) - _, err = io.Copy(io.MultiWriter(hash, buf), opts.Content) - if err != nil { + // io.Copy into md5 is very efficient as it's done in small chunks. + if _, err := io.Copy(hash, readSeeker); err != nil { return nil, nil, "", err } - localChecksum := fmt.Sprintf("%x", hash.Sum(nil)) - h["ETag"] = localChecksum + readSeeker.Seek(0, io.SeekStart) + + h["ETag"] = fmt.Sprintf("%x", hash.Sum(nil)) - return buf, h, q.String(), nil + return readSeeker, h, q.String(), nil } // Create is a function that creates a new object or replaces an existing @@ -315,20 +339,28 @@ func Delete(c *gophercloud.ServiceClient, containerName, objectName string, opts // GetOptsBuilder allows extensions to add additional parameters to the // Get request. type GetOptsBuilder interface { - ToObjectGetQuery() (string, error) + ToObjectGetParams() (map[string]string, string, error) } // GetOpts is a structure that holds parameters for getting an object's // metadata. type GetOpts struct { + Newest bool `h:"X-Newest"` Expires string `q:"expires"` Signature string `q:"signature"` } -// ToObjectGetQuery formats a GetOpts into a query string. -func (opts GetOpts) ToObjectGetQuery() (string, error) { +// ToObjectGetParams formats a GetOpts into a query string and a map of headers. +func (opts GetOpts) ToObjectGetParams() (map[string]string, string, error) { q, err := gophercloud.BuildQueryString(opts) - return q.String(), err + if err != nil { + return nil, "", err + } + h, err := gophercloud.BuildHeaders(opts) + if err != nil { + return nil, q.String(), err + } + return h, q.String(), nil } // Get is a function that retrieves the metadata of an object. To extract just @@ -336,16 +368,22 @@ func (opts GetOpts) ToObjectGetQuery() (string, error) { // function. func Get(c *gophercloud.ServiceClient, containerName, objectName string, opts GetOptsBuilder) (r GetResult) { url := getURL(c, containerName, objectName) + h := make(map[string]string) if opts != nil { - query, err := opts.ToObjectGetQuery() + headers, query, err := opts.ToObjectGetParams() if err != nil { r.Err = err return } + for k, v := range headers { + h[k] = v + } url += query } - resp, err := c.Request("HEAD", url, &gophercloud.RequestOpts{ - OkCodes: []int{200, 204}, + + resp, err := c.Head(url, &gophercloud.RequestOpts{ + MoreHeaders: h, + OkCodes: []int{200, 204}, }) if resp != nil { r.Header = resp.Header diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/results.go b/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/results.go index a47b39343..dd7c7044d 100644 --- a/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/results.go +++ b/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/results.go @@ -25,8 +25,7 @@ type Object struct { // Hash represents the MD5 checksum value of the object's content. Hash string `json:"hash"` - // LastModified is the time the object was last modified, represented - // as a string. + // LastModified is the time the object was last modified. LastModified time.Time `json:"-"` // Name is the unique name for the object. @@ -40,7 +39,7 @@ func (r *Object) UnmarshalJSON(b []byte) error { type tmp Object var s *struct { tmp - LastModified gophercloud.JSONRFC3339MilliNoZ `json:"last_modified"` + LastModified string `json:"last_modified"` } err := json.Unmarshal(b, &s) @@ -50,10 +49,18 @@ func (r *Object) UnmarshalJSON(b []byte) error { *r = Object(s.tmp) - r.LastModified = time.Time(s.LastModified) + if s.LastModified != "" { + t, err := time.Parse(gophercloud.RFC3339MilliNoZ, s.LastModified) + if err != nil { + t, err = time.Parse(gophercloud.RFC3339Milli, s.LastModified) + if err != nil { + return err + } + } + r.LastModified = t + } return nil - } // ObjectPage is a single page of objects that is returned from a call to the @@ -134,7 +141,7 @@ type DownloadHeader struct { ETag string `json:"Etag"` LastModified time.Time `json:"-"` ObjectManifest string `json:"X-Object-Manifest"` - StaticLargeObject bool `json:"X-Static-Large-Object"` + StaticLargeObject bool `json:"-"` TransID string `json:"X-Trans-Id"` } @@ -142,10 +149,11 @@ func (r *DownloadHeader) UnmarshalJSON(b []byte) error { type tmp DownloadHeader var s struct { tmp - ContentLength string `json:"Content-Length"` - Date gophercloud.JSONRFC1123 `json:"Date"` - DeleteAt gophercloud.JSONUnix `json:"X-Delete-At"` - LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"` + ContentLength string `json:"Content-Length"` + Date gophercloud.JSONRFC1123 `json:"Date"` + DeleteAt gophercloud.JSONUnix `json:"X-Delete-At"` + LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"` + StaticLargeObject interface{} `json:"X-Static-Large-Object"` } err := json.Unmarshal(b, &s) if err != nil { @@ -164,6 +172,15 @@ func (r *DownloadHeader) UnmarshalJSON(b []byte) error { } } + switch t := s.StaticLargeObject.(type) { + case string: + if t == "True" || t == "true" { + r.StaticLargeObject = true + } + case bool: + r.StaticLargeObject = t + } + r.Date = time.Time(s.Date) r.DeleteAt = time.Time(s.DeleteAt) r.LastModified = time.Time(s.LastModified) @@ -214,7 +231,7 @@ type GetHeader struct { ETag string `json:"Etag"` LastModified time.Time `json:"-"` ObjectManifest string `json:"X-Object-Manifest"` - StaticLargeObject bool `json:"X-Static-Large-Object"` + StaticLargeObject bool `json:"-"` TransID string `json:"X-Trans-Id"` } @@ -222,10 +239,11 @@ func (r *GetHeader) UnmarshalJSON(b []byte) error { type tmp GetHeader var s struct { tmp - ContentLength string `json:"Content-Length"` - Date gophercloud.JSONRFC1123 `json:"Date"` - DeleteAt gophercloud.JSONUnix `json:"X-Delete-At"` - LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"` + ContentLength string `json:"Content-Length"` + Date gophercloud.JSONRFC1123 `json:"Date"` + DeleteAt gophercloud.JSONUnix `json:"X-Delete-At"` + LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"` + StaticLargeObject interface{} `json:"X-Static-Large-Object"` } err := json.Unmarshal(b, &s) if err != nil { @@ -244,6 +262,15 @@ func (r *GetHeader) UnmarshalJSON(b []byte) error { } } + switch t := s.StaticLargeObject.(type) { + case string: + if t == "True" || t == "true" { + r.StaticLargeObject = true + } + case bool: + r.StaticLargeObject = t + } + r.Date = time.Time(s.Date) r.DeleteAt = time.Time(s.DeleteAt) r.LastModified = time.Time(s.LastModified) @@ -391,7 +418,7 @@ func (r UpdateResult) Extract() (*UpdateHeader, error) { // DeleteHeader represents the headers returned in the response from a // Delete request. type DeleteHeader struct { - ContentLength int64 `json:"Content-Length"` + ContentLength int64 `json:"-"` ContentType string `json:"Content-Type"` Date time.Time `json:"-"` TransID string `json:"X-Trans-Id"` diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/utils/BUILD.bazel b/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/utils/BUILD.bazel index a768b735b..aeb1da73f 100644 --- a/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/utils/BUILD.bazel +++ b/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/utils/BUILD.bazel @@ -2,7 +2,10 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", - srcs = ["choose_version.go"], + srcs = [ + "base_endpoint.go", + "choose_version.go", + ], importmap = "kope.io/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/utils", importpath = "github.com/gophercloud/gophercloud/openstack/utils", visibility = ["//visibility:public"], diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/utils/base_endpoint.go b/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/utils/base_endpoint.go new file mode 100644 index 000000000..40080f7af --- /dev/null +++ b/etcd-manager/vendor/github.com/gophercloud/gophercloud/openstack/utils/base_endpoint.go @@ -0,0 +1,28 @@ +package utils + +import ( + "net/url" + "regexp" + "strings" +) + +// BaseEndpoint will return a URL without the /vX.Y +// portion of the URL. +func BaseEndpoint(endpoint string) (string, error) { + u, err := url.Parse(endpoint) + if err != nil { + return "", err + } + + u.RawQuery, u.Fragment = "", "" + + path := u.Path + versionRe := regexp.MustCompile("v[0-9.]+/?") + + if version := versionRe.FindString(path); version != "" { + versionIndex := strings.Index(path, version) + u.Path = path[:versionIndex] + } + + return u.String(), nil +} diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/pagination/pager.go b/etcd-manager/vendor/github.com/gophercloud/gophercloud/pagination/pager.go index 7c65926b7..42c0b2dbe 100644 --- a/etcd-manager/vendor/github.com/gophercloud/gophercloud/pagination/pager.go +++ b/etcd-manager/vendor/github.com/gophercloud/gophercloud/pagination/pager.go @@ -41,6 +41,8 @@ type Pager struct { createPage func(r PageResult) Page + firstPage Page + Err error // Headers supplies additional HTTP headers to populate on each paged request. @@ -89,9 +91,18 @@ func (p Pager) EachPage(handler func(Page) (bool, error)) error { } currentURL := p.initialURL for { - currentPage, err := p.fetchNextPage(currentURL) - if err != nil { - return err + var currentPage Page + + // if first page has already been fetched, no need to fetch it again + if p.firstPage != nil { + currentPage = p.firstPage + p.firstPage = nil + } else { + var err error + currentPage, err = p.fetchNextPage(currentURL) + if err != nil { + return err + } } empty, err := currentPage.IsEmpty() @@ -128,23 +139,26 @@ func (p Pager) AllPages() (Page, error) { // body will contain the final concatenated Page body. var body reflect.Value - // Grab a test page to ascertain the page body type. - testPage, err := p.fetchNextPage(p.initialURL) + // Grab a first page to ascertain the page body type. + firstPage, err := p.fetchNextPage(p.initialURL) if err != nil { return nil, err } // Store the page type so we can use reflection to create a new mega-page of // that type. - pageType := reflect.TypeOf(testPage) + pageType := reflect.TypeOf(firstPage) - // if it's a single page, just return the testPage (first page) + // if it's a single page, just return the firstPage (first page) if _, found := pageType.FieldByName("SinglePageBase"); found { - return testPage, nil + return firstPage, nil } + // store the first page to avoid getting it twice + p.firstPage = firstPage + // Switch on the page body type. Recognized types are `map[string]interface{}`, // `[]byte`, and `[]interface{}`. - switch pb := testPage.GetBody().(type) { + switch pb := firstPage.GetBody().(type) { case map[string]interface{}: // key is the map key for the page body if the body type is `map[string]interface{}`. var key string diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/params.go b/etcd-manager/vendor/github.com/gophercloud/gophercloud/params.go index 28ad90685..b9986660c 100644 --- a/etcd-manager/vendor/github.com/gophercloud/gophercloud/params.go +++ b/etcd-manager/vendor/github.com/gophercloud/gophercloud/params.go @@ -120,6 +120,22 @@ func BuildRequestBody(opts interface{}, parent string) (map[string]interface{}, continue } + if v.Kind() == reflect.Slice || (v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Slice) { + sliceValue := v + if sliceValue.Kind() == reflect.Ptr { + sliceValue = sliceValue.Elem() + } + + for i := 0; i < sliceValue.Len(); i++ { + element := sliceValue.Index(i) + if element.Kind() == reflect.Struct || (element.Kind() == reflect.Ptr && element.Elem().Kind() == reflect.Struct) { + _, err := BuildRequestBody(element.Interface(), "") + if err != nil { + return nil, err + } + } + } + } if v.Kind() == reflect.Struct || (v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct) { if zero { //fmt.Printf("value before change: %+v\n", optsValue.Field(i)) @@ -363,9 +379,8 @@ func BuildQueryString(opts interface{}) (*url.URL, error) { } } } else { - // Otherwise, the field is not set. - if len(tags) == 2 && tags[1] == "required" { - // And the field is required. Return an error. + // if the field has a 'required' tag, it can't have a zero-value + if requiredTag := f.Tag.Get("required"); requiredTag == "true" { return &url.URL{}, fmt.Errorf("Required query parameter [%s] not set.", f.Name) } } @@ -439,10 +454,9 @@ func BuildHeaders(opts interface{}) (map[string]string, error) { optsMap[tags[0]] = strconv.FormatBool(v.Bool()) } } else { - // Otherwise, the field is not set. - if len(tags) == 2 && tags[1] == "required" { - // And the field is required. Return an error. - return optsMap, fmt.Errorf("Required header not set.") + // if the field has a 'required' tag, it can't have a zero-value + if requiredTag := f.Tag.Get("required"); requiredTag == "true" { + return optsMap, fmt.Errorf("Required header [%s] not set.", f.Name) } } } diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/provider_client.go b/etcd-manager/vendor/github.com/gophercloud/gophercloud/provider_client.go index 72daeb0a3..365b4cbcd 100644 --- a/etcd-manager/vendor/github.com/gophercloud/gophercloud/provider_client.go +++ b/etcd-manager/vendor/github.com/gophercloud/gophercloud/provider_client.go @@ -3,6 +3,7 @@ package gophercloud import ( "bytes" "encoding/json" + "errors" "io" "io/ioutil" "net/http" @@ -71,26 +72,36 @@ type ProviderClient struct { // authentication functions for different Identity service versions. ReauthFunc func() error + // Throwaway determines whether if this client is a throw-away client. It's a copy of user's provider client + // with the token and reauth func zeroed. Such client can be used to perform reauthorization. + Throwaway bool + mut *sync.RWMutex reauthmut *reauthlock + + authResult AuthResult } type reauthlock struct { sync.RWMutex - reauthing bool + reauthing bool + reauthingErr error + done *sync.Cond } // AuthenticatedHeaders returns a map of HTTP headers that are common for all -// authenticated service requests. +// authenticated service requests. Blocks if Reauthenticate is in progress. func (client *ProviderClient) AuthenticatedHeaders() (m map[string]string) { + if client.IsThrowaway() { + return + } if client.reauthmut != nil { - client.reauthmut.RLock() - if client.reauthmut.reauthing { - client.reauthmut.RUnlock() - return + client.reauthmut.Lock() + for client.reauthmut.reauthing { + client.reauthmut.done.Wait() } - client.reauthmut.RUnlock() + client.reauthmut.Unlock() } t := client.Token() if t == "" { @@ -106,6 +117,20 @@ func (client *ProviderClient) UseTokenLock() { client.reauthmut = new(reauthlock) } +// GetAuthResult returns the result from the request that was used to obtain a +// provider client's Keystone token. +// +// The result is nil when authentication has not yet taken place, when the token +// was set manually with SetToken(), or when a ReauthFunc was used that does not +// record the AuthResult. +func (client *ProviderClient) GetAuthResult() AuthResult { + if client.mut != nil { + client.mut.RLock() + defer client.mut.RUnlock() + } + return client.authResult +} + // Token safely reads the value of the auth token from the ProviderClient. Applications should // call this method to access the token instead of the TokenID field func (client *ProviderClient) Token() string { @@ -117,13 +142,117 @@ func (client *ProviderClient) Token() string { } // SetToken safely sets the value of the auth token in the ProviderClient. Applications may -// use this method in a custom ReauthFunc +// use this method in a custom ReauthFunc. +// +// WARNING: This function is deprecated. Use SetTokenAndAuthResult() instead. func (client *ProviderClient) SetToken(t string) { if client.mut != nil { client.mut.Lock() defer client.mut.Unlock() } client.TokenID = t + client.authResult = nil +} + +// SetTokenAndAuthResult safely sets the value of the auth token in the +// ProviderClient and also records the AuthResult that was returned from the +// token creation request. Applications may call this in a custom ReauthFunc. +func (client *ProviderClient) SetTokenAndAuthResult(r AuthResult) error { + tokenID := "" + var err error + if r != nil { + tokenID, err = r.ExtractTokenID() + if err != nil { + return err + } + } + + if client.mut != nil { + client.mut.Lock() + defer client.mut.Unlock() + } + client.TokenID = tokenID + client.authResult = r + return nil +} + +// CopyTokenFrom safely copies the token from another ProviderClient into the +// this one. +func (client *ProviderClient) CopyTokenFrom(other *ProviderClient) { + if client.mut != nil { + client.mut.Lock() + defer client.mut.Unlock() + } + if other.mut != nil && other.mut != client.mut { + other.mut.RLock() + defer other.mut.RUnlock() + } + client.TokenID = other.TokenID + client.authResult = other.authResult +} + +// IsThrowaway safely reads the value of the client Throwaway field. +func (client *ProviderClient) IsThrowaway() bool { + if client.reauthmut != nil { + client.reauthmut.RLock() + defer client.reauthmut.RUnlock() + } + return client.Throwaway +} + +// SetThrowaway safely sets the value of the client Throwaway field. +func (client *ProviderClient) SetThrowaway(v bool) { + if client.reauthmut != nil { + client.reauthmut.Lock() + defer client.reauthmut.Unlock() + } + client.Throwaway = v +} + +// Reauthenticate calls client.ReauthFunc in a thread-safe way. If this is +// called because of a 401 response, the caller may pass the previous token. In +// this case, the reauthentication can be skipped if another thread has already +// reauthenticated in the meantime. If no previous token is known, an empty +// string should be passed instead to force unconditional reauthentication. +func (client *ProviderClient) Reauthenticate(previousToken string) (err error) { + if client.ReauthFunc == nil { + return nil + } + + if client.mut == nil { + return client.ReauthFunc() + } + + client.reauthmut.Lock() + if client.reauthmut.reauthing { + for !client.reauthmut.reauthing { + client.reauthmut.done.Wait() + } + err = client.reauthmut.reauthingErr + client.reauthmut.Unlock() + return err + } + client.reauthmut.Unlock() + + client.mut.Lock() + defer client.mut.Unlock() + + client.reauthmut.Lock() + client.reauthmut.reauthing = true + client.reauthmut.done = sync.NewCond(client.reauthmut) + client.reauthmut.reauthingErr = nil + client.reauthmut.Unlock() + + if previousToken == "" || client.TokenID == previousToken { + err = client.ReauthFunc() + } + + client.reauthmut.Lock() + client.reauthmut.reauthing = false + client.reauthmut.reauthingErr = err + client.reauthmut.done.Broadcast() + client.reauthmut.Unlock() + return } // RequestOpts customizes the behavior of the provider.Request() method. @@ -162,7 +291,7 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts) // io.ReadSeeker as-is. Default the content-type to application/json. if options.JSONBody != nil { if options.RawBody != nil { - panic("Please provide only one of JSONBody or RawBody to gophercloud.Request().") + return nil, errors.New("please provide only one of JSONBody or RawBody to gophercloud.Request()") } rendered, err := json.Marshal(options.JSONBody) @@ -221,13 +350,14 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts) } // Allow default OkCodes if none explicitly set - if options.OkCodes == nil { - options.OkCodes = defaultOkCodes(method) + okc := options.OkCodes + if okc == nil { + okc = defaultOkCodes(method) } // Validate the HTTP response status. var ok bool - for _, code := range options.OkCodes { + for _, code := range okc { if resp.StatusCode == code { ok = true break @@ -254,21 +384,7 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts) } case http.StatusUnauthorized: if client.ReauthFunc != nil { - if client.mut != nil { - client.mut.Lock() - client.reauthmut.Lock() - client.reauthmut.reauthing = true - client.reauthmut.Unlock() - if curtok := client.TokenID; curtok == prereqtok { - err = client.ReauthFunc() - } - client.reauthmut.Lock() - client.reauthmut.reauthing = false - client.reauthmut.Unlock() - client.mut.Unlock() - } else { - err = client.ReauthFunc() - } + err = client.Reauthenticate(prereqtok) if err != nil { e := &ErrUnableToReauthenticate{} e.ErrOriginal = respErr @@ -298,6 +414,11 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts) if error401er, ok := errType.(Err401er); ok { err = error401er.Error401(respErr) } + case http.StatusForbidden: + err = ErrDefault403{respErr} + if error403er, ok := errType.(Err403er); ok { + err = error403er.Error403(respErr) + } case http.StatusNotFound: err = ErrDefault404{respErr} if error404er, ok := errType.(Err404er); ok { @@ -357,7 +478,7 @@ func defaultOkCodes(method string) []int { case method == "PUT": return []int{201, 202} case method == "PATCH": - return []int{200, 204} + return []int{200, 202, 204} case method == "DELETE": return []int{202, 204} } diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/results.go b/etcd-manager/vendor/github.com/gophercloud/gophercloud/results.go index e64feee19..94a16bff0 100644 --- a/etcd-manager/vendor/github.com/gophercloud/gophercloud/results.go +++ b/etcd-manager/vendor/github.com/gophercloud/gophercloud/results.go @@ -89,23 +89,47 @@ func (r Result) extractIntoPtr(to interface{}, label string) error { if typeOfV.Kind() == reflect.Struct { if typeOfV.NumField() > 0 && typeOfV.Field(0).Anonymous { newSlice := reflect.MakeSlice(reflect.SliceOf(typeOfV), 0, 0) - newType := reflect.New(typeOfV).Elem() - for _, v := range m[label].([]interface{}) { - b, err := json.Marshal(v) - if err != nil { - return err - } - - for i := 0; i < newType.NumField(); i++ { - s := newType.Field(i).Addr().Interface() - err = json.NewDecoder(bytes.NewReader(b)).Decode(s) + if mSlice, ok := m[label].([]interface{}); ok { + for _, v := range mSlice { + // For each iteration of the slice, we create a new struct. + // This is to work around a bug where elements of a slice + // are reused and not overwritten when the same copy of the + // struct is used: + // + // https://github.com/golang/go/issues/21092 + // https://github.com/golang/go/issues/24155 + // https://play.golang.org/p/NHo3ywlPZli + newType := reflect.New(typeOfV).Elem() + + b, err := json.Marshal(v) if err != nil { return err } + + // This is needed for structs with an UnmarshalJSON method. + // Technically this is just unmarshalling the response into + // a struct that is never used, but it's good enough to + // trigger the UnmarshalJSON method. + for i := 0; i < newType.NumField(); i++ { + s := newType.Field(i).Addr().Interface() + + // Unmarshal is used rather than NewDecoder to also work + // around the above-mentioned bug. + err = json.Unmarshal(b, s) + if err != nil { + return err + } + } + + newSlice = reflect.Append(newSlice, newType) } - newSlice = reflect.Append(newSlice, newType) } + + // "to" should now be properly modeled to receive the + // JSON response body and unmarshal into all the correct + // fields of the struct or composed extension struct + // at the end of this method. toValue.Set(newSlice) } } @@ -345,6 +369,48 @@ func (jt *JSONRFC3339NoZ) UnmarshalJSON(data []byte) error { return nil } +// RFC3339ZNoT is the time format used in Zun (Containers Service). +const RFC3339ZNoT = "2006-01-02 15:04:05-07:00" + +type JSONRFC3339ZNoT time.Time + +func (jt *JSONRFC3339ZNoT) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + if s == "" { + return nil + } + t, err := time.Parse(RFC3339ZNoT, s) + if err != nil { + return err + } + *jt = JSONRFC3339ZNoT(t) + return nil +} + +// RFC3339ZNoTNoZ is another time format used in Zun (Containers Service). +const RFC3339ZNoTNoZ = "2006-01-02 15:04:05" + +type JSONRFC3339ZNoTNoZ time.Time + +func (jt *JSONRFC3339ZNoTNoZ) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + if s == "" { + return nil + } + t, err := time.Parse(RFC3339ZNoTNoZ, s) + if err != nil { + return err + } + *jt = JSONRFC3339ZNoTNoZ(t) + return nil +} + /* Link is an internal type to be used in packages of collection resources that are paginated in a certain way. diff --git a/etcd-manager/vendor/github.com/gophercloud/gophercloud/service_client.go b/etcd-manager/vendor/github.com/gophercloud/gophercloud/service_client.go index d1a48fea3..c889201f9 100644 --- a/etcd-manager/vendor/github.com/gophercloud/gophercloud/service_client.go +++ b/etcd-manager/vendor/github.com/gophercloud/gophercloud/service_client.go @@ -28,6 +28,10 @@ type ServiceClient struct { // The microversion of the service to use. Set this to use a particular microversion. Microversion string + + // MoreHeaders allows users (or Gophercloud) to set service-wide headers on requests. Put another way, + // values set in this field will be set on all the HTTP requests the service client sends. + MoreHeaders map[string]string } // ResourceBaseURL returns the base URL of any resources used by this service. It MUST end with a /. @@ -108,6 +112,15 @@ func (client *ServiceClient) Delete(url string, opts *RequestOpts) (*http.Respon return client.Request("DELETE", url, opts) } +// Head calls `Request` with the "HEAD" HTTP verb. +func (client *ServiceClient) Head(url string, opts *RequestOpts) (*http.Response, error) { + if opts == nil { + opts = new(RequestOpts) + } + client.initReqOpts(url, nil, nil, opts) + return client.Request("HEAD", url, opts) +} + func (client *ServiceClient) setMicroversionHeader(opts *RequestOpts) { switch client.Type { case "compute": @@ -116,9 +129,24 @@ func (client *ServiceClient) setMicroversionHeader(opts *RequestOpts) { opts.MoreHeaders["X-OpenStack-Manila-API-Version"] = client.Microversion case "volume": opts.MoreHeaders["X-OpenStack-Volume-API-Version"] = client.Microversion + case "baremetal": + opts.MoreHeaders["X-OpenStack-Ironic-API-Version"] = client.Microversion } if client.Type != "" { opts.MoreHeaders["OpenStack-API-Version"] = client.Type + " " + client.Microversion } } + +// Request carries out the HTTP operation for the service client +func (client *ServiceClient) Request(method, url string, options *RequestOpts) (*http.Response, error) { + if len(client.MoreHeaders) > 0 { + if options == nil { + options = new(RequestOpts) + } + for k, v := range client.MoreHeaders { + options.MoreHeaders[k] = v + } + } + return client.ProviderClient.Request(method, url, options) +} diff --git a/etcd-manager/vendor/k8s.io/kops/pkg/try/BUILD.bazel b/etcd-manager/vendor/k8s.io/kops/pkg/try/BUILD.bazel new file mode 100644 index 000000000..f7e7e7933 --- /dev/null +++ b/etcd-manager/vendor/k8s.io/kops/pkg/try/BUILD.bazel @@ -0,0 +1,10 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["files.go"], + importmap = "kope.io/etcd-manager/vendor/k8s.io/kops/pkg/try", + importpath = "k8s.io/kops/pkg/try", + visibility = ["//visibility:public"], + deps = ["//vendor/github.com/golang/glog:go_default_library"], +) diff --git a/etcd-manager/vendor/k8s.io/kops/pkg/try/files.go b/etcd-manager/vendor/k8s.io/kops/pkg/try/files.go new file mode 100644 index 000000000..6ce8244ca --- /dev/null +++ b/etcd-manager/vendor/k8s.io/kops/pkg/try/files.go @@ -0,0 +1,37 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package try + +import ( + "os" + + "github.com/golang/glog" +) + +// RemoveFile will try to os.Remove the file, logging an error if it fails +func RemoveFile(fp string) { + if err := os.Remove(fp); err != nil { + glog.Warningf("unable to remove file %s: %v", fp, err) + } +} + +// CloseFile will try to call close on the file, logging an error if it fails +func CloseFile(f *os.File) { + if err := f.Close(); err != nil { + glog.Warningf("unable to close file %s: %v", f.Name(), err) + } +} diff --git a/etcd-manager/vendor/k8s.io/kops/util/pkg/hashing/BUILD.bazel b/etcd-manager/vendor/k8s.io/kops/util/pkg/hashing/BUILD.bazel index 2e35e667b..33f216ed8 100644 --- a/etcd-manager/vendor/k8s.io/kops/util/pkg/hashing/BUILD.bazel +++ b/etcd-manager/vendor/k8s.io/kops/util/pkg/hashing/BUILD.bazel @@ -6,5 +6,8 @@ go_library( importmap = "kope.io/etcd-manager/vendor/k8s.io/kops/util/pkg/hashing", importpath = "k8s.io/kops/util/pkg/hashing", visibility = ["//visibility:public"], - deps = ["//vendor/github.com/golang/glog:go_default_library"], + deps = [ + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/k8s.io/kops/pkg/try:go_default_library", + ], ) diff --git a/etcd-manager/vendor/k8s.io/kops/util/pkg/hashing/hash.go b/etcd-manager/vendor/k8s.io/kops/util/pkg/hashing/hash.go index a150d2cc9..8a34e3fcf 100644 --- a/etcd-manager/vendor/k8s.io/kops/util/pkg/hashing/hash.go +++ b/etcd-manager/vendor/k8s.io/kops/util/pkg/hashing/hash.go @@ -29,6 +29,8 @@ import ( "strings" "github.com/golang/glog" + + "k8s.io/kops/pkg/try" ) type HashAlgorithm string @@ -132,7 +134,7 @@ func (ha HashAlgorithm) HashFile(p string) (*Hash, error) { } return nil, fmt.Errorf("error opening file %q: %v", p, err) } - defer f.Close() + defer try.CloseFile(f) return ha.Hash(f) } diff --git a/etcd-manager/vendor/k8s.io/kops/util/pkg/vfs/BUILD.bazel b/etcd-manager/vendor/k8s.io/kops/util/pkg/vfs/BUILD.bazel index ee7dafa37..6e3c0bc0e 100644 --- a/etcd-manager/vendor/k8s.io/kops/util/pkg/vfs/BUILD.bazel +++ b/etcd-manager/vendor/k8s.io/kops/util/pkg/vfs/BUILD.bazel @@ -26,6 +26,7 @@ go_library( "//vendor/github.com/aws/aws-sdk-go/aws:go_default_library", "//vendor/github.com/aws/aws-sdk-go/aws/awserr:go_default_library", "//vendor/github.com/aws/aws-sdk-go/aws/credentials:go_default_library", + "//vendor/github.com/aws/aws-sdk-go/aws/ec2metadata:go_default_library", "//vendor/github.com/aws/aws-sdk-go/aws/endpoints:go_default_library", "//vendor/github.com/aws/aws-sdk-go/aws/session:go_default_library", "//vendor/github.com/aws/aws-sdk-go/service/ec2:go_default_library", @@ -46,6 +47,7 @@ go_library( "//vendor/google.golang.org/api/storage/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//vendor/k8s.io/client-go/util/homedir:go_default_library", + "//vendor/k8s.io/kops/pkg/try:go_default_library", "//vendor/k8s.io/kops/util/pkg/hashing:go_default_library", ], ) diff --git a/etcd-manager/vendor/k8s.io/kops/util/pkg/vfs/context.go b/etcd-manager/vendor/k8s.io/kops/util/pkg/vfs/context.go index 2a76c02b9..7844cedb0 100644 --- a/etcd-manager/vendor/k8s.io/kops/util/pkg/vfs/context.go +++ b/etcd-manager/vendor/k8s.io/kops/util/pkg/vfs/context.go @@ -228,7 +228,7 @@ func RetryWithBackoff(backoff wait.Backoff, condition func() (bool, error)) (boo } if noMoreRetries { - glog.V(2).Infof("hit maximum retries %d with error %v", i, err) + glog.Infof("hit maximum retries %d with error %v", i, err) return done, err } } diff --git a/etcd-manager/vendor/k8s.io/kops/util/pkg/vfs/fs.go b/etcd-manager/vendor/k8s.io/kops/util/pkg/vfs/fs.go index f58c8897b..e1340da57 100644 --- a/etcd-manager/vendor/k8s.io/kops/util/pkg/vfs/fs.go +++ b/etcd-manager/vendor/k8s.io/kops/util/pkg/vfs/fs.go @@ -25,6 +25,7 @@ import ( "sync" "github.com/golang/glog" + "k8s.io/kops/pkg/try" "k8s.io/kops/util/pkg/hashing" ) @@ -120,7 +121,7 @@ func (p *FSPath) WriteTo(out io.Writer) (int64, error) { if err != nil { return 0, err } - defer f.Close() + defer try.CloseFile(f) return io.Copy(out, f) } diff --git a/etcd-manager/vendor/k8s.io/kops/util/pkg/vfs/gsfs.go b/etcd-manager/vendor/k8s.io/kops/util/pkg/vfs/gsfs.go index 6b66d0dee..612e083e4 100644 --- a/etcd-manager/vendor/k8s.io/kops/util/pkg/vfs/gsfs.go +++ b/etcd-manager/vendor/k8s.io/kops/util/pkg/vfs/gsfs.go @@ -19,7 +19,6 @@ package vfs import ( "bytes" "encoding/base64" - "encoding/hex" "fmt" "io" "net/http" @@ -351,7 +350,7 @@ func (p *GSPath) Hash(a hashing.HashAlgorithm) (*hashing.Hash, error) { return nil, nil } - md5Bytes, err := hex.DecodeString(md5) + md5Bytes, err := base64.StdEncoding.DecodeString(md5) if err != nil { return nil, fmt.Errorf("Etag was not a valid MD5 sum: %q", md5) } diff --git a/etcd-manager/vendor/k8s.io/kops/util/pkg/vfs/s3context.go b/etcd-manager/vendor/k8s.io/kops/util/pkg/vfs/s3context.go index 633c94f7a..d971588d1 100644 --- a/etcd-manager/vendor/k8s.io/kops/util/pkg/vfs/s3context.go +++ b/etcd-manager/vendor/k8s.io/kops/util/pkg/vfs/s3context.go @@ -18,13 +18,18 @@ package vfs import ( "fmt" + "io/ioutil" + "net/http" "os" "regexp" + "runtime" + "strings" "sync" "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/ec2metadata" "github.com/aws/aws-sdk-go/aws/endpoints" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/ec2" @@ -35,14 +40,26 @@ import ( var ( // matches all regional naming conventions of S3: // https://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region - // TODO: perhaps make region regex more specific, ie. (us|eu|ap|cn|ca|sa), to prevent catching bucket names that match region format? + // TODO: perhaps make region regex more specific, i.e. (us|eu|ap|cn|ca|sa), to prevent matching bucket names that match region format? // but that will mean updating this list when AWS introduces new regions s3UrlRegexp = regexp.MustCompile(`s3([-.](?P\w{2}-\w+-\d{1})|[-.](?P[\w.\-\_]+)|)?.amazonaws.com(.cn)?(?P.*)?`) ) type S3BucketDetails struct { - region string - defaultEncryption bool + // context is the S3Context we are associated with + context *S3Context + + // region is the region we have determined for the bucket + region string + + // name is the name of the bucket + name string + + // mutex protects applyServerSideEncryptionByDefault + mutex sync.Mutex + + // applyServerSideEncryptionByDefault caches information on whether server-side encryption is enabled on the bucket + applyServerSideEncryptionByDefault *bool } type S3Context struct { @@ -112,17 +129,19 @@ func getCustomS3Config(endpoint string, region string) (*aws.Config, error) { } func (s *S3Context) getDetailsForBucket(bucket string) (*S3BucketDetails, error) { - bucketDetails := func() *S3BucketDetails { - s.mutex.Lock() - defer s.mutex.Unlock() - return s.bucketDetails[bucket] - }() + s.mutex.Lock() + bucketDetails := s.bucketDetails[bucket] + s.mutex.Unlock() if bucketDetails != nil && bucketDetails.region != "" { return bucketDetails, nil } - bucketDetails = &S3BucketDetails{region: "", defaultEncryption: false} + bucketDetails = &S3BucketDetails{ + context: s, + region: "", + name: bucket, + } // Probe to find correct region for bucket endpoint := os.Getenv("S3_ENDPOINT") @@ -131,14 +150,24 @@ func (s *S3Context) getDetailsForBucket(bucket string) (*S3BucketDetails, error) bucketDetails.region = os.Getenv("S3_REGION") if bucketDetails.region == "" { bucketDetails.region = "us-east-1" - bucketDetails.defaultEncryption = s.checkDefaultEncryption(bucketDetails.region, bucket) } return bucketDetails, nil } awsRegion := os.Getenv("AWS_REGION") + if awsRegion == "" && isRunningOnEC2() { + region, err := getRegionFromMetadata() + if err != nil { + glog.V(2).Infof("unable to get region from metadata:%v", err) + } else { + awsRegion = region + glog.V(2).Infof("got region from metadata: %q", awsRegion) + } + } + if awsRegion == "" { awsRegion = "us-east-1" + glog.V(2).Infof("defaulting region to %q", awsRegion) } if err := validateRegion(awsRegion); err != nil { @@ -170,55 +199,70 @@ func (s *S3Context) getDetailsForBucket(bucket string) (*S3BucketDetails, error) if response.LocationConstraint == nil { // US Classic does not return a region bucketDetails.region = "us-east-1" - bucketDetails.defaultEncryption = s.checkDefaultEncryption(bucketDetails.region, bucket) } else { bucketDetails.region = *response.LocationConstraint // Another special case: "EU" can mean eu-west-1 if bucketDetails.region == "EU" { bucketDetails.region = "eu-west-1" } - bucketDetails.defaultEncryption = s.checkDefaultEncryption(bucketDetails.region, bucket) } - glog.V(2).Infof("Found bucket %q in region %q with default encryption set to %t", bucket, bucketDetails.region, bucketDetails.defaultEncryption) + + glog.V(2).Infof("found bucket in region %q", bucketDetails.region) s.mutex.Lock() - defer s.mutex.Unlock() s.bucketDetails[bucket] = bucketDetails + s.mutex.Unlock() return bucketDetails, nil } -func (s *S3Context) checkDefaultEncryption(region string, bucket string) bool { - client, err := s.getClient(region) +func (b *S3BucketDetails) hasServerSideEncryptionByDefault() bool { + b.mutex.Lock() + defer b.mutex.Unlock() + + if b.applyServerSideEncryptionByDefault != nil { + return *b.applyServerSideEncryptionByDefault + } + + applyServerSideEncryptionByDefault := false + + // We only make one attempt to find the SSE policy (even if there's an error) + b.applyServerSideEncryptionByDefault = &applyServerSideEncryptionByDefault + + client, err := b.context.getClient(b.region) if err != nil { - glog.Warningf("Unable to read bucket encryption policy in region %q: will encrypt using AES256", region) + glog.Warningf("Unable to read bucket encryption policy for %q in region %q: will encrypt using AES256", b.name, b.region) return false } - glog.V(4).Infof("Checking default bucket encryption %q", bucket) + glog.V(4).Infof("Checking default bucket encryption for %q", b.name) request := &s3.GetBucketEncryptionInput{} - request.Bucket = aws.String(bucket) + request.Bucket = aws.String(b.name) - glog.V(8).Infof("Calling S3 GetBucketEncryption Bucket=%q", bucket) + glog.V(8).Infof("Calling S3 GetBucketEncryption Bucket=%q", b.name) result, err := client.GetBucketEncryption(request) if err != nil { // the following cases might lead to the operation failing: // 1. A deny policy on s3:GetEncryptionConfiguration // 2. No default encryption policy set - glog.V(8).Infof("Unable to read bucket encryption policy: will encrypt using AES256") + glog.V(8).Infof("Unable to read bucket encryption policy for %q: will encrypt using AES256", b.name) return false } // currently, only one element is in the rules array, iterating nonetheless for future compatibility for _, element := range result.ServerSideEncryptionConfiguration.Rules { if element.ApplyServerSideEncryptionByDefault != nil { - return true + applyServerSideEncryptionByDefault = true } } - return false + b.applyServerSideEncryptionByDefault = &applyServerSideEncryptionByDefault + + glog.V(2).Infof("bucket %q has default encryption set to %t", b.name, applyServerSideEncryptionByDefault) + + return applyServerSideEncryptionByDefault } /* @@ -270,6 +314,57 @@ func bruteforceBucketLocation(region *string, request *s3.GetBucketLocationInput } } +// isRunningOnEC2 determines if we could be running on EC2. +// It is used to avoid a call to the metadata service to get the current region, +// because that call is slow if not running on EC2 +func isRunningOnEC2() bool { + if runtime.GOOS == "linux" { + // Approach based on https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/identify_ec2_instances.html + productUUID, err := ioutil.ReadFile("/sys/devices/virtual/dmi/id/product_uuid") + if err != nil { + glog.V(2).Infof("unable to read /sys/devices/virtual/dmi/id/product_uuid, assuming not running on EC2: %v", err) + return false + } + + s := strings.ToLower(strings.TrimSpace(string(productUUID))) + if strings.HasPrefix(s, "ec2") { + glog.V(2).Infof("product_uuid is %q, assuming running on EC2", s) + return true + } else { + glog.V(2).Infof("product_uuid is %q, assuming not running on EC2", s) + return false + } + } else { + glog.V(2).Infof("GOOS=%q, assuming not running on EC2", runtime.GOOS) + return false + } +} + +// getRegionFromMetadata queries the metadata service for the current region, if running in EC2 +func getRegionFromMetadata() (string, error) { + // Use an even shorter timeout, to minimize impact when not running on EC2 + // Note that we still retry a few times, this works out a little under a 1s delay + shortTimeout := &aws.Config{ + HTTPClient: &http.Client{ + Timeout: 100 * time.Millisecond, + }, + } + + metadataSession, err := session.NewSession(shortTimeout) + if err != nil { + return "", fmt.Errorf("unable to build session: %v", err) + } + + metadata := ec2metadata.New(metadataSession) + metadataRegion, err := metadata.Region() + + if err != nil { + return "", fmt.Errorf("unable to get region from metadata: %v", err) + } + + return metadataRegion, nil +} + func validateRegion(region string) error { resolver := endpoints.DefaultResolver() partitions := resolver.(endpoints.EnumPartitions).Partitions() @@ -280,7 +375,7 @@ func validateRegion(region string) error { } } } - return fmt.Errorf("%s is not a valid region\nPlease check that your region is formatted correctly (i.e. us-east-1)", region) + return fmt.Errorf("%s is not a valid region\nPlease check that your region is formatted correctly (e.g. us-east-1)", region) } func VFSPath(url string) (string, error) { diff --git a/etcd-manager/vendor/k8s.io/kops/util/pkg/vfs/s3fs.go b/etcd-manager/vendor/k8s.io/kops/util/pkg/vfs/s3fs.go index b33b33255..82da8ecb9 100644 --- a/etcd-manager/vendor/k8s.io/kops/util/pkg/vfs/s3fs.go +++ b/etcd-manager/vendor/k8s.io/kops/util/pkg/vfs/s3fs.go @@ -139,7 +139,8 @@ func (p *S3Path) WriteFile(data io.ReadSeeker, aclObj ACL) error { // standard. sseLog := "-" if p.sse { - if p.bucketDetails.defaultEncryption { + defaultEncryption := p.bucketDetails.hasServerSideEncryptionByDefault() + if defaultEncryption { sseLog = "DefaultBucketEncryption" } else { sseLog = "AES256" @@ -150,7 +151,7 @@ func (p *S3Path) WriteFile(data io.ReadSeeker, aclObj ACL) error { acl := os.Getenv("KOPS_STATE_S3_ACL") acl = strings.TrimSpace(acl) if acl != "" { - glog.Infof("Using KOPS_STATE_S3_ACL=%s", acl) + glog.V(8).Infof("Using KOPS_STATE_S3_ACL=%s", acl) request.ACL = aws.String(acl) } else if aclObj != nil { s3Acl, ok := aclObj.(*S3Acl) diff --git a/etcd-manager/vendor/k8s.io/kops/util/pkg/vfs/swiftfs.go b/etcd-manager/vendor/k8s.io/kops/util/pkg/vfs/swiftfs.go index c8666ae47..56be8ec2f 100644 --- a/etcd-manager/vendor/k8s.io/kops/util/pkg/vfs/swiftfs.go +++ b/etcd-manager/vendor/k8s.io/kops/util/pkg/vfs/swiftfs.go @@ -18,9 +18,11 @@ package vfs import ( "bytes" + "crypto/tls" "encoding/hex" "fmt" "io" + "net/http" "os" "path" "path/filepath" @@ -43,20 +45,46 @@ import ( func NewSwiftClient() (*gophercloud.ServiceClient, error) { config := OpenstackConfig{} + // Check if env credentials are valid first authOption, err := config.GetCredential() if err != nil { return nil, err } - provider, err := openstack.AuthenticatedClient(authOption) + + pc, err := openstack.NewClient(authOption.IdentityEndpoint) if err != nil { - return nil, fmt.Errorf("error building openstack authenticated client: %v", err) + return nil, fmt.Errorf("error building openstack provider client: %v", err) + } + + tlsconfig := &tls.Config{} + tlsconfig.InsecureSkipVerify = true + transport := &http.Transport{TLSClientConfig: tlsconfig} + pc.HTTPClient = http.Client{ + Transport: transport, } - endpointOpt, err := config.GetServiceConfig("Swift") + + glog.V(2).Info("authenticating to keystone") + + err = openstack.Authenticate(pc, authOption) if err != nil { - return nil, err + return nil, fmt.Errorf("error building openstack authenticated client: %v", err) + } + + var endpointOpt gophercloud.EndpointOpts + if region, err := config.GetRegion(); err != nil { + glog.Warningf("Retrieving swift configuration from openstack config file: %v", err) + endpointOpt, err = config.GetServiceConfig("Swift") + if err != nil { + return nil, err + } + } else { + endpointOpt = gophercloud.EndpointOpts{ + Type: "object-store", + Region: region, + } } - client, err := openstack.NewObjectStorageV1(provider, endpointOpt) + client, err := openstack.NewObjectStorageV1(pc, endpointOpt) if err != nil { return nil, fmt.Errorf("error building swift client: %v", err) } @@ -103,6 +131,40 @@ func (oc OpenstackConfig) getSection(name string, items []string) (map[string]st } func (oc OpenstackConfig) GetCredential() (gophercloud.AuthOptions, error) { + + // prioritize environment config + env, enverr := openstack.AuthOptionsFromEnv() + if enverr != nil { + glog.Warningf("Could not initialize swift from environment: %v", enverr) + // fallback to config file + return oc.getCredentialFromFile() + } + return env, nil + +} + +func (oc OpenstackConfig) GetRegion() (string, error) { + + var region string + if region = os.Getenv("OS_REGION_NAME"); region != "" { + if len(region) > 1 { + if region[0] == '\'' && region[len(region)-1] == '\'' { + region = region[1 : len(region)-1] + } + } + return region, nil + } + + items := []string{"region"} + // TODO: Unsure if this is the correct section for region + values, err := oc.getSection("Global", items) + if err != nil { + return "", fmt.Errorf("Region not provided in OS_REGION_NAME or openstack config section GLOBAL") + } + return values["region"], nil +} + +func (oc OpenstackConfig) getCredentialFromFile() (gophercloud.AuthOptions, error) { opt := gophercloud.AuthOptions{} name := "Default" items := []string{"identity", "user", "user_id", "password", "domain_id", "domain_name", "tenant_id", "tenant_name"} @@ -304,7 +366,7 @@ func (p *SwiftPath) CreateFile(data io.ReadSeeker, acl ACL) error { func (p *SwiftPath) createBucket() error { done, err := RetryWithBackoff(swiftWriteBackoff, func() (bool, error) { - _, err := swiftcontainer.Get(p.client, p.bucket).Extract() + _, err := swiftcontainer.Get(p.client, p.bucket, swiftcontainer.GetOpts{}).Extract() if err == nil { return true, nil }