2
// goamz - Go packages to interact with the Amazon Web Services.
4
// https://wiki.ubuntu.com/goamz
6
// Copyright (c) 2011 Canonical Ltd.
8
// Written by Gustavo Niemeyer <gustavo.niemeyer@canonical.com>
18
"launchpad.net/goamz/aws"
30
// The EC2 type encapsulates operations with a specific EC2 region.
34
private byte // Reserve the right of using private data.
37
// New creates a new EC2.
38
func New(auth aws.Auth, region aws.Region) *EC2 {
39
return &EC2{auth, region, 0}
42
// ----------------------------------------------------------------------------
45
// Filter builds filtering parameters to be used in an EC2 query which supports
46
// filtering. For example:
48
// filter := NewFilter()
49
// filter.Add("architecture", "i386")
50
// filter.Add("launch-index", "0")
51
// resp, err := ec2.Instances(nil, filter)
57
// NewFilter creates a new Filter.
58
func NewFilter() *Filter {
59
return &Filter{make(map[string][]string)}
62
// Add appends a filtering parameter with the given name and value(s).
63
func (f *Filter) Add(name string, value ...string) {
64
f.m[name] = append(f.m[name], value...)
67
func (f *Filter) addParams(params map[string]string) {
69
a := make([]string, len(f.m))
75
sort.StringSlice(a).Sort()
77
prefix := "Filter." + strconv.Itoa(i+1)
78
params[prefix+".Name"] = k
79
for j, v := range f.m[k] {
80
params[prefix+".Value."+strconv.Itoa(j+1)] = v
86
// ----------------------------------------------------------------------------
87
// Request dispatching logic.
89
// Error encapsulates an error returned by EC2.
91
// See http://goo.gl/VZGuC for more details.
93
// HTTP status code (200, 403, ...)
95
// EC2 error code ("UnsupportedOperation", ...)
97
// The human-oriented error message
99
RequestId string `xml:"RequestID"`
102
func (err *Error) Error() string {
107
return fmt.Sprintf("%s (%s)", err.Message, err.Code)
110
// For now a single error inst is being exposed. In the future it may be useful
111
// to provide access to all of them, but rather than doing it as an array/slice,
112
// use a *next pointer, so that it's backward compatible and it continues to be
113
// easy to handle the first error, which is what most people will want.
114
type xmlErrors struct {
115
RequestId string `xml:"RequestID"`
116
Errors []Error `xml:"Errors>Error"`
119
var timeNow = time.Now
121
func (ec2 *EC2) query(params map[string]string, resp interface{}) error {
122
params["Version"] = "2011-12-15"
123
params["Timestamp"] = timeNow().In(time.UTC).Format(time.RFC3339)
124
endpoint, err := url.Parse(ec2.Region.EC2Endpoint)
128
if endpoint.Path == "" {
131
sign(ec2.Auth, "GET", endpoint.Path, params, endpoint.Host)
132
endpoint.RawQuery = multimap(params).Encode()
134
log.Printf("get { %v } -> {\n", endpoint.String())
136
r, err := http.Get(endpoint.String())
143
dump, _ := httputil.DumpResponse(r, true)
144
log.Printf("response:\n")
145
log.Printf("%v\n}\n", string(dump))
147
if r.StatusCode != 200 {
150
err = xml.NewDecoder(r.Body).Decode(resp)
154
func multimap(p map[string]string) url.Values {
155
q := make(url.Values, len(p))
156
for k, v := range p {
162
func buildError(r *http.Response) error {
163
errors := xmlErrors{}
164
xml.NewDecoder(r.Body).Decode(&errors)
166
if len(errors.Errors) > 0 {
167
err = errors.Errors[0]
169
err.RequestId = errors.RequestId
170
err.StatusCode = r.StatusCode
171
if err.Message == "" {
172
err.Message = r.Status
177
func makeParams(action string) map[string]string {
178
params := make(map[string]string)
179
params["Action"] = action
183
func addParamsList(params map[string]string, label string, ids []string) {
184
for i, id := range ids {
185
params[label+"."+strconv.Itoa(i+1)] = id
189
// ----------------------------------------------------------------------------
190
// Instance management functions and types.
192
// The RunInstances type encapsulates options for the respective request in EC2.
194
// See http://goo.gl/Mcm3b for more details.
195
type RunInstances struct {
201
SecurityGroups []SecurityGroup
206
PlacementGroupName string
209
DisableAPITermination bool
210
ShutdownBehavior string
211
PrivateIPAddress string
214
// Response to a RunInstances request.
216
// See http://goo.gl/Mcm3b for more details.
217
type RunInstancesResp struct {
218
RequestId string `xml:"requestId"`
219
ReservationId string `xml:"reservationId"`
220
OwnerId string `xml:"ownerId"`
221
SecurityGroups []SecurityGroup `xml:"groupSet>item"`
222
Instances []Instance `xml:"instancesSet>item"`
225
// Instance encapsulates a running instance in EC2.
227
// See http://goo.gl/OCH8a for more details.
228
type Instance struct {
229
InstanceId string `xml:"instanceId"`
230
InstanceType string `xml:"instanceType"`
231
ImageId string `xml:"imageId"`
232
PrivateDNSName string `xml:"privateDnsName"`
233
DNSName string `xml:"dnsName"`
234
KeyName string `xml:"keyName"`
235
AMILaunchIndex int `xml:"amiLaunchIndex"`
236
Hypervisor string `xml:"hypervisor"`
237
VirtType string `xml:"virtualizationType"`
238
Monitoring string `xml:"monitoring>state"`
239
AvailZone string `xml:"placement>availabilityZone"`
240
PlacementGroupName string `xml:"placement>groupName"`
241
State InstanceState `xml:"instanceState"`
242
Tags []Tag `xml:"tagSet>item"`
245
// RunInstances starts new instances in EC2.
246
// If options.MinCount and options.MaxCount are both zero, a single instance
247
// will be started; otherwise if options.MaxCount is zero, options.MinCount
248
// will be used insteead.
250
// See http://goo.gl/Mcm3b for more details.
251
func (ec2 *EC2) RunInstances(options *RunInstances) (resp *RunInstancesResp, err error) {
252
params := makeParams("RunInstances")
253
params["ImageId"] = options.ImageId
254
params["InstanceType"] = options.InstanceType
256
if options.MinCount == 0 && options.MaxCount == 0 {
259
} else if options.MaxCount == 0 {
260
min = options.MinCount
263
min = options.MinCount
264
max = options.MaxCount
266
params["MinCount"] = strconv.Itoa(min)
267
params["MaxCount"] = strconv.Itoa(max)
269
for _, g := range options.SecurityGroups {
271
params["SecurityGroupId."+strconv.Itoa(i)] = g.Id
274
params["SecurityGroup."+strconv.Itoa(j)] = g.Name
278
token, err := clientToken()
282
params["ClientToken"] = token
284
if options.KeyName != "" {
285
params["KeyName"] = options.KeyName
287
if options.KernelId != "" {
288
params["KernelId"] = options.KernelId
290
if options.RamdiskId != "" {
291
params["RamdiskId"] = options.RamdiskId
293
if options.UserData != nil {
294
userData := make([]byte, b64.EncodedLen(len(options.UserData)))
295
b64.Encode(userData, options.UserData)
296
params["UserData"] = string(userData)
298
if options.AvailZone != "" {
299
params["Placement.AvailabilityZone"] = options.AvailZone
301
if options.PlacementGroupName != "" {
302
params["Placement.GroupName"] = options.PlacementGroupName
304
if options.Monitoring {
305
params["Monitoring.Enabled"] = "true"
307
if options.SubnetId != "" {
308
params["SubnetId"] = options.SubnetId
310
if options.DisableAPITermination {
311
params["DisableApiTermination"] = "true"
313
if options.ShutdownBehavior != "" {
314
params["InstanceInitiatedShutdownBehavior"] = options.ShutdownBehavior
316
if options.PrivateIPAddress != "" {
317
params["PrivateIpAddress"] = options.PrivateIPAddress
320
resp = &RunInstancesResp{}
321
err = ec2.query(params, resp)
328
func clientToken() (string, error) {
329
// Maximum EC2 client token size is 64 bytes.
330
// Each byte expands to two when hex encoded.
331
buf := make([]byte, 32)
332
_, err := rand.Read(buf)
336
return hex.EncodeToString(buf), nil
339
// Response to a TerminateInstances request.
341
// See http://goo.gl/3BKHj for more details.
342
type TerminateInstancesResp struct {
343
RequestId string `xml:"requestId"`
344
StateChanges []InstanceStateChange `xml:"instancesSet>item"`
347
// InstanceState encapsulates the state of an instance in EC2.
349
// See http://goo.gl/y3ZBq for more details.
350
type InstanceState struct {
351
Code int `xml:"code"` // Watch out, bits 15-8 have unpublished meaning.
352
Name string `xml:"name"`
355
// InstanceStateChange informs of the previous and current states
356
// for an instance when a state change is requested.
357
type InstanceStateChange struct {
358
InstanceId string `xml:"instanceId"`
359
CurrentState InstanceState `xml:"currentState"`
360
PreviousState InstanceState `xml:"previousState"`
363
// TerminateInstances requests the termination of instances when the given ids.
365
// See http://goo.gl/3BKHj for more details.
366
func (ec2 *EC2) TerminateInstances(instIds []string) (resp *TerminateInstancesResp, err error) {
367
params := makeParams("TerminateInstances")
368
addParamsList(params, "InstanceId", instIds)
369
resp = &TerminateInstancesResp{}
370
err = ec2.query(params, resp)
377
// Response to a DescribeInstances request.
379
// See http://goo.gl/mLbmw for more details.
380
type InstancesResp struct {
381
RequestId string `xml:"requestId"`
382
Reservations []Reservation `xml:"reservationSet>item"`
385
// Reservation represents details about a reservation in EC2.
387
// See http://goo.gl/0ItPT for more details.
388
type Reservation struct {
389
ReservationId string `xml:"reservationId"`
390
OwnerId string `xml:"ownerId"`
391
RequesterId string `xml:"requesterId"`
392
SecurityGroups []SecurityGroup `xml:"groupSet>item"`
393
Instances []Instance `xml:"instancesSet>item"`
396
// Instances returns details about instances in EC2. Both parameters
397
// are optional, and if provided will limit the instances returned to those
398
// matching the given instance ids or filtering rules.
400
// See http://goo.gl/4No7c for more details.
401
func (ec2 *EC2) Instances(instIds []string, filter *Filter) (resp *InstancesResp, err error) {
402
params := makeParams("DescribeInstances")
403
addParamsList(params, "InstanceId", instIds)
404
filter.addParams(params)
405
resp = &InstancesResp{}
406
err = ec2.query(params, resp)
413
// ----------------------------------------------------------------------------
414
// Image and snapshot management functions and types.
416
// Response to a DescribeImages request.
418
// See http://goo.gl/hLnyg for more details.
419
type ImagesResp struct {
420
RequestId string `xml:"requestId"`
421
Images []Image `xml:"imagesSet>item"`
424
// BlockDeviceMapping represents the association of a block device with an image.
426
// See http://goo.gl/wnDBf for more details.
427
type BlockDeviceMapping struct {
428
DeviceName string `xml:"deviceName"`
429
VirtualName string `xml:"virtualName"`
430
SnapshotId string `xml:"ebs>snapshotId"`
431
VolumeType string `xml:"ebs>volumeType"`
432
VolumeSize int64 `xml:"ebs>volumeSize"`
433
DeleteOnTermination bool `xml:"ebs>deleteOnTermination"`
435
// The number of I/O operations per second (IOPS) that the volume supports.
436
IOPS int64 `xml:"ebs>iops"`
439
// Image represents details about an image.
441
// See http://goo.gl/iSqJG for more details.
443
Id string `xml:"imageId"`
444
Name string `xml:"name"`
445
Description string `xml:"description"`
446
Type string `xml:"imageType"`
447
State string `xml:"imageState"`
448
Location string `xml:"imageLocation"`
449
Public bool `xml:"isPublic"`
450
Architecture string `xml:"architecture"`
451
Platform string `xml:"platform"`
452
ProductCodes []string `xml:"productCode>item>productCode"`
453
KernelId string `xml:"kernelId"`
454
RamdiskId string `xml:"ramdiskId"`
455
StateReason string `xml:"stateReason"`
456
OwnerId string `xml:"imageOwnerId"`
457
OwnerAlias string `xml:"imageOwnerAlias"`
458
RootDeviceType string `xml:"rootDeviceType"`
459
RootDeviceName string `xml:"rootDeviceName"`
460
VirtualizationType string `xml:"virtualizationType"`
461
Hypervisor string `xml:"hypervisor"`
462
BlockDevices []BlockDeviceMapping `xml:"blockDeviceMapping>item"`
465
// Images returns details about available images.
466
// The ids and filter parameters, if provided, will limit the images returned.
467
// For example, to get all the private images associated with this account set
468
// the boolean filter "is-private" to true.
470
// Note: calling this function with nil ids and filter parameters will result in
471
// a very large number of images being returned.
473
// See http://goo.gl/SRBhW for more details.
474
func (ec2 *EC2) Images(ids []string, filter *Filter) (resp *ImagesResp, err error) {
475
params := makeParams("DescribeImages")
476
for i, id := range ids {
477
params["ImageId."+strconv.Itoa(i+1)] = id
479
filter.addParams(params)
482
err = ec2.query(params, resp)
489
// Response to a CreateSnapshot request.
491
// See http://goo.gl/ttcda for more details.
492
type CreateSnapshotResp struct {
493
RequestId string `xml:"requestId"`
497
// CreateSnapshot creates a volume snapshot and stores it in S3.
499
// See http://goo.gl/ttcda for more details.
500
func (ec2 *EC2) CreateSnapshot(volumeId, description string) (resp *CreateSnapshotResp, err error) {
501
params := makeParams("CreateSnapshot")
502
params["VolumeId"] = volumeId
503
params["Description"] = description
505
resp = &CreateSnapshotResp{}
506
err = ec2.query(params, resp)
513
// DeleteSnapshots deletes the volume snapshots with the given ids.
515
// Note: If you make periodic snapshots of a volume, the snapshots are
516
// incremental so that only the blocks on the device that have changed
517
// since your last snapshot are incrementally saved in the new snapshot.
518
// Even though snapshots are saved incrementally, the snapshot deletion
519
// process is designed so that you need to retain only the most recent
520
// snapshot in order to restore the volume.
522
// See http://goo.gl/vwU1y for more details.
523
func (ec2 *EC2) DeleteSnapshots(ids []string) (resp *SimpleResp, err error) {
524
params := makeParams("DeleteSnapshot")
525
for i, id := range ids {
526
params["SnapshotId."+strconv.Itoa(i+1)] = id
530
err = ec2.query(params, resp)
537
// Response to a DescribeSnapshots request.
539
// See http://goo.gl/nClDT for more details.
540
type SnapshotsResp struct {
541
RequestId string `xml:"requestId"`
542
Snapshots []Snapshot `xml:"snapshotSet>item"`
545
// Snapshot represents details about a volume snapshot.
547
// See http://goo.gl/nkovs for more details.
548
type Snapshot struct {
549
Id string `xml:"snapshotId"`
550
VolumeId string `xml:"volumeId"`
551
VolumeSize string `xml:"volumeSize"`
552
Status string `xml:"status"`
553
StartTime string `xml:"startTime"`
554
Description string `xml:"description"`
555
Progress string `xml:"progress"`
556
OwnerId string `xml:"ownerId"`
557
OwnerAlias string `xml:"ownerAlias"`
558
Tags []Tag `xml:"tagSet>item"`
561
// Snapshots returns details about volume snapshots available to the user.
562
// The ids and filter parameters, if provided, limit the snapshots returned.
564
// See http://goo.gl/ogJL4 for more details.
565
func (ec2 *EC2) Snapshots(ids []string, filter *Filter) (resp *SnapshotsResp, err error) {
566
params := makeParams("DescribeSnapshots")
567
for i, id := range ids {
568
params["SnapshotId."+strconv.Itoa(i+1)] = id
570
filter.addParams(params)
572
resp = &SnapshotsResp{}
573
err = ec2.query(params, resp)
580
// ----------------------------------------------------------------------------
581
// Security group management functions and types.
583
// SimpleResp represents a response to an EC2 request which on success will
584
// return no other information besides a request id.
585
type SimpleResp struct {
587
RequestId string `xml:"requestId"`
590
// CreateSecurityGroupResp represents a response to a CreateSecurityGroup request.
591
type CreateSecurityGroupResp struct {
593
RequestId string `xml:"requestId"`
596
// CreateSecurityGroup run a CreateSecurityGroup request in EC2, with the provided
597
// name and description.
599
// See http://goo.gl/Eo7Yl for more details.
600
func (ec2 *EC2) CreateSecurityGroup(name, description string) (resp *CreateSecurityGroupResp, err error) {
601
params := makeParams("CreateSecurityGroup")
602
params["GroupName"] = name
603
params["GroupDescription"] = description
605
resp = &CreateSecurityGroupResp{}
606
err = ec2.query(params, resp)
614
// SecurityGroupsResp represents a response to a DescribeSecurityGroups
617
// See http://goo.gl/k12Uy for more details.
618
type SecurityGroupsResp struct {
619
RequestId string `xml:"requestId"`
620
Groups []SecurityGroupInfo `xml:"securityGroupInfo>item"`
623
// SecurityGroup encapsulates details for a security group in EC2.
625
// See http://goo.gl/CIdyP for more details.
626
type SecurityGroupInfo struct {
628
OwnerId string `xml:"ownerId"`
629
Description string `xml:"groupDescription"`
630
IPPerms []IPPerm `xml:"ipPermissions>item"`
633
// IPPerm represents an allowance within an EC2 security group.
635
// See http://goo.gl/4oTxv for more details.
637
Protocol string `xml:"ipProtocol"`
638
FromPort int `xml:"fromPort"`
639
ToPort int `xml:"toPort"`
640
SourceIPs []string `xml:"ipRanges>item>cidrIp"`
641
SourceGroups []UserSecurityGroup `xml:"groups>item"`
644
// UserSecurityGroup holds a security group and the owner
646
type UserSecurityGroup struct {
647
Id string `xml:"groupId"`
648
Name string `xml:"groupName"`
649
OwnerId string `xml:"userId"`
652
// SecurityGroup represents an EC2 security group.
653
// If SecurityGroup is used as a parameter, then one of Id or Name
654
// may be empty. If both are set, then Id is used.
655
type SecurityGroup struct {
656
Id string `xml:"groupId"`
657
Name string `xml:"groupName"`
660
// SecurityGroupNames is a convenience function that
661
// returns a slice of security groups with the given names.
662
func SecurityGroupNames(names ...string) []SecurityGroup {
663
g := make([]SecurityGroup, len(names))
664
for i, name := range names {
665
g[i] = SecurityGroup{Name: name}
670
// SecurityGroupNames is a convenience function that
671
// returns a slice of security groups with the given ids.
672
func SecurityGroupIds(ids ...string) []SecurityGroup {
673
g := make([]SecurityGroup, len(ids))
674
for i, id := range ids {
675
g[i] = SecurityGroup{Id: id}
680
// SecurityGroups returns details about security groups in EC2. Both parameters
681
// are optional, and if provided will limit the security groups returned to those
682
// matching the given groups or filtering rules.
684
// See http://goo.gl/k12Uy for more details.
685
func (ec2 *EC2) SecurityGroups(groups []SecurityGroup, filter *Filter) (resp *SecurityGroupsResp, err error) {
686
params := makeParams("DescribeSecurityGroups")
688
for _, g := range groups {
690
params["GroupId."+strconv.Itoa(i)] = g.Id
693
params["GroupName."+strconv.Itoa(j)] = g.Name
697
filter.addParams(params)
699
resp = &SecurityGroupsResp{}
700
err = ec2.query(params, resp)
707
// DeleteSecurityGroup removes the given security group in EC2.
709
// See http://goo.gl/QJJDO for more details.
710
func (ec2 *EC2) DeleteSecurityGroup(group SecurityGroup) (resp *SimpleResp, err error) {
711
params := makeParams("DeleteSecurityGroup")
713
params["GroupId"] = group.Id
715
params["GroupName"] = group.Name
719
err = ec2.query(params, resp)
726
// AuthorizeSecurityGroup creates an allowance for clients matching the provided
727
// rules to access instances within the given security group.
729
// See http://goo.gl/u2sDJ for more details.
730
func (ec2 *EC2) AuthorizeSecurityGroup(group SecurityGroup, perms []IPPerm) (resp *SimpleResp, err error) {
731
return ec2.authOrRevoke("AuthorizeSecurityGroupIngress", group, perms)
734
// RevokeSecurityGroup revokes permissions from a group.
736
// See http://goo.gl/ZgdxA for more details.
737
func (ec2 *EC2) RevokeSecurityGroup(group SecurityGroup, perms []IPPerm) (resp *SimpleResp, err error) {
738
return ec2.authOrRevoke("RevokeSecurityGroupIngress", group, perms)
741
func (ec2 *EC2) authOrRevoke(op string, group SecurityGroup, perms []IPPerm) (resp *SimpleResp, err error) {
742
params := makeParams(op)
744
params["GroupId"] = group.Id
746
params["GroupName"] = group.Name
749
for i, perm := range perms {
750
prefix := "IpPermissions." + strconv.Itoa(i+1)
751
params[prefix+".IpProtocol"] = perm.Protocol
752
params[prefix+".FromPort"] = strconv.Itoa(perm.FromPort)
753
params[prefix+".ToPort"] = strconv.Itoa(perm.ToPort)
754
for j, ip := range perm.SourceIPs {
755
params[prefix+".IpRanges."+strconv.Itoa(j+1)+".CidrIp"] = ip
757
for j, g := range perm.SourceGroups {
758
subprefix := prefix + ".Groups." + strconv.Itoa(j+1)
760
params[subprefix+".UserId"] = g.OwnerId
763
params[subprefix+".GroupId"] = g.Id
765
params[subprefix+".GroupName"] = g.Name
771
err = ec2.query(params, resp)
778
// ResourceTag represents key-value metadata used to classify and organize
781
// See http://goo.gl/bncl3 for more details
783
Key string `xml:"key"`
784
Value string `xml:"value"`
787
// CreateTags adds or overwrites one or more tags for the specified instance ids.
789
// See http://goo.gl/Vmkqc for more details
790
func (ec2 *EC2) CreateTags(instIds []string, tags []Tag) (resp *SimpleResp, err error) {
791
params := makeParams("CreateTags")
792
addParamsList(params, "ResourceId", instIds)
794
for j, tag := range tags {
795
params["Tag."+strconv.Itoa(j+1)+".Key"] = tag.Key
796
params["Tag."+strconv.Itoa(j+1)+".Value"] = tag.Value
800
err = ec2.query(params, resp)
807
// Response to a StartInstances request.
809
// See http://goo.gl/awKeF for more details.
810
type StartInstanceResp struct {
811
RequestId string `xml:"requestId"`
812
StateChanges []InstanceStateChange `xml:"instancesSet>item"`
815
// Response to a StopInstances request.
817
// See http://goo.gl/436dJ for more details.
818
type StopInstanceResp struct {
819
RequestId string `xml:"requestId"`
820
StateChanges []InstanceStateChange `xml:"instancesSet>item"`
823
// StartInstances starts an Amazon EBS-backed AMI that you've previously stopped.
825
// See http://goo.gl/awKeF for more details.
826
func (ec2 *EC2) StartInstances(ids ...string) (resp *StartInstanceResp, err error) {
827
params := makeParams("StartInstances")
828
addParamsList(params, "InstanceId", ids)
829
resp = &StartInstanceResp{}
830
err = ec2.query(params, resp)
837
// StopInstances requests stopping one or more Amazon EBS-backed instances.
839
// See http://goo.gl/436dJ for more details.
840
func (ec2 *EC2) StopInstances(ids ...string) (resp *StopInstanceResp, err error) {
841
params := makeParams("StopInstances")
842
addParamsList(params, "InstanceId", ids)
843
resp = &StopInstanceResp{}
844
err = ec2.query(params, resp)
851
// RebootInstance requests a reboot of one or more instances. This operation is asynchronous;
852
// it only queues a request to reboot the specified instance(s). The operation will succeed
853
// if the instances are valid and belong to you.
855
// Requests to reboot terminated instances are ignored.
857
// See http://goo.gl/baoUf for more details.
858
func (ec2 *EC2) RebootInstances(ids ...string) (resp *SimpleResp, err error) {
859
params := makeParams("RebootInstances")
860
addParamsList(params, "InstanceId", ids)
862
err = ec2.query(params, resp)