Moving calls to client helper while I'm at it
diff --git a/_site/openstack/utils/choose_version.go b/_site/openstack/utils/choose_version.go
new file mode 100644
index 0000000..753f8f8
--- /dev/null
+++ b/_site/openstack/utils/choose_version.go
@@ -0,0 +1,114 @@
+package utils
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/racker/perigee"
+)
+
+// Version is a supported API version, corresponding to a vN package within the appropriate service.
+type Version struct {
+	ID       string
+	Suffix   string
+	Priority int
+}
+
+var goodStatus = map[string]bool{
+	"current":   true,
+	"supported": true,
+	"stable":    true,
+}
+
+// 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(identityBase string, identityEndpoint 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"`
+	}
+
+	normalize := func(endpoint string) string {
+		if !strings.HasSuffix(endpoint, "/") {
+			return endpoint + "/"
+		}
+		return endpoint
+	}
+	identityEndpoint = normalize(identityEndpoint)
+
+	// If a full endpoint is specified, check version suffixes for a match first.
+	for _, v := range recognized {
+		if strings.HasSuffix(identityEndpoint, v.Suffix) {
+			return v, identityEndpoint, nil
+		}
+	}
+
+	var resp response
+	_, err := perigee.Request("GET", identityBase, perigee.Options{
+		Results: &resp,
+		OkCodes: []int{200, 300},
+	})
+
+	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 {
+		href := ""
+		for _, link := range value.Links {
+			if link.Rel == "self" {
+				href = normalize(link.Href)
+			}
+		}
+
+		if matching, ok := byID[value.ID]; ok {
+			// Prefer a version that exactly matches the provided endpoint.
+			if href == identityEndpoint {
+				if href == "" {
+					return nil, "", fmt.Errorf("Endpoint missing in version %s response from %s", value.ID, identityBase)
+				}
+				return matching, href, nil
+			}
+
+			// Otherwise, find the highest-priority version with a whitelisted status.
+			if goodStatus[strings.ToLower(value.Status)] {
+				if highest == nil || matching.Priority > highest.Priority {
+					highest = matching
+					endpoint = href
+				}
+			}
+		}
+	}
+
+	if highest == nil {
+		return nil, "", fmt.Errorf("No supported version available from endpoint %s", identityBase)
+	}
+	if endpoint == "" {
+		return nil, "", fmt.Errorf("Endpoint missing in version %s response from %s", highest.ID, identityBase)
+	}
+
+	return highest, endpoint, nil
+}
diff --git a/_site/openstack/utils/choose_version_test.go b/_site/openstack/utils/choose_version_test.go
new file mode 100644
index 0000000..9552696
--- /dev/null
+++ b/_site/openstack/utils/choose_version_test.go
@@ -0,0 +1,105 @@
+package utils
+
+import (
+	"fmt"
+	"net/http"
+	"testing"
+
+	"github.com/rackspace/gophercloud/testhelper"
+)
+
+func setupVersionHandler() {
+	testhelper.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+		fmt.Fprintf(w, `
+			{
+				"versions": {
+					"values": [
+						{
+							"status": "stable",
+							"id": "v3.0",
+							"links": [
+								{ "href": "%s/v3.0", "rel": "self" }
+							]
+						},
+						{
+							"status": "stable",
+							"id": "v2.0",
+							"links": [
+								{ "href": "%s/v2.0", "rel": "self" }
+							]
+						}
+					]
+				}
+			}
+		`, testhelper.Server.URL, testhelper.Server.URL)
+	})
+}
+
+func TestChooseVersion(t *testing.T) {
+	testhelper.SetupHTTP()
+	defer testhelper.TeardownHTTP()
+	setupVersionHandler()
+
+	v2 := &Version{ID: "v2.0", Priority: 2, Suffix: "blarg"}
+	v3 := &Version{ID: "v3.0", Priority: 3, Suffix: "hargl"}
+
+	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 := testhelper.Endpoint() + "v3.0/"
+	if endpoint != expected {
+		t.Errorf("Expected endpoint [%s], but was [%s] instead", expected, endpoint)
+	}
+}
+
+func TestChooseVersionOpinionatedLink(t *testing.T) {
+	testhelper.SetupHTTP()
+	defer testhelper.TeardownHTTP()
+	setupVersionHandler()
+
+	v2 := &Version{ID: "v2.0", Priority: 2, Suffix: "nope"}
+	v3 := &Version{ID: "v3.0", Priority: 3, Suffix: "northis"}
+
+	v, endpoint, err := ChooseVersion(testhelper.Endpoint(), testhelper.Endpoint()+"v2.0/", []*Version{v2, v3})
+	if err != nil {
+		t.Fatalf("Unexpected error from ChooseVersion: %v", err)
+	}
+
+	if v != v2 {
+		t.Errorf("Expected %#v to win, but %#v did instead", v2, v)
+	}
+
+	expected := testhelper.Endpoint() + "v2.0/"
+	if endpoint != expected {
+		t.Errorf("Expected endpoint [%s], but was [%s] instead", expected, endpoint)
+	}
+}
+
+func TestChooseVersionFromSuffix(t *testing.T) {
+	testhelper.SetupHTTP()
+	defer testhelper.TeardownHTTP()
+
+	v2 := &Version{ID: "v2.0", Priority: 2, Suffix: "/v2.0/"}
+	v3 := &Version{ID: "v3.0", Priority: 3, Suffix: "/v3.0/"}
+
+	v, endpoint, err := ChooseVersion(testhelper.Endpoint(), testhelper.Endpoint()+"v2.0/", []*Version{v2, v3})
+	if err != nil {
+		t.Fatalf("Unexpected error from ChooseVersion: %v", err)
+	}
+
+	if v != v2 {
+		t.Errorf("Expected %#v to win, but %#v did instead", v2, v)
+	}
+
+	expected := testhelper.Endpoint() + "v2.0/"
+	if endpoint != expected {
+		t.Errorf("Expected endpoint [%s], but was [%s] instead", expected, endpoint)
+	}
+}
diff --git a/_site/openstack/utils/utils.go b/_site/openstack/utils/utils.go
new file mode 100644
index 0000000..1d09d9e
--- /dev/null
+++ b/_site/openstack/utils/utils.go
@@ -0,0 +1,73 @@
+// Package utils contains utilities which eases working with Gophercloud's OpenStack APIs.
+package utils
+
+import (
+	"fmt"
+	"os"
+
+	"github.com/rackspace/gophercloud"
+)
+
+var nilOptions = gophercloud.AuthOptions{}
+
+// ErrNoAuthUrl, ErrNoUsername, and ErrNoPassword errors indicate of the required OS_AUTH_URL, OS_USERNAME, or OS_PASSWORD
+// environment variables, respectively, remain undefined.  See the AuthOptions() function for more details.
+var (
+	ErrNoAuthURL  = fmt.Errorf("Environment variable OS_AUTH_URL needs to be set.")
+	ErrNoUsername = fmt.Errorf("Environment variable OS_USERNAME needs to be set.")
+	ErrNoPassword = fmt.Errorf("Environment variable OS_PASSWORD needs to be set.")
+)
+
+// AuthOptions fills out an identity.AuthOptions structure with the settings found on the various OpenStack
+// OS_* environment variables.  The following variables provide sources of truth: OS_AUTH_URL, OS_USERNAME,
+// OS_PASSWORD, OS_TENANT_ID, and OS_TENANT_NAME.  Of these, OS_USERNAME, OS_PASSWORD, and OS_AUTH_URL must
+// have settings, or an error will result.  OS_TENANT_ID and OS_TENANT_NAME are optional.
+func AuthOptions() (gophercloud.AuthOptions, error) {
+	authURL := os.Getenv("OS_AUTH_URL")
+	username := os.Getenv("OS_USERNAME")
+	userID := os.Getenv("OS_USERID")
+	password := os.Getenv("OS_PASSWORD")
+	tenantID := os.Getenv("OS_TENANT_ID")
+	tenantName := os.Getenv("OS_TENANT_NAME")
+	domainID := os.Getenv("OS_DOMAIN_ID")
+	domainName := os.Getenv("OS_DOMAIN_NAME")
+
+	if authURL == "" {
+		return nilOptions, ErrNoAuthURL
+	}
+
+	if username == "" && userID == "" {
+		return nilOptions, ErrNoUsername
+	}
+
+	if password == "" {
+		return nilOptions, ErrNoPassword
+	}
+
+	ao := gophercloud.AuthOptions{
+		IdentityEndpoint: authURL,
+		UserID:           userID,
+		Username:         username,
+		Password:         password,
+		TenantID:         tenantID,
+		TenantName:       tenantName,
+		DomainID:         domainID,
+		DomainName:       domainName,
+	}
+
+	return ao, nil
+}
+
+// BuildQuery constructs the query section of a URI from a map.
+func BuildQuery(params map[string]string) string {
+	if len(params) == 0 {
+		return ""
+	}
+
+	query := "?"
+	for k, v := range params {
+		query += k + "=" + v + "&"
+	}
+	query = query[:len(query)-1]
+	return query
+}