Merge pull request #160 from jrperritt/cf-refactor

Cf refactor.

I'm making a command decision to merge this PR.  It's baked long enough, and people are wanting to use it.  The only issues were documentation related (if memory serves), which is not going to break the success of the project.  We'll just have to chalk it up to technical debt and move on.
diff --git a/acceptance/openstack/compute_test.go b/acceptance/openstack/compute_test.go
index d7c0eba..f03c0f9 100644
--- a/acceptance/openstack/compute_test.go
+++ b/acceptance/openstack/compute_test.go
@@ -111,7 +111,7 @@
 
 		client := flavors.NewClient(ep.PublicURL, ts.a, ts.o)
 
-		listResults, err := flavors.List(client)
+		listResults, err := flavors.List(client, flavors.ListFilterOptions{})
 		if err != nil {
 			t.Error(err)
 			return
@@ -130,7 +130,32 @@
 		}
 	}
 	ts.w.Flush()
-	fmt.Printf("--------\n%d images listed.\n", n)
+	fmt.Printf("--------\n%d flavors listed.\n", n)
+}
+
+func TestGetFlavor(t *testing.T) {
+	ts, err := setupForCRUD()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	region := os.Getenv("OS_REGION_NAME")
+	for _, ep := range ts.eps {
+		if (region != "") && (region != ep.Region) {
+			continue
+		}
+		client := flavors.NewClient(ep.PublicURL, ts.a, ts.o)
+
+		getResults, err := flavors.Get(client, ts.flavorId)
+		if err != nil {
+			t.Fatal(err)
+		}
+		flav, err := flavors.GetFlavor(getResults)
+		if err != nil {
+			t.Fatal(err)
+		}
+		fmt.Printf("%#v\n", flav)
+	}
 }
 
 func TestCreateDestroyServer(t *testing.T) {
diff --git a/openstack/compute/flavors/client.go b/openstack/compute/flavors/client.go
index 60c7cd1..2c64908 100644
--- a/openstack/compute/flavors/client.go
+++ b/openstack/compute/flavors/client.go
@@ -1,7 +1,10 @@
 package flavors
 
 import (
+	"fmt"
+	"net/url"
 	"github.com/rackspace/gophercloud/openstack/identity"
+	"strconv"
 )
 
 type Client struct {
@@ -18,8 +21,32 @@
 	}
 }
 
-func (c *Client) getListUrl() string {
-	return c.endpoint + "/flavors/detail"
+func (c *Client) getListUrl(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 fmt.Sprintf("%s/flavors/detail%s", c.endpoint, tail)
+}
+
+func (c *Client) getGetUrl(id string) string {
+	return fmt.Sprintf("%s/flavors/%s", c.endpoint, id)
 }
 
 func (c *Client) getListHeaders() (map[string]string, error) {
diff --git a/openstack/compute/flavors/flavors.go b/openstack/compute/flavors/flavors.go
index 81996a5..146bcc4 100644
--- a/openstack/compute/flavors/flavors.go
+++ b/openstack/compute/flavors/flavors.go
@@ -35,6 +35,7 @@
 	return v, nil
 }
 
+// GetFlavors provides access to the list of flavors returned by the List function.
 func GetFlavors(lr ListResults) ([]Flavor, error) {
 	fa, ok := lr["flavors"]
 	if !ok {
@@ -60,3 +61,23 @@
 	}
 	return flavors, nil
 }
+
+// GetFlavor provides access to the individual flavor returned by the Get function.
+func GetFlavor(gr GetResults) (*Flavor, error) {
+	f, ok := gr["flavor"]
+	if !ok {
+		return nil, ErrNotImplemented
+	}
+
+	flav := new(Flavor)
+	cfg := &mapstructure.DecoderConfig{
+		DecodeHook: defaulter,
+		Result: flav,
+	}
+	decoder, err := mapstructure.NewDecoder(cfg)
+	if err != nil {
+		return flav, err
+	}
+	err = decoder.Decode(f)
+	return flav, err
+}
diff --git a/openstack/compute/flavors/requests.go b/openstack/compute/flavors/requests.go
index f4dd072..3758206 100644
--- a/openstack/compute/flavors/requests.go
+++ b/openstack/compute/flavors/requests.go
@@ -8,8 +8,27 @@
 var ErrNotImplemented = fmt.Errorf("Flavors functionality not implemented.")
 
 type ListResults map[string]interface{}
+type GetResults map[string]interface{}
 
-func List(c *Client) (ListResults, error) {
+// ListFilterOptions helps control the results returned by the List() function.
+// ChangesSince, if provided, instructs List to return only those things which have changed since the timestamp provided.
+// MinDisk and MinRam, if provided, elides flavors which do not meet your criteria.
+// For example, a flavor with a minDisk field of 10 will not be returned if you specify MinDisk set to 20.
+// Marker and Limit control paging.
+// Limit instructs List to refrain from sending excessively large lists of flavors.
+// Marker instructs List where to start listing from.
+// Typically, software will use the last ID of the previous call to List to set the Marker for the current call.
+type ListFilterOptions struct {
+	ChangesSince string
+	MinDisk, MinRam int
+	Marker string
+	Limit int
+}
+
+// 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(c *Client, lfo ListFilterOptions) (ListResults, error) {
 	var lr ListResults
 
 	h, err := c.getListHeaders()
@@ -17,9 +36,23 @@
 		return nil, err
 	}
 
-	err = perigee.Get(c.getListUrl(), perigee.Options{
+	err = perigee.Get(c.getListUrl(lfo), perigee.Options{
 		Results:     &lr,
 		MoreHeaders: h,
 	})
 	return lr, err
 }
+
+// Get instructs OpenStack to provide details on a single flavor, identified by its ID.
+func Get(c *Client, id string) (GetResults, error) {
+	var gr GetResults
+	h, err := c.getListHeaders()	// same for Get Flavor API
+	if err != nil {
+		return gr, err
+	}
+	err = perigee.Get(c.getGetUrl(id), perigee.Options{
+		Results: &gr,
+		MoreHeaders: h,
+	})
+	return gr, err
+}