Fix api interfaces for orchestration resources
Some of the interfaces don't correspond well to the values
expected by the requests and returned by api.
diff --git a/openstack/orchestration/v1/stackresources/fixtures.go b/openstack/orchestration/v1/stackresources/fixtures.go
index c3c3d3f..2273ba7 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..51c3c0c 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/fixtures.go b/openstack/orchestration/v1/stacks/fixtures.go
index 3a621da..f884d51 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,35 @@
"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)
})
}
diff --git a/openstack/orchestration/v1/stacks/requests.go b/openstack/orchestration/v1/stacks/requests.go
index 0dd6af2..bd8e59e 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"
@@ -60,6 +61,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.
@@ -97,6 +100,9 @@
s["timeout_mins"] = opts.Timeout
}
+ if opts.Tags != nil {
+ s["tags"] = strings.Join(opts.Tags, ",")
+ }
return s, nil
}
@@ -197,12 +203,12 @@
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
@@ -329,6 +335,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
}
// ToStackUpdateMap casts a CreateOpts struct to a map.
@@ -359,6 +367,10 @@
s["timeout_mins"] = opts.Timeout
}
+ if opts.Tags != nil {
+ s["tags"] = strings.Join(opts.Tags, ",")
+ }
+
return s, nil
}
diff --git a/openstack/orchestration/v1/stacks/requests_test.go b/openstack/orchestration/v1/stacks/requests_test.go
index 1e32ca2..1606d98 100644
--- a/openstack/orchestration/v1/stacks/requests_test.go
+++ b/openstack/orchestration/v1/stacks/requests_test.go
@@ -215,3 +215,15 @@
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/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\" > /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\" > /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\" > /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\" > /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.