Merge pull request #160 from jrperritt/cf-refactor
Cf refactor.
I'm making a command decision to merge this PR. It's baked long enough, and people are wanting to use it. The only issues were documentation related (if memory serves), which is not going to break the success of the project. We'll just have to chalk it up to technical debt and move on.
diff --git a/acceptance/openstack/compute_test.go b/acceptance/openstack/compute_test.go
index f396fe2..f03c0f9 100644
--- a/acceptance/openstack/compute_test.go
+++ b/acceptance/openstack/compute_test.go
@@ -11,8 +11,10 @@
"testing"
)
+var service = "compute"
+
func TestListServers(t *testing.T) {
- ts, err := setupForList()
+ ts, err := setupForList(service)
if err != nil {
t.Error(err)
return
@@ -52,7 +54,7 @@
}
func TestListImages(t *testing.T) {
- ts, err := setupForList()
+ ts, err := setupForList(service)
if err != nil {
t.Error(err)
return
@@ -92,7 +94,7 @@
}
func TestListFlavors(t *testing.T) {
- ts, err := setupForList()
+ ts, err := setupForList(service)
if err != nil {
t.Error(err)
return
diff --git a/acceptance/openstack/storage_test.go b/acceptance/openstack/storage_test.go
new file mode 100644
index 0000000..d194936
--- /dev/null
+++ b/acceptance/openstack/storage_test.go
@@ -0,0 +1,361 @@
+// +build acceptance
+
+package openstack
+
+import (
+ "bytes"
+ storage "github.com/rackspace/gophercloud/openstack/storage/v1"
+ "github.com/rackspace/gophercloud/openstack/storage/v1/accounts"
+ "github.com/rackspace/gophercloud/openstack/storage/v1/containers"
+ "github.com/rackspace/gophercloud/openstack/storage/v1/objects"
+ "os"
+ "strings"
+ "testing"
+)
+
+var objectStorage = "object-store"
+var metadata = map[string]string{"gopher": "cloud"}
+var numContainers = 2
+var numObjects = 2
+
+func TestAccount(t *testing.T) {
+ ts, err := setupForList(objectStorage)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ region := os.Getenv("OS_REGION_NAME")
+ for _, ep := range ts.eps {
+ if (region != "") && (region != ep.Region) {
+ continue
+ }
+
+ client := storage.NewClient(ep.PublicURL, ts.a, ts.o)
+
+ err := accounts.Update(client, accounts.UpdateOpts{
+ Metadata: metadata,
+ })
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ defer func() {
+ tempMap := make(map[string]string)
+ for k := range metadata {
+ tempMap[k] = ""
+ }
+ err = accounts.Update(client, accounts.UpdateOpts{
+ Metadata: tempMap,
+ })
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ }()
+
+ gr, err := accounts.Get(client, accounts.GetOpts{})
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ am := accounts.ExtractMetadata(gr)
+ for k := range metadata {
+ if am[k] != metadata[strings.Title(k)] {
+ t.Errorf("Expected custom metadata with key: %s", k)
+ return
+ }
+ }
+ }
+}
+
+func TestContainers(t *testing.T) {
+ ts, err := setupForList(objectStorage)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ region := os.Getenv("OS_REGION_NAME")
+ for _, ep := range ts.eps {
+ if (region != "") && (region != ep.Region) {
+ continue
+ }
+
+ client := storage.NewClient(ep.PublicURL, ts.a, ts.o)
+
+ cNames := make([]string, numContainers)
+ for i := 0; i < numContainers; i++ {
+ cNames[i] = randomString("test-container-", 8)
+ }
+
+ for i := 0; i < len(cNames); i++ {
+ _, err := containers.Create(client, containers.CreateOpts{
+ Name: cNames[i],
+ })
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ }
+ defer func() {
+ for i := 0; i < len(cNames); i++ {
+ err = containers.Delete(client, containers.DeleteOpts{
+ Name: cNames[i],
+ })
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ }
+ }()
+
+ lr, err := containers.List(client, containers.ListOpts{
+ Full: false,
+ })
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ cns, err := containers.ExtractNames(lr)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ if len(cns) != len(cNames) {
+ t.Errorf("Expected %d names and got %d", len(cNames), len(cns))
+ return
+ }
+
+ lr, err = containers.List(client, containers.ListOpts{
+ Full: true,
+ })
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ cis, err := containers.ExtractInfo(lr)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ if len(cis) != len(cNames) {
+ t.Errorf("Expected %d containers and got %d", len(cNames), len(cis))
+ return
+ }
+
+ err = containers.Update(client, containers.UpdateOpts{
+ Name: cNames[0],
+ Metadata: metadata,
+ })
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ defer func() {
+ tempMap := make(map[string]string)
+ for k := range metadata {
+ tempMap[k] = ""
+ }
+ err = containers.Update(client, containers.UpdateOpts{
+ Name: cNames[0],
+ Metadata: tempMap,
+ })
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ }()
+
+ gr, err := containers.Get(client, containers.GetOpts{})
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ cm := containers.ExtractMetadata(gr)
+ for k := range metadata {
+ if cm[k] != metadata[strings.Title(k)] {
+ t.Errorf("Expected custom metadata with key: %s", k)
+ return
+ }
+ }
+ }
+}
+
+func TestObjects(t *testing.T) {
+ ts, err := setupForList(objectStorage)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ region := os.Getenv("OS_REGION_NAME")
+
+ for _, ep := range ts.eps {
+ if (region != "") && (region != ep.Region) {
+ continue
+ }
+
+ client := storage.NewClient(ep.PublicURL, ts.a, ts.o)
+
+ oNames := make([]string, numObjects)
+ for i := 0; i < len(oNames); i++ {
+ oNames[i] = randomString("test-object-", 8)
+ }
+
+ cName := randomString("test-container-", 8)
+ _, err := containers.Create(client, containers.CreateOpts{
+ Name: cName,
+ })
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ defer func() {
+ err = containers.Delete(client, containers.DeleteOpts{
+ Name: cName,
+ })
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ }()
+
+ oContents := make([]*bytes.Buffer, numObjects)
+ for i := 0; i < numObjects; i++ {
+ oContents[i] = bytes.NewBuffer([]byte(randomString("", 10)))
+ err = objects.Create(client, objects.CreateOpts{
+ Container: cName,
+ Name: oNames[i],
+ Content: oContents[i],
+ })
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ }
+ defer func() {
+ for i := 0; i < numObjects; i++ {
+ err = objects.Delete(client, objects.DeleteOpts{
+ Container: cName,
+ Name: oNames[i],
+ })
+ }
+ }()
+
+ lr, err := objects.List(client, objects.ListOpts{
+ Full: false,
+ Container: cName,
+ })
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ ons, err := objects.ExtractNames(lr)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ if len(ons) != len(oNames) {
+ t.Errorf("Expected %d names and got %d", len(oNames), len(ons))
+ return
+ }
+
+ lr, err = objects.List(client, objects.ListOpts{
+ Full: true,
+ Container: cName,
+ })
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ ois, err := objects.ExtractInfo(lr)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ if len(ois) != len(oNames) {
+ t.Errorf("Expected %d containers and got %d", len(oNames), len(ois))
+ return
+ }
+
+ err = objects.Copy(client, objects.CopyOpts{
+ Container: cName,
+ Name: oNames[0],
+ NewContainer: cName,
+ NewName: oNames[1],
+ })
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ dr, err := objects.Download(client, objects.DownloadOpts{
+ Container: cName,
+ Name: oNames[1],
+ })
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ o2Content, err := objects.ExtractContent(dr)
+ if err != nil {
+ t.Error(err)
+ }
+ dr, err = objects.Download(client, objects.DownloadOpts{
+ Container: cName,
+ Name: oNames[0],
+ })
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ o1Content, err := objects.ExtractContent(dr)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ if string(o2Content) != string(o1Content) {
+ t.Errorf("Copy failed. Expected\n%s\nand got\n%s", string(o1Content), string(o2Content))
+ return
+ }
+
+ err = objects.Update(client, objects.UpdateOpts{
+ Container: cName,
+ Name: oNames[0],
+ Metadata: metadata,
+ })
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ defer func() {
+ tempMap := make(map[string]string)
+ for k := range metadata {
+ tempMap[k] = ""
+ }
+ err = objects.Update(client, objects.UpdateOpts{
+ Container: cName,
+ Name: oNames[0],
+ Metadata: tempMap,
+ })
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ }()
+
+ gr, err := objects.Get(client, objects.GetOpts{})
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ om := objects.ExtractMetadata(gr)
+ for k := range metadata {
+ if om[k] != metadata[strings.Title(k)] {
+ t.Errorf("Expected custom metadata with key: %s", k)
+ return
+ }
+ }
+ }
+}
diff --git a/acceptance/openstack/tools_test.go b/acceptance/openstack/tools_test.go
index 17377f9..042ab34 100644
--- a/acceptance/openstack/tools_test.go
+++ b/acceptance/openstack/tools_test.go
@@ -34,7 +34,7 @@
flavorIdResize string
}
-func setupForList() (*testState, error) {
+func setupForList(service string) (*testState, error) {
var err error
ts := new(testState)
@@ -54,7 +54,7 @@
return ts, err
}
- ts.eps, err = findAllComputeEndpoints(ts.sc)
+ ts.eps, err = findAllEndpoints(ts.sc, service)
if err != nil {
return ts, err
}
@@ -66,7 +66,7 @@
}
func setupForCRUD() (*testState, error) {
- ts, err := setupForList()
+ ts, err := setupForList("compute")
if err != nil {
return ts, err
}
@@ -103,19 +103,19 @@
return ts, err
}
-func findAllComputeEndpoints(sc *identity.ServiceCatalog) ([]identity.Endpoint, error) {
+func findAllEndpoints(sc *identity.ServiceCatalog, service string) ([]identity.Endpoint, error) {
ces, err := sc.CatalogEntries()
if err != nil {
return nil, err
}
for _, ce := range ces {
- if ce.Type == "compute" {
+ if ce.Type == service {
return ce.Endpoints, nil
}
}
- return nil, fmt.Errorf("Compute endpoint not found.")
+ return nil, fmt.Errorf(service + " endpoint not found.")
}
func findEndpointForRegion(eps []identity.Endpoint, r string) (string, error) {
diff --git a/api_fetch.go b/api_fetch.go
index 54ca9be..493f61e 100644
--- a/api_fetch.go
+++ b/api_fetch.go
@@ -1,7 +1,7 @@
package gophercloud
-import(
- "github.com/mitchellh/mapstructure"
+import (
+ "github.com/mitchellh/mapstructure"
)
//The default generic openstack api
@@ -16,10 +16,9 @@
"UrlChoice": PublicURL,
}
-
//Populates an ApiCriteria struct with the api values
-//from one of the api maps
-func PopulateApi(variant string) (ApiCriteria, error){
+//from one of the api maps
+func PopulateApi(variant string) (ApiCriteria, error) {
var Api ApiCriteria
var variantMap map[string]interface{}
@@ -30,13 +29,13 @@
case "openstack":
variantMap = OpenstackApi
- case "rackspace":
+ case "rackspace":
variantMap = RackspaceApi
}
- err := mapstructure.Decode(variantMap,&Api)
- if err != nil{
- return Api,err
- }
- return Api, err
+ err := mapstructure.Decode(variantMap, &Api)
+ if err != nil {
+ return Api, err
+ }
+ return Api, err
}
diff --git a/openstack/compute/images/images_test.go b/openstack/compute/images/images_test.go
index 05a8550..ee3b79e 100644
--- a/openstack/compute/images/images_test.go
+++ b/openstack/compute/images/images_test.go
@@ -1,8 +1,8 @@
package images
import (
- "testing"
"encoding/json"
+ "testing"
)
const (
@@ -27,17 +27,17 @@
if err != nil {
t.Fatal(err)
}
-
+
image, err := GetImage(simpleImageMap)
if err != nil {
t.Fatal(err)
}
-
+
if image.Id != "52415800-8b69-11e0-9b19-734f6f006e54" {
- t.Fatal("I expected an image ID of 52415800-8b69-11e0-9b19-734f6f006e54; got "+image.Id)
+ t.Fatal("I expected an image ID of 52415800-8b69-11e0-9b19-734f6f006e54; got " + image.Id)
}
-
+
if image.Name != "CentOS 5.2" {
- t.Fatal("I expected an image name of CentOS 5.2; got "+image.Name)
+ t.Fatal("I expected an image name of CentOS 5.2; got " + image.Name)
}
}
diff --git a/openstack/compute/servers/requests.go b/openstack/compute/servers/requests.go
index 2a004f6..95818da 100644
--- a/openstack/compute/servers/requests.go
+++ b/openstack/compute/servers/requests.go
@@ -1,8 +1,8 @@
package servers
import (
- "github.com/racker/perigee"
"fmt"
+ "github.com/racker/perigee"
)
// ListResult abstracts the raw results of making a List() request against the
@@ -113,11 +113,13 @@
}
err = perigee.Post(c.getActionUrl(id), perigee.Options{
- ReqBody: struct{C map[string]string `json:"changePassword"`}{
+ ReqBody: struct {
+ C map[string]string `json:"changePassword"`
+ }{
map[string]string{"adminPass": newPassword},
},
MoreHeaders: h,
- OkCodes: []int{202},
+ OkCodes: []int{202},
})
return err
}
@@ -136,7 +138,7 @@
// Value provides the value as it was passed into the function.
type ErrArgument struct {
Function, Argument string
- Value interface{}
+ Value interface{}
}
// Error yields a useful diagnostic for debugging purposes.
@@ -153,7 +155,7 @@
const (
SoftReboot = "SOFT"
HardReboot = "HARD"
- OSReboot = SoftReboot
+ OSReboot = SoftReboot
PowerCycle = HardReboot
)
@@ -174,21 +176,23 @@
return &ErrArgument{
Function: "Reboot",
Argument: "how",
- Value: how,
+ Value: how,
}
}
-
+
h, err := c.getActionHeaders()
if err != nil {
return err
}
err = perigee.Post(c.getActionUrl(id), perigee.Options{
- ReqBody: struct{C map[string]string `json:"reboot"`}{
+ ReqBody: struct {
+ C map[string]string `json:"reboot"`
+ }{
map[string]string{"type": how},
},
MoreHeaders: h,
- OkCodes: []int{202},
+ OkCodes: []int{202},
})
return err
}
@@ -212,7 +216,7 @@
return sr, &ErrArgument{
Function: "Rebuild",
Argument: "id",
- Value: "",
+ Value: "",
}
}
@@ -220,7 +224,7 @@
return sr, &ErrArgument{
Function: "Rebuild",
Argument: "name",
- Value: "",
+ Value: "",
}
}
@@ -228,7 +232,7 @@
return sr, &ErrArgument{
Function: "Rebuild",
Argument: "password",
- Value: "",
+ Value: "",
}
}
@@ -236,7 +240,7 @@
return sr, &ErrArgument{
Function: "Rebuild",
Argument: "imageRef",
- Value: "",
+ Value: "",
}
}
@@ -254,12 +258,14 @@
}
err = perigee.Post(c.getActionUrl(id), perigee.Options{
- ReqBody: struct{R map[string]interface{} `json:"rebuild"`}{
+ ReqBody: struct {
+ R map[string]interface{} `json:"rebuild"`
+ }{
additional,
},
- Results: &sr,
+ Results: &sr,
MoreHeaders: h,
- OkCodes: []int{202},
+ OkCodes: []int{202},
})
return sr, err
}
@@ -277,11 +283,13 @@
}
err = perigee.Post(c.getActionUrl(id), perigee.Options{
- ReqBody: struct{R map[string]interface{} `json:"resize"`}{
+ ReqBody: struct {
+ R map[string]interface{} `json:"resize"`
+ }{
map[string]interface{}{"flavorRef": flavorRef},
},
MoreHeaders: h,
- OkCodes: []int{202},
+ OkCodes: []int{202},
})
return err
}
@@ -295,9 +303,9 @@
}
err = perigee.Post(c.getActionUrl(id), perigee.Options{
- ReqBody: map[string]interface{}{"confirmResize": nil},
+ ReqBody: map[string]interface{}{"confirmResize": nil},
MoreHeaders: h,
- OkCodes: []int{204},
+ OkCodes: []int{204},
})
return err
}
@@ -311,9 +319,9 @@
}
err = perigee.Post(c.getActionUrl(id), perigee.Options{
- ReqBody: map[string]interface{}{"revertResize": nil},
+ ReqBody: map[string]interface{}{"revertResize": nil},
MoreHeaders: h,
- OkCodes: []int{202},
+ OkCodes: []int{202},
})
return err
}
diff --git a/openstack/storage/v1/accounts/accounts.go b/openstack/storage/v1/accounts/accounts.go
new file mode 100644
index 0000000..c460e45
--- /dev/null
+++ b/openstack/storage/v1/accounts/accounts.go
@@ -0,0 +1,30 @@
+package accounts
+
+import (
+ "strings"
+)
+
+// UpdateOpts is a structure that contains parameters for updating, creating, or deleting an
+// account's metadata.
+type UpdateOpts struct {
+ Metadata map[string]string
+ Headers map[string]string
+}
+
+// GetOpts is a structure that contains parameters for getting an account's metadata.
+type GetOpts struct {
+ Headers map[string]string
+}
+
+// ExtractMetadata is a function that takes a GetResult (of type *http.Response)
+// and returns the custom metatdata associated with the account.
+func ExtractMetadata(gr GetResult) map[string]string {
+ 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
+}
diff --git a/openstack/storage/v1/accounts/requests.go b/openstack/storage/v1/accounts/requests.go
new file mode 100644
index 0000000..7b84497
--- /dev/null
+++ b/openstack/storage/v1/accounts/requests.go
@@ -0,0 +1,50 @@
+package accounts
+
+import (
+ "github.com/racker/perigee"
+ storage "github.com/rackspace/gophercloud/openstack/storage/v1"
+ "net/http"
+)
+
+// GetResult is a *http.Response that is returned from a call to the Get function.
+type GetResult *http.Response
+
+// Update is a function that creates, updates, or deletes an account's metadata.
+func Update(c *storage.Client, opts UpdateOpts) error {
+ h, err := c.GetHeaders()
+ if err != nil {
+ return err
+ }
+ for k, v := range opts.Headers {
+ h[k] = v
+ }
+
+ for k, v := range opts.Metadata {
+ h["X-Account-Meta-"+k] = v
+ }
+
+ url := c.GetAccountURL()
+ _, err = perigee.Request("POST", url, perigee.Options{
+ MoreHeaders: h,
+ })
+ return err
+}
+
+// Get is a function that retrieves an account's metadata. To extract just the custom
+// metadata, pass the GetResult response to the ExtractMetadata function.
+func Get(c *storage.Client, opts GetOpts) (GetResult, error) {
+ h, err := c.GetHeaders()
+ if err != nil {
+ return nil, err
+ }
+
+ for k, v := range opts.Headers {
+ h[k] = v
+ }
+
+ url := c.GetAccountURL()
+ resp, err := perigee.Request("HEAD", url, perigee.Options{
+ MoreHeaders: h,
+ })
+ return &resp.HttpResponse, err
+}
diff --git a/openstack/storage/v1/client.go b/openstack/storage/v1/client.go
new file mode 100644
index 0000000..f616038
--- /dev/null
+++ b/openstack/storage/v1/client.go
@@ -0,0 +1,68 @@
+package v1
+
+import (
+ "fmt"
+ "github.com/rackspace/gophercloud/openstack/identity"
+)
+
+// Client is a structure that contains information for communicating with a provider.
+type Client struct {
+ endpoint string
+ authority identity.AuthResults
+ options identity.AuthOptions
+ token *identity.Token
+}
+
+// NewClient creates and returns a *Client.
+func NewClient(e string, a identity.AuthResults, o identity.AuthOptions) *Client {
+ return &Client{
+ endpoint: e,
+ authority: a,
+ options: o,
+ }
+}
+
+// GetAccountURL returns the URI for making Account requests. This function is exported to allow
+// the 'Accounts' subpackage to use it. It is not meant for public consumption.
+func (c *Client) GetAccountURL() string {
+ return fmt.Sprintf("%s", c.endpoint)
+}
+
+// GetContainerURL returns the URI for making Container requests. This function is exported to allow
+// the 'Containers' subpackage to use it. It is not meant for public consumption.
+func (c *Client) GetContainerURL(container string) string {
+ return fmt.Sprintf("%s/%s", c.endpoint, container)
+}
+
+// GetObjectURL returns the URI for making Object requests. This function is exported to allow
+// the 'Objects' subpackage to use it. It is not meant for public consumption.
+func (c *Client) GetObjectURL(container, object string) string {
+ return fmt.Sprintf("%s/%s/%s", c.endpoint, container, object)
+}
+
+// GetHeaders is a function that gets the header for token authentication against a client's endpoint.
+// This function is exported to allow the subpackages to use it. It is not meant for public consumption.
+func (c *Client) GetHeaders() (map[string]string, error) {
+ t, err := c.getAuthToken()
+ if err != nil {
+ return map[string]string{}, err
+ }
+
+ return map[string]string{
+ "X-Auth-Token": t,
+ }, nil
+}
+
+// getAuthToken is a function that tries to retrieve an authentication token from a client's endpoint.
+func (c *Client) getAuthToken() (string, error) {
+ var err error
+
+ if c.token == nil {
+ c.token, err = identity.GetToken(c.authority)
+ if err != nil {
+ return "", err
+ }
+ }
+
+ return c.token.Id, err
+}
diff --git a/openstack/storage/v1/containers/containers.go b/openstack/storage/v1/containers/containers.go
new file mode 100644
index 0000000..3a00647
--- /dev/null
+++ b/openstack/storage/v1/containers/containers.go
@@ -0,0 +1,84 @@
+package containers
+
+import (
+ "encoding/json"
+ "io/ioutil"
+ "strings"
+)
+
+// Container is a structure that holds information related to a storage container.
+type Container map[string]interface{}
+
+// ListOpts is a structure that holds parameters for listing containers.
+type ListOpts struct {
+ Full bool
+ Params map[string]string
+}
+
+// CreateOpts is a structure that holds parameters for creating a container.
+type CreateOpts struct {
+ Name string
+ Metadata map[string]string
+ Headers map[string]string
+}
+
+// DeleteOpts is a structure that holds parameters for deleting a container.
+type DeleteOpts struct {
+ Name string
+ Params map[string]string
+}
+
+// UpdateOpts is a structure that holds parameters for updating, creating, or deleting a
+// container's metadata.
+type UpdateOpts struct {
+ Name string
+ Metadata map[string]string
+ Headers map[string]string
+}
+
+// GetOpts is a structure that holds parameters for getting a container's metadata.
+type GetOpts struct {
+ Name string
+ Metadata map[string]string
+}
+
+// ExtractInfo is a function that takes a ListResult (of type *http.Response)
+// and returns the containers' information.
+func ExtractInfo(lr ListResult) ([]Container, error) {
+ var ci []Container
+ defer lr.Body.Close()
+ body, err := ioutil.ReadAll(lr.Body)
+ if err != nil {
+ return ci, err
+ }
+ err = json.Unmarshal(body, &ci)
+ return ci, err
+}
+
+// ExtractNames is a function that takes a ListResult (of type *http.Response)
+// and returns the containers' names.
+func ExtractNames(lr ListResult) ([]string, error) {
+ var cns []string
+ defer lr.Body.Close()
+ body, err := ioutil.ReadAll(lr.Body)
+ if err != nil {
+ return cns, err
+ }
+ jr := string(body)
+ cns = strings.Split(jr, "\n")
+ cns = cns[:len(cns)-1]
+ return cns, nil
+}
+
+// ExtractMetadata is a function that takes a GetResult (of type *http.Response)
+// and returns the custom metadata associated with the container.
+func ExtractMetadata(gr GetResult) map[string]string {
+ 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
+}
diff --git a/openstack/storage/v1/containers/requests.go b/openstack/storage/v1/containers/requests.go
new file mode 100644
index 0000000..b6d3a89
--- /dev/null
+++ b/openstack/storage/v1/containers/requests.go
@@ -0,0 +1,125 @@
+package containers
+
+import (
+ "github.com/racker/perigee"
+ storage "github.com/rackspace/gophercloud/openstack/storage/v1"
+ "github.com/rackspace/gophercloud/openstack/utils"
+ "net/http"
+)
+
+// ListResult is a *http.Response that is returned from a call to the List function.
+type ListResult *http.Response
+
+// GetResult is a *http.Response that is returned from a call to the Get function.
+type GetResult *http.Response
+
+// List is a function that retrieves all objects in a container. It also returns the details
+// for the account. To extract just the container information or names, pass the ListResult
+// response to the ExtractInfo or ExtractNames function, respectively.
+func List(c *storage.Client, opts ListOpts) (ListResult, error) {
+ contentType := ""
+
+ h, err := c.GetHeaders()
+ if err != nil {
+ return nil, err
+ }
+
+ query := utils.BuildQuery(opts.Params)
+
+ if !opts.Full {
+ contentType = "text/plain"
+ }
+
+ url := c.GetAccountURL() + query
+ resp, err := perigee.Request("GET", url, perigee.Options{
+ MoreHeaders: h,
+ Accept: contentType,
+ })
+ return &resp.HttpResponse, err
+}
+
+// Create is a function that creates a new container.
+func Create(c *storage.Client, opts CreateOpts) (Container, error) {
+ var ci Container
+
+ h, err := c.GetHeaders()
+ if err != nil {
+ return Container{}, err
+ }
+
+ for k, v := range opts.Headers {
+ h[k] = v
+ }
+
+ for k, v := range opts.Metadata {
+ h["X-Container-Meta-"+k] = v
+ }
+
+ url := c.GetContainerURL(opts.Name)
+ _, err = perigee.Request("PUT", url, perigee.Options{
+ MoreHeaders: h,
+ })
+ if err == nil {
+ ci = Container{
+ "name": opts.Name,
+ }
+ }
+ return ci, err
+}
+
+// Delete is a function that deletes a container.
+func Delete(c *storage.Client, opts DeleteOpts) error {
+ h, err := c.GetHeaders()
+ if err != nil {
+ return err
+ }
+
+ query := utils.BuildQuery(opts.Params)
+
+ url := c.GetContainerURL(opts.Name) + query
+ _, err = perigee.Request("DELETE", url, perigee.Options{
+ MoreHeaders: h,
+ })
+ return err
+}
+
+// Update is a function that creates, updates, or deletes a container's metadata.
+func Update(c *storage.Client, opts UpdateOpts) error {
+ h, err := c.GetHeaders()
+ if err != nil {
+ return err
+ }
+
+ for k, v := range opts.Headers {
+ h[k] = v
+ }
+
+ for k, v := range opts.Metadata {
+ h["X-Container-Meta-"+k] = v
+ }
+
+ url := c.GetContainerURL(opts.Name)
+ _, err = perigee.Request("POST", url, perigee.Options{
+ MoreHeaders: h,
+ })
+ return err
+}
+
+// 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 *storage.Client, opts GetOpts) (GetResult, error) {
+ h, err := c.GetHeaders()
+ if err != nil {
+ return nil, err
+ }
+
+ for k, v := range opts.Metadata {
+ h["X-Container-Meta-"+k] = v
+ }
+
+ url := c.GetContainerURL(opts.Name)
+ resp, err := perigee.Request("HEAD", url, perigee.Options{
+ MoreHeaders: h,
+ })
+ return &resp.HttpResponse, err
+}
diff --git a/openstack/storage/v1/objects/objects.go b/openstack/storage/v1/objects/objects.go
new file mode 100644
index 0000000..ab390fa
--- /dev/null
+++ b/openstack/storage/v1/objects/objects.go
@@ -0,0 +1,120 @@
+package objects
+
+import (
+ "bytes"
+ "encoding/json"
+ "io/ioutil"
+ "strings"
+)
+
+// Object is a structure that holds information related to a storage object.
+type Object map[string]interface{}
+
+// ListOpts is a structure that holds parameters for listing objects.
+type ListOpts struct {
+ Container string
+ Full bool
+ Params map[string]string
+}
+
+// DownloadOpts is a structure that holds parameters for downloading an object.
+type DownloadOpts struct {
+ Container string
+ Name string
+ Headers map[string]string
+ Params map[string]string
+}
+
+// CreateOpts is a structure that holds parameters for creating an object.
+type CreateOpts struct {
+ Container string
+ Name string
+ Content *bytes.Buffer
+ Metadata map[string]string
+ Headers map[string]string
+ Params map[string]string
+}
+
+// CopyOpts is a structure that holds parameters for copying one object to another.
+type CopyOpts struct {
+ Container string
+ Name string
+ NewContainer string
+ NewName string
+ Metadata map[string]string
+ Headers map[string]string
+}
+
+// DeleteOpts is a structure that holds parameters for deleting an object.
+type DeleteOpts struct {
+ Container string
+ Name string
+ Params map[string]string
+}
+
+// GetOpts is a structure that holds parameters for getting an object's metadata.
+type GetOpts struct {
+ Container string
+ Name string
+ Headers map[string]string
+ Params map[string]string
+}
+
+// UpdateOpts is a structure that holds parameters for updating, creating, or deleting an
+// object's metadata.
+type UpdateOpts struct {
+ Container string
+ Name string
+ Metadata map[string]string
+ Headers map[string]string
+}
+
+// ExtractInfo is a function that takes a ListResult (of type *http.Response)
+// and returns the objects' information.
+func ExtractInfo(lr ListResult) ([]Object, error) {
+ var oi []Object
+ defer lr.Body.Close()
+ body, err := ioutil.ReadAll(lr.Body)
+ if err != nil {
+ return oi, err
+ }
+ err = json.Unmarshal(body, &oi)
+ return oi, err
+}
+
+// ExtractNames is a function that takes a ListResult (of type *http.Response)
+// and returns the objects' names.
+func ExtractNames(lr ListResult) ([]string, error) {
+ var ons []string
+ defer lr.Body.Close()
+ body, err := ioutil.ReadAll(lr.Body)
+ if err != nil {
+ return ons, err
+ }
+ jr := string(body)
+ ons = strings.Split(jr, "\n")
+ ons = ons[:len(ons)-1]
+ return ons, nil
+}
+
+// ExtractContent is a function that takes a DownloadResult (of type *http.Response)
+// and returns the object's content.
+func ExtractContent(dr DownloadResult) ([]byte, error) {
+ var body []byte
+ defer dr.Body.Close()
+ body, err := ioutil.ReadAll(dr.Body)
+ return body, err
+}
+
+// ExtractMetadata is a function that takes a GetResult (of type *http.Response)
+// and returns the custom metadata associated with the object.
+func ExtractMetadata(gr GetResult) map[string]string {
+ 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
+}
diff --git a/openstack/storage/v1/objects/requests.go b/openstack/storage/v1/objects/requests.go
new file mode 100644
index 0000000..a7dff40
--- /dev/null
+++ b/openstack/storage/v1/objects/requests.go
@@ -0,0 +1,178 @@
+package objects
+
+import (
+ "fmt"
+ "github.com/racker/perigee"
+ storage "github.com/rackspace/gophercloud/openstack/storage/v1"
+ "github.com/rackspace/gophercloud/openstack/utils"
+ "net/http"
+)
+
+// ListResult is a *http.Response that is returned from a call to the List function.
+type ListResult *http.Response
+
+// DownloadResult is a *http.Response that is returned from a call to the Download function.
+type DownloadResult *http.Response
+
+// GetResult is a *http.Response that is returned from a call to the Get function.
+type GetResult *http.Response
+
+// 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 *storage.Client, opts ListOpts) (ListResult, error) {
+ contentType := ""
+
+ h, err := c.GetHeaders()
+ if err != nil {
+ return nil, err
+ }
+
+ query := utils.BuildQuery(opts.Params)
+
+ if !opts.Full {
+ contentType = "text/plain"
+ }
+
+ url := c.GetContainerURL(opts.Container) + query
+ resp, err := perigee.Request("GET", url, perigee.Options{
+ MoreHeaders: h,
+ Accept: contentType,
+ })
+ return &resp.HttpResponse, err
+}
+
+// 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 *storage.Client, opts DownloadOpts) (DownloadResult, error) {
+ h, err := c.GetHeaders()
+ if err != nil {
+ return nil, err
+ }
+
+ for k, v := range opts.Headers {
+ h[k] = v
+ }
+
+ query := utils.BuildQuery(opts.Params)
+
+ url := c.GetObjectURL(opts.Container, opts.Name) + query
+ resp, err := perigee.Request("GET", url, perigee.Options{
+ MoreHeaders: h,
+ })
+ return &resp.HttpResponse, err
+}
+
+// Create is a function that creates a new object or replaces an existing object.
+func Create(c *storage.Client, opts CreateOpts) error {
+ var reqBody []byte
+
+ h, err := c.GetHeaders()
+ if err != nil {
+ return err
+ }
+
+ for k, v := range opts.Headers {
+ h[k] = v
+ }
+
+ for k, v := range opts.Metadata {
+ h["X-Object-Meta-"+k] = v
+ }
+
+ query := utils.BuildQuery(opts.Params)
+
+ content := opts.Content
+ if content != nil {
+ reqBody = make([]byte, content.Len())
+ _, err = content.Read(reqBody)
+ if err != nil {
+ return err
+ }
+ }
+
+ url := c.GetObjectURL(opts.Container, opts.Name) + query
+ _, err = perigee.Request("PUT", url, perigee.Options{
+ ReqBody: reqBody,
+ MoreHeaders: h,
+ })
+ return err
+}
+
+// Copy is a function that copies one object to another.
+func Copy(c *storage.Client, opts CopyOpts) error {
+ h, err := c.GetHeaders()
+ if err != nil {
+ return err
+ }
+
+ for k, v := range opts.Metadata {
+ h["X-Object-Meta-"+k] = v
+ }
+
+ h["Destination"] = fmt.Sprintf("/%s/%s", opts.NewContainer, opts.NewName)
+
+ url := c.GetObjectURL(opts.Container, opts.Name)
+ _, err = perigee.Request("COPY", url, perigee.Options{
+ MoreHeaders: h,
+ })
+ return err
+}
+
+// Delete is a function that deletes an object.
+func Delete(c *storage.Client, opts DeleteOpts) error {
+ h, err := c.GetHeaders()
+ if err != nil {
+ return err
+ }
+
+ query := utils.BuildQuery(opts.Params)
+
+ url := c.GetObjectURL(opts.Container, opts.Name) + query
+ _, err = perigee.Request("DELETE", url, perigee.Options{
+ MoreHeaders: h,
+ })
+ return err
+}
+
+// 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 *storage.Client, opts GetOpts) (GetResult, error) {
+ h, err := c.GetHeaders()
+ if err != nil {
+ return nil, err
+ }
+
+ for k, v := range opts.Headers {
+ h[k] = v
+ }
+
+ url := c.GetObjectURL(opts.Container, opts.Name)
+ resp, err := perigee.Request("HEAD", url, perigee.Options{
+ MoreHeaders: h,
+ })
+ return &resp.HttpResponse, err
+}
+
+// Update is a function that creates, updates, or deletes an object's metadata.
+func Update(c *storage.Client, opts UpdateOpts) error {
+ h, err := c.GetHeaders()
+ if err != nil {
+ return err
+ }
+
+ for k, v := range opts.Headers {
+ h[k] = v
+ }
+
+ for k, v := range opts.Metadata {
+ h["X-Object-Meta-"+k] = v
+ }
+
+ url := c.GetObjectURL(opts.Container, opts.Name)
+ _, err = perigee.Request("POST", url, perigee.Options{
+ MoreHeaders: h,
+ })
+ return err
+}
diff --git a/openstack/utils/utils.go b/openstack/utils/utils.go
index 0d4c05c..a814c7e 100644
--- a/openstack/utils/utils.go
+++ b/openstack/utils/utils.go
@@ -50,3 +50,12 @@
return ao, nil
}
+
+func BuildQuery(params map[string]string) string {
+ query := "?"
+ for k, v := range params {
+ query += k + "=" + v + "&"
+ }
+ query = query[:len(query)-1]
+ return query
+}