Merge pull request #495 from jrperritt/optimize-object-upload

[rfr] don't copy file contents for etag
diff --git a/.travis.yml b/.travis.yml
index 0882a56..bd9e4c6 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,10 +2,10 @@
 install:
   - go get -v -tags 'fixtures acceptance' ./...
 go:
-  - 1.1
   - 1.2
   - 1.3
   - 1.4
+  - 1.5
   - tip
 script: script/cibuild
 after_success:
diff --git a/acceptance/openstack/orchestration/v1/stacks_test.go b/acceptance/openstack/orchestration/v1/stacks_test.go
index 01e76d6..db31cd4 100644
--- a/acceptance/openstack/orchestration/v1/stacks_test.go
+++ b/acceptance/openstack/orchestration/v1/stacks_test.go
@@ -79,3 +79,75 @@
 	t.Logf("Abandonded stack %+v\n", abandonedStack)
 	th.AssertNoErr(t, err)
 }
+
+// Test using the updated interface
+func TestStacksNewTemplateFormat(t *testing.T) {
+	// Create a provider client for making the HTTP requests.
+	// See common.go in this directory for more information.
+	client := newClient(t)
+
+	stackName1 := "gophercloud-test-stack-2"
+	templateOpts := new(osStacks.Template)
+	templateOpts.Bin = []byte(template)
+	createOpts := osStacks.CreateOpts{
+		Name:         stackName1,
+		TemplateOpts: templateOpts,
+		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, stackName1, stack.ID).ExtractErr()
+		th.AssertNoErr(t, err)
+		t.Logf("Deleted stack (%s)", stackName1)
+	}()
+	err = gophercloud.WaitFor(60, func() (bool, error) {
+		getStack, err := stacks.Get(client, stackName1, stack.ID).Extract()
+		if err != nil {
+			return false, err
+		}
+		if getStack.Status == "CREATE_COMPLETE" {
+			return true, nil
+		}
+		return false, nil
+	})
+
+	updateOpts := osStacks.UpdateOpts{
+		TemplateOpts: templateOpts,
+		Timeout:      20,
+	}
+	err = stacks.Update(client, stackName1, stack.ID, updateOpts).ExtractErr()
+	th.AssertNoErr(t, err)
+	err = gophercloud.WaitFor(60, func() (bool, error) {
+		getStack, err := stacks.Get(client, stackName1, stack.ID).Extract()
+		if err != nil {
+			return false, err
+		}
+		if getStack.Status == "UPDATE_COMPLETE" {
+			return true, nil
+		}
+		return false, nil
+	})
+
+	t.Logf("Updated stack")
+
+	err = stacks.List(client, nil).EachPage(func(page pagination.Page) (bool, error) {
+		stackList, err := osStacks.ExtractStacks(page)
+		th.AssertNoErr(t, err)
+
+		t.Logf("Got stack list: %+v\n", stackList)
+
+		return true, nil
+	})
+	th.AssertNoErr(t, err)
+
+	getStack, err := stacks.Get(client, stackName1, stack.ID).Extract()
+	th.AssertNoErr(t, err)
+	t.Logf("Got stack: %+v\n", getStack)
+
+	abandonedStack, err := stacks.Abandon(client, stackName1, stack.ID).Extract()
+	th.AssertNoErr(t, err)
+	t.Logf("Abandonded stack %+v\n", abandonedStack)
+	th.AssertNoErr(t, err)
+}
diff --git a/acceptance/openstack/orchestration/v1/stacktemplates_test.go b/acceptance/openstack/orchestration/v1/stacktemplates_test.go
index 14d8f44..22d5e88 100644
--- a/acceptance/openstack/orchestration/v1/stacktemplates_test.go
+++ b/acceptance/openstack/orchestration/v1/stacktemplates_test.go
@@ -46,22 +46,21 @@
 	th.AssertNoErr(t, err)
 	t.Logf("retrieved template: %+v\n", tmpl)
 
-	validateOpts := stacktemplates.ValidateOpts{
-		Template: map[string]interface{}{
-			"heat_template_version": "2013-05-23",
-			"description":           "Simple template to test heat commands",
-			"parameters": map[string]interface{}{
-				"flavor": map[string]interface{}{
+	validateOpts := osStacktemplates.ValidateOpts{
+		Template: `{"heat_template_version": "2013-05-23",
+			"description": "Simple template to test heat commands",
+			"parameters": {
+				"flavor": {
 					"default": "m1.tiny",
 					"type":    "string",
 				},
 			},
-			"resources": map[string]interface{}{
-				"hello_world": map[string]interface{}{
+			"resources": {
+				"hello_world": {
 					"type": "OS::Nova::Server",
-					"properties": map[string]interface{}{
+					"properties": {
 						"key_name": "heat_key",
-						"flavor": map[string]interface{}{
+						"flavor": {
 							"get_param": "flavor",
 						},
 						"image":     "ad091b52-742f-469e-8f3c-fd81cadf0743",
@@ -69,8 +68,7 @@
 					},
 				},
 			},
-		},
-	}
+		}`}
 	validatedTemplate, err := stacktemplates.Validate(client, validateOpts).Extract()
 	th.AssertNoErr(t, err)
 	t.Logf("validated template: %+v\n", validatedTemplate)
diff --git a/acceptance/rackspace/orchestration/v1/stacks_test.go b/acceptance/rackspace/orchestration/v1/stacks_test.go
index cfec4e9..61969b5 100644
--- a/acceptance/rackspace/orchestration/v1/stacks_test.go
+++ b/acceptance/rackspace/orchestration/v1/stacks_test.go
@@ -80,3 +80,75 @@
 	t.Logf("Abandonded stack %+v\n", abandonedStack)
 	th.AssertNoErr(t, err)
 }
+
+// Test using the updated interface
+func TestStacksNewTemplateFormat(t *testing.T) {
+	// Create a provider client for making the HTTP requests.
+	// See common.go in this directory for more information.
+	client := newClient(t)
+
+	stackName1 := "gophercloud-test-stack-2"
+	templateOpts := new(osStacks.Template)
+	templateOpts.Bin = []byte(template)
+	createOpts := osStacks.CreateOpts{
+		Name:         stackName1,
+		TemplateOpts: templateOpts,
+		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, stackName1, stack.ID).ExtractErr()
+		th.AssertNoErr(t, err)
+		t.Logf("Deleted stack (%s)", stackName1)
+	}()
+	err = gophercloud.WaitFor(60, func() (bool, error) {
+		getStack, err := stacks.Get(client, stackName1, stack.ID).Extract()
+		if err != nil {
+			return false, err
+		}
+		if getStack.Status == "CREATE_COMPLETE" {
+			return true, nil
+		}
+		return false, nil
+	})
+
+	updateOpts := osStacks.UpdateOpts{
+		TemplateOpts: templateOpts,
+		Timeout:      20,
+	}
+	err = stacks.Update(client, stackName1, stack.ID, updateOpts).ExtractErr()
+	th.AssertNoErr(t, err)
+	err = gophercloud.WaitFor(60, func() (bool, error) {
+		getStack, err := stacks.Get(client, stackName1, stack.ID).Extract()
+		if err != nil {
+			return false, err
+		}
+		if getStack.Status == "UPDATE_COMPLETE" {
+			return true, nil
+		}
+		return false, nil
+	})
+
+	t.Logf("Updated stack")
+
+	err = stacks.List(client, nil).EachPage(func(page pagination.Page) (bool, error) {
+		stackList, err := osStacks.ExtractStacks(page)
+		th.AssertNoErr(t, err)
+
+		t.Logf("Got stack list: %+v\n", stackList)
+
+		return true, nil
+	})
+	th.AssertNoErr(t, err)
+
+	getStack, err := stacks.Get(client, stackName1, stack.ID).Extract()
+	th.AssertNoErr(t, err)
+	t.Logf("Got stack: %+v\n", getStack)
+
+	abandonedStack, err := stacks.Abandon(client, stackName1, stack.ID).Extract()
+	th.AssertNoErr(t, err)
+	t.Logf("Abandonded stack %+v\n", abandonedStack)
+	th.AssertNoErr(t, err)
+}
diff --git a/acceptance/rackspace/orchestration/v1/stacktemplates_test.go b/acceptance/rackspace/orchestration/v1/stacktemplates_test.go
index 1f7b217..e4ccd9e 100644
--- a/acceptance/rackspace/orchestration/v1/stacktemplates_test.go
+++ b/acceptance/rackspace/orchestration/v1/stacktemplates_test.go
@@ -49,21 +49,20 @@
 	t.Logf("retrieved template: %+v\n", tmpl)
 
 	validateOpts := osStacktemplates.ValidateOpts{
-		Template: map[string]interface{}{
-			"heat_template_version": "2013-05-23",
+		Template: `{"heat_template_version": "2013-05-23",
 			"description":           "Simple template to test heat commands",
-			"parameters": map[string]interface{}{
-				"flavor": map[string]interface{}{
+			"parameters": {
+				"flavor": {
 					"default": "m1.tiny",
 					"type":    "string",
 				},
 			},
-			"resources": map[string]interface{}{
-				"hello_world": map[string]interface{}{
+			"resources": {
+				"hello_world": {
 					"type": "OS::Nova::Server",
-					"properties": map[string]interface{}{
+					"properties": {
 						"key_name": "heat_key",
-						"flavor": map[string]interface{}{
+						"flavor": {
 							"get_param": "flavor",
 						},
 						"image":     "ad091b52-742f-469e-8f3c-fd81cadf0743",
@@ -71,8 +70,7 @@
 					},
 				},
 			},
-		},
-	}
+		}`}
 	validatedTemplate, err := stacktemplates.Validate(client, validateOpts).Extract()
 	th.AssertNoErr(t, err)
 	t.Logf("validated template: %+v\n", validatedTemplate)
diff --git a/openstack/orchestration/v1/stackresources/fixtures.go b/openstack/orchestration/v1/stackresources/fixtures.go
index c3c3d3f..952dc54 100644
--- a/openstack/orchestration/v1/stackresources/fixtures.go
+++ b/openstack/orchestration/v1/stackresources/fixtures.go
@@ -28,10 +28,13 @@
 		LogicalID:    "hello_world",
 		StatusReason: "state changed",
 		UpdatedTime:  time.Date(2015, 2, 5, 21, 33, 11, 0, time.UTC),
+		CreationTime: time.Date(2015, 2, 5, 21, 33, 10, 0, time.UTC),
 		RequiredBy:   []interface{}{},
 		Status:       "CREATE_IN_PROGRESS",
 		PhysicalID:   "49181cd6-169a-4130-9455-31185bbfc5bf",
 		Type:         "OS::Nova::Server",
+		Attributes:   map[string]interface{}{"SXSW": "atx"},
+		Description:  "Some resource",
 	},
 }
 
@@ -40,6 +43,8 @@
 {
   "resources": [
   {
+  	"description": "Some resource",
+  	"attributes": {"SXSW": "atx"},
     "resource_name": "hello_world",
     "links": [
       {
@@ -54,6 +59,7 @@
     "logical_resource_id": "hello_world",
     "resource_status_reason": "state changed",
     "updated_time": "2015-02-05T21:33:11",
+	"creation_time": "2015-02-05T21:33:10",
     "required_by": [],
     "resource_status": "CREATE_IN_PROGRESS",
     "physical_resource_id": "49181cd6-169a-4130-9455-31185bbfc5bf",
@@ -93,10 +99,13 @@
 		LogicalID:    "hello_world",
 		StatusReason: "state changed",
 		UpdatedTime:  time.Date(2015, 2, 5, 21, 33, 11, 0, time.UTC),
+		CreationTime: time.Date(2015, 2, 5, 21, 33, 10, 0, time.UTC),
 		RequiredBy:   []interface{}{},
 		Status:       "CREATE_IN_PROGRESS",
 		PhysicalID:   "49181cd6-169a-4130-9455-31185bbfc5bf",
 		Type:         "OS::Nova::Server",
+		Attributes:   map[string]interface{}{"SXSW": "atx"},
+		Description:  "Some resource",
 	},
 }
 
@@ -121,7 +130,10 @@
     "required_by": [],
     "resource_status": "CREATE_IN_PROGRESS",
     "physical_resource_id": "49181cd6-169a-4130-9455-31185bbfc5bf",
-    "resource_type": "OS::Nova::Server"
+	"creation_time": "2015-02-05T21:33:10",
+    "resource_type": "OS::Nova::Server",
+	"attributes": {"SXSW": "atx"},
+	"description": "Some resource"
   }
 ]
 }`
@@ -162,6 +174,7 @@
 		},
 	},
 	LogicalID:    "wordpress_instance",
+	Attributes:   map[string]interface{}{"SXSW": "atx"},
 	StatusReason: "state changed",
 	UpdatedTime:  time.Date(2014, 12, 10, 18, 34, 35, 0, time.UTC),
 	RequiredBy:   []interface{}{},
@@ -174,6 +187,8 @@
 const GetOutput = `
 {
   "resource": {
+    "description": "Some resource",
+    "attributes": {"SXSW": "atx"},
     "resource_name": "wordpress_instance",
     "description": "",
     "links": [
@@ -240,7 +255,7 @@
 }
 
 // ListTypesExpected represents the expected object from a ListTypes request.
-var ListTypesExpected = []string{
+var ListTypesExpected = ResourceTypes{
 	"OS::Nova::Server",
 	"OS::Heat::RandomString",
 	"OS::Swift::Container",
@@ -251,6 +266,18 @@
 	"OS::Nova::KeyPair",
 }
 
+// same as above, but sorted
+var SortedListTypesExpected = ResourceTypes{
+	"OS::Cinder::VolumeAttachment",
+	"OS::Heat::RandomString",
+	"OS::Nova::FloatingIP",
+	"OS::Nova::FloatingIPAssociation",
+	"OS::Nova::KeyPair",
+	"OS::Nova::Server",
+	"OS::Swift::Container",
+	"OS::Trove::Instance",
+}
+
 // ListTypesOutput represents the response body from a ListTypes request.
 const ListTypesOutput = `
 {
@@ -296,6 +323,11 @@
 		},
 	},
 	ResourceType: "OS::Heat::AResourceName",
+	SupportStatus: map[string]interface{}{
+		"message": "A status message",
+		"status":  "SUPPORTED",
+		"version": "2014.1",
+	},
 }
 
 // GetSchemaOutput represents the response body from a Schema request.
@@ -314,7 +346,12 @@
       "description": "A resource description."
     }
   },
-  "resource_type": "OS::Heat::AResourceName"
+  "resource_type": "OS::Heat::AResourceName",
+  "support_status": {
+	"message": "A status message",
+	"status": "SUPPORTED",
+	"version": "2014.1"
+  }
 }`
 
 // HandleGetSchemaSuccessfully creates an HTTP handler at `/resource_types/OS::Heat::AResourceName`
@@ -332,56 +369,7 @@
 }
 
 // GetTemplateExpected represents the expected object from a Template request.
-var GetTemplateExpected = &TypeTemplate{
-	HeatTemplateFormatVersion: "2012-12-12",
-	Outputs: map[string]interface{}{
-		"private_key": map[string]interface{}{
-			"Description": "The private key if it has been saved.",
-			"Value":       "{\"Fn::GetAtt\": [\"KeyPair\", \"private_key\"]}",
-		},
-		"public_key": map[string]interface{}{
-			"Description": "The public key.",
-			"Value":       "{\"Fn::GetAtt\": [\"KeyPair\", \"public_key\"]}",
-		},
-	},
-	Parameters: map[string]interface{}{
-		"name": map[string]interface{}{
-			"Description": "The name of the key pair.",
-			"Type":        "String",
-		},
-		"public_key": map[string]interface{}{
-			"Description": "The optional public key. This allows users to supply the public key from a pre-existing key pair. If not supplied, a new key pair will be generated.",
-			"Type":        "String",
-		},
-		"save_private_key": map[string]interface{}{
-			"AllowedValues": []string{
-				"True",
-				"true",
-				"False",
-				"false",
-			},
-			"Default":     false,
-			"Description": "True if the system should remember a generated private key; False otherwise.",
-			"Type":        "String",
-		},
-	},
-	Resources: map[string]interface{}{
-		"KeyPair": map[string]interface{}{
-			"Properties": map[string]interface{}{
-				"name": map[string]interface{}{
-					"Ref": "name",
-				},
-				"public_key": map[string]interface{}{
-					"Ref": "public_key",
-				},
-				"save_private_key": map[string]interface{}{
-					"Ref": "save_private_key",
-				},
-			},
-			"Type": "OS::Nova::KeyPair",
-		},
-	},
-}
+var GetTemplateExpected = "{\n  \"HeatTemplateFormatVersion\": \"2012-12-12\",\n  \"Outputs\": {\n    \"private_key\": {\n      \"Description\": \"The private key if it has been saved.\",\n      \"Value\": \"{\\\"Fn::GetAtt\\\": [\\\"KeyPair\\\", \\\"private_key\\\"]}\"\n    },\n    \"public_key\": {\n      \"Description\": \"The public key.\",\n      \"Value\": \"{\\\"Fn::GetAtt\\\": [\\\"KeyPair\\\", \\\"public_key\\\"]}\"\n    }\n  },\n  \"Parameters\": {\n    \"name\": {\n      \"Description\": \"The name of the key pair.\",\n      \"Type\": \"String\"\n    },\n    \"public_key\": {\n      \"Description\": \"The optional public key. This allows users to supply the public key from a pre-existing key pair. If not supplied, a new key pair will be generated.\",\n      \"Type\": \"String\"\n    },\n    \"save_private_key\": {\n      \"AllowedValues\": [\n        \"True\",\n        \"true\",\n        \"False\",\n        \"false\"\n      ],\n      \"Default\": false,\n      \"Description\": \"True if the system should remember a generated private key; False otherwise.\",\n      \"Type\": \"String\"\n    }\n  },\n  \"Resources\": {\n    \"KeyPair\": {\n      \"Properties\": {\n        \"name\": {\n          \"Ref\": \"name\"\n        },\n        \"public_key\": {\n          \"Ref\": \"public_key\"\n        },\n        \"save_private_key\": {\n          \"Ref\": \"save_private_key\"\n        }\n      },\n      \"Type\": \"OS::Nova::KeyPair\"\n    }\n  }\n}"
 
 // GetTemplateOutput represents the response body from a Template request.
 const GetTemplateOutput = `
diff --git a/openstack/orchestration/v1/stackresources/requests_test.go b/openstack/orchestration/v1/stackresources/requests_test.go
index f137878..e5045a7 100644
--- a/openstack/orchestration/v1/stackresources/requests_test.go
+++ b/openstack/orchestration/v1/stackresources/requests_test.go
@@ -1,6 +1,7 @@
 package stackresources
 
 import (
+	"sort"
 	"testing"
 
 	"github.com/rackspace/gophercloud/pagination"
@@ -75,6 +76,9 @@
 		th.AssertNoErr(t, err)
 
 		th.CheckDeepEquals(t, ListTypesExpected, actual)
+		// test if sorting works
+		sort.Sort(actual)
+		th.CheckDeepEquals(t, SortedListTypesExpected, actual)
 
 		return true, nil
 	})
@@ -103,5 +107,5 @@
 	th.AssertNoErr(t, err)
 
 	expected := GetTemplateExpected
-	th.AssertDeepEquals(t, expected, actual)
+	th.AssertDeepEquals(t, expected, string(actual))
 }
diff --git a/openstack/orchestration/v1/stackresources/results.go b/openstack/orchestration/v1/stackresources/results.go
index df79d58..6ddc766 100644
--- a/openstack/orchestration/v1/stackresources/results.go
+++ b/openstack/orchestration/v1/stackresources/results.go
@@ -1,6 +1,7 @@
 package stackresources
 
 import (
+	"encoding/json"
 	"fmt"
 	"reflect"
 	"time"
@@ -12,15 +13,18 @@
 
 // Resource represents a stack resource.
 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:"-"`
+	Attributes   map[string]interface{} `mapstructure:"attributes"`
+	CreationTime time.Time              `mapstructure:"-"`
+	Description  string                 `mapstructure:"description"`
+	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:"-"`
 }
 
 // FindResult represents the result of a Find operation.
@@ -54,6 +58,13 @@
 			}
 			res.Res[i].UpdatedTime = t
 		}
+		if date, ok := resource["creation_time"]; ok && date != nil {
+			t, err := time.Parse(gophercloud.STACK_TIME_FMT, date.(string))
+			if err != nil {
+				return nil, err
+			}
+			res.Res[i].CreationTime = t
+		}
 	}
 
 	return res.Res, nil
@@ -75,18 +86,6 @@
 	return len(resources) == 0, nil
 }
 
-// LastMarker returns the last container name in a ListResult.
-func (r ResourcePage) LastMarker() (string, error) {
-	resources, err := ExtractResources(r)
-	if err != nil {
-		return "", err
-	}
-	if len(resources) == 0 {
-		return "", nil
-	}
-	return resources[len(resources)-1].PhysicalID, nil
-}
-
 // 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
@@ -94,8 +93,9 @@
 	var response struct {
 		Resources []Resource `mapstructure:"resources"`
 	}
-	err := mapstructure.Decode(casted, &response)
-
+	if err := mapstructure.Decode(casted, &response); err != nil {
+		return nil, err
+	}
 	var resources []interface{}
 	switch casted.(type) {
 	case map[string]interface{}:
@@ -115,9 +115,16 @@
 			}
 			response.Resources[i].UpdatedTime = t
 		}
+		if date, ok := resource["creation_time"]; ok && date != nil {
+			t, err := time.Parse(gophercloud.STACK_TIME_FMT, date.(string))
+			if err != nil {
+				return nil, err
+			}
+			response.Resources[i].CreationTime = t
+		}
 	}
 
-	return response.Resources, err
+	return response.Resources, nil
 }
 
 // GetResult represents the result of a Get operation.
@@ -149,6 +156,13 @@
 		}
 		res.Res.UpdatedTime = t
 	}
+	if date, ok := resource["creation_time"]; ok && date != nil {
+		t, err := time.Parse(gophercloud.STACK_TIME_FMT, date.(string))
+		if err != nil {
+			return nil, err
+		}
+		res.Res.CreationTime = t
+	}
 
 	return res.Res, nil
 }
@@ -192,21 +206,42 @@
 	return len(rts) == 0, nil
 }
 
+// ResourceTypes represents the type that holds the result of ExtractResourceTypes.
+// We define methods on this type to sort it before output
+type ResourceTypes []string
+
+func (r ResourceTypes) Len() int {
+	return len(r)
+}
+
+func (r ResourceTypes) Swap(i, j int) {
+	r[i], r[j] = r[j], r[i]
+}
+
+func (r ResourceTypes) Less(i, j int) bool {
+	return r[i] < r[j]
+}
+
 // ExtractResourceTypes extracts and returns resource types.
-func ExtractResourceTypes(page pagination.Page) ([]string, error) {
+func ExtractResourceTypes(page pagination.Page) (ResourceTypes, error) {
+	casted := page.(ResourceTypePage).Body
+
 	var response struct {
-		ResourceTypes []string `mapstructure:"resource_types"`
+		ResourceTypes ResourceTypes `mapstructure:"resource_types"`
 	}
 
-	err := mapstructure.Decode(page.(ResourceTypePage).Body, &response)
-	return response.ResourceTypes, err
+	if err := mapstructure.Decode(casted, &response); err != nil {
+		return nil, err
+	}
+	return response.ResourceTypes, nil
 }
 
 // TypeSchema represents a stack resource schema.
 type TypeSchema struct {
-	Attributes   map[string]interface{} `mapstructure:"attributes"`
-	Properties   map[string]interface{} `mapstrucutre:"properties"`
-	ResourceType string                 `mapstructure:"resource_type"`
+	Attributes    map[string]interface{} `mapstructure:"attributes"`
+	Properties    map[string]interface{} `mapstrucutre:"properties"`
+	ResourceType  string                 `mapstructure:"resource_type"`
+	SupportStatus map[string]interface{} `mapstructure:"support_status"`
 }
 
 // SchemaResult represents the result of a Schema operation.
@@ -230,31 +265,20 @@
 	return &res, nil
 }
 
-// TypeTemplate represents a stack resource template.
-type TypeTemplate struct {
-	HeatTemplateFormatVersion string
-	Outputs                   map[string]interface{}
-	Parameters                map[string]interface{}
-	Resources                 map[string]interface{}
-}
-
 // TemplateResult represents the result of a Template operation.
 type TemplateResult struct {
 	gophercloud.Result
 }
 
-// Extract returns a pointer to a TypeTemplate object and is called after a
+// Extract returns the template and is called after a
 // Template operation.
-func (r TemplateResult) Extract() (*TypeTemplate, error) {
+func (r TemplateResult) Extract() ([]byte, error) {
 	if r.Err != nil {
 		return nil, r.Err
 	}
-
-	var res TypeTemplate
-
-	if err := mapstructure.Decode(r.Body, &res); err != nil {
+	template, err := json.MarshalIndent(r.Body, "", "  ")
+	if err != nil {
 		return nil, err
 	}
-
-	return &res, nil
+	return template, nil
 }
diff --git a/openstack/orchestration/v1/stacks/environment.go b/openstack/orchestration/v1/stacks/environment.go
new file mode 100644
index 0000000..abaff20
--- /dev/null
+++ b/openstack/orchestration/v1/stacks/environment.go
@@ -0,0 +1,137 @@
+package stacks
+
+import (
+	"fmt"
+	"strings"
+)
+
+// Environment is a structure that represents stack environments
+type Environment struct {
+	TE
+}
+
+// EnvironmentSections is a map containing allowed sections in a stack environment file
+var EnvironmentSections = map[string]bool{
+	"parameters":         true,
+	"parameter_defaults": true,
+	"resource_registry":  true,
+}
+
+// Validate validates the contents of the Environment
+func (e *Environment) Validate() error {
+	if e.Parsed == nil {
+		if err := e.Parse(); err != nil {
+			return err
+		}
+	}
+	for key := range e.Parsed {
+		if _, ok := EnvironmentSections[key]; !ok {
+			return fmt.Errorf("Environment has wrong section: %s", key)
+		}
+	}
+	return nil
+}
+
+// Parse environment file to resolve the URL's of the resources. This is done by
+// reading from the `Resource Registry` section, which is why the function is
+// named GetRRFileContents.
+func (e *Environment) getRRFileContents(ignoreIf igFunc) error {
+	// initialize environment if empty
+	if e.Files == nil {
+		e.Files = make(map[string]string)
+	}
+	if e.fileMaps == nil {
+		e.fileMaps = make(map[string]string)
+	}
+
+	// get the resource registry
+	rr := e.Parsed["resource_registry"]
+
+	// search the resource registry for URLs
+	switch rr.(type) {
+	// process further only if the resource registry is a map
+	case map[string]interface{}, map[interface{}]interface{}:
+		rrMap, err := toStringKeys(rr)
+		if err != nil {
+			return err
+		}
+		// the resource registry might contain a base URL for the resource. If
+		// such a field is present, use it. Otherwise, use the default base URL.
+		var baseURL string
+		if val, ok := rrMap["base_url"]; ok {
+			baseURL = val.(string)
+		} else {
+			baseURL = e.baseURL
+		}
+
+		// The contents of the resource may be located in a remote file, which
+		// will be a template. Instantiate a temporary template to manage the
+		// contents.
+		tempTemplate := new(Template)
+		tempTemplate.baseURL = baseURL
+		tempTemplate.client = e.client
+
+		// Fetch the contents of remote resource URL's
+		if err = tempTemplate.getFileContents(rr, ignoreIf, false); err != nil {
+			return err
+		}
+		// check the `resources` section (if it exists) for more URL's. Note that
+		// the previous call to GetFileContents was (deliberately) not recursive
+		// as we want more control over where to look for URL's
+		if val, ok := rrMap["resources"]; ok {
+			switch val.(type) {
+			// process further only if the contents are a map
+			case map[string]interface{}, map[interface{}]interface{}:
+				resourcesMap, err := toStringKeys(val)
+				if err != nil {
+					return err
+				}
+				for _, v := range resourcesMap {
+					switch v.(type) {
+					case map[string]interface{}, map[interface{}]interface{}:
+						resourceMap, err := toStringKeys(v)
+						if err != nil {
+							return err
+						}
+						var resourceBaseURL string
+						// if base_url for the resource type is defined, use it
+						if val, ok := resourceMap["base_url"]; ok {
+							resourceBaseURL = val.(string)
+						} else {
+							resourceBaseURL = baseURL
+						}
+						tempTemplate.baseURL = resourceBaseURL
+						if err := tempTemplate.getFileContents(v, ignoreIf, false); err != nil {
+							return err
+						}
+					}
+				}
+			}
+		}
+		// if the resource registry contained any URL's, store them. This can
+		// then be passed as parameter to api calls to Heat api.
+		e.Files = tempTemplate.Files
+		return nil
+	default:
+		return nil
+	}
+}
+
+// function to choose keys whose values are other environment files
+func ignoreIfEnvironment(key string, value interface{}) bool {
+	// base_url and hooks refer to components which cannot have urls
+	if key == "base_url" || key == "hooks" {
+		return true
+	}
+	// if value is not string, it cannot be a URL
+	valueString, ok := value.(string)
+	if !ok {
+		return true
+	}
+	// if value contains `::`, it must be a reference to another resource type
+	// e.g. OS::Nova::Server : Rackspace::Cloud::Server
+	if strings.Contains(valueString, "::") {
+		return true
+	}
+	return false
+}
diff --git a/openstack/orchestration/v1/stacks/environment_test.go b/openstack/orchestration/v1/stacks/environment_test.go
new file mode 100644
index 0000000..3a3c2b9
--- /dev/null
+++ b/openstack/orchestration/v1/stacks/environment_test.go
@@ -0,0 +1,184 @@
+package stacks
+
+import (
+	"fmt"
+	"net/http"
+	"net/url"
+	"strings"
+	"testing"
+
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestEnvironmentValidation(t *testing.T) {
+	environmentJSON := new(Environment)
+	environmentJSON.Bin = []byte(ValidJSONEnvironment)
+	err := environmentJSON.Validate()
+	th.AssertNoErr(t, err)
+
+	environmentYAML := new(Environment)
+	environmentYAML.Bin = []byte(ValidYAMLEnvironment)
+	err = environmentYAML.Validate()
+	th.AssertNoErr(t, err)
+
+	environmentInvalid := new(Environment)
+	environmentInvalid.Bin = []byte(InvalidEnvironment)
+	if err = environmentInvalid.Validate(); err == nil {
+		t.Error("environment validation did not catch invalid environment")
+	}
+}
+
+func TestEnvironmentParsing(t *testing.T) {
+	environmentJSON := new(Environment)
+	environmentJSON.Bin = []byte(ValidJSONEnvironment)
+	err := environmentJSON.Parse()
+	th.AssertNoErr(t, err)
+	th.AssertDeepEquals(t, ValidJSONEnvironmentParsed, environmentJSON.Parsed)
+
+	environmentYAML := new(Environment)
+	environmentYAML.Bin = []byte(ValidJSONEnvironment)
+	err = environmentYAML.Parse()
+	th.AssertNoErr(t, err)
+	th.AssertDeepEquals(t, ValidJSONEnvironmentParsed, environmentYAML.Parsed)
+
+	environmentInvalid := new(Environment)
+	environmentInvalid.Bin = []byte("Keep Austin Weird")
+	err = environmentInvalid.Parse()
+	if err == nil {
+		t.Error("environment parsing did not catch invalid environment")
+	}
+}
+
+func TestIgnoreIfEnvironment(t *testing.T) {
+	var keyValueTests = []struct {
+		key   string
+		value interface{}
+		out   bool
+	}{
+		{"base_url", "afksdf", true},
+		{"not_type", "hooks", false},
+		{"get_file", "::", true},
+		{"hooks", "dfsdfsd", true},
+		{"type", "sdfubsduf.yaml", false},
+		{"type", "sdfsdufs.environment", false},
+		{"type", "sdfsdf.file", false},
+		{"type", map[string]string{"key": "value"}, true},
+	}
+	var result bool
+	for _, kv := range keyValueTests {
+		result = ignoreIfEnvironment(kv.key, kv.value)
+		if result != kv.out {
+			t.Errorf("key: %v, value: %v expected: %v, actual: %v", kv.key, kv.value, kv.out, result)
+		}
+	}
+}
+
+func TestGetRRFileContents(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	environmentContent := `
+heat_template_version: 2013-05-23
+
+description:
+  Heat WordPress template to support F18, using only Heat OpenStack-native
+  resource types, and without the requirement for heat-cfntools in the image.
+  WordPress is web software you can use to create a beautiful website or blog.
+  This template installs a single-instance WordPress deployment using a local
+  MySQL database to store the data.
+
+parameters:
+
+  key_name:
+    type: string
+    description : Name of a KeyPair to enable SSH access to the instance
+
+resources:
+  wordpress_instance:
+    type: OS::Nova::Server
+    properties:
+      image: { get_param: image_id }
+      flavor: { get_param: instance_type }
+      key_name: { get_param: key_name }`
+
+	dbContent := `
+heat_template_version: 2014-10-16
+
+description:
+  Test template for Trove resource capabilities
+
+parameters:
+  db_pass:
+    type: string
+    hidden: true
+    description: Database access password
+    default: secrete
+
+resources:
+
+service_db:
+  type: OS::Trove::Instance
+  properties:
+    name: trove_test_db
+    datastore_type: mariadb
+    flavor: 1GB Instance
+    size: 10
+    databases:
+    - name: test_data
+    users:
+    - name: kitchen_sink
+      password: { get_param: db_pass }
+      databases: [ test_data ]`
+	baseurl, err := getBasePath()
+	th.AssertNoErr(t, err)
+
+	fakeEnvURL := strings.Join([]string{baseurl, "my_env.yaml"}, "/")
+	urlparsed, err := url.Parse(fakeEnvURL)
+	th.AssertNoErr(t, err)
+	// handler for my_env.yaml
+	th.Mux.HandleFunc(urlparsed.Path, func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		w.Header().Set("Content-Type", "application/jason")
+		w.WriteHeader(http.StatusOK)
+		fmt.Fprintf(w, environmentContent)
+	})
+
+	fakeDBURL := strings.Join([]string{baseurl, "my_db.yaml"}, "/")
+	urlparsed, err = url.Parse(fakeDBURL)
+	th.AssertNoErr(t, err)
+
+	// handler for my_db.yaml
+	th.Mux.HandleFunc(urlparsed.Path, func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		w.Header().Set("Content-Type", "application/jason")
+		w.WriteHeader(http.StatusOK)
+		fmt.Fprintf(w, dbContent)
+	})
+
+	client := fakeClient{BaseClient: getHTTPClient()}
+	env := new(Environment)
+	env.Bin = []byte(`{"resource_registry": {"My::WP::Server": "my_env.yaml", "resources": {"my_db_server": {"OS::DBInstance": "my_db.yaml"}}}}`)
+	env.client = client
+
+	err = env.Parse()
+	th.AssertNoErr(t, err)
+	err = env.getRRFileContents(ignoreIfEnvironment)
+	th.AssertNoErr(t, err)
+	expectedEnvFilesContent := "\nheat_template_version: 2013-05-23\n\ndescription:\n  Heat WordPress template to support F18, using only Heat OpenStack-native\n  resource types, and without the requirement for heat-cfntools in the image.\n  WordPress is web software you can use to create a beautiful website or blog.\n  This template installs a single-instance WordPress deployment using a local\n  MySQL database to store the data.\n\nparameters:\n\n  key_name:\n    type: string\n    description : Name of a KeyPair to enable SSH access to the instance\n\nresources:\n  wordpress_instance:\n    type: OS::Nova::Server\n    properties:\n      image: { get_param: image_id }\n      flavor: { get_param: instance_type }\n      key_name: { get_param: key_name }"
+	expectedDBFilesContent := "\nheat_template_version: 2014-10-16\n\ndescription:\n  Test template for Trove resource capabilities\n\nparameters:\n  db_pass:\n    type: string\n    hidden: true\n    description: Database access password\n    default: secrete\n\nresources:\n\nservice_db:\n  type: OS::Trove::Instance\n  properties:\n    name: trove_test_db\n    datastore_type: mariadb\n    flavor: 1GB Instance\n    size: 10\n    databases:\n    - name: test_data\n    users:\n    - name: kitchen_sink\n      password: { get_param: db_pass }\n      databases: [ test_data ]"
+
+	th.AssertEquals(t, expectedEnvFilesContent, env.Files[fakeEnvURL])
+	th.AssertEquals(t, expectedDBFilesContent, env.Files[fakeDBURL])
+
+	env.fixFileRefs()
+	expectedParsed := map[string]interface{}{
+		"resource_registry": "2015-04-30",
+		"My::WP::Server":    fakeEnvURL,
+		"resources": map[string]interface{}{
+			"my_db_server": map[string]interface{}{
+				"OS::DBInstance": fakeDBURL,
+			},
+		},
+	}
+	env.Parse()
+	th.AssertDeepEquals(t, expectedParsed, env.Parsed)
+}
diff --git a/openstack/orchestration/v1/stacks/fixtures.go b/openstack/orchestration/v1/stacks/fixtures.go
index 3a621da..83f5dec 100644
--- a/openstack/orchestration/v1/stacks/fixtures.go
+++ b/openstack/orchestration/v1/stacks/fixtures.go
@@ -63,6 +63,7 @@
 		CreationTime: time.Date(2015, 2, 3, 20, 7, 39, 0, time.UTC),
 		Status:       "CREATE_COMPLETE",
 		ID:           "16ef0584-4458-41eb-87c8-0dc8d5f66c87",
+		Tags:         []string{"rackspace", "atx"},
 	},
 	ListedStack{
 		Description: "Simple template to test heat commands",
@@ -78,6 +79,7 @@
 		UpdatedTime:  time.Date(2014, 12, 11, 17, 40, 37, 0, time.UTC),
 		Status:       "UPDATE_COMPLETE",
 		ID:           "db6977b2-27aa-4775-9ae7-6213212d4ada",
+		Tags:         []string{"sfo", "satx"},
 	},
 }
 
@@ -98,7 +100,8 @@
     "creation_time": "2015-02-03T20:07:39",
     "updated_time": null,
     "stack_status": "CREATE_COMPLETE",
-    "id": "16ef0584-4458-41eb-87c8-0dc8d5f66c87"
+    "id": "16ef0584-4458-41eb-87c8-0dc8d5f66c87",
+	"tags": ["rackspace", "atx"]
   },
   {
     "description": "Simple template to test heat commands",
@@ -113,7 +116,8 @@
     "creation_time": "2014-12-11T17:39:16",
     "updated_time": "2014-12-11T17:40:37",
     "stack_status": "UPDATE_COMPLETE",
-    "id": "db6977b2-27aa-4775-9ae7-6213212d4ada"
+    "id": "db6977b2-27aa-4775-9ae7-6213212d4ada",
+	"tags": ["sfo", "satx"]
   }
   ]
 }
@@ -165,6 +169,7 @@
 	Status:              "CREATE_COMPLETE",
 	ID:                  "16ef0584-4458-41eb-87c8-0dc8d5f66c87",
 	TemplateDescription: "Simple template to test heat commands",
+	Tags:                []string{"rackspace", "atx"},
 }
 
 // GetOutput represents the response body from a Get request.
@@ -194,7 +199,8 @@
     "stack_status": "CREATE_COMPLETE",
     "updated_time": null,
     "id": "16ef0584-4458-41eb-87c8-0dc8d5f66c87",
-    "template_description": "Simple template to test heat commands"
+    "template_description": "Simple template to test heat commands",
+	"tags": ["rackspace", "atx"]
   }
 }
 `
@@ -248,7 +254,6 @@
 		"OS::stack_name": "postman_stack",
 		"OS::stack_id":   "16ef0584-4458-41eb-87c8-0dc8d5f66c87",
 	},
-	StatusReason: "Stack CREATE completed successfully",
 	Name:         "postman_stack",
 	CreationTime: time.Date(2015, 2, 3, 20, 7, 39, 0, time.UTC),
 	Links: []gophercloud.Link{
@@ -259,7 +264,6 @@
 	},
 	Capabilities:        []interface{}{},
 	NotificationTopics:  []interface{}{},
-	Status:              "CREATE_COMPLETE",
 	ID:                  "16ef0584-4458-41eb-87c8-0dc8d5f66c87",
 	TemplateDescription: "Simple template to test heat commands",
 }
@@ -316,6 +320,20 @@
 			"type":        "OS::Nova::Server",
 		},
 	},
+	Files: map[string]string{
+		"file:///Users/prat8228/go/src/github.com/rackspace/rack/my_nova.yaml": "heat_template_version: 2014-10-16\nparameters:\n  flavor:\n    type: string\n    description: Flavor for the server to be created\n    default: 4353\n    hidden: true\nresources:\n  test_server:\n    type: \"OS::Nova::Server\"\n    properties:\n      name: test-server\n      flavor: 2 GB General Purpose v1\n image: Debian 7 (Wheezy) (PVHVM)\n",
+	},
+	StackUserProjectID: "897686",
+	ProjectID:          "897686",
+	Environment: map[string]interface{}{
+		"encrypted_param_names": make([]map[string]interface{}, 0),
+		"parameter_defaults":    make(map[string]interface{}),
+		"parameters":            make(map[string]interface{}),
+		"resource_registry": map[string]interface{}{
+			"file:///Users/prat8228/go/src/github.com/rackspace/rack/my_nova.yaml": "file:///Users/prat8228/go/src/github.com/rackspace/rack/my_nova.yaml",
+			"resources": make(map[string]interface{}),
+		},
+	},
 }
 
 // AbandonOutput represents the response body from an Abandon request.
@@ -354,21 +372,233 @@
       "name": "hello_world",
       "resource_id": "8a310d36-46fc-436f-8be4-37a696b8ac63",
       "action": "CREATE",
-      "type": "OS::Nova::Server",
+      "type": "OS::Nova::Server"
     }
-  }
+  },
+  "files": {
+    "file:///Users/prat8228/go/src/github.com/rackspace/rack/my_nova.yaml": "heat_template_version: 2014-10-16\nparameters:\n  flavor:\n    type: string\n    description: Flavor for the server to be created\n    default: 4353\n    hidden: true\nresources:\n  test_server:\n    type: \"OS::Nova::Server\"\n    properties:\n      name: test-server\n      flavor: 2 GB General Purpose v1\n image: Debian 7 (Wheezy) (PVHVM)\n"
+},
+  "environment": {
+	"encrypted_param_names": [],
+	"parameter_defaults": {},
+	"parameters": {},
+	"resource_registry": {
+		"file:///Users/prat8228/go/src/github.com/rackspace/rack/my_nova.yaml": "file:///Users/prat8228/go/src/github.com/rackspace/rack/my_nova.yaml",
+		"resources": {}
+	}
+  },
+  "stack_user_project_id": "897686",
+  "project_id": "897686"
 }`
 
 // HandleAbandonSuccessfully creates an HTTP handler at `/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87/abandon`
 // on the test handler mux that responds with an `Abandon` response.
-func HandleAbandonSuccessfully(t *testing.T) {
-	th.Mux.HandleFunc("/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87/abandon", func(w http.ResponseWriter, r *http.Request) {
+func HandleAbandonSuccessfully(t *testing.T, output string) {
+	th.Mux.HandleFunc("/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c8/abandon", func(w http.ResponseWriter, r *http.Request) {
 		th.TestMethod(t, r, "DELETE")
 		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
 		th.TestHeader(t, r, "Accept", "application/json")
 
 		w.Header().Set("Content-Type", "application/json")
 		w.WriteHeader(http.StatusOK)
-		fmt.Fprintf(w, AbandonOutput)
+		fmt.Fprintf(w, output)
 	})
 }
+
+// ValidJSONTemplate is a valid OpenStack Heat template in JSON format
+const ValidJSONTemplate = `
+{
+  "heat_template_version": "2014-10-16",
+  "parameters": {
+    "flavor": {
+      "default": 4353,
+      "description": "Flavor for the server to be created",
+      "hidden": true,
+      "type": "string"
+    }
+  },
+  "resources": {
+    "test_server": {
+      "properties": {
+        "flavor": "2 GB General Purpose v1",
+        "image": "Debian 7 (Wheezy) (PVHVM)",
+        "name": "test-server"
+      },
+      "type": "OS::Nova::Server"
+    }
+  }
+}
+`
+
+// ValidJSONTemplateParsed is the expected parsed version of ValidJSONTemplate
+var ValidJSONTemplateParsed = map[string]interface{}{
+	"heat_template_version": "2014-10-16",
+	"parameters": map[string]interface{}{
+		"flavor": map[string]interface{}{
+			"default":     4353,
+			"description": "Flavor for the server to be created",
+			"hidden":      true,
+			"type":        "string",
+		},
+	},
+	"resources": map[string]interface{}{
+		"test_server": map[string]interface{}{
+			"properties": map[string]interface{}{
+				"flavor": "2 GB General Purpose v1",
+				"image":  "Debian 7 (Wheezy) (PVHVM)",
+				"name":   "test-server",
+			},
+			"type": "OS::Nova::Server",
+		},
+	},
+}
+
+// ValidYAMLTemplate is a valid OpenStack Heat template in YAML format
+const ValidYAMLTemplate = `
+heat_template_version: 2014-10-16
+parameters:
+  flavor:
+    type: string
+    description: Flavor for the server to be created
+    default: 4353
+    hidden: true
+resources:
+  test_server:
+    type: "OS::Nova::Server"
+    properties:
+      name: test-server
+      flavor: 2 GB General Purpose v1
+      image: Debian 7 (Wheezy) (PVHVM)
+`
+
+// InvalidTemplateNoVersion is an invalid template as it has no `version` section
+const InvalidTemplateNoVersion = `
+parameters:
+  flavor:
+    type: string
+    description: Flavor for the server to be created
+    default: 4353
+    hidden: true
+resources:
+  test_server:
+    type: "OS::Nova::Server"
+    properties:
+      name: test-server
+      flavor: 2 GB General Purpose v1
+      image: Debian 7 (Wheezy) (PVHVM)
+`
+
+// ValidJSONEnvironment is a valid environment for a stack in JSON format
+const ValidJSONEnvironment = `
+{
+  "parameters": {
+    "user_key": "userkey"
+  },
+  "resource_registry": {
+    "My::WP::Server": "file:///home/shardy/git/heat-templates/hot/F18/WordPress_Native.yaml",
+    "OS::Quantum*": "OS::Neutron*",
+    "AWS::CloudWatch::Alarm": "file:///etc/heat/templates/AWS_CloudWatch_Alarm.yaml",
+    "OS::Metering::Alarm": "OS::Ceilometer::Alarm",
+    "AWS::RDS::DBInstance": "file:///etc/heat/templates/AWS_RDS_DBInstance.yaml",
+    "resources": {
+      "my_db_server": {
+        "OS::DBInstance": "file:///home/mine/all_my_cool_templates/db.yaml"
+      },
+      "my_server": {
+        "OS::DBInstance": "file:///home/mine/all_my_cool_templates/db.yaml",
+        "hooks": "pre-create"
+      },
+      "nested_stack": {
+        "nested_resource": {
+          "hooks": "pre-update"
+        },
+        "another_resource": {
+          "hooks": [
+            "pre-create",
+            "pre-update"
+          ]
+        }
+      }
+    }
+  }
+}
+`
+
+// ValidJSONEnvironmentParsed is the expected parsed version of ValidJSONEnvironment
+var ValidJSONEnvironmentParsed = map[string]interface{}{
+	"parameters": map[string]interface{}{
+		"user_key": "userkey",
+	},
+	"resource_registry": map[string]interface{}{
+		"My::WP::Server":         "file:///home/shardy/git/heat-templates/hot/F18/WordPress_Native.yaml",
+		"OS::Quantum*":           "OS::Neutron*",
+		"AWS::CloudWatch::Alarm": "file:///etc/heat/templates/AWS_CloudWatch_Alarm.yaml",
+		"OS::Metering::Alarm":    "OS::Ceilometer::Alarm",
+		"AWS::RDS::DBInstance":   "file:///etc/heat/templates/AWS_RDS_DBInstance.yaml",
+		"resources": map[string]interface{}{
+			"my_db_server": map[string]interface{}{
+				"OS::DBInstance": "file:///home/mine/all_my_cool_templates/db.yaml",
+			},
+			"my_server": map[string]interface{}{
+				"OS::DBInstance": "file:///home/mine/all_my_cool_templates/db.yaml",
+				"hooks":          "pre-create",
+			},
+			"nested_stack": map[string]interface{}{
+				"nested_resource": map[string]interface{}{
+					"hooks": "pre-update",
+				},
+				"another_resource": map[string]interface{}{
+					"hooks": []interface{}{
+						"pre-create",
+						"pre-update",
+					},
+				},
+			},
+		},
+	},
+}
+
+// ValidYAMLEnvironment is a valid environment for a stack in YAML format
+const ValidYAMLEnvironment = `
+parameters:
+  user_key: userkey
+resource_registry:
+  My::WP::Server: file:///home/shardy/git/heat-templates/hot/F18/WordPress_Native.yaml
+  # allow older templates with Quantum in them.
+  "OS::Quantum*": "OS::Neutron*"
+  # Choose your implementation of AWS::CloudWatch::Alarm
+  "AWS::CloudWatch::Alarm": "file:///etc/heat/templates/AWS_CloudWatch_Alarm.yaml"
+  #"AWS::CloudWatch::Alarm": "OS::Heat::CWLiteAlarm"
+  "OS::Metering::Alarm": "OS::Ceilometer::Alarm"
+  "AWS::RDS::DBInstance": "file:///etc/heat/templates/AWS_RDS_DBInstance.yaml"
+  resources:
+    my_db_server:
+      "OS::DBInstance": file:///home/mine/all_my_cool_templates/db.yaml
+    my_server:
+      "OS::DBInstance": file:///home/mine/all_my_cool_templates/db.yaml
+      hooks: pre-create
+    nested_stack:
+      nested_resource:
+        hooks: pre-update
+      another_resource:
+        hooks: [pre-create, pre-update]
+`
+
+// InvalidEnvironment is an invalid environment as it has an extra section called `resources`
+const InvalidEnvironment = `
+parameters:
+  flavor:
+    type: string
+    description: Flavor for the server to be created
+    default: 4353
+    hidden: true
+resources:
+  test_server:
+    type: "OS::Nova::Server"
+    properties:
+      name: test-server
+      flavor: 2 GB General Purpose v1
+      image: Debian 7 (Wheezy) (PVHVM)
+parameter_defaults:
+  KeyName: heat_key
+`
diff --git a/openstack/orchestration/v1/stacks/requests.go b/openstack/orchestration/v1/stacks/requests.go
index 0dd6af2..8616644 100644
--- a/openstack/orchestration/v1/stacks/requests.go
+++ b/openstack/orchestration/v1/stacks/requests.go
@@ -2,6 +2,7 @@
 
 import (
 	"errors"
+	"strings"
 
 	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/pagination"
@@ -32,9 +33,16 @@
 type CreateOpts struct {
 	// (REQUIRED) The name of the stack. It must start with an alphabetic character.
 	Name string
+	// (REQUIRED) A structure that contains either the template file or url. Call the
+	// associated methods to extract the information relevant to send in a create request.
+	TemplateOpts *Template
+	// (DEPRECATED): Please use TemplateOpts for providing the template. If
+	// TemplateOpts is provided, TemplateURL will be ignored
 	// (OPTIONAL; REQUIRED IF Template IS EMPTY) The URL of the template to instantiate.
 	// This value is ignored if Template is supplied inline.
 	TemplateURL string
+	// (DEPRECATED): Please use TemplateOpts for providing the template. If
+	// TemplateOpts is provided, Template will be ignored
 	// (OPTIONAL; REQUIRED IF TemplateURL IS EMPTY) A template to instantiate. The value
 	// is a stringified version of the JSON/YAML template. Since the template will likely
 	// be located in a file, one way to set this variable is by using ioutil.ReadFile:
@@ -50,8 +58,14 @@
 	// creation fails. Default is true, meaning all resources are not deleted when
 	// stack creation fails.
 	DisableRollback Rollback
+	// (OPTIONAL) A structure that contains details for the environment of the stack.
+	EnvironmentOpts *Environment
+	// (DEPRECATED): Please use EnvironmentOpts to provide Environment data
 	// (OPTIONAL) A stringified JSON environment for the stack.
 	Environment string
+	// (DEPRECATED): Files is automatically determined
+	// by parsing the template and environment passed as TemplateOpts and
+	// EnvironmentOpts respectively.
 	// (OPTIONAL) A map that maps file names to file contents. It can also be used
 	// to pass provider template contents. Example:
 	// Files: `{"myfile": "#!/bin/bash\necho 'Hello world' > /root/testfile.txt"}`
@@ -60,6 +74,8 @@
 	Parameters map[string]string
 	// (OPTIONAL) The timeout for stack creation in minutes.
 	Timeout int
+	// (OPTIONAL) A list of tags to assosciate with the Stack
+	Tags []string
 }
 
 // ToStackCreateMap casts a CreateOpts struct to a map.
@@ -70,25 +86,60 @@
 		return s, errors.New("Required field 'Name' not provided.")
 	}
 	s["stack_name"] = opts.Name
-
-	if opts.Template != "" {
-		s["template"] = opts.Template
-	} else if opts.TemplateURL != "" {
-		s["template_url"] = opts.TemplateURL
+	Files := make(map[string]string)
+	if opts.TemplateOpts == nil {
+		if opts.Template != "" {
+			s["template"] = opts.Template
+		} else if opts.TemplateURL != "" {
+			s["template_url"] = opts.TemplateURL
+		} else {
+			return s, errors.New("Either Template or TemplateURL must be provided.")
+		}
 	} else {
-		return s, errors.New("Either Template or TemplateURL must be provided.")
+		if err := opts.TemplateOpts.Parse(); err != nil {
+			return nil, err
+		}
+
+		if err := opts.TemplateOpts.getFileContents(opts.TemplateOpts.Parsed, ignoreIfTemplate, true); err != nil {
+			return nil, err
+		}
+		opts.TemplateOpts.fixFileRefs()
+		s["template"] = string(opts.TemplateOpts.Bin)
+
+		for k, v := range opts.TemplateOpts.Files {
+			Files[k] = v
+		}
+	}
+	if opts.DisableRollback != nil {
+		s["disable_rollback"] = &opts.DisableRollback
+	}
+
+	if opts.EnvironmentOpts != nil {
+		if err := opts.EnvironmentOpts.Parse(); err != nil {
+			return nil, err
+		}
+		if err := opts.EnvironmentOpts.getRRFileContents(ignoreIfEnvironment); err != nil {
+			return nil, err
+		}
+		opts.EnvironmentOpts.fixFileRefs()
+		for k, v := range opts.EnvironmentOpts.Files {
+			Files[k] = v
+		}
+		s["environment"] = string(opts.EnvironmentOpts.Bin)
+	} else if opts.Environment != "" {
+		s["environment"] = opts.Environment
+	}
+
+	if opts.Files != nil {
+		s["files"] = opts.Files
+	} else {
+		s["files"] = Files
 	}
 
 	if opts.DisableRollback != nil {
 		s["disable_rollback"] = &opts.DisableRollback
 	}
 
-	if opts.Environment != "" {
-		s["environment"] = opts.Environment
-	}
-	if opts.Files != nil {
-		s["files"] = opts.Files
-	}
 	if opts.Parameters != nil {
 		s["parameters"] = opts.Parameters
 	}
@@ -97,6 +148,9 @@
 		s["timeout_mins"] = opts.Timeout
 	}
 
+	if opts.Tags != nil {
+		s["tags"] = strings.Join(opts.Tags, ",")
+	}
 	return s, nil
 }
 
@@ -133,9 +187,16 @@
 	Name string
 	// (REQUIRED) The timeout for stack creation in minutes.
 	Timeout int
+	// (REQUIRED) A structure that contains either the template file or url. Call the
+	// associated methods to extract the information relevant to send in a create request.
+	TemplateOpts *Template
+	// (DEPRECATED): Please use TemplateOpts for providing the template. If
+	// TemplateOpts is provided, TemplateURL will be ignored
 	// (OPTIONAL; REQUIRED IF Template IS EMPTY) The URL of the template to instantiate.
 	// This value is ignored if Template is supplied inline.
 	TemplateURL string
+	// (DEPRECATED): Please use TemplateOpts for providing the template. If
+	// TemplateOpts is provided, Template will be ignored
 	// (OPTIONAL; REQUIRED IF TemplateURL IS EMPTY) A template to instantiate. The value
 	// is a stringified version of the JSON/YAML template. Since the template will likely
 	// be located in a file, one way to set this variable is by using ioutil.ReadFile:
@@ -151,8 +212,14 @@
 	// creation fails. Default is true, meaning all resources are not deleted when
 	// stack creation fails.
 	DisableRollback Rollback
+	// (OPTIONAL) A structure that contains details for the environment of the stack.
+	EnvironmentOpts *Environment
+	// (DEPRECATED): Please use EnvironmentOpts to provide Environment data
 	// (OPTIONAL) A stringified JSON environment for the stack.
 	Environment string
+	// (DEPRECATED): Files is automatically determined
+	// by parsing the template and environment passed as TemplateOpts and
+	// EnvironmentOpts respectively.
 	// (OPTIONAL) A map that maps file names to file contents. It can also be used
 	// to pass provider template contents. Example:
 	// Files: `{"myfile": "#!/bin/bash\necho 'Hello world' > /root/testfile.txt"}`
@@ -169,15 +236,30 @@
 		return s, errors.New("Required field 'Name' not provided.")
 	}
 	s["stack_name"] = opts.Name
-
-	if opts.Template != "" {
-		s["template"] = opts.Template
-	} else if opts.TemplateURL != "" {
-		s["template_url"] = opts.TemplateURL
+	Files := make(map[string]string)
+	if opts.TemplateOpts == nil {
+		if opts.Template != "" {
+			s["template"] = opts.Template
+		} else if opts.TemplateURL != "" {
+			s["template_url"] = opts.TemplateURL
+		} else {
+			return s, errors.New("Either Template or TemplateURL must be provided.")
+		}
 	} else {
-		return s, errors.New("Either Template or TemplateURL must be provided.")
-	}
+		if err := opts.TemplateOpts.Parse(); err != nil {
+			return nil, err
+		}
 
+		if err := opts.TemplateOpts.getFileContents(opts.TemplateOpts.Parsed, ignoreIfTemplate, true); err != nil {
+			return nil, err
+		}
+		opts.TemplateOpts.fixFileRefs()
+		s["template"] = string(opts.TemplateOpts.Bin)
+
+		for k, v := range opts.TemplateOpts.Files {
+			Files[k] = v
+		}
+	}
 	if opts.AdoptStackData == "" {
 		return s, errors.New("Required field 'AdoptStackData' not provided.")
 	}
@@ -187,22 +269,38 @@
 		s["disable_rollback"] = &opts.DisableRollback
 	}
 
-	if opts.Environment != "" {
+	if opts.EnvironmentOpts != nil {
+		if err := opts.EnvironmentOpts.Parse(); err != nil {
+			return nil, err
+		}
+		if err := opts.EnvironmentOpts.getRRFileContents(ignoreIfEnvironment); err != nil {
+			return nil, err
+		}
+		opts.EnvironmentOpts.fixFileRefs()
+		for k, v := range opts.EnvironmentOpts.Files {
+			Files[k] = v
+		}
+		s["environment"] = string(opts.EnvironmentOpts.Bin)
+	} else if opts.Environment != "" {
 		s["environment"] = opts.Environment
 	}
+
 	if opts.Files != nil {
 		s["files"] = opts.Files
+	} else {
+		s["files"] = Files
 	}
+
 	if opts.Parameters != nil {
 		s["parameters"] = opts.Parameters
 	}
 
-	if opts.Timeout == 0 {
-		return nil, errors.New("Required field 'Timeout' not provided.")
+	if opts.Timeout != 0 {
+		s["timeout"] = opts.Timeout
 	}
 	s["timeout_mins"] = opts.Timeout
 
-	return map[string]interface{}{"stack": s}, nil
+	return s, nil
 }
 
 // Adopt accepts an AdoptOpts struct and creates a new stack using the resources
@@ -305,9 +403,16 @@
 // UpdateOpts contains the common options struct used in this package's Update
 // operation.
 type UpdateOpts struct {
+	// (REQUIRED) A structure that contains either the template file or url. Call the
+	// associated methods to extract the information relevant to send in a create request.
+	TemplateOpts *Template
+	// (DEPRECATED): Please use TemplateOpts for providing the template. If
+	// TemplateOpts is provided, TemplateURL will be ignored
 	// (OPTIONAL; REQUIRED IF Template IS EMPTY) The URL of the template to instantiate.
 	// This value is ignored if Template is supplied inline.
 	TemplateURL string
+	// (DEPRECATED): Please use TemplateOpts for providing the template. If
+	// TemplateOpts is provided, Template will be ignored
 	// (OPTIONAL; REQUIRED IF TemplateURL IS EMPTY) A template to instantiate. The value
 	// is a stringified version of the JSON/YAML template. Since the template will likely
 	// be located in a file, one way to set this variable is by using ioutil.ReadFile:
@@ -319,8 +424,14 @@
 	// }
 	// opts.Template = string(b)
 	Template string
+	// (OPTIONAL) A structure that contains details for the environment of the stack.
+	EnvironmentOpts *Environment
+	// (DEPRECATED): Please use EnvironmentOpts to provide Environment data
 	// (OPTIONAL) A stringified JSON environment for the stack.
 	Environment string
+	// (DEPRECATED): Files is automatically determined
+	// by parsing the template and environment passed as TemplateOpts and
+	// EnvironmentOpts respectively.
 	// (OPTIONAL) A map that maps file names to file contents. It can also be used
 	// to pass provider template contents. Example:
 	// Files: `{"myfile": "#!/bin/bash\necho 'Hello world' > /root/testfile.txt"}`
@@ -329,26 +440,58 @@
 	Parameters map[string]string
 	// (OPTIONAL) The timeout for stack creation in minutes.
 	Timeout int
+	// (OPTIONAL) A list of tags to assosciate with the Stack
+	Tags []string
 }
 
 // ToStackUpdateMap casts a CreateOpts struct to a map.
 func (opts UpdateOpts) ToStackUpdateMap() (map[string]interface{}, error) {
 	s := make(map[string]interface{})
-
-	if opts.Template != "" {
-		s["template"] = opts.Template
-	} else if opts.TemplateURL != "" {
-		s["template_url"] = opts.TemplateURL
+	Files := make(map[string]string)
+	if opts.TemplateOpts == nil {
+		if opts.Template != "" {
+			s["template"] = opts.Template
+		} else if opts.TemplateURL != "" {
+			s["template_url"] = opts.TemplateURL
+		} else {
+			return s, errors.New("Either Template or TemplateURL must be provided.")
+		}
 	} else {
-		return s, errors.New("Either Template or TemplateURL must be provided.")
+		if err := opts.TemplateOpts.Parse(); err != nil {
+			return nil, err
+		}
+
+		if err := opts.TemplateOpts.getFileContents(opts.TemplateOpts.Parsed, ignoreIfTemplate, true); err != nil {
+			return nil, err
+		}
+		opts.TemplateOpts.fixFileRefs()
+		s["template"] = string(opts.TemplateOpts.Bin)
+
+		for k, v := range opts.TemplateOpts.Files {
+			Files[k] = v
+		}
 	}
 
-	if opts.Environment != "" {
+	if opts.EnvironmentOpts != nil {
+		if err := opts.EnvironmentOpts.Parse(); err != nil {
+			return nil, err
+		}
+		if err := opts.EnvironmentOpts.getRRFileContents(ignoreIfEnvironment); err != nil {
+			return nil, err
+		}
+		opts.EnvironmentOpts.fixFileRefs()
+		for k, v := range opts.EnvironmentOpts.Files {
+			Files[k] = v
+		}
+		s["environment"] = string(opts.EnvironmentOpts.Bin)
+	} else if opts.Environment != "" {
 		s["environment"] = opts.Environment
 	}
 
 	if opts.Files != nil {
 		s["files"] = opts.Files
+	} else {
+		s["files"] = Files
 	}
 
 	if opts.Parameters != nil {
@@ -359,6 +502,10 @@
 		s["timeout_mins"] = opts.Timeout
 	}
 
+	if opts.Tags != nil {
+		s["tags"] = strings.Join(opts.Tags, ",")
+	}
+
 	return s, nil
 }
 
@@ -397,9 +544,16 @@
 	Name string
 	// (REQUIRED) The timeout for stack creation in minutes.
 	Timeout int
+	// (REQUIRED) A structure that contains either the template file or url. Call the
+	// associated methods to extract the information relevant to send in a create request.
+	TemplateOpts *Template
+	// (DEPRECATED): Please use TemplateOpts for providing the template. If
+	// TemplateOpts is provided, TemplateURL will be ignored
 	// (OPTIONAL; REQUIRED IF Template IS EMPTY) The URL of the template to instantiate.
 	// This value is ignored if Template is supplied inline.
 	TemplateURL string
+	// (DEPRECATED): Please use TemplateOpts for providing the template. If
+	// TemplateOpts is provided, Template will be ignored
 	// (OPTIONAL; REQUIRED IF TemplateURL IS EMPTY) A template to instantiate. The value
 	// is a stringified version of the JSON/YAML template. Since the template will likely
 	// be located in a file, one way to set this variable is by using ioutil.ReadFile:
@@ -415,8 +569,14 @@
 	// creation fails. Default is true, meaning all resources are not deleted when
 	// stack creation fails.
 	DisableRollback Rollback
+	// (OPTIONAL) A structure that contains details for the environment of the stack.
+	EnvironmentOpts *Environment
+	// (DEPRECATED): Please use EnvironmentOpts to provide Environment data
 	// (OPTIONAL) A stringified JSON environment for the stack.
 	Environment string
+	// (DEPRECATED): Files is automatically determined
+	// by parsing the template and environment passed as TemplateOpts and
+	// EnvironmentOpts respectively.
 	// (OPTIONAL) A map that maps file names to file contents. It can also be used
 	// to pass provider template contents. Example:
 	// Files: `{"myfile": "#!/bin/bash\necho 'Hello world' > /root/testfile.txt"}`
@@ -433,25 +593,56 @@
 		return s, errors.New("Required field 'Name' not provided.")
 	}
 	s["stack_name"] = opts.Name
-
-	if opts.Template != "" {
-		s["template"] = opts.Template
-	} else if opts.TemplateURL != "" {
-		s["template_url"] = opts.TemplateURL
+	Files := make(map[string]string)
+	if opts.TemplateOpts == nil {
+		if opts.Template != "" {
+			s["template"] = opts.Template
+		} else if opts.TemplateURL != "" {
+			s["template_url"] = opts.TemplateURL
+		} else {
+			return s, errors.New("Either Template or TemplateURL must be provided.")
+		}
 	} else {
-		return s, errors.New("Either Template or TemplateURL must be provided.")
-	}
+		if err := opts.TemplateOpts.Parse(); err != nil {
+			return nil, err
+		}
 
+		if err := opts.TemplateOpts.getFileContents(opts.TemplateOpts.Parsed, ignoreIfTemplate, true); err != nil {
+			return nil, err
+		}
+		opts.TemplateOpts.fixFileRefs()
+		s["template"] = string(opts.TemplateOpts.Bin)
+
+		for k, v := range opts.TemplateOpts.Files {
+			Files[k] = v
+		}
+	}
 	if opts.DisableRollback != nil {
 		s["disable_rollback"] = &opts.DisableRollback
 	}
 
-	if opts.Environment != "" {
+	if opts.EnvironmentOpts != nil {
+		if err := opts.EnvironmentOpts.Parse(); err != nil {
+			return nil, err
+		}
+		if err := opts.EnvironmentOpts.getRRFileContents(ignoreIfEnvironment); err != nil {
+			return nil, err
+		}
+		opts.EnvironmentOpts.fixFileRefs()
+		for k, v := range opts.EnvironmentOpts.Files {
+			Files[k] = v
+		}
+		s["environment"] = string(opts.EnvironmentOpts.Bin)
+	} else if opts.Environment != "" {
 		s["environment"] = opts.Environment
 	}
+
 	if opts.Files != nil {
 		s["files"] = opts.Files
+	} else {
+		s["files"] = Files
 	}
+
 	if opts.Parameters != nil {
 		s["parameters"] = opts.Parameters
 	}
diff --git a/openstack/orchestration/v1/stacks/requests_test.go b/openstack/orchestration/v1/stacks/requests_test.go
index 1e32ca2..0fde44b 100644
--- a/openstack/orchestration/v1/stacks/requests_test.go
+++ b/openstack/orchestration/v1/stacks/requests_test.go
@@ -52,6 +52,35 @@
 	th.AssertDeepEquals(t, expected, actual)
 }
 
+func TestCreateStackNewTemplateFormat(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleCreateSuccessfully(t, CreateOutput)
+	template := new(Template)
+	template.Bin = []byte(`
+		{
+			"heat_template_version": "2013-05-23",
+			"description": "Simple template to test heat commands",
+			"parameters": {
+				"flavor": {
+					"default": "m1.tiny",
+					"type": "string"
+				}
+			}
+		}`)
+	createOpts := CreateOpts{
+		Name:            "stackcreated",
+		Timeout:         60,
+		TemplateOpts:    template,
+		DisableRollback: Disable,
+	}
+	actual, err := Create(fake.ServiceClient(), createOpts).Extract()
+	th.AssertNoErr(t, err)
+
+	expected := CreateExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
+
 func TestAdoptStack(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
@@ -97,6 +126,52 @@
 	th.AssertDeepEquals(t, expected, actual)
 }
 
+func TestAdoptStackNewTemplateFormat(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleCreateSuccessfully(t, CreateOutput)
+	template := new(Template)
+	template.Bin = []byte(`
+{
+  "stack_name": "postman_stack",
+  "template": {
+	"heat_template_version": "2013-05-23",
+	"description": "Simple template to test heat commands",
+	"parameters": {
+	  "flavor": {
+		"default": "m1.tiny",
+		"type": "string"
+	  }
+	},
+	"resources": {
+	  "hello_world": {
+		"type":"OS::Nova::Server",
+		"properties": {
+		  "key_name": "heat_key",
+		  "flavor": {
+			"get_param": "flavor"
+		  },
+		  "image": "ad091b52-742f-469e-8f3c-fd81cadf0743",
+		  "user_data": "#!/bin/bash -xv\necho \"hello world\" &gt; /root/hello-world.txt\n"
+		}
+	  }
+	}
+  }
+}`)
+	adoptOpts := AdoptOpts{
+		AdoptStackData:  `{environment{parameters{}}}`,
+		Name:            "stackcreated",
+		Timeout:         60,
+		TemplateOpts:    template,
+		DisableRollback: Disable,
+	}
+	actual, err := Adopt(fake.ServiceClient(), adoptOpts).Extract()
+	th.AssertNoErr(t, err)
+
+	expected := CreateExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
+
 func TestListStack(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
@@ -163,6 +238,30 @@
 	th.AssertNoErr(t, err)
 }
 
+func TestUpdateStackNewTemplateFormat(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleUpdateSuccessfully(t)
+
+	template := new(Template)
+	template.Bin = []byte(`
+		{
+			"heat_template_version": "2013-05-23",
+			"description": "Simple template to test heat commands",
+			"parameters": {
+				"flavor": {
+					"default": "m1.tiny",
+					"type": "string"
+				}
+			}
+		}`)
+	updateOpts := UpdateOpts{
+		TemplateOpts: template,
+	}
+	err := Update(fake.ServiceClient(), "gophercloud-test-stack-2", "db6977b2-27aa-4775-9ae7-6213212d4ada", updateOpts).ExtractErr()
+	th.AssertNoErr(t, err)
+}
+
 func TestDeleteStack(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
@@ -215,3 +314,45 @@
 	expected := PreviewExpected
 	th.AssertDeepEquals(t, expected, actual)
 }
+
+func TestPreviewStackNewTemplateFormat(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandlePreviewSuccessfully(t, GetOutput)
+
+	template := new(Template)
+	template.Bin = []byte(`
+		{
+			"heat_template_version": "2013-05-23",
+			"description": "Simple template to test heat commands",
+			"parameters": {
+				"flavor": {
+					"default": "m1.tiny",
+					"type": "string"
+				}
+			}
+		}`)
+	previewOpts := PreviewOpts{
+		Name:            "stackcreated",
+		Timeout:         60,
+		TemplateOpts:    template,
+		DisableRollback: Disable,
+	}
+	actual, err := Preview(fake.ServiceClient(), previewOpts).Extract()
+	th.AssertNoErr(t, err)
+
+	expected := PreviewExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
+
+func TestAbandonStack(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleAbandonSuccessfully(t, AbandonOutput)
+
+	actual, err := Abandon(fake.ServiceClient(), "postman_stack", "16ef0584-4458-41eb-87c8-0dc8d5f66c8").Extract()
+	th.AssertNoErr(t, err)
+
+	expected := AbandonExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
diff --git a/openstack/orchestration/v1/stacks/results.go b/openstack/orchestration/v1/stacks/results.go
index dca06e4..432bc8e 100644
--- a/openstack/orchestration/v1/stacks/results.go
+++ b/openstack/orchestration/v1/stacks/results.go
@@ -69,6 +69,7 @@
 	Name         string             `mapstructure:"stack_name"`
 	Status       string             `mapstructure:"stack_status"`
 	StatusReason string             `mapstructure:"stack_status_reason"`
+	Tags         []string           `mapstructure:"tags"`
 	UpdatedTime  time.Time          `mapstructure:"-"`
 }
 
@@ -81,7 +82,7 @@
 		Stacks []ListedStack `mapstructure:"stacks"`
 	}
 
-	err := mapstructure.Decode(page.(StackPage).Body, &res)
+	err := mapstructure.Decode(casted, &res)
 	if err != nil {
 		return nil, err
 	}
@@ -133,6 +134,7 @@
 	Name                string                   `mapstructure:"stack_name"`
 	Status              string                   `mapstructure:"stack_status"`
 	StatusReason        string                   `mapstructure:"stack_status_reason"`
+	Tags                []string                 `mapstructure:"tags"`
 	TemplateDescription string                   `mapstructure:"template_description"`
 	Timeout             int                      `mapstructure:"timeout_mins"`
 	UpdatedTime         time.Time                `mapstructure:"-"`
@@ -200,21 +202,19 @@
 
 // PreviewedStack represents the result of a Preview operation.
 type PreviewedStack struct {
-	Capabilities        []interface{}            `mapstructure:"capabilities"`
-	CreationTime        time.Time                `mapstructure:"-"`
-	Description         string                   `mapstructure:"description"`
-	DisableRollback     bool                     `mapstructure:"disable_rollback"`
-	ID                  string                   `mapstructure:"id"`
-	Links               []gophercloud.Link       `mapstructure:"links"`
-	Name                string                   `mapstructure:"stack_name"`
-	NotificationTopics  []interface{}            `mapstructure:"notification_topics"`
-	Parameters          map[string]string        `mapstructure:"parameters"`
-	Resources           []map[string]interface{} `mapstructure:"resources"`
-	Status              string                   `mapstructure:"stack_status"`
-	StatusReason        string                   `mapstructure:"stack_status_reason"`
-	TemplateDescription string                   `mapstructure:"template_description"`
-	Timeout             int                      `mapstructure:"timeout_mins"`
-	UpdatedTime         time.Time                `mapstructure:"-"`
+	Capabilities        []interface{}      `mapstructure:"capabilities"`
+	CreationTime        time.Time          `mapstructure:"-"`
+	Description         string             `mapstructure:"description"`
+	DisableRollback     bool               `mapstructure:"disable_rollback"`
+	ID                  string             `mapstructure:"id"`
+	Links               []gophercloud.Link `mapstructure:"links"`
+	Name                string             `mapstructure:"stack_name"`
+	NotificationTopics  []interface{}      `mapstructure:"notification_topics"`
+	Parameters          map[string]string  `mapstructure:"parameters"`
+	Resources           []interface{}      `mapstructure:"resources"`
+	TemplateDescription string             `mapstructure:"template_description"`
+	Timeout             int                `mapstructure:"timeout_mins"`
+	UpdatedTime         time.Time          `mapstructure:"-"`
 }
 
 // PreviewResult represents the result of a Preview operation.
@@ -269,12 +269,16 @@
 
 // AbandonedStack represents the result of an Abandon operation.
 type AbandonedStack struct {
-	Status    string                 `mapstructure:"status"`
-	Name      string                 `mapstructure:"name"`
-	Template  map[string]interface{} `mapstructure:"template"`
-	Action    string                 `mapstructure:"action"`
-	ID        string                 `mapstructure:"id"`
-	Resources map[string]interface{} `mapstructure:"resources"`
+	Status             string                 `mapstructure:"status"`
+	Name               string                 `mapstructure:"name"`
+	Template           map[string]interface{} `mapstructure:"template"`
+	Action             string                 `mapstructure:"action"`
+	ID                 string                 `mapstructure:"id"`
+	Resources          map[string]interface{} `mapstructure:"resources"`
+	Files              map[string]string      `mapstructure:"files"`
+	StackUserProjectID string                 `mapstructure:"stack_user_project_id"`
+	ProjectID          string                 `mapstructure:"project_id"`
+	Environment        map[string]interface{} `mapstructure:"environment"`
 }
 
 // AbandonResult represents the result of an Abandon operation.
diff --git a/openstack/orchestration/v1/stacks/template.go b/openstack/orchestration/v1/stacks/template.go
new file mode 100644
index 0000000..234ce49
--- /dev/null
+++ b/openstack/orchestration/v1/stacks/template.go
@@ -0,0 +1,139 @@
+package stacks
+
+import (
+	"fmt"
+	"github.com/rackspace/gophercloud"
+	"reflect"
+	"strings"
+)
+
+// Template is a structure that represents OpenStack Heat templates
+type Template struct {
+	TE
+}
+
+// TemplateFormatVersions is a map containing allowed variations of the template format version
+// Note that this contains the permitted variations of the _keys_ not the values.
+var TemplateFormatVersions = map[string]bool{
+	"HeatTemplateFormatVersion": true,
+	"heat_template_version":     true,
+	"AWSTemplateFormatVersion":  true,
+}
+
+// Validate validates the contents of the Template
+func (t *Template) Validate() error {
+	if t.Parsed == nil {
+		if err := t.Parse(); err != nil {
+			return err
+		}
+	}
+	for key := range t.Parsed {
+		if _, ok := TemplateFormatVersions[key]; ok {
+			return nil
+		}
+	}
+	return fmt.Errorf("Template format version not found.")
+}
+
+// GetFileContents recursively parses a template to search for urls. These urls
+// are assumed to point to other templates (known in OpenStack Heat as child
+// templates). The contents of these urls are fetched and stored in the `Files`
+// parameter of the template structure. This is the only way that a user can
+// use child templates that are located in their filesystem; urls located on the
+// web (e.g. on github or swift) can be fetched directly by Heat engine.
+func (t *Template) getFileContents(te interface{}, ignoreIf igFunc, recurse bool) error {
+	// initialize template if empty
+	if t.Files == nil {
+		t.Files = make(map[string]string)
+	}
+	if t.fileMaps == nil {
+		t.fileMaps = make(map[string]string)
+	}
+	switch te.(type) {
+	// if te is a map
+	case map[string]interface{}, map[interface{}]interface{}:
+		teMap, err := toStringKeys(te)
+		if err != nil {
+			return err
+		}
+		for k, v := range teMap {
+			value, ok := v.(string)
+			if !ok {
+				// if the value is not a string, recursively parse that value
+				if err := t.getFileContents(v, ignoreIf, recurse); err != nil {
+					return err
+				}
+			} else if !ignoreIf(k, value) {
+				// at this point, the k, v pair has a reference to an external template.
+				// The assumption of heatclient is that value v is a reference
+				// to a file in the users environment
+
+				// create a new child template
+				childTemplate := new(Template)
+
+				// initialize child template
+
+				// get the base location of the child template
+				baseURL, err := gophercloud.NormalizePathURL(t.baseURL, value)
+				if err != nil {
+					return err
+				}
+				childTemplate.baseURL = baseURL
+				childTemplate.client = t.client
+
+				// fetch the contents of the child template
+				if err := childTemplate.Parse(); err != nil {
+					return err
+				}
+
+				// process child template recursively if required. This is
+				// required if the child template itself contains references to
+				// other templates
+				if recurse {
+					if err := childTemplate.getFileContents(childTemplate.Parsed, ignoreIf, recurse); err != nil {
+						return err
+					}
+				}
+				// update parent template with current child templates' content.
+				// At this point, the child template has been parsed recursively.
+				t.fileMaps[value] = childTemplate.URL
+				t.Files[childTemplate.URL] = string(childTemplate.Bin)
+
+			}
+		}
+		return nil
+	// if te is a slice, call the function on each element of the slice.
+	case []interface{}:
+		teSlice := te.([]interface{})
+		for i := range teSlice {
+			if err := t.getFileContents(teSlice[i], ignoreIf, recurse); err != nil {
+				return err
+			}
+		}
+	// if te is anything else, return
+	case string, bool, float64, nil, int:
+		return nil
+	default:
+		return fmt.Errorf("%v: Unrecognized type", reflect.TypeOf(te))
+
+	}
+	return nil
+}
+
+// function to choose keys whose values are other template files
+func ignoreIfTemplate(key string, value interface{}) bool {
+	// key must be either `get_file` or `type` for value to be a URL
+	if key != "get_file" && key != "type" {
+		return true
+	}
+	// value must be a string
+	valueString, ok := value.(string)
+	if !ok {
+		return true
+	}
+	// `.template` and `.yaml` are allowed suffixes for template URLs when referred to by `type`
+	if key == "type" && !(strings.HasSuffix(valueString, ".template") || strings.HasSuffix(valueString, ".yaml")) {
+		return true
+	}
+	return false
+}
diff --git a/openstack/orchestration/v1/stacks/template_test.go b/openstack/orchestration/v1/stacks/template_test.go
new file mode 100644
index 0000000..6884db8
--- /dev/null
+++ b/openstack/orchestration/v1/stacks/template_test.go
@@ -0,0 +1,148 @@
+package stacks
+
+import (
+	"fmt"
+	"net/http"
+	"net/url"
+	"strings"
+	"testing"
+
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestTemplateValidation(t *testing.T) {
+	templateJSON := new(Template)
+	templateJSON.Bin = []byte(ValidJSONTemplate)
+	err := templateJSON.Validate()
+	th.AssertNoErr(t, err)
+
+	templateYAML := new(Template)
+	templateYAML.Bin = []byte(ValidYAMLTemplate)
+	err = templateYAML.Validate()
+	th.AssertNoErr(t, err)
+
+	templateInvalid := new(Template)
+	templateInvalid.Bin = []byte(InvalidTemplateNoVersion)
+	if err = templateInvalid.Validate(); err == nil {
+		t.Error("Template validation did not catch invalid template")
+	}
+}
+
+func TestTemplateParsing(t *testing.T) {
+	templateJSON := new(Template)
+	templateJSON.Bin = []byte(ValidJSONTemplate)
+	err := templateJSON.Parse()
+	th.AssertNoErr(t, err)
+	th.AssertDeepEquals(t, ValidJSONTemplateParsed, templateJSON.Parsed)
+
+	templateYAML := new(Template)
+	templateYAML.Bin = []byte(ValidJSONTemplate)
+	err = templateYAML.Parse()
+	th.AssertNoErr(t, err)
+	th.AssertDeepEquals(t, ValidJSONTemplateParsed, templateYAML.Parsed)
+
+	templateInvalid := new(Template)
+	templateInvalid.Bin = []byte("Keep Austin Weird")
+	err = templateInvalid.Parse()
+	if err == nil {
+		t.Error("Template parsing did not catch invalid template")
+	}
+}
+
+func TestIgnoreIfTemplate(t *testing.T) {
+	var keyValueTests = []struct {
+		key   string
+		value interface{}
+		out   bool
+	}{
+		{"not_get_file", "afksdf", true},
+		{"not_type", "sdfd", true},
+		{"get_file", "shdfuisd", false},
+		{"type", "dfsdfsd", true},
+		{"type", "sdfubsduf.yaml", false},
+		{"type", "sdfsdufs.template", false},
+		{"type", "sdfsdf.file", true},
+		{"type", map[string]string{"key": "value"}, true},
+	}
+	var result bool
+	for _, kv := range keyValueTests {
+		result = ignoreIfTemplate(kv.key, kv.value)
+		if result != kv.out {
+			t.Errorf("key: %v, value: %v expected: %v, actual: %v", kv.key, kv.value, result, kv.out)
+		}
+	}
+}
+
+func TestGetFileContents(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	baseurl, err := getBasePath()
+	th.AssertNoErr(t, err)
+	fakeURL := strings.Join([]string{baseurl, "my_nova.yaml"}, "/")
+	urlparsed, err := url.Parse(fakeURL)
+	th.AssertNoErr(t, err)
+	myNovaContent := `heat_template_version: 2014-10-16
+parameters:
+  flavor:
+    type: string
+    description: Flavor for the server to be created
+    default: 4353
+    hidden: true
+resources:
+  test_server:
+    type: "OS::Nova::Server"
+    properties:
+      name: test-server
+      flavor: 2 GB General Purpose v1
+      image: Debian 7 (Wheezy) (PVHVM)
+      networks:
+      - {uuid: 11111111-1111-1111-1111-111111111111}`
+	th.Mux.HandleFunc(urlparsed.Path, func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		w.Header().Set("Content-Type", "application/jason")
+		w.WriteHeader(http.StatusOK)
+		fmt.Fprintf(w, myNovaContent)
+	})
+
+	client := fakeClient{BaseClient: getHTTPClient()}
+	te := new(Template)
+	te.Bin = []byte(`heat_template_version: 2015-04-30
+resources:
+  my_server:
+    type: my_nova.yaml`)
+	te.client = client
+
+	err = te.Parse()
+	th.AssertNoErr(t, err)
+	err = te.getFileContents(te.Parsed, ignoreIfTemplate, true)
+	th.AssertNoErr(t, err)
+	expectedFiles := map[string]string{
+		"my_nova.yaml": `heat_template_version: 2014-10-16
+parameters:
+  flavor:
+    type: string
+    description: Flavor for the server to be created
+    default: 4353
+    hidden: true
+resources:
+  test_server:
+    type: "OS::Nova::Server"
+    properties:
+      name: test-server
+      flavor: 2 GB General Purpose v1
+      image: Debian 7 (Wheezy) (PVHVM)
+      networks:
+      - {uuid: 11111111-1111-1111-1111-111111111111}`}
+	th.AssertEquals(t, expectedFiles["my_nova.yaml"], te.Files[fakeURL])
+	te.fixFileRefs()
+	expectedParsed := map[string]interface{}{
+		"heat_template_version": "2015-04-30",
+		"resources": map[string]interface{}{
+			"my_server": map[string]interface{}{
+				"type": fakeURL,
+			},
+		},
+	}
+	te.Parse()
+	th.AssertDeepEquals(t, expectedParsed, te.Parsed)
+}
diff --git a/openstack/orchestration/v1/stacks/utils.go b/openstack/orchestration/v1/stacks/utils.go
new file mode 100644
index 0000000..7b476a9
--- /dev/null
+++ b/openstack/orchestration/v1/stacks/utils.go
@@ -0,0 +1,161 @@
+package stacks
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"path/filepath"
+	"reflect"
+	"strings"
+
+	"github.com/rackspace/gophercloud"
+	"gopkg.in/yaml.v2"
+)
+
+// Client is an interface that expects a Get method similar to http.Get. This
+// is needed for unit testing, since we can mock an http client. Thus, the
+// client will usually be an http.Client EXCEPT in unit tests.
+type Client interface {
+	Get(string) (*http.Response, error)
+}
+
+// TE is a base structure for both Template and Environment
+type TE struct {
+	// Bin stores the contents of the template or environment.
+	Bin []byte
+	// URL stores the URL of the template. This is allowed to be a 'file://'
+	// for local files.
+	URL string
+	// Parsed contains a parsed version of Bin. Since there are 2 different
+	// fields referring to the same value, you must be careful when accessing
+	// this filed.
+	Parsed map[string]interface{}
+	// Files contains a mapping between the urls in templates to their contents.
+	Files map[string]string
+	// fileMaps is a map used internally when determining Files.
+	fileMaps map[string]string
+	// baseURL represents the location of the template or environment file.
+	baseURL string
+	// client is an interface which allows TE to fetch contents from URLS
+	client Client
+}
+
+// Fetch fetches the contents of a TE from its URL. Once a TE structure has a
+// URL, call the fetch method to fetch the contents.
+func (t *TE) Fetch() error {
+	// if the baseURL is not provided, use the current directors as the base URL
+	if t.baseURL == "" {
+		u, err := getBasePath()
+		if err != nil {
+			return err
+		}
+		t.baseURL = u
+	}
+
+	// if the contents are already present, do nothing.
+	if t.Bin != nil {
+		return nil
+	}
+
+	// get a fqdn from the URL using the baseURL of the TE. For local files,
+	// the URL's will have the `file` scheme.
+	u, err := gophercloud.NormalizePathURL(t.baseURL, t.URL)
+	if err != nil {
+		return err
+	}
+	t.URL = u
+
+	// get an HTTP client if none present
+	if t.client == nil {
+		t.client = getHTTPClient()
+	}
+
+	// use the client to fetch the contents of the TE
+	resp, err := t.client.Get(t.URL)
+	if err != nil {
+		return err
+	}
+	defer resp.Body.Close()
+	body, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return err
+	}
+	t.Bin = body
+	return nil
+}
+
+// get the basepath of the TE
+func getBasePath() (string, error) {
+	basePath, err := filepath.Abs(".")
+	if err != nil {
+		return "", err
+	}
+	u, err := gophercloud.NormalizePathURL("", basePath)
+	if err != nil {
+		return "", err
+	}
+	return u, nil
+}
+
+// get a an HTTP client to retrieve URL's. This client allows the use of `file`
+// scheme since we may need to fetch files from users filesystem
+func getHTTPClient() Client {
+	transport := &http.Transport{}
+	transport.RegisterProtocol("file", http.NewFileTransport(http.Dir("/")))
+	return &http.Client{Transport: transport}
+}
+
+// Parse will parse the contents and then validate. The contents MUST be either JSON or YAML.
+func (t *TE) Parse() error {
+	if err := t.Fetch(); err != nil {
+		return err
+	}
+	if jerr := json.Unmarshal(t.Bin, &t.Parsed); jerr != nil {
+		if yerr := yaml.Unmarshal(t.Bin, &t.Parsed); yerr != nil {
+			return fmt.Errorf("Data in neither json nor yaml format.")
+		}
+	}
+	return t.Validate()
+}
+
+// Validate validates the contents of TE
+func (t *TE) Validate() error {
+	return nil
+}
+
+// igfunc is a parameter used by GetFileContents and GetRRFileContents to check
+// for valid URL's.
+type igFunc func(string, interface{}) bool
+
+// convert map[interface{}]interface{} to map[string]interface{}
+func toStringKeys(m interface{}) (map[string]interface{}, error) {
+	switch m.(type) {
+	case map[string]interface{}, map[interface{}]interface{}:
+		typedMap := make(map[string]interface{})
+		if _, ok := m.(map[interface{}]interface{}); ok {
+			for k, v := range m.(map[interface{}]interface{}) {
+				typedMap[k.(string)] = v
+			}
+		} else {
+			typedMap = m.(map[string]interface{})
+		}
+		return typedMap, nil
+	default:
+		return nil, fmt.Errorf("Expected a map of type map[string]interface{} or map[interface{}]interface{}, actual type: %v", reflect.TypeOf(m))
+
+	}
+}
+
+// fix the reference to files by replacing relative URL's by absolute
+// URL's
+func (t *TE) fixFileRefs() {
+	tStr := string(t.Bin)
+	if t.fileMaps == nil {
+		return
+	}
+	for k, v := range t.fileMaps {
+		tStr = strings.Replace(tStr, k, v, -1)
+	}
+	t.Bin = []byte(tStr)
+}
diff --git a/openstack/orchestration/v1/stacks/utils_test.go b/openstack/orchestration/v1/stacks/utils_test.go
new file mode 100644
index 0000000..2536e03
--- /dev/null
+++ b/openstack/orchestration/v1/stacks/utils_test.go
@@ -0,0 +1,94 @@
+package stacks
+
+import (
+	"fmt"
+	"net/http"
+	"net/url"
+	"strings"
+	"testing"
+
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestTEFixFileRefs(t *testing.T) {
+	te := TE{
+		Bin: []byte(`string_to_replace: my fair lady`),
+		fileMaps: map[string]string{
+			"string_to_replace": "london bridge is falling down",
+		},
+	}
+	te.fixFileRefs()
+	th.AssertEquals(t, string(te.Bin), `london bridge is falling down: my fair lady`)
+}
+
+func TesttoStringKeys(t *testing.T) {
+	var test1 interface{} = map[interface{}]interface{}{
+		"Adam":  "Smith",
+		"Isaac": "Newton",
+	}
+	result1, err := toStringKeys(test1)
+	th.AssertNoErr(t, err)
+
+	expected := map[string]interface{}{
+		"Adam":  "Smith",
+		"Isaac": "Newton",
+	}
+	th.AssertDeepEquals(t, result1, expected)
+}
+
+func TestGetBasePath(t *testing.T) {
+	_, err := getBasePath()
+	th.AssertNoErr(t, err)
+}
+
+// test if HTTP client can read file type URLS. Read the URL of this file
+// because if this test is running, it means this file _must_ exist
+func TestGetHTTPClient(t *testing.T) {
+	client := getHTTPClient()
+	baseurl, err := getBasePath()
+	th.AssertNoErr(t, err)
+	resp, err := client.Get(baseurl)
+	th.AssertNoErr(t, err)
+	th.AssertEquals(t, resp.StatusCode, 200)
+}
+
+// Implement a fakeclient that can be used to mock out HTTP requests
+type fakeClient struct {
+	BaseClient Client
+}
+
+// this client's Get method first changes the URL given to point to
+// testhelper's (th) endpoints. This is done because the http Mux does not seem
+// to work for fqdns with the `file` scheme
+func (c fakeClient) Get(url string) (*http.Response, error) {
+	newurl := strings.Replace(url, "file://", th.Endpoint(), 1)
+	return c.BaseClient.Get(newurl)
+}
+
+// test the fetch function
+func TestFetch(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	baseurl, err := getBasePath()
+	th.AssertNoErr(t, err)
+	fakeURL := strings.Join([]string{baseurl, "file.yaml"}, "/")
+	urlparsed, err := url.Parse(fakeURL)
+	th.AssertNoErr(t, err)
+
+	th.Mux.HandleFunc(urlparsed.Path, func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		w.Header().Set("Content-Type", "application/jason")
+		w.WriteHeader(http.StatusOK)
+		fmt.Fprintf(w, "Fee-fi-fo-fum")
+	})
+
+	client := fakeClient{BaseClient: getHTTPClient()}
+	te := TE{
+		URL:    "file.yaml",
+		client: client,
+	}
+	err = te.Fetch()
+	th.AssertNoErr(t, err)
+	th.AssertEquals(t, fakeURL, te.URL)
+	th.AssertEquals(t, "Fee-fi-fo-fum", string(te.Bin))
+}
diff --git a/openstack/orchestration/v1/stacktemplates/fixtures.go b/openstack/orchestration/v1/stacktemplates/fixtures.go
index 71fa808..fa9b301 100644
--- a/openstack/orchestration/v1/stacktemplates/fixtures.go
+++ b/openstack/orchestration/v1/stacktemplates/fixtures.go
@@ -10,29 +10,7 @@
 )
 
 // GetExpected represents the expected object from a Get request.
-var GetExpected = &Template{
-	Description:         "Simple template to test heat commands",
-	HeatTemplateVersion: "2013-05-23",
-	Parameters: map[string]interface{}{
-		"flavor": map[string]interface{}{
-			"default": "m1.tiny",
-			"type":    "string",
-		},
-	},
-	Resources: map[string]interface{}{
-		"hello_world": map[string]interface{}{
-			"type": "OS::Nova::Server",
-			"properties": map[string]interface{}{
-				"key_name": "heat_key",
-				"flavor": map[string]interface{}{
-					"get_param": "flavor",
-				},
-				"image":     "ad091b52-742f-469e-8f3c-fd81cadf0743",
-				"user_data": "#!/bin/bash -xv\necho \"hello world\" &gt; /root/hello-world.txt\n",
-			},
-		},
-	},
-}
+var GetExpected = "{\n  \"description\": \"Simple template to test heat commands\",\n  \"heat_template_version\": \"2013-05-23\",\n  \"parameters\": {\n    \"flavor\": {\n      \"default\": \"m1.tiny\",\n      \"type\": \"string\"\n    }\n  },\n  \"resources\": {\n    \"hello_world\": {\n      \"properties\": {\n        \"flavor\": {\n          \"get_param\": \"flavor\"\n        },\n        \"image\": \"ad091b52-742f-469e-8f3c-fd81cadf0743\",\n        \"key_name\": \"heat_key\"\n      },\n      \"type\": \"OS::Nova::Server\"\n    }\n  }\n}"
 
 // GetOutput represents the response body from a Get request.
 const GetOutput = `
@@ -53,8 +31,7 @@
         "flavor": {
           "get_param": "flavor"
         },
-        "image": "ad091b52-742f-469e-8f3c-fd81cadf0743",
-        "user_data": "#!/bin/bash -xv\necho \"hello world\" &gt; /root/hello-world.txt\n"
+        "image": "ad091b52-742f-469e-8f3c-fd81cadf0743"
       }
     }
   }
diff --git a/openstack/orchestration/v1/stacktemplates/requests.go b/openstack/orchestration/v1/stacktemplates/requests.go
index ad1e468..c0cea35 100644
--- a/openstack/orchestration/v1/stacktemplates/requests.go
+++ b/openstack/orchestration/v1/stacktemplates/requests.go
@@ -23,14 +23,14 @@
 
 // ValidateOpts specifies the template validation parameters.
 type ValidateOpts struct {
-	Template    map[string]interface{}
+	Template    string
 	TemplateURL string
 }
 
 // ToStackTemplateValidateMap assembles a request body based on the contents of a ValidateOpts.
 func (opts ValidateOpts) ToStackTemplateValidateMap() (map[string]interface{}, error) {
 	vo := make(map[string]interface{})
-	if opts.Template != nil {
+	if opts.Template != "" {
 		vo["template"] = opts.Template
 		return vo, nil
 	}
diff --git a/openstack/orchestration/v1/stacktemplates/requests_test.go b/openstack/orchestration/v1/stacktemplates/requests_test.go
index d31c4ac..42667c9 100644
--- a/openstack/orchestration/v1/stacktemplates/requests_test.go
+++ b/openstack/orchestration/v1/stacktemplates/requests_test.go
@@ -16,7 +16,7 @@
 	th.AssertNoErr(t, err)
 
 	expected := GetExpected
-	th.AssertDeepEquals(t, expected, actual)
+	th.AssertDeepEquals(t, expected, string(actual))
 }
 
 func TestValidateTemplate(t *testing.T) {
@@ -25,29 +25,29 @@
 	HandleValidateSuccessfully(t, ValidateOutput)
 
 	opts := ValidateOpts{
-		Template: map[string]interface{}{
-			"heat_template_version": "2013-05-23",
-			"description":           "Simple template to test heat commands",
-			"parameters": map[string]interface{}{
-				"flavor": map[string]interface{}{
-					"default": "m1.tiny",
-					"type":    "string",
-				},
-			},
-			"resources": map[string]interface{}{
-				"hello_world": map[string]interface{}{
-					"type": "OS::Nova::Server",
-					"properties": map[string]interface{}{
-						"key_name": "heat_key",
-						"flavor": map[string]interface{}{
-							"get_param": "flavor",
-						},
-						"image":     "ad091b52-742f-469e-8f3c-fd81cadf0743",
-						"user_data": "#!/bin/bash -xv\necho \"hello world\" &gt; /root/hello-world.txt\n",
-					},
-				},
-			},
-		},
+		Template: `{
+		  "heat_template_version": "2013-05-23",
+		  "description": "Simple template to test heat commands",
+		  "parameters": {
+		    "flavor": {
+		      "default": "m1.tiny",
+		      "type": "string"
+		    }
+		  },
+		  "resources": {
+		    "hello_world": {
+		      "type": "OS::Nova::Server",
+		      "properties": {
+		        "key_name": "heat_key",
+		        "flavor": {
+		          "get_param": "flavor"
+		        },
+		        "image": "ad091b52-742f-469e-8f3c-fd81cadf0743",
+		        "user_data": "#!/bin/bash -xv\necho \"hello world\" &gt; /root/hello-world.txt\n"
+		      }
+		    }
+		  }
+		}`,
 	}
 	actual, err := Validate(fake.ServiceClient(), opts).Extract()
 	th.AssertNoErr(t, err)
diff --git a/openstack/orchestration/v1/stacktemplates/results.go b/openstack/orchestration/v1/stacktemplates/results.go
index ac2f24b..4e9ba5a 100644
--- a/openstack/orchestration/v1/stacktemplates/results.go
+++ b/openstack/orchestration/v1/stacktemplates/results.go
@@ -1,42 +1,33 @@
 package stacktemplates
 
 import (
+	"encoding/json"
 	"github.com/mitchellh/mapstructure"
 	"github.com/rackspace/gophercloud"
 )
 
-// Template represents a stack template.
-type Template struct {
-	Description         string                 `mapstructure:"description"`
-	HeatTemplateVersion string                 `mapstructure:"heat_template_version"`
-	Parameters          map[string]interface{} `mapstructure:"parameters"`
-	Resources           map[string]interface{} `mapstructure:"resources"`
-}
-
 // GetResult represents the result of a Get operation.
 type GetResult struct {
 	gophercloud.Result
 }
 
-// Extract returns a pointer to a Template object and is called after a
-// Get operation.
-func (r GetResult) Extract() (*Template, error) {
+// Extract returns the JSON template and is called after a Get operation.
+func (r GetResult) Extract() ([]byte, error) {
 	if r.Err != nil {
 		return nil, r.Err
 	}
-
-	var res Template
-	if err := mapstructure.Decode(r.Body, &res); err != nil {
+	template, err := json.MarshalIndent(r.Body, "", "  ")
+	if err != nil {
 		return nil, err
 	}
-
-	return &res, nil
+	return template, nil
 }
 
 // ValidatedTemplate represents the parsed object returned from a Validate request.
 type ValidatedTemplate struct {
-	Description string
-	Parameters  map[string]interface{}
+	Description     string                 `mapstructure:"Description"`
+	Parameters      map[string]interface{} `mapstructure:"Parameters"`
+	ParameterGroups map[string]interface{} `mapstructure:"ParameterGroups"`
 }
 
 // ValidateResult represents the result of a Validate operation.
diff --git a/rackspace/orchestration/v1/stackresources/delegate_test.go b/rackspace/orchestration/v1/stackresources/delegate_test.go
index 18e9614..116e44c 100644
--- a/rackspace/orchestration/v1/stackresources/delegate_test.go
+++ b/rackspace/orchestration/v1/stackresources/delegate_test.go
@@ -104,5 +104,5 @@
 	th.AssertNoErr(t, err)
 
 	expected := os.GetTemplateExpected
-	th.AssertDeepEquals(t, expected, actual)
+	th.AssertDeepEquals(t, expected, string(actual))
 }
diff --git a/rackspace/orchestration/v1/stacks/delegate_test.go b/rackspace/orchestration/v1/stacks/delegate_test.go
index a1fb393..553ae94 100644
--- a/rackspace/orchestration/v1/stacks/delegate_test.go
+++ b/rackspace/orchestration/v1/stacks/delegate_test.go
@@ -172,6 +172,170 @@
 	th.AssertDeepEquals(t, expected, actual)
 }
 
+func TestCreateStackNewTemplateFormat(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleCreateSuccessfully(t, CreateOutput)
+
+	createOpts := os.CreateOpts{
+		Name:            "stackcreated",
+		Timeout:         60,
+		TemplateOpts:    new(os.Template),
+		DisableRollback: os.Disable,
+	}
+	createOpts.TemplateOpts.Bin = []byte(`{
+		    "outputs": {
+		        "db_host": {
+		            "value": {
+		                "get_attr": [
+		                    "db",
+		                    "hostname"
+		                ]
+		            }
+		        }
+		    },
+		    "heat_template_version": "2014-10-16",
+		    "description": "HEAT template for creating a Cloud Database.\n",
+		    "parameters": {
+		        "db_name": {
+		            "default": "wordpress",
+		            "type": "string",
+		            "description": "the name for the database",
+		            "constraints": [
+		                {
+		                    "length": {
+		                        "max": 64,
+		                        "min": 1
+		                    },
+		                    "description": "must be between 1 and 64 characters"
+		                },
+		                {
+		                    "allowed_pattern": "[a-zA-Z][a-zA-Z0-9]*",
+		                    "description": "must begin with a letter and contain only alphanumeric characters."
+		                }
+		            ]
+		        },
+		        "db_instance_name": {
+		            "default": "Cloud_DB",
+		            "type": "string",
+		            "description": "the database instance name"
+		        },
+		        "db_username": {
+		            "default": "admin",
+		            "hidden": true,
+		            "type": "string",
+		            "description": "database admin account username",
+		            "constraints": [
+		                {
+		                    "length": {
+		                        "max": 16,
+		                        "min": 1
+		                    },
+		                    "description": "must be between 1 and 16 characters"
+		                },
+		                {
+		                    "allowed_pattern": "[a-zA-Z][a-zA-Z0-9]*",
+		                    "description": "must begin with a letter and contain only alphanumeric characters."
+		                }
+		            ]
+		        },
+		        "db_volume_size": {
+		            "default": 30,
+		            "type": "number",
+		            "description": "database volume size (in GB)",
+		            "constraints": [
+		                {
+		                    "range": {
+		                        "max": 1024,
+		                        "min": 1
+		                    },
+		                    "description": "must be between 1 and 1024 GB"
+		                }
+		            ]
+		        },
+		        "db_flavor": {
+		            "default": "1GB Instance",
+		            "type": "string",
+		            "description": "database instance size",
+		            "constraints": [
+		                {
+		                    "description": "must be a valid cloud database flavor",
+		                    "allowed_values": [
+		                        "1GB Instance",
+		                        "2GB Instance",
+		                        "4GB Instance",
+		                        "8GB Instance",
+		                        "16GB Instance"
+		                    ]
+		                }
+		            ]
+		        },
+		        "db_password": {
+		            "default": "admin",
+		            "hidden": true,
+		            "type": "string",
+		            "description": "database admin account password",
+		            "constraints": [
+		                {
+		                    "length": {
+		                        "max": 41,
+		                        "min": 1
+		                    },
+		                    "description": "must be between 1 and 14 characters"
+		                },
+		                {
+		                    "allowed_pattern": "[a-zA-Z0-9]*",
+		                    "description": "must contain only alphanumeric characters."
+		                }
+		            ]
+		        }
+		    },
+		    "resources": {
+		        "db": {
+		            "type": "OS::Trove::Instance",
+		            "properties": {
+		                "flavor": {
+		                    "get_param": "db_flavor"
+		                },
+		                "size": {
+		                    "get_param": "db_volume_size"
+		                },
+		                "users": [
+		                    {
+		                        "password": {
+		                            "get_param": "db_password"
+		                        },
+		                        "name": {
+		                            "get_param": "db_username"
+		                        },
+		                        "databases": [
+		                            {
+		                                "get_param": "db_name"
+		                            }
+		                        ]
+		                    }
+		                ],
+		                "name": {
+		                    "get_param": "db_instance_name"
+		                },
+		                "databases": [
+		                    {
+		                        "name": {
+		                            "get_param": "db_name"
+		                        }
+		                    }
+		                ]
+		            }
+		        }
+		    }
+		}`)
+	actual, err := Create(fake.ServiceClient(), createOpts).Extract()
+	th.AssertNoErr(t, err)
+
+	expected := CreateExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
+
 func TestAdoptStack(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
@@ -336,6 +500,172 @@
 	th.AssertDeepEquals(t, expected, actual)
 }
 
+func TestAdoptStackNewTemplateFormat(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleCreateSuccessfully(t, CreateOutput)
+	template := new(os.Template)
+	template.Bin = []byte(`{
+  "outputs": {
+	"db_host": {
+	  "value": {
+		"get_attr": [
+		"db",
+		"hostname"
+		]
+	  }
+	}
+  },
+  "heat_template_version": "2014-10-16",
+  "description": "HEAT template for creating a Cloud Database.\n",
+  "parameters": {
+	"db_name": {
+	  "default": "wordpress",
+	  "type": "string",
+	  "description": "the name for the database",
+	  "constraints": [
+	  {
+		"length": {
+		  "max": 64,
+		  "min": 1
+		},
+		"description": "must be between 1 and 64 characters"
+	  },
+	  {
+		"allowed_pattern": "[a-zA-Z][a-zA-Z0-9]*",
+		"description": "must begin with a letter and contain only alphanumeric characters."
+	  }
+	  ]
+	},
+	"db_instance_name": {
+	  "default": "Cloud_DB",
+	  "type": "string",
+	  "description": "the database instance name"
+	},
+	"db_username": {
+	  "default": "admin",
+	  "hidden": true,
+	  "type": "string",
+	  "description": "database admin account username",
+	  "constraints": [
+	  {
+		"length": {
+		  "max": 16,
+		  "min": 1
+		},
+		"description": "must be between 1 and 16 characters"
+	  },
+	  {
+		"allowed_pattern": "[a-zA-Z][a-zA-Z0-9]*",
+		"description": "must begin with a letter and contain only alphanumeric characters."
+	  }
+	  ]
+	},
+	"db_volume_size": {
+	  "default": 30,
+	  "type": "number",
+	  "description": "database volume size (in GB)",
+	  "constraints": [
+	  {
+		"range": {
+		  "max": 1024,
+		  "min": 1
+		},
+		"description": "must be between 1 and 1024 GB"
+	  }
+	  ]
+	},
+	"db_flavor": {
+	  "default": "1GB Instance",
+	  "type": "string",
+	  "description": "database instance size",
+	  "constraints": [
+	  {
+		"description": "must be a valid cloud database flavor",
+		"allowed_values": [
+		"1GB Instance",
+		"2GB Instance",
+		"4GB Instance",
+		"8GB Instance",
+		"16GB Instance"
+		]
+	  }
+	  ]
+	},
+	"db_password": {
+	  "default": "admin",
+	  "hidden": true,
+	  "type": "string",
+	  "description": "database admin account password",
+	  "constraints": [
+	  {
+		"length": {
+		  "max": 41,
+		  "min": 1
+		},
+		"description": "must be between 1 and 14 characters"
+	  },
+	  {
+		"allowed_pattern": "[a-zA-Z0-9]*",
+		"description": "must contain only alphanumeric characters."
+	  }
+	  ]
+	}
+  },
+  "resources": {
+	"db": {
+	  "type": "OS::Trove::Instance",
+	  "properties": {
+		"flavor": {
+		  "get_param": "db_flavor"
+		},
+		"size": {
+		  "get_param": "db_volume_size"
+		},
+		"users": [
+		{
+		  "password": {
+			"get_param": "db_password"
+		  },
+		  "name": {
+			"get_param": "db_username"
+		  },
+		  "databases": [
+		  {
+			"get_param": "db_name"
+		  }
+		  ]
+		}
+		],
+		"name": {
+		  "get_param": "db_instance_name"
+		},
+		"databases": [
+		{
+		  "name": {
+			"get_param": "db_name"
+		  }
+		}
+		]
+	  }
+	}
+  }
+}`)
+
+	adoptOpts := os.AdoptOpts{
+		AdoptStackData:  `{\"environment\":{\"parameters\":{}},    \"status\":\"COMPLETE\",\"name\": \"trovestack\",\n  \"template\": {\n    \"outputs\": {\n      \"db_host\": {\n        \"value\": {\n          \"get_attr\": [\n            \"db\",\n            \"hostname\"\n          ]\n        }\n      }\n    },\n    \"heat_template_version\": \"2014-10-16\",\n    \"description\": \"HEAT template for creating a Cloud Database.\\n\",\n    \"parameters\": {\n      \"db_instance_name\": {\n        \"default\": \"Cloud_DB\",\n        \"type\": \"string\",\n        \"description\": \"the database instance name\"\n      },\n      \"db_flavor\": {\n        \"default\": \"1GB Instance\",\n        \"type\": \"string\",\n        \"description\": \"database instance size\",\n        \"constraints\": [\n          {\n            \"description\": \"must be a valid cloud database flavor\",\n            \"allowed_values\": [\n              \"1GB Instance\",\n              \"2GB Instance\",\n              \"4GB Instance\",\n              \"8GB Instance\",\n              \"16GB Instance\"\n            ]\n          }\n        ]\n      },\n      \"db_password\": {\n        \"default\": \"admin\",\n        \"hidden\": true,\n        \"type\": \"string\",\n        \"description\": \"database admin account password\",\n        \"constraints\": [\n          {\n            \"length\": {\n              \"max\": 41,\n              \"min\": 1\n            },\n            \"description\": \"must be between 1 and 14 characters\"\n          },\n          {\n            \"allowed_pattern\": \"[a-zA-Z0-9]*\",\n            \"description\": \"must contain only alphanumeric characters.\"\n          }\n        ]\n      },\n      \"db_name\": {\n        \"default\": \"wordpress\",\n        \"type\": \"string\",\n        \"description\": \"the name for the database\",\n        \"constraints\": [\n          {\n            \"length\": {\n              \"max\": 64,\n              \"min\": 1\n            },\n            \"description\": \"must be between 1 and 64 characters\"\n          },\n          {\n            \"allowed_pattern\": \"[a-zA-Z][a-zA-Z0-9]*\",\n            \"description\": \"must begin with a letter and contain only alphanumeric characters.\"\n          }\n        ]\n      },\n      \"db_username\": {\n        \"default\": \"admin\",\n        \"hidden\": true,\n        \"type\": \"string\",\n        \"description\": \"database admin account username\",\n        \"constraints\": [\n          {\n            \"length\": {\n              \"max\": 16,\n              \"min\": 1\n            },\n            \"description\": \"must be between 1 and 16 characters\"\n          },\n          {\n            \"allowed_pattern\": \"[a-zA-Z][a-zA-Z0-9]*\",\n            \"description\": \"must begin with a letter and contain only alphanumeric characters.\"\n          }\n        ]\n      },\n      \"db_volume_size\": {\n        \"default\": 30,\n        \"type\": \"number\",\n        \"description\": \"database volume size (in GB)\",\n        \"constraints\": [\n          {\n            \"range\": {\n              \"max\": 1024,\n              \"min\": 1\n            },\n            \"description\": \"must be between 1 and 1024 GB\"\n          }\n        ]\n      }\n    },\n    \"resources\": {\n      \"db\": {\n        \"type\": \"OS::Trove::Instance\",\n        \"properties\": {\n          \"flavor\": {\n            \"get_param\": \"db_flavor\"\n          },\n          \"databases\": [\n            {\n              \"name\": {\n                \"get_param\": \"db_name\"\n              }\n            }\n          ],\n          \"users\": [\n            {\n              \"password\": {\n                \"get_param\": \"db_password\"\n              },\n              \"name\": {\n                \"get_param\": \"db_username\"\n              },\n              \"databases\": [\n                {\n                  \"get_param\": \"db_name\"\n                }\n              ]\n            }\n          ],\n          \"name\": {\n            \"get_param\": \"db_instance_name\"\n          },\n          \"size\": {\n            \"get_param\": \"db_volume_size\"\n          }\n        }\n      }\n    }\n  },\n  \"action\": \"CREATE\",\n  \"id\": \"exxxxd-7xx5-4xxb-bxx2-cxxxxxx5\",\n  \"resources\": {\n    \"db\": {\n      \"status\": \"COMPLETE\",\n      \"name\": \"db\",\n      \"resource_data\": {},\n      \"resource_id\": \"exxxx2-9xx0-4xxxb-bxx2-dxxxxxx4\",\n      \"action\": \"CREATE\",\n      \"type\": \"OS::Trove::Instance\",\n      \"metadata\": {}\n    }\n  }\n},`,
+		Name:            "stackadopted",
+		Timeout:         60,
+		TemplateOpts:    template,
+		DisableRollback: os.Disable,
+	}
+	actual, err := Adopt(fake.ServiceClient(), adoptOpts).Extract()
+	th.AssertNoErr(t, err)
+
+	expected := CreateExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
+
 func TestListStack(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
@@ -390,6 +720,45 @@
 	th.AssertNoErr(t, err)
 }
 
+func TestUpdateStackNewTemplateFormat(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleUpdateSuccessfully(t)
+
+	updateOpts := os.UpdateOpts{
+		TemplateOpts: new(os.Template),
+	}
+	updateOpts.TemplateOpts.Bin = []byte(`
+		{
+			"stack_name": "postman_stack",
+			"template": {
+				"heat_template_version": "2013-05-23",
+				"description": "Simple template to test heat commands",
+				"parameters": {
+					"flavor": {
+						"default": "m1.tiny",
+						"type": "string"
+					}
+				},
+				"resources": {
+					"hello_world": {
+						"type": "OS::Nova::Server",
+						"properties": {
+							"key_name": "heat_key",
+							"flavor": {
+								"get_param": "flavor"
+							},
+							"image": "ad091b52-742f-469e-8f3c-fd81cadf0743",
+							"user_data": "#!/bin/bash -xv\necho \"hello world\" &gt; /root/hello-world.txt\n"
+						}
+					}
+				}
+			}
+		}`)
+	err := Update(fake.ServiceClient(), "gophercloud-test-stack-2", "db6977b2-27aa-4775-9ae7-6213212d4ada", updateOpts).ExtractErr()
+	th.AssertNoErr(t, err)
+}
+
 func TestDeleteStack(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
@@ -443,19 +812,59 @@
 	th.AssertDeepEquals(t, expected, actual)
 }
 
-/*
+func TestPreviewStackNewTemplateFormat(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandlePreviewSuccessfully(t, os.GetOutput)
+
+	previewOpts := os.PreviewOpts{
+		Name:            "stackcreated",
+		Timeout:         60,
+		TemplateOpts:    new(os.Template),
+		DisableRollback: os.Disable,
+	}
+	previewOpts.TemplateOpts.Bin = []byte(`
+		{
+		    "stack_name": "postman_stack",
+		    "template": {
+		        "heat_template_version": "2013-05-23",
+		        "description": "Simple template to test heat commands",
+		        "parameters": {
+		            "flavor": {
+		                "default": "m1.tiny",
+		                "type": "string"
+		            }
+		        },
+		        "resources": {
+		            "hello_world": {
+		                "type": "OS::Nova::Server",
+		                "properties": {
+		                    "key_name": "heat_key",
+		                    "flavor": {
+		                        "get_param": "flavor"
+		                    },
+		                    "image": "ad091b52-742f-469e-8f3c-fd81cadf0743",
+		                    "user_data": "#!/bin/bash -xv\necho \"hello world\" &gt; /root/hello-world.txt\n"
+		                }
+		            }
+		        }
+		    }
+		}`)
+	actual, err := Preview(fake.ServiceClient(), previewOpts).Extract()
+	th.AssertNoErr(t, err)
+
+	expected := os.PreviewExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
+
 func TestAbandonStack(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
-	os.HandleAbandonSuccessfully(t)
+	os.HandleAbandonSuccessfully(t, os.AbandonOutput)
 
-	//actual, err := Abandon(fake.ServiceClient(), "postman_stack", "16ef0584-4458-41eb-87c8-0dc8d5f66c87").Extract()
-	//th.AssertNoErr(t, err)
-	res := Abandon(fake.ServiceClient(), "postman_stack", "16ef0584-4458-41eb-87c8-0dc8d5f66c87") //.Extract()
-	th.AssertNoErr(t, res.Err)
-	t.Logf("actual: %+v", res)
+	actual, err := Abandon(fake.ServiceClient(), "postman_stack", "16ef0584-4458-41eb-87c8-0dc8d5f66c8").Extract()
+	th.AssertNoErr(t, err)
 
-	//expected := os.AbandonExpected
-	//th.AssertDeepEquals(t, expected, actual)
+	expected := os.AbandonExpected
+	th.AssertDeepEquals(t, expected, actual)
 }
-*/
diff --git a/rackspace/orchestration/v1/stacktemplates/delegate_test.go b/rackspace/orchestration/v1/stacktemplates/delegate_test.go
index d4006c4..d4d0f8f 100644
--- a/rackspace/orchestration/v1/stacktemplates/delegate_test.go
+++ b/rackspace/orchestration/v1/stacktemplates/delegate_test.go
@@ -17,7 +17,7 @@
 	th.AssertNoErr(t, err)
 
 	expected := os.GetExpected
-	th.AssertDeepEquals(t, expected, actual)
+	th.AssertDeepEquals(t, expected, string(actual))
 }
 
 func TestValidateTemplate(t *testing.T) {
@@ -26,29 +26,18 @@
 	os.HandleValidateSuccessfully(t, os.ValidateOutput)
 
 	opts := os.ValidateOpts{
-		Template: map[string]interface{}{
-			"heat_template_version": "2013-05-23",
-			"description":           "Simple template to test heat commands",
-			"parameters": map[string]interface{}{
-				"flavor": map[string]interface{}{
-					"default": "m1.tiny",
-					"type":    "string",
-				},
-			},
-			"resources": map[string]interface{}{
-				"hello_world": map[string]interface{}{
-					"type": "OS::Nova::Server",
-					"properties": map[string]interface{}{
-						"key_name": "heat_key",
-						"flavor": map[string]interface{}{
-							"get_param": "flavor",
-						},
-						"image":     "ad091b52-742f-469e-8f3c-fd81cadf0743",
-						"user_data": "#!/bin/bash -xv\necho \"hello world\" &gt; /root/hello-world.txt\n",
-					},
-				},
-			},
-		},
+		Template: `{
+			"Description": "Simple template to test heat commands",
+			"Parameters": {
+				"flavor": {
+					"Default": "m1.tiny",
+					"Type": "String",
+					"NoEcho": "false",
+					"Description": "",
+					"Label": "flavor"
+				}
+			}
+		}`,
 	}
 	actual, err := Validate(fake.ServiceClient(), opts).Extract()
 	th.AssertNoErr(t, err)
diff --git a/util.go b/util.go
index fbd9fe9..3d6a4e4 100644
--- a/util.go
+++ b/util.go
@@ -2,6 +2,8 @@
 
 import (
 	"errors"
+	"net/url"
+	"path/filepath"
 	"strings"
 	"time"
 )
@@ -42,3 +44,39 @@
 	}
 	return url
 }
+
+// NormalizePathURL is used to convert rawPath to a fqdn, using basePath as
+// a reference in the filesystem, if necessary. basePath is assumed to contain
+// either '.' when first used, or the file:// type fqdn of the parent resource.
+// e.g. myFavScript.yaml => file://opt/lib/myFavScript.yaml
+func NormalizePathURL(basePath, rawPath string) (string, error) {
+	u, err := url.Parse(rawPath)
+	if err != nil {
+		return "", err
+	}
+	// if a scheme is defined, it must be a fqdn already
+	if u.Scheme != "" {
+		return u.String(), nil
+	}
+	// if basePath is a url, then child resources are assumed to be relative to it
+	bu, err := url.Parse(basePath)
+	if err != nil {
+		return "", err
+	}
+	var basePathSys, absPathSys string
+	if bu.Scheme != "" {
+		basePathSys = filepath.FromSlash(bu.Path)
+		absPathSys = filepath.Join(basePathSys, rawPath)
+		bu.Path = filepath.ToSlash(absPathSys)
+		return bu.String(), nil
+	}
+
+	absPathSys = filepath.Join(basePath, rawPath)
+	u.Path = filepath.ToSlash(absPathSys)
+	if err != nil {
+		return "", err
+	}
+	u.Scheme = "file"
+	return u.String(), nil
+
+}
diff --git a/util_test.go b/util_test.go
index 5a15a00..dcec77f 100644
--- a/util_test.go
+++ b/util_test.go
@@ -1,6 +1,9 @@
 package gophercloud
 
 import (
+	"os"
+	"path/filepath"
+	"strings"
 	"testing"
 
 	th "github.com/rackspace/gophercloud/testhelper"
@@ -12,3 +15,71 @@
 	})
 	th.CheckNoErr(t, err)
 }
+
+func TestNormalizeURL(t *testing.T) {
+	urls := []string{
+		"NoSlashAtEnd",
+		"SlashAtEnd/",
+	}
+	expected := []string{
+		"NoSlashAtEnd/",
+		"SlashAtEnd/",
+	}
+	for i := 0; i < len(expected); i++ {
+		th.CheckEquals(t, expected[i], NormalizeURL(urls[i]))
+	}
+
+}
+
+func TestNormalizePathURL(t *testing.T) {
+	baseDir, _ := os.Getwd()
+
+	rawPath := "template.yaml"
+	basePath, _ := filepath.Abs(".")
+	result, _ := NormalizePathURL(basePath, rawPath)
+	expected := strings.Join([]string{"file:/", filepath.ToSlash(baseDir), "template.yaml"}, "/")
+	th.CheckEquals(t, expected, result)
+
+	rawPath = "http://www.google.com"
+	basePath, _ = filepath.Abs(".")
+	result, _ = NormalizePathURL(basePath, rawPath)
+	expected = "http://www.google.com"
+	th.CheckEquals(t, expected, result)
+
+	rawPath = "very/nested/file.yaml"
+	basePath, _ = filepath.Abs(".")
+	result, _ = NormalizePathURL(basePath, rawPath)
+	expected = strings.Join([]string{"file:/", filepath.ToSlash(baseDir), "very/nested/file.yaml"}, "/")
+	th.CheckEquals(t, expected, result)
+
+	rawPath = "very/nested/file.yaml"
+	basePath = "http://www.google.com"
+	result, _ = NormalizePathURL(basePath, rawPath)
+	expected = "http://www.google.com/very/nested/file.yaml"
+	th.CheckEquals(t, expected, result)
+
+	rawPath = "very/nested/file.yaml/"
+	basePath = "http://www.google.com/"
+	result, _ = NormalizePathURL(basePath, rawPath)
+	expected = "http://www.google.com/very/nested/file.yaml"
+	th.CheckEquals(t, expected, result)
+
+	rawPath = "very/nested/file.yaml"
+	basePath = "http://www.google.com/even/more"
+	result, _ = NormalizePathURL(basePath, rawPath)
+	expected = "http://www.google.com/even/more/very/nested/file.yaml"
+	th.CheckEquals(t, expected, result)
+
+	rawPath = "very/nested/file.yaml"
+	basePath = strings.Join([]string{"file:/", filepath.ToSlash(baseDir), "only/file/even/more"}, "/")
+	result, _ = NormalizePathURL(basePath, rawPath)
+	expected = strings.Join([]string{"file:/", filepath.ToSlash(baseDir), "only/file/even/more/very/nested/file.yaml"}, "/")
+	th.CheckEquals(t, expected, result)
+
+	rawPath = "very/nested/file.yaml/"
+	basePath = strings.Join([]string{"file:/", filepath.ToSlash(baseDir), "only/file/even/more"}, "/")
+	result, _ = NormalizePathURL(basePath, rawPath)
+	expected = strings.Join([]string{"file:/", filepath.ToSlash(baseDir), "only/file/even/more/very/nested/file.yaml"}, "/")
+	th.CheckEquals(t, expected, result)
+
+}