Choose a version from an API base endpoint.
diff --git a/openstack/utils/choose_version.go b/openstack/utils/choose_version.go
new file mode 100644
index 0000000..e1ddf6b
--- /dev/null
+++ b/openstack/utils/choose_version.go
@@ -0,0 +1,81 @@
+package utils
+
+import (
+ "fmt"
+
+ "github.com/racker/perigee"
+)
+
+// Version is a supported API version, corresponding to a vN package within the appropriate service.
+type Version struct {
+ ID string
+ Priority int
+}
+
+// ChooseVersion queries the base endpoint of a API to choose the most recent non-experimental alternative from a service's
+// published versions.
+// It returns the highest-Priority Version among the alternatives that are provided, as well as its corresponding endpoint.
+func ChooseVersion(baseEndpoint string, recognized []*Version) (*Version, string, error) {
+ type linkResp struct {
+ Href string `json:"href"`
+ Rel string `json:"rel"`
+ }
+
+ type valueResp struct {
+ ID string `json:"id"`
+ Status string `json:"status"`
+ Links []linkResp `json:"links"`
+ }
+
+ type versionsResp struct {
+ Values []valueResp `json:"values"`
+ }
+
+ type response struct {
+ Versions versionsResp `json:"versions"`
+ }
+
+ var resp response
+ _, err := perigee.Request("GET", baseEndpoint, perigee.Options{
+ Results: &resp,
+ OkCodes: []int{200},
+ })
+
+ if err != nil {
+ return nil, "", err
+ }
+
+ byID := make(map[string]*Version)
+ for _, version := range recognized {
+ byID[version.ID] = version
+ }
+
+ var highest *Version
+ var endpoint string
+
+ for _, value := range resp.Versions.Values {
+ if matching, ok := byID[value.ID]; ok {
+ if highest == nil || matching.Priority > highest.Priority {
+ highest = matching
+
+ found := false
+ for _, link := range value.Links {
+ if link.Rel == "self" {
+ found = true
+ endpoint = link.Href
+ }
+ }
+
+ if !found {
+ return nil, "", fmt.Errorf("Endpoint missing in version %s response from %s", value.ID, baseEndpoint)
+ }
+ }
+ }
+ }
+
+ if highest == nil || endpoint == "" {
+ return nil, "", fmt.Errorf("No supported version available from endpoint %s", baseEndpoint)
+ }
+
+ return highest, endpoint, nil
+}
diff --git a/openstack/utils/choose_version_test.go b/openstack/utils/choose_version_test.go
new file mode 100644
index 0000000..a252790
--- /dev/null
+++ b/openstack/utils/choose_version_test.go
@@ -0,0 +1,57 @@
+package utils
+
+import (
+ "fmt"
+ "net/http"
+ "testing"
+
+ "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestChooseVersion(t *testing.T) {
+ testhelper.SetupHTTP()
+ defer testhelper.TeardownHTTP()
+
+ testhelper.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintf(w, `
+ {
+ "versions": {
+ "values": [
+ {
+ "status": "stable",
+ "id": "v3.0",
+ "links": [
+ { "href": "https://example.com:1000/", "rel": "self" }
+ ]
+ },
+ {
+ "status": "stable",
+ "id": "v2.0",
+ "links": [
+ { "href": "https://example.com:2000/", "rel": "self" }
+ ]
+ }
+ ]
+ }
+ }
+ `)
+ })
+
+ v2 := &Version{ID: "v2.0", Priority: 2}
+ v3 := &Version{ID: "v3.0", Priority: 3}
+
+ v, endpoint, err := ChooseVersion(testhelper.Endpoint(), []*Version{v2, v3})
+
+ if err != nil {
+ t.Fatalf("Unexpected error from ChooseVersion: %v", err)
+ }
+
+ if v != v3 {
+ t.Errorf("Expected %#v to win, but %#v did instead", v3, v)
+ }
+
+ expected := "https://example.com:1000/"
+ if endpoint != expected {
+ t.Errorf("Expected endpoint [%s], but was [%s] instead", expected, endpoint)
+ }
+}