Merge pull request #224 from jrperritt/v0.2.0-consistency-checks
V0.2.0 consistency checks
diff --git a/acceptance/openstack/compute/v2/flavors_test.go b/acceptance/openstack/compute/v2/flavors_test.go
index ca810bc..9c8e322 100644
--- a/acceptance/openstack/compute/v2/flavors_test.go
+++ b/acceptance/openstack/compute/v2/flavors_test.go
@@ -17,7 +17,7 @@
t.Logf("ID\tRegion\tName\tStatus\tCreated")
- pager := flavors.List(client, flavors.ListFilterOptions{})
+ pager := flavors.List(client, nil)
count, pages := 0, 0
pager.EachPage(func(page pagination.Page) (bool, error) {
t.Logf("---")
diff --git a/acceptance/openstack/compute/v2/images_test.go b/acceptance/openstack/compute/v2/images_test.go
index 7fca3ec..6166fc8 100644
--- a/acceptance/openstack/compute/v2/images_test.go
+++ b/acceptance/openstack/compute/v2/images_test.go
@@ -17,7 +17,7 @@
t.Logf("ID\tRegion\tName\tStatus\tCreated")
- pager := images.List(client)
+ pager := images.ListDetail(client, nil)
count, pages := 0, 0
pager.EachPage(func(page pagination.Page) (bool, error) {
pages++
diff --git a/endpoint_search_test.go b/endpoint_search_test.go
new file mode 100644
index 0000000..3457453
--- /dev/null
+++ b/endpoint_search_test.go
@@ -0,0 +1,19 @@
+package gophercloud
+
+import (
+ "testing"
+
+ th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestApplyDefaultsToEndpointOpts(t *testing.T) {
+ eo := EndpointOpts{Availability: AvailabilityPublic}
+ eo.ApplyDefaults("compute")
+ expected := EndpointOpts{Availability: AvailabilityPublic, Type: "compute"}
+ th.CheckDeepEquals(t, expected, eo)
+
+ eo = EndpointOpts{Type: "compute"}
+ eo.ApplyDefaults("object-store")
+ expected = EndpointOpts{Availability: AvailabilityPublic, Type: "compute"}
+ th.CheckDeepEquals(t, expected, eo)
+}
diff --git a/openstack/blockstorage/v1/snapshots/requests.go b/openstack/blockstorage/v1/snapshots/requests.go
index f3180d7..7fac925 100644
--- a/openstack/blockstorage/v1/snapshots/requests.go
+++ b/openstack/blockstorage/v1/snapshots/requests.go
@@ -1,50 +1,74 @@
package snapshots
import (
+ "fmt"
+
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
"github.com/racker/perigee"
)
+// CreateOptsBuilder allows extensions to add additional parameters to the
+// Create request.
+type CreateOptsBuilder interface {
+ ToSnapshotCreateMap() (map[string]interface{}, error)
+}
+
// CreateOpts contains options for creating a Snapshot. This object is passed to
// the snapshots.Create function. For more information about these parameters,
// see the Snapshot object.
type CreateOpts struct {
- Description string // OPTIONAL
- Force bool // OPTIONAL
- Metadata map[string]interface{} // OPTIONAL
- Name string // OPTIONAL
- VolumeID string // REQUIRED
+ // OPTIONAL
+ Description string
+ // OPTIONAL
+ Force bool
+ // OPTIONAL
+ Metadata map[string]interface{}
+ // OPTIONAL
+ Name string
+ // REQUIRED
+ VolumeID string
}
-// Create will create a new Snapshot based on the values in CreateOpts. To extract
-// the Snapshot object from the response, call the Extract method on the
+// ToSnapshotCreateMap assembles a request body based on the contents of a
+// CreateOpts.
+func (opts CreateOpts) ToSnapshotCreateMap() (map[string]interface{}, error) {
+ s := make(map[string]interface{})
+
+ if opts.VolumeID == "" {
+ return nil, fmt.Errorf("Required CreateOpts field 'VolumeID' not set.")
+ }
+ s["volume_id"] = opts.VolumeID
+
+ if opts.Description != "" {
+ s["display_description"] = opts.Description
+ }
+ if opts.Force == true {
+ s["force"] = opts.Force
+ }
+ if opts.Metadata != nil {
+ s["metadata"] = opts.Metadata
+ }
+ if opts.Name != "" {
+ s["display_name"] = opts.Name
+ }
+
+ return map[string]interface{}{"snapshot": s}, nil
+}
+
+// Create will create a new Snapshot based on the values in CreateOpts. To
+// extract the Snapshot object from the response, call the Extract method on the
// CreateResult.
-func Create(client *gophercloud.ServiceClient, opts *CreateOpts) CreateResult {
- type snapshot struct {
- Description *string `json:"display_description,omitempty"`
- Force bool `json:"force,omitempty"`
- Metadata map[string]interface{} `json:"metadata,omitempty"`
- Name *string `json:"display_name,omitempty"`
- VolumeID *string `json:"volume_id,omitempty"`
- }
-
- type request struct {
- Snapshot snapshot `json:"snapshot"`
- }
-
- reqBody := request{
- Snapshot: snapshot{},
- }
-
- reqBody.Snapshot.Description = gophercloud.MaybeString(opts.Description)
- reqBody.Snapshot.Name = gophercloud.MaybeString(opts.Name)
- reqBody.Snapshot.VolumeID = gophercloud.MaybeString(opts.VolumeID)
-
- reqBody.Snapshot.Force = opts.Force
-
+func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
var res CreateResult
+
+ reqBody, err := opts.ToSnapshotCreateMap()
+ if err != nil {
+ res.Err = err
+ return res
+ }
+
_, res.Err = perigee.Request("POST", createURL(client), perigee.Options{
MoreHeaders: client.Provider.AuthenticatedHeaders(),
OkCodes: []int{200, 201},
@@ -63,8 +87,8 @@
return err
}
-// Get retrieves the Snapshot with the provided ID. To extract the Snapshot object
-// from the response, call the Extract method on the GetResult.
+// Get retrieves the Snapshot with the provided ID. To extract the Snapshot
+// object from the response, call the Extract method on the GetResult.
func Get(client *gophercloud.ServiceClient, id string) GetResult {
var res GetResult
_, res.Err = perigee.Request("GET", getURL(client, id), perigee.Options{
@@ -75,6 +99,12 @@
return res
}
+// ListOptsBuilder allows extensions to add additional parameters to the List
+// request.
+type ListOptsBuilder interface {
+ ToSnapshotListQuery() (string, error)
+}
+
// ListOpts hold options for listing Snapshots. It is passed to the
// snapshots.List function.
type ListOpts struct {
@@ -83,15 +113,25 @@
VolumeID string `q:"volume_id"`
}
-// List returns Snapshots optionally limited by the conditions provided in ListOpts.
-func List(client *gophercloud.ServiceClient, opts *ListOpts) pagination.Pager {
+// ToSnapshotListQuery formats a ListOpts into a query string.
+func (opts ListOpts) ToSnapshotListQuery() (string, error) {
+ q, err := gophercloud.BuildQueryString(opts)
+ if err != nil {
+ return "", err
+ }
+ return q.String(), nil
+}
+
+// List returns Snapshots optionally limited by the conditions provided in
+// ListOpts.
+func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
url := listURL(client)
if opts != nil {
- query, err := gophercloud.BuildQueryString(opts)
+ query, err := opts.ToSnapshotListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
- url += query.String()
+ url += query
}
createPage := func(r pagination.LastHTTPResponse) pagination.Page {
@@ -100,6 +140,12 @@
return pagination.NewPager(client, url, createPage)
}
+// UpdateMetadataOptsBuilder allows extensions to add additional parameters to
+// the Update request.
+type UpdateMetadataOptsBuilder interface {
+ ToSnapshotUpdateMetadataMap() (map[string]interface{}, error)
+}
+
// UpdateMetadataOpts contain options for updating an existing Snapshot. This
// object is passed to the snapshots.Update function. For more information
// about the parameters, see the Snapshot object.
@@ -107,20 +153,30 @@
Metadata map[string]interface{}
}
+// ToSnapshotUpdateMetadataMap assembles a request body based on the contents of
+// an UpdateMetadataOpts.
+func (opts UpdateMetadataOpts) ToSnapshotUpdateMetadataMap() (map[string]interface{}, error) {
+ v := make(map[string]interface{})
+
+ if opts.Metadata != nil {
+ v["metadata"] = opts.Metadata
+ }
+
+ return v, nil
+}
+
// UpdateMetadata will update the Snapshot with provided information. To
// extract the updated Snapshot from the response, call the ExtractMetadata
// method on the UpdateMetadataResult.
-func UpdateMetadata(client *gophercloud.ServiceClient, id string, opts *UpdateMetadataOpts) UpdateMetadataResult {
- type request struct {
- Metadata map[string]interface{} `json:"metadata,omitempty"`
- }
-
- reqBody := request{}
-
- reqBody.Metadata = opts.Metadata
-
+func UpdateMetadata(client *gophercloud.ServiceClient, id string, opts UpdateMetadataOptsBuilder) UpdateMetadataResult {
var res UpdateMetadataResult
+ reqBody, err := opts.ToSnapshotUpdateMetadataMap()
+ if err != nil {
+ res.Err = err
+ return res
+ }
+
_, res.Err = perigee.Request("PUT", updateMetadataURL(client, id), perigee.Options{
MoreHeaders: client.Provider.AuthenticatedHeaders(),
OkCodes: []int{200},
diff --git a/openstack/blockstorage/v1/snapshots/requests_test.go b/openstack/blockstorage/v1/snapshots/requests_test.go
index d29cc0d..ddfa81b 100644
--- a/openstack/blockstorage/v1/snapshots/requests_test.go
+++ b/openstack/blockstorage/v1/snapshots/requests_test.go
@@ -119,6 +119,7 @@
th.TestJSONRequest(t, r, `
{
"snapshot": {
+ "volume_id": "1234",
"display_name": "snapshot-001"
}
}
@@ -130,6 +131,7 @@
fmt.Fprintf(w, `
{
"snapshot": {
+ "volume_id": "1234",
"display_name": "snapshot-001",
"id": "d32019d3-bc6e-4319-9c1d-6722fc136a22"
}
@@ -137,10 +139,11 @@
`)
})
- options := &CreateOpts{Name: "snapshot-001"}
+ options := &CreateOpts{VolumeID: "1234", Name: "snapshot-001"}
n, err := Create(ServiceClient(), options).Extract()
th.AssertNoErr(t, err)
+ th.AssertEquals(t, n.VolumeID, "1234")
th.AssertEquals(t, n.Name, "snapshot-001")
th.AssertEquals(t, n.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22")
}
diff --git a/openstack/blockstorage/v1/snapshots/results.go b/openstack/blockstorage/v1/snapshots/results.go
index d23090d..dc94a32 100644
--- a/openstack/blockstorage/v1/snapshots/results.go
+++ b/openstack/blockstorage/v1/snapshots/results.go
@@ -9,19 +9,32 @@
// Snapshot contains all the information associated with an OpenStack Snapshot.
type Snapshot struct {
- Status string `mapstructure:"status"` // currect status of the Snapshot
- Name string `mapstructure:"display_name"` // display name
- Attachments []string `mapstructure:"attachments"` // instances onto which the Snapshot is attached
- AvailabilityZone string `mapstructure:"availability_zone"` // logical group
- Bootable string `mapstructure:"bootable"` // is the Snapshot bootable
- CreatedAt string `mapstructure:"created_at"` // date created
- Description string `mapstructure:"display_discription"` // display description
- VolumeType string `mapstructure:"volume_type"` // see VolumeType object for more information
- SnapshotID string `mapstructure:"snapshot_id"` // ID of the Snapshot from which this Snapshot was created
- SourceVolID string `mapstructure:"source_volid"` // ID of the Volume from which this Snapshot was created
- Metadata map[string]string `mapstructure:"metadata"` // user-defined key-value pairs
- ID string `mapstructure:"id"` // unique identifier
- Size int `mapstructure:"size"` // size of the Snapshot, in GB
+ // Currect status of the Snapshot.
+ Status string `mapstructure:"status"`
+ // Display name.
+ Name string `mapstructure:"display_name"`
+ // Instances onto which the Snapshot is attached.
+ Attachments []string `mapstructure:"attachments"`
+ // Logical group.
+ AvailabilityZone string `mapstructure:"availability_zone"`
+ // Is the Snapshot bootable?
+ Bootable string `mapstructure:"bootable"`
+ // Date created.
+ CreatedAt string `mapstructure:"created_at"`
+ // Display description.
+ Description string `mapstructure:"display_discription"`
+ // See VolumeType object for more information.
+ VolumeType string `mapstructure:"volume_type"`
+ // ID of the Snapshot from which this Snapshot was created.
+ SnapshotID string `mapstructure:"snapshot_id"`
+ // ID of the Volume from which this Snapshot was created.
+ VolumeID string `mapstructure:"volume_id"`
+ // User-defined key-value pairs.
+ Metadata map[string]string `mapstructure:"metadata"`
+ // Unique identifier.
+ ID string `mapstructure:"id"`
+ // Size of the Snapshot, in GB.
+ Size int `mapstructure:"size"`
}
// CreateResult contains the response body and error from a Create request.
diff --git a/openstack/blockstorage/v1/snapshots/util_test.go b/openstack/blockstorage/v1/snapshots/util_test.go
new file mode 100644
index 0000000..46b452e
--- /dev/null
+++ b/openstack/blockstorage/v1/snapshots/util_test.go
@@ -0,0 +1,37 @@
+package snapshots
+
+import (
+ "fmt"
+ "net/http"
+ "testing"
+ "time"
+
+ th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestWaitForStatus(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/snapshots/1234", func(w http.ResponseWriter, r *http.Request) {
+ time.Sleep(2 * time.Second)
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprintf(w, `
+ {
+ "snapshot": {
+ "display_name": "snapshot-001",
+ "id": "1234",
+ "status":"available"
+ }
+ }`)
+ })
+
+ err := WaitForStatus(ServiceClient(), "1234", "available", 0)
+ if err == nil {
+ t.Errorf("Expected error: 'Time Out in WaitFor'")
+ }
+
+ err = WaitForStatus(ServiceClient(), "1234", "available", 3)
+ th.CheckNoErr(t, err)
+}
diff --git a/openstack/blockstorage/v1/volumes/requests.go b/openstack/blockstorage/v1/volumes/requests.go
index bca27db..042a33e 100644
--- a/openstack/blockstorage/v1/volumes/requests.go
+++ b/openstack/blockstorage/v1/volumes/requests.go
@@ -1,59 +1,90 @@
package volumes
import (
+ "fmt"
+
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
"github.com/racker/perigee"
)
+// CreateOptsBuilder allows extensions to add additional parameters to the
+// Create request.
+type CreateOptsBuilder interface {
+ ToVolumeCreateMap() (map[string]interface{}, error)
+}
+
// CreateOpts contains options for creating a Volume. This object is passed to
// the volumes.Create function. For more information about these parameters,
// see the Volume object.
type CreateOpts struct {
- Availability string // OPTIONAL
- Description string // OPTIONAL
- Metadata map[string]string // OPTIONAL
- Name string // OPTIONAL
- Size int // REQUIRED
- SnapshotID, SourceVolID, ImageID string // REQUIRED (one of them)
- VolumeType string // OPTIONAL
+ // OPTIONAL
+ Availability string
+ // OPTIONAL
+ Description string
+ // OPTIONAL
+ Metadata map[string]string
+ // OPTIONAL
+ Name string
+ // REQUIRED
+ Size int
+ // OPTIONAL
+ SnapshotID, SourceVolID, ImageID string
+ // OPTIONAL
+ VolumeType string
+}
+
+// ToVolumeCreateMap assembles a request body based on the contents of a
+// CreateOpts.
+func (opts CreateOpts) ToVolumeCreateMap() (map[string]interface{}, error) {
+ v := make(map[string]interface{})
+
+ if opts.Size == 0 {
+ return nil, fmt.Errorf("Required CreateOpts field 'Size' not set.")
+ }
+ v["size"] = opts.Size
+
+ if opts.Availability != "" {
+ v["availability_zone"] = opts.Availability
+ }
+ if opts.Description != "" {
+ v["display_description"] = opts.Description
+ }
+ if opts.ImageID != "" {
+ v["imageRef"] = opts.ImageID
+ }
+ if opts.Metadata != nil {
+ v["metadata"] = opts.Metadata
+ }
+ if opts.Name != "" {
+ v["display_name"] = opts.Name
+ }
+ if opts.SourceVolID != "" {
+ v["source_volid"] = opts.SourceVolID
+ }
+ if opts.SnapshotID != "" {
+ v["snapshot_id"] = opts.SnapshotID
+ }
+ if opts.VolumeType != "" {
+ v["volume_type"] = opts.VolumeType
+ }
+
+ return map[string]interface{}{"volume": v}, nil
}
// Create will create a new Volume based on the values in CreateOpts. To extract
-// the Volume object from the response, call the Extract method on the CreateResult.
-func Create(client *gophercloud.ServiceClient, opts *CreateOpts) CreateResult {
-
- type volume struct {
- Availability *string `json:"availability_zone,omitempty"`
- Description *string `json:"display_description,omitempty"`
- ImageID *string `json:"imageRef,omitempty"`
- Metadata map[string]string `json:"metadata,omitempty"`
- Name *string `json:"display_name,omitempty"`
- Size *int `json:"size,omitempty"`
- SnapshotID *string `json:"snapshot_id,omitempty"`
- SourceVolID *string `json:"source_volid,omitempty"`
- VolumeType *string `json:"volume_type,omitempty"`
- }
-
- type request struct {
- Volume volume `json:"volume"`
- }
-
- reqBody := request{
- Volume: volume{},
- }
-
- reqBody.Volume.Availability = gophercloud.MaybeString(opts.Availability)
- reqBody.Volume.Description = gophercloud.MaybeString(opts.Description)
- reqBody.Volume.ImageID = gophercloud.MaybeString(opts.ImageID)
- reqBody.Volume.Name = gophercloud.MaybeString(opts.Name)
- reqBody.Volume.Size = gophercloud.MaybeInt(opts.Size)
- reqBody.Volume.SnapshotID = gophercloud.MaybeString(opts.SnapshotID)
- reqBody.Volume.SourceVolID = gophercloud.MaybeString(opts.SourceVolID)
- reqBody.Volume.VolumeType = gophercloud.MaybeString(opts.VolumeType)
-
+// the Volume object from the response, call the Extract method on the
+// CreateResult.
+func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
var res CreateResult
+
+ reqBody, err := opts.ToVolumeCreateMap()
+ if err != nil {
+ res.Err = err
+ return res
+ }
+
_, res.Err = perigee.Request("POST", createURL(client), perigee.Options{
MoreHeaders: client.Provider.AuthenticatedHeaders(),
ReqBody: &reqBody,
@@ -72,8 +103,8 @@
return err
}
-// Get retrieves the Volume with the provided ID. To extract the Volume object from
-// the response, call the Extract method on the GetResult.
+// Get retrieves the Volume with the provided ID. To extract the Volume object
+// from the response, call the Extract method on the GetResult.
func Get(client *gophercloud.ServiceClient, id string) GetResult {
var res GetResult
_, res.Err = perigee.Request("GET", getURL(client, id), perigee.Options{
@@ -84,62 +115,97 @@
return res
}
+// ListOptsBuilder allows extensions to add additional parameters to the List
+// request.
+type ListOptsBuilder interface {
+ ToVolumeListQuery() (string, error)
+}
+
// ListOpts holds options for listing Volumes. It is passed to the volumes.List
// function.
type ListOpts struct {
- AllTenants bool `q:"all_tenants"` // admin-only option. Set it to true to see all tenant volumes.
- Metadata map[string]string `q:"metadata"` // List only volumes that contain Metadata.
- Name string `q:"name"` // List only volumes that have Name as the display name.
- Status string `q:"status"` // List only volumes that have a status of Status.
+ // admin-only option. Set it to true to see all tenant volumes.
+ AllTenants bool `q:"all_tenants"`
+ // List only volumes that contain Metadata.
+ Metadata map[string]string `q:"metadata"`
+ // List only volumes that have Name as the display name.
+ Name string `q:"name"`
+ // List only volumes that have a status of Status.
+ Status string `q:"status"`
+}
+
+// ToVolumeListQuery formats a ListOpts into a query string.
+func (opts ListOpts) ToVolumeListQuery() (string, error) {
+ q, err := gophercloud.BuildQueryString(opts)
+ if err != nil {
+ return "", err
+ }
+ return q.String(), nil
}
// List returns Volumes optionally limited by the conditions provided in ListOpts.
-func List(client *gophercloud.ServiceClient, opts *ListOpts) pagination.Pager {
+func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
url := listURL(client)
if opts != nil {
- query, err := gophercloud.BuildQueryString(opts)
+ query, err := opts.ToVolumeListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
- url += query.String()
+ url += query
}
createPage := func(r pagination.LastHTTPResponse) pagination.Page {
return ListResult{pagination.SinglePageBase(r)}
}
- return pagination.NewPager(client, listURL(client), createPage)
+ return pagination.NewPager(client, url, createPage)
+}
+
+// UpdateOptsBuilder allows extensions to add additional parameters to the
+// Update request.
+type UpdateOptsBuilder interface {
+ ToVolumeUpdateMap() (map[string]interface{}, error)
}
// UpdateOpts contain options for updating an existing Volume. This object is passed
// to the volumes.Update function. For more information about the parameters, see
// the Volume object.
type UpdateOpts struct {
- Name string // OPTIONAL
- Description string // OPTIONAL
- Metadata map[string]string // OPTIONAL
+ // OPTIONAL
+ Name string
+ // OPTIONAL
+ Description string
+ // OPTIONAL
+ Metadata map[string]string
+}
+
+// ToVolumeUpdateMap assembles a request body based on the contents of an
+// UpdateOpts.
+func (opts UpdateOpts) ToVolumeUpdateMap() (map[string]interface{}, error) {
+ v := make(map[string]interface{})
+
+ if opts.Description != "" {
+ v["display_description"] = opts.Description
+ }
+ if opts.Metadata != nil {
+ v["metadata"] = opts.Metadata
+ }
+ if opts.Name != "" {
+ v["display_name"] = opts.Name
+ }
+
+ return map[string]interface{}{"volume": v}, nil
}
// Update will update the Volume with provided information. To extract the updated
// Volume from the response, call the Extract method on the UpdateResult.
-func Update(client *gophercloud.ServiceClient, id string, opts *UpdateOpts) UpdateResult {
- type update struct {
- Description *string `json:"display_description,omitempty"`
- Metadata map[string]string `json:"metadata,omitempty"`
- Name *string `json:"display_name,omitempty"`
- }
-
- type request struct {
- Volume update `json:"volume"`
- }
-
- reqBody := request{
- Volume: update{},
- }
-
- reqBody.Volume.Description = gophercloud.MaybeString(opts.Description)
- reqBody.Volume.Name = gophercloud.MaybeString(opts.Name)
-
+func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult {
var res UpdateResult
+ reqBody, err := opts.ToVolumeUpdateMap()
+ if err != nil {
+ res.Err = err
+ return res
+ }
+
_, res.Err = perigee.Request("PUT", updateURL(client, id), perigee.Options{
MoreHeaders: client.Provider.AuthenticatedHeaders(),
OkCodes: []int{200},
diff --git a/openstack/blockstorage/v1/volumes/requests_test.go b/openstack/blockstorage/v1/volumes/requests_test.go
index 54ff91d..7cd37d5 100644
--- a/openstack/blockstorage/v1/volumes/requests_test.go
+++ b/openstack/blockstorage/v1/volumes/requests_test.go
@@ -119,7 +119,7 @@
th.TestJSONRequest(t, r, `
{
"volume": {
- "display_name": "vol-001"
+ "size": 4
}
}
`)
@@ -130,18 +130,18 @@
fmt.Fprintf(w, `
{
"volume": {
- "display_name": "vol-001",
+ "size": 4,
"id": "d32019d3-bc6e-4319-9c1d-6722fc136a22"
}
}
`)
})
- options := &CreateOpts{Name: "vol-001"}
+ options := &CreateOpts{Size: 4}
n, err := Create(ServiceClient(), options).Extract()
th.AssertNoErr(t, err)
- th.AssertEquals(t, n.Name, "vol-001")
+ th.AssertEquals(t, n.Size, 4)
th.AssertEquals(t, n.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22")
}
@@ -158,3 +158,27 @@
err := Delete(ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22")
th.AssertNoErr(t, err)
}
+
+func TestUpdate(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/volumes/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "PUT")
+ th.TestHeader(t, r, "X-Auth-Token", TokenID)
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprintf(w, `
+ {
+ "volume": {
+ "display_name": "vol-002",
+ "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22"
+ }
+ }
+ `)
+ })
+
+ options := &UpdateOpts{Name: "vol-002"}
+ v, err := Update(ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22", options).Extract()
+ th.AssertNoErr(t, err)
+ th.CheckEquals(t, "vol-002", v.Name)
+}
diff --git a/openstack/blockstorage/v1/volumes/util_test.go b/openstack/blockstorage/v1/volumes/util_test.go
new file mode 100644
index 0000000..7de1326
--- /dev/null
+++ b/openstack/blockstorage/v1/volumes/util_test.go
@@ -0,0 +1,37 @@
+package volumes
+
+import (
+ "fmt"
+ "net/http"
+ "testing"
+ "time"
+
+ th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestWaitForStatus(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/volumes/1234", func(w http.ResponseWriter, r *http.Request) {
+ time.Sleep(2 * time.Second)
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprintf(w, `
+ {
+ "volume": {
+ "display_name": "vol-001",
+ "id": "1234",
+ "status":"available"
+ }
+ }`)
+ })
+
+ err := WaitForStatus(ServiceClient(), "1234", "available", 0)
+ if err == nil {
+ t.Errorf("Expected error: 'Time Out in WaitFor'")
+ }
+
+ err = WaitForStatus(ServiceClient(), "1234", "available", 3)
+ th.CheckNoErr(t, err)
+}
diff --git a/openstack/blockstorage/v1/volumetypes/requests.go b/openstack/blockstorage/v1/volumetypes/requests.go
index afe650d..d4f880f 100644
--- a/openstack/blockstorage/v1/volumetypes/requests.go
+++ b/openstack/blockstorage/v1/volumetypes/requests.go
@@ -6,6 +6,12 @@
"github.com/rackspace/gophercloud/pagination"
)
+// CreateOptsBuilder allows extensions to add additional parameters to the
+// Create request.
+type CreateOptsBuilder interface {
+ ToVolumeTypeCreateMap() (map[string]interface{}, error)
+}
+
// CreateOpts are options for creating a volume type.
type CreateOpts struct {
// OPTIONAL. See VolumeType.
@@ -14,26 +20,31 @@
Name string
}
-// Create will create a new volume, optionally wih CreateOpts. To extract the
-// created volume type object, call the Extract method on the CreateResult.
-func Create(client *gophercloud.ServiceClient, opts *CreateOpts) CreateResult {
- type volumeType struct {
- ExtraSpecs map[string]interface{} `json:"extra_specs,omitempty"`
- Name *string `json:"name,omitempty"`
+// ToVolumeTypeCreateMap casts a CreateOpts struct to a map.
+func (opts CreateOpts) ToVolumeTypeCreateMap() (map[string]interface{}, error) {
+ vt := make(map[string]interface{})
+
+ if opts.ExtraSpecs != nil {
+ vt["extra_specs"] = opts.ExtraSpecs
+ }
+ if opts.Name != "" {
+ vt["name"] = opts.Name
}
- type request struct {
- VolumeType volumeType `json:"volume_type"`
- }
+ return map[string]interface{}{"volume_type": vt}, nil
+}
- reqBody := request{
- VolumeType: volumeType{},
- }
-
- reqBody.VolumeType.Name = gophercloud.MaybeString(opts.Name)
- reqBody.VolumeType.ExtraSpecs = opts.ExtraSpecs
-
+// Create will create a new volume. To extract the created volume type object,
+// call the Extract method on the CreateResult.
+func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
var res CreateResult
+
+ reqBody, err := opts.ToVolumeTypeCreateMap()
+ if err != nil {
+ res.Err = err
+ return res
+ }
+
_, res.Err = perigee.Request("POST", createURL(client), perigee.Options{
MoreHeaders: client.Provider.AuthenticatedHeaders(),
OkCodes: []int{200, 201},
diff --git a/openstack/compute/v2/flavors/requests.go b/openstack/compute/v2/flavors/requests.go
index 47eb172..7af11fc 100644
--- a/openstack/compute/v2/flavors/requests.go
+++ b/openstack/compute/v2/flavors/requests.go
@@ -6,41 +6,65 @@
"github.com/rackspace/gophercloud/pagination"
)
-// ListFilterOptions helps control the results returned by the List() function.
+// ListOptsBuilder allows extensions to add additional parameters to the
+// List request.
+type ListOptsBuilder interface {
+ ToFlavorListQuery() (string, error)
+}
+
+// ListOpts helps control the results returned by the List() function.
// For example, a flavor with a minDisk field of 10 will not be returned if you specify MinDisk set to 20.
// Typically, software will use the last ID of the previous call to List to set the Marker for the current call.
-type ListFilterOptions struct {
+type ListOpts struct {
// ChangesSince, if provided, instructs List to return only those things which have changed since the timestamp provided.
- ChangesSince string
+ ChangesSince string `q:"changes-since"`
// MinDisk and MinRAM, if provided, elides flavors which do not meet your criteria.
- MinDisk, MinRAM int
+ MinDisk int `q:"minDisk"`
+ MinRAM int `q:"minRam"`
// Marker and Limit control paging.
// Marker instructs List where to start listing from.
- Marker string
+ Marker string `q:"marker"`
// Limit instructs List to refrain from sending excessively large lists of flavors.
- Limit int
+ Limit int `q:"limit"`
+}
+
+// ToFlavorListQuery formats a ListOpts into a query string.
+func (opts ListOpts) ToFlavorListQuery() (string, error) {
+ q, err := gophercloud.BuildQueryString(opts)
+ if err != nil {
+ return "", err
+ }
+ return q.String(), nil
}
// List instructs OpenStack to provide a list of flavors.
// You may provide criteria by which List curtails its results for easier processing.
-// See ListFilterOptions for more details.
-func List(client *gophercloud.ServiceClient, lfo ListFilterOptions) pagination.Pager {
+// See ListOpts for more details.
+func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
+ url := listURL(client)
+ if opts != nil {
+ query, err := opts.ToFlavorListQuery()
+ if err != nil {
+ return pagination.Pager{Err: err}
+ }
+ url += query
+ }
createPage := func(r pagination.LastHTTPResponse) pagination.Page {
return FlavorPage{pagination.LinkedPageBase{LastHTTPResponse: r}}
}
- return pagination.NewPager(client, listURL(client, lfo), createPage)
+ return pagination.NewPager(client, url, createPage)
}
// Get instructs OpenStack to provide details on a single flavor, identified by its ID.
// Use ExtractFlavor to convert its result into a Flavor.
func Get(client *gophercloud.ServiceClient, id string) GetResult {
var gr GetResult
- gr.Err = perigee.Get(flavorURL(client, id), perigee.Options{
+ gr.Err = perigee.Get(getURL(client, id), perigee.Options{
Results: &gr.Resp,
MoreHeaders: client.Provider.AuthenticatedHeaders(),
})
diff --git a/openstack/compute/v2/flavors/requests_test.go b/openstack/compute/v2/flavors/requests_test.go
index e1b6b4f..bc9b82e 100644
--- a/openstack/compute/v2/flavors/requests_test.go
+++ b/openstack/compute/v2/flavors/requests_test.go
@@ -6,27 +6,20 @@
"reflect"
"testing"
- "github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
- "github.com/rackspace/gophercloud/testhelper"
+ th "github.com/rackspace/gophercloud/testhelper"
+ fake "github.com/rackspace/gophercloud/testhelper/client"
)
const tokenID = "blerb"
-func serviceClient() *gophercloud.ServiceClient {
- return &gophercloud.ServiceClient{
- Provider: &gophercloud.ProviderClient{TokenID: tokenID},
- Endpoint: testhelper.Endpoint(),
- }
-}
-
func TestListFlavors(t *testing.T) {
- testhelper.SetupHTTP()
- defer testhelper.TeardownHTTP()
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
- testhelper.Mux.HandleFunc("/flavors/detail", func(w http.ResponseWriter, r *http.Request) {
- testhelper.TestMethod(t, r, "GET")
- testhelper.TestHeader(t, r, "X-Auth-Token", tokenID)
+ th.Mux.HandleFunc("/flavors/detail", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.Header().Add("Content-Type", "application/json")
r.ParseForm()
@@ -58,7 +51,7 @@
}
]
}
- `, testhelper.Server.URL)
+ `, th.Server.URL)
case "2":
fmt.Fprintf(w, `{ "flavors": [] }`)
default:
@@ -66,9 +59,8 @@
}
})
- client := serviceClient()
pages := 0
- err := List(client, ListFilterOptions{}).EachPage(func(page pagination.Page) (bool, error) {
+ err := List(fake.ServiceClient(), &ListOpts{}).EachPage(func(page pagination.Page) (bool, error) {
pages++
actual, err := ExtractFlavors(page)
@@ -96,12 +88,12 @@
}
func TestGetFlavor(t *testing.T) {
- testhelper.SetupHTTP()
- defer testhelper.TeardownHTTP()
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
- testhelper.Mux.HandleFunc("/flavors/12345", func(w http.ResponseWriter, r *http.Request) {
- testhelper.TestMethod(t, r, "GET")
- testhelper.TestHeader(t, r, "X-Auth-Token", tokenID)
+ th.Mux.HandleFunc("/flavors/12345", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.Header().Add("Content-Type", "application/json")
fmt.Fprintf(w, `
@@ -118,8 +110,7 @@
`)
})
- client := serviceClient()
- actual, err := Get(client, "12345").Extract()
+ actual, err := Get(fake.ServiceClient(), "12345").Extract()
if err != nil {
t.Fatalf("Unable to get flavor: %v", err)
}
diff --git a/openstack/compute/v2/flavors/urls.go b/openstack/compute/v2/flavors/urls.go
index 9e5b562..683c107 100644
--- a/openstack/compute/v2/flavors/urls.go
+++ b/openstack/compute/v2/flavors/urls.go
@@ -1,37 +1,13 @@
package flavors
import (
- "fmt"
- "net/url"
- "strconv"
-
"github.com/rackspace/gophercloud"
)
-func listURL(client *gophercloud.ServiceClient, lfo ListFilterOptions) string {
- v := url.Values{}
- if lfo.ChangesSince != "" {
- v.Set("changes-since", lfo.ChangesSince)
- }
- if lfo.MinDisk != 0 {
- v.Set("minDisk", strconv.Itoa(lfo.MinDisk))
- }
- if lfo.MinRAM != 0 {
- v.Set("minRam", strconv.Itoa(lfo.MinRAM))
- }
- if lfo.Marker != "" {
- v.Set("marker", lfo.Marker)
- }
- if lfo.Limit != 0 {
- v.Set("limit", strconv.Itoa(lfo.Limit))
- }
- tail := ""
- if len(v) > 0 {
- tail = fmt.Sprintf("?%s", v.Encode())
- }
- return client.ServiceURL("flavors", "detail") + tail
+func getURL(client *gophercloud.ServiceClient, id string) string {
+ return client.ServiceURL("flavors", id)
}
-func flavorURL(client *gophercloud.ServiceClient, id string) string {
- return client.ServiceURL("flavors", id)
+func listURL(client *gophercloud.ServiceClient) string {
+ return client.ServiceURL("flavors", "detail")
}
diff --git a/openstack/compute/v2/flavors/urls_test.go b/openstack/compute/v2/flavors/urls_test.go
new file mode 100644
index 0000000..069da24
--- /dev/null
+++ b/openstack/compute/v2/flavors/urls_test.go
@@ -0,0 +1,26 @@
+package flavors
+
+import (
+ "testing"
+
+ "github.com/rackspace/gophercloud"
+ th "github.com/rackspace/gophercloud/testhelper"
+)
+
+const endpoint = "http://localhost:57909/"
+
+func endpointClient() *gophercloud.ServiceClient {
+ return &gophercloud.ServiceClient{Endpoint: endpoint}
+}
+
+func TestGetURL(t *testing.T) {
+ actual := getURL(endpointClient(), "foo")
+ expected := endpoint + "flavors/foo"
+ th.CheckEquals(t, expected, actual)
+}
+
+func TestListURL(t *testing.T) {
+ actual := listURL(endpointClient())
+ expected := endpoint + "flavors/detail"
+ th.CheckEquals(t, expected, actual)
+}
diff --git a/openstack/compute/v2/images/requests.go b/openstack/compute/v2/images/requests.go
index a887cc6..603909c 100644
--- a/openstack/compute/v2/images/requests.go
+++ b/openstack/compute/v2/images/requests.go
@@ -1,25 +1,68 @@
package images
import (
- "github.com/racker/perigee"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
+
+ "github.com/racker/perigee"
)
-// List enumerates the available images.
-func List(client *gophercloud.ServiceClient) pagination.Pager {
+// ListOptsBuilder allows extensions to add additional parameters to the
+// List request.
+type ListOptsBuilder interface {
+ ToImageListQuery() (string, error)
+}
+
+// ListOpts contain options for limiting the number of Images returned from a call to ListDetail.
+type ListOpts struct {
+ // When the image last changed status (in date-time format).
+ ChangesSince string `q:"changes-since"`
+ // The number of Images to return.
+ Limit int `q:"limit"`
+ // UUID of the Image at which to set a marker.
+ Marker string `q:"marker"`
+ // The name of the Image.
+ Name string `q:"name:"`
+ // The name of the Server (in URL format).
+ Server string `q:"server"`
+ // The current status of the Image.
+ Status string `q:"status"`
+ // The value of the type of image (e.g. BASE, SERVER, ALL)
+ Type string `q:"type"`
+}
+
+// ToImageListQuery formats a ListOpts into a query string.
+func (opts ListOpts) ToImageListQuery() (string, error) {
+ q, err := gophercloud.BuildQueryString(opts)
+ if err != nil {
+ return "", err
+ }
+ return q.String(), nil
+}
+
+// ListDetail enumerates the available images.
+func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
+ url := listDetailURL(client)
+ if opts != nil {
+ query, err := opts.ToImageListQuery()
+ if err != nil {
+ return pagination.Pager{Err: err}
+ }
+ url += query
+ }
+
createPage := func(r pagination.LastHTTPResponse) pagination.Page {
return ImagePage{pagination.LinkedPageBase{LastHTTPResponse: r}}
}
- return pagination.NewPager(client, listURL(client), createPage)
+ return pagination.NewPager(client, url, createPage)
}
// Get acquires additional detail about a specific image by ID.
// Use ExtractImage() to intepret the result as an openstack Image.
func Get(client *gophercloud.ServiceClient, id string) GetResult {
var result GetResult
- _, result.Err = perigee.Request("GET", imageURL(client, id), perigee.Options{
+ _, result.Err = perigee.Request("GET", getURL(client, id), perigee.Options{
MoreHeaders: client.Provider.AuthenticatedHeaders(),
Results: &result.Resp,
OkCodes: []int{200},
diff --git a/openstack/compute/v2/images/requests_test.go b/openstack/compute/v2/images/requests_test.go
index 396c21f..9a05f97 100644
--- a/openstack/compute/v2/images/requests_test.go
+++ b/openstack/compute/v2/images/requests_test.go
@@ -1,32 +1,24 @@
package images
import (
+ "encoding/json"
"fmt"
"net/http"
"reflect"
"testing"
- "github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
- "github.com/rackspace/gophercloud/testhelper"
+ th "github.com/rackspace/gophercloud/testhelper"
+ fake "github.com/rackspace/gophercloud/testhelper/client"
)
-const tokenID = "aaaaaa"
-
-func serviceClient() *gophercloud.ServiceClient {
- return &gophercloud.ServiceClient{
- Provider: &gophercloud.ProviderClient{TokenID: tokenID},
- Endpoint: testhelper.Endpoint(),
- }
-}
-
func TestListImages(t *testing.T) {
- testhelper.SetupHTTP()
- defer testhelper.TeardownHTTP()
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
- testhelper.Mux.HandleFunc("/images/detail", func(w http.ResponseWriter, r *http.Request) {
- testhelper.TestMethod(t, r, "GET")
- testhelper.TestHeader(t, r, "X-Auth-Token", tokenID)
+ th.Mux.HandleFunc("/images/detail", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.Header().Add("Content-Type", "application/json")
r.ParseForm()
@@ -70,9 +62,9 @@
}
})
- client := serviceClient()
pages := 0
- err := List(client).EachPage(func(page pagination.Page) (bool, error) {
+ options := &ListOpts{Limit: 2}
+ err := ListDetail(fake.ServiceClient(), options).EachPage(func(page pagination.Page) (bool, error) {
pages++
actual, err := ExtractImages(page)
@@ -119,12 +111,12 @@
}
func TestGetImage(t *testing.T) {
- testhelper.SetupHTTP()
- defer testhelper.TeardownHTTP()
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
- testhelper.Mux.HandleFunc("/images/12345678", func(w http.ResponseWriter, r *http.Request) {
- testhelper.TestMethod(t, r, "GET")
- testhelper.TestHeader(t, r, "X-Auth-Token", tokenID)
+ th.Mux.HandleFunc("/images/12345678", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.Header().Add("Content-Type", "application/json")
fmt.Fprintf(w, `
@@ -145,8 +137,7 @@
`)
})
- client := serviceClient()
- actual, err := Get(client, "12345678").Extract()
+ actual, err := Get(fake.ServiceClient(), "12345678").Extract()
if err != nil {
t.Fatalf("Unexpected error from Get: %v", err)
}
@@ -166,3 +157,19 @@
t.Errorf("Expected %#v, but got %#v", expected, actual)
}
}
+
+func TestNextPageURL(t *testing.T) {
+ var page ImagePage
+ var body map[string]interface{}
+ bodyString := []byte(`{"images":{"links":[{"href":"http://192.154.23.87/12345/images/image3","rel":"bookmark"}]}, "images_links":[{"href":"http://192.154.23.87/12345/images/image4","rel":"next"}]}`)
+ err := json.Unmarshal(bodyString, &body)
+ if err != nil {
+ t.Fatalf("Error unmarshaling data into page body: %v", err)
+ }
+ page.Body = body
+
+ expected := "http://192.154.23.87/12345/images/image4"
+ actual, err := page.NextPageURL()
+ th.AssertNoErr(t, err)
+ th.CheckEquals(t, expected, actual)
+}
diff --git a/openstack/compute/v2/images/urls.go b/openstack/compute/v2/images/urls.go
index 4ae2269..9b3c86d 100644
--- a/openstack/compute/v2/images/urls.go
+++ b/openstack/compute/v2/images/urls.go
@@ -2,10 +2,10 @@
import "github.com/rackspace/gophercloud"
-func listURL(client *gophercloud.ServiceClient) string {
+func listDetailURL(client *gophercloud.ServiceClient) string {
return client.ServiceURL("images", "detail")
}
-func imageURL(client *gophercloud.ServiceClient, id string) string {
+func getURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("images", id)
}
diff --git a/openstack/compute/v2/images/urls_test.go b/openstack/compute/v2/images/urls_test.go
new file mode 100644
index 0000000..b1ab3d6
--- /dev/null
+++ b/openstack/compute/v2/images/urls_test.go
@@ -0,0 +1,26 @@
+package images
+
+import (
+ "testing"
+
+ "github.com/rackspace/gophercloud"
+ th "github.com/rackspace/gophercloud/testhelper"
+)
+
+const endpoint = "http://localhost:57909/"
+
+func endpointClient() *gophercloud.ServiceClient {
+ return &gophercloud.ServiceClient{Endpoint: endpoint}
+}
+
+func TestGetURL(t *testing.T) {
+ actual := getURL(endpointClient(), "foo")
+ expected := endpoint + "images/foo"
+ th.CheckEquals(t, expected, actual)
+}
+
+func TestListDetailURL(t *testing.T) {
+ actual := listDetailURL(endpointClient())
+ expected := endpoint + "images/detail"
+ th.CheckEquals(t, expected, actual)
+}
diff --git a/openstack/compute/v2/servers/requests.go b/openstack/compute/v2/servers/requests.go
index 20ca52e..6f626be 100644
--- a/openstack/compute/v2/servers/requests.go
+++ b/openstack/compute/v2/servers/requests.go
@@ -15,7 +15,7 @@
return ServerPage{pagination.LinkedPageBase{LastHTTPResponse: r}}
}
- return pagination.NewPager(client, detailURL(client), createPage)
+ return pagination.NewPager(client, listDetailURL(client), createPage)
}
// CreateOptsBuilder describes struct types that can be accepted by the Create call.
@@ -140,7 +140,7 @@
// Delete requests that a server previously provisioned be removed from your account.
func Delete(client *gophercloud.ServiceClient, id string) error {
- _, err := perigee.Request("DELETE", serverURL(client, id), perigee.Options{
+ _, err := perigee.Request("DELETE", deleteURL(client, id), perigee.Options{
MoreHeaders: client.Provider.AuthenticatedHeaders(),
OkCodes: []int{204},
})
@@ -150,15 +150,15 @@
// Get requests details on a single server, by ID.
func Get(client *gophercloud.ServiceClient, id string) GetResult {
var result GetResult
- _, result.Err = perigee.Request("GET", serverURL(client, id), perigee.Options{
+ _, result.Err = perigee.Request("GET", getURL(client, id), perigee.Options{
Results: &result.Resp,
MoreHeaders: client.Provider.AuthenticatedHeaders(),
})
return result
}
-// UpdateOptsLike allows extentions to add additional attributes to the Update request.
-type UpdateOptsLike interface {
+// UpdateOptsBuilder allows extentions to add additional attributes to the Update request.
+type UpdateOptsBuilder interface {
ToServerUpdateMap() map[string]interface{}
}
@@ -192,9 +192,9 @@
}
// Update requests that various attributes of the indicated server be changed.
-func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsLike) UpdateResult {
+func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult {
var result UpdateResult
- _, result.Err = perigee.Request("PUT", serverURL(client, id), perigee.Options{
+ _, result.Err = perigee.Request("PUT", updateURL(client, id), perigee.Options{
Results: &result.Resp,
ReqBody: opts.ToServerUpdateMap(),
MoreHeaders: client.Provider.AuthenticatedHeaders(),
diff --git a/openstack/compute/v2/servers/urls.go b/openstack/compute/v2/servers/urls.go
index 52be73e..57587ab 100644
--- a/openstack/compute/v2/servers/urls.go
+++ b/openstack/compute/v2/servers/urls.go
@@ -2,18 +2,30 @@
import "github.com/rackspace/gophercloud"
-func listURL(client *gophercloud.ServiceClient) string {
+func createURL(client *gophercloud.ServiceClient) string {
return client.ServiceURL("servers")
}
-func detailURL(client *gophercloud.ServiceClient) string {
+func listURL(client *gophercloud.ServiceClient) string {
+ return createURL(client)
+}
+
+func listDetailURL(client *gophercloud.ServiceClient) string {
return client.ServiceURL("servers", "detail")
}
-func serverURL(client *gophercloud.ServiceClient, id string) string {
+func deleteURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("servers", id)
}
+func getURL(client *gophercloud.ServiceClient, id string) string {
+ return deleteURL(client, id)
+}
+
+func updateURL(client *gophercloud.ServiceClient, id string) string {
+ return deleteURL(client, id)
+}
+
func actionURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("servers", id, "action")
}
diff --git a/openstack/compute/v2/servers/urls_test.go b/openstack/compute/v2/servers/urls_test.go
new file mode 100644
index 0000000..cc895c9
--- /dev/null
+++ b/openstack/compute/v2/servers/urls_test.go
@@ -0,0 +1,56 @@
+package servers
+
+import (
+ "testing"
+
+ "github.com/rackspace/gophercloud"
+ th "github.com/rackspace/gophercloud/testhelper"
+)
+
+const endpoint = "http://localhost:57909"
+
+func endpointClient() *gophercloud.ServiceClient {
+ return &gophercloud.ServiceClient{Endpoint: endpoint}
+}
+
+func TestCreateURL(t *testing.T) {
+ actual := createURL(endpointClient())
+ expected := endpoint + "servers"
+ th.CheckEquals(t, expected, actual)
+}
+
+func TestListURL(t *testing.T) {
+ actual := listURL(endpointClient())
+ expected := endpoint + "servers"
+ th.CheckEquals(t, expected, actual)
+}
+
+func TestListDetailURL(t *testing.T) {
+ actual := listDetailURL(endpointClient())
+ expected := endpoint + "servers/detail"
+ th.CheckEquals(t, expected, actual)
+}
+
+func TestDeleteURL(t *testing.T) {
+ actual := deleteURL(endpointClient(), "foo")
+ expected := endpoint + "servers/foo"
+ th.CheckEquals(t, expected, actual)
+}
+
+func TestGetURL(t *testing.T) {
+ actual := getURL(endpointClient(), "foo")
+ expected := endpoint + "servers/foo"
+ th.CheckEquals(t, expected, actual)
+}
+
+func TestUpdateURL(t *testing.T) {
+ actual := updateURL(endpointClient(), "foo")
+ expected := endpoint + "servers/foo"
+ th.CheckEquals(t, expected, actual)
+}
+
+func TestActionURL(t *testing.T) {
+ actual := actionURL(endpointClient(), "foo")
+ expected := endpoint + "servers/foo/action"
+ th.CheckEquals(t, expected, actual)
+}
diff --git a/openstack/networking/v2/extensions/external/requests.go b/openstack/networking/v2/extensions/external/requests.go
index afdd428..2f04593 100644
--- a/openstack/networking/v2/extensions/external/requests.go
+++ b/openstack/networking/v2/extensions/external/requests.go
@@ -24,12 +24,15 @@
}
// ToNetworkCreateMap casts a CreateOpts struct to a map.
-func (o CreateOpts) ToNetworkCreateMap() map[string]map[string]interface{} {
- outer := o.Parent.ToNetworkCreateMap()
+func (o CreateOpts) ToNetworkCreateMap() (map[string]interface{}, error) {
+ outer, err := o.Parent.ToNetworkCreateMap()
+ if err != nil {
+ return nil, err
+ }
- outer["network"]["router:external"] = o.External
+ outer["network"].(map[string]interface{})["router:external"] = o.External
- return outer
+ return outer, nil
}
// UpdateOpts is the structure used when updating existing external network
@@ -41,10 +44,13 @@
}
// ToNetworkUpdateMap casts an UpdateOpts struct to a map.
-func (o UpdateOpts) ToNetworkUpdateMap() map[string]map[string]interface{} {
- outer := o.Parent.ToNetworkUpdateMap()
+func (o UpdateOpts) ToNetworkUpdateMap() (map[string]interface{}, error) {
+ outer, err := o.Parent.ToNetworkUpdateMap()
+ if err != nil {
+ return nil, err
+ }
- outer["network"]["router:external"] = o.External
+ outer["network"].(map[string]interface{})["router:external"] = o.External
- return outer
+ return outer, nil
}
diff --git a/openstack/networking/v2/networks/requests.go b/openstack/networking/v2/networks/requests.go
index 39a151a..8cfe4e0 100644
--- a/openstack/networking/v2/networks/requests.go
+++ b/openstack/networking/v2/networks/requests.go
@@ -1,9 +1,10 @@
package networks
import (
- "github.com/racker/perigee"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
+
+ "github.com/racker/perigee"
)
// AdminState gives users a solid type to work with for create and update
@@ -26,6 +27,12 @@
TenantID string
}
+// ListOptsBuilder allows extensions to add additional parameters to the
+// List request.
+type ListOptsBuilder interface {
+ ToNetworkListQuery() (string, error)
+}
+
// ListOpts allows the filtering and sorting of paginated collections through
// the API. Filtering is achieved by passing in struct field values that map to
// the network attributes you want to see returned. SortKey allows you to sort
@@ -44,17 +51,29 @@
SortDir string `q:"sort_dir"`
}
+// ToNetworkListQuery formats a ListOpts into a query string.
+func (opts ListOpts) ToNetworkListQuery() (string, error) {
+ q, err := gophercloud.BuildQueryString(opts)
+ if err != nil {
+ return "", err
+ }
+ return q.String(), nil
+}
+
// List returns a Pager which allows you to iterate over a collection of
// networks. It accepts a ListOpts struct, which allows you to filter and sort
// the returned collection for greater efficiency.
-func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager {
- // Build query parameters
- q, err := gophercloud.BuildQueryString(&opts)
- if err != nil {
- return pagination.Pager{Err: err}
+func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
+ url := listURL(c)
+ if opts != nil {
+ query, err := opts.ToNetworkListQuery()
+ if err != nil {
+ return pagination.Pager{Err: err}
+ }
+ url += query
}
- u := listURL(c) + q.String()
- return pagination.NewPager(c, u, func(r pagination.LastHTTPResponse) pagination.Page {
+
+ return pagination.NewPager(c, url, func(r pagination.LastHTTPResponse) pagination.Page {
return NetworkPage{pagination.LinkedPageBase{LastHTTPResponse: r}}
})
}
@@ -75,7 +94,7 @@
// extensions decorate or modify the common logic, it is useful for them to
// satisfy a basic interface in order for them to be used.
type CreateOptsBuilder interface {
- ToNetworkCreateMap() map[string]map[string]interface{}
+ ToNetworkCreateMap() (map[string]interface{}, error)
}
// CreateOpts is the common options struct used in this package's Create
@@ -83,26 +102,23 @@
type CreateOpts networkOpts
// ToNetworkCreateMap casts a CreateOpts struct to a map.
-func (o CreateOpts) ToNetworkCreateMap() map[string]map[string]interface{} {
- inner := make(map[string]interface{})
+func (opts CreateOpts) ToNetworkCreateMap() (map[string]interface{}, error) {
+ n := make(map[string]interface{})
- if o.AdminStateUp != nil {
- inner["admin_state_up"] = &o.AdminStateUp
+ if opts.AdminStateUp != nil {
+ n["admin_state_up"] = &opts.AdminStateUp
}
- if o.Name != "" {
- inner["name"] = o.Name
+ if opts.Name != "" {
+ n["name"] = opts.Name
}
- if o.Shared != nil {
- inner["shared"] = &o.Shared
+ if opts.Shared != nil {
+ n["shared"] = &opts.Shared
}
- if o.TenantID != "" {
- inner["tenant_id"] = o.TenantID
+ if opts.TenantID != "" {
+ n["tenant_id"] = opts.TenantID
}
- outer := make(map[string]map[string]interface{})
- outer["network"] = inner
-
- return outer
+ return map[string]interface{}{"network": n}, nil
}
// Create accepts a CreateOpts struct and creates a new network using the values
@@ -115,7 +131,11 @@
func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
var res CreateResult
- reqBody := opts.ToNetworkCreateMap()
+ reqBody, err := opts.ToNetworkCreateMap()
+ if err != nil {
+ res.Err = err
+ return res
+ }
// Send request to API
_, res.Err = perigee.Request("POST", createURL(c), perigee.Options{
@@ -132,7 +152,7 @@
// extensions decorate or modify the common logic, it is useful for them to
// satisfy a basic interface in order for them to be used.
type UpdateOptsBuilder interface {
- ToNetworkUpdateMap() map[string]map[string]interface{}
+ ToNetworkUpdateMap() (map[string]interface{}, error)
}
// UpdateOpts is the common options struct used in this package's Update
@@ -140,23 +160,20 @@
type UpdateOpts networkOpts
// ToNetworkUpdateMap casts a UpdateOpts struct to a map.
-func (o UpdateOpts) ToNetworkUpdateMap() map[string]map[string]interface{} {
- inner := make(map[string]interface{})
+func (opts UpdateOpts) ToNetworkUpdateMap() (map[string]interface{}, error) {
+ n := make(map[string]interface{})
- if o.AdminStateUp != nil {
- inner["admin_state_up"] = &o.AdminStateUp
+ if opts.AdminStateUp != nil {
+ n["admin_state_up"] = &opts.AdminStateUp
}
- if o.Name != "" {
- inner["name"] = o.Name
+ if opts.Name != "" {
+ n["name"] = opts.Name
}
- if o.Shared != nil {
- inner["shared"] = &o.Shared
+ if opts.Shared != nil {
+ n["shared"] = &opts.Shared
}
- outer := make(map[string]map[string]interface{})
- outer["network"] = inner
-
- return outer
+ return map[string]interface{}{"network": n}, nil
}
// Update accepts a UpdateOpts struct and updates an existing network using the
@@ -164,7 +181,11 @@
func Update(c *gophercloud.ServiceClient, networkID string, opts UpdateOptsBuilder) UpdateResult {
var res UpdateResult
- reqBody := opts.ToNetworkUpdateMap()
+ reqBody, err := opts.ToNetworkUpdateMap()
+ if err != nil {
+ res.Err = err
+ return res
+ }
// Send request to API
_, res.Err = perigee.Request("PUT", getURL(c, networkID), perigee.Options{
diff --git a/openstack/networking/v2/ports/requests.go b/openstack/networking/v2/ports/requests.go
index 0fbb9f2..c846de9 100644
--- a/openstack/networking/v2/ports/requests.go
+++ b/openstack/networking/v2/ports/requests.go
@@ -1,9 +1,10 @@
package ports
import (
- "github.com/racker/perigee"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
+
+ "github.com/racker/perigee"
)
// AdminState gives users a solid type to work with for create and update
@@ -19,6 +20,12 @@
Down AdminState = &iFalse
)
+// ListOptsBuilder allows extensions to add additional parameters to the
+// List request.
+type ListOptsBuilder interface {
+ ToPortListQuery() (string, error)
+}
+
// ListOpts allows the filtering and sorting of paginated collections through
// the API. Filtering is achieved by passing in struct field values that map to
// the port attributes you want to see returned. SortKey allows you to sort
@@ -40,6 +47,15 @@
SortDir string `q:"sort_dir"`
}
+// ToPortListQuery formats a ListOpts into a query string.
+func (opts ListOpts) ToPortListQuery() (string, error) {
+ q, err := gophercloud.BuildQueryString(opts)
+ if err != nil {
+ return "", err
+ }
+ return q.String(), nil
+}
+
// List returns a Pager which allows you to iterate over a collection of
// ports. It accepts a ListOpts struct, which allows you to filter and sort
// the returned collection for greater efficiency.
@@ -47,15 +63,17 @@
// Default policy settings return only those ports that are owned by the tenant
// who submits the request, unless the request is submitted by an user with
// administrative rights.
-func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager {
- // Build query parameters
- q, err := gophercloud.BuildQueryString(&opts)
- if err != nil {
- return pagination.Pager{Err: err}
+func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
+ url := listURL(c)
+ if opts != nil {
+ query, err := opts.ToPortListQuery()
+ if err != nil {
+ return pagination.Pager{Err: err}
+ }
+ url += query
}
- u := listURL(c) + q.String()
- return pagination.NewPager(c, u, func(r pagination.LastHTTPResponse) pagination.Page {
+ return pagination.NewPager(c, url, func(r pagination.LastHTTPResponse) pagination.Page {
return PortPage{pagination.LinkedPageBase{LastHTTPResponse: r}}
})
}
@@ -71,6 +89,14 @@
return res
}
+// CreateOptsBuilder is the interface options structs have to satisfy in order
+// to be used in the main Create operation in this package. Since many
+// extensions decorate or modify the common logic, it is useful for them to
+// satisfy a basic interface in order for them to be used.
+type CreateOptsBuilder interface {
+ ToPortCreateMap() (map[string]interface{}, error)
+}
+
// CreateOpts represents the attributes used when creating a new port.
type CreateOpts struct {
NetworkID string
@@ -84,51 +110,54 @@
SecurityGroups []string
}
+// ToPortCreateMap casts a CreateOpts struct to a map.
+func (opts CreateOpts) ToPortCreateMap() (map[string]interface{}, error) {
+ p := make(map[string]interface{})
+
+ if opts.NetworkID == "" {
+ return nil, errNetworkIDRequired
+ }
+ p["network_id"] = opts.NetworkID
+
+ if opts.DeviceID != "" {
+ p["device_id"] = opts.DeviceID
+ }
+ if opts.DeviceOwner != "" {
+ p["device_owner"] = opts.DeviceOwner
+ }
+ if opts.FixedIPs != nil {
+ p["fixed_ips"] = opts.FixedIPs
+ }
+ if opts.SecurityGroups != nil {
+ p["security_groups"] = opts.SecurityGroups
+ }
+ if opts.TenantID != "" {
+ p["tenant_id"] = opts.TenantID
+ }
+ if opts.AdminStateUp != nil {
+ p["admin_state_up"] = &opts.AdminStateUp
+ }
+ if opts.Name != "" {
+ p["name"] = opts.Name
+ }
+ if opts.MACAddress != "" {
+ p["mac_address"] = opts.MACAddress
+ }
+
+ return map[string]interface{}{"port": p}, nil
+}
+
// Create accepts a CreateOpts struct and creates a new network using the values
// provided. You must remember to provide a NetworkID value.
-func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult {
+func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
var res CreateResult
- type port struct {
- NetworkID string `json:"network_id"`
- Name *string `json:"name,omitempty"`
- AdminStateUp *bool `json:"admin_state_up,omitempty"`
- MACAddress *string `json:"mac_address,omitempty"`
- FixedIPs interface{} `json:"fixed_ips,omitempty"`
- DeviceID *string `json:"device_id,omitempty"`
- DeviceOwner *string `json:"device_owner,omitempty"`
- TenantID *string `json:"tenant_id,omitempty"`
- SecurityGroups []string `json:"security_groups,omitempty"`
- }
- type request struct {
- Port port `json:"port"`
- }
-
- // Validate
- if opts.NetworkID == "" {
- res.Err = errNetworkIDRequired
+ reqBody, err := opts.ToPortCreateMap()
+ if err != nil {
+ res.Err = err
return res
}
- // Populate request body
- reqBody := request{Port: port{
- NetworkID: opts.NetworkID,
- Name: gophercloud.MaybeString(opts.Name),
- AdminStateUp: opts.AdminStateUp,
- TenantID: gophercloud.MaybeString(opts.TenantID),
- MACAddress: gophercloud.MaybeString(opts.MACAddress),
- DeviceID: gophercloud.MaybeString(opts.DeviceID),
- DeviceOwner: gophercloud.MaybeString(opts.DeviceOwner),
- }}
-
- if opts.FixedIPs != nil {
- reqBody.Port.FixedIPs = opts.FixedIPs
- }
-
- if opts.SecurityGroups != nil {
- reqBody.Port.SecurityGroups = opts.SecurityGroups
- }
-
// Response
_, res.Err = perigee.Request("POST", createURL(c), perigee.Options{
MoreHeaders: c.Provider.AuthenticatedHeaders(),
@@ -141,6 +170,14 @@
return res
}
+// UpdateOptsBuilder is the interface options structs have to satisfy in order
+// to be used in the main Update operation in this package. Since many
+// extensions decorate or modify the common logic, it is useful for them to
+// satisfy a basic interface in order for them to be used.
+type UpdateOptsBuilder interface {
+ ToPortUpdateMap() (map[string]interface{}, error)
+}
+
// UpdateOpts represents the attributes used when updating an existing port.
type UpdateOpts struct {
Name string
@@ -151,39 +188,43 @@
SecurityGroups []string
}
+// ToPortUpdateMap casts an UpdateOpts struct to a map.
+func (opts UpdateOpts) ToPortUpdateMap() (map[string]interface{}, error) {
+ p := make(map[string]interface{})
+
+ if opts.DeviceID != "" {
+ p["device_id"] = opts.DeviceID
+ }
+ if opts.DeviceOwner != "" {
+ p["device_owner"] = opts.DeviceOwner
+ }
+ if opts.FixedIPs != nil {
+ p["fixed_ips"] = opts.FixedIPs
+ }
+ if opts.SecurityGroups != nil {
+ p["security_groups"] = opts.SecurityGroups
+ }
+ if opts.AdminStateUp != nil {
+ p["admin_state_up"] = &opts.AdminStateUp
+ }
+ if opts.Name != "" {
+ p["name"] = opts.Name
+ }
+
+ return map[string]interface{}{"port": p}, nil
+}
+
// Update accepts a UpdateOpts struct and updates an existing port using the
// values provided.
-func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResult {
- type port struct {
- Name *string `json:"name,omitempty"`
- AdminStateUp *bool `json:"admin_state_up,omitempty"`
- FixedIPs interface{} `json:"fixed_ips,omitempty"`
- DeviceID *string `json:"device_id,omitempty"`
- DeviceOwner *string `json:"device_owner,omitempty"`
- SecurityGroups []string `json:"security_groups,omitempty"`
- }
- type request struct {
- Port port `json:"port"`
- }
-
- // Populate request body
- reqBody := request{Port: port{
- Name: gophercloud.MaybeString(opts.Name),
- AdminStateUp: opts.AdminStateUp,
- DeviceID: gophercloud.MaybeString(opts.DeviceID),
- DeviceOwner: gophercloud.MaybeString(opts.DeviceOwner),
- }}
-
- if opts.FixedIPs != nil {
- reqBody.Port.FixedIPs = opts.FixedIPs
- }
-
- if opts.SecurityGroups != nil {
- reqBody.Port.SecurityGroups = opts.SecurityGroups
- }
-
- // Response
+func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult {
var res UpdateResult
+
+ reqBody, err := opts.ToPortUpdateMap()
+ if err != nil {
+ res.Err = err
+ return res
+ }
+
_, res.Err = perigee.Request("PUT", updateURL(c, id), perigee.Options{
MoreHeaders: c.Provider.AuthenticatedHeaders(),
ReqBody: &reqBody,
diff --git a/openstack/networking/v2/subnets/requests.go b/openstack/networking/v2/subnets/requests.go
index a7e6b53..8eed269 100644
--- a/openstack/networking/v2/subnets/requests.go
+++ b/openstack/networking/v2/subnets/requests.go
@@ -1,9 +1,10 @@
package subnets
import (
- "github.com/racker/perigee"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
+
+ "github.com/racker/perigee"
)
// AdminState gives users a solid type to work with for create and update
@@ -19,6 +20,12 @@
Down AdminState = &iFalse
)
+// ListOptsBuilder allows extensions to add additional parameters to the
+// List request.
+type ListOptsBuilder interface {
+ ToSubnetListQuery() (string, error)
+}
+
// ListOpts allows the filtering and sorting of paginated collections through
// the API. Filtering is achieved by passing in struct field values that map to
// the subnet attributes you want to see returned. SortKey allows you to sort
@@ -39,6 +46,15 @@
SortDir string `q:"sort_dir"`
}
+// ToSubnetListQuery formats a ListOpts into a query string.
+func (opts ListOpts) ToSubnetListQuery() (string, error) {
+ q, err := gophercloud.BuildQueryString(opts)
+ if err != nil {
+ return "", err
+ }
+ return q.String(), nil
+}
+
// List returns a Pager which allows you to iterate over a collection of
// subnets. It accepts a ListOpts struct, which allows you to filter and sort
// the returned collection for greater efficiency.
@@ -46,13 +62,15 @@
// Default policy settings return only those subnets that are owned by the tenant
// who submits the request, unless the request is submitted by an user with
// administrative rights.
-func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager {
- // Build query parameters
- query, err := gophercloud.BuildQueryString(&opts)
- if err != nil {
- return pagination.Pager{Err: err}
+func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
+ url := listURL(c)
+ if opts != nil {
+ query, err := opts.ToSubnetListQuery()
+ if err != nil {
+ return pagination.Pager{Err: err}
+ }
+ url += query
}
- url := listURL(c) + query.String()
return pagination.NewPager(c, url, func(r pagination.LastHTTPResponse) pagination.Page {
return SubnetPage{pagination.LinkedPageBase{LastHTTPResponse: r}}
@@ -76,6 +94,14 @@
IPv6 = 6
)
+// CreateOptsBuilder is the interface options structs have to satisfy in order
+// to be used in the main Create operation in this package. Since many
+// extensions decorate or modify the common logic, it is useful for them to
+// satisfy a basic interface in order for them to be used.
+type CreateOptsBuilder interface {
+ ToSubnetCreateMap() (map[string]interface{}, error)
+}
+
// CreateOpts represents the attributes used when creating a new subnet.
type CreateOpts struct {
// Required
@@ -92,61 +118,60 @@
HostRoutes []HostRoute
}
-// Create accepts a CreateOpts struct and creates a new subnet using the values
-// provided. You must remember to provide a valid NetworkID, CIDR and IP version.
-func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult {
- var res CreateResult
+// ToSubnetCreateMap casts a CreateOpts struct to a map.
+func (opts CreateOpts) ToSubnetCreateMap() (map[string]interface{}, error) {
+ s := make(map[string]interface{})
- // Validate required options
if opts.NetworkID == "" {
- res.Err = errNetworkIDRequired
- return res
+ return nil, errNetworkIDRequired
}
if opts.CIDR == "" {
- res.Err = errCIDRRequired
- return res
+ return nil, errCIDRRequired
}
if opts.IPVersion != 0 && opts.IPVersion != IPv4 && opts.IPVersion != IPv6 {
- res.Err = errInvalidIPType
- return res
+ return nil, errInvalidIPType
}
- type subnet struct {
- NetworkID string `json:"network_id"`
- CIDR string `json:"cidr"`
- Name *string `json:"name,omitempty"`
- TenantID *string `json:"tenant_id,omitempty"`
- AllocationPools []AllocationPool `json:"allocation_pools,omitempty"`
- GatewayIP *string `json:"gateway_ip,omitempty"`
- IPVersion int `json:"ip_version,omitempty"`
- EnableDHCP *bool `json:"enable_dhcp,omitempty"`
- DNSNameservers []string `json:"dns_nameservers,omitempty"`
- HostRoutes []HostRoute `json:"host_routes,omitempty"`
- }
- type request struct {
- Subnet subnet `json:"subnet"`
- }
+ s["network_id"] = opts.NetworkID
+ s["cidr"] = opts.CIDR
- reqBody := request{Subnet: subnet{
- NetworkID: opts.NetworkID,
- CIDR: opts.CIDR,
- Name: gophercloud.MaybeString(opts.Name),
- TenantID: gophercloud.MaybeString(opts.TenantID),
- GatewayIP: gophercloud.MaybeString(opts.GatewayIP),
- EnableDHCP: opts.EnableDHCP,
- }}
-
+ if opts.EnableDHCP != nil {
+ s["enable_dhcp"] = &opts.EnableDHCP
+ }
+ if opts.Name != "" {
+ s["name"] = opts.Name
+ }
+ if opts.GatewayIP != "" {
+ s["gateway_ip"] = opts.GatewayIP
+ }
+ if opts.TenantID != "" {
+ s["tenant_id"] = opts.TenantID
+ }
if opts.IPVersion != 0 {
- reqBody.Subnet.IPVersion = opts.IPVersion
+ s["ip_version"] = opts.IPVersion
}
if len(opts.AllocationPools) != 0 {
- reqBody.Subnet.AllocationPools = opts.AllocationPools
+ s["allocation_pools"] = opts.AllocationPools
}
if len(opts.DNSNameservers) != 0 {
- reqBody.Subnet.DNSNameservers = opts.DNSNameservers
+ s["dns_nameservers"] = opts.DNSNameservers
}
if len(opts.HostRoutes) != 0 {
- reqBody.Subnet.HostRoutes = opts.HostRoutes
+ s["host_routes"] = opts.HostRoutes
+ }
+
+ return map[string]interface{}{"subnet": s}, nil
+}
+
+// Create accepts a CreateOpts struct and creates a new subnet using the values
+// provided. You must remember to provide a valid NetworkID, CIDR and IP version.
+func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
+ var res CreateResult
+
+ reqBody, err := opts.ToSubnetCreateMap()
+ if err != nil {
+ res.Err = err
+ return res
}
_, res.Err = perigee.Request("POST", createURL(c), perigee.Options{
@@ -159,6 +184,12 @@
return res
}
+// UpdateOptsBuilder allows extensions to add additional parameters to the
+// Update request.
+type UpdateOptsBuilder interface {
+ ToSubnetUpdateMap() (map[string]interface{}, error)
+}
+
// UpdateOpts represents the attributes used when updating an existing subnet.
type UpdateOpts struct {
Name string
@@ -168,35 +199,40 @@
EnableDHCP *bool
}
+// ToSubnetUpdateMap casts an UpdateOpts struct to a map.
+func (opts UpdateOpts) ToSubnetUpdateMap() (map[string]interface{}, error) {
+ s := make(map[string]interface{})
+
+ if opts.EnableDHCP != nil {
+ s["enable_dhcp"] = &opts.EnableDHCP
+ }
+ if opts.Name != "" {
+ s["name"] = opts.Name
+ }
+ if opts.GatewayIP != "" {
+ s["gateway_ip"] = opts.GatewayIP
+ }
+ if len(opts.DNSNameservers) != 0 {
+ s["dns_nameservers"] = opts.DNSNameservers
+ }
+ if len(opts.HostRoutes) != 0 {
+ s["host_routes"] = opts.HostRoutes
+ }
+
+ return map[string]interface{}{"subnet": s}, nil
+}
+
// Update accepts a UpdateOpts struct and updates an existing subnet using the
// values provided.
-func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResult {
- type subnet struct {
- Name *string `json:"name,omitempty"`
- GatewayIP *string `json:"gateway_ip,omitempty"`
- DNSNameservers []string `json:"dns_nameservers,omitempty"`
- HostRoutes []HostRoute `json:"host_routes,omitempty"`
- EnableDHCP *bool `json:"enable_dhcp,omitempty"`
- }
- type request struct {
- Subnet subnet `json:"subnet"`
- }
-
- reqBody := request{Subnet: subnet{
- Name: gophercloud.MaybeString(opts.Name),
- GatewayIP: gophercloud.MaybeString(opts.GatewayIP),
- EnableDHCP: opts.EnableDHCP,
- }}
-
- if len(opts.DNSNameservers) != 0 {
- reqBody.Subnet.DNSNameservers = opts.DNSNameservers
- }
-
- if len(opts.HostRoutes) != 0 {
- reqBody.Subnet.HostRoutes = opts.HostRoutes
- }
-
+func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult {
var res UpdateResult
+
+ reqBody, err := opts.ToSubnetUpdateMap()
+ if err != nil {
+ res.Err = err
+ return res
+ }
+
_, res.Err = perigee.Request("PUT", updateURL(c, id), perigee.Options{
MoreHeaders: c.Provider.AuthenticatedHeaders(),
ReqBody: &reqBody,
diff --git a/openstack/objectstorage/v1/accounts/requests.go b/openstack/objectstorage/v1/accounts/requests.go
index 5de09da..55fcb05 100644
--- a/openstack/objectstorage/v1/accounts/requests.go
+++ b/openstack/objectstorage/v1/accounts/requests.go
@@ -5,63 +5,103 @@
"github.com/rackspace/gophercloud"
)
-// 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
+// GetOptsBuilder allows extensions to add additional headers to the Get
+// request.
+type GetOptsBuilder interface {
+ ToAccountGetMap() (map[string]string, error)
}
-// Update is a function that creates, updates, or deletes an account's metadata.
-func Update(c *gophercloud.ServiceClient, opts UpdateOpts) UpdateResult {
- headers := c.Provider.AuthenticatedHeaders()
+// GetOpts is a structure that contains parameters for getting an account's
+// metadata.
+type GetOpts struct {
+ Newest bool `h:"X-Newest"`
+}
- for k, v := range opts.Headers {
- headers[k] = v
+// 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
+// ExtractHeaders method on the GetResult.
+func Get(c *gophercloud.ServiceClient, opts GetOptsBuilder) GetResult {
+ var res GetResult
+ h := c.Provider.AuthenticatedHeaders()
+
+ if opts != nil {
+ headers, err := opts.ToAccountGetMap()
+ if err != nil {
+ res.Err = err
+ return res
+ }
+
+ for k, v := range headers {
+ h[k] = v
+ }
}
- for k, v := range opts.Metadata {
- headers["X-Account-Meta-"+k] = v
- }
-
- var res UpdateResult
-
- var resp *perigee.Response
-
- resp, res.Err = perigee.Request("POST", accountURL(c), perigee.Options{
- MoreHeaders: headers,
+ resp, err := perigee.Request("HEAD", getURL(c), perigee.Options{
+ MoreHeaders: h,
OkCodes: []int{204},
})
-
res.Resp = &resp.HttpResponse
-
+ res.Err = err
return res
}
-// GetOpts is a structure that contains parameters for getting an account's metadata.
-type GetOpts struct {
- Headers map[string]string
+// UpdateOptsBuilder allows extensions to add additional headers to the Update
+// request.
+type UpdateOptsBuilder interface {
+ ToAccountUpdateMap() (map[string]string, error)
}
-// 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 *gophercloud.ServiceClient, opts GetOpts) GetResult {
- headers := c.Provider.AuthenticatedHeaders()
+// 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"`
+}
- for k, v := range opts.Headers {
- headers[k] = v
+// 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 ExtractHeaders method on the
+// UpdateResult.
+func Update(c *gophercloud.ServiceClient, opts UpdateOptsBuilder) UpdateResult {
+ var res UpdateResult
+ h := c.Provider.AuthenticatedHeaders()
+
+ if opts != nil {
+ headers, err := opts.ToAccountUpdateMap()
+ if err != nil {
+ res.Err = err
+ return res
+ }
+ for k, v := range headers {
+ h[k] = v
+ }
}
- var res GetResult
- var resp *perigee.Response
-
- resp, res.Err = perigee.Request("HEAD", accountURL(c), perigee.Options{
- MoreHeaders: headers,
- Results: &res.Resp,
+ resp, err := perigee.Request("POST", updateURL(c), perigee.Options{
+ MoreHeaders: h,
OkCodes: []int{204},
})
-
res.Resp = &resp.HttpResponse
-
+ res.Err = err
return res
}
diff --git a/openstack/objectstorage/v1/accounts/requests_test.go b/openstack/objectstorage/v1/accounts/requests_test.go
index 8cb2be8..0090eea 100644
--- a/openstack/objectstorage/v1/accounts/requests_test.go
+++ b/openstack/objectstorage/v1/accounts/requests_test.go
@@ -4,20 +4,20 @@
"net/http"
"testing"
- "github.com/rackspace/gophercloud/testhelper"
+ th "github.com/rackspace/gophercloud/testhelper"
fake "github.com/rackspace/gophercloud/testhelper/client"
)
var metadata = map[string]string{"gophercloud-test": "accounts"}
func TestUpdateAccount(t *testing.T) {
- testhelper.SetupHTTP()
- defer testhelper.TeardownHTTP()
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
- testhelper.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
- testhelper.TestMethod(t, r, "POST")
- testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
- testhelper.TestHeader(t, r, "X-Account-Meta-Gophercloud-Test", "accounts")
+ th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "POST")
+ th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+ th.TestHeader(t, r, "X-Account-Meta-Gophercloud-Test", "accounts")
w.Header().Set("X-Account-Container-Count", "2")
w.Header().Set("X-Account-Bytes-Used", "14")
@@ -26,35 +26,28 @@
w.WriteHeader(http.StatusNoContent)
})
- res := Update(fake.ServiceClient(), UpdateOpts{Metadata: metadata})
-
- metadata := res.ExtractMetadata()
- expected := map[string]string{"Subject": "books"}
-
- testhelper.AssertDeepEquals(t, expected, metadata)
- testhelper.AssertNoErr(t, res.Err)
+ options := &UpdateOpts{Metadata: map[string]string{"gophercloud-test": "accounts"}}
+ _, err := Update(fake.ServiceClient(), options).ExtractHeaders()
+ if err != nil {
+ t.Fatalf("Unable to update account: %v", err)
+ }
}
func TestGetAccount(t *testing.T) {
- testhelper.SetupHTTP()
- defer testhelper.TeardownHTTP()
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
- testhelper.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
- testhelper.TestMethod(t, r, "HEAD")
- testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
-
- w.Header().Set("X-Account-Container-Count", "2")
- w.Header().Set("X-Account-Bytes-Used", "14")
- w.Header().Set("X-Account-Meta-Subject", "books")
-
+ th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "HEAD")
+ th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+ w.Header().Set("X-Account-Meta-Foo", "bar")
w.WriteHeader(http.StatusNoContent)
})
- res := Get(fake.ServiceClient(), GetOpts{})
-
- metadata := res.ExtractMetadata()
- expected := map[string]string{"Subject": "books"}
-
- testhelper.AssertDeepEquals(t, expected, metadata)
- testhelper.AssertNoErr(t, res.Err)
+ expected := map[string]string{"Foo": "bar"}
+ actual, err := Get(fake.ServiceClient(), &GetOpts{}).ExtractMetadata()
+ if err != nil {
+ t.Fatalf("Unable to get account metadata: %v", err)
+ }
+ th.CheckDeepEquals(t, expected, actual)
}
diff --git a/openstack/objectstorage/v1/accounts/results.go b/openstack/objectstorage/v1/accounts/results.go
index 8ec2eff..8ff8183 100644
--- a/openstack/objectstorage/v1/accounts/results.go
+++ b/openstack/objectstorage/v1/accounts/results.go
@@ -1,38 +1,34 @@
package accounts
import (
- "net/http"
"strings"
- "github.com/rackspace/gophercloud"
+ objectstorage "github.com/rackspace/gophercloud/openstack/objectstorage/v1"
)
-type commonResult struct {
- gophercloud.CommonResult
- Resp *http.Response
-}
-
-// GetResult represents the result of a create operation.
+// GetResult is returned from a call to the Get function. See v1.CommonResult.
type GetResult struct {
- commonResult
-}
-
-// UpdateResult represents the result of an update operation.
-type UpdateResult struct {
- commonResult
+ objectstorage.CommonResult
}
// ExtractMetadata is a function that takes a GetResult (of type *http.Response)
// and returns the custom metatdata associated with the account.
-func (res commonResult) ExtractMetadata() map[string]string {
- metadata := make(map[string]string)
+func (gr GetResult) ExtractMetadata() (map[string]string, error) {
+ if gr.Err != nil {
+ return nil, gr.Err
+ }
- for k, v := range res.Resp.Header {
+ metadata := make(map[string]string)
+ for k, v := range gr.Resp.Header {
if strings.HasPrefix(k, "X-Account-Meta-") {
key := strings.TrimPrefix(k, "X-Account-Meta-")
metadata[key] = v[0]
}
}
+ return metadata, nil
+}
- return metadata
+// UpdateResult is returned from a call to the Update function. See v1.CommonResult.
+type UpdateResult struct {
+ objectstorage.CommonResult
}
diff --git a/openstack/objectstorage/v1/accounts/urls.go b/openstack/objectstorage/v1/accounts/urls.go
index 53b1343..9952fe4 100644
--- a/openstack/objectstorage/v1/accounts/urls.go
+++ b/openstack/objectstorage/v1/accounts/urls.go
@@ -2,7 +2,10 @@
import "github.com/rackspace/gophercloud"
-// accountURL returns the URI for making Account requests.
-func accountURL(c *gophercloud.ServiceClient) string {
+func getURL(c *gophercloud.ServiceClient) string {
return c.Endpoint
}
+
+func updateURL(c *gophercloud.ServiceClient) string {
+ return getURL(c)
+}
diff --git a/openstack/objectstorage/v1/accounts/urls_test.go b/openstack/objectstorage/v1/accounts/urls_test.go
index f127a5e..074d52d 100644
--- a/openstack/objectstorage/v1/accounts/urls_test.go
+++ b/openstack/objectstorage/v1/accounts/urls_test.go
@@ -4,14 +4,23 @@
"testing"
"github.com/rackspace/gophercloud"
+ th "github.com/rackspace/gophercloud/testhelper"
)
-func TestAccountURL(t *testing.T) {
- client := gophercloud.ServiceClient{
- Endpoint: "http://localhost:5000/v3/",
- }
- url := accountURL(&client)
- if url != "http://localhost:5000/v3/" {
- t.Errorf("Unexpected service URL generated: [%s]", url)
- }
+const endpoint = "http://localhost:57909/"
+
+func endpointClient() *gophercloud.ServiceClient {
+ return &gophercloud.ServiceClient{Endpoint: endpoint}
+}
+
+func TestGetURL(t *testing.T) {
+ actual := getURL(endpointClient())
+ expected := endpoint
+ th.CheckEquals(t, expected, actual)
+}
+
+func TestUpdateURL(t *testing.T) {
+ actual := updateURL(endpointClient())
+ expected := endpoint
+ th.CheckEquals(t, expected, actual)
}
diff --git a/openstack/objectstorage/v1/common.go b/openstack/objectstorage/v1/common.go
new file mode 100644
index 0000000..1a6c44a
--- /dev/null
+++ b/openstack/objectstorage/v1/common.go
@@ -0,0 +1,25 @@
+package v1
+
+import (
+ "net/http"
+)
+
+// CommonResult is a structure that contains the response and error of a call to an
+// object storage endpoint.
+type CommonResult struct {
+ Resp *http.Response
+ Err error
+}
+
+// ExtractHeaders will extract and return the headers from a *http.Response.
+func (cr CommonResult) ExtractHeaders() (http.Header, error) {
+ if cr.Err != nil {
+ return nil, cr.Err
+ }
+
+ var headers http.Header
+ if cr.Err != nil {
+ return headers, cr.Err
+ }
+ return cr.Resp.Header, nil
+}
diff --git a/openstack/objectstorage/v1/containers/requests.go b/openstack/objectstorage/v1/containers/requests.go
index d772a43..ce3f540 100644
--- a/openstack/objectstorage/v1/containers/requests.go
+++ b/openstack/objectstorage/v1/containers/requests.go
@@ -6,35 +6,50 @@
"github.com/rackspace/gophercloud/pagination"
)
+// ListOptsBuilder allows extensions to add additional parameters to the List
+// request.
+type ListOptsBuilder interface {
+ ToContainerListParams() (bool, string, error)
+}
+
// ListOpts is a structure that holds options for listing containers.
type ListOpts struct {
Full bool
- Limit int `q:"limit"`
- Marker string `q:"marker"`
- EndMarker string `q:"end_marker"`
- Format string `q:"format"`
- Prefix string `q:"prefix"`
- Delimiter [1]byte `q:"delimiter"`
+ 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"`
}
-// 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 *ListOpts) pagination.Pager {
- var headers map[string]string
+// ToContainerListParams formats a ListOpts into a query string and boolean
+// representing whether to list complete information for each container.
+func (opts ListOpts) ToContainerListParams() (bool, string, error) {
+ q, err := gophercloud.BuildQueryString(opts)
+ if err != nil {
+ return false, "", err
+ }
+ return opts.Full, q.String(), nil
+}
- url := accountURL(c)
+// 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 {
- query, err := gophercloud.BuildQueryString(opts)
+ full, query, err := opts.ToContainerListParams()
if err != nil {
return pagination.Pager{Err: err}
}
- url += query.String()
+ url += query
- if !opts.Full {
- headers = map[string]string{"Accept": "text/plain", "Content-Type": "text/plain"}
+ if full {
+ headers = map[string]string{"Accept": "application/json", "Content-Type": "application/json"}
}
- } else {
- headers = map[string]string{"Accept": "text/plain", "Content-Type": "text/plain"}
}
createPage := func(r pagination.LastHTTPResponse) pagination.Page {
@@ -48,6 +63,12 @@
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
@@ -61,13 +82,25 @@
VersionsLocation string `h:"X-Versions-Location"`
}
+// ToContainerCreateMap formats a CreateOpts into a map of headers.
+func (opts CreateOpts) ToContainerCreateMap() (map[string]string, error) {
+ h, err := gophercloud.BuildHeaders(opts)
+ if err != nil {
+ return nil, err
+ }
+ for k, v := range opts.Metadata {
+ h["X-Container-Meta-"+k] = v
+ }
+ return h, nil
+}
+
// Create is a function that creates a new container.
-func Create(c *gophercloud.ServiceClient, containerName string, opts *CreateOpts) CreateResult {
+func Create(c *gophercloud.ServiceClient, containerName string, opts CreateOptsBuilder) CreateResult {
var res CreateResult
h := c.Provider.AuthenticatedHeaders()
if opts != nil {
- headers, err := gophercloud.BuildHeaders(opts)
+ headers, err := opts.ToContainerCreateMap()
if err != nil {
res.Err = err
return res
@@ -76,13 +109,9 @@
for k, v := range headers {
h[k] = v
}
-
- for k, v := range opts.Metadata {
- h["X-Container-Meta-"+k] = v
- }
}
- resp, err := perigee.Request("PUT", containerURL(c, containerName), perigee.Options{
+ resp, err := perigee.Request("PUT", createURL(c, containerName), perigee.Options{
MoreHeaders: h,
OkCodes: []int{201, 204},
})
@@ -94,7 +123,7 @@
// Delete is a function that deletes a container.
func Delete(c *gophercloud.ServiceClient, containerName string) DeleteResult {
var res DeleteResult
- resp, err := perigee.Request("DELETE", containerURL(c, containerName), perigee.Options{
+ resp, err := perigee.Request("DELETE", deleteURL(c, containerName), perigee.Options{
MoreHeaders: c.Provider.AuthenticatedHeaders(),
OkCodes: []int{204},
})
@@ -103,8 +132,14 @@
return res
}
-// UpdateOpts is a structure that holds parameters for updating, creating, or deleting a
-// container's metadata.
+// 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"`
@@ -117,13 +152,26 @@
VersionsLocation string `h:"X-Versions-Location"`
}
-// Update is a function that creates, updates, or deletes a container's metadata.
-func Update(c *gophercloud.ServiceClient, containerName string, opts *UpdateOpts) UpdateResult {
+// ToContainerUpdateMap formats a CreateOpts into a map of headers.
+func (opts UpdateOpts) ToContainerUpdateMap() (map[string]string, error) {
+ h, err := gophercloud.BuildHeaders(opts)
+ if err != nil {
+ return nil, err
+ }
+ for k, v := range opts.Metadata {
+ h["X-Container-Meta-"+k] = v
+ }
+ return h, nil
+}
+
+// Update is a function that creates, updates, or deletes a container's
+// metadata.
+func Update(c *gophercloud.ServiceClient, containerName string, opts UpdateOptsBuilder) UpdateResult {
var res UpdateResult
h := c.Provider.AuthenticatedHeaders()
if opts != nil {
- headers, err := gophercloud.BuildHeaders(opts)
+ headers, err := opts.ToContainerUpdateMap()
if err != nil {
res.Err = err
return res
@@ -132,13 +180,9 @@
for k, v := range headers {
h[k] = v
}
-
- for k, v := range opts.Metadata {
- h["X-Container-Meta-"+k] = v
- }
}
- resp, err := perigee.Request("POST", containerURL(c, containerName), perigee.Options{
+ resp, err := perigee.Request("POST", updateURL(c, containerName), perigee.Options{
MoreHeaders: h,
OkCodes: []int{204},
})
@@ -147,11 +191,12 @@
return res
}
-// Get is a function that retrieves the metadata of a container. To extract just the custom
-// metadata, pass the GetResult response to the ExtractMetadata function.
+// Get is a function that retrieves the metadata of a container. To extract just
+// the custom metadata, pass the GetResult response to the ExtractMetadata
+// function.
func Get(c *gophercloud.ServiceClient, containerName string) GetResult {
var res GetResult
- resp, err := perigee.Request("HEAD", containerURL(c, containerName), perigee.Options{
+ resp, err := perigee.Request("HEAD", getURL(c, containerName), perigee.Options{
MoreHeaders: c.Provider.AuthenticatedHeaders(),
OkCodes: []int{204},
})
diff --git a/openstack/objectstorage/v1/containers/requests_test.go b/openstack/objectstorage/v1/containers/requests_test.go
index 09930d0..9562676 100644
--- a/openstack/objectstorage/v1/containers/requests_test.go
+++ b/openstack/objectstorage/v1/containers/requests_test.go
@@ -6,20 +6,20 @@
"testing"
"github.com/rackspace/gophercloud/pagination"
- "github.com/rackspace/gophercloud/testhelper"
+ th "github.com/rackspace/gophercloud/testhelper"
fake "github.com/rackspace/gophercloud/testhelper/client"
)
var metadata = map[string]string{"gophercloud-test": "containers"}
func TestListContainerInfo(t *testing.T) {
- testhelper.SetupHTTP()
- defer testhelper.TeardownHTTP()
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
- testhelper.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
- testhelper.TestMethod(t, r, "GET")
- testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
- testhelper.TestHeader(t, r, "Accept", "application/json")
+ th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+ th.TestHeader(t, r, "Accept", "application/json")
w.Header().Set("Content-Type", "application/json")
r.ParseForm()
@@ -68,7 +68,7 @@
},
}
- testhelper.CheckDeepEquals(t, expected, actual)
+ th.CheckDeepEquals(t, expected, actual)
return true, nil
})
@@ -79,13 +79,13 @@
}
func TestListContainerNames(t *testing.T) {
- testhelper.SetupHTTP()
- defer testhelper.TeardownHTTP()
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
- testhelper.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
- testhelper.TestMethod(t, r, "GET")
- testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
- testhelper.TestHeader(t, r, "Accept", "text/plain")
+ th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+ th.TestHeader(t, r, "Accept", "text/plain")
w.Header().Set("Content-Type", "text/plain")
r.ParseForm()
@@ -112,7 +112,7 @@
expected := []string{"janeausten", "marktwain"}
- testhelper.CheckDeepEquals(t, expected, actual)
+ th.CheckDeepEquals(t, expected, actual)
return true, nil
})
@@ -123,30 +123,34 @@
}
func TestCreateContainer(t *testing.T) {
- testhelper.SetupHTTP()
- defer testhelper.TeardownHTTP()
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
- testhelper.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
- testhelper.TestMethod(t, r, "PUT")
- testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
- testhelper.TestHeader(t, r, "Accept", "application/json")
+ th.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "PUT")
+ th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+ th.TestHeader(t, r, "Accept", "application/json")
+
+ w.Header().Add("X-Container-Meta-Foo", "bar")
w.WriteHeader(http.StatusNoContent)
})
- _, err := Create(fake.ServiceClient(), "testContainer", nil).ExtractHeaders()
+ options := CreateOpts{ContentType: "application/json", Metadata: map[string]string{"foo": "bar"}}
+ headers, err := Create(fake.ServiceClient(), "testContainer", options).ExtractHeaders()
if err != nil {
t.Fatalf("Unexpected error creating container: %v", err)
}
+ th.CheckEquals(t, "bar", headers["X-Container-Meta-Foo"][0])
}
func TestDeleteContainer(t *testing.T) {
- testhelper.SetupHTTP()
- defer testhelper.TeardownHTTP()
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
- testhelper.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
- testhelper.TestMethod(t, r, "DELETE")
- testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
- testhelper.TestHeader(t, r, "Accept", "application/json")
+ th.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "DELETE")
+ th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+ th.TestHeader(t, r, "Accept", "application/json")
w.WriteHeader(http.StatusNoContent)
})
@@ -157,30 +161,31 @@
}
func TestUpateContainer(t *testing.T) {
- testhelper.SetupHTTP()
- defer testhelper.TeardownHTTP()
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
- testhelper.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
- testhelper.TestMethod(t, r, "POST")
- testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
- testhelper.TestHeader(t, r, "Accept", "application/json")
+ th.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "POST")
+ th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+ th.TestHeader(t, r, "Accept", "application/json")
w.WriteHeader(http.StatusNoContent)
})
- _, err := Update(fake.ServiceClient(), "testContainer", nil).ExtractHeaders()
+ options := &UpdateOpts{Metadata: map[string]string{"foo": "bar"}}
+ _, err := Update(fake.ServiceClient(), "testContainer", options).ExtractHeaders()
if err != nil {
t.Fatalf("Unexpected error updating container metadata: %v", err)
}
}
func TestGetContainer(t *testing.T) {
- testhelper.SetupHTTP()
- defer testhelper.TeardownHTTP()
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
- testhelper.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
- testhelper.TestMethod(t, r, "HEAD")
- testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
- testhelper.TestHeader(t, r, "Accept", "application/json")
+ th.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "HEAD")
+ th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+ th.TestHeader(t, r, "Accept", "application/json")
w.WriteHeader(http.StatusNoContent)
})
diff --git a/openstack/objectstorage/v1/containers/results.go b/openstack/objectstorage/v1/containers/results.go
index be96fca..227a9dc 100644
--- a/openstack/objectstorage/v1/containers/results.go
+++ b/openstack/objectstorage/v1/containers/results.go
@@ -2,11 +2,12 @@
import (
"fmt"
- "net/http"
"strings"
- "github.com/mitchellh/mapstructure"
+ objectstorage "github.com/rackspace/gophercloud/openstack/objectstorage/v1"
"github.com/rackspace/gophercloud/pagination"
+
+ "github.com/mitchellh/mapstructure"
)
// Container represents a container resource.
@@ -97,8 +98,7 @@
// GetResult represents the result of a get operation.
type GetResult struct {
- Resp *http.Response
- Err error
+ objectstorage.CommonResult
}
// ExtractMetadata is a function that takes a GetResult (of type *http.Response)
@@ -117,37 +117,23 @@
return metadata, nil
}
-type commonResult struct {
- Resp *http.Response
- Err error
-}
-
-func (cr commonResult) ExtractHeaders() (http.Header, error) {
- var headers http.Header
- if cr.Err != nil {
- return headers, cr.Err
- }
-
- return cr.Resp.Header, nil
-}
-
// CreateResult represents the result of a create operation. To extract the
// the headers from the HTTP response, you can invoke the 'ExtractHeaders'
// method on the result struct.
type CreateResult struct {
- commonResult
+ objectstorage.CommonResult
}
// UpdateResult represents the result of an update operation. To extract the
// the headers from the HTTP response, you can invoke the 'ExtractHeaders'
// method on the result struct.
type UpdateResult struct {
- commonResult
+ objectstorage.CommonResult
}
// DeleteResult represents the result of a delete operation. To extract the
// the headers from the HTTP response, you can invoke the 'ExtractHeaders'
// method on the result struct.
type DeleteResult struct {
- commonResult
+ objectstorage.CommonResult
}
diff --git a/openstack/objectstorage/v1/containers/urls.go b/openstack/objectstorage/v1/containers/urls.go
index 2a06f95..f864f84 100644
--- a/openstack/objectstorage/v1/containers/urls.go
+++ b/openstack/objectstorage/v1/containers/urls.go
@@ -2,12 +2,22 @@
import "github.com/rackspace/gophercloud"
-// accountURL returns the URI used to list Containers.
-func accountURL(c *gophercloud.ServiceClient) string {
+func listURL(c *gophercloud.ServiceClient) string {
return c.Endpoint
}
-// containerURL returns the URI for making Container requests.
-func containerURL(c *gophercloud.ServiceClient, container string) string {
+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/openstack/objectstorage/v1/containers/urls_test.go b/openstack/objectstorage/v1/containers/urls_test.go
index da37bf6..d043a2a 100644
--- a/openstack/objectstorage/v1/containers/urls_test.go
+++ b/openstack/objectstorage/v1/containers/urls_test.go
@@ -1,29 +1,43 @@
package containers
import (
- "testing"
"github.com/rackspace/gophercloud"
+ th "github.com/rackspace/gophercloud/testhelper"
+ "testing"
)
-func TestAccountURL(t *testing.T) {
- client := gophercloud.ServiceClient{
- Endpoint: "http://localhost:5000/v1/",
- }
- expected := "http://localhost:5000/v1/"
- actual := accountURL(&client)
- if actual != expected {
- t.Errorf("Unexpected service URL generated: [%s]", actual)
- }
+const endpoint = "http://localhost:57909/"
+func endpointClient() *gophercloud.ServiceClient {
+ return &gophercloud.ServiceClient{Endpoint: endpoint}
}
-func TestContainerURL(t *testing.T) {
- client := gophercloud.ServiceClient{
- Endpoint: "http://localhost:5000/v1/",
- }
- expected := "http://localhost:5000/v1/testContainer"
- actual := containerURL(&client, "testContainer")
- if actual != expected {
- t.Errorf("Unexpected service URL generated: [%s]", actual)
- }
+func TestListURL(t *testing.T) {
+ actual := listURL(endpointClient())
+ expected := endpoint
+ th.CheckEquals(t, expected, actual)
+}
+
+func TestCreateURL(t *testing.T) {
+ actual := createURL(endpointClient(), "foo")
+ expected := endpoint + "foo"
+ th.CheckEquals(t, expected, actual)
+}
+
+func TestGetURL(t *testing.T) {
+ actual := getURL(endpointClient(), "foo")
+ expected := endpoint + "foo"
+ th.CheckEquals(t, expected, actual)
+}
+
+func TestDeleteURL(t *testing.T) {
+ actual := deleteURL(endpointClient(), "foo")
+ expected := endpoint + "foo"
+ th.CheckEquals(t, expected, actual)
+}
+
+func TestUpdateURL(t *testing.T) {
+ actual := updateURL(endpointClient(), "foo")
+ expected := endpoint + "foo"
+ th.CheckEquals(t, expected, actual)
}
diff --git a/openstack/objectstorage/v1/objects/requests.go b/openstack/objectstorage/v1/objects/requests.go
index bc21496..3274e04 100644
--- a/openstack/objectstorage/v1/objects/requests.go
+++ b/openstack/objectstorage/v1/objects/requests.go
@@ -10,38 +10,52 @@
"github.com/rackspace/gophercloud/pagination"
)
+// ListOptsBuilder allows extensions to add additional parameters to the List
+// request.
+type ListOptsBuilder interface {
+ ToObjectListParams() (bool, string, error)
+}
+
// ListOpts is a structure that holds parameters for listing objects.
type ListOpts struct {
Full bool
- Limit int `q:"limit"`
- Marker string `q:"marker"`
- EndMarker string `q:"end_marker"`
- Format string `q:"format"`
- Prefix string `q:"prefix"`
- Delimiter [1]byte `q:"delimiter"`
- Path string `q:"path"`
+ Limit int `q:"limit"`
+ Marker string `q:"marker"`
+ EndMarker string `q:"end_marker"`
+ Format string `q:"format"`
+ Prefix string `q:"prefix"`
+ Delimiter string `q:"delimiter"`
+ Path string `q:"path"`
+}
+
+// ToObjectListParams formats a ListOpts into a query string and boolean
+// representing whether to list complete information for each object.
+func (opts ListOpts) ToObjectListParams() (bool, string, error) {
+ q, err := gophercloud.BuildQueryString(opts)
+ if err != nil {
+ return false, "", err
+ }
+ return opts.Full, q.String(), nil
}
// List is a function that retrieves all objects in a container. It also returns the details
// for the container. To extract only the object information or names, pass the ListResult
// response to the ExtractInfo or ExtractNames function, respectively.
-func List(c *gophercloud.ServiceClient, containerName string, opts *ListOpts) pagination.Pager {
- var headers map[string]string
+func List(c *gophercloud.ServiceClient, containerName string, opts ListOptsBuilder) pagination.Pager {
+ headers := map[string]string{"Accept": "text/plain", "Content-Type": "text/plain"}
- url := containerURL(c, containerName)
+ url := listURL(c, containerName)
if opts != nil {
- query, err := gophercloud.BuildQueryString(opts)
+ full, query, err := opts.ToObjectListParams()
if err != nil {
fmt.Printf("Error building query string: %v", err)
return pagination.Pager{Err: err}
}
- url += query.String()
+ url += query
- if !opts.Full {
- headers = map[string]string{"Accept": "text/plain", "Content-Type": "text/plain"}
+ if full {
+ headers = map[string]string{"Accept": "application/json", "Content-Type": "application/json"}
}
- } else {
- headers = map[string]string{"Accept": "text/plain", "Content-Type": "text/plain"}
}
createPage := func(r pagination.LastHTTPResponse) pagination.Page {
@@ -55,6 +69,12 @@
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"`
@@ -67,17 +87,31 @@
Signature string `q:"signature"`
}
+// ToObjectDownloadParams formats a DownloadOpts into a query string and map of
+// headers.
+func (opts ListOpts) 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 *DownloadOpts) DownloadResult {
+// To extract just the content, pass the DownloadResult response to the
+// ExtractContent function.
+func Download(c *gophercloud.ServiceClient, containerName, objectName string, opts DownloadOptsBuilder) DownloadResult {
var res DownloadResult
- url := objectURL(c, containerName, objectName)
+ url := downloadURL(c, containerName, objectName)
h := c.Provider.AuthenticatedHeaders()
if opts != nil {
- headers, err := gophercloud.BuildHeaders(opts)
+ headers, query, err := opts.ToObjectDownloadParams()
if err != nil {
res.Err = err
return res
@@ -87,12 +121,7 @@
h[k] = v
}
- query, err := gophercloud.BuildQueryString(opts)
- if err != nil {
- res.Err = err
- return res
- }
- url += query.String()
+ url += query
}
resp, err := perigee.Request("GET", url, perigee.Options{
@@ -104,6 +133,12 @@
return res
}
+// CreateOptsBuilder allows extensions to add additional parameters to the
+// Create request.
+type CreateOptsBuilder interface {
+ ToObjectCreateParams() (map[string]string, string, error)
+}
+
// CreateOpts is a structure that holds parameters for creating an object.
type CreateOpts struct {
Metadata map[string]string
@@ -124,16 +159,35 @@
Signature string `q:"signature"`
}
+// ToObjectCreateParams formats a CreateOpts into a query string and map of
+// headers.
+func (opts CreateOpts) ToObjectCreateParams() (map[string]string, string, error) {
+ q, err := gophercloud.BuildQueryString(opts)
+ if err != nil {
+ return nil, "", err
+ }
+ h, err := gophercloud.BuildHeaders(opts)
+ if err != nil {
+ return nil, q.String(), err
+ }
+
+ for k, v := range opts.Metadata {
+ h["X-Object-Meta-"+k] = v
+ }
+
+ return h, q.String(), nil
+}
+
// Create is a function that creates a new object or replaces an existing object.
-func Create(c *gophercloud.ServiceClient, containerName, objectName string, content io.Reader, opts *CreateOpts) CreateResult {
+func Create(c *gophercloud.ServiceClient, containerName, objectName string, content io.Reader, opts CreateOptsBuilder) CreateResult {
var res CreateResult
var reqBody []byte
- url := objectURL(c, containerName, objectName)
+ url := createURL(c, containerName, objectName)
h := c.Provider.AuthenticatedHeaders()
if opts != nil {
- headers, err := gophercloud.BuildHeaders(opts)
+ headers, query, err := opts.ToObjectCreateParams()
if err != nil {
res.Err = err
return res
@@ -143,17 +197,7 @@
h[k] = v
}
- for k, v := range opts.Metadata {
- h["X-Object-Meta-"+k] = v
- }
-
- query, err := gophercloud.BuildQueryString(opts)
- if err != nil {
- res.Err = err
- return res
- }
-
- url += query.String()
+ url += query
}
if content != nil {
@@ -175,7 +219,14 @@
return res
}
-// CopyOpts is a structure that holds parameters for copying one object to another.
+// 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"`
@@ -184,29 +235,37 @@
Destination string `h:"Destination,required"`
}
+// ToObjectCopyMap formats a CopyOpts into a map of headers.
+func (opts CopyOpts) ToObjectCopyMap() (map[string]string, error) {
+ if opts.Destination == "" {
+ return nil, fmt.Errorf("Required CopyOpts field 'Destination' not set.")
+ }
+ h, err := gophercloud.BuildHeaders(opts)
+ if err != nil {
+ return nil, err
+ }
+ for k, v := range opts.Metadata {
+ h["X-Object-Meta-"+k] = v
+ }
+ return h, nil
+}
+
// Copy is a function that copies one object to another.
-func Copy(c *gophercloud.ServiceClient, containerName, objectName string, opts *CopyOpts) CopyResult {
+func Copy(c *gophercloud.ServiceClient, containerName, objectName string, opts CopyOptsBuilder) CopyResult {
var res CopyResult
h := c.Provider.AuthenticatedHeaders()
- if opts == nil {
- res.Err = fmt.Errorf("Required CopyOpts field 'Destination' not set.")
- return res
- }
- headers, err := gophercloud.BuildHeaders(opts)
+ headers, err := opts.ToObjectCopyMap()
if err != nil {
res.Err = err
return res
}
+
for k, v := range headers {
h[k] = v
}
- for k, v := range opts.Metadata {
- h["X-Object-Meta-"+k] = v
- }
-
- url := objectURL(c, containerName, objectName)
+ url := copyURL(c, containerName, objectName)
resp, err := perigee.Request("COPY", url, perigee.Options{
MoreHeaders: h,
OkCodes: []int{201},
@@ -215,23 +274,38 @@
return res
}
+// DeleteOptsBuilder allows extensions to add additional parameters to the
+// Delete request.
+type DeleteOptsBuilder interface {
+ ToObjectDeleteQuery() (string, error)
+}
+
// DeleteOpts is a structure that holds parameters for deleting an object.
type DeleteOpts struct {
MultipartManifest string `q:"multipart-manifest"`
}
+// ToObjectDeleteQuery formats a DeleteOpts into a query string.
+func (opts DeleteOpts) ToObjectDeleteQuery() (string, error) {
+ q, err := gophercloud.BuildQueryString(opts)
+ if err != nil {
+ return "", err
+ }
+ return q.String(), nil
+}
+
// Delete is a function that deletes an object.
-func Delete(c *gophercloud.ServiceClient, containerName, objectName string, opts *DeleteOpts) DeleteResult {
+func Delete(c *gophercloud.ServiceClient, containerName, objectName string, opts DeleteOptsBuilder) DeleteResult {
var res DeleteResult
- url := objectURL(c, containerName, objectName)
+ url := deleteURL(c, containerName, objectName)
if opts != nil {
- query, err := gophercloud.BuildQueryString(opts)
+ query, err := opts.ToObjectDeleteQuery()
if err != nil {
res.Err = err
return res
}
- url += query.String()
+ url += query
}
resp, err := perigee.Request("DELETE", url, perigee.Options{
@@ -243,25 +317,40 @@
return res
}
+// GetOptsBuilder allows extensions to add additional parameters to the
+// Get request.
+type GetOptsBuilder interface {
+ ToObjectGetQuery() (string, error)
+}
+
// GetOpts is a structure that holds parameters for getting an object's metadata.
type GetOpts struct {
Expires string `q:"expires"`
Signature string `q:"signature"`
}
+// ToObjectGetQuery formats a GetOpts into a query string.
+func (opts GetOpts) ToObjectGetQuery() (string, error) {
+ q, err := gophercloud.BuildQueryString(opts)
+ if err != nil {
+ return "", err
+ }
+ return q.String(), nil
+}
+
// Get is a function that retrieves the metadata of an object. To extract just the custom
// metadata, pass the GetResult response to the ExtractMetadata function.
-func Get(c *gophercloud.ServiceClient, containerName, objectName string, opts *GetOpts) GetResult {
+func Get(c *gophercloud.ServiceClient, containerName, objectName string, opts GetOptsBuilder) GetResult {
var res GetResult
- url := objectURL(c, containerName, objectName)
+ url := getURL(c, containerName, objectName)
if opts != nil {
- query, err := gophercloud.BuildQueryString(opts)
+ query, err := opts.ToObjectGetQuery()
if err != nil {
res.Err = err
return res
}
- url += query.String()
+ url += query
}
resp, err := perigee.Request("HEAD", url, perigee.Options{
@@ -273,6 +362,12 @@
return res
}
+// UpdateOptsBuilder allows extensions to add additional parameters to the
+// Update request.
+type UpdateOptsBuilder interface {
+ ToObjectUpdateMap() (map[string]string, error)
+}
+
// UpdateOpts is a structure that holds parameters for updating, creating, or deleting an
// object's metadata.
type UpdateOpts struct {
@@ -285,13 +380,25 @@
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 *UpdateOpts) UpdateResult {
+func Update(c *gophercloud.ServiceClient, containerName, objectName string, opts UpdateOptsBuilder) UpdateResult {
var res UpdateResult
h := c.Provider.AuthenticatedHeaders()
if opts != nil {
- headers, err := gophercloud.BuildHeaders(opts)
+ headers, err := opts.ToObjectUpdateMap()
if err != nil {
res.Err = err
return res
@@ -300,13 +407,9 @@
for k, v := range headers {
h[k] = v
}
-
- for k, v := range opts.Metadata {
- h["X-Object-Meta-"+k] = v
- }
}
- url := objectURL(c, containerName, objectName)
+ url := updateURL(c, containerName, objectName)
resp, err := perigee.Request("POST", url, perigee.Options{
MoreHeaders: h,
OkCodes: []int{202},
diff --git a/openstack/objectstorage/v1/objects/requests_test.go b/openstack/objectstorage/v1/objects/requests_test.go
index 089081f..11d7c44 100644
--- a/openstack/objectstorage/v1/objects/requests_test.go
+++ b/openstack/objectstorage/v1/objects/requests_test.go
@@ -166,7 +166,8 @@
})
content := bytes.NewBufferString("Did gyre and gimble in the wabe")
- _, err := Create(fake.ServiceClient(), "testContainer", "testObject", content, nil).ExtractHeaders()
+ options := &CreateOpts{ContentType: "application/json"}
+ _, err := Create(fake.ServiceClient(), "testContainer", "testObject", content, options).ExtractHeaders()
if err != nil {
t.Fatalf("Unexpected error creating object: %v", err)
}
@@ -184,7 +185,8 @@
w.WriteHeader(http.StatusCreated)
})
- _, err := Copy(fake.ServiceClient(), "testContainer", "testObject", &CopyOpts{Destination: "/newTestContainer/newTestObject"}).ExtractHeaders()
+ options := &CopyOpts{Destination: "/newTestContainer/newTestObject"}
+ _, err := Copy(fake.ServiceClient(), "testContainer", "testObject", options).ExtractHeaders()
if err != nil {
t.Fatalf("Unexpected error copying object: %v", err)
}
diff --git a/openstack/objectstorage/v1/objects/results.go b/openstack/objectstorage/v1/objects/results.go
index f7db3ed..1dda7a3 100644
--- a/openstack/objectstorage/v1/objects/results.go
+++ b/openstack/objectstorage/v1/objects/results.go
@@ -3,11 +3,12 @@
import (
"fmt"
"io/ioutil"
- "net/http"
"strings"
- "github.com/mitchellh/mapstructure"
+ objectstorage "github.com/rackspace/gophercloud/openstack/objectstorage/v1"
"github.com/rackspace/gophercloud/pagination"
+
+ "github.com/mitchellh/mapstructure"
)
// Object is a structure that holds information related to a storage object.
@@ -97,7 +98,7 @@
// DownloadResult is a *http.Response that is returned from a call to the Download function.
type DownloadResult struct {
- commonResult
+ objectstorage.CommonResult
}
// ExtractContent is a function that takes a DownloadResult (of type *http.Response)
@@ -117,7 +118,7 @@
// GetResult is a *http.Response that is returned from a call to the Get function.
type GetResult struct {
- commonResult
+ objectstorage.CommonResult
}
// ExtractMetadata is a function that takes a GetResult (of type *http.Response)
@@ -136,36 +137,22 @@
return metadata, nil
}
-type commonResult struct {
- Resp *http.Response
- Err error
-}
-
-func (cr commonResult) ExtractHeaders() (http.Header, error) {
- var headers http.Header
- if cr.Err != nil {
- return headers, cr.Err
- }
-
- return cr.Resp.Header, nil
-}
-
// CreateResult represents the result of a create operation.
type CreateResult struct {
- commonResult
+ objectstorage.CommonResult
}
// UpdateResult represents the result of an update operation.
type UpdateResult struct {
- commonResult
+ objectstorage.CommonResult
}
// DeleteResult represents the result of a delete operation.
type DeleteResult struct {
- commonResult
+ objectstorage.CommonResult
}
// CopyResult represents the result of a copy operation.
type CopyResult struct {
- commonResult
+ objectstorage.CommonResult
}
diff --git a/openstack/objectstorage/v1/objects/urls.go b/openstack/objectstorage/v1/objects/urls.go
index a377960..d2ec62c 100644
--- a/openstack/objectstorage/v1/objects/urls.go
+++ b/openstack/objectstorage/v1/objects/urls.go
@@ -1,13 +1,33 @@
package objects
-import "github.com/rackspace/gophercloud"
+import (
+ "github.com/rackspace/gophercloud"
+)
-// objectURL returns the URI for making Object requests.
-func objectURL(c *gophercloud.ServiceClient, container, object string) string {
+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)
}
-// containerURL returns the URI for making Container requests.
-func containerURL(c *gophercloud.ServiceClient, container string) string {
- return c.ServiceURL(container)
+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)
}
diff --git a/openstack/objectstorage/v1/objects/urls_test.go b/openstack/objectstorage/v1/objects/urls_test.go
index 89d1cb1..1dcfe35 100644
--- a/openstack/objectstorage/v1/objects/urls_test.go
+++ b/openstack/objectstorage/v1/objects/urls_test.go
@@ -2,27 +2,55 @@
import (
"testing"
+
"github.com/rackspace/gophercloud"
+ th "github.com/rackspace/gophercloud/testhelper"
)
-func TestContainerURL(t *testing.T) {
- client := gophercloud.ServiceClient{
- Endpoint: "http://localhost:5000/v1/",
- }
- expected := "http://localhost:5000/v1/testContainer"
- actual := containerURL(&client, "testContainer")
- if actual != expected {
- t.Errorf("Unexpected service URL generated: %s", actual)
- }
+const endpoint = "http://localhost:57909/"
+
+func endpointClient() *gophercloud.ServiceClient {
+ return &gophercloud.ServiceClient{Endpoint: endpoint}
}
-func TestObjectURL(t *testing.T) {
- client := gophercloud.ServiceClient{
- Endpoint: "http://localhost:5000/v1/",
- }
- expected := "http://localhost:5000/v1/testContainer/testObject"
- actual := objectURL(&client, "testContainer", "testObject")
- if actual != expected {
- t.Errorf("Unexpected service URL generated: %s", actual)
- }
+func TestListURL(t *testing.T) {
+ actual := listURL(endpointClient(), "foo")
+ expected := endpoint + "foo"
+ th.CheckEquals(t, expected, actual)
+}
+
+func TestCopyURL(t *testing.T) {
+ actual := copyURL(endpointClient(), "foo", "bar")
+ expected := endpoint + "foo/bar"
+ th.CheckEquals(t, expected, actual)
+}
+
+func TestCreateURL(t *testing.T) {
+ actual := createURL(endpointClient(), "foo", "bar")
+ expected := endpoint + "foo/bar"
+ th.CheckEquals(t, expected, actual)
+}
+
+func TestGetURL(t *testing.T) {
+ actual := getURL(endpointClient(), "foo", "bar")
+ expected := endpoint + "foo/bar"
+ th.CheckEquals(t, expected, actual)
+}
+
+func TestDeleteURL(t *testing.T) {
+ actual := deleteURL(endpointClient(), "foo", "bar")
+ expected := endpoint + "foo/bar"
+ th.CheckEquals(t, expected, actual)
+}
+
+func TestDownloadURL(t *testing.T) {
+ actual := downloadURL(endpointClient(), "foo", "bar")
+ expected := endpoint + "foo/bar"
+ th.CheckEquals(t, expected, actual)
+}
+
+func TestUpdateURL(t *testing.T) {
+ actual := updateURL(endpointClient(), "foo", "bar")
+ expected := endpoint + "foo/bar"
+ th.CheckEquals(t, expected, actual)
}
diff --git a/params_test.go b/params_test.go
index 03eaefc..9f1d3bd 100644
--- a/params_test.go
+++ b/params_test.go
@@ -2,58 +2,141 @@
import (
"net/url"
+ "reflect"
"testing"
+ "time"
th "github.com/rackspace/gophercloud/testhelper"
)
-func TestMaybeStringWithNonEmptyString(t *testing.T) {
- testString := "carol"
- expected := &testString
- actual := MaybeString("carol")
- th.CheckDeepEquals(t, actual, expected)
-}
-
-func TestMaybeStringWithEmptyString(t *testing.T) {
+func TestMaybeString(t *testing.T) {
+ testString := ""
var expected *string
- actual := MaybeString("")
- th.CheckDeepEquals(t, actual, expected)
+ actual := MaybeString(testString)
+ th.CheckDeepEquals(t, expected, actual)
+
+ testString = "carol"
+ expected = &testString
+ actual = MaybeString(testString)
+ th.CheckDeepEquals(t, expected, actual)
}
-func TestBuildQueryStringWithPointerToStruct(t *testing.T) {
- expected := &url.URL{
- RawQuery: "j=2&r=red",
- }
+func TestMaybeInt(t *testing.T) {
+ testInt := 0
+ var expected *int
+ actual := MaybeInt(testInt)
+ th.CheckDeepEquals(t, expected, actual)
- type Opts struct {
+ testInt = 4
+ expected = &testInt
+ actual = MaybeInt(testInt)
+ th.CheckDeepEquals(t, expected, actual)
+}
+
+func TestBuildQueryString(t *testing.T) {
+ opts := struct {
J int `q:"j"`
- R string `q:"r"`
- C bool
+ R string `q:"r,required"`
+ C bool `q:"c"`
+ }{
+ J: 2,
+ R: "red",
+ C: true,
}
-
- opts := Opts{J: 2, R: "red"}
-
+ expected := &url.URL{RawQuery: "j=2&r=red&c=true"}
actual, err := BuildQueryString(&opts)
if err != nil {
t.Errorf("Error building query string: %v", err)
}
+ th.CheckDeepEquals(t, expected, actual)
- th.CheckDeepEquals(t, actual, expected)
-}
-
-func TestBuildQueryStringWithoutRequiredFieldSet(t *testing.T) {
- type Opts struct {
+ opts = struct {
J int `q:"j"`
R string `q:"r,required"`
- C bool
+ C bool `q:"c"`
+ }{
+ J: 2,
+ C: true,
}
-
- opts := Opts{J: 2, C: true}
-
- _, err := BuildQueryString(&opts)
+ _, err = BuildQueryString(&opts)
if err == nil {
- t.Error("Unexpected result: There should be an error thrown when a required field isn't set.")
+ t.Errorf("Expected error: 'Required field not set'")
+ }
+ th.CheckDeepEquals(t, expected, actual)
+
+ _, err = BuildQueryString(map[string]interface{}{"Number": 4})
+ if err == nil {
+ t.Errorf("Expected error: 'Options type is not a struct'")
+ }
+}
+
+func TestBuildHeaders(t *testing.T) {
+ testStruct := struct {
+ Accept string `h:"Accept"`
+ Num int `h:"Number,required"`
+ Style bool `h:"Style"`
+ }{
+ Accept: "application/json",
+ Num: 4,
+ Style: true,
+ }
+ expected := map[string]string{"Accept": "application/json", "Number": "4", "Style": "true"}
+ actual, err := BuildHeaders(&testStruct)
+ th.CheckNoErr(t, err)
+ th.CheckDeepEquals(t, expected, actual)
+
+ testStruct.Num = 0
+ _, err = BuildHeaders(&testStruct)
+ if err == nil {
+ t.Errorf("Expected error: 'Required header not set'")
}
- t.Log(err)
+ _, err = BuildHeaders(map[string]interface{}{"Number": 4})
+ if err == nil {
+ t.Errorf("Expected error: 'Options type is not a struct'")
+ }
+}
+
+func TestIsZero(t *testing.T) {
+ var testMap map[string]interface{}
+ testMapValue := reflect.ValueOf(testMap)
+ expected := true
+ actual := isZero(testMapValue)
+ th.CheckEquals(t, expected, actual)
+ testMap = map[string]interface{}{"empty": false}
+ testMapValue = reflect.ValueOf(testMap)
+ expected = false
+ actual = isZero(testMapValue)
+ th.CheckEquals(t, expected, actual)
+
+ var testArray [2]string
+ testArrayValue := reflect.ValueOf(testArray)
+ expected = true
+ actual = isZero(testArrayValue)
+ th.CheckEquals(t, expected, actual)
+ testArray = [2]string{"one", "two"}
+ testArrayValue = reflect.ValueOf(testArray)
+ expected = false
+ actual = isZero(testArrayValue)
+ th.CheckEquals(t, expected, actual)
+
+ var testStruct struct {
+ A string
+ B time.Time
+ }
+ testStructValue := reflect.ValueOf(testStruct)
+ expected = true
+ actual = isZero(testStructValue)
+ th.CheckEquals(t, expected, actual)
+ testStruct = struct {
+ A string
+ B time.Time
+ }{
+ B: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC),
+ }
+ testStructValue = reflect.ValueOf(testStruct)
+ expected = false
+ actual = isZero(testStructValue)
+ th.CheckEquals(t, expected, actual)
+
}
diff --git a/provider_client_test.go b/provider_client_test.go
new file mode 100644
index 0000000..b260246
--- /dev/null
+++ b/provider_client_test.go
@@ -0,0 +1,16 @@
+package gophercloud
+
+import (
+ "testing"
+
+ th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestAuthenticatedHeaders(t *testing.T) {
+ p := &ProviderClient{
+ TokenID: "1234",
+ }
+ expected := map[string]string{"X-Auth-Token": "1234"}
+ actual := p.AuthenticatedHeaders()
+ th.CheckDeepEquals(t, expected, actual)
+}
diff --git a/service_client_test.go b/service_client_test.go
new file mode 100644
index 0000000..84beb3f
--- /dev/null
+++ b/service_client_test.go
@@ -0,0 +1,14 @@
+package gophercloud
+
+import (
+ "testing"
+
+ th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestServiceURL(t *testing.T) {
+ c := &ServiceClient{Endpoint: "http://123.45.67.8/"}
+ expected := "http://123.45.67.8/more/parts/here"
+ actual := c.ServiceURL("more", "parts", "here")
+ th.CheckEquals(t, expected, actual)
+}
diff --git a/util_test.go b/util_test.go
new file mode 100644
index 0000000..6fbd920
--- /dev/null
+++ b/util_test.go
@@ -0,0 +1,21 @@
+package gophercloud
+
+import (
+ "testing"
+
+ th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestWaitFor(t *testing.T) {
+ err := WaitFor(0, func() (bool, error) {
+ return true, nil
+ })
+ if err == nil {
+ t.Errorf("Expected error: 'Time out in WaitFor'")
+ }
+
+ err = WaitFor(5, func() (bool, error) {
+ return true, nil
+ })
+ th.CheckNoErr(t, err)
+}