Move deps from _workspace/ to vendor/

godep restore
pushd $GOPATH/src/github.com/appc/spec
git co master
popd
go get go4.org/errorutil
rm -rf Godeps
godep save ./...
git add vendor
git add -f $(git ls-files --other vendor/)
git co -- Godeps/LICENSES Godeps/.license_file_state Godeps/OWNERS
This commit is contained in:
Tim Hockin
2016-05-08 20:30:21 -07:00
parent 899f9b4e31
commit 3c0c5ed4e0
4400 changed files with 16739 additions and 376 deletions

View File

@@ -0,0 +1,8 @@
// Package accounts contains functionality for working with Object Storage
// account resources. An account is the top-level resource the object storage
// hierarchy: containers belong to accounts, objects belong to containers.
//
// Another way of thinking of an account is like a namespace for all your
// resources. It is synonymous with a project or tenant in other OpenStack
// services.
package accounts

View File

@@ -0,0 +1,38 @@
// +build fixtures
package accounts
import (
"net/http"
"testing"
th "github.com/rackspace/gophercloud/testhelper"
fake "github.com/rackspace/gophercloud/testhelper/client"
)
// HandleGetAccountSuccessfully creates an HTTP handler at `/` on the test handler mux that
// responds with a `Get` response.
func HandleGetAccountSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "HEAD")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.Header().Set("X-Account-Container-Count", "2")
w.Header().Set("X-Account-Bytes-Used", "14")
w.Header().Set("X-Account-Meta-Subject", "books")
w.WriteHeader(http.StatusNoContent)
})
}
// HandleUpdateAccountSuccessfully creates an HTTP handler at `/` on the test handler mux that
// responds with a `Update` response.
func HandleUpdateAccountSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "POST")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestHeader(t, r, "X-Account-Meta-Gophercloud-Test", "accounts")
w.WriteHeader(http.StatusNoContent)
})
}

View File

@@ -0,0 +1,107 @@
package accounts
import "github.com/rackspace/gophercloud"
// GetOptsBuilder allows extensions to add additional headers to the Get
// request.
type GetOptsBuilder interface {
ToAccountGetMap() (map[string]string, error)
}
// GetOpts is a structure that contains parameters for getting an account's
// metadata.
type GetOpts struct {
Newest bool `h:"X-Newest"`
}
// ToAccountGetMap formats a GetOpts into a map[string]string of headers.
func (opts GetOpts) ToAccountGetMap() (map[string]string, error) {
return gophercloud.BuildHeaders(opts)
}
// Get is a function that retrieves an account's metadata. To extract just the
// custom metadata, call the ExtractMetadata method on the GetResult. To extract
// all the headers that are returned (including the metadata), call the
// ExtractHeader method on the GetResult.
func Get(c *gophercloud.ServiceClient, opts GetOptsBuilder) GetResult {
var res GetResult
h := c.AuthenticatedHeaders()
if opts != nil {
headers, err := opts.ToAccountGetMap()
if err != nil {
res.Err = err
return res
}
for k, v := range headers {
h[k] = v
}
}
resp, err := c.Request("HEAD", getURL(c), gophercloud.RequestOpts{
MoreHeaders: h,
OkCodes: []int{204},
})
if resp != nil {
res.Header = resp.Header
}
res.Err = err
return res
}
// UpdateOptsBuilder allows extensions to add additional headers to the Update
// request.
type UpdateOptsBuilder interface {
ToAccountUpdateMap() (map[string]string, error)
}
// UpdateOpts is a structure that contains parameters for updating, creating, or
// deleting an account's metadata.
type UpdateOpts struct {
Metadata map[string]string
ContentType string `h:"Content-Type"`
DetectContentType bool `h:"X-Detect-Content-Type"`
TempURLKey string `h:"X-Account-Meta-Temp-URL-Key"`
TempURLKey2 string `h:"X-Account-Meta-Temp-URL-Key-2"`
}
// ToAccountUpdateMap formats an UpdateOpts into a map[string]string of headers.
func (opts UpdateOpts) ToAccountUpdateMap() (map[string]string, error) {
headers, err := gophercloud.BuildHeaders(opts)
if err != nil {
return nil, err
}
for k, v := range opts.Metadata {
headers["X-Account-Meta-"+k] = v
}
return headers, err
}
// Update is a function that creates, updates, or deletes an account's metadata.
// To extract the headers returned, call the Extract method on the UpdateResult.
func Update(c *gophercloud.ServiceClient, opts UpdateOptsBuilder) UpdateResult {
var res UpdateResult
h := make(map[string]string)
if opts != nil {
headers, err := opts.ToAccountUpdateMap()
if err != nil {
res.Err = err
return res
}
for k, v := range headers {
h[k] = v
}
}
resp, err := c.Request("POST", updateURL(c), gophercloud.RequestOpts{
MoreHeaders: h,
OkCodes: []int{201, 202, 204},
})
if resp != nil {
res.Header = resp.Header
}
res.Err = err
return res
}

View File

@@ -0,0 +1,102 @@
package accounts
import (
"strings"
"time"
"github.com/rackspace/gophercloud"
)
// UpdateResult is returned from a call to the Update function.
type UpdateResult struct {
gophercloud.HeaderResult
}
// UpdateHeader represents the headers returned in the response from an Update request.
type UpdateHeader struct {
ContentLength string `mapstructure:"Content-Length"`
ContentType string `mapstructure:"Content-Type"`
Date time.Time `mapstructure:"-"`
TransID string `mapstructure:"X-Trans-Id"`
}
// 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.
func (ur UpdateResult) Extract() (UpdateHeader, error) {
var uh UpdateHeader
if ur.Err != nil {
return uh, ur.Err
}
if err := gophercloud.DecodeHeader(ur.Header, &uh); err != nil {
return uh, err
}
if date, ok := ur.Header["Date"]; ok && len(date) > 0 {
t, err := time.Parse(time.RFC1123, ur.Header["Date"][0])
if err != nil {
return uh, err
}
uh.Date = t
}
return uh, nil
}
// GetHeader represents the headers returned in the response from a Get request.
type GetHeader struct {
BytesUsed int64 `mapstructure:"X-Account-Bytes-Used"`
ContainerCount int `mapstructure:"X-Account-Container-Count"`
ContentLength int64 `mapstructure:"Content-Length"`
ContentType string `mapstructure:"Content-Type"`
Date time.Time `mapstructure:"-"`
ObjectCount int64 `mapstructure:"X-Account-Object-Count"`
TransID string `mapstructure:"X-Trans-Id"`
TempURLKey string `mapstructure:"X-Account-Meta-Temp-URL-Key"`
TempURLKey2 string `mapstructure:"X-Account-Meta-Temp-URL-Key-2"`
}
// GetResult is returned from a call to the Get function.
type GetResult struct {
gophercloud.HeaderResult
}
// 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.
func (gr GetResult) Extract() (GetHeader, error) {
var gh GetHeader
if gr.Err != nil {
return gh, gr.Err
}
if err := gophercloud.DecodeHeader(gr.Header, &gh); err != nil {
return gh, err
}
if date, ok := gr.Header["Date"]; ok && len(date) > 0 {
t, err := time.Parse(time.RFC1123, gr.Header["Date"][0])
if err != nil {
return gh, err
}
gh.Date = t
}
return gh, nil
}
// ExtractMetadata is a function that takes a GetResult (of type *http.Response)
// and returns the custom metatdata associated with the account.
func (gr GetResult) ExtractMetadata() (map[string]string, error) {
if gr.Err != nil {
return nil, gr.Err
}
metadata := make(map[string]string)
for k, v := range gr.Header {
if strings.HasPrefix(k, "X-Account-Meta-") {
key := strings.TrimPrefix(k, "X-Account-Meta-")
metadata[key] = v[0]
}
}
return metadata, nil
}

View File

@@ -0,0 +1,11 @@
package accounts
import "github.com/rackspace/gophercloud"
func getURL(c *gophercloud.ServiceClient) string {
return c.Endpoint
}
func updateURL(c *gophercloud.ServiceClient) string {
return getURL(c)
}

View File

@@ -0,0 +1,8 @@
// Package containers contains functionality for working with Object Storage
// container resources. A container serves as a logical namespace for objects
// that are placed inside it - an object with the same name in two different
// containers represents two different objects.
//
// In addition to containing objects, you can also use the container to control
// access to objects by using an access control list (ACL).
package containers

View File

@@ -0,0 +1,143 @@
// +build fixtures
package containers
import (
"fmt"
"net/http"
"testing"
th "github.com/rackspace/gophercloud/testhelper"
fake "github.com/rackspace/gophercloud/testhelper/client"
)
// ExpectedListInfo is the result expected from a call to `List` when full
// info is requested.
var ExpectedListInfo = []Container{
Container{
Count: 0,
Bytes: 0,
Name: "janeausten",
},
Container{
Count: 1,
Bytes: 14,
Name: "marktwain",
},
}
// ExpectedListNames is the result expected from a call to `List` when just
// container names are requested.
var ExpectedListNames = []string{"janeausten", "marktwain"}
// HandleListContainerInfoSuccessfully creates an HTTP handler at `/` on the test handler mux that
// responds with a `List` response when full info is requested.
func HandleListContainerInfoSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestHeader(t, r, "Accept", "application/json")
w.Header().Set("Content-Type", "application/json")
r.ParseForm()
marker := r.Form.Get("marker")
switch marker {
case "":
fmt.Fprintf(w, `[
{
"count": 0,
"bytes": 0,
"name": "janeausten"
},
{
"count": 1,
"bytes": 14,
"name": "marktwain"
}
]`)
case "janeausten":
fmt.Fprintf(w, `[
{
"count": 1,
"bytes": 14,
"name": "marktwain"
}
]`)
case "marktwain":
fmt.Fprintf(w, `[]`)
default:
t.Fatalf("Unexpected marker: [%s]", marker)
}
})
}
// HandleListContainerNamesSuccessfully creates an HTTP handler at `/` on the test handler mux that
// responds with a `ListNames` response when only container names are requested.
func HandleListContainerNamesSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestHeader(t, r, "Accept", "text/plain")
w.Header().Set("Content-Type", "text/plain")
r.ParseForm()
marker := r.Form.Get("marker")
switch marker {
case "":
fmt.Fprintf(w, "janeausten\nmarktwain\n")
case "janeausten":
fmt.Fprintf(w, "marktwain\n")
case "marktwain":
fmt.Fprintf(w, ``)
default:
t.Fatalf("Unexpected marker: [%s]", marker)
}
})
}
// HandleCreateContainerSuccessfully creates an HTTP handler at `/testContainer` on the test handler mux that
// responds with a `Create` response.
func HandleCreateContainerSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "PUT")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestHeader(t, r, "Accept", "application/json")
w.Header().Add("X-Container-Meta-Foo", "bar")
w.Header().Add("X-Trans-Id", "1234567")
w.WriteHeader(http.StatusNoContent)
})
}
// HandleDeleteContainerSuccessfully creates an HTTP handler at `/testContainer` on the test handler mux that
// responds with a `Delete` response.
func HandleDeleteContainerSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "DELETE")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestHeader(t, r, "Accept", "application/json")
w.WriteHeader(http.StatusNoContent)
})
}
// HandleUpdateContainerSuccessfully creates an HTTP handler at `/testContainer` on the test handler mux that
// responds with a `Update` response.
func HandleUpdateContainerSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "POST")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestHeader(t, r, "Accept", "application/json")
w.WriteHeader(http.StatusNoContent)
})
}
// HandleGetContainerSuccessfully creates an HTTP handler at `/testContainer` on the test handler mux that
// responds with a `Get` response.
func HandleGetContainerSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "HEAD")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestHeader(t, r, "Accept", "application/json")
w.WriteHeader(http.StatusNoContent)
})
}

View File

@@ -0,0 +1,205 @@
package containers
import (
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
)
// ListOptsBuilder allows extensions to add additional parameters to the List
// request.
type ListOptsBuilder interface {
ToContainerListParams() (bool, string, error)
}
// ListOpts is a structure that holds options for listing containers.
type ListOpts struct {
Full bool
Limit int `q:"limit"`
Marker string `q:"marker"`
EndMarker string `q:"end_marker"`
Format string `q:"format"`
Prefix string `q:"prefix"`
Delimiter string `q:"delimiter"`
}
// ToContainerListParams formats a ListOpts into a query string and boolean
// representing whether to list complete information for each container.
func (opts ListOpts) ToContainerListParams() (bool, string, error) {
q, err := gophercloud.BuildQueryString(opts)
if err != nil {
return false, "", err
}
return opts.Full, q.String(), nil
}
// List is a function that retrieves containers associated with the account as
// well as account metadata. It returns a pager which can be iterated with the
// EachPage function.
func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
headers := map[string]string{"Accept": "text/plain", "Content-Type": "text/plain"}
url := listURL(c)
if opts != nil {
full, query, err := opts.ToContainerListParams()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
if full {
headers = map[string]string{"Accept": "application/json", "Content-Type": "application/json"}
}
}
createPage := func(r pagination.PageResult) pagination.Page {
p := ContainerPage{pagination.MarkerPageBase{PageResult: r}}
p.MarkerPageBase.Owner = p
return p
}
pager := pagination.NewPager(c, url, createPage)
pager.Headers = headers
return pager
}
// CreateOptsBuilder allows extensions to add additional parameters to the
// Create request.
type CreateOptsBuilder interface {
ToContainerCreateMap() (map[string]string, error)
}
// CreateOpts is a structure that holds parameters for creating a container.
type CreateOpts struct {
Metadata map[string]string
ContainerRead string `h:"X-Container-Read"`
ContainerSyncTo string `h:"X-Container-Sync-To"`
ContainerSyncKey string `h:"X-Container-Sync-Key"`
ContainerWrite string `h:"X-Container-Write"`
ContentType string `h:"Content-Type"`
DetectContentType bool `h:"X-Detect-Content-Type"`
IfNoneMatch string `h:"If-None-Match"`
VersionsLocation string `h:"X-Versions-Location"`
}
// ToContainerCreateMap formats a CreateOpts into a map of headers.
func (opts CreateOpts) ToContainerCreateMap() (map[string]string, error) {
h, err := gophercloud.BuildHeaders(opts)
if err != nil {
return nil, err
}
for k, v := range opts.Metadata {
h["X-Container-Meta-"+k] = v
}
return h, nil
}
// Create is a function that creates a new container.
func Create(c *gophercloud.ServiceClient, containerName string, opts CreateOptsBuilder) CreateResult {
var res CreateResult
h := c.AuthenticatedHeaders()
if opts != nil {
headers, err := opts.ToContainerCreateMap()
if err != nil {
res.Err = err
return res
}
for k, v := range headers {
h[k] = v
}
}
resp, err := c.Request("PUT", createURL(c, containerName), gophercloud.RequestOpts{
MoreHeaders: h,
OkCodes: []int{201, 202, 204},
})
if resp != nil {
res.Header = resp.Header
}
res.Err = err
return res
}
// Delete is a function that deletes a container.
func Delete(c *gophercloud.ServiceClient, containerName string) DeleteResult {
var res DeleteResult
_, res.Err = c.Delete(deleteURL(c, containerName), nil)
return res
}
// UpdateOptsBuilder allows extensions to add additional parameters to the
// Update request.
type UpdateOptsBuilder interface {
ToContainerUpdateMap() (map[string]string, error)
}
// UpdateOpts is a structure that holds parameters for updating, creating, or
// deleting a container's metadata.
type UpdateOpts struct {
Metadata map[string]string
ContainerRead string `h:"X-Container-Read"`
ContainerSyncTo string `h:"X-Container-Sync-To"`
ContainerSyncKey string `h:"X-Container-Sync-Key"`
ContainerWrite string `h:"X-Container-Write"`
ContentType string `h:"Content-Type"`
DetectContentType bool `h:"X-Detect-Content-Type"`
RemoveVersionsLocation string `h:"X-Remove-Versions-Location"`
VersionsLocation string `h:"X-Versions-Location"`
}
// ToContainerUpdateMap formats a CreateOpts into a map of headers.
func (opts UpdateOpts) ToContainerUpdateMap() (map[string]string, error) {
h, err := gophercloud.BuildHeaders(opts)
if err != nil {
return nil, err
}
for k, v := range opts.Metadata {
h["X-Container-Meta-"+k] = v
}
return h, nil
}
// Update is a function that creates, updates, or deletes a container's
// metadata.
func Update(c *gophercloud.ServiceClient, containerName string, opts UpdateOptsBuilder) UpdateResult {
var res UpdateResult
h := c.AuthenticatedHeaders()
if opts != nil {
headers, err := opts.ToContainerUpdateMap()
if err != nil {
res.Err = err
return res
}
for k, v := range headers {
h[k] = v
}
}
resp, err := c.Request("POST", updateURL(c, containerName), gophercloud.RequestOpts{
MoreHeaders: h,
OkCodes: []int{201, 202, 204},
})
if resp != nil {
res.Header = resp.Header
}
res.Err = err
return res
}
// Get is a function that retrieves the metadata of a container. To extract just
// the custom metadata, pass the GetResult response to the ExtractMetadata
// function.
func Get(c *gophercloud.ServiceClient, containerName string) GetResult {
var res GetResult
resp, err := c.Request("HEAD", getURL(c, containerName), gophercloud.RequestOpts{
OkCodes: []int{200, 204},
})
if resp != nil {
res.Header = resp.Header
}
res.Err = err
return res
}

View File

@@ -0,0 +1,270 @@
package containers
import (
"fmt"
"strings"
"time"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
"github.com/mitchellh/mapstructure"
)
// Container represents a container resource.
type Container struct {
// The total number of bytes stored in the container.
Bytes int `json:"bytes" mapstructure:"bytes"`
// The total number of objects stored in the container.
Count int `json:"count" mapstructure:"count"`
// The name of the container.
Name string `json:"name" mapstructure:"name"`
}
// ContainerPage is the page returned by a pager when traversing over a
// collection of containers.
type ContainerPage struct {
pagination.MarkerPageBase
}
// IsEmpty returns true if a ListResult contains no container names.
func (r ContainerPage) IsEmpty() (bool, error) {
names, err := ExtractNames(r)
if err != nil {
return true, err
}
return len(names) == 0, nil
}
// LastMarker returns the last container name in a ListResult.
func (r ContainerPage) LastMarker() (string, error) {
names, err := ExtractNames(r)
if err != nil {
return "", err
}
if len(names) == 0 {
return "", nil
}
return names[len(names)-1], nil
}
// ExtractInfo is a function that takes a ListResult and returns the containers' information.
func ExtractInfo(page pagination.Page) ([]Container, error) {
untyped := page.(ContainerPage).Body.([]interface{})
results := make([]Container, len(untyped))
for index, each := range untyped {
container := each.(map[string]interface{})
err := mapstructure.Decode(container, &results[index])
if err != nil {
return results, err
}
}
return results, nil
}
// ExtractNames is a function that takes a ListResult and returns the containers' names.
func ExtractNames(page pagination.Page) ([]string, error) {
casted := page.(ContainerPage)
ct := casted.Header.Get("Content-Type")
switch {
case strings.HasPrefix(ct, "application/json"):
parsed, err := ExtractInfo(page)
if err != nil {
return nil, err
}
names := make([]string, 0, len(parsed))
for _, container := range parsed {
names = append(names, container.Name)
}
return names, nil
case strings.HasPrefix(ct, "text/plain"):
names := make([]string, 0, 50)
body := string(page.(ContainerPage).Body.([]uint8))
for _, name := range strings.Split(body, "\n") {
if len(name) > 0 {
names = append(names, name)
}
}
return names, nil
default:
return nil, fmt.Errorf("Cannot extract names from response with content-type: [%s]", ct)
}
}
// GetHeader represents the headers returned in the response from a Get request.
type GetHeader struct {
AcceptRanges string `mapstructure:"Accept-Ranges"`
BytesUsed int64 `mapstructure:"X-Account-Bytes-Used"`
ContentLength int64 `mapstructure:"Content-Length"`
ContentType string `mapstructure:"Content-Type"`
Date time.Time `mapstructure:"-"`
ObjectCount int64 `mapstructure:"X-Container-Object-Count"`
Read string `mapstructure:"X-Container-Read"`
TransID string `mapstructure:"X-Trans-Id"`
VersionsLocation string `mapstructure:"X-Versions-Location"`
Write string `mapstructure:"X-Container-Write"`
}
// GetResult represents the result of a get operation.
type GetResult struct {
gophercloud.HeaderResult
}
// 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.
func (gr GetResult) Extract() (GetHeader, error) {
var gh GetHeader
if gr.Err != nil {
return gh, gr.Err
}
if err := gophercloud.DecodeHeader(gr.Header, &gh); err != nil {
return gh, err
}
if date, ok := gr.Header["Date"]; ok && len(date) > 0 {
t, err := time.Parse(time.RFC1123, gr.Header["Date"][0])
if err != nil {
return gh, err
}
gh.Date = t
}
return gh, nil
}
// ExtractMetadata is a function that takes a GetResult (of type *http.Response)
// and returns the custom metadata associated with the container.
func (gr GetResult) ExtractMetadata() (map[string]string, error) {
if gr.Err != nil {
return nil, gr.Err
}
metadata := make(map[string]string)
for k, v := range gr.Header {
if strings.HasPrefix(k, "X-Container-Meta-") {
key := strings.TrimPrefix(k, "X-Container-Meta-")
metadata[key] = v[0]
}
}
return metadata, nil
}
// CreateHeader represents the headers returned in the response from a Create request.
type CreateHeader struct {
ContentLength int64 `mapstructure:"Content-Length"`
ContentType string `mapstructure:"Content-Type"`
Date time.Time `mapstructure:"-"`
TransID string `mapstructure:"X-Trans-Id"`
}
// CreateResult represents the result of a create operation. To extract the
// the headers from the HTTP response, you can invoke the 'ExtractHeader'
// method on the result struct.
type CreateResult struct {
gophercloud.HeaderResult
}
// Extract will return a struct of headers returned from a call to Create. To obtain
// a map of headers, call the ExtractHeader method on the CreateResult.
func (cr CreateResult) Extract() (CreateHeader, error) {
var ch CreateHeader
if cr.Err != nil {
return ch, cr.Err
}
if err := gophercloud.DecodeHeader(cr.Header, &ch); err != nil {
return ch, err
}
if date, ok := cr.Header["Date"]; ok && len(date) > 0 {
t, err := time.Parse(time.RFC1123, cr.Header["Date"][0])
if err != nil {
return ch, err
}
ch.Date = t
}
return ch, nil
}
// UpdateHeader represents the headers returned in the response from a Update request.
type UpdateHeader struct {
ContentLength int64 `mapstructure:"Content-Length"`
ContentType string `mapstructure:"Content-Type"`
Date time.Time `mapstructure:"-"`
TransID string `mapstructure:"X-Trans-Id"`
}
// UpdateResult represents the result of an update operation. To extract the
// the headers from the HTTP response, you can invoke the 'ExtractHeader'
// method on the result struct.
type UpdateResult struct {
gophercloud.HeaderResult
}
// Extract will return a struct of headers returned from a call to Update. To obtain
// a map of headers, call the ExtractHeader method on the UpdateResult.
func (ur UpdateResult) Extract() (UpdateHeader, error) {
var uh UpdateHeader
if ur.Err != nil {
return uh, ur.Err
}
if err := gophercloud.DecodeHeader(ur.Header, &uh); err != nil {
return uh, err
}
if date, ok := ur.Header["Date"]; ok && len(date) > 0 {
t, err := time.Parse(time.RFC1123, ur.Header["Date"][0])
if err != nil {
return uh, err
}
uh.Date = t
}
return uh, nil
}
// DeleteHeader represents the headers returned in the response from a Delete request.
type DeleteHeader struct {
ContentLength int64 `mapstructure:"Content-Length"`
ContentType string `mapstructure:"Content-Type"`
Date time.Time `mapstructure:"-"`
TransID string `mapstructure:"X-Trans-Id"`
}
// DeleteResult represents the result of a delete operation. To extract the
// the headers from the HTTP response, you can invoke the 'ExtractHeader'
// method on the result struct.
type DeleteResult struct {
gophercloud.HeaderResult
}
// Extract will return a struct of headers returned from a call to Delete. To obtain
// a map of headers, call the ExtractHeader method on the DeleteResult.
func (dr DeleteResult) Extract() (DeleteHeader, error) {
var dh DeleteHeader
if dr.Err != nil {
return dh, dr.Err
}
if err := gophercloud.DecodeHeader(dr.Header, &dh); err != nil {
return dh, err
}
if date, ok := dr.Header["Date"]; ok && len(date) > 0 {
t, err := time.Parse(time.RFC1123, dr.Header["Date"][0])
if err != nil {
return dh, err
}
dh.Date = t
}
return dh, nil
}

View File

@@ -0,0 +1,23 @@
package containers
import "github.com/rackspace/gophercloud"
func listURL(c *gophercloud.ServiceClient) string {
return c.Endpoint
}
func createURL(c *gophercloud.ServiceClient, container string) string {
return c.ServiceURL(container)
}
func getURL(c *gophercloud.ServiceClient, container string) string {
return createURL(c, container)
}
func deleteURL(c *gophercloud.ServiceClient, container string) string {
return createURL(c, container)
}
func updateURL(c *gophercloud.ServiceClient, container string) string {
return createURL(c, container)
}

View File

@@ -0,0 +1,5 @@
// Package objects contains functionality for working with Object Storage
// object resources. An object is a resource that represents and contains data
// - such as documents, images, and so on. You can also store custom metadata
// with an object.
package objects

View File

@@ -0,0 +1,195 @@
// +build fixtures
package objects
import (
"crypto/md5"
"fmt"
"io"
"net/http"
"testing"
th "github.com/rackspace/gophercloud/testhelper"
fake "github.com/rackspace/gophercloud/testhelper/client"
)
// HandleDownloadObjectSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler mux that
// responds with a `Download` response.
func HandleDownloadObjectSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestHeader(t, r, "Accept", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Successful download with Gophercloud")
})
}
// ExpectedListInfo is the result expected from a call to `List` when full
// info is requested.
var ExpectedListInfo = []Object{
Object{
Hash: "451e372e48e0f6b1114fa0724aa79fa1",
LastModified: "2009-11-10 23:00:00 +0000 UTC",
Bytes: 14,
Name: "goodbye",
ContentType: "application/octet-stream",
},
Object{
Hash: "451e372e48e0f6b1114fa0724aa79fa1",
LastModified: "2009-11-10 23:00:00 +0000 UTC",
Bytes: 14,
Name: "hello",
ContentType: "application/octet-stream",
},
}
// ExpectedListNames is the result expected from a call to `List` when just
// object names are requested.
var ExpectedListNames = []string{"hello", "goodbye"}
// HandleListObjectsInfoSuccessfully creates an HTTP handler at `/testContainer` on the test handler mux that
// responds with a `List` response when full info is requested.
func HandleListObjectsInfoSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestHeader(t, r, "Accept", "application/json")
w.Header().Set("Content-Type", "application/json")
r.ParseForm()
marker := r.Form.Get("marker")
switch marker {
case "":
fmt.Fprintf(w, `[
{
"hash": "451e372e48e0f6b1114fa0724aa79fa1",
"last_modified": "2009-11-10 23:00:00 +0000 UTC",
"bytes": 14,
"name": "goodbye",
"content_type": "application/octet-stream"
},
{
"hash": "451e372e48e0f6b1114fa0724aa79fa1",
"last_modified": "2009-11-10 23:00:00 +0000 UTC",
"bytes": 14,
"name": "hello",
"content_type": "application/octet-stream"
}
]`)
case "hello":
fmt.Fprintf(w, `[]`)
default:
t.Fatalf("Unexpected marker: [%s]", marker)
}
})
}
// HandleListObjectNamesSuccessfully creates an HTTP handler at `/testContainer` on the test handler mux that
// responds with a `List` response when only object names are requested.
func HandleListObjectNamesSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestHeader(t, r, "Accept", "text/plain")
w.Header().Set("Content-Type", "text/plain")
r.ParseForm()
marker := r.Form.Get("marker")
switch marker {
case "":
fmt.Fprintf(w, "hello\ngoodbye\n")
case "goodbye":
fmt.Fprintf(w, "")
default:
t.Fatalf("Unexpected marker: [%s]", marker)
}
})
}
// HandleCreateTextObjectSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler mux
// that responds with a `Create` response. A Content-Type of "text/plain" is expected.
func HandleCreateTextObjectSuccessfully(t *testing.T, content string) {
th.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "PUT")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestHeader(t, r, "Content-Type", "text/plain")
th.TestHeader(t, r, "Accept", "application/json")
hash := md5.New()
io.WriteString(hash, content)
localChecksum := hash.Sum(nil)
w.Header().Set("ETag", fmt.Sprintf("%x", localChecksum))
w.WriteHeader(http.StatusCreated)
})
}
// HandleCreateTypelessObjectSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler
// mux that responds with a `Create` response. No Content-Type header may be present in the request, so that server-
// side content-type detection will be triggered properly.
func HandleCreateTypelessObjectSuccessfully(t *testing.T, content string) {
th.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "PUT")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestHeader(t, r, "Accept", "application/json")
if contentType, present := r.Header["Content-Type"]; present {
t.Errorf("Expected Content-Type header to be omitted, but was %#v", contentType)
}
hash := md5.New()
io.WriteString(hash, content)
localChecksum := hash.Sum(nil)
w.Header().Set("ETag", fmt.Sprintf("%x", localChecksum))
w.WriteHeader(http.StatusCreated)
})
}
// HandleCopyObjectSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler mux that
// responds with a `Copy` response.
func HandleCopyObjectSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "COPY")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestHeader(t, r, "Accept", "application/json")
th.TestHeader(t, r, "Destination", "/newTestContainer/newTestObject")
w.WriteHeader(http.StatusCreated)
})
}
// HandleDeleteObjectSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler mux that
// responds with a `Delete` response.
func HandleDeleteObjectSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "DELETE")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestHeader(t, r, "Accept", "application/json")
w.WriteHeader(http.StatusNoContent)
})
}
// HandleUpdateObjectSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler mux that
// responds with a `Update` response.
func HandleUpdateObjectSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "POST")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestHeader(t, r, "Accept", "application/json")
th.TestHeader(t, r, "X-Object-Meta-Gophercloud-Test", "objects")
w.WriteHeader(http.StatusAccepted)
})
}
// HandleGetObjectSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler mux that
// responds with a `Get` response.
func HandleGetObjectSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "HEAD")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestHeader(t, r, "Accept", "application/json")
w.Header().Add("X-Object-Meta-Gophercloud-Test", "objects")
w.WriteHeader(http.StatusNoContent)
})
}

View File

@@ -0,0 +1,501 @@
package objects
import (
"bufio"
"crypto/hmac"
"crypto/md5"
"crypto/sha1"
"fmt"
"io"
"io/ioutil"
"strings"
"time"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack/objectstorage/v1/accounts"
"github.com/rackspace/gophercloud/pagination"
)
// ListOptsBuilder allows extensions to add additional parameters to the List
// request.
type ListOptsBuilder interface {
ToObjectListParams() (bool, string, error)
}
// ListOpts is a structure that holds parameters for listing objects.
type ListOpts struct {
// Full is a true/false value that represents the amount of object information
// returned. If Full is set to true, then the content-type, number of bytes, hash
// date last modified, and name are returned. If set to false or not set, then
// only the object names are returned.
Full bool
Limit int `q:"limit"`
Marker string `q:"marker"`
EndMarker string `q:"end_marker"`
Format string `q:"format"`
Prefix string `q:"prefix"`
Delimiter string `q:"delimiter"`
Path string `q:"path"`
}
// ToObjectListParams formats a ListOpts into a query string and boolean
// representing whether to list complete information for each object.
func (opts ListOpts) ToObjectListParams() (bool, string, error) {
q, err := gophercloud.BuildQueryString(opts)
if err != nil {
return false, "", err
}
return opts.Full, q.String(), nil
}
// List is a function that retrieves all objects in a container. It also returns the details
// for the container. To extract only the object information or names, pass the ListResult
// response to the ExtractInfo or ExtractNames function, respectively.
func List(c *gophercloud.ServiceClient, containerName string, opts ListOptsBuilder) pagination.Pager {
headers := map[string]string{"Accept": "text/plain", "Content-Type": "text/plain"}
url := listURL(c, containerName)
if opts != nil {
full, query, err := opts.ToObjectListParams()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
if full {
headers = map[string]string{"Accept": "application/json", "Content-Type": "application/json"}
}
}
createPage := func(r pagination.PageResult) pagination.Page {
p := ObjectPage{pagination.MarkerPageBase{PageResult: r}}
p.MarkerPageBase.Owner = p
return p
}
pager := pagination.NewPager(c, url, createPage)
pager.Headers = headers
return pager
}
// DownloadOptsBuilder allows extensions to add additional parameters to the
// Download request.
type DownloadOptsBuilder interface {
ToObjectDownloadParams() (map[string]string, string, error)
}
// DownloadOpts is a structure that holds parameters for downloading an object.
type DownloadOpts struct {
IfMatch string `h:"If-Match"`
IfModifiedSince time.Time `h:"If-Modified-Since"`
IfNoneMatch string `h:"If-None-Match"`
IfUnmodifiedSince time.Time `h:"If-Unmodified-Since"`
Range string `h:"Range"`
Expires string `q:"expires"`
MultipartManifest string `q:"multipart-manifest"`
Signature string `q:"signature"`
}
// ToObjectDownloadParams formats a DownloadOpts into a query string and map of
// headers.
func (opts DownloadOpts) ToObjectDownloadParams() (map[string]string, string, error) {
q, err := gophercloud.BuildQueryString(opts)
if err != nil {
return nil, "", err
}
h, err := gophercloud.BuildHeaders(opts)
if err != nil {
return nil, q.String(), err
}
return h, q.String(), nil
}
// Download is a function that retrieves the content and metadata for an object.
// To extract just the content, pass the DownloadResult response to the
// ExtractContent function.
func Download(c *gophercloud.ServiceClient, containerName, objectName string, opts DownloadOptsBuilder) DownloadResult {
var res DownloadResult
url := downloadURL(c, containerName, objectName)
h := c.AuthenticatedHeaders()
if opts != nil {
headers, query, err := opts.ToObjectDownloadParams()
if err != nil {
res.Err = err
return res
}
for k, v := range headers {
h[k] = v
}
url += query
}
resp, err := c.Request("GET", url, gophercloud.RequestOpts{
MoreHeaders: h,
OkCodes: []int{200, 304},
})
if resp != nil {
res.Header = resp.Header
res.Body = resp.Body
}
res.Err = err
return res
}
// CreateOptsBuilder allows extensions to add additional parameters to the
// Create request.
type CreateOptsBuilder interface {
ToObjectCreateParams() (map[string]string, string, error)
}
// CreateOpts is a structure that holds parameters for creating an object.
type CreateOpts struct {
Metadata map[string]string
ContentDisposition string `h:"Content-Disposition"`
ContentEncoding string `h:"Content-Encoding"`
ContentLength int64 `h:"Content-Length"`
ContentType string `h:"Content-Type"`
CopyFrom string `h:"X-Copy-From"`
DeleteAfter int `h:"X-Delete-After"`
DeleteAt int `h:"X-Delete-At"`
DetectContentType string `h:"X-Detect-Content-Type"`
ETag string `h:"ETag"`
IfNoneMatch string `h:"If-None-Match"`
ObjectManifest string `h:"X-Object-Manifest"`
TransferEncoding string `h:"Transfer-Encoding"`
Expires string `q:"expires"`
MultipartManifest string `q:"multipart-manifest"`
Signature string `q:"signature"`
}
// ToObjectCreateParams formats a CreateOpts into a query string and map of
// headers.
func (opts CreateOpts) ToObjectCreateParams() (map[string]string, string, error) {
q, err := gophercloud.BuildQueryString(opts)
if err != nil {
return nil, "", err
}
h, err := gophercloud.BuildHeaders(opts)
if err != nil {
return nil, q.String(), err
}
for k, v := range opts.Metadata {
h["X-Object-Meta-"+k] = v
}
return h, q.String(), nil
}
// Create is a function that creates a new object or replaces an existing object. If the returned response's ETag
// header fails to match the local checksum, the failed request will automatically be retried up to a maximum of 3 times.
func Create(c *gophercloud.ServiceClient, containerName, objectName string, content io.ReadSeeker, opts CreateOptsBuilder) CreateResult {
var res CreateResult
url := createURL(c, containerName, objectName)
h := make(map[string]string)
if opts != nil {
headers, query, err := opts.ToObjectCreateParams()
if err != nil {
res.Err = err
return res
}
for k, v := range headers {
h[k] = v
}
url += query
}
hash := md5.New()
bufioReader := bufio.NewReader(io.TeeReader(content, hash))
io.Copy(ioutil.Discard, bufioReader)
localChecksum := hash.Sum(nil)
h["ETag"] = fmt.Sprintf("%x", localChecksum)
_, err := content.Seek(0, 0)
if err != nil {
res.Err = err
return res
}
ropts := gophercloud.RequestOpts{
RawBody: content,
MoreHeaders: h,
}
resp, err := c.Request("PUT", url, ropts)
if err != nil {
res.Err = err
return res
}
if resp != nil {
res.Header = resp.Header
if resp.Header.Get("ETag") == fmt.Sprintf("%x", localChecksum) {
res.Err = err
return res
}
res.Err = fmt.Errorf("Local checksum does not match API ETag header")
}
return res
}
// CopyOptsBuilder allows extensions to add additional parameters to the
// Copy request.
type CopyOptsBuilder interface {
ToObjectCopyMap() (map[string]string, error)
}
// CopyOpts is a structure that holds parameters for copying one object to
// another.
type CopyOpts struct {
Metadata map[string]string
ContentDisposition string `h:"Content-Disposition"`
ContentEncoding string `h:"Content-Encoding"`
ContentType string `h:"Content-Type"`
Destination string `h:"Destination,required"`
}
// ToObjectCopyMap formats a CopyOpts into a map of headers.
func (opts CopyOpts) ToObjectCopyMap() (map[string]string, error) {
if opts.Destination == "" {
return nil, fmt.Errorf("Required CopyOpts field 'Destination' not set.")
}
h, err := gophercloud.BuildHeaders(opts)
if err != nil {
return nil, err
}
for k, v := range opts.Metadata {
h["X-Object-Meta-"+k] = v
}
return h, nil
}
// Copy is a function that copies one object to another.
func Copy(c *gophercloud.ServiceClient, containerName, objectName string, opts CopyOptsBuilder) CopyResult {
var res CopyResult
h := c.AuthenticatedHeaders()
headers, err := opts.ToObjectCopyMap()
if err != nil {
res.Err = err
return res
}
for k, v := range headers {
h[k] = v
}
url := copyURL(c, containerName, objectName)
resp, err := c.Request("COPY", url, gophercloud.RequestOpts{
MoreHeaders: h,
OkCodes: []int{201},
})
if resp != nil {
res.Header = resp.Header
}
res.Err = err
return res
}
// DeleteOptsBuilder allows extensions to add additional parameters to the
// Delete request.
type DeleteOptsBuilder interface {
ToObjectDeleteQuery() (string, error)
}
// DeleteOpts is a structure that holds parameters for deleting an object.
type DeleteOpts struct {
MultipartManifest string `q:"multipart-manifest"`
}
// ToObjectDeleteQuery formats a DeleteOpts into a query string.
func (opts DeleteOpts) ToObjectDeleteQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
if err != nil {
return "", err
}
return q.String(), nil
}
// Delete is a function that deletes an object.
func Delete(c *gophercloud.ServiceClient, containerName, objectName string, opts DeleteOptsBuilder) DeleteResult {
var res DeleteResult
url := deleteURL(c, containerName, objectName)
if opts != nil {
query, err := opts.ToObjectDeleteQuery()
if err != nil {
res.Err = err
return res
}
url += query
}
resp, err := c.Delete(url, nil)
if resp != nil {
res.Header = resp.Header
}
res.Err = err
return res
}
// GetOptsBuilder allows extensions to add additional parameters to the
// Get request.
type GetOptsBuilder interface {
ToObjectGetQuery() (string, error)
}
// GetOpts is a structure that holds parameters for getting an object's metadata.
type GetOpts struct {
Expires string `q:"expires"`
Signature string `q:"signature"`
}
// ToObjectGetQuery formats a GetOpts into a query string.
func (opts GetOpts) ToObjectGetQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
if err != nil {
return "", err
}
return q.String(), nil
}
// Get is a function that retrieves the metadata of an object. To extract just the custom
// metadata, pass the GetResult response to the ExtractMetadata function.
func Get(c *gophercloud.ServiceClient, containerName, objectName string, opts GetOptsBuilder) GetResult {
var res GetResult
url := getURL(c, containerName, objectName)
if opts != nil {
query, err := opts.ToObjectGetQuery()
if err != nil {
res.Err = err
return res
}
url += query
}
resp, err := c.Request("HEAD", url, gophercloud.RequestOpts{
OkCodes: []int{200, 204},
})
if resp != nil {
res.Header = resp.Header
}
res.Err = err
return res
}
// UpdateOptsBuilder allows extensions to add additional parameters to the
// Update request.
type UpdateOptsBuilder interface {
ToObjectUpdateMap() (map[string]string, error)
}
// UpdateOpts is a structure that holds parameters for updating, creating, or deleting an
// object's metadata.
type UpdateOpts struct {
Metadata map[string]string
ContentDisposition string `h:"Content-Disposition"`
ContentEncoding string `h:"Content-Encoding"`
ContentType string `h:"Content-Type"`
DeleteAfter int `h:"X-Delete-After"`
DeleteAt int `h:"X-Delete-At"`
DetectContentType bool `h:"X-Detect-Content-Type"`
}
// ToObjectUpdateMap formats a UpdateOpts into a map of headers.
func (opts UpdateOpts) ToObjectUpdateMap() (map[string]string, error) {
h, err := gophercloud.BuildHeaders(opts)
if err != nil {
return nil, err
}
for k, v := range opts.Metadata {
h["X-Object-Meta-"+k] = v
}
return h, nil
}
// Update is a function that creates, updates, or deletes an object's metadata.
func Update(c *gophercloud.ServiceClient, containerName, objectName string, opts UpdateOptsBuilder) UpdateResult {
var res UpdateResult
h := c.AuthenticatedHeaders()
if opts != nil {
headers, err := opts.ToObjectUpdateMap()
if err != nil {
res.Err = err
return res
}
for k, v := range headers {
h[k] = v
}
}
url := updateURL(c, containerName, objectName)
resp, err := c.Request("POST", url, gophercloud.RequestOpts{
MoreHeaders: h,
})
if resp != nil {
res.Header = resp.Header
}
res.Err = err
return res
}
// HTTPMethod represents an HTTP method string (e.g. "GET").
type HTTPMethod string
var (
// GET represents an HTTP "GET" method.
GET HTTPMethod = "GET"
// POST represents an HTTP "POST" method.
POST HTTPMethod = "POST"
)
// CreateTempURLOpts are options for creating a temporary URL for an object.
type CreateTempURLOpts struct {
// (REQUIRED) Method is the HTTP method to allow for users of the temp URL. Valid values
// are "GET" and "POST".
Method HTTPMethod
// (REQUIRED) TTL is the number of seconds the temp URL should be active.
TTL int
// (Optional) Split is the string on which to split the object URL. Since only
// the object path is used in the hash, the object URL needs to be parsed. If
// empty, the default OpenStack URL split point will be used ("/v1/").
Split string
}
// CreateTempURL is a function for creating a temporary URL for an object. It
// allows users to have "GET" or "POST" access to a particular tenant's object
// for a limited amount of time.
func CreateTempURL(c *gophercloud.ServiceClient, containerName, objectName string, opts CreateTempURLOpts) (string, error) {
if opts.Split == "" {
opts.Split = "/v1/"
}
duration := time.Duration(opts.TTL) * time.Second
expiry := time.Now().Add(duration).Unix()
getHeader, err := accounts.Get(c, nil).Extract()
if err != nil {
return "", err
}
secretKey := []byte(getHeader.TempURLKey)
url := getURL(c, containerName, objectName)
splitPath := strings.Split(url, opts.Split)
baseURL, objectPath := splitPath[0], splitPath[1]
objectPath = opts.Split + objectPath
body := fmt.Sprintf("%s\n%d\n%s", opts.Method, expiry, objectPath)
hash := hmac.New(sha1.New, secretKey)
hash.Write([]byte(body))
hexsum := fmt.Sprintf("%x", hash.Sum(nil))
return fmt.Sprintf("%s%s?temp_url_sig=%s&temp_url_expires=%d", baseURL, objectPath, hexsum, expiry), nil
}

View File

@@ -0,0 +1,438 @@
package objects
import (
"fmt"
"io"
"io/ioutil"
"strconv"
"strings"
"time"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
"github.com/mitchellh/mapstructure"
)
// Object is a structure that holds information related to a storage object.
type Object struct {
// Bytes is the total number of bytes that comprise the object.
Bytes int64 `json:"bytes" mapstructure:"bytes"`
// ContentType is the content type of the object.
ContentType string `json:"content_type" mapstructure:"content_type"`
// Hash represents the MD5 checksum value of the object's content.
Hash string `json:"hash" mapstructure:"hash"`
// LastModified is the RFC3339Milli time the object was last modified, represented
// as a string. For any given object (obj), this value may be parsed to a time.Time:
// lastModified, err := time.Parse(gophercloud.RFC3339Milli, obj.LastModified)
LastModified string `json:"last_modified" mapstructure:"last_modified"`
// Name is the unique name for the object.
Name string `json:"name" mapstructure:"name"`
}
// ObjectPage is a single page of objects that is returned from a call to the
// List function.
type ObjectPage struct {
pagination.MarkerPageBase
}
// IsEmpty returns true if a ListResult contains no object names.
func (r ObjectPage) IsEmpty() (bool, error) {
names, err := ExtractNames(r)
if err != nil {
return true, err
}
return len(names) == 0, nil
}
// LastMarker returns the last object name in a ListResult.
func (r ObjectPage) LastMarker() (string, error) {
names, err := ExtractNames(r)
if err != nil {
return "", err
}
if len(names) == 0 {
return "", nil
}
return names[len(names)-1], nil
}
// ExtractInfo is a function that takes a page of objects and returns their full information.
func ExtractInfo(page pagination.Page) ([]Object, error) {
untyped := page.(ObjectPage).Body.([]interface{})
results := make([]Object, len(untyped))
for index, each := range untyped {
object := each.(map[string]interface{})
err := mapstructure.Decode(object, &results[index])
if err != nil {
return results, err
}
}
return results, nil
}
// ExtractNames is a function that takes a page of objects and returns only their names.
func ExtractNames(page pagination.Page) ([]string, error) {
casted := page.(ObjectPage)
ct := casted.Header.Get("Content-Type")
switch {
case strings.HasPrefix(ct, "application/json"):
parsed, err := ExtractInfo(page)
if err != nil {
return nil, err
}
names := make([]string, 0, len(parsed))
for _, object := range parsed {
names = append(names, object.Name)
}
return names, nil
case strings.HasPrefix(ct, "text/plain"):
names := make([]string, 0, 50)
body := string(page.(ObjectPage).Body.([]uint8))
for _, name := range strings.Split(body, "\n") {
if len(name) > 0 {
names = append(names, name)
}
}
return names, nil
case strings.HasPrefix(ct, "text/html"):
return []string{}, nil
default:
return nil, fmt.Errorf("Cannot extract names from response with content-type: [%s]", ct)
}
}
// DownloadHeader represents the headers returned in the response from a Download request.
type DownloadHeader struct {
AcceptRanges string `mapstructure:"Accept-Ranges"`
ContentDisposition string `mapstructure:"Content-Disposition"`
ContentEncoding string `mapstructure:"Content-Encoding"`
ContentLength int64 `mapstructure:"Content-Length"`
ContentType string `mapstructure:"Content-Type"`
Date time.Time `mapstructure:"-"`
DeleteAt time.Time `mapstructure:"-"`
ETag string `mapstructure:"Etag"`
LastModified time.Time `mapstructure:"-"`
ObjectManifest string `mapstructure:"X-Object-Manifest"`
StaticLargeObject bool `mapstructure:"X-Static-Large-Object"`
TransID string `mapstructure:"X-Trans-Id"`
}
// DownloadResult is a *http.Response that is returned from a call to the Download function.
type DownloadResult struct {
gophercloud.HeaderResult
Body io.ReadCloser
}
// Extract will return a struct of headers returned from a call to Download. To obtain
// a map of headers, call the ExtractHeader method on the DownloadResult.
func (dr DownloadResult) Extract() (DownloadHeader, error) {
var dh DownloadHeader
if dr.Err != nil {
return dh, dr.Err
}
if err := gophercloud.DecodeHeader(dr.Header, &dh); err != nil {
return dh, err
}
if date, ok := dr.Header["Date"]; ok && len(date) > 0 {
t, err := time.Parse(time.RFC1123, date[0])
if err != nil {
return dh, err
}
dh.Date = t
}
if date, ok := dr.Header["Last-Modified"]; ok && len(date) > 0 {
t, err := time.Parse(time.RFC1123, date[0])
if err != nil {
return dh, err
}
dh.LastModified = t
}
if date, ok := dr.Header["X-Delete-At"]; ok && len(date) > 0 {
unix, err := strconv.ParseInt(date[0], 10, 64)
if err != nil {
return dh, err
}
dh.DeleteAt = time.Unix(unix, 0)
}
return dh, nil
}
// ExtractContent is a function that takes a DownloadResult's io.Reader body
// and reads all available data into a slice of bytes. Please be aware that due
// the nature of io.Reader is forward-only - meaning that it can only be read
// once and not rewound. You can recreate a reader from the output of this
// function by using bytes.NewReader(downloadBytes)
func (dr DownloadResult) ExtractContent() ([]byte, error) {
if dr.Err != nil {
return nil, dr.Err
}
body, err := ioutil.ReadAll(dr.Body)
if err != nil {
return nil, err
}
dr.Body.Close()
return body, nil
}
// GetHeader represents the headers returned in the response from a Get request.
type GetHeader struct {
ContentDisposition string `mapstructure:"Content-Disposition"`
ContentEncoding string `mapstructure:"Content-Encoding"`
ContentLength int64 `mapstructure:"Content-Length"`
ContentType string `mapstructure:"Content-Type"`
Date time.Time `mapstructure:"-"`
DeleteAt time.Time `mapstructure:"-"`
ETag string `mapstructure:"Etag"`
LastModified time.Time `mapstructure:"-"`
ObjectManifest string `mapstructure:"X-Object-Manifest"`
StaticLargeObject bool `mapstructure:"X-Static-Large-Object"`
TransID string `mapstructure:"X-Trans-Id"`
}
// GetResult is a *http.Response that is returned from a call to the Get function.
type GetResult struct {
gophercloud.HeaderResult
}
// 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.
func (gr GetResult) Extract() (GetHeader, error) {
var gh GetHeader
if gr.Err != nil {
return gh, gr.Err
}
if err := gophercloud.DecodeHeader(gr.Header, &gh); err != nil {
return gh, err
}
if date, ok := gr.Header["Date"]; ok && len(date) > 0 {
t, err := time.Parse(time.RFC1123, gr.Header["Date"][0])
if err != nil {
return gh, err
}
gh.Date = t
}
if date, ok := gr.Header["Last-Modified"]; ok && len(date) > 0 {
t, err := time.Parse(time.RFC1123, gr.Header["Last-Modified"][0])
if err != nil {
return gh, err
}
gh.LastModified = t
}
if date, ok := gr.Header["X-Delete-At"]; ok && len(date) > 0 {
unix, err := strconv.ParseInt(date[0], 10, 64)
if err != nil {
return gh, err
}
gh.DeleteAt = time.Unix(unix, 0)
}
return gh, nil
}
// ExtractMetadata is a function that takes a GetResult (of type *http.Response)
// and returns the custom metadata associated with the object.
func (gr GetResult) ExtractMetadata() (map[string]string, error) {
if gr.Err != nil {
return nil, gr.Err
}
metadata := make(map[string]string)
for k, v := range gr.Header {
if strings.HasPrefix(k, "X-Object-Meta-") {
key := strings.TrimPrefix(k, "X-Object-Meta-")
metadata[key] = v[0]
}
}
return metadata, nil
}
// CreateHeader represents the headers returned in the response from a Create request.
type CreateHeader struct {
ContentLength int64 `mapstructure:"Content-Length"`
ContentType string `mapstructure:"Content-Type"`
Date time.Time `mapstructure:"-"`
ETag string `mapstructure:"Etag"`
LastModified time.Time `mapstructure:"-"`
TransID string `mapstructure:"X-Trans-Id"`
}
// CreateResult represents the result of a create operation.
type CreateResult struct {
gophercloud.HeaderResult
}
// Extract will return a struct of headers returned from a call to Create. To obtain
// a map of headers, call the ExtractHeader method on the CreateResult.
func (cr CreateResult) Extract() (CreateHeader, error) {
var ch CreateHeader
if cr.Err != nil {
return ch, cr.Err
}
if err := gophercloud.DecodeHeader(cr.Header, &ch); err != nil {
return ch, err
}
if date, ok := cr.Header["Date"]; ok && len(date) > 0 {
t, err := time.Parse(time.RFC1123, cr.Header["Date"][0])
if err != nil {
return ch, err
}
ch.Date = t
}
if date, ok := cr.Header["Last-Modified"]; ok && len(date) > 0 {
t, err := time.Parse(time.RFC1123, cr.Header["Last-Modified"][0])
if err != nil {
return ch, err
}
ch.LastModified = t
}
return ch, nil
}
// UpdateHeader represents the headers returned in the response from a Update request.
type UpdateHeader struct {
ContentLength int64 `mapstructure:"Content-Length"`
ContentType string `mapstructure:"Content-Type"`
Date time.Time `mapstructure:"-"`
TransID string `mapstructure:"X-Trans-Id"`
}
// UpdateResult represents the result of an update operation.
type UpdateResult struct {
gophercloud.HeaderResult
}
// Extract will return a struct of headers returned from a call to Update. To obtain
// a map of headers, call the ExtractHeader method on the UpdateResult.
func (ur UpdateResult) Extract() (UpdateHeader, error) {
var uh UpdateHeader
if ur.Err != nil {
return uh, ur.Err
}
if err := gophercloud.DecodeHeader(ur.Header, &uh); err != nil {
return uh, err
}
if date, ok := ur.Header["Date"]; ok && len(date) > 0 {
t, err := time.Parse(time.RFC1123, ur.Header["Date"][0])
if err != nil {
return uh, err
}
uh.Date = t
}
return uh, nil
}
// DeleteHeader represents the headers returned in the response from a Delete request.
type DeleteHeader struct {
ContentLength int64 `mapstructure:"Content-Length"`
ContentType string `mapstructure:"Content-Type"`
Date time.Time `mapstructure:"-"`
TransID string `mapstructure:"X-Trans-Id"`
}
// DeleteResult represents the result of a delete operation.
type DeleteResult struct {
gophercloud.HeaderResult
}
// Extract will return a struct of headers returned from a call to Delete. To obtain
// a map of headers, call the ExtractHeader method on the DeleteResult.
func (dr DeleteResult) Extract() (DeleteHeader, error) {
var dh DeleteHeader
if dr.Err != nil {
return dh, dr.Err
}
if err := gophercloud.DecodeHeader(dr.Header, &dh); err != nil {
return dh, err
}
if date, ok := dr.Header["Date"]; ok && len(date) > 0 {
t, err := time.Parse(time.RFC1123, dr.Header["Date"][0])
if err != nil {
return dh, err
}
dh.Date = t
}
return dh, nil
}
// CopyHeader represents the headers returned in the response from a Copy request.
type CopyHeader struct {
ContentLength int64 `mapstructure:"Content-Length"`
ContentType string `mapstructure:"Content-Type"`
CopiedFrom string `mapstructure:"X-Copied-From"`
CopiedFromLastModified time.Time `mapstructure:"-"`
Date time.Time `mapstructure:"-"`
ETag string `mapstructure:"Etag"`
LastModified time.Time `mapstructure:"-"`
TransID string `mapstructure:"X-Trans-Id"`
}
// CopyResult represents the result of a copy operation.
type CopyResult struct {
gophercloud.HeaderResult
}
// Extract will return a struct of headers returned from a call to Copy. To obtain
// a map of headers, call the ExtractHeader method on the CopyResult.
func (cr CopyResult) Extract() (CopyHeader, error) {
var ch CopyHeader
if cr.Err != nil {
return ch, cr.Err
}
if err := gophercloud.DecodeHeader(cr.Header, &ch); err != nil {
return ch, err
}
if date, ok := cr.Header["Date"]; ok && len(date) > 0 {
t, err := time.Parse(time.RFC1123, cr.Header["Date"][0])
if err != nil {
return ch, err
}
ch.Date = t
}
if date, ok := cr.Header["Last-Modified"]; ok && len(date) > 0 {
t, err := time.Parse(time.RFC1123, cr.Header["Last-Modified"][0])
if err != nil {
return ch, err
}
ch.LastModified = t
}
if date, ok := cr.Header["X-Copied-From-Last-Modified"]; ok && len(date) > 0 {
t, err := time.Parse(time.RFC1123, cr.Header["X-Copied-From-Last-Modified"][0])
if err != nil {
return ch, err
}
ch.CopiedFromLastModified = t
}
return ch, nil
}

View File

@@ -0,0 +1,33 @@
package objects
import (
"github.com/rackspace/gophercloud"
)
func listURL(c *gophercloud.ServiceClient, container string) string {
return c.ServiceURL(container)
}
func copyURL(c *gophercloud.ServiceClient, container, object string) string {
return c.ServiceURL(container, object)
}
func createURL(c *gophercloud.ServiceClient, container, object string) string {
return copyURL(c, container, object)
}
func getURL(c *gophercloud.ServiceClient, container, object string) string {
return copyURL(c, container, object)
}
func deleteURL(c *gophercloud.ServiceClient, container, object string) string {
return copyURL(c, container, object)
}
func downloadURL(c *gophercloud.ServiceClient, container, object string) string {
return copyURL(c, container, object)
}
func updateURL(c *gophercloud.ServiceClient, container, object string) string {
return copyURL(c, container, object)
}