Merge pull request #236 from jamiehannaford/server-list-opts
Adding Server list opts
diff --git a/acceptance/openstack/compute/v2/servers_test.go b/acceptance/openstack/compute/v2/servers_test.go
index 047d763..5193e4e 100644
--- a/acceptance/openstack/compute/v2/servers_test.go
+++ b/acceptance/openstack/compute/v2/servers_test.go
@@ -20,7 +20,7 @@
t.Logf("ID\tRegion\tName\tStatus\tIPv4\tIPv6")
- pager := servers.List(client)
+ pager := servers.List(client, servers.ListOpts{})
count, pages := 0, 0
pager.EachPage(func(page pagination.Page) (bool, error) {
pages++
diff --git a/docs/identity/v2/index.md b/docs/identity/v2/index.md
new file mode 100644
index 0000000..c0e82bd
--- /dev/null
+++ b/docs/identity/v2/index.md
@@ -0,0 +1,60 @@
+---
+layout: page
+title: Getting Started with Identity v2
+---
+
+## Tokens
+
+A token is an arbitrary bit of text that is used to access resources. Each
+token has a scope that describes which resources are accessible with it. A
+token may be revoked at anytime and is valid for a finite duration.
+
+### Generate a token
+
+The nature of required and optional auth options will depend on your provider,
+but generally the `Username` and `IdentityEndpoint` fields are always
+required. Some providers will insist on a `Password` instead of an `APIKey`,
+others will prefer `TenantID` over `TenantName` - so it is always worth
+checking before writing your implementation in Go.
+
+{% highlight go %}
+import "github.com/rackspace/gophercloud/openstack/identity/v2/tokens"
+
+opts := tokens.AuthOptions{
+ IdentityEndpoint: "{identityURL}",
+ Username: "{username}",
+ APIKey: "{apiKey}",
+}
+
+token, err := tokens.Create(client, opts).Extract()
+{% endhighlight %}
+
+## Tenants
+
+A tenant is a container used to group or isolate API resources. Depending on
+the provider, a tenant can map to a customer, account, organization, or project.
+
+### List tenants
+
+{% highlight go %}
+import (
+ "github.com/rackspace/gophercloud/pagination"
+ "github.com/rackspace/gophercloud/openstack/identity/v2/tenants"
+)
+
+// We have the option of filtering the tenant list. If we want the full
+// collection, leave it as an empty struct
+opts := tenants.ListOpts{Limit: 10}
+
+// Retrieve a pager (i.e. a paginated collection)
+pager := tenants.List(client, opts)
+
+// Define an anonymous function to be executed on each page's iteration
+err := pager.EachPage(func(page pagination.Page) (bool, error) {
+ tenantList, err := tenants.ExtractTenants(page)
+
+ for _, t := range tenantList {
+ // "t" will be a tenants.Tenant
+ }
+})
+{% endhighlight %}
diff --git a/docs/identity/v3/index.md b/docs/identity/v3/index.md
new file mode 100644
index 0000000..ab2da67
--- /dev/null
+++ b/docs/identity/v3/index.md
@@ -0,0 +1,36 @@
+---
+layout: page
+title: Getting Started with Identity v3
+---
+
+## Tokens
+
+### Generate a token
+
+### Get a token
+
+### Validate token
+
+### Revoke token
+
+## Endpoints
+
+### Create an endpoint
+
+### List endpoints
+
+### Update endpoint
+
+### Delete endpoint
+
+## Services
+
+### Create a service
+
+### List services
+
+### Get a service
+
+### Update service
+
+### Delete service
diff --git a/openstack/compute/v2/servers/requests.go b/openstack/compute/v2/servers/requests.go
index 386e0f6..df622bf 100644
--- a/openstack/compute/v2/servers/requests.go
+++ b/openstack/compute/v2/servers/requests.go
@@ -9,13 +9,71 @@
"github.com/rackspace/gophercloud/pagination"
)
+// ListOptsBuilder allows extensions to add additional parameters to the
+// List request.
+type ListOptsBuilder interface {
+ ToServerListQuery() (string, error)
+}
+
+// ListOpts allows the filtering and sorting of paginated collections through
+// the API. Filtering is achieved by passing in struct field values that map to
+// the server attributes you want to see returned. Marker and Limit are used
+// for pagination.
+type ListOpts struct {
+ // A time/date stamp for when the server last changed status.
+ ChangesSince string `q:"changes-since"`
+
+ // Name of the image in URL format.
+ Image string `q:"image"`
+
+ // Name of the flavor in URL format.
+ Flavor string `q:"flavor"`
+
+ // Name of the server as a string; can be queried with regular expressions.
+ // Realize that ?name=bob returns both bob and bobb. If you need to match bob
+ // only, you can use a regular expression matching the syntax of the
+ // underlying database server implemented for Compute.
+ Name string `q:"name"`
+
+ // Value of the status of the server so that you can filter on "ACTIVE" for example.
+ Status string `q:"status"`
+
+ // Name of the host as a string.
+ Host string `q:"host"`
+
+ // UUID of the server at which you want to set a marker.
+ Marker string `q:"marker"`
+
+ // Integer value for the limit of values to return.
+ Limit int `q:"limit"`
+}
+
+// ToServerListQuery formats a ListOpts into a query string.
+func (opts ListOpts) ToServerListQuery() (string, error) {
+ q, err := gophercloud.BuildQueryString(opts)
+ if err != nil {
+ return "", err
+ }
+ return q.String(), nil
+}
+
// List makes a request against the API to list servers accessible to you.
-func List(client *gophercloud.ServiceClient) pagination.Pager {
- createPage := func(r pagination.LastHTTPResponse) pagination.Page {
+func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
+ url := listDetailURL(client)
+
+ if opts != nil {
+ query, err := opts.ToServerListQuery()
+ if err != nil {
+ return pagination.Pager{Err: err}
+ }
+ url += query
+ }
+
+ createPageFn := func(r pagination.LastHTTPResponse) pagination.Page {
return ServerPage{pagination.LinkedPageBase{LastHTTPResponse: r}}
}
- return pagination.NewPager(client, listDetailURL(client), createPage)
+ return pagination.NewPager(client, url, createPageFn)
}
// CreateOptsBuilder describes struct types that can be accepted by the Create call.
diff --git a/openstack/compute/v2/servers/requests_test.go b/openstack/compute/v2/servers/requests_test.go
index 806efd3..d7ce2a0 100644
--- a/openstack/compute/v2/servers/requests_test.go
+++ b/openstack/compute/v2/servers/requests_test.go
@@ -5,27 +5,18 @@
"net/http"
"testing"
- "github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
"github.com/rackspace/gophercloud/testhelper"
+ fake "github.com/rackspace/gophercloud/testhelper/client"
)
-const tokenID = "bzbzbzbzbz"
-
-func serviceClient() *gophercloud.ServiceClient {
- return &gophercloud.ServiceClient{
- Provider: &gophercloud.ProviderClient{TokenID: tokenID},
- Endpoint: testhelper.Endpoint(),
- }
-}
-
func TestListServers(t *testing.T) {
testhelper.SetupHTTP()
defer testhelper.TeardownHTTP()
testhelper.Mux.HandleFunc("/servers/detail", func(w http.ResponseWriter, r *http.Request) {
testhelper.TestMethod(t, r, "GET")
- testhelper.TestHeader(t, r, "X-Auth-Token", tokenID)
+ testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.Header().Add("Content-Type", "application/json")
r.ParseForm()
@@ -40,9 +31,8 @@
}
})
- client := serviceClient()
pages := 0
- err := List(client).EachPage(func(page pagination.Page) (bool, error) {
+ err := List(fake.ServiceClient(), ListOpts{}).EachPage(func(page pagination.Page) (bool, error) {
pages++
actual, err := ExtractServers(page)
@@ -59,9 +49,8 @@
return true, nil
})
- if err != nil {
- t.Fatalf("Unexpected error from EachPage: %v", err)
- }
+ testhelper.AssertNoErr(t, err)
+
if pages != 1 {
t.Errorf("Expected 1 page, saw %d", pages)
}
@@ -73,7 +62,7 @@
testhelper.Mux.HandleFunc("/servers", func(w http.ResponseWriter, r *http.Request) {
testhelper.TestMethod(t, r, "POST")
- testhelper.TestHeader(t, r, "X-Auth-Token", tokenID)
+ testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
testhelper.TestJSONRequest(t, r, `{
"server": {
"name": "derp",
@@ -87,7 +76,7 @@
fmt.Fprintf(w, singleServerBody)
})
- client := serviceClient()
+ client := fake.ServiceClient()
actual, err := Create(client, CreateOpts{
Name: "derp",
ImageRef: "f90f6034-2570-4974-8351-6b49732ef2eb",
@@ -106,12 +95,12 @@
testhelper.Mux.HandleFunc("/servers/asdfasdfasdf", func(w http.ResponseWriter, r *http.Request) {
testhelper.TestMethod(t, r, "DELETE")
- testhelper.TestHeader(t, r, "X-Auth-Token", tokenID)
+ testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
w.WriteHeader(http.StatusNoContent)
})
- client := serviceClient()
+ client := fake.ServiceClient()
err := Delete(client, "asdfasdfasdf")
if err != nil {
t.Fatalf("Unexpected Delete error: %v", err)
@@ -124,13 +113,13 @@
testhelper.Mux.HandleFunc("/servers/1234asdf", func(w http.ResponseWriter, r *http.Request) {
testhelper.TestMethod(t, r, "GET")
- testhelper.TestHeader(t, r, "X-Auth-Token", tokenID)
+ testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
testhelper.TestHeader(t, r, "Accept", "application/json")
fmt.Fprintf(w, singleServerBody)
})
- client := serviceClient()
+ client := fake.ServiceClient()
actual, err := Get(client, "1234asdf").Extract()
if err != nil {
t.Fatalf("Unexpected Get error: %v", err)
@@ -145,7 +134,7 @@
testhelper.Mux.HandleFunc("/servers/1234asdf", func(w http.ResponseWriter, r *http.Request) {
testhelper.TestMethod(t, r, "PUT")
- testhelper.TestHeader(t, r, "X-Auth-Token", tokenID)
+ testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
testhelper.TestHeader(t, r, "Accept", "application/json")
testhelper.TestHeader(t, r, "Content-Type", "application/json")
testhelper.TestJSONRequest(t, r, `{ "server": { "name": "new-name" } }`)
@@ -153,7 +142,7 @@
fmt.Fprintf(w, singleServerBody)
})
- client := serviceClient()
+ client := fake.ServiceClient()
actual, err := Update(client, "1234asdf", UpdateOpts{Name: "new-name"}).Extract()
if err != nil {
t.Fatalf("Unexpected Update error: %v", err)
@@ -168,13 +157,13 @@
testhelper.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) {
testhelper.TestMethod(t, r, "POST")
- testhelper.TestHeader(t, r, "X-Auth-Token", tokenID)
+ testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
testhelper.TestJSONRequest(t, r, `{ "changePassword": { "adminPass": "new-password" } }`)
w.WriteHeader(http.StatusAccepted)
})
- client := serviceClient()
+ client := fake.ServiceClient()
err := ChangeAdminPassword(client, "1234asdf", "new-password")
if err != nil {
t.Errorf("Unexpected ChangeAdminPassword error: %v", err)
@@ -187,13 +176,13 @@
testhelper.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) {
testhelper.TestMethod(t, r, "POST")
- testhelper.TestHeader(t, r, "X-Auth-Token", tokenID)
+ testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
testhelper.TestJSONRequest(t, r, `{ "reboot": { "type": "SOFT" } }`)
w.WriteHeader(http.StatusAccepted)
})
- client := serviceClient()
+ client := fake.ServiceClient()
err := Reboot(client, "1234asdf", SoftReboot)
if err != nil {
t.Errorf("Unexpected Reboot error: %v", err)
@@ -206,7 +195,7 @@
testhelper.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) {
testhelper.TestMethod(t, r, "POST")
- testhelper.TestHeader(t, r, "X-Auth-Token", tokenID)
+ testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
testhelper.TestJSONRequest(t, r, `
{
"rebuild": {
@@ -230,7 +219,7 @@
AccessIPv4: "1.2.3.4",
}
- actual, err := Rebuild(serviceClient(), "1234asdf", opts).Extract()
+ actual, err := Rebuild(fake.ServiceClient(), "1234asdf", opts).Extract()
testhelper.AssertNoErr(t, err)
equalServers(t, serverDerp, *actual)
@@ -242,13 +231,13 @@
testhelper.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) {
testhelper.TestMethod(t, r, "POST")
- testhelper.TestHeader(t, r, "X-Auth-Token", tokenID)
+ testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
testhelper.TestJSONRequest(t, r, `{ "resize": { "flavorRef": "2" } }`)
w.WriteHeader(http.StatusAccepted)
})
- client := serviceClient()
+ client := fake.ServiceClient()
err := Resize(client, "1234asdf", "2")
if err != nil {
t.Errorf("Unexpected Reboot error: %v", err)
@@ -261,13 +250,13 @@
testhelper.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) {
testhelper.TestMethod(t, r, "POST")
- testhelper.TestHeader(t, r, "X-Auth-Token", tokenID)
+ testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
testhelper.TestJSONRequest(t, r, `{ "confirmResize": null }`)
w.WriteHeader(http.StatusNoContent)
})
- client := serviceClient()
+ client := fake.ServiceClient()
err := ConfirmResize(client, "1234asdf")
if err != nil {
t.Errorf("Unexpected ConfirmResize error: %v", err)
@@ -280,13 +269,13 @@
testhelper.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) {
testhelper.TestMethod(t, r, "POST")
- testhelper.TestHeader(t, r, "X-Auth-Token", tokenID)
+ testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
testhelper.TestJSONRequest(t, r, `{ "revertResize": null }`)
w.WriteHeader(http.StatusAccepted)
})
- client := serviceClient()
+ client := fake.ServiceClient()
err := RevertResize(client, "1234asdf")
if err != nil {
t.Errorf("Unexpected RevertResize error: %v", err)