Implement first function of cloud compute API
diff --git a/acceptance/openstack/compute/01-servers.go b/acceptance/openstack/compute/01-servers.go
new file mode 100644
index 0000000..9a729ff
--- /dev/null
+++ b/acceptance/openstack/compute/01-servers.go
@@ -0,0 +1,66 @@
+package main
+
+import (
+ "fmt"
+ "github.com/rackspace/gophercloud/openstack/compute/servers"
+ "github.com/rackspace/gophercloud/openstack/identity"
+ "github.com/rackspace/gophercloud/openstack/utils"
+)
+
+func main() {
+ ao, err := utils.AuthOptions()
+ if err != nil {
+ panic(err)
+ }
+
+ a, err := identity.Authenticate(ao)
+ if err != nil {
+ panic(err)
+ }
+
+ sc, err := identity.GetServiceCatalog(a)
+ if err != nil {
+ panic(err)
+ }
+
+ ep, err := findAnyComputeEndpoint(sc)
+ if err != nil {
+ panic(err)
+ }
+
+ client := servers.NewClient(ep, a, ao)
+
+ listResults, err := servers.List(client)
+ if err != nil {
+ panic(err)
+ }
+
+ svrs, err := servers.GetServers(listResults)
+ if err != nil {
+ panic(err)
+ }
+
+ for _, s := range svrs {
+ fmt.Printf("ID(%s)\n", s.Id)
+ fmt.Printf(" Name(%s)\n", s.Name)
+ fmt.Printf(" IPv4(%s)\n IPv6(%s)\n", s.AccessIPv4, s.AccessIPv6)
+ }
+ fmt.Printf("--------\n%d servers listed.\n", len(svrs))
+}
+
+
+func findAnyComputeEndpoint(sc *identity.ServiceCatalog) (string, error) {
+ ces, err := sc.CatalogEntries()
+ if err != nil {
+ return "", err
+ }
+
+ for _, ce := range ces {
+ if ce.Type == "compute" {
+ return ce.Endpoints[0].PublicURL, nil
+ }
+ }
+
+ return "", fmt.Errorf("Compute endpoint not found.")
+}
+
diff --git a/openstack/compute/servers/client.go b/openstack/compute/servers/client.go
new file mode 100644
index 0000000..a59a339
--- /dev/null
+++ b/openstack/compute/servers/client.go
@@ -0,0 +1,52 @@
+package servers
+
+import (
+ "fmt"
+ "github.com/rackspace/gophercloud/openstack/identity"
+)
+
+// Client abstracts the connection information needed to make API requests for OpenStack compute endpoints.
+type Client struct {
+ endpoint string
+ authority identity.AuthResults
+ options identity.AuthOptions
+ token *identity.Token
+}
+
+// NewClient creates a new Client structure to use when issuing requests to the server.
+func NewClient(e string, a identity.AuthResults, o identity.AuthOptions) *Client {
+ return &Client{
+ endpoint: e,
+ authority: a,
+ options: o,
+ }
+}
+
+func (c *Client) getListUrl() string {
+ return fmt.Sprintf("%s/servers/detail", c.endpoint)
+}
+
+func (c *Client) getListHeaders() (map[string]string, error) {
+ t, err := c.getAuthToken()
+ if err != nil {
+ return map[string]string{}, err
+ }
+
+ return map[string]string{
+ "X-Auth-Token": t,
+ }, nil
+}
+
+func (c *Client) getAuthToken() (string, error) {
+ var err error
+
+ if c.token == nil {
+ c.token, err = identity.GetToken(c.authority)
+ if err != nil {
+ return "", err
+ }
+ }
+
+ return c.token.Id, err
+}
+
diff --git a/openstack/compute/servers/doc.go b/openstack/compute/servers/doc.go
new file mode 100644
index 0000000..c5c6777
--- /dev/null
+++ b/openstack/compute/servers/doc.go
@@ -0,0 +1,3 @@
+// The servers package provides convenient access to standard, OpenStack-defined compute services.
+package servers
+
diff --git a/openstack/compute/servers/requests.go b/openstack/compute/servers/requests.go
new file mode 100644
index 0000000..0c49159
--- /dev/null
+++ b/openstack/compute/servers/requests.go
@@ -0,0 +1,28 @@
+package servers
+
+import (
+ "github.com/racker/perigee"
+)
+
+// ListResult abstracts the raw results of making a List() request against the
+// API. As OpenStack extensions may freely alter the response bodies of
+// structures returned to the client, you may only safely access the data
+// provided through separate, type-safe accessors or methods.
+type ListResult map[string]interface{}
+
+// List makes a request against the API to list servers accessible to you.
+func List(c *Client) (ListResult, error) {
+ var lr ListResult
+
+ h, err := c.getListHeaders()
+ if err != nil {
+ return nil, err
+ }
+
+ err = perigee.Get(c.getListUrl(), perigee.Options{
+ Results: &lr,
+ MoreHeaders: h,
+ })
+ return lr, err
+}
+
diff --git a/openstack/compute/servers/servers.go b/openstack/compute/servers/servers.go
new file mode 100644
index 0000000..7061993
--- /dev/null
+++ b/openstack/compute/servers/servers.go
@@ -0,0 +1,77 @@
+package servers
+
+import (
+ "github.com/mitchellh/mapstructure"
+ "fmt"
+)
+
+// ErrNotImplemented indicates a failure to discover a feature of the response from the API.
+// E.g., a missing server field, a missing extension, etc.
+var ErrNotImplemented = fmt.Errorf("Compute Servers feature not implemented.")
+
+// Server exposes only the standard OpenStack fields corresponding to a given server on the user's account.
+//
+// Id uniquely identifies this server amongst all other servers, including those not accessible to the current tenant.
+//
+// TenantId identifies the tenant owning this server resource.
+//
+// UserId uniquely identifies the user account owning the tenant.
+//
+// Name contains the human-readable name for the server.
+//
+// Updated and Created contain ISO-8601 timestamps of when the state of the server last changed, and when it was created.
+//
+// Status contains the current operational status of the server, such as IN_PROGRESS or ACTIVE.
+//
+// Progress ranges from 0..100. A request made against the server completes only once Progress reaches 100.
+//
+// AccessIPv4 and AccessIPv6 contain the IP addresses of the server, suitable for remote access for administration.
+//
+// Image refers to a JSON object, which itself indicates the OS image used to deploy the server.
+//
+// Flavor refers to a JSON object, which itself indicates the hardware configuration of the deployed server.
+//
+// Addresses includes a list of all IP addresses assigned to the server, keyed by pool.
+//
+// Metadata includes a list of all user-specified key-value pairs attached to the server.
+//
+// Links includes HTTP references to the itself, useful for passing along to other APIs that might want a server reference.
+type Server struct {
+ Id string
+ TenantId string `mapstructure:tenant_id`
+ UserId string `mapstructure:user_id`
+ Name string
+ Updated string
+ Created string
+ HostId string
+ Status string
+ Progress int
+ AccessIPv4 string
+ AccessIPv6 string
+ Image map[string]interface{}
+ Flavor map[string]interface{}
+ Addresses map[string]interface{}
+ Metadata map[string]interface{}
+ Links []interface{}
+}
+
+// GetServers interprets the result of a List() call, producing a slice of Server entities.
+func GetServers(lr ListResult) ([]Server, error) {
+ sa, ok := lr["servers"]
+ if !ok {
+ return nil, ErrNotImplemented
+ }
+ serversArray := sa.([]interface{})
+
+ servers := make([]Server, len(serversArray))
+ for i, so := range serversArray {
+ serverObj := so.(map[string]interface{})
+ err := mapstructure.Decode(serverObj, &servers[i])
+ if err != nil {
+ return servers, err
+ }
+ }
+
+ return servers, nil
+}
+
diff --git a/openstack/compute/servers/servers_test.go b/openstack/compute/servers/servers_test.go
new file mode 100644
index 0000000..8a58a46
--- /dev/null
+++ b/openstack/compute/servers/servers_test.go
@@ -0,0 +1,189 @@
+package servers
+
+import (
+ "testing"
+ "encoding/json"
+)
+
+// Taken from: http://docs.openstack.org/api/openstack-compute/2/content/List_Servers-d1e2078.html
+const goodListServersResult = `{
+"servers": [
+{
+"id": "52415800-8b69-11e0-9b19-734f6af67565",
+"tenant_id": "1234",
+"user_id": "5678",
+"name": "sample-server",
+"updated": "2010-10-10T12:00:00Z",
+"created": "2010-08-10T12:00:00Z",
+"hostId": "e4d909c290d0fb1ca068ffaddf22cbd0",
+"status": "BUILD",
+"progress": 60,
+"accessIPv4" : "67.23.10.132",
+"accessIPv6" : "::babe:67.23.10.132",
+"image" : {
+"id": "52415800-8b69-11e0-9b19-734f6f006e54",
+"links": [
+{
+"rel": "self",
+"href": "http://servers.api.openstack.org/v2/1234/images/52415800-8b69-11e0-9b19-734f6f006e54"
+},
+{
+"rel": "bookmark",
+"href": "http://servers.api.openstack.org/1234/images/52415800-8b69-11e0-9b19-734f6f006e54"
+}
+]
+},
+"flavor" : {
+"id": "52415800-8b69-11e0-9b19-734f216543fd",
+"links": [
+{
+"rel": "self",
+"href": "http://servers.api.openstack.org/v2/1234/flavors/52415800-8b69-11e0-9b19-734f216543fd"
+},
+{
+"rel": "bookmark",
+"href": "http://servers.api.openstack.org/1234/flavors/52415800-8b69-11e0-9b19-734f216543fd"
+}
+]
+},
+"addresses": {
+"public" : [
+{
+"version": 4,
+"addr": "67.23.10.132"
+},
+{
+"version": 6,
+"addr": "::babe:67.23.10.132"
+},
+{
+"version": 4,
+"addr": "67.23.10.131"
+},
+{
+"version": 6,
+"addr": "::babe:4317:0A83"
+}
+],
+"private" : [
+{
+"version": 4,
+"addr": "10.176.42.16"
+},
+{
+"version": 6,
+"addr": "::babe:10.176.42.16"
+}
+]
+},
+"metadata": {
+"Server Label": "Web Head 1",
+"Image Version": "2.1"
+},
+"links": [
+{
+"rel": "self",
+"href": "http://servers.api.openstack.org/v2/1234/servers/52415800-8b69-11e0-9b19-734f6af67565"
+},
+{
+"rel": "bookmark",
+"href": "http://servers.api.openstack.org/1234/servers/52415800-8b69-11e0-9b19-734f6af67565"
+}
+]
+},
+{
+"id": "52415800-8b69-11e0-9b19-734f1f1350e5",
+"user_id": "5678",
+"name": "sample-server2",
+"tenant_id": "1234",
+"updated": "2010-10-10T12:00:00Z",
+"created": "2010-08-10T12:00:00Z",
+"hostId": "9e107d9d372bb6826bd81d3542a419d6",
+"status": "ACTIVE",
+"accessIPv4" : "67.23.10.133",
+"accessIPv6" : "::babe:67.23.10.133",
+"image" : {
+"id": "52415800-8b69-11e0-9b19-734f5736d2a2",
+"links": [
+{
+"rel": "self",
+"href": "http://servers.api.openstack.org/v2/1234/images/52415800-8b69-11e0-9b19-734f5736d2a2"
+},
+{
+"rel": "bookmark",
+"href": "http://servers.api.openstack.org/1234/images/52415800-8b69-11e0-9b19-734f5736d2a2"
+}
+]
+},
+"flavor" : {
+"id": "52415800-8b69-11e0-9b19-734f216543fd",
+"links": [
+{
+"rel": "self",
+"href": "http://servers.api.openstack.org/v2/1234/flavors/52415800-8b69-11e0-9b19-734f216543fd"
+},
+{
+"rel": "bookmark",
+"href": "http://servers.api.openstack.org/1234/flavors/52415800-8b69-11e0-9b19-734f216543fd"
+}
+]
+},
+"addresses": {
+"public" : [
+{
+"version": 4,
+"addr": "67.23.10.133"
+},
+{
+"version": 6,
+"addr": "::babe:67.23.10.133"
+}
+],
+"private" : [
+{
+"version": 4,
+"addr": "10.176.42.17"
+},
+{
+"version": 6,
+"addr": "::babe:10.176.42.17"
+}
+]
+},
+"metadata": {
+"Server Label": "DB 1"
+},
+"links": [
+{
+"rel": "self",
+"href": "http://servers.api.openstack.org/v2/1234/servers/52415800-8b69-11e0-9b19-734f1f1350e5"
+},
+{
+"rel": "bookmark",
+"href": "http://servers.api.openstack.org/1234/servers/52415800-8b69-11e0-9b19-734f1f1350e5"
+}
+]
+}
+]
+}`
+
+func TestGetServer(t *testing.T) {
+ var listResults map[string]interface{}
+ err := json.Unmarshal([]byte(goodListServersResult), &listResults)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ svrs, err := GetServers(listResults)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ if len(svrs) != 2 {
+ t.Errorf("Expected 2 servers; got %d", len(svrs))
+ return
+ }
+}
+