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")
+}