vendor: Updating Gophercloud for OpenStack Provider (#11191)

This commit is contained in:
Joe Topjian 2017-01-13 03:47:20 -07:00 committed by Paul Stack
parent a2c0b85b2d
commit 7b168604a1
14 changed files with 645 additions and 642 deletions

View File

@ -1,235 +0,0 @@
# Contributing to Gophercloud
- [Getting started](#getting-started)
- [Tests](#tests)
- [Style guide](#basic-style-guide)
- [3 ways to get involved](#5-ways-to-get-involved)
## Setting up your git workspace
As a contributor you will need to setup your workspace in a slightly different
way than just downloading it. Here are the basic installation instructions:
1. Configure your `$GOPATH` and run `go get` as described in the main
[README](/README.md#how-to-install) but add `-tags "fixtures acceptance"` to
get dependencies for unit and acceptance tests.
```bash
go get -tags "fixtures acceptance" github.com/gophercloud/gophercloud
```
2. Move into the directory that houses your local repository:
```bash
cd ${GOPATH}/src/github.com/gophercloud/gophercloud
```
3. Fork the `gophercloud/gophercloud` repository and update your remote refs. You
will need to rename the `origin` remote branch to `upstream`, and add your
fork as `origin` instead:
```bash
git remote rename origin upstream
git remote add origin git@github.com:<my_username>/gophercloud.git
```
4. Checkout the latest development branch:
```bash
git checkout master
```
5. If you're working on something (discussed more in detail below), you will
need to checkout a new feature branch:
```bash
git checkout -b my-new-feature
```
Another thing to bear in mind is that you will need to add a few extra
environment variables for acceptance tests - this is documented in our
[acceptance tests readme](/acceptance).
## Tests
When working on a new or existing feature, testing will be the backbone of your
work since it helps uncover and prevent regressions in the codebase. There are
two types of test we use in Gophercloud: unit tests and acceptance tests, which
are both described below.
### Unit tests
Unit tests are the fine-grained tests that establish and ensure the behavior
of individual units of functionality. We usually test on an
operation-by-operation basis (an operation typically being an API action) with
the use of mocking to set up explicit expectations. Each operation will set up
its HTTP response expectation, and then test how the system responds when fed
this controlled, pre-determined input.
To make life easier, we've introduced a bunch of test helpers to simplify the
process of testing expectations with assertions:
```go
import (
"testing"
"github.com/gophercloud/gophercloud/testhelper"
)
func TestSomething(t *testing.T) {
result, err := Operation()
testhelper.AssertEquals(t, "foo", result.Bar)
testhelper.AssertNoErr(t, err)
}
func TestSomethingElse(t *testing.T) {
testhelper.CheckEquals(t, "expected", "actual")
}
```
`AssertEquals` and `AssertNoErr` will throw a fatal error if a value does not
match an expected value or if an error has been declared, respectively. You can
also use `CheckEquals` and `CheckNoErr` for the same purpose; the only difference
being that `t.Errorf` is raised rather than `t.Fatalf`.
Here is a truncated example of mocked HTTP responses:
```go
import (
"testing"
th "github.com/gophercloud/gophercloud/testhelper"
fake "github.com/gophercloud/gophercloud/testhelper/client"
"github.com/gophercloud/gophercloud/openstack/networking/v2/networks"
)
func TestGet(t *testing.T) {
// Setup the HTTP request multiplexer and server
th.SetupHTTP()
defer th.TeardownHTTP()
th.Mux.HandleFunc("/networks/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) {
// Test we're using the correct HTTP method
th.TestMethod(t, r, "GET")
// Test we're setting the auth token
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
// Set the appropriate headers for our mocked response
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
// Set the HTTP body
fmt.Fprintf(w, `
{
"network": {
"status": "ACTIVE",
"name": "private-network",
"admin_state_up": true,
"tenant_id": "4fd44f30292945e481c7b8a0c8908869",
"shared": true,
"id": "d32019d3-bc6e-4319-9c1d-6722fc136a22"
}
}
`)
})
// Call our API operation
network, err := networks.Get(fake.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract()
// Assert no errors and equality
th.AssertNoErr(t, err)
th.AssertEquals(t, n.Status, "ACTIVE")
}
```
### Acceptance tests
As we've already mentioned, unit tests have a very narrow and confined focus -
they test small units of behavior. Acceptance tests on the other hand have a
far larger scope: they are fully functional tests that test the entire API of a
service in one fell swoop. They don't care about unit isolation or mocking
expectations, they instead do a full run-through and consequently test how the
entire system _integrates_ together. When an API satisfies expectations, it
proves by default that the requirements for a contract have been met.
Please be aware that acceptance tests will hit a live API - and may incur
service charges from your provider. Although most tests handle their own
teardown procedures, it is always worth manually checking that resources are
deleted after the test suite finishes.
### Running tests
To run all tests:
```bash
go test -tags fixtures ./...
```
To run all tests with verbose output:
```bash
go test -v -tags fixtures ./...
```
To run tests that match certain [build tags]():
```bash
go test -tags "fixtures foo bar" ./...
```
To run tests for a particular sub-package:
```bash
cd ./path/to/package && go test -tags fixtures .
```
## Style guide
See [here](/STYLEGUIDE.md)
## 3 ways to get involved
There are five main ways you can get involved in our open-source project, and
each is described briefly below. Once you've made up your mind and decided on
your fix, you will need to follow the same basic steps that all submissions are
required to adhere to:
1. [fork](https://help.github.com/articles/fork-a-repo/) the `gophercloud/gophercloud` repository
2. checkout a [new branch](https://github.com/Kunena/Kunena-Forum/wiki/Create-a-new-branch-with-git-and-manage-branches)
3. submit your branch as a [pull request](https://help.github.com/articles/creating-a-pull-request/)
### 1. Fixing bugs
If you want to start fixing open bugs, we'd really appreciate that! Bug fixing
is central to any project. The best way to get started is by heading to our
[bug tracker](https://github.com/gophercloud/gophercloud/issues) and finding open
bugs that you think nobody is working on. It might be useful to comment on the
thread to see the current state of the issue and if anybody has made any
breakthroughs on it so far.
### 2. Improving documentation
The best source of documentation is on [godoc.org](http://godoc.org). It is
automatically generated from the source code.
If you feel that a certain section could be improved - whether it's to clarify
ambiguity, correct a technical mistake, or to fix a grammatical error - please
feel entitled to do so! We welcome doc pull requests with the same childlike
enthusiasm as any other contribution!
###3. Working on a new feature
If you've found something we've left out, definitely feel free to start work on
introducing that feature. It's always useful to open an issue or submit a pull
request early on to indicate your intent to a core contributor - this enables
quick/early feedback and can help steer you in the right direction by avoiding
known issues. It might also help you avoid losing time implementing something
that might not ever work. One tip is to prefix your Pull Request issue title
with [wip] - then people know it's a work in progress.
You must ensure that all of your work is well tested - both in terms of unit
and acceptance tests. Untested code will not be merged because it introduces
too much of a risk to end-users.
Happy hacking!

View File

@ -131,7 +131,7 @@ None. Vendor it and write tests covering the parts you use.
## Contributing ## Contributing
See the [contributing guide](./CONTRIBUTING.md). See the [contributing guide](./.github/CONTRIBUTING.md).
## Help and feedback ## Help and feedback

View File

@ -1,12 +1,15 @@
package volumes package volumes
import ( import (
"encoding/json"
"time"
"github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination" "github.com/gophercloud/gophercloud/pagination"
) )
type Attachment struct { type Attachment struct {
AttachedAt gophercloud.JSONRFC3339MilliNoZ `json:"attached_at"` AttachedAt time.Time `json:"-"`
AttachmentID string `json:"attachment_id"` AttachmentID string `json:"attachment_id"`
Device string `json:"device"` Device string `json:"device"`
HostName string `json:"host_name"` HostName string `json:"host_name"`
@ -15,6 +18,23 @@ type Attachment struct {
VolumeID string `json:"volume_id"` VolumeID string `json:"volume_id"`
} }
func (r *Attachment) UnmarshalJSON(b []byte) error {
type tmp Attachment
var s struct {
tmp
AttachedAt gophercloud.JSONRFC3339MilliNoZ `json:"attached_at"`
}
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
*r = Attachment(s.tmp)
r.AttachedAt = time.Time(s.AttachedAt)
return err
}
// Volume contains all the information associated with an OpenStack Volume. // Volume contains all the information associated with an OpenStack Volume.
type Volume struct { type Volume struct {
// Unique identifier for the volume. // Unique identifier for the volume.
@ -26,9 +46,9 @@ type Volume struct {
// AvailabilityZone is which availability zone the volume is in. // AvailabilityZone is which availability zone the volume is in.
AvailabilityZone string `json:"availability_zone"` AvailabilityZone string `json:"availability_zone"`
// The date when this volume was created. // The date when this volume was created.
CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"` CreatedAt time.Time `json:"-"`
// The date when this volume was last updated // The date when this volume was last updated
UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"` UpdatedAt time.Time `json:"-"`
// Instances onto which the volume is attached. // Instances onto which the volume is attached.
Attachments []Attachment `json:"attachments"` Attachments []Attachment `json:"attachments"`
// Human-readable display name for the volume. // Human-readable display name for the volume.
@ -57,15 +77,24 @@ type Volume struct {
Multiattach bool `json:"multiattach"` Multiattach bool `json:"multiattach"`
} }
/* func (r *Volume) UnmarshalJSON(b []byte) error {
THESE BELONG IN EXTENSIONS: type tmp Volume
// ReplicationDriverData contains data about the replication driver. var s struct {
ReplicationDriverData string `json:"os-volume-replication:driver_data"` tmp
// ReplicationExtendedStatus contains extended status about replication. CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"`
ReplicationExtendedStatus string `json:"os-volume-replication:extended_status"` UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"`
// TenantID is the id of the project that owns the volume. }
TenantID string `json:"os-vol-tenant-attr:tenant_id"` err := json.Unmarshal(b, &s)
*/ if err != nil {
return err
}
*r = Volume(s.tmp)
r.CreatedAt = time.Time(s.CreatedAt)
r.UpdatedAt = time.Time(s.UpdatedAt)
return err
}
// VolumePage is a pagination.pager that is returned from a call to the List function. // VolumePage is a pagination.pager that is returned from a call to the List function.
type VolumePage struct { type VolumePage struct {
@ -80,11 +109,9 @@ func (r VolumePage) IsEmpty() (bool, error) {
// ExtractVolumes extracts and returns Volumes. It is used while iterating over a volumes.List call. // ExtractVolumes extracts and returns Volumes. It is used while iterating over a volumes.List call.
func ExtractVolumes(r pagination.Page) ([]Volume, error) { func ExtractVolumes(r pagination.Page) ([]Volume, error) {
var s struct { var s []Volume
Volumes []Volume `json:"volumes"` err := ExtractVolumesInto(r, &s)
} return s, err
err := (r.(VolumePage)).ExtractInto(&s)
return s.Volumes, err
} }
type commonResult struct { type commonResult struct {
@ -93,11 +120,17 @@ type commonResult struct {
// Extract will get the Volume object out of the commonResult object. // Extract will get the Volume object out of the commonResult object.
func (r commonResult) Extract() (*Volume, error) { func (r commonResult) Extract() (*Volume, error) {
var s struct { var s Volume
Volume *Volume `json:"volume"`
}
err := r.ExtractInto(&s) err := r.ExtractInto(&s)
return s.Volume, err return &s, err
}
func (r commonResult) ExtractInto(v interface{}) error {
return r.Result.ExtractIntoStructPtr(v, "volume")
}
func ExtractVolumesInto(r pagination.Page, v interface{}) error {
return r.(VolumePage).Result.ExtractIntoSlicePtr(v, "volumes")
} }
// CreateResult contains the response body and error from a Create request. // CreateResult contains the response body and error from a Create request.

View File

@ -309,3 +309,15 @@ func NewDBV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*
} }
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
} }
// 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
}

View File

@ -1,6 +1,9 @@
package secgroups package secgroups
import ( import (
"encoding/json"
"strconv"
"github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination" "github.com/gophercloud/gophercloud/pagination"
) )
@ -10,28 +13,51 @@ type SecurityGroup struct {
// The unique ID of the group. If Neutron is installed, this ID will be // The unique ID of the group. If Neutron is installed, this ID will be
// represented as a string UUID; if Neutron is not installed, it will be a // represented as a string UUID; if Neutron is not installed, it will be a
// numeric ID. For the sake of consistency, we always cast it to a string. // numeric ID. For the sake of consistency, we always cast it to a string.
ID string ID string `json:"-"`
// The human-readable name of the group, which needs to be unique. // The human-readable name of the group, which needs to be unique.
Name string Name string `json:"name"`
// The human-readable description of the group. // The human-readable description of the group.
Description string Description string `json:"description"`
// The rules which determine how this security group operates. // The rules which determine how this security group operates.
Rules []Rule Rules []Rule `json:"rules"`
// The ID of the tenant to which this security group belongs. // The ID of the tenant to which this security group belongs.
TenantID string `json:"tenant_id"` TenantID string `json:"tenant_id"`
} }
func (r *SecurityGroup) UnmarshalJSON(b []byte) error {
type tmp SecurityGroup
var s struct {
tmp
ID interface{} `json:"id"`
}
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
*r = SecurityGroup(s.tmp)
switch t := s.ID.(type) {
case float64:
r.ID = strconv.FormatFloat(t, 'f', -1, 64)
case string:
r.ID = t
}
return err
}
// Rule represents a security group rule, a policy which determines how a // Rule represents a security group rule, a policy which determines how a
// security group operates and what inbound traffic it allows in. // security group operates and what inbound traffic it allows in.
type Rule struct { type Rule struct {
// The unique ID. If Neutron is installed, this ID will be // The unique ID. If Neutron is installed, this ID will be
// represented as a string UUID; if Neutron is not installed, it will be a // represented as a string UUID; if Neutron is not installed, it will be a
// numeric ID. For the sake of consistency, we always cast it to a string. // numeric ID. For the sake of consistency, we always cast it to a string.
ID string ID string `json:"-"`
// The lower bound of the port range which this security group should open up // The lower bound of the port range which this security group should open up
FromPort int `json:"from_port"` FromPort int `json:"from_port"`
@ -52,6 +78,37 @@ type Rule struct {
Group Group Group Group
} }
func (r *Rule) UnmarshalJSON(b []byte) error {
type tmp Rule
var s struct {
tmp
ID interface{} `json:"id"`
ParentGroupID interface{} `json:"parent_group_id"`
}
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
*r = Rule(s.tmp)
switch t := s.ID.(type) {
case float64:
r.ID = strconv.FormatFloat(t, 'f', -1, 64)
case string:
r.ID = t
}
switch t := s.ParentGroupID.(type) {
case float64:
r.ParentGroupID = strconv.FormatFloat(t, 'f', -1, 64)
case string:
r.ParentGroupID = t
}
return err
}
// IPRange represents the IP range whose traffic will be accepted by the // IPRange represents the IP range whose traffic will be accepted by the
// security group. // security group.
type IPRange struct { type IPRange struct {

View File

@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"net/url" "net/url"
"path" "path"
"time"
"github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination" "github.com/gophercloud/gophercloud/pagination"
@ -101,12 +102,12 @@ func decryptPassword(encryptedPassword string, privateKey *rsa.PrivateKey) (stri
} }
// ExtractImageID gets the ID of the newly created server image from the header // ExtractImageID gets the ID of the newly created server image from the header
func (res CreateImageResult) ExtractImageID() (string, error) { func (r CreateImageResult) ExtractImageID() (string, error) {
if res.Err != nil { if r.Err != nil {
return "", res.Err return "", r.Err
} }
// Get the image id from the header // 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 { if err != nil {
return "", err return "", err
} }
@ -137,26 +138,27 @@ type Server struct {
// Name contains the human-readable name for the server. // Name contains the human-readable name for the server.
Name string `json:"name"` 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 and Created contain ISO-8601 timestamps of when the state of the server last changed, and when it was created.
Updated string Updated time.Time `json:"updated"`
Created string Created time.Time `json:"created"`
HostID string HostID string `json:"hostid"`
// Status contains the current operational status of the server, such as IN_PROGRESS or ACTIVE. // 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. // Progress ranges from 0..100.
// A request made against the server completes only once Progress reaches 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 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 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 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 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 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 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 indicates which public key was injected into the server on launch.
KeyName string `json:"key_name"` 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. // 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 +168,30 @@ type Server struct {
SecurityGroups []map[string]interface{} `json:"security_groups"` SecurityGroups []map[string]interface{} `json:"security_groups"`
} }
func (s *Server) UnmarshalJSON(b []byte) error { func (r *Server) UnmarshalJSON(b []byte) error {
type tmp Server type tmp Server
var server *struct { var s struct {
tmp tmp
Image interface{} Image interface{} `json:"image"`
} }
err := json.Unmarshal(b, &server) err := json.Unmarshal(b, &s)
if err != nil { if err != nil {
return err return err
} }
*s = Server(server.tmp) *r = Server(s.tmp)
switch t := server.Image.(type) { switch t := s.Image.(type) {
case map[string]interface{}: case map[string]interface{}:
s.Image = t r.Image = t
case string: case string:
switch t { switch t {
case "": case "":
s.Image = nil r.Image = nil
} }
} }
return nil return err
} }
// ServerPage abstracts the raw results of making a List() request against the API. // ServerPage abstracts the raw results of making a List() request against the API.
@ -200,17 +202,17 @@ type ServerPage struct {
} }
// IsEmpty returns true if a page contains no Server results. // IsEmpty returns true if a page contains no Server results.
func (page ServerPage) IsEmpty() (bool, error) { func (r ServerPage) IsEmpty() (bool, error) {
servers, err := ExtractServers(page) s, err := ExtractServers(r)
return len(servers) == 0, err return len(s) == 0, err
} }
// NextPageURL uses the response's embedded link reference to navigate to the next page of results. // 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 { var s struct {
Links []gophercloud.Link `json:"servers_links"` Links []gophercloud.Link `json:"servers_links"`
} }
err := page.ExtractInto(&s) err := r.ExtractInto(&s)
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -132,11 +132,6 @@ func (r CreateResult) ExtractServiceCatalog() (*ServiceCatalog, error) {
return &ServiceCatalog{Entries: s.Access.Entries}, err 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. // ExtractUser returns the User from a GetResult.
func (r GetResult) ExtractUser() (*User, error) { func (r GetResult) ExtractUser() (*User, error) {
var s struct { var s struct {

View File

@ -1,7 +1,10 @@
package tokens package tokens
import "errors" import (
import "github.com/gophercloud/gophercloud" "time"
"github.com/gophercloud/gophercloud"
)
// Endpoint represents a single API endpoint offered by a service. // Endpoint represents a single API endpoint offered by a service.
// It matches either a public, internal or admin URL. // It matches either a public, internal or admin URL.
@ -35,7 +38,7 @@ type CatalogEntry struct {
// ServiceCatalog provides a view into the service catalog from a previous, successful authentication. // ServiceCatalog provides a view into the service catalog from a previous, successful authentication.
type ServiceCatalog struct { type ServiceCatalog struct {
Entries []CatalogEntry Entries []CatalogEntry `json:"catalog"`
} }
// commonResult is the deferred result of a Create or a Get call. // commonResult is the deferred result of a Create or a Get call.
@ -51,34 +54,23 @@ func (r commonResult) Extract() (*Token, error) {
// ExtractToken interprets a commonResult as a Token. // ExtractToken interprets a commonResult as a Token.
func (r commonResult) ExtractToken() (*Token, error) { func (r commonResult) ExtractToken() (*Token, error) {
var s struct { var s Token
Token *Token `json:"token"`
}
err := r.ExtractInto(&s) err := r.ExtractInto(&s)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if s.Token == nil {
return nil, errors.New("'token' missing in JSON response")
}
// Parse the token itself from the stored headers. // 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. // ExtractServiceCatalog returns the ServiceCatalog that was generated along with the user's Token.
func (r CreateResult) ExtractServiceCatalog() (*ServiceCatalog, error) { func (r CreateResult) ExtractServiceCatalog() (*ServiceCatalog, error) {
var s struct { var s ServiceCatalog
Token struct {
Entries []CatalogEntry `json:"catalog"`
} `json:"token"`
}
err := r.ExtractInto(&s) err := r.ExtractInto(&s)
return &ServiceCatalog{Entries: s.Token.Entries}, err return &s, err
} }
// CreateResult defers the interpretation of a created token. // CreateResult defers the interpretation of a created token.
@ -87,13 +79,6 @@ type CreateResult struct {
commonResult 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. // GetResult is the deferred response from a Get call.
type GetResult struct { type GetResult struct {
commonResult commonResult
@ -110,5 +95,9 @@ type Token struct {
// ID is the issued token. // ID is the issued token.
ID string `json:"id"` ID string `json:"id"`
// ExpiresAt is the timestamp at which this token will no longer be accepted. // 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")
} }

View File

@ -2,9 +2,9 @@ package accounts
import ( import (
"encoding/json" "encoding/json"
"fmt"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud"
) )
@ -19,41 +19,44 @@ type UpdateHeader struct {
ContentLength int64 `json:"-"` ContentLength int64 `json:"-"`
ContentType string `json:"Content-Type"` ContentType string `json:"Content-Type"`
TransID string `json:"X-Trans-Id"` TransID string `json:"X-Trans-Id"`
Date gophercloud.JSONRFC1123 `json:"Date"` Date time.Time `json:"-"`
} }
func (h *UpdateHeader) UnmarshalJSON(b []byte) error { func (r *UpdateHeader) UnmarshalJSON(b []byte) error {
type tmp UpdateHeader type tmp UpdateHeader
var updateHeader *struct { var s struct {
tmp tmp
ContentLength string `json:"Content-Length"` ContentLength string `json:"Content-Length"`
Date gophercloud.JSONRFC1123 `json:"Date"`
} }
err := json.Unmarshal(b, &updateHeader) err := json.Unmarshal(b, &s)
if err != nil { if err != nil {
return err return err
} }
*h = UpdateHeader(updateHeader.tmp) *r = UpdateHeader(s.tmp)
switch updateHeader.ContentLength { switch s.ContentLength {
case "": case "":
h.ContentLength = 0 r.ContentLength = 0
default: default:
h.ContentLength, err = strconv.ParseInt(updateHeader.ContentLength, 10, 64) r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64)
if err != nil { if err != nil {
return err return err
} }
} }
return nil r.Date = time.Time(s.Date)
return err
} }
// Extract will return a struct of headers returned from a call to Get. To obtain // Extract will return a struct of headers returned from a call to Get. To obtain
// a map of headers, call the ExtractHeader method on the GetResult. // a map of headers, call the ExtractHeader method on the GetResult.
func (ur UpdateResult) Extract() (*UpdateHeader, error) { func (r UpdateResult) Extract() (*UpdateHeader, error) {
var uh *UpdateHeader var s *UpdateHeader
err := ur.ExtractInto(&uh) err := r.ExtractInto(&s)
return uh, err return s, err
} }
// GetHeader represents the headers returned in the response from a Get request. // GetHeader represents the headers returned in the response from a Get request.
@ -66,67 +69,71 @@ type GetHeader struct {
TransID string `json:"X-Trans-Id"` TransID string `json:"X-Trans-Id"`
TempURLKey string `json:"X-Account-Meta-Temp-URL-Key"` TempURLKey string `json:"X-Account-Meta-Temp-URL-Key"`
TempURLKey2 string `json:"X-Account-Meta-Temp-URL-Key-2"` TempURLKey2 string `json:"X-Account-Meta-Temp-URL-Key-2"`
Date gophercloud.JSONRFC1123 `json:"Date"` Date time.Time `json:"-"`
} }
func (h *GetHeader) UnmarshalJSON(b []byte) error { func (r *GetHeader) UnmarshalJSON(b []byte) error {
type tmp GetHeader type tmp GetHeader
var getHeader *struct { var s struct {
tmp tmp
BytesUsed string `json:"X-Account-Bytes-Used"` BytesUsed string `json:"X-Account-Bytes-Used"`
ContentLength string `json:"Content-Length"` ContentLength string `json:"Content-Length"`
ContainerCount string `json:"X-Account-Container-Count"` ContainerCount string `json:"X-Account-Container-Count"`
ObjectCount string `json:"X-Account-Object-Count"` ObjectCount string `json:"X-Account-Object-Count"`
Date string `json:"Date"`
} }
err := json.Unmarshal(b, &getHeader) err := json.Unmarshal(b, &s)
if err != nil { if err != nil {
return err return err
} }
*h = GetHeader(getHeader.tmp) *r = GetHeader(s.tmp)
switch getHeader.BytesUsed { switch s.BytesUsed {
case "": case "":
h.BytesUsed = 0 r.BytesUsed = 0
default: default:
h.BytesUsed, err = strconv.ParseInt(getHeader.BytesUsed, 10, 64) r.BytesUsed, err = strconv.ParseInt(s.BytesUsed, 10, 64)
if err != nil { if err != nil {
return err return err
} }
} }
fmt.Println("getHeader: ", getHeader.ContentLength) switch s.ContentLength {
switch getHeader.ContentLength {
case "": case "":
h.ContentLength = 0 r.ContentLength = 0
default: default:
h.ContentLength, err = strconv.ParseInt(getHeader.ContentLength, 10, 64) r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64)
if err != nil { if err != nil {
return err return err
} }
} }
switch getHeader.ObjectCount { switch s.ObjectCount {
case "": case "":
h.ObjectCount = 0 r.ObjectCount = 0
default: default:
h.ObjectCount, err = strconv.ParseInt(getHeader.ObjectCount, 10, 64) r.ObjectCount, err = strconv.ParseInt(s.ObjectCount, 10, 64)
if err != nil { if err != nil {
return err return err
} }
} }
switch getHeader.ContainerCount { switch s.ContainerCount {
case "": case "":
h.ContainerCount = 0 r.ContainerCount = 0
default: default:
h.ContainerCount, err = strconv.ParseInt(getHeader.ContainerCount, 10, 64) r.ContainerCount, err = strconv.ParseInt(s.ContainerCount, 10, 64)
if err != nil { if err != nil {
return err return err
} }
} }
return nil if s.Date != "" {
r.Date, err = time.Parse(time.RFC1123, s.Date)
}
return err
} }
// GetResult is returned from a call to the Get function. // GetResult is returned from a call to the Get function.
@ -142,7 +149,7 @@ func (r GetResult) Extract() (*GetHeader, error) {
return s, err return s, err
} }
// ExtractMetadata is a function that takes a GetResult (of type *http.Response) // ExtractMetadata is a function that takes a GetResult (of type *htts.Response)
// and returns the custom metatdata associated with the account. // and returns the custom metatdata associated with the account.
func (r GetResult) ExtractMetadata() (map[string]string, error) { func (r GetResult) ExtractMetadata() (map[string]string, error) {
if r.Err != nil { if r.Err != nil {

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination" "github.com/gophercloud/gophercloud/pagination"
@ -92,7 +93,7 @@ type GetHeader struct {
BytesUsed int64 `json:"-"` BytesUsed int64 `json:"-"`
ContentLength int64 `json:"-"` ContentLength int64 `json:"-"`
ContentType string `json:"Content-Type"` ContentType string `json:"Content-Type"`
Date gophercloud.JSONRFC1123 `json:"Date"` Date time.Time `json:"-"`
ObjectCount int64 `json:"-"` ObjectCount int64 `json:"-"`
Read []string `json:"-"` Read []string `json:"-"`
TransID string `json:"X-Trans-Id"` TransID string `json:"X-Trans-Id"`
@ -100,57 +101,60 @@ type GetHeader struct {
Write []string `json:"-"` Write []string `json:"-"`
} }
func (h *GetHeader) UnmarshalJSON(b []byte) error { func (r *GetHeader) UnmarshalJSON(b []byte) error {
type tmp GetHeader type tmp GetHeader
var getHeader *struct { var s struct {
tmp tmp
BytesUsed string `json:"X-Container-Bytes-Used"` BytesUsed string `json:"X-Container-Bytes-Used"`
ContentLength string `json:"Content-Length"` ContentLength string `json:"Content-Length"`
ObjectCount string `json:"X-Container-Object-Count"` ObjectCount string `json:"X-Container-Object-Count"`
Write string `json:"X-Container-Write"` Write string `json:"X-Container-Write"`
Read string `json:"X-Container-Read"` Read string `json:"X-Container-Read"`
Date gophercloud.JSONRFC1123 `json:"Date"`
} }
err := json.Unmarshal(b, &getHeader) err := json.Unmarshal(b, &s)
if err != nil { if err != nil {
return err return err
} }
*h = GetHeader(getHeader.tmp) *r = GetHeader(s.tmp)
switch getHeader.BytesUsed { switch s.BytesUsed {
case "": case "":
h.BytesUsed = 0 r.BytesUsed = 0
default: default:
h.BytesUsed, err = strconv.ParseInt(getHeader.BytesUsed, 10, 64) r.BytesUsed, err = strconv.ParseInt(s.BytesUsed, 10, 64)
if err != nil { if err != nil {
return err return err
} }
} }
switch getHeader.ContentLength { switch s.ContentLength {
case "": case "":
h.ContentLength = 0 r.ContentLength = 0
default: default:
h.ContentLength, err = strconv.ParseInt(getHeader.ContentLength, 10, 64) r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64)
if err != nil { if err != nil {
return err return err
} }
} }
switch getHeader.ObjectCount { switch s.ObjectCount {
case "": case "":
h.ObjectCount = 0 r.ObjectCount = 0
default: default:
h.ObjectCount, err = strconv.ParseInt(getHeader.ObjectCount, 10, 64) r.ObjectCount, err = strconv.ParseInt(s.ObjectCount, 10, 64)
if err != nil { if err != nil {
return err return err
} }
} }
h.Read = strings.Split(getHeader.Read, ",") r.Read = strings.Split(s.Read, ",")
h.Write = strings.Split(getHeader.Write, ",") r.Write = strings.Split(s.Write, ",")
return nil r.Date = time.Time(s.Date)
return err
} }
// GetResult represents the result of a get operation. // GetResult represents the result of a get operation.
@ -166,7 +170,7 @@ func (r GetResult) Extract() (*GetHeader, error) {
return s, err return s, err
} }
// ExtractMetadata is a function that takes a GetResult (of type *http.Response) // ExtractMetadata is a function that takes a GetResult (of type *stts.Response)
// and returns the custom metadata associated with the container. // and returns the custom metadata associated with the container.
func (r GetResult) ExtractMetadata() (map[string]string, error) { func (r GetResult) ExtractMetadata() (map[string]string, error) {
if r.Err != nil { if r.Err != nil {
@ -186,34 +190,37 @@ func (r GetResult) ExtractMetadata() (map[string]string, error) {
type CreateHeader struct { type CreateHeader struct {
ContentLength int64 `json:"-"` ContentLength int64 `json:"-"`
ContentType string `json:"Content-Type"` ContentType string `json:"Content-Type"`
Date gophercloud.JSONRFC1123 `json:"Date"` Date time.Time `json:"-"`
TransID string `json:"X-Trans-Id"` TransID string `json:"X-Trans-Id"`
} }
func (h *CreateHeader) UnmarshalJSON(b []byte) error { func (r *CreateHeader) UnmarshalJSON(b []byte) error {
type tmp CreateHeader type tmp CreateHeader
var header *struct { var s struct {
tmp tmp
ContentLength string `json:"Content-Length"` ContentLength string `json:"Content-Length"`
Date gophercloud.JSONRFC1123 `json:"Date"`
} }
err := json.Unmarshal(b, &header) err := json.Unmarshal(b, &s)
if err != nil { if err != nil {
return err return err
} }
*h = CreateHeader(header.tmp) *r = CreateHeader(s.tmp)
switch header.ContentLength { switch s.ContentLength {
case "": case "":
h.ContentLength = 0 r.ContentLength = 0
default: default:
h.ContentLength, err = strconv.ParseInt(header.ContentLength, 10, 64) r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64)
if err != nil { if err != nil {
return err return err
} }
} }
return nil r.Date = time.Time(s.Date)
return err
} }
// CreateResult represents the result of a create operation. To extract the // CreateResult represents the result of a create operation. To extract the
@ -235,34 +242,37 @@ func (r CreateResult) Extract() (*CreateHeader, error) {
type UpdateHeader struct { type UpdateHeader struct {
ContentLength int64 `json:"-"` ContentLength int64 `json:"-"`
ContentType string `json:"Content-Type"` ContentType string `json:"Content-Type"`
Date gophercloud.JSONRFC1123 `json:"Date"` Date time.Time `json:"-"`
TransID string `json:"X-Trans-Id"` TransID string `json:"X-Trans-Id"`
} }
func (h *UpdateHeader) UnmarshalJSON(b []byte) error { func (r *UpdateHeader) UnmarshalJSON(b []byte) error {
type tmp UpdateHeader type tmp UpdateHeader
var header *struct { var s struct {
tmp tmp
ContentLength string `json:"Content-Length"` ContentLength string `json:"Content-Length"`
Date gophercloud.JSONRFC1123 `json:"Date"`
} }
err := json.Unmarshal(b, &header) err := json.Unmarshal(b, &s)
if err != nil { if err != nil {
return err return err
} }
*h = UpdateHeader(header.tmp) *r = UpdateHeader(s.tmp)
switch header.ContentLength { switch s.ContentLength {
case "": case "":
h.ContentLength = 0 r.ContentLength = 0
default: default:
h.ContentLength, err = strconv.ParseInt(header.ContentLength, 10, 64) r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64)
if err != nil { if err != nil {
return err return err
} }
} }
return nil r.Date = time.Time(s.Date)
return err
} }
// UpdateResult represents the result of an update operation. To extract the // UpdateResult represents the result of an update operation. To extract the
@ -284,34 +294,37 @@ func (r UpdateResult) Extract() (*UpdateHeader, error) {
type DeleteHeader struct { type DeleteHeader struct {
ContentLength int64 `json:"-"` ContentLength int64 `json:"-"`
ContentType string `json:"Content-Type"` ContentType string `json:"Content-Type"`
Date gophercloud.JSONRFC1123 `json:"Date"` Date time.Time `json:"-"`
TransID string `json:"X-Trans-Id"` TransID string `json:"X-Trans-Id"`
} }
func (h *DeleteHeader) UnmarshalJSON(b []byte) error { func (r *DeleteHeader) UnmarshalJSON(b []byte) error {
type tmp DeleteHeader type tmp DeleteHeader
var header *struct { var s struct {
tmp tmp
ContentLength string `json:"Content-Length"` ContentLength string `json:"Content-Length"`
Date gophercloud.JSONRFC1123 `json:"Date"`
} }
err := json.Unmarshal(b, &header) err := json.Unmarshal(b, &s)
if err != nil { if err != nil {
return err return err
} }
*h = DeleteHeader(header.tmp) *r = DeleteHeader(s.tmp)
switch header.ContentLength { switch s.ContentLength {
case "": case "":
h.ContentLength = 0 r.ContentLength = 0
default: default:
h.ContentLength, err = strconv.ParseInt(header.ContentLength, 10, 64) r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64)
if err != nil { if err != nil {
return err return err
} }
} }
return nil r.Date = time.Time(s.Date)
return err
} }
// DeleteResult represents the result of a delete operation. To extract the // DeleteResult represents the result of a delete operation. To extract the

View File

@ -7,6 +7,7 @@ import (
"io/ioutil" "io/ioutil"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination" "github.com/gophercloud/gophercloud/pagination"
@ -23,15 +24,34 @@ type Object struct {
// Hash represents the MD5 checksum value of the object's content. // Hash represents the MD5 checksum value of the object's content.
Hash string `json:"hash"` Hash string `json:"hash"`
// LastModified is the RFC3339Milli time the object was last modified, represented // LastModified is the time the object was last modified, represented
// as a string. For any given object (obj), this value may be parsed to a time.Time: // as a string.
// lastModified, err := time.Parse(gophercloud.RFC3339Milli, obj.LastModified) LastModified time.Time `json:"-"`
LastModified gophercloud.JSONRFC3339MilliNoZ `json:"last_modified"`
// Name is the unique name for the object. // Name is the unique name for the object.
Name string `json:"name"` Name string `json:"name"`
} }
func (r *Object) UnmarshalJSON(b []byte) error {
type tmp Object
var s *struct {
tmp
LastModified gophercloud.JSONRFC3339MilliNoZ `json:"last_modified"`
}
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
*r = Object(s.tmp)
r.LastModified = time.Time(s.LastModified)
return nil
}
// ObjectPage is a single page of objects that is returned from a call to the // ObjectPage is a single page of objects that is returned from a call to the
// List function. // List function.
type ObjectPage struct { type ObjectPage struct {
@ -105,38 +125,45 @@ type DownloadHeader struct {
ContentEncoding string `json:"Content-Encoding"` ContentEncoding string `json:"Content-Encoding"`
ContentLength int64 `json:"-"` ContentLength int64 `json:"-"`
ContentType string `json:"Content-Type"` ContentType string `json:"Content-Type"`
Date gophercloud.JSONRFC1123 `json:"Date"` Date time.Time `json:"-"`
DeleteAt gophercloud.JSONUnix `json:"X-Delete-At"` DeleteAt time.Time `json:"-"`
ETag string `json:"Etag"` ETag string `json:"Etag"`
LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"` LastModified time.Time `json:"-"`
ObjectManifest string `json:"X-Object-Manifest"` ObjectManifest string `json:"X-Object-Manifest"`
StaticLargeObject bool `json:"X-Static-Large-Object"` StaticLargeObject bool `json:"X-Static-Large-Object"`
TransID string `json:"X-Trans-Id"` TransID string `json:"X-Trans-Id"`
} }
func (h *DownloadHeader) UnmarshalJSON(b []byte) error { func (r *DownloadHeader) UnmarshalJSON(b []byte) error {
type tmp DownloadHeader type tmp DownloadHeader
var hTmp *struct { var s struct {
tmp tmp
ContentLength string `json:"Content-Length"` ContentLength string `json:"Content-Length"`
Date gophercloud.JSONRFC1123 `json:"Date"`
DeleteAt gophercloud.JSONUnix `json:"X-Delete-At"`
LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"`
} }
err := json.Unmarshal(b, &hTmp) err := json.Unmarshal(b, &s)
if err != nil { if err != nil {
return err return err
} }
*h = DownloadHeader(hTmp.tmp) *r = DownloadHeader(s.tmp)
switch hTmp.ContentLength { switch s.ContentLength {
case "": case "":
h.ContentLength = 0 r.ContentLength = 0
default: default:
h.ContentLength, err = strconv.ParseInt(hTmp.ContentLength, 10, 64) r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64)
if err != nil { if err != nil {
return err return err
} }
} }
r.Date = time.Time(s.Date)
r.DeleteAt = time.Time(s.DeleteAt)
r.LastModified = time.Time(s.LastModified)
return nil return nil
} }
@ -176,40 +203,47 @@ func (r *DownloadResult) ExtractContent() ([]byte, error) {
type GetHeader struct { type GetHeader struct {
ContentDisposition string `json:"Content-Disposition"` ContentDisposition string `json:"Content-Disposition"`
ContentEncoding string `json:"Content-Encoding"` ContentEncoding string `json:"Content-Encoding"`
ContentLength int64 `json:"Content-Length"` ContentLength int64 `json:"-"`
ContentType string `json:"Content-Type"` ContentType string `json:"Content-Type"`
Date gophercloud.JSONRFC1123 `json:"Date"` Date time.Time `json:"-"`
DeleteAt gophercloud.JSONUnix `json:"X-Delete-At"` DeleteAt time.Time `json:"-"`
ETag string `json:"Etag"` ETag string `json:"Etag"`
LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"` LastModified time.Time `json:"-"`
ObjectManifest string `json:"X-Object-Manifest"` ObjectManifest string `json:"X-Object-Manifest"`
StaticLargeObject bool `json:"X-Static-Large-Object"` StaticLargeObject bool `json:"X-Static-Large-Object"`
TransID string `json:"X-Trans-Id"` TransID string `json:"X-Trans-Id"`
} }
func (h *GetHeader) UnmarshalJSON(b []byte) error { func (r *GetHeader) UnmarshalJSON(b []byte) error {
type tmp GetHeader type tmp GetHeader
var hTmp *struct { var s struct {
tmp tmp
ContentLength string `json:"Content-Length"` ContentLength string `json:"Content-Length"`
Date gophercloud.JSONRFC1123 `json:"Date"`
DeleteAt gophercloud.JSONUnix `json:"X-Delete-At"`
LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"`
} }
err := json.Unmarshal(b, &hTmp) err := json.Unmarshal(b, &s)
if err != nil { if err != nil {
return err return err
} }
*h = GetHeader(hTmp.tmp) *r = GetHeader(s.tmp)
switch hTmp.ContentLength { switch s.ContentLength {
case "": case "":
h.ContentLength = 0 r.ContentLength = 0
default: default:
h.ContentLength, err = strconv.ParseInt(hTmp.ContentLength, 10, 64) r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64)
if err != nil { if err != nil {
return err return err
} }
} }
r.Date = time.Time(s.Date)
r.DeleteAt = time.Time(s.DeleteAt)
r.LastModified = time.Time(s.LastModified)
return nil return nil
} }
@ -244,37 +278,42 @@ func (r GetResult) ExtractMetadata() (map[string]string, error) {
// CreateHeader represents the headers returned in the response from a Create request. // CreateHeader represents the headers returned in the response from a Create request.
type CreateHeader struct { type CreateHeader struct {
ContentLength int64 `json:"Content-Length"` ContentLength int64 `json:"-"`
ContentType string `json:"Content-Type"` ContentType string `json:"Content-Type"`
Date gophercloud.JSONRFC1123 `json:"Date"` Date time.Time `json:"-"`
ETag string `json:"Etag"` ETag string `json:"Etag"`
LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"` LastModified time.Time `json:"-"`
TransID string `json:"X-Trans-Id"` TransID string `json:"X-Trans-Id"`
} }
func (h *CreateHeader) UnmarshalJSON(b []byte) error { func (r *CreateHeader) UnmarshalJSON(b []byte) error {
type tmp CreateHeader type tmp CreateHeader
var hTmp *struct { var s struct {
tmp tmp
ContentLength string `json:"Content-Length"` ContentLength string `json:"Content-Length"`
Date gophercloud.JSONRFC1123 `json:"Date"`
LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"`
} }
err := json.Unmarshal(b, &hTmp) err := json.Unmarshal(b, &s)
if err != nil { if err != nil {
return err return err
} }
*h = CreateHeader(hTmp.tmp) *r = CreateHeader(s.tmp)
switch hTmp.ContentLength { switch s.ContentLength {
case "": case "":
h.ContentLength = 0 r.ContentLength = 0
default: default:
h.ContentLength, err = strconv.ParseInt(hTmp.ContentLength, 10, 64) r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64)
if err != nil { if err != nil {
return err return err
} }
} }
r.Date = time.Time(s.Date)
r.LastModified = time.Time(s.LastModified)
return nil return nil
} }
@ -297,35 +336,38 @@ func (r CreateResult) Extract() (*CreateHeader, error) {
// UpdateHeader represents the headers returned in the response from a Update request. // UpdateHeader represents the headers returned in the response from a Update request.
type UpdateHeader struct { type UpdateHeader struct {
ContentLength int64 `json:"Content-Length"` ContentLength int64 `json:"-"`
ContentType string `json:"Content-Type"` ContentType string `json:"Content-Type"`
Date gophercloud.JSONRFC1123 `json:"Date"` Date time.Time `json:"-"`
TransID string `json:"X-Trans-Id"` TransID string `json:"X-Trans-Id"`
} }
func (h *UpdateHeader) UnmarshalJSON(b []byte) error { func (r *UpdateHeader) UnmarshalJSON(b []byte) error {
type tmp UpdateHeader type tmp UpdateHeader
var hTmp *struct { var s struct {
tmp tmp
ContentLength string `json:"Content-Length"` ContentLength string `json:"Content-Length"`
Date gophercloud.JSONRFC1123 `json:"Date"`
} }
err := json.Unmarshal(b, &hTmp) err := json.Unmarshal(b, &s)
if err != nil { if err != nil {
return err return err
} }
*h = UpdateHeader(hTmp.tmp) *r = UpdateHeader(s.tmp)
switch hTmp.ContentLength { switch s.ContentLength {
case "": case "":
h.ContentLength = 0 r.ContentLength = 0
default: default:
h.ContentLength, err = strconv.ParseInt(hTmp.ContentLength, 10, 64) r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64)
if err != nil { if err != nil {
return err return err
} }
} }
r.Date = time.Time(s.Date)
return nil return nil
} }
@ -346,33 +388,36 @@ func (r UpdateResult) Extract() (*UpdateHeader, error) {
type DeleteHeader struct { type DeleteHeader struct {
ContentLength int64 `json:"Content-Length"` ContentLength int64 `json:"Content-Length"`
ContentType string `json:"Content-Type"` ContentType string `json:"Content-Type"`
Date gophercloud.JSONRFC1123 `json:"Date"` Date time.Time `json:"-"`
TransID string `json:"X-Trans-Id"` TransID string `json:"X-Trans-Id"`
} }
func (h *DeleteHeader) UnmarshalJSON(b []byte) error { func (r *DeleteHeader) UnmarshalJSON(b []byte) error {
type tmp DeleteHeader type tmp DeleteHeader
var hTmp *struct { var s struct {
tmp tmp
ContentLength string `json:"Content-Length"` ContentLength string `json:"Content-Length"`
Date gophercloud.JSONRFC1123 `json:"Date"`
} }
err := json.Unmarshal(b, &hTmp) err := json.Unmarshal(b, &s)
if err != nil { if err != nil {
return err return err
} }
*h = DeleteHeader(hTmp.tmp) *r = DeleteHeader(s.tmp)
switch hTmp.ContentLength { switch s.ContentLength {
case "": case "":
h.ContentLength = 0 r.ContentLength = 0
default: default:
h.ContentLength, err = strconv.ParseInt(hTmp.ContentLength, 10, 64) r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64)
if err != nil { if err != nil {
return err return err
} }
} }
r.Date = time.Time(s.Date)
return nil return nil
} }
@ -391,39 +436,46 @@ func (r DeleteResult) Extract() (*DeleteHeader, error) {
// CopyHeader represents the headers returned in the response from a Copy request. // CopyHeader represents the headers returned in the response from a Copy request.
type CopyHeader struct { type CopyHeader struct {
ContentLength int64 `json:"Content-Length"` ContentLength int64 `json:"-"`
ContentType string `json:"Content-Type"` ContentType string `json:"Content-Type"`
CopiedFrom string `json:"X-Copied-From"` CopiedFrom string `json:"X-Copied-From"`
CopiedFromLastModified gophercloud.JSONRFC1123 `json:"X-Copied-From-Last-Modified"` CopiedFromLastModified time.Time `json:"-"`
Date gophercloud.JSONRFC1123 `json:"Date"` Date time.Time `json:"-"`
ETag string `json:"Etag"` ETag string `json:"Etag"`
LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"` LastModified time.Time `json:"-"`
TransID string `json:"X-Trans-Id"` TransID string `json:"X-Trans-Id"`
} }
func (h *CopyHeader) UnmarshalJSON(b []byte) error { func (r *CopyHeader) UnmarshalJSON(b []byte) error {
type tmp CopyHeader type tmp CopyHeader
var hTmp *struct { var s struct {
tmp tmp
ContentLength string `json:"Content-Length"` ContentLength string `json:"Content-Length"`
CopiedFromLastModified gophercloud.JSONRFC1123 `json:"X-Copied-From-Last-Modified"`
Date gophercloud.JSONRFC1123 `json:"Date"`
LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"`
} }
err := json.Unmarshal(b, &hTmp) err := json.Unmarshal(b, &s)
if err != nil { if err != nil {
return err return err
} }
*h = CopyHeader(hTmp.tmp) *r = CopyHeader(s.tmp)
switch hTmp.ContentLength { switch s.ContentLength {
case "": case "":
h.ContentLength = 0 r.ContentLength = 0
default: default:
h.ContentLength, err = strconv.ParseInt(hTmp.ContentLength, 10, 64) r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64)
if err != nil { if err != nil {
return err return err
} }
} }
r.Date = time.Time(s.Date)
r.CopiedFromLastModified = time.Time(s.CopiedFromLastModified)
r.LastModified = time.Time(s.LastModified)
return nil return nil
} }

View File

@ -232,7 +232,7 @@ func isZero(v reflect.Value) bool {
if v.IsNil() { if v.IsNil() {
return true return true
} }
return isZero(v.Elem()) return false
case reflect.Func, reflect.Map, reflect.Slice: case reflect.Func, reflect.Map, reflect.Slice:
return v.IsNil() return v.IsNil()
case reflect.Array: case reflect.Array:
@ -307,7 +307,11 @@ func BuildQueryString(opts interface{}) (*url.URL, error) {
// if the field is set, add it to the slice of query pieces // if the field is set, add it to the slice of query pieces
if !isZero(v) { if !isZero(v) {
loop:
switch v.Kind() { switch v.Kind() {
case reflect.Ptr:
v = v.Elem()
goto loop
case reflect.String: case reflect.String:
params.Add(tags[0], v.String()) params.Add(tags[0], v.String())
case reflect.Int: case reflect.Int:

View File

@ -3,8 +3,10 @@ package gophercloud
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt"
"io" "io"
"net/http" "net/http"
"reflect"
"strconv" "strconv"
"time" "time"
) )
@ -60,6 +62,78 @@ func (r Result) ExtractInto(to interface{}) error {
return err return err
} }
func (r Result) extractIntoPtr(to interface{}, label string) error {
if label == "" {
return r.ExtractInto(&to)
}
var m map[string]interface{}
err := r.ExtractInto(&m)
if err != nil {
return err
}
b, err := json.Marshal(m[label])
if err != nil {
return err
}
err = json.Unmarshal(b, &to)
return err
}
// ExtractIntoStructPtr will unmarshal the Result (r) into the provided
// interface{} (to).
//
// NOTE: For internal use only
//
// `to` must be a pointer to an underlying struct type
//
// If provided, `label` will be filtered out of the response
// body prior to `r` being unmarshalled into `to`.
func (r Result) ExtractIntoStructPtr(to interface{}, label string) error {
if r.Err != nil {
return r.Err
}
t := reflect.TypeOf(to)
if k := t.Kind(); k != reflect.Ptr {
return fmt.Errorf("Expected pointer, got %v", k)
}
switch t.Elem().Kind() {
case reflect.Struct:
return r.extractIntoPtr(to, label)
default:
return fmt.Errorf("Expected pointer to struct, got: %v", t)
}
}
// ExtractIntoSlicePtr will unmarshal the Result (r) into the provided
// interface{} (to).
//
// NOTE: For internal use only
//
// `to` must be a pointer to an underlying slice type
//
// If provided, `label` will be filtered out of the response
// body prior to `r` being unmarshalled into `to`.
func (r Result) ExtractIntoSlicePtr(to interface{}, label string) error {
if r.Err != nil {
return r.Err
}
t := reflect.TypeOf(to)
if k := t.Kind(); k != reflect.Ptr {
return fmt.Errorf("Expected pointer, got %v", k)
}
switch t.Elem().Kind() {
case reflect.Slice:
return r.extractIntoPtr(to, label)
default:
return fmt.Errorf("Expected pointer to slice, got: %v", t)
}
}
// PrettyPrintJSON creates a string containing the full response body as // PrettyPrintJSON creates a string containing the full response body as
// pretty-printed JSON. It's useful for capturing test fixtures and for // pretty-printed JSON. It's useful for capturing test fixtures and for
// debugging extraction bugs. If you include its output in an issue related to // debugging extraction bugs. If you include its output in an issue related to

196
vendor/vendor.json vendored
View File

@ -1282,268 +1282,268 @@
"revision": "2a60fc2ba6c19de80291203597d752e9ba58e4c0" "revision": "2a60fc2ba6c19de80291203597d752e9ba58e4c0"
}, },
{ {
"checksumSHA1": "WJ/6nt4LiRCzFQcaxfc/iodURPM=", "checksumSHA1": "DkbpYqirk9i+2YDR5Ujzpot/oAg=",
"path": "github.com/gophercloud/gophercloud", "path": "github.com/gophercloud/gophercloud",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "KHRGRGaThzPw7fpZ+h6enJbSyDY=", "checksumSHA1": "S3zTth9INyj1RfyHkQEvJAvRWvw=",
"path": "github.com/gophercloud/gophercloud/openstack", "path": "github.com/gophercloud/gophercloud/openstack",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "XAKLUSwXSMGtbp+U874qU4MzT/A=", "checksumSHA1": "XAKLUSwXSMGtbp+U874qU4MzT/A=",
"path": "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions", "path": "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions",
"revision": "d5eda9707e146108e4d424062b602fd97a71c2e6", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-11-14T18:28:31Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "PFD8SEqhArAy/6jRbIlYb5lp64k=", "checksumSHA1": "PFD8SEqhArAy/6jRbIlYb5lp64k=",
"path": "github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes", "path": "github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "Ucc8dbvgihRt/2YqLhBkBYXF8v0=", "checksumSHA1": "B4IXSmq364HcBruvvV0QjDFxZgc=",
"path": "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes", "path": "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "w2wHF5eEBE89ZYlkS9GAJsSIq9U=", "checksumSHA1": "w2wHF5eEBE89ZYlkS9GAJsSIq9U=",
"path": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume", "path": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "pUlKsepGmWDd4PqPaK4W85pHsRU=", "checksumSHA1": "pUlKsepGmWDd4PqPaK4W85pHsRU=",
"path": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips", "path": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "RWwUliHD65cWApdEo4ckOcPSArg=", "checksumSHA1": "RWwUliHD65cWApdEo4ckOcPSArg=",
"path": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs", "path": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "tOmntqlmZ/r8aObUChNloddLhwk=", "checksumSHA1": "tOmntqlmZ/r8aObUChNloddLhwk=",
"path": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints", "path": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "IZQJSUx3hRGbhGrkm9Vtk1GP5XY=", "checksumSHA1": "jNrUTQf+9dYfaD7YqvKwC+kGvyY=",
"path": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups", "path": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "ci4gzd7Uy9JC4NcQ2ms19pjtW6s=", "checksumSHA1": "ci4gzd7Uy9JC4NcQ2ms19pjtW6s=",
"path": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups", "path": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "qBpGbX7LQMPATdO8XyQmU7IXDiI=", "checksumSHA1": "qBpGbX7LQMPATdO8XyQmU7IXDiI=",
"path": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop", "path": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "5JuziAp9BSRA/z+8pTjVLTWeTw4=", "checksumSHA1": "5JuziAp9BSRA/z+8pTjVLTWeTw4=",
"path": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/tenantnetworks", "path": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/tenantnetworks",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "2VNgU0F9PDax5VKClvMLmbzuksw=", "checksumSHA1": "2VNgU0F9PDax5VKClvMLmbzuksw=",
"path": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach", "path": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "a9xDFPigDjHlPlthknKlBduGvKY=", "checksumSHA1": "a9xDFPigDjHlPlthknKlBduGvKY=",
"path": "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors", "path": "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "UGeqrw3KdPNRwDxl315MAYyy/uY=", "checksumSHA1": "UGeqrw3KdPNRwDxl315MAYyy/uY=",
"path": "github.com/gophercloud/gophercloud/openstack/compute/v2/images", "path": "github.com/gophercloud/gophercloud/openstack/compute/v2/images",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "8rOLNDSqwz/DSKL1BoPqjtWSWAE=", "checksumSHA1": "efmzF4m/gZ8nKJ5B9RcdQfgCy/o=",
"path": "github.com/gophercloud/gophercloud/openstack/compute/v2/servers", "path": "github.com/gophercloud/gophercloud/openstack/compute/v2/servers",
"revision": "21a44e132331d6d46ab4187411c85ced6b1a9963", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-11-08T07:34:31Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "1sVqsZBZBNhDXLY9XzjMkcOkcbg=", "checksumSHA1": "1sVqsZBZBNhDXLY9XzjMkcOkcbg=",
"path": "github.com/gophercloud/gophercloud/openstack/identity/v2/tenants", "path": "github.com/gophercloud/gophercloud/openstack/identity/v2/tenants",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "q1VGeltZl57OidZ5UDxbMsnyV2g=", "checksumSHA1": "AvUU5En9YpG25iLlcAPDgcQODjI=",
"path": "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens", "path": "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "6M6ofb8ri5G+sZ8OiExLi7irdx8=", "checksumSHA1": "ZKyEbJuIlvuZ9aUushINCXJHF4w=",
"path": "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens", "path": "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "aTHxjMlfNXFJ3l2TZyvIwqt/3kM=", "checksumSHA1": "aTHxjMlfNXFJ3l2TZyvIwqt/3kM=",
"path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls", "path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "14ZhP0wE/WCL/6oujcML755AaH4=", "checksumSHA1": "14ZhP0wE/WCL/6oujcML755AaH4=",
"path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/policies", "path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/policies",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "sYET5A7WTyJ7dpuxR/VXYoReldw=", "checksumSHA1": "sYET5A7WTyJ7dpuxR/VXYoReldw=",
"path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/rules", "path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/rules",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "0UcU/7oQbhlnYKoT+I+T403U8MQ=", "checksumSHA1": "0UcU/7oQbhlnYKoT+I+T403U8MQ=",
"path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips", "path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "Mjt7GwFygyqPxygY8xZZnUasHmk=", "checksumSHA1": "Mjt7GwFygyqPxygY8xZZnUasHmk=",
"path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers", "path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "mCTz2rnyVfhjJ+AD/WihCNcYWiY=", "checksumSHA1": "mCTz2rnyVfhjJ+AD/WihCNcYWiY=",
"path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/members", "path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/members",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "B2mtHvADREtFLam72wyijyQh/Ds=", "checksumSHA1": "B2mtHvADREtFLam72wyijyQh/Ds=",
"path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/monitors", "path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/monitors",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "pTr22CKKJ26yvhgd0SRxFF4jkEs=", "checksumSHA1": "pTr22CKKJ26yvhgd0SRxFF4jkEs=",
"path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/pools", "path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/pools",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "E7/Z7g5O9o+ge+8YklheTpKgWNw=", "checksumSHA1": "E7/Z7g5O9o+ge+8YklheTpKgWNw=",
"path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/vips", "path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/vips",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "mhpwj5tPv7Uw5aUfC55fhLPBcKo=", "checksumSHA1": "mhpwj5tPv7Uw5aUfC55fhLPBcKo=",
"path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners", "path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "5efJz6UH7JCFeav5ZCCzicXCFTU=", "checksumSHA1": "5efJz6UH7JCFeav5ZCCzicXCFTU=",
"path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers", "path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "TVFgBTz7B6bb1R4TWdgAkbE1/fk=", "checksumSHA1": "TVFgBTz7B6bb1R4TWdgAkbE1/fk=",
"path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors", "path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "xirjw9vJIN6rmkT3T56bfPfOLUM=", "checksumSHA1": "xirjw9vJIN6rmkT3T56bfPfOLUM=",
"path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools", "path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "FKwSMrpQf7b3TcCOQfh+ovoBShA=", "checksumSHA1": "FKwSMrpQf7b3TcCOQfh+ovoBShA=",
"path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups", "path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "CsS/kI3VeLcSHzMKviFVDwqwgvk=", "checksumSHA1": "CsS/kI3VeLcSHzMKviFVDwqwgvk=",
"path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules", "path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "zKOhFTL5BDZPMC58ZzZkryjskno=", "checksumSHA1": "zKOhFTL5BDZPMC58ZzZkryjskno=",
"path": "github.com/gophercloud/gophercloud/openstack/networking/v2/networks", "path": "github.com/gophercloud/gophercloud/openstack/networking/v2/networks",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "BE+CO3QrEGpIgv3Ee2ANZp1WtSo=", "checksumSHA1": "BE+CO3QrEGpIgv3Ee2ANZp1WtSo=",
"path": "github.com/gophercloud/gophercloud/openstack/networking/v2/ports", "path": "github.com/gophercloud/gophercloud/openstack/networking/v2/ports",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "wY0MY7RpX0Z2Y0rMmrAuYS6cHYA=", "checksumSHA1": "wY0MY7RpX0Z2Y0rMmrAuYS6cHYA=",
"path": "github.com/gophercloud/gophercloud/openstack/networking/v2/subnets", "path": "github.com/gophercloud/gophercloud/openstack/networking/v2/subnets",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "yIerdfSMJRUnjh2EZikMhWcgwlY=", "checksumSHA1": "LtdQKIKKRKe6FOGdBvrBz/bg1Gc=",
"path": "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts", "path": "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts",
"revision": "45720eeefeeeba03b2d7da500297ec68eeee51af", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-31T15:28:56Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "5XMyCSYDLmv/b54K3HNNNCJdnBk=", "checksumSHA1": "1lwXcRrM5A7iCfekbn3bpfNLe3g=",
"path": "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers", "path": "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "CRiIA3oRRlKRDF2ZYnZT8rTlJBU=", "checksumSHA1": "dotTh+ZsNiyv8e9Z4e0chPEZDKE=",
"path": "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects", "path": "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects",
"revision": "45720eeefeeeba03b2d7da500297ec68eeee51af", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-31T15:28:56Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "roxPPVwS2CjJhf0CApHNQxAX7EA=", "checksumSHA1": "roxPPVwS2CjJhf0CApHNQxAX7EA=",
"path": "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/swauth", "path": "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/swauth",
"revision": "d5eda9707e146108e4d424062b602fd97a71c2e6", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-11-14T18:28:31Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "TDOZnaS0TO0NirpxV1QwPerAQTY=", "checksumSHA1": "TDOZnaS0TO0NirpxV1QwPerAQTY=",
"path": "github.com/gophercloud/gophercloud/openstack/utils", "path": "github.com/gophercloud/gophercloud/openstack/utils",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "pmpLcbUZ+EgLUmTbzMtGRq3haOU=", "checksumSHA1": "pmpLcbUZ+EgLUmTbzMtGRq3haOU=",
"path": "github.com/gophercloud/gophercloud/pagination", "path": "github.com/gophercloud/gophercloud/pagination",
"revision": "e3d6384a3714b335d075862e6eb0a681180643df", "revision": "368deee20062b2c7a043f792d7d998abe621872e",
"revisionTime": "2016-10-25T18:03:21Z" "revisionTime": "2017-01-12T21:19:23Z"
}, },
{ {
"checksumSHA1": "6tvhO5ieOvX9R6o0vtl19s0lr8E=", "checksumSHA1": "6tvhO5ieOvX9R6o0vtl19s0lr8E=",