openstack stack templates ops and unit tests
diff --git a/openstack/orchestration/v1/stacktemplates/fixtures.go b/openstack/orchestration/v1/stacktemplates/fixtures.go
new file mode 100644
index 0000000..dc354ec
--- /dev/null
+++ b/openstack/orchestration/v1/stacktemplates/fixtures.go
@@ -0,0 +1,116 @@
+package stacktemplates
+
+import (
+	"fmt"
+	"net/http"
+	"testing"
+
+	th "github.com/rackspace/gophercloud/testhelper"
+	fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+// 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\" > /root/hello-world.txt\n",
+			},
+		},
+	},
+}
+
+// GetOutput represents the response body from a Get request.
+const GetOutput = `
+{
+  "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\" > /root/hello-world.txt\n"
+      }
+    }
+  }
+}`
+
+// HandleGetSuccessfully creates an HTTP handler at `/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87/template`
+// on the test handler mux that responds with a `Get` response.
+func HandleGetSuccessfully(t *testing.T, output string) {
+	th.Mux.HandleFunc("/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87/template", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		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, output)
+	})
+}
+
+var ValidateExpected = &ValidatedTemplate{
+	Description: "Simple template to test heat commands",
+	Parameters: map[string]interface{}{
+		"flavor": map[string]interface{}{
+			"Default":     "m1.tiny",
+			"Type":        "String",
+			"NoEcho":      "false",
+			"Description": "",
+			"Label":       "flavor",
+		},
+	},
+}
+
+const ValidateOutput = `
+{
+	"Description": "Simple template to test heat commands",
+	"Parameters": {
+		"flavor": {
+			"Default": "m1.tiny",
+			"Type": "String",
+			"NoEcho": "false",
+			"Description": "",
+			"Label": "flavor"
+		}
+	}
+}`
+
+// HandleValidateSuccessfully creates an HTTP handler at `/validate`
+// on the test handler mux that responds with a `Validate` response.
+func HandleValidateSuccessfully(t *testing.T, output string) {
+	th.Mux.HandleFunc("/validate", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "POST")
+		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, output)
+	})
+}
diff --git a/openstack/orchestration/v1/stacktemplates/requests.go b/openstack/orchestration/v1/stacktemplates/requests.go
new file mode 100644
index 0000000..5f8aba9
--- /dev/null
+++ b/openstack/orchestration/v1/stacktemplates/requests.go
@@ -0,0 +1,64 @@
+package stacktemplates
+
+import (
+	"fmt"
+
+	"github.com/racker/perigee"
+	"github.com/rackspace/gophercloud"
+)
+
+// Get retreives data for the given stack template.
+func Get(c *gophercloud.ServiceClient, stackName, stackID string) GetResult {
+	var res GetResult
+	_, res.Err = perigee.Request("GET", getURL(c, stackName, stackID), perigee.Options{
+		MoreHeaders: c.AuthenticatedHeaders(),
+		Results:     &res.Body,
+		OkCodes:     []int{200},
+	})
+	return res
+}
+
+// ValidateOptsBuilder describes struct types that can be accepted by the Validate call.
+// The ValidateOpts struct in this package does.
+type ValidateOptsBuilder interface {
+	ToStackTemplateValidateMap() (map[string]interface{}, error)
+}
+
+// ValidateOpts specifies the template validation parameters.
+type ValidateOpts struct {
+	Template    map[string]interface{}
+	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 {
+		vo["template"] = opts.Template
+		return vo, nil
+	}
+	if opts.TemplateURL != "" {
+		vo["template_url"] = opts.TemplateURL
+		return vo, nil
+	}
+	return vo, fmt.Errorf("One of Template or TemplateURL is required.")
+}
+
+// Validate validates the given stack template.
+func Validate(c *gophercloud.ServiceClient, opts ValidateOptsBuilder) ValidateResult {
+	var res ValidateResult
+
+	reqBody, err := opts.ToStackTemplateValidateMap()
+	if err != nil {
+		res.Err = err
+		return res
+	}
+
+	_, res.Err = perigee.Request("POST", validateURL(c), perigee.Options{
+		MoreHeaders: c.AuthenticatedHeaders(),
+		ReqBody:     reqBody,
+		Results:     &res.Body,
+		OkCodes:     []int{200},
+	})
+	return res
+}
diff --git a/openstack/orchestration/v1/stacktemplates/requests_test.go b/openstack/orchestration/v1/stacktemplates/requests_test.go
new file mode 100644
index 0000000..d31c4ac
--- /dev/null
+++ b/openstack/orchestration/v1/stacktemplates/requests_test.go
@@ -0,0 +1,57 @@
+package stacktemplates
+
+import (
+	"testing"
+
+	th "github.com/rackspace/gophercloud/testhelper"
+	fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+func TestGetTemplate(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleGetSuccessfully(t, GetOutput)
+
+	actual, err := Get(fake.ServiceClient(), "postman_stack", "16ef0584-4458-41eb-87c8-0dc8d5f66c87").Extract()
+	th.AssertNoErr(t, err)
+
+	expected := GetExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
+
+func TestValidateTemplate(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	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\" > /root/hello-world.txt\n",
+					},
+				},
+			},
+		},
+	}
+	actual, err := Validate(fake.ServiceClient(), opts).Extract()
+	th.AssertNoErr(t, err)
+
+	expected := ValidateExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
diff --git a/openstack/orchestration/v1/stacktemplates/results.go b/openstack/orchestration/v1/stacktemplates/results.go
new file mode 100644
index 0000000..968a3ec
--- /dev/null
+++ b/openstack/orchestration/v1/stacktemplates/results.go
@@ -0,0 +1,52 @@
+package stacktemplates
+
+import (
+	"github.com/mitchellh/mapstructure"
+	"github.com/rackspace/gophercloud"
+)
+
+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"`
+}
+
+type GetResult struct {
+	gophercloud.Result
+}
+
+func (r GetResult) Extract() (*Template, error) {
+	if r.Err != nil {
+		return nil, r.Err
+	}
+
+	var res Template
+	if err := mapstructure.Decode(r.Body, &res); err != nil {
+		return nil, err
+	}
+
+	return &res, nil
+}
+
+type ValidatedTemplate struct {
+	Description string
+	Parameters  map[string]interface{}
+}
+
+type ValidateResult struct {
+	gophercloud.Result
+}
+
+func (r ValidateResult) Extract() (*ValidatedTemplate, error) {
+	if r.Err != nil {
+		return nil, r.Err
+	}
+
+	var res ValidatedTemplate
+	if err := mapstructure.Decode(r.Body, &res); err != nil {
+		return nil, err
+	}
+
+	return &res, nil
+}
diff --git a/openstack/orchestration/v1/stacktemplates/urls.go b/openstack/orchestration/v1/stacktemplates/urls.go
new file mode 100644
index 0000000..c30b7ca
--- /dev/null
+++ b/openstack/orchestration/v1/stacktemplates/urls.go
@@ -0,0 +1,11 @@
+package stacktemplates
+
+import "github.com/rackspace/gophercloud"
+
+func getURL(c *gophercloud.ServiceClient, stackName, stackID string) string {
+	return c.ServiceURL("stacks", stackName, stackID, "template")
+}
+
+func validateURL(c *gophercloud.ServiceClient) string {
+	return c.ServiceURL("validate")
+}