diff --git a/vendor/github.com/gophercloud/gophercloud/FAQ.md b/vendor/github.com/gophercloud/gophercloud/FAQ.md new file mode 100644 index 000000000..88a366a28 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/FAQ.md @@ -0,0 +1,148 @@ +# Tips + +## Implementing default logging and re-authentication attempts + +You can implement custom logging and/or limit re-auth attempts by creating a custom HTTP client +like the following and setting it as the provider client's HTTP Client (via the +`gophercloud.ProviderClient.HTTPClient` field): + +```go +//... + +// LogRoundTripper satisfies the http.RoundTripper interface and is used to +// customize the default Gophercloud RoundTripper to allow for logging. +type LogRoundTripper struct { + rt http.RoundTripper + numReauthAttempts int +} + +// newHTTPClient return a custom HTTP client that allows for logging relevant +// information before and after the HTTP request. +func newHTTPClient() http.Client { + return http.Client{ + Transport: &LogRoundTripper{ + rt: http.DefaultTransport, + }, + } +} + +// RoundTrip performs a round-trip HTTP request and logs relevant information about it. +func (lrt *LogRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) { + glog.Infof("Request URL: %s\n", request.URL) + + response, err := lrt.rt.RoundTrip(request) + if response == nil { + return nil, err + } + + if response.StatusCode == http.StatusUnauthorized { + if lrt.numReauthAttempts == 3 { + return response, fmt.Errorf("Tried to re-authenticate 3 times with no success.") + } + lrt.numReauthAttempts++ + } + + glog.Debugf("Response Status: %s\n", response.Status) + + return response, nil +} + +endpoint := "https://127.0.0.1/auth" +pc := openstack.NewClient(endpoint) +pc.HTTPClient = newHTTPClient() + +//... +``` + + +## Implementing custom objects + +OpenStack request/response objects may differ among variable names or types. + +### Custom request objects + +To pass custom options to a request, implement the desired `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/vendor/github.com/gophercloud/gophercloud/README.md b/vendor/github.com/gophercloud/gophercloud/README.md index 0e1fe0630..60ca479de 100644 --- a/vendor/github.com/gophercloud/gophercloud/README.md +++ b/vendor/github.com/gophercloud/gophercloud/README.md @@ -74,7 +74,7 @@ import ( // Option 1: Pass in the values yourself opts := gophercloud.AuthOptions{ - IdentityEndpoint: "https://my-openstack.com:5000/v2.0", + IdentityEndpoint: "https://openstack.example.com:5000/v2.0", Username: "{username}", Password: "{password}", } @@ -125,6 +125,10 @@ The above code sample creates a new server with the parameters, and embodies the new resource in the `server` variable (a [`servers.Server`](http://godoc.org/github.com/gophercloud/gophercloud) struct). +## Advanced Usage + +Have a look at the [FAQ](./FAQ.md) for some tips on customizing the way Gophercloud works. + ## Backwards-Compatibility Guarantees None. Vendor it and write tests covering the parts you use. diff --git a/vendor/github.com/gophercloud/gophercloud/STYLEGUIDE.md b/vendor/github.com/gophercloud/gophercloud/STYLEGUIDE.md index 18f6dc46b..e7531a83d 100644 --- a/vendor/github.com/gophercloud/gophercloud/STYLEGUIDE.md +++ b/vendor/github.com/gophercloud/gophercloud/STYLEGUIDE.md @@ -20,6 +20,12 @@ - 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). @@ -51,7 +57,7 @@ ### Naming -- For methods on a type in `response.go`, the receiver should be named `r` and the +- 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 diff --git a/vendor/github.com/gophercloud/gophercloud/auth_options.go b/vendor/github.com/gophercloud/gophercloud/auth_options.go index 3ee97dfb3..19c08341a 100644 --- a/vendor/github.com/gophercloud/gophercloud/auth_options.go +++ b/vendor/github.com/gophercloud/gophercloud/auth_options.go @@ -1,7 +1,7 @@ package gophercloud /* -AuthOptions stores information needed to authenticate to an OpenStack cluster. +AuthOptions stores information needed to authenticate to an OpenStack Cloud. You can populate one manually, or use a provider's AuthOptionsFromEnv() function to read relevant information from the standard environment variables. Pass one to a provider's AuthenticatedClient function to authenticate and obtain a @@ -21,19 +21,26 @@ type AuthOptions struct { // control panel to discover your account's username. In Identity V3, either // UserID or a combination of Username and DomainID or DomainName are needed. Username string `json:"username,omitempty"` - UserID string `json:"id,omitempty"` + UserID string `json:"-"` Password string `json:"password,omitempty"` // At most one of DomainID and DomainName must be provided if using Username // with Identity V3. Otherwise, either are optional. - DomainID string `json:"id,omitempty"` + DomainID string `json:"-"` DomainName string `json:"name,omitempty"` // The TenantID and TenantName fields are optional for the Identity V2 API. + // The same fields are known as project_id and project_name in the Identity + // V3 API, but are collected as TenantID and TenantName here in both cases. // Some providers allow you to specify a TenantName instead of the TenantId. // Some require both. Your provider's authentication policies will determine // how these fields influence authentication. + // If DomainID or DomainName are provided, they will also apply to TenantName. + // It is not currently possible to authenticate with Username and a Domain + // and scope to a Project in a different Domain by using TenantName. To + // accomplish that, the ProjectID will need to be provided to the TenantID + // option. TenantID string `json:"tenantId,omitempty"` TenantName string `json:"tenantName,omitempty"` @@ -132,14 +139,6 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s // if insufficient or incompatible information is present. var req request - // Test first for unrecognized arguments. - if opts.TenantID != "" { - return nil, ErrTenantIDProvided{} - } - if opts.TenantName != "" { - return nil, ErrTenantNameProvided{} - } - if opts.Password == "" { if opts.TokenID != "" { // Because we aren't using password authentication, it's an error to also provide any of the user-based authentication @@ -252,15 +251,12 @@ func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) { if opts.TenantID != "" { scope.ProjectID = opts.TenantID - opts.TenantID = "" - opts.TenantName = "" } else { if opts.TenantName != "" { scope.ProjectName = opts.TenantName scope.DomainID = opts.DomainID scope.DomainName = opts.DomainName } - opts.TenantName = "" } if scope.ProjectName != "" { diff --git a/vendor/github.com/gophercloud/gophercloud/doc.go b/vendor/github.com/gophercloud/gophercloud/doc.go index fb81a9d8f..b559516f9 100644 --- a/vendor/github.com/gophercloud/gophercloud/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/doc.go @@ -4,11 +4,13 @@ clouds. The library has a three-level hierarchy: providers, services, and resources. Provider structs represent the service providers that offer and manage a -collection of services. Examples of providers include: OpenStack, Rackspace, -HP. These are defined like so: +collection of services. The IdentityEndpoint is typically refered to as +"auth_url" in information provided by the cloud operator. Additionally, +the cloud may refer to TenantID or TenantName as project_id and project_name. +These are defined like so: opts := gophercloud.AuthOptions{ - IdentityEndpoint: "https://my-openstack.com:5000/v2.0", + IdentityEndpoint: "https://openstack.example.com:5000/v2.0", Username: "{username}", Password: "{password}", TenantID: "{tenant_id}", diff --git a/vendor/github.com/gophercloud/gophercloud/internal/pkg.go b/vendor/github.com/gophercloud/gophercloud/internal/pkg.go new file mode 100644 index 000000000..5bf0569ce --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/internal/pkg.go @@ -0,0 +1 @@ +package internal diff --git a/vendor/github.com/gophercloud/gophercloud/internal/util.go b/vendor/github.com/gophercloud/gophercloud/internal/util.go new file mode 100644 index 000000000..8efb283e7 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/internal/util.go @@ -0,0 +1,34 @@ +package internal + +import ( + "reflect" + "strings" +) + +// RemainingKeys will inspect a struct and compare it to a map. Any struct +// field that does not have a JSON tag that matches a key in the map or +// a matching lower-case field in the map will be returned as an extra. +// +// This is useful for determining the extra fields returned in response bodies +// for resources that can contain an arbitrary or dynamic number of fields. +func RemainingKeys(s interface{}, m map[string]interface{}) (extras map[string]interface{}) { + extras = make(map[string]interface{}) + for k, v := range m { + extras[k] = v + } + + valueOf := reflect.ValueOf(s) + typeOf := reflect.TypeOf(s) + for i := 0; i < valueOf.NumField(); i++ { + field := typeOf.Field(i) + + lowerField := strings.ToLower(field.Name) + delete(extras, lowerField) + + if tagValue := field.Tag.Get("json"); tagValue != "" && tagValue != "-" { + delete(extras, tagValue) + } + } + + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/client.go b/vendor/github.com/gophercloud/gophercloud/openstack/client.go index 6e61944a1..09120e8fa 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/client.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/client.go @@ -182,9 +182,10 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.Au // NewIdentityV2 creates a ServiceClient that may be used to interact with the v2 identity service. func NewIdentityV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { endpoint := client.IdentityBase + "v2.0/" + clientType := "identity" var err error if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) { - eo.ApplyDefaults("identity") + eo.ApplyDefaults(clientType) endpoint, err = client.EndpointLocator(eo) if err != nil { return nil, err @@ -194,15 +195,17 @@ func NewIdentityV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOp return &gophercloud.ServiceClient{ ProviderClient: client, Endpoint: endpoint, + Type: clientType, }, nil } // NewIdentityV3 creates a ServiceClient that may be used to access the v3 identity service. func NewIdentityV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { endpoint := client.IdentityBase + "v3/" + clientType := "identity" var err error if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) { - eo.ApplyDefaults("identity") + eo.ApplyDefaults(clientType) endpoint, err = client.EndpointLocator(eo) if err != nil { return nil, err @@ -212,112 +215,81 @@ func NewIdentityV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOp return &gophercloud.ServiceClient{ ProviderClient: client, Endpoint: endpoint, + Type: clientType, }, nil } +func initClientOpts(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts, clientType string) (*gophercloud.ServiceClient, error) { + sc := new(gophercloud.ServiceClient) + eo.ApplyDefaults(clientType) + url, err := client.EndpointLocator(eo) + if err != nil { + return sc, err + } + sc.ProviderClient = client + sc.Endpoint = url + sc.Type = clientType + return sc, nil +} + // NewObjectStorageV1 creates a ServiceClient that may be used with the v1 object storage package. func NewObjectStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - eo.ApplyDefaults("object-store") - url, err := client.EndpointLocator(eo) - if err != nil { - return nil, err - } - return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil + return initClientOpts(client, eo, "object-store") } // NewComputeV2 creates a ServiceClient that may be used with the v2 compute package. func NewComputeV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - eo.ApplyDefaults("compute") - url, err := client.EndpointLocator(eo) - if err != nil { - return nil, err - } - return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil + return initClientOpts(client, eo, "compute") } // NewNetworkV2 creates a ServiceClient that may be used with the v2 network package. func NewNetworkV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - eo.ApplyDefaults("network") - url, err := client.EndpointLocator(eo) - if err != nil { - return nil, err - } - return &gophercloud.ServiceClient{ - ProviderClient: client, - Endpoint: url, - ResourceBase: url + "v2.0/", - }, nil + sc, err := initClientOpts(client, eo, "network") + sc.ResourceBase = sc.Endpoint + "v2.0/" + return sc, err } // NewBlockStorageV1 creates a ServiceClient that may be used to access the v1 block storage service. func NewBlockStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - eo.ApplyDefaults("volume") - url, err := client.EndpointLocator(eo) - if err != nil { - return nil, err - } - return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil + return initClientOpts(client, eo, "volume") } // NewBlockStorageV2 creates a ServiceClient that may be used to access the v2 block storage service. func NewBlockStorageV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - eo.ApplyDefaults("volumev2") - url, err := client.EndpointLocator(eo) - if err != nil { - return nil, err - } - return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil + return initClientOpts(client, eo, "volumev2") } // NewSharedFileSystemV2 creates a ServiceClient that may be used to access the v2 shared file system service. func NewSharedFileSystemV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - eo.ApplyDefaults("sharev2") - url, err := client.EndpointLocator(eo) - if err != nil { - return nil, err - } - return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil + return initClientOpts(client, eo, "sharev2") } // NewCDNV1 creates a ServiceClient that may be used to access the OpenStack v1 // CDN service. func NewCDNV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - eo.ApplyDefaults("cdn") - url, err := client.EndpointLocator(eo) - if err != nil { - return nil, err - } - return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil + return initClientOpts(client, eo, "cdn") } // NewOrchestrationV1 creates a ServiceClient that may be used to access the v1 orchestration service. func NewOrchestrationV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - eo.ApplyDefaults("orchestration") - url, err := client.EndpointLocator(eo) - if err != nil { - return nil, err - } - return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil + return initClientOpts(client, eo, "orchestration") } // NewDBV1 creates a ServiceClient that may be used to access the v1 DB service. func NewDBV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - eo.ApplyDefaults("database") - url, err := client.EndpointLocator(eo) - if err != nil { - return nil, err - } - return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil + return initClientOpts(client, eo, "database") +} + +// NewDNSV2 creates a ServiceClient that may be used to access the v2 DNS service. +func NewDNSV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + sc, err := initClientOpts(client, eo, "dns") + sc.ResourceBase = sc.Endpoint + "v2/" + return sc, err } // NewImageServiceV2 creates a ServiceClient that may be used to access the v2 image service. func NewImageServiceV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - eo.ApplyDefaults("image") - url, err := client.EndpointLocator(eo) - if err != nil { - return nil, err - } - return &gophercloud.ServiceClient{ProviderClient: client, - Endpoint: url, - ResourceBase: url + "v2/"}, nil + sc, err := initClientOpts(client, eo, "image") + sc.ResourceBase = sc.Endpoint + "v2/" + return sc, err } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips/results.go index 753f3afa7..2f5b33844 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips/results.go @@ -1,6 +1,9 @@ package floatingips import ( + "encoding/json" + "strconv" + "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/pagination" ) @@ -8,7 +11,7 @@ import ( // A FloatingIP is an IP that can be associated with an instance type FloatingIP struct { // ID is a unique ID of the Floating IP - ID string `json:"id"` + ID string `json:"-"` // FixedIP is the IP of the instance related to the Floating IP FixedIP string `json:"fixed_ip,omitempty"` @@ -23,6 +26,29 @@ type FloatingIP struct { Pool string `json:"pool"` } +func (r *FloatingIP) UnmarshalJSON(b []byte) error { + type tmp FloatingIP + var s struct { + tmp + ID interface{} `json:"id"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + + *r = FloatingIP(s.tmp) + + switch t := s.ID.(type) { + case float64: + r.ID = strconv.FormatFloat(t, 'f', -1, 64) + case string: + r.ID = t + } + + return err +} + // FloatingIPPage stores a single, only page of FloatingIPs // results from a List call. type FloatingIPPage struct { diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs/results.go index f4d8d35c3..4c785a24c 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs/results.go @@ -5,7 +5,7 @@ import ( "github.com/gophercloud/gophercloud/pagination" ) -// KeyPair is an SSH key known to the OpenStack cluster that is available to be injected into +// KeyPair is an SSH key known to the OpenStack Cloud that is available to be injected into // servers. type KeyPair struct { // Name is used to refer to this keypair from other services within this region. diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/requests.go index ef133ff80..03d7e8724 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/requests.go @@ -11,6 +11,24 @@ type ListOptsBuilder interface { ToFlavorListQuery() (string, error) } +// AccessType maps to OpenStack's Flavor.is_public field. Although the is_public field is boolean, the +// request options are ternary, which is why AccessType is a string. The following values are +// allowed: +// +// PublicAccess (the default): Returns public flavors and private flavors associated with that project. +// PrivateAccess (admin only): Returns private flavors, across all projects. +// AllAccess (admin only): Returns public and private flavors across all projects. +// +// The AccessType arguement is optional, and if it is not supplied, OpenStack returns the PublicAccess +// flavors. +type AccessType string + +const ( + PublicAccess AccessType = "true" + PrivateAccess AccessType = "false" + AllAccess AccessType = "None" +) + // ListOpts helps control the results returned by the List() function. // For example, a flavor with a minDisk field of 10 will not be returned if you specify MinDisk set to 20. // Typically, software will use the last ID of the previous call to List to set the Marker for the current call. @@ -29,6 +47,10 @@ type ListOpts struct { // Limit instructs List to refrain from sending excessively large lists of flavors. Limit int `q:"limit"` + + // AccessType, if provided, instructs List which set of flavors to return. If IsPublic not provided, + // flavors for the current project are returned. + AccessType AccessType `q:"is_public"` } // ToFlavorListQuery formats a ListOpts into a query string. @@ -54,6 +76,47 @@ func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) paginat }) } +type CreateOptsBuilder interface { + ToFlavorCreateMap() (map[string]interface{}, error) +} + +// CreateOpts is passed to Create to create a flavor +// Source: +// https://github.com/openstack/nova/blob/stable/newton/nova/api/openstack/compute/schemas/flavor_manage.py#L20 +type CreateOpts struct { + Name string `json:"name" required:"true"` + // memory size, in MBs + RAM int `json:"ram" required:"true"` + VCPUs int `json:"vcpus" required:"true"` + // disk size, in GBs + Disk *int `json:"disk" required:"true"` + ID string `json:"id,omitempty"` + // non-zero, positive + Swap *int `json:"swap,omitempty"` + RxTxFactor float64 `json:"rxtx_factor,omitempty"` + IsPublic *bool `json:"os-flavor-access:is_public,omitempty"` + // ephemeral disk size, in GBs, non-zero, positive + Ephemeral *int `json:"OS-FLV-EXT-DATA:ephemeral,omitempty"` +} + +// ToFlavorCreateMap satisfies the CreateOptsBuilder interface +func (opts *CreateOpts) ToFlavorCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "flavor") +} + +// Create a flavor +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToFlavorCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201}, + }) + return +} + // Get instructs OpenStack to provide details on a single flavor, identified by its ID. // Use ExtractFlavor to convert its result into a Flavor. func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/results.go index a49de0da7..121abbb8d 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/results.go @@ -8,13 +8,21 @@ import ( "github.com/gophercloud/gophercloud/pagination" ) -// GetResult temporarily holds the response from a Get call. -type GetResult struct { +type commonResult struct { gophercloud.Result } -// Extract provides access to the individual Flavor returned by the Get function. -func (r GetResult) Extract() (*Flavor, error) { +type CreateResult struct { + commonResult +} + +// GetResult temporarily holds the response from a Get call. +type GetResult struct { + commonResult +} + +// Extract provides access to the individual Flavor returned by the Get and Create functions. +func (r commonResult) Extract() (*Flavor, error) { var s struct { Flavor *Flavor `json:"flavor"` } @@ -38,43 +46,36 @@ type Flavor struct { Swap int `json:"swap"` // VCPUs indicates how many (virtual) CPUs are available for this flavor. VCPUs int `json:"vcpus"` + // IsPublic indicates whether the flavor is public. + IsPublic bool `json:"is_public"` } -func (f *Flavor) UnmarshalJSON(b []byte) error { - var flavor struct { - ID string `json:"id"` - Disk int `json:"disk"` - RAM int `json:"ram"` - Name string `json:"name"` - RxTxFactor float64 `json:"rxtx_factor"` - Swap interface{} `json:"swap"` - VCPUs int `json:"vcpus"` +func (r *Flavor) UnmarshalJSON(b []byte) error { + type tmp Flavor + var s struct { + tmp + Swap interface{} `json:"swap"` } - err := json.Unmarshal(b, &flavor) + err := json.Unmarshal(b, &s) if err != nil { return err } - f.ID = flavor.ID - f.Disk = flavor.Disk - f.RAM = flavor.RAM - f.Name = flavor.Name - f.RxTxFactor = flavor.RxTxFactor - f.VCPUs = flavor.VCPUs + *r = Flavor(s.tmp) - switch t := flavor.Swap.(type) { + switch t := s.Swap.(type) { case float64: - f.Swap = int(t) + r.Swap = int(t) case string: switch t { case "": - f.Swap = 0 + r.Swap = 0 default: swap, err := strconv.ParseFloat(t, 64) if err != nil { return err } - f.Swap = int(swap) + r.Swap = int(swap) } } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/urls.go index ee0dfdbe3..2fc21796f 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/urls.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/urls.go @@ -11,3 +11,7 @@ func getURL(client *gophercloud.ServiceClient, id string) string { func listURL(client *gophercloud.ServiceClient) string { return client.ServiceURL("flavors", "detail") } + +func createURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("flavors") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/results.go index a55b8f160..f9ebc69e9 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images/results.go @@ -45,8 +45,8 @@ type Image struct { Status string Updated string - - Metadata map[string]string + + Metadata map[string]interface{} } // ImagePage contains a single page of results from a List operation. diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/requests.go index 0ec5b0fdb..961863731 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/requests.go @@ -160,7 +160,7 @@ type CreateOpts struct { // Personality includes files to inject into the server at launch. // Create will base64-encode file contents for you. - Personality Personality `json:"-"` + Personality Personality `json:"personality,omitempty"` // ConfigDrive enables metadata injection through a configuration drive. ConfigDrive *bool `json:"config_drive,omitempty"` @@ -401,11 +401,10 @@ type RebuildOptsBuilder interface { // operation type RebuildOpts struct { // The server's admin password - AdminPass string `json:"adminPass" required:"true"` + AdminPass string `json:"adminPass,omitempty"` // The ID of the image you want your server to be provisioned on ImageID string `json:"imageRef"` ImageName string `json:"-"` - //ImageName string `json:"-"` // Name to set the server to Name string `json:"name,omitempty"` // AccessIPv4 [optional] provides a new IPv4 address for the instance. diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/results.go index a23923a76..1ae1e91c7 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/results.go @@ -7,6 +7,7 @@ import ( "fmt" "net/url" "path" + "time" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/pagination" @@ -18,11 +19,17 @@ type serverResult struct { // Extract interprets any serverResult as a Server, if possible. func (r serverResult) Extract() (*Server, error) { - var s struct { - Server *Server `json:"server"` - } + var s Server err := r.ExtractInto(&s) - return s.Server, err + return &s, err +} + +func (r serverResult) ExtractInto(v interface{}) error { + return r.Result.ExtractIntoStructPtr(v, "server") +} + +func ExtractServersInto(r pagination.Page, v interface{}) error { + return r.(ServerPage).Result.ExtractIntoSlicePtr(v, "servers") } // CreateResult temporarily contains the response from a Create call. @@ -101,12 +108,12 @@ func decryptPassword(encryptedPassword string, privateKey *rsa.PrivateKey) (stri } // ExtractImageID gets the ID of the newly created server image from the header -func (res CreateImageResult) ExtractImageID() (string, error) { - if res.Err != nil { - return "", res.Err +func (r CreateImageResult) ExtractImageID() (string, error) { + if r.Err != nil { + return "", r.Err } // Get the image id from the header - u, err := url.ParseRequestURI(res.Header.Get("Location")) + u, err := url.ParseRequestURI(r.Header.Get("Location")) if err != nil { return "", err } @@ -137,26 +144,27 @@ type Server struct { // Name contains the human-readable name for the server. Name string `json:"name"` // Updated and Created contain ISO-8601 timestamps of when the state of the server last changed, and when it was created. - Updated string - Created string - HostID string + Updated time.Time `json:"updated"` + Created time.Time `json:"created"` + HostID string `json:"hostid"` // Status contains the current operational status of the server, such as IN_PROGRESS or ACTIVE. - Status string + Status string `json:"status"` // Progress ranges from 0..100. // A request made against the server completes only once Progress reaches 100. - Progress int + Progress int `json:"progress"` // AccessIPv4 and AccessIPv6 contain the IP addresses of the server, suitable for remote access for administration. - AccessIPv4, AccessIPv6 string + AccessIPv4 string `json:"accessIPv4"` + AccessIPv6 string `json:"accessIPv6"` // Image refers to a JSON object, which itself indicates the OS image used to deploy the server. - Image map[string]interface{} + Image map[string]interface{} `json:"-"` // Flavor refers to a JSON object, which itself indicates the hardware configuration of the deployed server. - Flavor map[string]interface{} + Flavor map[string]interface{} `json:"flavor"` // Addresses includes a list of all IP addresses assigned to the server, keyed by pool. - Addresses map[string]interface{} + Addresses map[string]interface{} `json:"addresses"` // Metadata includes a list of all user-specified key-value pairs attached to the server. - Metadata map[string]string + Metadata map[string]string `json:"metadata"` // Links includes HTTP references to the itself, useful for passing along to other APIs that might want a server reference. - Links []interface{} + Links []interface{} `json:"links"` // KeyName indicates which public key was injected into the server on launch. KeyName string `json:"key_name"` // AdminPass will generally be empty (""). However, it will contain the administrative password chosen when provisioning a new server without a set AdminPass setting in the first place. @@ -166,30 +174,30 @@ type Server struct { SecurityGroups []map[string]interface{} `json:"security_groups"` } -func (s *Server) UnmarshalJSON(b []byte) error { +func (r *Server) UnmarshalJSON(b []byte) error { type tmp Server - var server *struct { + var s struct { tmp - Image interface{} + Image interface{} `json:"image"` } - err := json.Unmarshal(b, &server) + err := json.Unmarshal(b, &s) if err != nil { return err } - *s = Server(server.tmp) + *r = Server(s.tmp) - switch t := server.Image.(type) { + switch t := s.Image.(type) { case map[string]interface{}: - s.Image = t + r.Image = t case string: switch t { case "": - s.Image = nil + r.Image = nil } } - return nil + return err } // ServerPage abstracts the raw results of making a List() request against the API. @@ -200,17 +208,17 @@ type ServerPage struct { } // IsEmpty returns true if a page contains no Server results. -func (page ServerPage) IsEmpty() (bool, error) { - servers, err := ExtractServers(page) - return len(servers) == 0, err +func (r ServerPage) IsEmpty() (bool, error) { + s, err := ExtractServers(r) + return len(s) == 0, err } // NextPageURL uses the response's embedded link reference to navigate to the next page of results. -func (page ServerPage) NextPageURL() (string, error) { +func (r ServerPage) NextPageURL() (string, error) { var s struct { Links []gophercloud.Link `json:"servers_links"` } - err := page.ExtractInto(&s) + err := r.ExtractInto(&s) if err != nil { return "", err } @@ -219,11 +227,9 @@ func (page ServerPage) NextPageURL() (string, error) { // ExtractServers interprets the results of a single page from a List() call, producing a slice of Server entities. func ExtractServers(r pagination.Page) ([]Server, error) { - var s struct { - Servers []Server `json:"servers"` - } - err := (r.(ServerPage)).ExtractInto(&s) - return s.Servers, err + var s []Server + err := ExtractServersInto(r, &s) + return s, err } // MetadataResult contains the result of a call for (potentially) multiple key-value pairs. diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/requests.go index b9d7de65f..b6550ce60 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/requests.go @@ -27,3 +27,81 @@ func List(client *gophercloud.ServiceClient, opts *ListOpts) pagination.Pager { return TenantPage{pagination.LinkedPageBase{PageResult: r}} }) } + +// CreateOpts represents the options needed when creating new tenant. +type CreateOpts struct { + // Name is the name of the tenant. + Name string `json:"name" required:"true"` + // Description is the description of the tenant. + Description string `json:"description,omitempty"` + // Enabled sets the tenant status to enabled or disabled. + Enabled *bool `json:"enabled,omitempty"` +} + +// CreateOptsBuilder describes struct types that can be accepted by the Create call. +type CreateOptsBuilder interface { + ToTenantCreateMap() (map[string]interface{}, error) +} + +// ToTenantCreateMap assembles a request body based on the contents of a CreateOpts. +func (opts CreateOpts) ToTenantCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "tenant") +} + +// Create is the operation responsible for creating new tenant. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToTenantCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201}, + }) + return +} + +// Get requests details on a single tenant by ID. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, id), &r.Body, nil) + return +} + +// UpdateOptsBuilder allows extensions to add additional attributes to the Update request. +type UpdateOptsBuilder interface { + ToTenantUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts specifies the base attributes that may be updated on an existing server. +type UpdateOpts struct { + // Name is the name of the tenant. + Name string `json:"name,omitempty"` + // Description is the description of the tenant. + Description string `json:"description,omitempty"` + // Enabled sets the tenant status to enabled or disabled. + Enabled *bool `json:"enabled,omitempty"` +} + +// ToTenantUpdateMap formats an UpdateOpts structure into a request body. +func (opts UpdateOpts) ToTenantUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "tenant") +} + +// Update is the operation responsible for updating exist tenants by their TenantID. +func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToTenantUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Put(updateURL(client, id), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Delete is the operation responsible for permanently deleting an API tenant. +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, id), nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/results.go index 3ce1e6773..5a319de5c 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/results.go @@ -51,3 +51,36 @@ func ExtractTenants(r pagination.Page) ([]Tenant, error) { err := (r.(TenantPage)).ExtractInto(&s) return s.Tenants, err } + +type tenantResult struct { + gophercloud.Result +} + +// Extract interprets any tenantResults as a tenant. +func (r tenantResult) Extract() (*Tenant, error) { + var s struct { + Tenant *Tenant `json:"tenant"` + } + err := r.ExtractInto(&s) + return s.Tenant, err +} + +// GetResult temporarily contains the response from the Get call. +type GetResult struct { + tenantResult +} + +// CreateResult temporarily contains the reponse from the Create call. +type CreateResult struct { + tenantResult +} + +// DeleteResult temporarily contains the response from the Delete call. +type DeleteResult struct { + gophercloud.ErrResult +} + +// UpdateResult temporarily contains the response from the Update call. +type UpdateResult struct { + tenantResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/urls.go index 101599bc9..0f0266907 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/urls.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/urls.go @@ -5,3 +5,19 @@ import "github.com/gophercloud/gophercloud" func listURL(client *gophercloud.ServiceClient) string { return client.ServiceURL("tenants") } + +func getURL(client *gophercloud.ServiceClient, tenantID string) string { + return client.ServiceURL("tenants", tenantID) +} + +func createURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("tenants") +} + +func deleteURL(client *gophercloud.ServiceClient, tenantID string) string { + return client.ServiceURL("tenants", tenantID) +} + +func updateURL(client *gophercloud.ServiceClient, tenantID string) string { + return client.ServiceURL("tenants", tenantID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/results.go index 93c0554ae..6b3649370 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/results.go @@ -132,11 +132,6 @@ func (r CreateResult) ExtractServiceCatalog() (*ServiceCatalog, error) { return &ServiceCatalog{Entries: s.Access.Entries}, err } -// createErr quickly packs an error in a CreateResult. -func createErr(err error) CreateResult { - return CreateResult{gophercloud.Result{Err: err}} -} - // ExtractUser returns the User from a GetResult. func (r GetResult) ExtractUser() (*User, error) { var s struct { diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go index ba4363b2b..39c19aee3 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go @@ -4,10 +4,10 @@ import "github.com/gophercloud/gophercloud" // Scope allows a created token to be limited to a specific domain or project. type Scope struct { - ProjectID string `json:"scope.project.id,omitempty" not:"ProjectName,DomainID,DomainName"` - ProjectName string `json:"scope.project.name,omitempty"` - DomainID string `json:"scope.project.id,omitempty" not:"ProjectName,ProjectID,DomainName"` - DomainName string `json:"scope.project.id,omitempty"` + ProjectID string + ProjectName string + DomainID string + DomainName string } // AuthOptionsBuilder describes any argument that may be passed to the Create call. @@ -36,7 +36,7 @@ type AuthOptions struct { // At most one of DomainID and DomainName must be provided if using Username // with Identity V3. Otherwise, either are optional. - DomainID string `json:"id,omitempty"` + DomainID string `json:"-"` DomainName string `json:"name,omitempty"` // AllowReauth should be set to true if you grant permission for Gophercloud to @@ -182,13 +182,13 @@ func Get(c *gophercloud.ServiceClient, token string) (r GetResult) { func Validate(c *gophercloud.ServiceClient, token string) (bool, error) { resp, err := c.Request("HEAD", tokenURL(c), &gophercloud.RequestOpts{ MoreHeaders: subjectTokenHeaders(c, token), - OkCodes: []int{204, 404}, + OkCodes: []int{200, 204, 404}, }) if err != nil { return false, err } - return resp.StatusCode == 204, nil + return resp.StatusCode == 200 || resp.StatusCode == 204, nil } // Revoke immediately makes specified token invalid. diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/results.go index 36c9ce619..7c306e83f 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/results.go @@ -1,7 +1,10 @@ package tokens -import "errors" -import "github.com/gophercloud/gophercloud" +import ( + "time" + + "github.com/gophercloud/gophercloud" +) // Endpoint represents a single API endpoint offered by a service. // It matches either a public, internal or admin URL. @@ -35,7 +38,33 @@ type CatalogEntry struct { // ServiceCatalog provides a view into the service catalog from a previous, successful authentication. type ServiceCatalog struct { - Entries []CatalogEntry + Entries []CatalogEntry `json:"catalog"` +} + +// Domain provides information about the domain to which this token grants access. +type Domain struct { + ID string `json:"id"` + Name string `json:"name"` +} + +// User represents a user resource that exists on the API. +type User struct { + Domain Domain `json:"domain"` + ID string `json:"id"` + Name string `json:"name"` +} + +// Role provides information about roles to which User is authorized. +type Role struct { + ID string `json:"id"` + Name string `json:"name"` +} + +// Project provides information about project to which User is authorized. +type Project struct { + Domain Domain `json:"domain"` + ID string `json:"id"` + Name string `json:"name"` } // commonResult is the deferred result of a Create or a Get call. @@ -51,34 +80,50 @@ func (r commonResult) Extract() (*Token, error) { // ExtractToken interprets a commonResult as a Token. func (r commonResult) ExtractToken() (*Token, error) { - var s struct { - Token *Token `json:"token"` - } - + var s Token err := r.ExtractInto(&s) if err != nil { return nil, err } - if s.Token == nil { - return nil, errors.New("'token' missing in JSON response") - } - // Parse the token itself from the stored headers. - s.Token.ID = r.Header.Get("X-Subject-Token") + s.ID = r.Header.Get("X-Subject-Token") - return s.Token, err + return &s, err } // ExtractServiceCatalog returns the ServiceCatalog that was generated along with the user's Token. -func (r CreateResult) ExtractServiceCatalog() (*ServiceCatalog, error) { +func (r commonResult) ExtractServiceCatalog() (*ServiceCatalog, error) { + var s ServiceCatalog + err := r.ExtractInto(&s) + return &s, err +} + +// ExtractUser returns the User that is the owner of the Token. +func (r commonResult) ExtractUser() (*User, error) { var s struct { - Token struct { - Entries []CatalogEntry `json:"catalog"` - } `json:"token"` + User *User `json:"user"` } err := r.ExtractInto(&s) - return &ServiceCatalog{Entries: s.Token.Entries}, err + return s.User, err +} + +// ExtractRoles returns Roles to which User is authorized. +func (r commonResult) ExtractRoles() ([]Role, error) { + var s struct { + Roles []Role `json:"roles"` + } + err := r.ExtractInto(&s) + return s.Roles, err +} + +// ExtractProject returns Project to which User is authorized. +func (r commonResult) ExtractProject() (*Project, error) { + var s struct { + Project *Project `json:"project"` + } + err := r.ExtractInto(&s) + return s.Project, err } // CreateResult defers the interpretation of a created token. @@ -87,13 +132,6 @@ type CreateResult struct { commonResult } -// createErr quickly creates a CreateResult that reports an error. -func createErr(err error) CreateResult { - return CreateResult{ - commonResult: commonResult{Result: gophercloud.Result{Err: err}}, - } -} - // GetResult is the deferred response from a Get call. type GetResult struct { commonResult @@ -110,5 +148,9 @@ type Token struct { // ID is the issued token. ID string `json:"id"` // ExpiresAt is the timestamp at which this token will no longer be accepted. - ExpiresAt gophercloud.JSONRFC3339Milli `json:"expires_at"` + ExpiresAt time.Time `json:"expires_at"` +} + +func (r commonResult) ExtractInto(v interface{}) error { + return r.ExtractIntoStructPtr(v, "token") } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/requests.go index 32f09ee95..044b5cb95 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/requests.go @@ -99,7 +99,7 @@ type CreateOpts struct { // properties is a set of properties, if any, that // are associated with the image. - Properties map[string]string `json:"-,omitempty"` + Properties map[string]string `json:"-"` } // ToImageCreateMap assembles a request body based on the contents of diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/results.go index 653c68c73..632186b72 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/results.go @@ -7,6 +7,7 @@ import ( "time" "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/internal" "github.com/gophercloud/gophercloud/pagination" ) @@ -63,13 +64,13 @@ type Image struct { Metadata map[string]string `json:"metadata"` // Properties is a set of key-value pairs, if any, that are associated with the image. - Properties map[string]string `json:"properties"` + Properties map[string]interface{} `json:"-"` // CreatedAt is the date when the image has been created. - CreatedAt time.Time `json:"-"` + CreatedAt time.Time `json:"created_at"` // UpdatedAt is the date when the last change has been made to the image or it's properties. - UpdatedAt time.Time `json:"-"` + UpdatedAt time.Time `json:"updated_at"` // File is the trailing path after the glance endpoint that represent the location // of the image or the path to retrieve it. @@ -77,38 +78,45 @@ type Image struct { // Schema is the path to the JSON-schema that represent the image or image entity. Schema string `json:"schema"` + + // VirtualSize is the virtual size of the image + VirtualSize int64 `json:"virtual_size"` } -func (s *Image) UnmarshalJSON(b []byte) error { +func (r *Image) UnmarshalJSON(b []byte) error { type tmp Image - var p *struct { + var s struct { tmp SizeBytes interface{} `json:"size"` - CreatedAt string `json:"created_at"` - UpdatedAt string `json:"updated_at"` } - err := json.Unmarshal(b, &p) + err := json.Unmarshal(b, &s) if err != nil { return err } - *s = Image(p.tmp) + *r = Image(s.tmp) - switch t := p.SizeBytes.(type) { + switch t := s.SizeBytes.(type) { case nil: return nil case float32: - s.SizeBytes = int64(t) + r.SizeBytes = int64(t) case float64: - s.SizeBytes = int64(t) + r.SizeBytes = int64(t) default: return fmt.Errorf("Unknown type for SizeBytes: %v (value: %v)", reflect.TypeOf(t), t) } - s.CreatedAt, err = time.Parse(time.RFC3339, p.CreatedAt) + // Bundle all other fields into Properties + var result interface{} + err = json.Unmarshal(b, &result) if err != nil { return err } - s.UpdatedAt, err = time.Parse(time.RFC3339, p.UpdatedAt) + if resultMap, ok := result.(map[string]interface{}); ok { + delete(resultMap, "self") + r.Properties = internal.RemainingKeys(Image{}, resultMap) + } + return err } @@ -163,7 +171,12 @@ func (r ImagePage) NextPageURL() (string, error) { if err != nil { return "", err } - return nextPageURL(r.URL.String(), s.Next), nil + + if s.Next == "" { + return "", nil + } + + return nextPageURL(r.URL.String(), s.Next) } // ExtractImages interprets the results of a single page from a List() call, producing a slice of Image entities. diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/urls.go index 58cb8f715..bf7cea1ef 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/urls.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/urls.go @@ -1,7 +1,7 @@ package images import ( - "strings" + "net/url" "github.com/gophercloud/gophercloud" ) @@ -38,7 +38,14 @@ func deleteURL(c *gophercloud.ServiceClient, imageID string) string { } // builds next page full url based on current url -func nextPageURL(currentURL string, next string) string { - base := currentURL[:strings.Index(currentURL, "/images")] - return base + next +func nextPageURL(currentURL string, next string) (string, error) { + base, err := url.Parse(currentURL) + if err != nil { + return "", err + } + rel, err := url.Parse(next) + if err != nil { + return "", err + } + return base.ResolveReference(rel).String(), nil } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/members/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/members/requests.go index 8c667cbdd..b16fb82d4 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/members/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/members/requests.go @@ -18,7 +18,7 @@ import ( func Create(client *gophercloud.ServiceClient, id string, member string) (r CreateResult) { b := map[string]interface{}{"member": member} _, r.Err = client.Post(createMemberURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ - OkCodes: []int{200, 409, 403}, + OkCodes: []int{200}, }) return } @@ -42,7 +42,7 @@ func Get(client *gophercloud.ServiceClient, imageID string, memberID string) (r // Callee should be image owner // More details: http://developer.openstack.org/api-ref-image-v2.html#deleteImageMember-v2 func Delete(client *gophercloud.ServiceClient, imageID string, memberID string) (r DeleteResult) { - _, r.Err = client.Delete(deleteMemberURL(client, imageID, memberID), &gophercloud.RequestOpts{OkCodes: []int{204, 403}}) + _, r.Err = client.Delete(deleteMemberURL(client, imageID, memberID), &gophercloud.RequestOpts{OkCodes: []int{204}}) return } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/members/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/members/results.go index 1d2a9c05c..d3cc1ceaf 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/members/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/members/results.go @@ -1,7 +1,6 @@ package members import ( - "encoding/json" "time" "github.com/gophercloud/gophercloud" @@ -10,33 +9,12 @@ import ( // Member model type Member struct { - CreatedAt time.Time `json:"-"` + CreatedAt time.Time `json:"created_at"` ImageID string `json:"image_id"` MemberID string `json:"member_id"` Schema string `json:"schema"` Status string `json:"status"` - UpdatedAt time.Time `json:"-"` -} - -func (s *Member) UnmarshalJSON(b []byte) error { - type tmp Member - var p *struct { - tmp - CreatedAt string `json:"created_at"` - UpdatedAt string `json:"updated_at"` - } - err := json.Unmarshal(b, &p) - if err != nil { - return err - } - - *s = Member(p.tmp) - s.CreatedAt, err = time.Parse(time.RFC3339, p.CreatedAt) - if err != nil { - return err - } - s.UpdatedAt, err = time.Parse(time.RFC3339, p.UpdatedAt) - return err + UpdatedAt time.Time `json:"updated_at"` } // Extract Member model from request if possible diff --git a/vendor/github.com/gophercloud/gophercloud/pagination/http.go b/vendor/github.com/gophercloud/gophercloud/pagination/http.go index cb4b4ae6b..757295c42 100644 --- a/vendor/github.com/gophercloud/gophercloud/pagination/http.go +++ b/vendor/github.com/gophercloud/gophercloud/pagination/http.go @@ -55,6 +55,6 @@ func PageResultFromParsed(resp *http.Response, body interface{}) PageResult { func Request(client *gophercloud.ServiceClient, headers map[string]string, url string) (*http.Response, error) { return client.Get(url, nil, &gophercloud.RequestOpts{ MoreHeaders: headers, - OkCodes: []int{200, 204}, + OkCodes: []int{200, 204, 300}, }) } diff --git a/vendor/github.com/gophercloud/gophercloud/pagination/pager.go b/vendor/github.com/gophercloud/gophercloud/pagination/pager.go index 1b5192ad6..6f1609ef2 100644 --- a/vendor/github.com/gophercloud/gophercloud/pagination/pager.go +++ b/vendor/github.com/gophercloud/gophercloud/pagination/pager.go @@ -145,27 +145,24 @@ func (p Pager) AllPages() (Page, error) { // Switch on the page body type. Recognized types are `map[string]interface{}`, // `[]byte`, and `[]interface{}`. - switch testPage.GetBody().(type) { + switch pb := testPage.GetBody().(type) { case map[string]interface{}: // key is the map key for the page body if the body type is `map[string]interface{}`. var key string // Iterate over the pages to concatenate the bodies. err = p.EachPage(func(page Page) (bool, error) { b := page.GetBody().(map[string]interface{}) - for k := range b { + for k, v := range b { // If it's a linked page, we don't want the `links`, we want the other one. if !strings.HasSuffix(k, "links") { - key = k + // check the field's type. we only want []interface{} (which is really []map[string]interface{}) + switch vt := v.(type) { + case []interface{}: + key = k + pagesSlice = append(pagesSlice, vt...) + } } } - switch keyType := b[key].(type) { - case map[string]interface{}: - pagesSlice = append(pagesSlice, keyType) - case []interface{}: - pagesSlice = append(pagesSlice, b[key].([]interface{})...) - default: - return false, fmt.Errorf("Unsupported page body type: %+v", keyType) - } return true, nil }) if err != nil { @@ -216,7 +213,7 @@ func (p Pager) AllPages() (Page, error) { default: err := gophercloud.ErrUnexpectedType{} err.Expected = "map[string]interface{}/[]byte/[]interface{}" - err.Actual = fmt.Sprintf("%v", reflect.TypeOf(testPage.GetBody())) + err.Actual = fmt.Sprintf("%T", pb) return nil, err } diff --git a/vendor/github.com/gophercloud/gophercloud/service_client.go b/vendor/github.com/gophercloud/gophercloud/service_client.go index 7484c67e5..1160fefa7 100644 --- a/vendor/github.com/gophercloud/gophercloud/service_client.go +++ b/vendor/github.com/gophercloud/gophercloud/service_client.go @@ -21,6 +21,12 @@ type ServiceClient struct { // as-is, instead. ResourceBase string + // This is the service client type (e.g. compute, sharev2). + // NOTE: FOR INTERNAL USE ONLY. DO NOT SET. GOPHERCLOUD WILL SET THIS. + // It is only exported because it gets set in a different package. + Type string + + // The microversion of the service to use. Set this to use a particular microversion. Microversion string } @@ -37,11 +43,13 @@ func (client *ServiceClient) ServiceURL(parts ...string) string { return client.ResourceBaseURL() + strings.Join(parts, "/") } -// Get calls `Request` with the "GET" HTTP verb. -func (client *ServiceClient) Get(url string, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) { - if opts == nil { - opts = &RequestOpts{} +func (client *ServiceClient) initReqOpts(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) { + if v, ok := (JSONBody).(io.Reader); ok { + opts.RawBody = v + } else if JSONBody != nil { + opts.JSONBody = JSONBody } + if JSONResponse != nil { opts.JSONResponse = JSONResponse } @@ -49,93 +57,66 @@ func (client *ServiceClient) Get(url string, JSONResponse interface{}, opts *Req if opts.MoreHeaders == nil { opts.MoreHeaders = make(map[string]string) } - opts.MoreHeaders["X-OpenStack-Nova-API-Version"] = client.Microversion + if client.Microversion != "" { + client.setMicroversionHeader(opts) + } +} + +// Get calls `Request` with the "GET" HTTP verb. +func (client *ServiceClient) Get(url string, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) { + if opts == nil { + opts = new(RequestOpts) + } + client.initReqOpts(url, nil, JSONResponse, opts) return client.Request("GET", url, opts) } // Post calls `Request` with the "POST" HTTP verb. func (client *ServiceClient) Post(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) { if opts == nil { - opts = &RequestOpts{} + opts = new(RequestOpts) } - - if v, ok := (JSONBody).(io.Reader); ok { - opts.RawBody = v - } else if JSONBody != nil { - opts.JSONBody = JSONBody - } - - if JSONResponse != nil { - opts.JSONResponse = JSONResponse - } - - if opts.MoreHeaders == nil { - opts.MoreHeaders = make(map[string]string) - } - opts.MoreHeaders["X-OpenStack-Nova-API-Version"] = client.Microversion - + client.initReqOpts(url, JSONBody, JSONResponse, opts) return client.Request("POST", url, opts) } // Put calls `Request` with the "PUT" HTTP verb. func (client *ServiceClient) Put(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) { if opts == nil { - opts = &RequestOpts{} + opts = new(RequestOpts) } - - if v, ok := (JSONBody).(io.Reader); ok { - opts.RawBody = v - } else if JSONBody != nil { - opts.JSONBody = JSONBody - } - - if JSONResponse != nil { - opts.JSONResponse = JSONResponse - } - - if opts.MoreHeaders == nil { - opts.MoreHeaders = make(map[string]string) - } - opts.MoreHeaders["X-OpenStack-Nova-API-Version"] = client.Microversion - + client.initReqOpts(url, JSONBody, JSONResponse, opts) return client.Request("PUT", url, opts) } // Patch calls `Request` with the "PATCH" HTTP verb. func (client *ServiceClient) Patch(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) { if opts == nil { - opts = &RequestOpts{} + opts = new(RequestOpts) } - - if v, ok := (JSONBody).(io.Reader); ok { - opts.RawBody = v - } else if JSONBody != nil { - opts.JSONBody = JSONBody - } - - if JSONResponse != nil { - opts.JSONResponse = JSONResponse - } - - if opts.MoreHeaders == nil { - opts.MoreHeaders = make(map[string]string) - } - opts.MoreHeaders["X-OpenStack-Nova-API-Version"] = client.Microversion - + client.initReqOpts(url, JSONBody, JSONResponse, opts) return client.Request("PATCH", url, opts) } // Delete calls `Request` with the "DELETE" HTTP verb. func (client *ServiceClient) Delete(url string, opts *RequestOpts) (*http.Response, error) { if opts == nil { - opts = &RequestOpts{} + opts = new(RequestOpts) } - - if opts.MoreHeaders == nil { - opts.MoreHeaders = make(map[string]string) - } - opts.MoreHeaders["X-OpenStack-Nova-API-Version"] = client.Microversion - + client.initReqOpts(url, nil, nil, opts) return client.Request("DELETE", url, opts) } + +func (client *ServiceClient) setMicroversionHeader(opts *RequestOpts) { + switch client.Type { + case "compute": + opts.MoreHeaders["X-OpenStack-Nova-API-Version"] = client.Microversion + case "sharev2": + opts.MoreHeaders["X-OpenStack-Manila-API-Version"] = client.Microversion + } + + if client.Type != "" { + opts.MoreHeaders["OpenStack-API-Version"] = client.Type + " " + client.Microversion + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/util.go b/vendor/github.com/gophercloud/gophercloud/util.go index 3d6a4e490..68f9a5d3e 100644 --- a/vendor/github.com/gophercloud/gophercloud/util.go +++ b/vendor/github.com/gophercloud/gophercloud/util.go @@ -1,7 +1,7 @@ package gophercloud import ( - "errors" + "fmt" "net/url" "path/filepath" "strings" @@ -9,27 +9,47 @@ import ( ) // WaitFor polls a predicate function, once per second, up to a timeout limit. -// It usually does this to wait for a resource to transition to a certain state. +// This is useful to wait for a resource to transition to a certain state. +// To handle situations when the predicate might hang indefinitely, the +// predicate will be prematurely cancelled after the timeout. // Resource packages will wrap this in a more convenient function that's // specific to a certain resource, but it can also be useful on its own. func WaitFor(timeout int, predicate func() (bool, error)) error { - start := time.Now().Second() + type WaitForResult struct { + Success bool + Error error + } + + start := time.Now().Unix() + for { - // Force a 1s sleep + // If a timeout is set, and that's been exceeded, shut it down. + if timeout >= 0 && time.Now().Unix()-start >= int64(timeout) { + return fmt.Errorf("A timeout occurred") + } + time.Sleep(1 * time.Second) - // If a timeout is set, and that's been exceeded, shut it down - if timeout >= 0 && time.Now().Second()-start >= timeout { - return errors.New("A timeout occurred") - } + var result WaitForResult + ch := make(chan bool, 1) + go func() { + defer close(ch) + satisfied, err := predicate() + result.Success = satisfied + result.Error = err + }() - // Execute the function - satisfied, err := predicate() - if err != nil { - return err - } - if satisfied { - return nil + select { + case <-ch: + if result.Error != nil { + return result.Error + } + if result.Success { + return nil + } + // If the predicate has not finished by the timeout, cancel it. + case <-time.After(time.Duration(timeout) * time.Second): + return fmt.Errorf("A timeout occurred") } } } diff --git a/vendor/vendor.json b/vendor/vendor.json index f3bbea787..d6d93a61b 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -425,106 +425,112 @@ "revisionTime": "2015-01-27T13:39:51Z" }, { - "checksumSHA1": "DkbpYqirk9i+2YDR5Ujzpot/oAg=", + "checksumSHA1": "QTqcF26Y2e0SHe2Z+2wj+fedud4=", "path": "github.com/gophercloud/gophercloud", - "revision": "d5eda9707e146108e4d424062b602fd97a71c2e6", - "revisionTime": "2016-11-14T18:28:31Z" + "revision": "95a28eb606def6aaaed082b6b82d3244b0552184", + "revisionTime": "2017-06-23T01:44:30Z" }, { - "checksumSHA1": "S3zTth9INyj1RfyHkQEvJAvRWvw=", + "checksumSHA1": "b7g9TcU1OmW7e2UySYeOAmcfHpY=", + "path": "github.com/gophercloud/gophercloud/internal", + "revision": "95a28eb606def6aaaed082b6b82d3244b0552184", + "revisionTime": "2017-06-23T01:44:30Z" + }, + { + "checksumSHA1": "24DO5BEQdFKNl1rfWgI2b4+ry5U=", "path": "github.com/gophercloud/gophercloud/openstack", - "revision": "d5eda9707e146108e4d424062b602fd97a71c2e6", - "revisionTime": "2016-11-14T18:28:31Z" + "revision": "95a28eb606def6aaaed082b6b82d3244b0552184", + "revisionTime": "2017-06-23T01:44:30Z" }, { "checksumSHA1": "Au6MAsI90lewLByg9n+Yjtdqdh8=", "path": "github.com/gophercloud/gophercloud/openstack/common/extensions", - "revision": "d5eda9707e146108e4d424062b602fd97a71c2e6", - "revisionTime": "2016-11-14T18:28:31Z" + "revision": "95a28eb606def6aaaed082b6b82d3244b0552184", + "revisionTime": "2017-06-23T01:44:30Z" }, { "checksumSHA1": "4XWDCGMYqipwJymi9xJo9UffD7g=", "path": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions", - "revision": "d5eda9707e146108e4d424062b602fd97a71c2e6", - "revisionTime": "2016-11-14T18:28:31Z" + "revision": "95a28eb606def6aaaed082b6b82d3244b0552184", + "revisionTime": "2017-06-23T01:44:30Z" }, { - "checksumSHA1": "pUlKsepGmWDd4PqPaK4W85pHsRU=", + "checksumSHA1": "e7AW3YDVYJPKUjpqsB4AL9RRlTw=", "path": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips", - "revision": "d5eda9707e146108e4d424062b602fd97a71c2e6", - "revisionTime": "2016-11-14T18:28:31Z" + "revision": "95a28eb606def6aaaed082b6b82d3244b0552184", + "revisionTime": "2017-06-23T01:44:30Z" }, { - "checksumSHA1": "RWwUliHD65cWApdEo4ckOcPSArg=", + "checksumSHA1": "bx6QnHtpgB6nKmN4QRVKa5PszqY=", "path": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs", - "revision": "d5eda9707e146108e4d424062b602fd97a71c2e6", - "revisionTime": "2016-11-14T18:28:31Z" + "revision": "95a28eb606def6aaaed082b6b82d3244b0552184", + "revisionTime": "2017-06-23T01:44:30Z" }, { "checksumSHA1": "qBpGbX7LQMPATdO8XyQmU7IXDiI=", "path": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop", - "revision": "d5eda9707e146108e4d424062b602fd97a71c2e6", - "revisionTime": "2016-11-14T18:28:31Z" + "revision": "95a28eb606def6aaaed082b6b82d3244b0552184", + "revisionTime": "2017-06-23T01:44:30Z" }, { - "checksumSHA1": "a9xDFPigDjHlPlthknKlBduGvKY=", + "checksumSHA1": "vTyXSR+Znw7/o/70UBOWG0F09r8=", "path": "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors", - "revision": "d5eda9707e146108e4d424062b602fd97a71c2e6", - "revisionTime": "2016-11-14T18:28:31Z" + "revision": "95a28eb606def6aaaed082b6b82d3244b0552184", + "revisionTime": "2017-06-23T01:44:30Z" }, { - "checksumSHA1": "UGeqrw3KdPNRwDxl315MAYyy/uY=", + "checksumSHA1": "Rnzx2YgOD41k8KoPA08tR992PxQ=", "path": "github.com/gophercloud/gophercloud/openstack/compute/v2/images", - "revision": "d5eda9707e146108e4d424062b602fd97a71c2e6", - "revisionTime": "2016-11-14T18:28:31Z" + "revision": "95a28eb606def6aaaed082b6b82d3244b0552184", + "revisionTime": "2017-06-23T01:44:30Z" }, { - "checksumSHA1": "8rOLNDSqwz/DSKL1BoPqjtWSWAE=", + "checksumSHA1": "IjCvcaNnRW++hclt21WUkMYinaA=", "path": "github.com/gophercloud/gophercloud/openstack/compute/v2/servers", - "revision": "d5eda9707e146108e4d424062b602fd97a71c2e6", - "revisionTime": "2016-11-14T18:28:31Z" + "revision": "95a28eb606def6aaaed082b6b82d3244b0552184", + "revisionTime": "2017-06-23T01:44:30Z" }, { - "checksumSHA1": "1sVqsZBZBNhDXLY9XzjMkcOkcbg=", + "checksumSHA1": "S8bHmOP+NjtlYioJC89zIBVvhYc=", "path": "github.com/gophercloud/gophercloud/openstack/identity/v2/tenants", - "revision": "d5eda9707e146108e4d424062b602fd97a71c2e6", - "revisionTime": "2016-11-14T18:28:31Z" + "revision": "95a28eb606def6aaaed082b6b82d3244b0552184", + "revisionTime": "2017-06-23T01:44:30Z" }, { - "checksumSHA1": "q1VGeltZl57OidZ5UDxbMsnyV2g=", + "checksumSHA1": "AvUU5En9YpG25iLlcAPDgcQODjI=", "path": "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens", - "revision": "d5eda9707e146108e4d424062b602fd97a71c2e6", - "revisionTime": "2016-11-14T18:28:31Z" + "revision": "95a28eb606def6aaaed082b6b82d3244b0552184", + "revisionTime": "2017-06-23T01:44:30Z" }, { - "checksumSHA1": "6M6ofb8ri5G+sZ8OiExLi7irdx8=", + "checksumSHA1": "rqE0NwmQ9qhXADXxg3DcuZ4A3wk=", "path": "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens", - "revision": "d5eda9707e146108e4d424062b602fd97a71c2e6", - "revisionTime": "2016-11-14T18:28:31Z" + "revision": "95a28eb606def6aaaed082b6b82d3244b0552184", + "revisionTime": "2017-06-23T01:44:30Z" }, { - "checksumSHA1": "Eo+cKV/XzaB5TyxK5ZKWYxPqGWY=", + "checksumSHA1": "p2ivHupXGBmyHkusnob2NsbsCQk=", "path": "github.com/gophercloud/gophercloud/openstack/imageservice/v2/images", - "revision": "d5eda9707e146108e4d424062b602fd97a71c2e6", - "revisionTime": "2016-11-14T18:28:31Z" + "revision": "95a28eb606def6aaaed082b6b82d3244b0552184", + "revisionTime": "2017-06-23T01:44:30Z" }, { - "checksumSHA1": "HKHLR7xxAb5aJ5zN8XYhDkn/PoM=", + "checksumSHA1": "KA5YKF9TwIsTy9KssO27y+wk/6U=", "path": "github.com/gophercloud/gophercloud/openstack/imageservice/v2/members", - "revision": "d5eda9707e146108e4d424062b602fd97a71c2e6", - "revisionTime": "2016-11-14T18:28:31Z" + "revision": "95a28eb606def6aaaed082b6b82d3244b0552184", + "revisionTime": "2017-06-23T01:44:30Z" }, { "checksumSHA1": "TDOZnaS0TO0NirpxV1QwPerAQTY=", "path": "github.com/gophercloud/gophercloud/openstack/utils", - "revision": "d5eda9707e146108e4d424062b602fd97a71c2e6", - "revisionTime": "2016-11-14T18:28:31Z" + "revision": "95a28eb606def6aaaed082b6b82d3244b0552184", + "revisionTime": "2017-06-23T01:44:30Z" }, { - "checksumSHA1": "pmpLcbUZ+EgLUmTbzMtGRq3haOU=", + "checksumSHA1": "YspETi3tOMvawKIT91HyuqaA5lM=", "path": "github.com/gophercloud/gophercloud/pagination", - "revision": "d5eda9707e146108e4d424062b602fd97a71c2e6", - "revisionTime": "2016-11-14T18:28:31Z" + "revision": "95a28eb606def6aaaed082b6b82d3244b0552184", + "revisionTime": "2017-06-23T01:44:30Z" }, { "checksumSHA1": "FUiF2WLrih0JdHsUTMMDz3DRokw=",