diff --git a/Gopkg.lock b/Gopkg.lock index f051eff0500..611cd55874f 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -201,7 +201,7 @@ [[projects]] branch = "master" - digest = "1:ad26de5429bd2d4dec5ba169e758795db2f6b054a3fd15dee10b029a5f371992" + digest = "1:56c12cdd01d62ed0836da8d8756f7331b3544dd0dd023f1ab1db69c204776a1c" name = "github.com/gophercloud/gophercloud" packages = [ ".", @@ -218,6 +218,9 @@ "openstack/networking/v2/networks", "openstack/networking/v2/ports", "openstack/networking/v2/subnets", + "openstack/objectstorage/v1/accounts", + "openstack/objectstorage/v1/containers", + "openstack/objectstorage/v1/objects", "openstack/utils", "pagination", ] @@ -804,6 +807,8 @@ "github.com/gophercloud/gophercloud/openstack/networking/v2/networks", "github.com/gophercloud/gophercloud/openstack/networking/v2/ports", "github.com/gophercloud/gophercloud/openstack/networking/v2/subnets", + "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers", + "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects", "github.com/gophercloud/utils/openstack/clientconfig", "github.com/gregjones/httpcache", "github.com/gregjones/httpcache/diskcache", diff --git a/pkg/destroy/openstack/openstack_deprovision.go b/pkg/destroy/openstack/openstack_deprovision.go index 56eb4fbb009..0681fbd3908 100644 --- a/pkg/destroy/openstack/openstack_deprovision.go +++ b/pkg/destroy/openstack/openstack_deprovision.go @@ -15,6 +15,8 @@ import ( "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" "github.com/gophercloud/gophercloud/openstack/networking/v2/ports" "github.com/gophercloud/gophercloud/openstack/networking/v2/subnets" + "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers" + "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects" "github.com/gophercloud/utils/openstack/clientconfig" "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/util/wait" @@ -105,6 +107,7 @@ func populateDeleteFuncs(funcs map[string]deleteFunc) { funcs["deleteRouters"] = deleteRouters funcs["deleteSubnets"] = deleteSubnets funcs["deleteNetworks"] = deleteNetworks + funcs["deleteContainers"] = deleteContainers } // filterObjects will do client-side filtering given an appropriately filled out @@ -411,6 +414,70 @@ func deleteNetworks(opts *clientconfig.ClientOpts, filter Filter, logger logrus. return len(allNetworks) == 0, nil } +func deleteContainers(opts *clientconfig.ClientOpts, filter Filter, logger logrus.FieldLogger) (bool, error) { + logger.Debug("Deleting openstack containers") + defer logger.Debugf("Exiting deleting openstack containers") + + conn, err := clientconfig.NewServiceClient("object-store", opts) + if err != nil { + logger.Fatalf("%v", err) + os.Exit(1) + } + listOpts := containers.ListOpts{Full: false} + + allPages, err := containers.List(conn, listOpts).AllPages() + if err != nil { + logger.Fatalf("%v", err) + os.Exit(1) + } + + allContainers, err := containers.ExtractNames(allPages) + if err != nil { + logger.Fatalf("%v", err) + os.Exit(1) + } + for _, container := range allContainers { + metadata, err := containers.Get(conn, container, nil).ExtractMetadata() + if err != nil { + logger.Fatalf("%v", err) + os.Exit(1) + } + for key, val := range filter { + // Swift mangles the case so tectonicClusterID becomes + // Tectonicclusterid in the X-Container-Meta- HEAD output + titlekey := strings.Title(strings.ToLower(key)) + if metadata[titlekey] == val { + listOpts := objects.ListOpts{Full: false} + allPages, err := objects.List(conn, container, listOpts).AllPages() + if err != nil { + logger.Fatalf("%v", err) + os.Exit(1) + } + allObjects, err := objects.ExtractNames(allPages) + if err != nil { + logger.Fatalf("%v", err) + os.Exit(1) + } + for _, object := range allObjects { + logger.Debugf("Deleting object: %+v\n", object) + _, err = objects.Delete(conn, container, object, nil).Extract() + if err != nil { + logger.Fatalf("%v", err) + os.Exit(1) + } + } + logger.Debugf("Deleting container: %+v\n", container) + _, err = containers.Delete(conn, container).Extract() + if err != nil { + logger.Fatalf("%v", err) + os.Exit(1) + } + } + } + } + return true, nil +} + // New returns an OpenStack destroyer from ClusterMetadata. func New(logger logrus.FieldLogger, metadata *types.ClusterMetadata) (destroy.Destroyer, error) { return &ClusterUninstaller{ diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/doc.go new file mode 100644 index 00000000000..0fa1c083a26 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/doc.go @@ -0,0 +1,29 @@ +/* +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. + +Example to Get an Account + + account, err := accounts.Get(objectStorageClient, nil).Extract() + fmt.Printf("%+v\n", account) + +Example to Update an Account + + metadata := map[string]string{ + "some": "metadata", + } + + updateOpts := accounts.UpdateOpts{ + Metadata: metadata, + } + + updateResult, err := accounts.Update(objectStorageClient, updateOpts).Extract() + fmt.Printf("%+v\n", updateResult) + +*/ +package accounts diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/requests.go new file mode 100644 index 00000000000..452a331c74c --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/requests.go @@ -0,0 +1,100 @@ +package accounts + +import "github.com/gophercloud/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 +// Extract method on the GetResult. +func Get(c *gophercloud.ServiceClient, opts GetOptsBuilder) (r GetResult) { + h := make(map[string]string) + if opts != nil { + headers, err := opts.ToAccountGetMap() + if err != nil { + r.Err = err + return + } + for k, v := range headers { + h[k] = v + } + } + resp, err := c.Head(getURL(c), &gophercloud.RequestOpts{ + MoreHeaders: h, + OkCodes: []int{204}, + }) + if resp != nil { + r.Header = resp.Header + } + r.Err = err + return +} + +// 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) (r UpdateResult) { + h := make(map[string]string) + if opts != nil { + headers, err := opts.ToAccountUpdateMap() + if err != nil { + r.Err = err + return + } + 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 { + r.Header = resp.Header + } + r.Err = err + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/results.go new file mode 100644 index 00000000000..10661e67157 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/results.go @@ -0,0 +1,180 @@ +package accounts + +import ( + "encoding/json" + "strconv" + "strings" + "time" + + "github.com/gophercloud/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 int64 `json:"-"` + ContentType string `json:"Content-Type"` + TransID string `json:"X-Trans-Id"` + Date time.Time `json:"-"` +} + +func (r *UpdateHeader) UnmarshalJSON(b []byte) error { + type tmp UpdateHeader + var s struct { + tmp + ContentLength string `json:"Content-Length"` + Date gophercloud.JSONRFC1123 `json:"Date"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + + *r = UpdateHeader(s.tmp) + + switch s.ContentLength { + case "": + r.ContentLength = 0 + default: + r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64) + if err != nil { + return err + } + } + + r.Date = time.Time(s.Date) + + return err +} + +// Extract will return a struct of headers returned from a call to Get. To +// obtain a map of headers, call the Extract method on the GetResult. +func (r UpdateResult) Extract() (*UpdateHeader, error) { + var s *UpdateHeader + err := r.ExtractInto(&s) + return s, err +} + +// GetHeader represents the headers returned in the response from a Get request. +type GetHeader struct { + BytesUsed int64 `json:"-"` + QuotaBytes *int64 `json:"-"` + ContainerCount int64 `json:"-"` + ContentLength int64 `json:"-"` + ObjectCount int64 `json:"-"` + ContentType string `json:"Content-Type"` + TransID string `json:"X-Trans-Id"` + TempURLKey string `json:"X-Account-Meta-Temp-URL-Key"` + TempURLKey2 string `json:"X-Account-Meta-Temp-URL-Key-2"` + Date time.Time `json:"-"` +} + +func (r *GetHeader) UnmarshalJSON(b []byte) error { + type tmp GetHeader + var s struct { + tmp + BytesUsed string `json:"X-Account-Bytes-Used"` + QuotaBytes string `json:"X-Account-Meta-Quota-Bytes"` + ContentLength string `json:"Content-Length"` + ContainerCount string `json:"X-Account-Container-Count"` + ObjectCount string `json:"X-Account-Object-Count"` + Date string `json:"Date"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + + *r = GetHeader(s.tmp) + + switch s.BytesUsed { + case "": + r.BytesUsed = 0 + default: + r.BytesUsed, err = strconv.ParseInt(s.BytesUsed, 10, 64) + if err != nil { + return err + } + } + + switch s.QuotaBytes { + case "": + r.QuotaBytes = nil + default: + v, err := strconv.ParseInt(s.QuotaBytes, 10, 64) + if err != nil { + return err + } + r.QuotaBytes = &v + } + + switch s.ContentLength { + case "": + r.ContentLength = 0 + default: + r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64) + if err != nil { + return err + } + } + + switch s.ObjectCount { + case "": + r.ObjectCount = 0 + default: + r.ObjectCount, err = strconv.ParseInt(s.ObjectCount, 10, 64) + if err != nil { + return err + } + } + + switch s.ContainerCount { + case "": + r.ContainerCount = 0 + default: + r.ContainerCount, err = strconv.ParseInt(s.ContainerCount, 10, 64) + if err != nil { + return err + } + } + + if s.Date != "" { + r.Date, err = time.Parse(time.RFC1123, s.Date) + } + + return err +} + +// 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. +func (r GetResult) Extract() (*GetHeader, error) { + var s *GetHeader + err := r.ExtractInto(&s) + return s, err +} + +// ExtractMetadata is a function that takes a GetResult (of type *http.Response) +// and returns the custom metatdata associated with the account. +func (r GetResult) ExtractMetadata() (map[string]string, error) { + if r.Err != nil { + return nil, r.Err + } + + metadata := make(map[string]string) + for k, v := range r.Header { + if strings.HasPrefix(k, "X-Account-Meta-") { + key := strings.TrimPrefix(k, "X-Account-Meta-") + metadata[key] = v[0] + } + } + return metadata, nil +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/urls.go new file mode 100644 index 00000000000..71540b1daf3 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/urls.go @@ -0,0 +1,11 @@ +package accounts + +import "github.com/gophercloud/gophercloud" + +func getURL(c *gophercloud.ServiceClient) string { + return c.Endpoint +} + +func updateURL(c *gophercloud.ServiceClient) string { + return getURL(c) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/doc.go new file mode 100644 index 00000000000..9e5f6641980 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/doc.go @@ -0,0 +1,92 @@ +/* +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). + +Note: When referencing the Object Storage API docs, some of the API actions +are listed under "accounts" rather than "containers". This was an intentional +design in Gophercloud to make some container actions feel more natural. + +Example to List Containers + + listOpts := containers.ListOpts{ + Full: true, + } + + allPages, err := containers.List(objectStorageClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allContainers, err := containers.ExtractInfo(allPages) + if err != nil { + panic(err) + } + + for _, container := range allContainers { + fmt.Printf("%+v\n", container) + } + +Example to List Only Container Names + + listOpts := containers.ListOpts{ + Full: false, + } + + allPages, err := containers.List(objectStorageClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allContainers, err := containers.ExtractNames(allPages) + if err != nil { + panic(err) + } + + for _, container := range allContainers { + fmt.Printf("%+v\n", container) + } + +Example to Create a Container + + createOpts := containers.CreateOpts{ + ContentType: "application/json", + Metadata: map[string]string{ + "foo": "bar", + }, + } + + container, err := containers.Create(objectStorageClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Container + + containerName := "my_container" + + updateOpts := containers.UpdateOpts{ + Metadata: map[string]string{ + "bar": "baz", + }, + } + + container, err := containers.Update(objectStorageClient, containerName, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Container + + containerName := "my_container" + + container, err := containers.Delete(objectStorageClient, containerName).Extract() + if err != nil { + panic(err) + } +*/ +package containers diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/requests.go new file mode 100644 index 00000000000..89aa996839d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/requests.go @@ -0,0 +1,224 @@ +package containers + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/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) + return opts.Full, q.String(), err +} + +// 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"} + } + } + + pager := pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + p := ContainerPage{pagination.MarkerPageBase{PageResult: r}} + p.MarkerPageBase.Owner = p + return p + }) + 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"` + HistoryLocation string `h:"X-History-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) (r CreateResult) { + h := make(map[string]string) + if opts != nil { + headers, err := opts.ToContainerCreateMap() + if err != nil { + r.Err = err + return + } + 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 { + r.Header = resp.Header + resp.Body.Close() + } + r.Err = err + return +} + +// Delete is a function that deletes a container. +func Delete(c *gophercloud.ServiceClient, containerName string) (r DeleteResult) { + _, r.Err = c.Delete(deleteURL(c, containerName), nil) + return +} + +// 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"` + RemoveHistoryLocation string `h:"X-Remove-History-Location"` + HistoryLocation string `h:"X-History-Location"` +} + +// ToContainerUpdateMap formats a UpdateOpts 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) (r UpdateResult) { + h := make(map[string]string) + if opts != nil { + headers, err := opts.ToContainerUpdateMap() + if err != nil { + r.Err = err + return + } + + 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 { + r.Header = resp.Header + } + r.Err = err + return +} + +// GetOptsBuilder allows extensions to add additional parameters to the Get +// request. +type GetOptsBuilder interface { + ToContainerGetMap() (map[string]string, error) +} + +// GetOpts is a structure that holds options for listing containers. +type GetOpts struct { + Newest bool `h:"X-Newest"` +} + +// ToContainerGetMap formats a GetOpts into a map of headers. +func (opts GetOpts) ToContainerGetMap() (map[string]string, error) { + return gophercloud.BuildHeaders(opts) +} + +// Get is a function that retrieves the metadata of a container. To extract just +// the custom metadata, pass the GetResult response to the ExtractMetadata +// function. +func Get(c *gophercloud.ServiceClient, containerName string, opts GetOptsBuilder) (r GetResult) { + h := make(map[string]string) + if opts != nil { + headers, err := opts.ToContainerGetMap() + if err != nil { + r.Err = err + return + } + + for k, v := range headers { + h[k] = v + } + } + resp, err := c.Head(getURL(c, containerName), &gophercloud.RequestOpts{ + MoreHeaders: h, + OkCodes: []int{200, 204}, + }) + if resp != nil { + r.Header = resp.Header + } + r.Err = err + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/results.go new file mode 100644 index 00000000000..cce2190ff9f --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/results.go @@ -0,0 +1,344 @@ +package containers + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// Container represents a container resource. +type Container struct { + // The total number of bytes stored in the container. + Bytes int64 `json:"bytes"` + + // The total number of objects stored in the container. + Count int64 `json:"count"` + + // The name of the container. + Name string `json:"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) + return len(names) == 0, err +} + +// 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(r pagination.Page) ([]Container, error) { + var s []Container + err := (r.(ContainerPage)).ExtractInto(&s) + return s, err +} + +// 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 `json:"Accept-Ranges"` + BytesUsed int64 `json:"-"` + ContentLength int64 `json:"-"` + ContentType string `json:"Content-Type"` + Date time.Time `json:"-"` + ObjectCount int64 `json:"-"` + Read []string `json:"-"` + TransID string `json:"X-Trans-Id"` + VersionsLocation string `json:"X-Versions-Location"` + HistoryLocation string `json:"X-History-Location"` + Write []string `json:"-"` + StoragePolicy string `json:"X-Storage-Policy"` +} + +func (r *GetHeader) UnmarshalJSON(b []byte) error { + type tmp GetHeader + var s struct { + tmp + BytesUsed string `json:"X-Container-Bytes-Used"` + ContentLength string `json:"Content-Length"` + ObjectCount string `json:"X-Container-Object-Count"` + Write string `json:"X-Container-Write"` + Read string `json:"X-Container-Read"` + Date gophercloud.JSONRFC1123 `json:"Date"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + + *r = GetHeader(s.tmp) + + switch s.BytesUsed { + case "": + r.BytesUsed = 0 + default: + r.BytesUsed, err = strconv.ParseInt(s.BytesUsed, 10, 64) + if err != nil { + return err + } + } + + switch s.ContentLength { + case "": + r.ContentLength = 0 + default: + r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64) + if err != nil { + return err + } + } + + switch s.ObjectCount { + case "": + r.ObjectCount = 0 + default: + r.ObjectCount, err = strconv.ParseInt(s.ObjectCount, 10, 64) + if err != nil { + return err + } + } + + r.Read = strings.Split(s.Read, ",") + r.Write = strings.Split(s.Write, ",") + + r.Date = time.Time(s.Date) + + return err +} + +// 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. +func (r GetResult) Extract() (*GetHeader, error) { + var s *GetHeader + err := r.ExtractInto(&s) + return s, err +} + +// ExtractMetadata is a function that takes a GetResult (of type *http.Response) +// and returns the custom metadata associated with the container. +func (r GetResult) ExtractMetadata() (map[string]string, error) { + if r.Err != nil { + return nil, r.Err + } + metadata := make(map[string]string) + for k, v := range r.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 `json:"-"` + ContentType string `json:"Content-Type"` + Date time.Time `json:"-"` + TransID string `json:"X-Trans-Id"` +} + +func (r *CreateHeader) UnmarshalJSON(b []byte) error { + type tmp CreateHeader + var s struct { + tmp + ContentLength string `json:"Content-Length"` + Date gophercloud.JSONRFC1123 `json:"Date"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + + *r = CreateHeader(s.tmp) + + switch s.ContentLength { + case "": + r.ContentLength = 0 + default: + r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64) + if err != nil { + return err + } + } + + r.Date = time.Time(s.Date) + + return err +} + +// CreateResult represents the result of a create operation. To extract the +// the headers from the HTTP response, call its Extract method. +type CreateResult struct { + gophercloud.HeaderResult +} + +// Extract will return a struct of headers returned from a call to Create. +// To extract the headers from the HTTP response, call its Extract method. +func (r CreateResult) Extract() (*CreateHeader, error) { + var s *CreateHeader + err := r.ExtractInto(&s) + return s, err +} + +// UpdateHeader represents the headers returned in the response from a Update +// request. +type UpdateHeader struct { + ContentLength int64 `json:"-"` + ContentType string `json:"Content-Type"` + Date time.Time `json:"-"` + TransID string `json:"X-Trans-Id"` +} + +func (r *UpdateHeader) UnmarshalJSON(b []byte) error { + type tmp UpdateHeader + var s struct { + tmp + ContentLength string `json:"Content-Length"` + Date gophercloud.JSONRFC1123 `json:"Date"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + + *r = UpdateHeader(s.tmp) + + switch s.ContentLength { + case "": + r.ContentLength = 0 + default: + r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64) + if err != nil { + return err + } + } + + r.Date = time.Time(s.Date) + + return err +} + +// UpdateResult represents the result of an update operation. To extract the +// the headers from the HTTP response, call its Extract method. +type UpdateResult struct { + gophercloud.HeaderResult +} + +// Extract will return a struct of headers returned from a call to Update. +func (r UpdateResult) Extract() (*UpdateHeader, error) { + var s *UpdateHeader + err := r.ExtractInto(&s) + return s, err +} + +// DeleteHeader represents the headers returned in the response from a Delete +// request. +type DeleteHeader struct { + ContentLength int64 `json:"-"` + ContentType string `json:"Content-Type"` + Date time.Time `json:"-"` + TransID string `json:"X-Trans-Id"` +} + +func (r *DeleteHeader) UnmarshalJSON(b []byte) error { + type tmp DeleteHeader + var s struct { + tmp + ContentLength string `json:"Content-Length"` + Date gophercloud.JSONRFC1123 `json:"Date"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + + *r = DeleteHeader(s.tmp) + + switch s.ContentLength { + case "": + r.ContentLength = 0 + default: + r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64) + if err != nil { + return err + } + } + + r.Date = time.Time(s.Date) + + return err +} + +// DeleteResult represents the result of a delete operation. To extract the +// the headers from the HTTP response, call its Extract method. +type DeleteResult struct { + gophercloud.HeaderResult +} + +// Extract will return a struct of headers returned from a call to Delete. +func (r DeleteResult) Extract() (*DeleteHeader, error) { + var s *DeleteHeader + err := r.ExtractInto(&s) + return s, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/urls.go new file mode 100644 index 00000000000..9b380470dd7 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers/urls.go @@ -0,0 +1,23 @@ +package containers + +import "github.com/gophercloud/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) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/doc.go new file mode 100644 index 00000000000..e9b4b8a9f32 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/doc.go @@ -0,0 +1,106 @@ +/* +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. + +Note: When referencing the Object Storage API docs, some of the API actions +are listed under "containers" rather than "objects". This was an intentional +design in Gophercloud to make some object actions feel more natural. + +Example to List Objects + + containerName := "my_container" + + listOpts := objects.ListOpts{ + Full: true, + } + + allPages, err := objects.List(objectStorageClient, containerName, listOpts).AllPages() + if err != nil { + panic(err) + } + + allObjects, err := objects.ExtractInfo(allPages) + if err != nil { + panic(err) + } + + for _, object := range allObjects { + fmt.Printf("%+v\n", object) + } + +Example to List Object Names + + containerName := "my_container" + + listOpts := objects.ListOpts{ + Full: false, + } + + allPages, err := objects.List(objectStorageClient, containerName, listOpts).AllPages() + if err != nil { + panic(err) + } + + allObjects, err := objects.ExtractNames(allPages) + if err != nil { + panic(err) + } + + for _, object := range allObjects { + fmt.Printf("%+v\n", object) + } + +Example to Create an Object + + content := "some object content" + objectName := "my_object" + containerName := "my_container" + + createOpts := objects.CreateOpts{ + ContentType: "text/plain" + Content: strings.NewReader(content), + } + + object, err := objects.Create(objectStorageClient, containerName, objectName, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Copy an Object + + objectName := "my_object" + containerName := "my_container" + + copyOpts := objects.CopyOpts{ + Destination: "/newContainer/newObject", + } + + object, err := objects.Copy(objectStorageClient, containerName, objectName, copyOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete an Object + + objectName := "my_object" + containerName := "my_container" + + object, err := objects.Delete(objectStorageClient, containerName, objectName).Extract() + if err != nil { + panic(err) + } + +Example to Download an Object's Data + + objectName := "my_object" + containerName := "my_container" + + object := objects.Download(objectStorageClient, containerName, objectName, nil) + content, err := object.ExtractContent() + if err != nil { + panic(err) + } +*/ +package objects diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/errors.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/errors.go new file mode 100644 index 00000000000..5c4ae44d317 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/errors.go @@ -0,0 +1,13 @@ +package objects + +import "github.com/gophercloud/gophercloud" + +// ErrWrongChecksum is the error when the checksum generated for an object +// doesn't match the ETAG header. +type ErrWrongChecksum struct { + gophercloud.BaseError +} + +func (e ErrWrongChecksum) Error() string { + return "Local checksum does not match API ETag header" +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/requests.go new file mode 100644 index 00000000000..7325cd7d0b9 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/requests.go @@ -0,0 +1,499 @@ +package objects + +import ( + "bytes" + "crypto/hmac" + "crypto/md5" + "crypto/sha1" + "fmt" + "io" + "io/ioutil" + "strings" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts" + "github.com/gophercloud/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) + return opts.Full, q.String(), err +} + +// 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"} + } + } + + pager := pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + p := ObjectPage{pagination.MarkerPageBase{PageResult: r}} + p.MarkerPageBase.Owner = p + return p + }) + 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"` + Newest bool `h:"X-Newest"` + 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) (r DownloadResult) { + url := downloadURL(c, containerName, objectName) + h := make(map[string]string) + if opts != nil { + headers, query, err := opts.ToObjectDownloadParams() + if err != nil { + r.Err = err + return + } + for k, v := range headers { + h[k] = v + } + url += query + } + + resp, err := c.Get(url, nil, &gophercloud.RequestOpts{ + MoreHeaders: h, + OkCodes: []int{200, 206, 304}, + }) + if resp != nil { + r.Header = resp.Header + r.Body = resp.Body + } + r.Err = err + return +} + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToObjectCreateParams() (io.Reader, map[string]string, string, error) +} + +// CreateOpts is a structure that holds parameters for creating an object. +type CreateOpts struct { + Content io.Reader + Metadata map[string]string + NoETag bool + CacheControl string `h:"Cache-Control"` + ContentDisposition string `h:"Content-Disposition"` + ContentEncoding string `h:"Content-Encoding"` + 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() (io.Reader, map[string]string, string, error) { + q, err := gophercloud.BuildQueryString(opts) + if err != nil { + return nil, nil, "", err + } + h, err := gophercloud.BuildHeaders(opts) + if err != nil { + return nil, nil, "", err + } + + for k, v := range opts.Metadata { + h["X-Object-Meta-"+k] = v + } + + if opts.NoETag { + delete(h, "etag") + return opts.Content, h, q.String(), nil + } + + if h["ETag"] != "" { + return opts.Content, h, q.String(), nil + } + + // When we're dealing with big files an io.ReadSeeker allows us to efficiently calculate + // the md5 sum. An io.Reader is only readable once which means we have to copy the entire + // file content into memory first. + readSeeker, isReadSeeker := opts.Content.(io.ReadSeeker) + if !isReadSeeker { + data, err := ioutil.ReadAll(opts.Content) + if err != nil { + return nil, nil, "", err + } + readSeeker = bytes.NewReader(data) + } + + hash := md5.New() + // io.Copy into md5 is very efficient as it's done in small chunks. + if _, err := io.Copy(hash, readSeeker); err != nil { + return nil, nil, "", err + } + readSeeker.Seek(0, io.SeekStart) + + h["ETag"] = fmt.Sprintf("%x", hash.Sum(nil)) + + return readSeeker, 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, opts CreateOptsBuilder) (r CreateResult) { + url := createURL(c, containerName, objectName) + h := make(map[string]string) + var b io.Reader + if opts != nil { + tmpB, headers, query, err := opts.ToObjectCreateParams() + if err != nil { + r.Err = err + return + } + for k, v := range headers { + h[k] = v + } + url += query + b = tmpB + } + + resp, err := c.Put(url, nil, nil, &gophercloud.RequestOpts{ + RawBody: b, + MoreHeaders: h, + }) + r.Err = err + if resp != nil { + r.Header = resp.Header + } + return +} + +// 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:"true"` +} + +// ToObjectCopyMap formats a CopyOpts into a map of headers. +func (opts CopyOpts) ToObjectCopyMap() (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 +} + +// Copy is a function that copies one object to another. +func Copy(c *gophercloud.ServiceClient, containerName, objectName string, opts CopyOptsBuilder) (r CopyResult) { + h := make(map[string]string) + headers, err := opts.ToObjectCopyMap() + if err != nil { + r.Err = err + return + } + + 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 { + r.Header = resp.Header + } + r.Err = err + return +} + +// 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) + return q.String(), err +} + +// Delete is a function that deletes an object. +func Delete(c *gophercloud.ServiceClient, containerName, objectName string, opts DeleteOptsBuilder) (r DeleteResult) { + url := deleteURL(c, containerName, objectName) + if opts != nil { + query, err := opts.ToObjectDeleteQuery() + if err != nil { + r.Err = err + return + } + url += query + } + resp, err := c.Delete(url, nil) + if resp != nil { + r.Header = resp.Header + } + r.Err = err + return +} + +// GetOptsBuilder allows extensions to add additional parameters to the +// Get request. +type GetOptsBuilder interface { + ToObjectGetParams() (map[string]string, string, error) +} + +// GetOpts is a structure that holds parameters for getting an object's +// metadata. +type GetOpts struct { + Newest bool `h:"X-Newest"` + Expires string `q:"expires"` + Signature string `q:"signature"` +} + +// ToObjectGetParams formats a GetOpts into a query string and a map of headers. +func (opts GetOpts) ToObjectGetParams() (map[string]string, string, error) { + q, err := gophercloud.BuildQueryString(opts) + if err != nil { + return nil, "", err + } + h, err := gophercloud.BuildHeaders(opts) + if err != nil { + return nil, q.String(), err + } + return h, q.String(), nil +} + +// Get is a function that retrieves the metadata of an object. To extract just +// the custom metadata, pass the GetResult response to the ExtractMetadata +// function. +func Get(c *gophercloud.ServiceClient, containerName, objectName string, opts GetOptsBuilder) (r GetResult) { + url := getURL(c, containerName, objectName) + h := make(map[string]string) + if opts != nil { + headers, query, err := opts.ToObjectGetParams() + if err != nil { + r.Err = err + return + } + for k, v := range headers { + h[k] = v + } + url += query + } + + resp, err := c.Head(url, &gophercloud.RequestOpts{ + MoreHeaders: h, + OkCodes: []int{200, 204}, + }) + if resp != nil { + r.Header = resp.Header + } + r.Err = err + return +} + +// 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) (r UpdateResult) { + h := make(map[string]string) + if opts != nil { + headers, err := opts.ToObjectUpdateMap() + if err != nil { + r.Err = err + return + } + + for k, v := range headers { + h[k] = v + } + } + url := updateURL(c, containerName, objectName) + resp, err := c.Post(url, nil, nil, &gophercloud.RequestOpts{ + MoreHeaders: h, + }) + if resp != nil { + r.Header = resp.Header + } + r.Err = err + return +} + +// 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 +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/results.go new file mode 100644 index 00000000000..dd7c7044d0e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/results.go @@ -0,0 +1,580 @@ +package objects + +import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/url" + "strconv" + "strings" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// 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"` + + // ContentType is the content type of the object. + ContentType string `json:"content_type"` + + // Hash represents the MD5 checksum value of the object's content. + Hash string `json:"hash"` + + // LastModified is the time the object was last modified. + LastModified time.Time `json:"-"` + + // Name is the unique name for the object. + Name string `json:"name"` + + // Subdir denotes if the result contains a subdir. + Subdir string `json:"subdir"` +} + +func (r *Object) UnmarshalJSON(b []byte) error { + type tmp Object + var s *struct { + tmp + LastModified string `json:"last_modified"` + } + + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + + *r = Object(s.tmp) + + if s.LastModified != "" { + t, err := time.Parse(gophercloud.RFC3339MilliNoZ, s.LastModified) + if err != nil { + t, err = time.Parse(gophercloud.RFC3339Milli, s.LastModified) + if err != nil { + return err + } + } + r.LastModified = t + } + + return nil +} + +// ObjectPage is a single page of objects that is returned from a call to the +// 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) + return len(names) == 0, err +} + +// LastMarker returns the last object name in a ListResult. +func (r ObjectPage) LastMarker() (string, error) { + return extractLastMarker(r) +} + +// ExtractInfo is a function that takes a page of objects and returns their +// full information. +func ExtractInfo(r pagination.Page) ([]Object, error) { + var s []Object + err := (r.(ObjectPage)).ExtractInto(&s) + return s, err +} + +// ExtractNames is a function that takes a page of objects and returns only +// their names. +func ExtractNames(r pagination.Page) ([]string, error) { + casted := r.(ObjectPage) + ct := casted.Header.Get("Content-Type") + switch { + case strings.HasPrefix(ct, "application/json"): + parsed, err := ExtractInfo(r) + if err != nil { + return nil, err + } + + names := make([]string, 0, len(parsed)) + for _, object := range parsed { + if object.Subdir != "" { + names = append(names, object.Subdir) + } else { + names = append(names, object.Name) + } + } + + return names, nil + case strings.HasPrefix(ct, "text/plain"): + names := make([]string, 0, 50) + + body := string(r.(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 `json:"Accept-Ranges"` + ContentDisposition string `json:"Content-Disposition"` + ContentEncoding string `json:"Content-Encoding"` + ContentLength int64 `json:"-"` + ContentType string `json:"Content-Type"` + Date time.Time `json:"-"` + DeleteAt time.Time `json:"-"` + ETag string `json:"Etag"` + LastModified time.Time `json:"-"` + ObjectManifest string `json:"X-Object-Manifest"` + StaticLargeObject bool `json:"-"` + TransID string `json:"X-Trans-Id"` +} + +func (r *DownloadHeader) UnmarshalJSON(b []byte) error { + type tmp DownloadHeader + var s struct { + tmp + ContentLength string `json:"Content-Length"` + Date gophercloud.JSONRFC1123 `json:"Date"` + DeleteAt gophercloud.JSONUnix `json:"X-Delete-At"` + LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"` + StaticLargeObject interface{} `json:"X-Static-Large-Object"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + + *r = DownloadHeader(s.tmp) + + switch s.ContentLength { + case "": + r.ContentLength = 0 + default: + r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64) + if err != nil { + return err + } + } + + switch t := s.StaticLargeObject.(type) { + case string: + if t == "True" || t == "true" { + r.StaticLargeObject = true + } + case bool: + r.StaticLargeObject = t + } + + r.Date = time.Time(s.Date) + r.DeleteAt = time.Time(s.DeleteAt) + r.LastModified = time.Time(s.LastModified) + + return nil +} + +// 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. +func (r DownloadResult) Extract() (*DownloadHeader, error) { + var s *DownloadHeader + err := r.ExtractInto(&s) + return s, err +} + +// 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 (r *DownloadResult) ExtractContent() ([]byte, error) { + if r.Err != nil { + return nil, r.Err + } + defer r.Body.Close() + body, err := ioutil.ReadAll(r.Body) + if err != nil { + return nil, err + } + r.Body.Close() + return body, nil +} + +// GetHeader represents the headers returned in the response from a Get request. +type GetHeader struct { + ContentDisposition string `json:"Content-Disposition"` + ContentEncoding string `json:"Content-Encoding"` + ContentLength int64 `json:"-"` + ContentType string `json:"Content-Type"` + Date time.Time `json:"-"` + DeleteAt time.Time `json:"-"` + ETag string `json:"Etag"` + LastModified time.Time `json:"-"` + ObjectManifest string `json:"X-Object-Manifest"` + StaticLargeObject bool `json:"-"` + TransID string `json:"X-Trans-Id"` +} + +func (r *GetHeader) UnmarshalJSON(b []byte) error { + type tmp GetHeader + var s struct { + tmp + ContentLength string `json:"Content-Length"` + Date gophercloud.JSONRFC1123 `json:"Date"` + DeleteAt gophercloud.JSONUnix `json:"X-Delete-At"` + LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"` + StaticLargeObject interface{} `json:"X-Static-Large-Object"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + + *r = GetHeader(s.tmp) + + switch s.ContentLength { + case "": + r.ContentLength = 0 + default: + r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64) + if err != nil { + return err + } + } + + switch t := s.StaticLargeObject.(type) { + case string: + if t == "True" || t == "true" { + r.StaticLargeObject = true + } + case bool: + r.StaticLargeObject = t + } + + r.Date = time.Time(s.Date) + r.DeleteAt = time.Time(s.DeleteAt) + r.LastModified = time.Time(s.LastModified) + + return nil +} + +// 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. +func (r GetResult) Extract() (*GetHeader, error) { + var s *GetHeader + err := r.ExtractInto(&s) + return s, err +} + +// ExtractMetadata is a function that takes a GetResult (of type *http.Response) +// and returns the custom metadata associated with the object. +func (r GetResult) ExtractMetadata() (map[string]string, error) { + if r.Err != nil { + return nil, r.Err + } + metadata := make(map[string]string) + for k, v := range r.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 `json:"-"` + ContentType string `json:"Content-Type"` + Date time.Time `json:"-"` + ETag string `json:"Etag"` + LastModified time.Time `json:"-"` + TransID string `json:"X-Trans-Id"` +} + +func (r *CreateHeader) UnmarshalJSON(b []byte) error { + type tmp CreateHeader + var s struct { + tmp + ContentLength string `json:"Content-Length"` + Date gophercloud.JSONRFC1123 `json:"Date"` + LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + + *r = CreateHeader(s.tmp) + + switch s.ContentLength { + case "": + r.ContentLength = 0 + default: + r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64) + if err != nil { + return err + } + } + + r.Date = time.Time(s.Date) + r.LastModified = time.Time(s.LastModified) + + return nil +} + +// CreateResult represents the result of a create operation. +type CreateResult struct { + checksum string + gophercloud.HeaderResult +} + +// Extract will return a struct of headers returned from a call to Create. +func (r CreateResult) Extract() (*CreateHeader, error) { + //if r.Header.Get("ETag") != fmt.Sprintf("%x", localChecksum) { + // return nil, ErrWrongChecksum{} + //} + var s *CreateHeader + err := r.ExtractInto(&s) + return s, err +} + +// UpdateHeader represents the headers returned in the response from a +// Update request. +type UpdateHeader struct { + ContentLength int64 `json:"-"` + ContentType string `json:"Content-Type"` + Date time.Time `json:"-"` + TransID string `json:"X-Trans-Id"` +} + +func (r *UpdateHeader) UnmarshalJSON(b []byte) error { + type tmp UpdateHeader + var s struct { + tmp + ContentLength string `json:"Content-Length"` + Date gophercloud.JSONRFC1123 `json:"Date"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + + *r = UpdateHeader(s.tmp) + + switch s.ContentLength { + case "": + r.ContentLength = 0 + default: + r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64) + if err != nil { + return err + } + } + + r.Date = time.Time(s.Date) + + return nil +} + +// 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. +func (r UpdateResult) Extract() (*UpdateHeader, error) { + var s *UpdateHeader + err := r.ExtractInto(&s) + return s, err +} + +// DeleteHeader represents the headers returned in the response from a +// Delete request. +type DeleteHeader struct { + ContentLength int64 `json:"-"` + ContentType string `json:"Content-Type"` + Date time.Time `json:"-"` + TransID string `json:"X-Trans-Id"` +} + +func (r *DeleteHeader) UnmarshalJSON(b []byte) error { + type tmp DeleteHeader + var s struct { + tmp + ContentLength string `json:"Content-Length"` + Date gophercloud.JSONRFC1123 `json:"Date"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + + *r = DeleteHeader(s.tmp) + + switch s.ContentLength { + case "": + r.ContentLength = 0 + default: + r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64) + if err != nil { + return err + } + } + + r.Date = time.Time(s.Date) + + return nil +} + +// 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. +func (r DeleteResult) Extract() (*DeleteHeader, error) { + var s *DeleteHeader + err := r.ExtractInto(&s) + return s, err +} + +// CopyHeader represents the headers returned in the response from a +// Copy request. +type CopyHeader struct { + ContentLength int64 `json:"-"` + ContentType string `json:"Content-Type"` + CopiedFrom string `json:"X-Copied-From"` + CopiedFromLastModified time.Time `json:"-"` + Date time.Time `json:"-"` + ETag string `json:"Etag"` + LastModified time.Time `json:"-"` + TransID string `json:"X-Trans-Id"` +} + +func (r *CopyHeader) UnmarshalJSON(b []byte) error { + type tmp CopyHeader + var s struct { + tmp + 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, &s) + if err != nil { + return err + } + + *r = CopyHeader(s.tmp) + + switch s.ContentLength { + case "": + r.ContentLength = 0 + default: + r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64) + if err != nil { + return err + } + } + + r.Date = time.Time(s.Date) + r.CopiedFromLastModified = time.Time(s.CopiedFromLastModified) + r.LastModified = time.Time(s.LastModified) + + return nil +} + +// 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. +func (r CopyResult) Extract() (*CopyHeader, error) { + var s *CopyHeader + err := r.ExtractInto(&s) + return s, err +} + +// extractLastMarker is a function that takes a page of objects and returns the +// marker for the page. This can either be a subdir or the last object's name. +func extractLastMarker(r pagination.Page) (string, error) { + casted := r.(ObjectPage) + + // If a delimiter was requested, check if a subdir exists. + queryParams, err := url.ParseQuery(casted.URL.RawQuery) + if err != nil { + return "", err + } + + var delimeter bool + if v, ok := queryParams["delimiter"]; ok && len(v) > 0 { + delimeter = true + } + + ct := casted.Header.Get("Content-Type") + switch { + case strings.HasPrefix(ct, "application/json"): + parsed, err := ExtractInfo(r) + if err != nil { + return "", err + } + + var lastObject Object + if len(parsed) > 0 { + lastObject = parsed[len(parsed)-1] + } + + if !delimeter { + return lastObject.Name, nil + } + + if lastObject.Name != "" { + return lastObject.Name, nil + } + + return lastObject.Subdir, nil + case strings.HasPrefix(ct, "text/plain"): + names := make([]string, 0, 50) + + body := string(r.(ObjectPage).Body.([]uint8)) + for _, name := range strings.Split(body, "\n") { + if len(name) > 0 { + names = append(names, name) + } + } + + return names[len(names)-1], err + case strings.HasPrefix(ct, "text/html"): + return "", nil + default: + return "", fmt.Errorf("Cannot extract names from response with content-type: [%s]", ct) + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/urls.go new file mode 100644 index 00000000000..b3ac304b742 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/urls.go @@ -0,0 +1,33 @@ +package objects + +import ( + "github.com/gophercloud/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) +}