summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/goamz/goamz/cloudformation/cloudformation.go
blob: 00081d0c2e7738053b1ef2a0d8c085249984e1e4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
//
// cloudformation: This package provides types and functions to interact with the AWS CloudFormation API
//
// Depends on https://github.com/goamz/goamz
//

package cloudformation

import (
	"encoding/xml"
	"fmt"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strconv"
	"strings"
	"time"

	"github.com/goamz/goamz/aws"
)

// The CloudFormation type encapsulates operations within a specific EC2 region.
type CloudFormation struct {
	aws.Auth
	aws.Region
}

// New creates a new CloudFormation Client.
func New(auth aws.Auth, region aws.Region) *CloudFormation {

	return &CloudFormation{auth, region}

}

const debug = false

// ----------------------------------------------------------------------------
// Request dispatching logic.

// Error encapsulates an error returned by the AWS CloudFormation API.
//
// See http://goo.gl/zDZbuQ  for more details.
type Error struct {
	// HTTP status code (200, 403, ...)
	StatusCode int
	// Error type
	Type string `xml:"Type"`
	// CloudFormation error code
	Code string `xml:"Code"`
	// The human-oriented error message
	Message   string `xml:"Message"`
	RequestId string `xml:"RequestID"`
}

func (err *Error) Error() string {
	if err.Code == "" {
		return err.Message
	}

	return fmt.Sprintf("%s (%s)", err.Message, err.Code)
}

type xmlErrors struct {
	RequestId string  `xml:"RequestId"`
	Errors    []Error `xml:"Error"`
}

func (c *CloudFormation) query(params map[string]string, resp interface{}) error {
	params["Version"] = "2010-05-15"

	data := strings.NewReader(multimap(params).Encode())

	hreq, err := http.NewRequest("POST", c.Region.CloudFormationEndpoint+"/", data)
	if err != nil {
		return err
	}

	hreq.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value")

	token := c.Auth.Token()
	if token != "" {
		hreq.Header.Set("X-Amz-Security-Token", token)
	}

	signer := aws.NewV4Signer(c.Auth, "cloudformation", c.Region)
	signer.Sign(hreq)

	if debug {
		log.Printf("%v -> {\n", hreq)
	}
	r, err := http.DefaultClient.Do(hreq)

	if err != nil {
		log.Printf("Error calling Amazon")
		return err
	}

	defer r.Body.Close()

	if debug {
		dump, _ := httputil.DumpResponse(r, true)
		log.Printf("response:\n")
		log.Printf("%v\n}\n", string(dump))
	}
	if r.StatusCode != 200 {
		return buildError(r)
	}
	err = xml.NewDecoder(r.Body).Decode(resp)
	return err
}

func buildError(r *http.Response) error {
	var (
		err    Error
		errors xmlErrors
	)
	xml.NewDecoder(r.Body).Decode(&errors)
	if len(errors.Errors) > 0 {
		err = errors.Errors[0]
	}

	err.RequestId = errors.RequestId
	err.StatusCode = r.StatusCode
	if err.Message == "" {
		err.Message = r.Status
	}
	return &err
}

func makeParams(action string) map[string]string {
	params := make(map[string]string)
	params["Action"] = action
	return params
}

func multimap(p map[string]string) url.Values {
	q := make(url.Values, len(p))
	for k, v := range p {
		q[k] = []string{v}
	}
	return q
}

// addParamsList adds params in the form of param.member.N to the params map
func addParamsList(params map[string]string, label string, ids []string) {
	for i, id := range ids {
		params[label+"."+strconv.Itoa(i+1)] = id
	}
}

// -----------------------------------------------------------------------
// API Supported Types and Methods

// SimpleResp is the basic response from most actions.
type SimpleResp struct {
	XMLName   xml.Name
	RequestId string `xml:"ResponseMetadata>RequestId"`
}

// CancelUpdateStack cancels an update on the specified stack.
// If the call completes successfully, the stack will roll back the update and revert
// to the previous stack configuration.
//
// See http://goo.gl/ZE6fOa for more details
func (c *CloudFormation) CancelUpdateStack(stackName string) (resp *SimpleResp, err error) {
	params := makeParams("CancelUpdateStack")

	params["StackName"] = stackName

	resp = new(SimpleResp)
	if err := c.query(params, resp); err != nil {
		return nil, err
	}
	return resp, nil
}

// Parameter encapsulates the cloudstack paramter data type
//
// See http://goo.gl/2rg9eG for more details
type Parameter struct {
	ParameterKey     string `xml:"ParameterKey"`
	ParameterValue   string `xml:"ParameterValue"`
	UsePreviousValue bool   `xml:"UsePreviousValue"`
}

type Tag struct {
	Key   string `xml:"Key"`
	Value string `xml:"Value"`
}

// CreateStackParams wraps CreateStack request options
//
// See http://goo.gl/yDZYuV for more information
type CreateStackParams struct {
	Capabilities     []string
	DisableRollback  bool
	NotificationARNs []string
	OnFailure        string
	Parameters       []Parameter
	StackName        string
	StackPolicyBody  string
	StackPolicyURL   string
	Tags             []Tag
	TemplateBody     string
	TemplateURL      string
	TimeoutInMinutes int
}

// CreateStackResponse wraps a CreateStack call response
//
// See http://goo.gl/yDZYuV for more details
type CreateStackResponse struct {
	StackId   string `xml:"CreateStackResult>StackId"`
	RequestId string `xml:"ResponseMetadata>RequestId"`
}

// CreateStack creates a stack as specified in the template. After the call completes successfully,
// the stack creation starts.
//
// Required params: StackName
//
// See http://goo.gl/yDZYuV for more details
func (c *CloudFormation) CreateStack(options *CreateStackParams) (
	resp *CreateStackResponse, err error) {
	params := makeParams("CreateStack")

	params["StackName"] = options.StackName

	if options.DisableRollback {
		params["DisableRollback"] = strconv.FormatBool(options.DisableRollback)
	}
	if options.OnFailure != "" {
		params["OnFailure"] = options.OnFailure
	}
	if options.StackPolicyBody != "" {
		params["StackPolicyBody"] = options.StackPolicyBody
	}
	if options.StackPolicyURL != "" {
		params["StackPolicyURL"] = options.StackPolicyURL
	}
	if options.TemplateBody != "" {
		params["TemplateBody"] = options.TemplateBody
	}
	if options.TemplateURL != "" {
		params["TemplateURL"] = options.TemplateURL
	}
	if options.TimeoutInMinutes != 0 {
		params["TimeoutInMinutes"] = strconv.Itoa(options.TimeoutInMinutes)
	}
	if len(options.Capabilities) > 0 {
		addParamsList(params, "Capabilities.member", options.Capabilities)
	}
	if len(options.NotificationARNs) > 0 {
		addParamsList(params, "NotificationARNs.member", options.NotificationARNs)
	}
	// Add any parameters
	for i, t := range options.Parameters {
		key := "Parameters.member.%d.%s"
		index := i + 1
		params[fmt.Sprintf(key, index, "ParameterKey")] = t.ParameterKey
		params[fmt.Sprintf(key, index, "ParameterValue")] = t.ParameterValue
		params[fmt.Sprintf(key, index, "UsePreviousValue")] = strconv.FormatBool(t.UsePreviousValue)
	}
	// Add any tags
	for i, t := range options.Tags {
		key := "Tags.member.%d.%s"
		index := i + 1
		params[fmt.Sprintf(key, index, "Key")] = t.Key
		params[fmt.Sprintf(key, index, "Value")] = t.Value
	}

	resp = new(CreateStackResponse)
	if err := c.query(params, resp); err != nil {
		return nil, err
	}
	return resp, nil
}

// DeleteStack deletes a specified stack.
// Once the call completes successfully, stack deletion starts.
//
// See http://goo.gl/CVMpxC for more details
func (c *CloudFormation) DeleteStack(stackName string) (resp *SimpleResp, err error) {
	params := makeParams("DeleteStack")

	params["StackName"] = stackName

	resp = new(SimpleResp)
	if err := c.query(params, resp); err != nil {
		return nil, err
	}
	return resp, nil
}

// StackEvent encapsulates the StackEvent data type
//
// See http://goo.gl/EHwiMf for more details
type StackEvent struct {
	EventId              string    `xml:"EventId"`
	LogicalResourceId    string    `xml:"LogicalResourceId"`
	PhysicalResourceId   string    `xml:"PhysicalResourceId"`
	ResourceProperties   string    `xml:"ResourceProperties"`
	ResourceStatus       string    `xml:"ResourceStatus"`
	ResourceStatusReason string    `xml:"ResourceStatusReason"`
	ResourceType         string    `xml:"ResourceType"`
	StackId              string    `xml:"StackId"`
	StackName            string    `xml:"StackName"`
	Timestamp            time.Time `xml:"Timestamp"`
}

// DescribeStackEventsResponse wraps a response returned by DescribeStackEvents request
//
// See http://goo.gl/zqj4Bz for more details
type DescribeStackEventsResponse struct {
	NextToken   string       `xml:"DescribeStackEventsResult>NextToken"`
	StackEvents []StackEvent `xml:"DescribeStackEventsResult>StackEvents>member"`
	RequestId   string       `xml:"ResponseMetadata>RequestId"`
}

// DescribeStackEvents returns all stack related events for a specified stack.
//
// See http://goo.gl/zqj4Bz for more details
func (c *CloudFormation) DescribeStackEvents(stackName string, nextToken string) (
	resp *DescribeStackEventsResponse, err error) {
	params := makeParams("DescribeStackEvents")

	if stackName != "" {
		params["StackName"] = stackName
	}
	if nextToken != "" {
		params["NextToken"] = nextToken
	}

	resp = new(DescribeStackEventsResponse)
	if err := c.query(params, resp); err != nil {
		return nil, err
	}
	return resp, nil
}

// StackResourceDetail encapsulates the StackResourceDetail data type
//
// See http://goo.gl/flce6I for more details
type StackResourceDetail struct {
	Description          string    `xml:"Description"`
	LastUpdatedTimestamp time.Time `xml:"LastUpdatedTimestamp"`
	LogicalResourceId    string    `xml:"LogicalResourceId"`
	Metadata             string    `xml:"Metadata"`
	PhysicalResourceId   string    `xml:"PhysicalResourceId"`
	ResourceStatus       string    `xml:"ResourceStatus"`
	ResourceStatusReason string    `xml:"ResourceStatusReason"`
	ResourceType         string    `xml:"ResourceType"`
	StackId              string    `xml:"StackId"`
	StackName            string    `xml:"StackName"`
}

// DescribeStackResourceResponse wraps a response returned by DescribeStackResource request
//
// See http://goo.gl/6pfPFs for more details
type DescribeStackResourceResponse struct {
	StackResourceDetail StackResourceDetail `xml:"DescribeStackResourceResult>StackResourceDetail"`
	RequestId           string              `xml:"ResponseMetadata>RequestId"`
}

// DescribeStackResource returns a description of the specified resource in the specified stack.
// For deleted stacks, DescribeStackResource returns resource information
// for up to 90 days after the stack has been deleted.
//
// Required params: stackName, logicalResourceId
//
// See http://goo.gl/6pfPFs for more details
func (c *CloudFormation) DescribeStackResource(stackName string, logicalResourceId string) (
	resp *DescribeStackResourceResponse, err error) {
	params := makeParams("DescribeStackResource")

	params["StackName"] = stackName
	params["LogicalResourceId"] = logicalResourceId

	resp = new(DescribeStackResourceResponse)
	if err := c.query(params, resp); err != nil {
		return nil, err
	}
	return resp, nil
}

// StackResource encapsulates the StackResource data type
//
// See http://goo.gl/j4eli5 for more details
type StackResource struct {
	Description          string    `xml:"Description"`
	LogicalResourceId    string    `xml:"LogicalResourceId"`
	PhysicalResourceId   string    `xml:"PhysicalResourceId"`
	ResourceStatus       string    `xml:"ResourceStatus"`
	ResourceStatusReason string    `xml:"ResourceStatusReason"`
	ResourceType         string    `xml:"ResourceType"`
	StackId              string    `xml:"StackId"`
	StackName            string    `xml:"StackName"`
	Timestamp            time.Time `xml:"Timestamp"`
}

// DescribeStackResourcesResponse wraps a response returned by DescribeStackResources request
//
// See http://goo.gl/YnY5rs for more details
type DescribeStackResourcesResponse struct {
	StackResources []StackResource `xml:"DescribeStackResourcesResult>StackResources>member"`
	RequestId      string          `xml:"ResponseMetadata>RequestId"`
}

// DescribeStackResources returns AWS resource descriptions for running and deleted stacks.
// If stackName is specified, all the associated resources that are part of the stack are returned.
// If physicalResourceId is specified, the associated resources of the stack that the resource
// belongs to are returned.
//
// Only the first 100 resources will be returned. If your stack has more resources than this,
// you should use ListStackResources instead.
//
// See http://goo.gl/YnY5rs for more details
func (c *CloudFormation) DescribeStackResources(stackName, physicalResourceId, logicalResourceId string) (
	resp *DescribeStackResourcesResponse, err error) {
	params := makeParams("DescribeStackResources")

	if stackName != "" {
		params["StackName"] = stackName
	}
	if physicalResourceId != "" {
		params["PhysicalResourceId"] = physicalResourceId
	}
	if logicalResourceId != "" {
		params["LogicalResourceId"] = logicalResourceId
	}

	resp = new(DescribeStackResourcesResponse)
	if err := c.query(params, resp); err != nil {
		return nil, err
	}
	return resp, nil
}

// Output encapsulates the Output AWS data type
//
// See http://goo.gl/UOn7q6 for more information
type Output struct {
	Description string `xml:"Description"`
	OutputKey   string `xml:"OutputKey"`
	OutputValue string `xml:"OutputValue"`
}

// Stack encapsulates the Stack AWS data type
//
// See http://goo.gl/yDZYuV for more information
type Stack struct {
	Capabilities      []string    `xml:"Capabilities>member"`
	CreationTime      time.Time   `xml:"CreationTime"`
	Description       string      `xml:"Description"`
	DisableRollback   bool        `xml:"DisableRollback"`
	LastUpdatedTime   time.Time   `xml:"LastUpdatedTime"`
	NotificationARNs  []string    `xml:"NotificationARNs>member"`
	Outputs           []Output    `xml:"Outputs>member"`
	Parameters        []Parameter `xml:"Parameters>member"`
	StackId           string      `xml:"StackId"`
	StackName         string      `xml:"StackName"`
	StackStatus       string      `xml:"StackStatus"`
	StackStatusReason string      `xml:"StackStatusReason"`
	Tags              []Tag       `xml:"Tags>member"`
	TimeoutInMinutes  int         `xml:"TimeoutInMinutes"`
}

// DescribeStacksResponse wraps a response returned by DescribeStacks request
//
// See http://goo.gl/UOLsXD for more information
type DescribeStacksResponse struct {
	NextToken string  `xml:"DescribeStacksResult>NextToken"`
	Stacks    []Stack `xml:"DescribeStacksResult>Stacks>member"`
	RequestId string  `xml:"ResponseMetadata>RequestId"`
}

// DescribeStacks returns the description for the specified stack;
// If no stack name was specified, then it returns the description for all the stacks created.
//
// See http://goo.gl/UOLsXD for more information
func (c *CloudFormation) DescribeStacks(stackName string, nextToken string) (
	resp *DescribeStacksResponse, err error) {
	params := makeParams("DescribeStacks")

	if stackName != "" {
		params["StackName"] = stackName
	}
	if nextToken != "" {
		params["NextToken"] = nextToken
	}

	resp = new(DescribeStacksResponse)
	if err := c.query(params, resp); err != nil {
		return nil, err
	}
	return resp, nil
}

// EstimateTemplateCostResponse wraps a response returned by EstimateTemplateCost request
//
// See http://goo.gl/PD9hle for more information
type EstimateTemplateCostResponse struct {
	Url       string `xml:"EstimateTemplateCostResult>Url"`
	RequestId string `xml:"ResponseMetadata>RequestId"`
}

// EstimateTemplateCost returns the estimated monthly cost of a template.
// The return value is an AWS Simple Monthly Calculator URL with a query string that describes
// the resources required to run the template.
//
// See http://goo.gl/PD9hle for more information
func (c *CloudFormation) EstimateTemplateCost(parameters []Parameter, templateBody, templateUrl string) (
	resp *EstimateTemplateCostResponse, err error) {
	params := makeParams("EstimateTemplateCost")

	if templateBody != "" {
		params["TemplateBody"] = templateBody
	}
	if templateUrl != "" {
		params["TemplateURL"] = templateUrl
	}
	// Add any parameters
	for i, t := range parameters {
		key := "Parameters.member.%d.%s"
		index := i + 1
		params[fmt.Sprintf(key, index, "ParameterKey")] = t.ParameterKey
		params[fmt.Sprintf(key, index, "ParameterValue")] = t.ParameterValue
		params[fmt.Sprintf(key, index, "UsePreviousValue")] = strconv.FormatBool(t.UsePreviousValue)
	}

	resp = new(EstimateTemplateCostResponse)
	if err := c.query(params, resp); err != nil {
		return nil, err
	}
	return resp, nil
}

// GetStackPolicyResponse wraps a response returned by GetStackPolicy request
//
// See http://goo.gl/iZFSgy for more information
type GetStackPolicyResponse struct {
	StackPolicyBody string `xml:"GetStackPolicyResult>StackPolicyBody"`
	RequestId       string `xml:"ResponseMetadata>RequestId"`
}

// GetStackPolicy returns the stack policy for a specified stack. If a stack doesn't have a policy,
// a null value is returned.
//
// See http://goo.gl/iZFSgy for more information
func (c *CloudFormation) GetStackPolicy(stackName string) (
	resp *GetStackPolicyResponse, err error) {
	params := makeParams("GetStackPolicy")

	params["StackName"] = stackName

	resp = new(GetStackPolicyResponse)
	if err := c.query(params, resp); err != nil {
		return nil, err
	}
	return resp, nil
}

// GetTemplateResponse wraps a response returned by GetTemplate request
//
// See http://goo.gl/GU59CB for more information
type GetTemplateResponse struct {
	TemplateBody string `xml:"GetTemplateResult>TemplateBody"`
	RequestId    string `xml:"ResponseMetadata>RequestId"`
}

// GetTemplate returns the template body for a specified stack.
// You can get the template for running or deleted stacks
//
// Required Params: StackName - The name or the unique identifier associated with the stack,
// which are not always interchangeable:
// Running stacks: You can specify either the stack's name or its unique stack ID.
// Deleted stacks: You must specify the unique stack ID.
//
// See http://goo.gl/GU59CB for more information
func (c *CloudFormation) GetTemplate(stackName string) (
	resp *GetTemplateResponse, err error) {
	params := makeParams("GetTemplate")

	params["StackName"] = stackName

	resp = new(GetTemplateResponse)
	if err := c.query(params, resp); err != nil {
		return nil, err
	}
	return resp, nil
}

// StackResourceSummary encapsulates the StackResourceSummary data type
//
// See http://goo.gl/Af0vcm for more details
type StackResourceSummary struct {
	LastUpdatedTimestamp time.Time `xml:"LastUpdatedTimestamp"`
	LogicalResourceId    string    `xml:"LogicalResourceId"`
	PhysicalResourceId   string    `xml:"PhysicalResourceId"`
	ResourceStatus       string    `xml:"ResourceStatus"`
	ResourceStatusReason string    `xml:"ResourceStatusReason"`
	ResourceType         string    `xml:"ResourceType"`
}

// ListStackResourcesResponse wraps a response returned by ListStackResources request
//
// See http://goo.gl/JUCgLf for more details
type ListStackResourcesResponse struct {
	NextToken              string                 `xml:"ListStackResourcesResult>NextToken"`
	StackResourceSummaries []StackResourceSummary `xml:"ListStackResourcesResult>StackResourceSummaries>member"`
	RequestId              string                 `xml:"ResponseMetadata>RequestId"`
}

// ListStackResources returns descriptions of all resources of the specified stack.
// For deleted stacks, ListStackResources returns resource information for up to 90 days
// after the stack has been deleted.
//
// Required Params: stackName - the name or the unique identifier associated with the stack,
// which are not always interchangeable:
// Running stacks: You can specify either the stack's name or its unique stack ID.
// Deleted stacks: You must specify the unique stack ID.
//
// See http://goo.gl/JUCgLf for more details
func (c *CloudFormation) ListStackResources(stackName, nextToken string) (
	resp *ListStackResourcesResponse, err error) {
	params := makeParams("ListStackResources")

	params["StackName"] = stackName

	if nextToken != "" {
		params["NextToken"] = nextToken
	}

	resp = new(ListStackResourcesResponse)
	if err := c.query(params, resp); err != nil {
		return nil, err
	}
	return resp, nil
}

// StackSummary encapsulates the StackSummary data type
//
// See http://goo.gl/35j3wf for more details
type StackSummary struct {
	CreationTime        time.Time `xml:"CreationTime"`
	DeletionTime        time.Time `xml:"DeletionTime"`
	LastUpdatedTime     time.Time `xml:"LastUpdatedTime"`
	StackId             string    `xml:"StackId"`
	StackName           string    `xml:"StackName"`
	StackStatus         string    `xml:"StackStatus"`
	StackStatusReason   string    `xml:"StackStatusReason"`
	TemplateDescription string    `xml:"TemplateDescription"`
}

// ListStacksResponse wraps a response returned by ListStacks request
//
// See http://goo.gl/UWi6nm for more details
type ListStacksResponse struct {
	NextToken      string         `xml:"ListStacksResult>NextToken"`
	StackSummaries []StackSummary `xml:"ListStacksResult>StackSummaries>member"`
	RequestId      string         `xml:"ResponseMetadata>RequestId"`
}

// ListStacks Returns the summary information for stacks whose status matches the specified StackStatusFilter.
// Summary information for stacks that have been deleted is kept for 90 days after the stack is deleted.
// If no StackStatusFilter is specified, summary information for all stacks is returned
// (including existing stacks and stacks that have been deleted).
//
// See http://goo.gl/UWi6nm for more details
func (c *CloudFormation) ListStacks(stackStatusFilters []string, nextToken string) (
	resp *ListStacksResponse, err error) {
	params := makeParams("ListStacks")

	if nextToken != "" {
		params["NextToken"] = nextToken
	}

	if len(stackStatusFilters) > 0 {
		addParamsList(params, "StackStatusFilter.member", stackStatusFilters)
	}

	resp = new(ListStacksResponse)
	if err := c.query(params, resp); err != nil {
		return nil, err
	}
	return resp, nil
}

// SetStackPolicy sets a stack policy for a specified stack.
//
// Required Params: stackName
//
// See http://goo.gl/iY9ohu for more information
func (c *CloudFormation) SetStackPolicy(stackName, stackPolicyBody, stackPolicyUrl string) (
	resp *SimpleResp, err error) {
	params := makeParams("SetStackPolicy")

	params["StackName"] = stackName

	if stackPolicyBody != "" {
		params["StackPolicyBody"] = stackPolicyBody
	}
	if stackPolicyUrl != "" {
		params["StackPolicyURL"] = stackPolicyUrl
	}

	resp = new(SimpleResp)
	if err := c.query(params, resp); err != nil {
		return nil, err
	}
	return resp, nil
}

// UpdateStackParams wraps UpdateStack request options
//
// See http://goo.gl/LvkhZq for more information
type UpdateStackParams struct {
	Capabilities                []string
	NotificationARNs            []string
	Parameters                  []Parameter
	StackName                   string
	StackPolicyBody             string
	StackPolicyDuringUpdateBody string
	StackPolicyDuringUpdateURL  string
	StackPolicyURL              string
	TemplateBody                string
	TemplateURL                 string
	UsePreviousTemplate         bool
}

// UpdateStackResponse wraps the UpdateStack call response
//
// See http://goo.gl/LvkhZq for more information
type UpdateStackResponse struct {
	StackId   string `xml:"UpdateStackResult>StackId"`
	RequestId string `xml:"ResponseMetadata>RequestId"`
}

// UpdateStack updates a stack as specified in the template.
// After the call completes successfully, the stack update starts.
// You can check the status of the stack via the DescribeStacks action.
//
// Required Params: options.StackName
//
// See http://goo.gl/LvkhZq for more information
func (c *CloudFormation) UpdateStack(options *UpdateStackParams) (
	resp *UpdateStackResponse, err error) {
	params := makeParams("UpdateStack")

	params["StackName"] = options.StackName

	if options.StackPolicyBody != "" {
		params["StackPolicyBody"] = options.StackPolicyBody
	}
	if options.StackPolicyDuringUpdateBody != "" {
		params["StackPolicyDuringUpdateBody"] = options.StackPolicyDuringUpdateBody
	}
	if options.StackPolicyDuringUpdateURL != "" {
		params["StackPolicyDuringUpdateURL"] = options.StackPolicyDuringUpdateURL
	}
	if options.StackPolicyURL != "" {
		params["StackPolicyURL"] = options.StackPolicyURL
	}
	if options.TemplateBody != "" {
		params["TemplateBody"] = options.TemplateBody
	}
	if options.TemplateURL != "" {
		params["TemplateURL"] = options.TemplateURL
	}
	if options.UsePreviousTemplate {
		params["UsePreviousTemplate"] = strconv.FormatBool(options.UsePreviousTemplate)
	}

	if len(options.Capabilities) > 0 {
		addParamsList(params, "Capabilities.member", options.Capabilities)
	}
	if len(options.NotificationARNs) > 0 {
		addParamsList(params, "NotificationARNs.member", options.NotificationARNs)
	}
	// Add any parameters
	for i, t := range options.Parameters {
		key := "Parameters.member.%d.%s"
		index := i + 1
		params[fmt.Sprintf(key, index, "ParameterKey")] = t.ParameterKey
		params[fmt.Sprintf(key, index, "ParameterValue")] = t.ParameterValue
		params[fmt.Sprintf(key, index, "UsePreviousValue")] = strconv.FormatBool(t.UsePreviousValue)
	}

	resp = new(UpdateStackResponse)
	if err := c.query(params, resp); err != nil {
		return nil, err
	}
	return resp, nil
}

// TemplateParameter encapsulates the AWS TemplateParameter data type
//
// See http://goo.gl/OBhNzk for more information
type TemplateParameter struct {
	DefaultValue string `xml:"DefaultValue"`
	Description  string `xml:Description"`
	NoEcho       bool   `xml:NoEcho"`
	ParameterKey string `xml:ParameterKey"`
}

// ValidateTemplateResponse wraps the ValidateTemplate call response
//
// See http://goo.gl/OBhNzk for more information
type ValidateTemplateResponse struct {
	Capabilities       []string            `xml:"ValidateTemplateResult>Capabilities>member"`
	CapabilitiesReason string              `xml:"ValidateTemplateResult>CapabilitiesReason"`
	Description        string              `xml:"ValidateTemplateResult>Description"`
	Parameters         []TemplateParameter `xml:"ValidateTemplateResult>Parameters>member"`
	RequestId          string              `xml:"ResponseMetadata>RequestId"`
}

// ValidateTemplate validates a specified template.
//
// See http://goo.gl/OBhNzk for more information
func (c *CloudFormation) ValidateTemplate(templateBody, templateUrl string) (
	resp *ValidateTemplateResponse, err error) {
	params := makeParams("ValidateTemplate")

	if templateBody != "" {
		params["TemplateBody"] = templateBody
	}
	if templateUrl != "" {
		params["TemplateURL"] = templateUrl
	}

	resp = new(ValidateTemplateResponse)
	if err := c.query(params, resp); err != nil {
		return nil, err
	}
	return resp, nil
}