Merge pull request #250 from jamiehannaford/auth-utils

Auth utils
diff --git a/README.md b/README.md
index 74a4cd4..96dcd31 100644
--- a/README.md
+++ b/README.md
@@ -80,7 +80,7 @@
 }
 
 // Option 2: Use a utility function to retrieve all your environment variables
-opts, err := utils.AuthOptions()
+opts, err := openstack.AuthOptionsFromEnv()
 ```
 
 Once you have the `opts` variable, you can pass it in and get back a
diff --git a/acceptance/openstack/blockstorage/v1/volumes_test.go b/acceptance/openstack/blockstorage/v1/volumes_test.go
index 21a47ac..f84f5cb 100644
--- a/acceptance/openstack/blockstorage/v1/volumes_test.go
+++ b/acceptance/openstack/blockstorage/v1/volumes_test.go
@@ -10,12 +10,11 @@
 	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/openstack"
 	"github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes"
-	"github.com/rackspace/gophercloud/openstack/utils"
 	"github.com/rackspace/gophercloud/pagination"
 )
 
 func newClient() (*gophercloud.ServiceClient, error) {
-	ao, err := utils.AuthOptions()
+	ao, err := openstack.AuthOptionsFromEnv()
 	if err != nil {
 		return nil, err
 	}
diff --git a/acceptance/openstack/client_test.go b/acceptance/openstack/client_test.go
index 6c0f9ee..6e88819 100644
--- a/acceptance/openstack/client_test.go
+++ b/acceptance/openstack/client_test.go
@@ -8,12 +8,11 @@
 
 	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/openstack"
-	"github.com/rackspace/gophercloud/openstack/utils"
 )
 
 func TestAuthenticatedClient(t *testing.T) {
 	// Obtain credentials from the environment.
-	ao, err := utils.AuthOptions()
+	ao, err := openstack.AuthOptionsFromEnv()
 	if err != nil {
 		t.Fatalf("Unable to acquire credentials: %v", err)
 	}
diff --git a/acceptance/openstack/compute/v2/compute_test.go b/acceptance/openstack/compute/v2/compute_test.go
index 15b5163..46eb9ff 100644
--- a/acceptance/openstack/compute/v2/compute_test.go
+++ b/acceptance/openstack/compute/v2/compute_test.go
@@ -11,11 +11,10 @@
 	"github.com/rackspace/gophercloud/acceptance/tools"
 	"github.com/rackspace/gophercloud/openstack"
 	"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
-	"github.com/rackspace/gophercloud/openstack/utils"
 )
 
 func newClient() (*gophercloud.ServiceClient, error) {
-	ao, err := utils.AuthOptions()
+	ao, err := openstack.AuthOptionsFromEnv()
 	if err != nil {
 		return nil, err
 	}
diff --git a/acceptance/openstack/identity/v2/identity_test.go b/acceptance/openstack/identity/v2/identity_test.go
index 2ecd3ca..feae233 100644
--- a/acceptance/openstack/identity/v2/identity_test.go
+++ b/acceptance/openstack/identity/v2/identity_test.go
@@ -7,13 +7,12 @@
 
 	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/openstack"
-	"github.com/rackspace/gophercloud/openstack/utils"
 	th "github.com/rackspace/gophercloud/testhelper"
 )
 
 func v2AuthOptions(t *testing.T) gophercloud.AuthOptions {
 	// Obtain credentials from the environment.
-	ao, err := utils.AuthOptions()
+	ao, err := openstack.AuthOptionsFromEnv()
 	th.AssertNoErr(t, err)
 
 	// Trim out unused fields. Prefer authentication by API key to password.
diff --git a/acceptance/openstack/identity/v3/identity_test.go b/acceptance/openstack/identity/v3/identity_test.go
index e0503e2..293606b 100644
--- a/acceptance/openstack/identity/v3/identity_test.go
+++ b/acceptance/openstack/identity/v3/identity_test.go
@@ -7,12 +7,11 @@
 
 	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/openstack"
-	"github.com/rackspace/gophercloud/openstack/utils"
 )
 
 func createAuthenticatedClient(t *testing.T) *gophercloud.ServiceClient {
 	// Obtain credentials from the environment.
-	ao, err := utils.AuthOptions()
+	ao, err := openstack.AuthOptionsFromEnv()
 	if err != nil {
 		t.Fatalf("Unable to acquire credentials: %v", err)
 	}
diff --git a/acceptance/openstack/identity/v3/token_test.go b/acceptance/openstack/identity/v3/token_test.go
index 341acb7..4342ade 100644
--- a/acceptance/openstack/identity/v3/token_test.go
+++ b/acceptance/openstack/identity/v3/token_test.go
@@ -7,12 +7,11 @@
 
 	"github.com/rackspace/gophercloud/openstack"
 	tokens3 "github.com/rackspace/gophercloud/openstack/identity/v3/tokens"
-	"github.com/rackspace/gophercloud/openstack/utils"
 )
 
 func TestGetToken(t *testing.T) {
 	// Obtain credentials from the environment.
-	ao, err := utils.AuthOptions()
+	ao, err := openstack.AuthOptionsFromEnv()
 	if err != nil {
 		t.Fatalf("Unable to acquire credentials: %v", err)
 	}
diff --git a/acceptance/openstack/networking/v2/common.go b/acceptance/openstack/networking/v2/common.go
index 6dd58af..1efac2c 100644
--- a/acceptance/openstack/networking/v2/common.go
+++ b/acceptance/openstack/networking/v2/common.go
@@ -6,14 +6,13 @@
 
 	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/openstack"
-	"github.com/rackspace/gophercloud/openstack/utils"
 	th "github.com/rackspace/gophercloud/testhelper"
 )
 
 var Client *gophercloud.ServiceClient
 
 func NewClient() (*gophercloud.ServiceClient, error) {
-	opts, err := utils.AuthOptions()
+	opts, err := openstack.AuthOptionsFromEnv()
 	if err != nil {
 		return nil, err
 	}
diff --git a/acceptance/openstack/objectstorage/v1/common.go b/acceptance/openstack/objectstorage/v1/common.go
index 4e2f9b5..fd1deda 100644
--- a/acceptance/openstack/objectstorage/v1/common.go
+++ b/acceptance/openstack/objectstorage/v1/common.go
@@ -7,14 +7,13 @@
 
 	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/openstack"
-	"github.com/rackspace/gophercloud/openstack/utils"
 	th "github.com/rackspace/gophercloud/testhelper"
 )
 
 var metadata = map[string]string{"gopher": "cloud"}
 
 func newClient() (*gophercloud.ServiceClient, error) {
-	ao, err := utils.AuthOptions()
+	ao, err := openstack.AuthOptionsFromEnv()
 	th.AssertNoErr(t, err)
 
 	client, err := openstack.AuthenticatedClient(ao)
diff --git a/acceptance/rackspace/client_test.go b/acceptance/rackspace/client_test.go
index e68aef8..825e3ac 100644
--- a/acceptance/rackspace/client_test.go
+++ b/acceptance/rackspace/client_test.go
@@ -5,13 +5,12 @@
 import (
 	"testing"
 
-	"github.com/rackspace/gophercloud/openstack/utils"
 	"github.com/rackspace/gophercloud/rackspace"
 )
 
 func TestAuthenticatedClient(t *testing.T) {
 	// Obtain credentials from the environment.
-	ao, err := utils.AuthOptions()
+	ao, err := rackspace.AuthOptionsFromEnv()
 	if err != nil {
 		t.Fatalf("Unable to acquire credentials: %v", err)
 	}
diff --git a/openstack/utils/utils.go b/openstack/auth_env.go
similarity index 80%
rename from openstack/utils/utils.go
rename to openstack/auth_env.go
index 1d09d9e..a4402b6 100644
--- a/openstack/utils/utils.go
+++ b/openstack/auth_env.go
@@ -1,5 +1,4 @@
-// Package utils contains utilities which eases working with Gophercloud's OpenStack APIs.
-package utils
+package openstack
 
 import (
 	"fmt"
@@ -22,7 +21,7 @@
 // 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) {
+func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) {
 	authURL := os.Getenv("OS_AUTH_URL")
 	username := os.Getenv("OS_USERNAME")
 	userID := os.Getenv("OS_USERID")
@@ -57,17 +56,3 @@
 
 	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
-}
diff --git a/openstack/identity/v3/endpoints/requests.go b/openstack/identity/v3/endpoints/requests.go
index 99600ef..4bec427 100644
--- a/openstack/identity/v3/endpoints/requests.go
+++ b/openstack/identity/v3/endpoints/requests.go
@@ -5,7 +5,6 @@
 
 	"github.com/racker/perigee"
 	"github.com/rackspace/gophercloud"
-	"github.com/rackspace/gophercloud/openstack/utils"
 	"github.com/rackspace/gophercloud/pagination"
 )
 
@@ -98,7 +97,7 @@
 		return EndpointPage{pagination.LinkedPageBase{PageResult: r}}
 	}
 
-	u := listURL(client) + utils.BuildQuery(q)
+	u := listURL(client) + gophercloud.BuildQuery(q)
 	return pagination.NewPager(client, u, createPage)
 }
 
diff --git a/openstack/identity/v3/services/requests.go b/openstack/identity/v3/services/requests.go
index a70bbd3..425a67c 100644
--- a/openstack/identity/v3/services/requests.go
+++ b/openstack/identity/v3/services/requests.go
@@ -5,7 +5,6 @@
 
 	"github.com/racker/perigee"
 	"github.com/rackspace/gophercloud"
-	"github.com/rackspace/gophercloud/openstack/utils"
 	"github.com/rackspace/gophercloud/pagination"
 )
 
@@ -50,7 +49,7 @@
 	if opts.PerPage != 0 {
 		q["perPage"] = strconv.Itoa(opts.PerPage)
 	}
-	u := listURL(client) + utils.BuildQuery(q)
+	u := listURL(client) + gophercloud.BuildQuery(q)
 
 	createPage := func(r pagination.PageResult) pagination.Page {
 		return ServicePage{pagination.LinkedPageBase{PageResult: r}}
diff --git a/rackspace/auth_env.go b/rackspace/auth_env.go
new file mode 100644
index 0000000..1706cc4
--- /dev/null
+++ b/rackspace/auth_env.go
@@ -0,0 +1,49 @@
+package rackspace
+
+import (
+	"fmt"
+	"os"
+
+	"github.com/rackspace/gophercloud"
+)
+
+var nilOptions = gophercloud.AuthOptions{}
+
+// ErrNoAuthUrl, ErrNoUsername, and ErrNoPassword errors indicate of the
+// required RAX_AUTH_URL, RAX_USERNAME, or RAX_PASSWORD environment variables,
+// respectively, remain undefined.  See the AuthOptions() function for more details.
+var (
+	ErrNoAuthURL  = fmt.Errorf("Environment variable RAX_AUTH_URL needs to be set.")
+	ErrNoUsername = fmt.Errorf("Environment variable RAX_USERNAME needs to be set.")
+	ErrNoPassword = fmt.Errorf("Environment variable RAX_API_KEY or RAX_PASSWORD needs to be set.")
+)
+
+// AuthOptionsFromEnv fills out an identity.AuthOptions structure with the
+// settings found on the various Rackspace RAX_* environment variables.
+func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) {
+	authURL := os.Getenv("RAX_AUTH_URL")
+	username := os.Getenv("RAX_USERNAME")
+	password := os.Getenv("RAX_PASSWORD")
+	apiKey := os.Getenv("RAX_API_KEY")
+
+	if authURL == "" {
+		return nilOptions, ErrNoAuthURL
+	}
+
+	if username == "" {
+		return nilOptions, ErrNoUsername
+	}
+
+	if password == "" && apiKey == "" {
+		return nilOptions, ErrNoPassword
+	}
+
+	ao := gophercloud.AuthOptions{
+		IdentityEndpoint: authURL,
+		Username:         username,
+		Password:         password,
+		APIKey:           apiKey,
+	}
+
+	return ao, nil
+}
diff --git a/util.go b/util.go
index 1715458..c66af89 100644
--- a/util.go
+++ b/util.go
@@ -29,3 +29,17 @@
 	}
 	return url
 }
+
+// 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
+}