Add global Authenticate() function.
Also finishes work started in last commit.
diff --git a/api.go b/api.go
new file mode 100644
index 0000000..73b3a8c
--- /dev/null
+++ b/api.go
@@ -0,0 +1,43 @@
+package gophercloud
+
+// globalContext is the, well, "global context."
+// Most of this SDK is written in a manner to facilitate easier testing,
+// which doesn't require all the configuration a real-world application would require.
+// However, for real-world deployments, applications should be able to rely on a consistent configuration of providers, etc.
+var globalContext *Context
+
+// providers is the set of supported providers.
+var providers = map[string]Provider{
+ "rackspace-us": Provider{
+ AuthEndpoint: "https://identity.api.rackspacecloud.com/v2.0/tokens",
+ },
+ "rackspace-uk": Provider{
+ AuthEndpoint: "https://lon.identity.api.rackspacecloud.com/v2.0/tokens",
+ },
+}
+
+// Initialize the global context to sane configuration.
+// The Go runtime ensures this function is called before main(),
+// thus guaranteeing proper configuration before your application ever runs.
+func init() {
+ globalContext = TestContext()
+ for name, descriptor := range providers {
+ globalContext.RegisterProvider(name, descriptor)
+ }
+}
+
+// Authenticate() grants access to the OpenStack-compatible provider API.
+//
+// Providers are identified through a unique key string.
+// Specifying an unsupported provider will result in an ErrProvider error.
+//
+// The supplied AuthOptions instance allows the client to specify only those credentials
+// relevant for the authentication request. At present, support exists for OpenStack
+// Identity V2 API only; support for V3 will become available as soon as documentation for it
+// becomes readily available.
+//
+// For Identity V2 API requirements, you must provide at least the Username and Password
+// options. The TenantId field is optional, and defaults to "".
+func Authenticate(provider string, options AuthOptions) (*Access, error) {
+ return globalContext.Authenticate(provider, options)
+}
diff --git a/authenticate.go b/authenticate.go
index 4d3254f..836c21b 100644
--- a/authenticate.go
+++ b/authenticate.go
@@ -4,19 +4,35 @@
"github.com/racker/perigee"
)
+// AuthOptions lets anyone calling Authenticate() supply the required access credentials.
+// At present, only Identity V2 API support exists; therefore, only Username, Password,
+// and optionally, TenantId are provided. If future Identity API versions become available,
+// alternative fields unique to those versions may appear here.
type AuthOptions struct {
- Username, Password, TenantId string
+ // Username and Password are required if using Identity V2 API.
+ // Consult with your provider's control panel to discover your
+ // account's username and password.
+ Username, Password string
+
+ // The TenantId field is optional for the Identity V2 API.
+ TenantId string
}
+// AuthContainer provides a JSON encoding wrapper for passing credentials to the Identity
+// service. You will not work with this structure directly.
type AuthContainer struct {
Auth Auth `json:"auth"`
}
+// Auth provides a JSON encoding wrapper for passing credentials to the Identity
+// service. You will not work with this structure directly.
type Auth struct {
PasswordCredentials PasswordCredentials `json:"passwordCredentials"`
TenantId string `json:"tenantId,omitempty"`
}
+// PasswordCredentials provides a JSON encoding wrapper for passing credentials to the Identity
+// service. You will not work with this structure directly.
type PasswordCredentials struct {
Username string `json:"username"`
Password string `json:"password"`
@@ -71,6 +87,18 @@
VersionId, VersionInfo, VersionList string
}
+// Authenticate() grants access to the OpenStack-compatible provider API.
+//
+// Providers are identified through a unique key string.
+// See the RegisterProvider() method for more details.
+//
+// The supplied AuthOptions instance allows the client to specify only those credentials
+// relevant for the authentication request. At present, support exists for OpenStack
+// Identity V2 API only; support for V3 will become available as soon as documentation for it
+// becomes readily available.
+//
+// For Identity V2 API requirements, you must provide at least the Username and Password
+// options. The TenantId field is optional, and defaults to "".
func (c *Context) Authenticate(provider string, options AuthOptions) (*Access, error) {
var access *Access
@@ -93,7 +121,7 @@
TenantId: options.TenantId,
},
},
- Results: &struct{
+ Results: &struct {
Access **Access `json:"access"`
}{
&access,
diff --git a/authenticate_test.go b/authenticate_test.go
index 0402e2a..f077dab 100644
--- a/authenticate_test.go
+++ b/authenticate_test.go
@@ -148,7 +148,7 @@
return
}
- err = c.RegisterProvider("provider", &Provider{AuthEndpoint: "/"})
+ err = c.RegisterProvider("provider", Provider{AuthEndpoint: "/"})
if err != nil {
t.Error(err)
return
@@ -170,7 +170,7 @@
c.UseCustomClient(&http.Client{
Transport: tt,
})
- c.RegisterProvider("provider", &Provider{AuthEndpoint: "/"})
+ c.RegisterProvider("provider", Provider{AuthEndpoint: "/"})
tt.expectTenantId = false
_, err := c.Authenticate("provider", AuthOptions{
@@ -205,7 +205,7 @@
func TestUserNameAndPassword(t *testing.T) {
c := TestContext()
c.UseCustomClient(&http.Client{Transport: &testTransport{}})
- c.RegisterProvider("provider", &Provider{AuthEndpoint: "http://localhost/"})
+ c.RegisterProvider("provider", Provider{AuthEndpoint: "http://localhost/"})
credentials := []AuthOptions{
AuthOptions{},
@@ -232,7 +232,7 @@
tt := &testTransport{}
tt.response = SUCCESSFUL_RESPONSE
c.UseCustomClient(&http.Client{Transport: tt})
- c.RegisterProvider("provider", &Provider{AuthEndpoint: "http://localhost"})
+ c.RegisterProvider("provider", Provider{AuthEndpoint: "http://localhost"})
acc, err := c.Authenticate("provider", AuthOptions{Username: "u", Password: "p"})
if err != nil {
@@ -252,7 +252,7 @@
tt := &testTransport{}
tt.response = SUCCESSFUL_RESPONSE
c.UseCustomClient(&http.Client{Transport: tt})
- c.RegisterProvider("provider", &Provider{AuthEndpoint: "http://localhost"})
+ c.RegisterProvider("provider", Provider{AuthEndpoint: "http://localhost"})
acc, err := c.Authenticate("provider", AuthOptions{Username: "u", Password: "p"})
if err != nil {
@@ -266,8 +266,8 @@
return
}
- types := map[string]bool {
- "compute": true,
+ types := map[string]bool{
+ "compute": true,
"rax:database": true,
}
for _, entry := range svcs {
@@ -283,7 +283,7 @@
tt := &testTransport{}
tt.response = SUCCESSFUL_RESPONSE
c.UseCustomClient(&http.Client{Transport: tt})
- c.RegisterProvider("provider", &Provider{AuthEndpoint: "http://localhost"})
+ c.RegisterProvider("provider", Provider{AuthEndpoint: "http://localhost"})
acc, err := c.Authenticate("provider", AuthOptions{Username: "u", Password: "p"})
if err != nil {
@@ -296,4 +296,4 @@
t.Error("Expected user ID of 16148; got", u.Id)
return
}
-}
\ No newline at end of file
+}
diff --git a/context.go b/context.go
index 28cb5f9..70d70a7 100644
--- a/context.go
+++ b/context.go
@@ -4,21 +4,34 @@
"net/http"
)
+// Context structures encapsulate Gophercloud-global state in a manner which
+// facilitates easier unit testing. As a user of this SDK, you'll never
+// have to use this structure, except when contributing new code to the SDK.
type Context struct {
// providerMap serves as a directory of supported providers.
- providerMap map[string]*Provider
+ providerMap map[string]Provider
// httpClient refers to the current HTTP client interface to use.
httpClient *http.Client
}
+// TestContext yields a new Context instance, pre-initialized with a barren
+// state suitable for per-unit-test customization. This configuration consists
+// of:
+//
+// * An empty provider map.
+//
+// * An HTTP client built by the net/http package (see http://godoc.org/net/http#Client).
func TestContext() *Context {
return &Context{
- providerMap: make(map[string]*Provider),
+ providerMap: make(map[string]Provider),
httpClient: &http.Client{},
}
}
+// UseCustomClient configures the context to use a customized HTTP client
+// instance. By default, TestContext() will return a Context which uses
+// the net/http package's default client instance.
func (c *Context) UseCustomClient(hc *http.Client) {
c.httpClient = hc
}
diff --git a/errors.go b/errors.go
index 079f0ae..58a898c 100644
--- a/errors.go
+++ b/errors.go
@@ -4,7 +4,24 @@
"fmt"
)
+// ErrNotImplemented should be used only while developing new SDK features.
+// No established function or method will ever produce this error.
var ErrNotImplemented = fmt.Errorf("Not implemented")
+
+// ErrProvider errors occur when attempting to reference an unsupported
+// provider. More often than not, this error happens due to a typo in
+// the name.
var ErrProvider = fmt.Errorf("Missing or incorrect provider")
+
+// ErrCredentials errors happen when attempting to authenticate using a
+// set of credentials not recognized by the Authenticate() method.
+// For example, not providing a username or password when attempting to
+// authenticate against an Identity V2 API.
var ErrCredentials = fmt.Errorf("Missing or incomplete credentials")
+
+// ErrConfiguration errors happen when attempting to add a new provider, and
+// the provider added lacks a correct or consistent configuration.
+// For example, all providers must expose at least an Identity V2 API
+// for authentication; if this endpoint isn't specified, you may receive
+// this error when attempting to register it against a context.
var ErrConfiguration = fmt.Errorf("Missing or incomplete configuration")
diff --git a/package.go b/package.go
index c05681e..396e523 100644
--- a/package.go
+++ b/package.go
@@ -1 +1,7 @@
+// Gophercloud provides a multi-vendor interface to OpenStack-compatible clouds which attempts to follow
+// established Go community coding standards and social norms.
+//
+// Unless you intend on contributing code to the SDK, you will almost certainly never have to use any
+// Context structures or any of its methods. Contextual methods exist for easier unit testing only.
+// Stick with the global functions unless you know exactly what you're doing, and why.
package gophercloud
diff --git a/provider.go b/provider.go
index 08bdfbe..c741c4c 100644
--- a/provider.go
+++ b/provider.go
@@ -1,12 +1,17 @@
package gophercloud
+// Provider structures exist for each tangible provider of OpenStack service.
+// For example, Rackspace, Hewlett-Packard, and NASA might have their own instance of this structure.
+//
+// At a minimum, a provider must expose an authentication endpoint.
type Provider struct {
AuthEndpoint string
}
-var providerMap = make(map[string]*Provider)
-
-func (c *Context) RegisterProvider(name string, p *Provider) error {
+// RegisterProvider allows a unit test to register a mythical provider convenient for testing.
+// If the provider structure lacks adequate configuration, or the configuration given has some
+// detectable error, an ErrConfiguration error will result.
+func (c *Context) RegisterProvider(name string, p Provider) error {
if p.AuthEndpoint == "" {
return ErrConfiguration
}
@@ -15,11 +20,13 @@
return nil
}
-func (c *Context) ProviderByName(name string) (p *Provider, err error) {
+// ProviderByName will locate a provider amongst those previously registered, if it exists.
+// If the named provider has not been registered, an ErrProvider error will result.
+func (c *Context) ProviderByName(name string) (p Provider, err error) {
for provider, descriptor := range c.providerMap {
if name == provider {
return descriptor, nil
}
}
- return nil, ErrProvider
+ return Provider{}, ErrProvider
}
diff --git a/provider_test.go b/provider_test.go
index 8c37dae..2936526 100644
--- a/provider_test.go
+++ b/provider_test.go
@@ -13,13 +13,13 @@
return
}
- err = c.RegisterProvider("aProvider", &Provider{})
+ err = c.RegisterProvider("aProvider", Provider{})
if err != ErrConfiguration {
t.Error("Unexpected error/nil when registering a provider w/out an auth endpoint\n %s", err)
return
}
- _ = c.RegisterProvider("aProvider", &Provider{AuthEndpoint: "http://localhost/auth"})
+ _ = c.RegisterProvider("aProvider", Provider{AuthEndpoint: "http://localhost/auth"})
_, err = c.ProviderByName("aProvider")
if err != nil {
t.Error(err)