stack resources [list, get, find, metadata]
diff --git a/acceptance/openstack/orchestration/v1/stackresources_test.go b/acceptance/openstack/orchestration/v1/stackresources_test.go
new file mode 100644
index 0000000..0d2196a
--- /dev/null
+++ b/acceptance/openstack/orchestration/v1/stackresources_test.go
@@ -0,0 +1,75 @@
+// +build acceptance
+
+package v1
+
+import (
+ "testing"
+
+ "github.com/rackspace/gophercloud"
+ "github.com/rackspace/gophercloud/openstack/orchestration/v1/stackresources"
+ "github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks"
+ "github.com/rackspace/gophercloud/pagination"
+ th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestStackResources(t *testing.T) {
+ // Create a provider client for making the HTTP requests.
+ // See common.go in this directory for more information.
+ client := newClient(t)
+
+ stackName := "postman_stack_2"
+
+ createOpts := stacks.CreateOpts{
+ Name: stackName,
+ Template: template,
+ Timeout: 5,
+ }
+ stack, err := stacks.Create(client, createOpts).Extract()
+ th.AssertNoErr(t, err)
+ t.Logf("Created stack: %+v\n", stack)
+ defer func() {
+ err := stacks.Delete(client, stackName, stack.ID).ExtractErr()
+ th.AssertNoErr(t, err)
+ t.Logf("Deleted stack (%s)", stackName)
+ }()
+ err = gophercloud.WaitFor(60, func() (bool, error) {
+ getStack, err := stacks.Get(client, stackName, stack.ID).Extract()
+ if err != nil {
+ return false, err
+ }
+ if getStack.Status == "CREATE_COMPLETE" {
+ return true, nil
+ }
+ return false, nil
+ })
+
+ var resourceName string
+ pager := stackresources.List(client, stackName, stack.ID, stackresources.ListOpts{})
+ pager.EachPage(func(page pagination.Page) (bool, error) {
+ resources, err := stackresources.ExtractResources(page)
+ if err != nil {
+ return false, err
+ }
+
+ t.Logf("%+v\n", resources)
+
+ resourceName = resources[0].Name
+
+ return true, nil
+ })
+
+ /*
+ resource, err := stackresources.Find(client, stackName).Extract()
+ th.AssertNoErr(t, err)
+ t.Logf("Got stack resource: %+v\n", resource)
+ */
+
+ resource, err := stackresources.Get(client, stackName, stack.ID, resourceName).Extract()
+ th.AssertNoErr(t, err)
+ t.Logf("Got stack resource: %+v\n", resource)
+
+ metadata, err := stackresources.Metadata(client, stackName, stack.ID, resourceName).Extract()
+ th.AssertNoErr(t, err)
+ t.Logf("Got stack resource metadata: %+v\n", metadata)
+
+}
diff --git a/openstack/orchestration/v1/stackresources/requests.go b/openstack/orchestration/v1/stackresources/requests.go
new file mode 100644
index 0000000..2461200
--- /dev/null
+++ b/openstack/orchestration/v1/stackresources/requests.go
@@ -0,0 +1,93 @@
+package stackresources
+
+import (
+ "github.com/racker/perigee"
+ "github.com/rackspace/gophercloud"
+ "github.com/rackspace/gophercloud/pagination"
+)
+
+// Find retreives stack resources for the given stack name.
+func Find(c *gophercloud.ServiceClient, stackName string) FindResult {
+ var res FindResult
+
+ // Send request to API
+ _, res.Err = perigee.Request("GET", findURL(c, stackName), perigee.Options{
+ MoreHeaders: c.AuthenticatedHeaders(),
+ Results: &res.Body,
+ OkCodes: []int{302},
+ })
+ return res
+}
+
+// ListOptsBuilder allows extensions to add additional parameters to the
+// List request.
+type ListOptsBuilder interface {
+ ToStackResourceListQuery() (string, error)
+}
+
+// ListOpts allows the filtering and sorting of paginated collections through
+// the API. Marker and Limit are used for pagination.
+type ListOpts struct {
+ // The stack resource ID with which to start the listing.
+ Marker string `q:"marker"`
+
+ // Integer value for the limit of values to return.
+ Limit int `q:"limit"`
+
+ // Include resources from nest stacks up to Depth levels of recursion.
+ Depth int `q:"nested_depth"`
+}
+
+// ToStackResourceListQuery formats a ListOpts into a query string.
+func (opts ListOpts) ToStackResourceListQuery() (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 resources for the given stack.
+func List(client *gophercloud.ServiceClient, stackName, stackID string, opts ListOptsBuilder) pagination.Pager {
+ url := listURL(client, stackName, stackID)
+
+ if opts != nil {
+ query, err := opts.ToStackResourceListQuery()
+ if err != nil {
+ return pagination.Pager{Err: err}
+ }
+ url += query
+ }
+
+ createPageFn := func(r pagination.PageResult) pagination.Page {
+ return ResourcePage{pagination.LinkedPageBase{PageResult: r}}
+ }
+
+ return pagination.NewPager(client, url, createPageFn)
+}
+
+// Get retreives data for the given stack resource.
+func Get(c *gophercloud.ServiceClient, stackName, stackID, resourceName string) GetResult {
+ var res GetResult
+
+ // Send request to API
+ _, res.Err = perigee.Request("GET", getURL(c, stackName, stackID, resourceName), perigee.Options{
+ MoreHeaders: c.AuthenticatedHeaders(),
+ Results: &res.Body,
+ OkCodes: []int{200},
+ })
+ return res
+}
+
+// Metadata retreives the metadata for the given stack resource.
+func Metadata(c *gophercloud.ServiceClient, stackName, stackID, resourceName string) MetadataResult {
+ var res MetadataResult
+
+ // Send request to API
+ _, res.Err = perigee.Request("GET", metadataURL(c, stackName, stackID, resourceName), perigee.Options{
+ MoreHeaders: c.AuthenticatedHeaders(),
+ Results: &res.Body,
+ OkCodes: []int{200},
+ })
+ return res
+}
diff --git a/openstack/orchestration/v1/stackresources/results.go b/openstack/orchestration/v1/stackresources/results.go
new file mode 100644
index 0000000..2de9fd3
--- /dev/null
+++ b/openstack/orchestration/v1/stackresources/results.go
@@ -0,0 +1,145 @@
+package stackresources
+
+import (
+ "time"
+
+ "github.com/mitchellh/mapstructure"
+ "github.com/rackspace/gophercloud"
+ "github.com/rackspace/gophercloud/pagination"
+)
+
+type Resource struct {
+ Links []gophercloud.Link `mapstructure:"links"`
+ LogicalID string `mapstructure:"logical_resource_id"`
+ Name string `mapstructure:"resource_name"`
+ PhysicalID string `mapstructure:"physical_resource_id"`
+ RequiredBy []interface{} `mapstructure:"required_by"`
+ Status string `mapstructure:"resource_status"`
+ StatusReason string `mapstructure:"resource_status_reason"`
+ Type string `mapstructure:"resource_type"`
+ UpdatedTime time.Time `mapstructure:"-"`
+}
+
+type FindResult struct {
+ gophercloud.Result
+}
+
+func (r FindResult) Extract() ([]Resource, error) {
+ if r.Err != nil {
+ return nil, r.Err
+ }
+
+ var res struct {
+ Res []Resource `mapstructure:"resources"`
+ }
+
+ if err := mapstructure.Decode(r.Body, &res); err != nil {
+ return nil, err
+ }
+
+ resources := r.Body.(map[string]interface{})["resources"].([]map[string]interface{})
+
+ for i, resource := range resources {
+ if date, ok := resource["updated_time"]; ok && date != nil {
+ t, err := time.Parse(time.RFC3339, date.(string))
+ if err != nil {
+ return nil, err
+ }
+ res.Res[i].UpdatedTime = t
+ }
+ }
+
+ return res.Res, nil
+}
+
+// ResourcePage 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 the ExtractResources call.
+type ResourcePage struct {
+ pagination.LinkedPageBase
+}
+
+// IsEmpty returns true if a page contains no Server results.
+func (page ResourcePage) IsEmpty() (bool, error) {
+ resources, err := ExtractResources(page)
+ if err != nil {
+ return true, err
+ }
+ return len(resources) == 0, nil
+}
+
+// NextPageURL uses the response's embedded link reference to navigate to the next page of results.
+func (page ResourcePage) NextPageURL() (string, error) {
+ type resp struct {
+ Links []gophercloud.Link `mapstructure:"servers_links"`
+ }
+
+ var r resp
+ err := mapstructure.Decode(page.Body, &r)
+ if err != nil {
+ return "", err
+ }
+
+ return gophercloud.ExtractNextURL(r.Links)
+}
+
+// ExtractResources interprets the results of a single page from a List() call, producing a slice of Resource entities.
+func ExtractResources(page pagination.Page) ([]Resource, error) {
+ casted := page.(ResourcePage).Body
+
+ var response struct {
+ Resources []Resource `mapstructure:"resources"`
+ }
+ err := mapstructure.Decode(casted, &response)
+ return response.Resources, err
+}
+
+type GetResult struct {
+ gophercloud.Result
+}
+
+func (r GetResult) Extract() (*Resource, error) {
+ if r.Err != nil {
+ return nil, r.Err
+ }
+
+ var res struct {
+ Res *Resource `mapstructure:"resource"`
+ }
+
+ if err := mapstructure.Decode(r.Body, &res); err != nil {
+ return nil, err
+ }
+
+ resource := r.Body.(map[string]interface{})["resource"].(map[string]interface{})
+
+ if date, ok := resource["updated_time"]; ok && date != nil {
+ t, err := time.Parse(time.RFC3339, date.(string))
+ if err != nil {
+ return nil, err
+ }
+ res.Res.UpdatedTime = t
+ }
+
+ return res.Res, nil
+}
+
+type MetadataResult struct {
+ gophercloud.Result
+}
+
+func (r MetadataResult) Extract() (map[string]string, error) {
+ if r.Err != nil {
+ return nil, r.Err
+ }
+
+ var res struct {
+ Meta map[string]string `mapstructure:"metadata"`
+ }
+
+ if err := mapstructure.Decode(r.Body, &res); err != nil {
+ return nil, err
+ }
+
+ return res.Meta, nil
+}
diff --git a/openstack/orchestration/v1/stackresources/urls.go b/openstack/orchestration/v1/stackresources/urls.go
new file mode 100644
index 0000000..ef078d9
--- /dev/null
+++ b/openstack/orchestration/v1/stackresources/urls.go
@@ -0,0 +1,31 @@
+package stackresources
+
+import "github.com/rackspace/gophercloud"
+
+func findURL(c *gophercloud.ServiceClient, stackName string) string {
+ return c.ServiceURL("stacks", stackName, "resources")
+}
+
+func listURL(c *gophercloud.ServiceClient, stackName, stackID string) string {
+ return c.ServiceURL("stacks", stackName, stackID, "resources")
+}
+
+func getURL(c *gophercloud.ServiceClient, stackName, stackID, resourceName string) string {
+ return c.ServiceURL("stacks", stackName, stackID, "resources", resourceName)
+}
+
+func metadataURL(c *gophercloud.ServiceClient, stackName, stackID, resourceName string) string {
+ return c.ServiceURL("stacks", stackName, stackID, "resources", resourceName, "metadata")
+}
+
+func listTypesURL(c *gophercloud.ServiceClient) string {
+ return c.ServiceURL("resource_types")
+}
+
+func schemaURL(c *gophercloud.ServiceClient, typeName string) string {
+ return c.ServiceURL("resource_types", typeName)
+}
+
+func templateURL(c *gophercloud.ServiceClient, typeName string) string {
+ return c.ServiceURL("resource_types", typeName, "template")
+}