Browse Source

backend: Add Amazon VPC backend

The Amazon VPC backend adds routes to a VPC route table. Routing is then
handled by Amazon's infrastructure.
Rohan Singh 10 years ago
parent
commit
f73565cc16
27 changed files with 9719 additions and 0 deletions
  1. 12 0
      Godeps/Godeps.json
  2. 74 0
      Godeps/_workspace/src/github.com/mitchellh/goamz/aws/attempt.go
  3. 57 0
      Godeps/_workspace/src/github.com/mitchellh/goamz/aws/attempt_test.go
  4. 445 0
      Godeps/_workspace/src/github.com/mitchellh/goamz/aws/aws.go
  5. 203 0
      Godeps/_workspace/src/github.com/mitchellh/goamz/aws/aws_test.go
  6. 125 0
      Godeps/_workspace/src/github.com/mitchellh/goamz/aws/client.go
  7. 121 0
      Godeps/_workspace/src/github.com/mitchellh/goamz/aws/client_test.go
  8. 3390 0
      Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/ec2.go
  9. 1501 0
      Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/ec2_test.go
  10. 204 0
      Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/ec2i_test.go
  11. 580 0
      Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/ec2t_test.go
  12. 84 0
      Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/ec2test/filter.go
  13. 993 0
      Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/ec2test/server.go
  14. 22 0
      Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/export_test.go
  15. 1263 0
      Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/responses_test.go
  16. 45 0
      Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/sign.go
  17. 68 0
      Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/sign_test.go
  18. 14 0
      Godeps/_workspace/src/github.com/vaughan0/go-ini/LICENSE
  19. 70 0
      Godeps/_workspace/src/github.com/vaughan0/go-ini/README.md
  20. 123 0
      Godeps/_workspace/src/github.com/vaughan0/go-ini/ini.go
  21. 43 0
      Godeps/_workspace/src/github.com/vaughan0/go-ini/ini_linux_test.go
  22. 89 0
      Godeps/_workspace/src/github.com/vaughan0/go-ini/ini_test.go
  23. 2 0
      Godeps/_workspace/src/github.com/vaughan0/go-ini/test.ini
  24. 10 0
      README.md
  25. 132 0
      backend/awsvpc/awsvpc.go
  26. 46 0
      backend/awsvpc/ec2.go
  27. 3 0
      main.go

+ 12 - 0
Godeps/Godeps.json

@@ -19,6 +19,18 @@
 			"ImportPath": "github.com/golang/glog",
 			"Rev": "d1c4472bf2efd3826f2b5bdcc02d8416798d678c"
 		},
+		{
+			"ImportPath": "github.com/mitchellh/goamz/aws",
+			"Rev": "caaaea8b30ee15616494ee68abd5d8ebbbef05cf"
+		},
+		{
+			"ImportPath": "github.com/mitchellh/goamz/ec2",
+			"Rev": "caaaea8b30ee15616494ee68abd5d8ebbbef05cf"
+		},
+		{
+			"ImportPath": "github.com/vaughan0/go-ini",
+			"Rev": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1"
+		},
 		{
 			"ImportPath": "github.com/vishvananda/netlink",
 			"Rev": "2187ba67a244c1c32f53bf88876e766bbbbcd5e6"

+ 74 - 0
Godeps/_workspace/src/github.com/mitchellh/goamz/aws/attempt.go

@@ -0,0 +1,74 @@
+package aws
+
+import (
+	"time"
+)
+
+// AttemptStrategy represents a strategy for waiting for an action
+// to complete successfully. This is an internal type used by the
+// implementation of other goamz packages.
+type AttemptStrategy struct {
+	Total time.Duration // total duration of attempt.
+	Delay time.Duration // interval between each try in the burst.
+	Min   int           // minimum number of retries; overrides Total
+}
+
+type Attempt struct {
+	strategy AttemptStrategy
+	last     time.Time
+	end      time.Time
+	force    bool
+	count    int
+}
+
+// Start begins a new sequence of attempts for the given strategy.
+func (s AttemptStrategy) Start() *Attempt {
+	now := time.Now()
+	return &Attempt{
+		strategy: s,
+		last:     now,
+		end:      now.Add(s.Total),
+		force:    true,
+	}
+}
+
+// Next waits until it is time to perform the next attempt or returns
+// false if it is time to stop trying.
+func (a *Attempt) Next() bool {
+	now := time.Now()
+	sleep := a.nextSleep(now)
+	if !a.force && !now.Add(sleep).Before(a.end) && a.strategy.Min <= a.count {
+		return false
+	}
+	a.force = false
+	if sleep > 0 && a.count > 0 {
+		time.Sleep(sleep)
+		now = time.Now()
+	}
+	a.count++
+	a.last = now
+	return true
+}
+
+func (a *Attempt) nextSleep(now time.Time) time.Duration {
+	sleep := a.strategy.Delay - now.Sub(a.last)
+	if sleep < 0 {
+		return 0
+	}
+	return sleep
+}
+
+// HasNext returns whether another attempt will be made if the current
+// one fails. If it returns true, the following call to Next is
+// guaranteed to return true.
+func (a *Attempt) HasNext() bool {
+	if a.force || a.strategy.Min > a.count {
+		return true
+	}
+	now := time.Now()
+	if now.Add(a.nextSleep(now)).Before(a.end) {
+		a.force = true
+		return true
+	}
+	return false
+}

+ 57 - 0
Godeps/_workspace/src/github.com/mitchellh/goamz/aws/attempt_test.go

@@ -0,0 +1,57 @@
+package aws_test
+
+import (
+	"github.com/coreos/flannel/Godeps/_workspace/src/github.com/mitchellh/goamz/aws"
+	. "github.com/motain/gocheck"
+	"time"
+)
+
+func (S) TestAttemptTiming(c *C) {
+	testAttempt := aws.AttemptStrategy{
+		Total: 0.25e9,
+		Delay: 0.1e9,
+	}
+	want := []time.Duration{0, 0.1e9, 0.2e9, 0.2e9}
+	got := make([]time.Duration, 0, len(want)) // avoid allocation when testing timing
+	t0 := time.Now()
+	for a := testAttempt.Start(); a.Next(); {
+		got = append(got, time.Now().Sub(t0))
+	}
+	got = append(got, time.Now().Sub(t0))
+	c.Assert(got, HasLen, len(want))
+	const margin = 0.01e9
+	for i, got := range want {
+		lo := want[i] - margin
+		hi := want[i] + margin
+		if got < lo || got > hi {
+			c.Errorf("attempt %d want %g got %g", i, want[i].Seconds(), got.Seconds())
+		}
+	}
+}
+
+func (S) TestAttemptNextHasNext(c *C) {
+	a := aws.AttemptStrategy{}.Start()
+	c.Assert(a.Next(), Equals, true)
+	c.Assert(a.Next(), Equals, false)
+
+	a = aws.AttemptStrategy{}.Start()
+	c.Assert(a.Next(), Equals, true)
+	c.Assert(a.HasNext(), Equals, false)
+	c.Assert(a.Next(), Equals, false)
+
+	a = aws.AttemptStrategy{Total: 2e8}.Start()
+	c.Assert(a.Next(), Equals, true)
+	c.Assert(a.HasNext(), Equals, true)
+	time.Sleep(2e8)
+	c.Assert(a.HasNext(), Equals, true)
+	c.Assert(a.Next(), Equals, true)
+	c.Assert(a.Next(), Equals, false)
+
+	a = aws.AttemptStrategy{Total: 1e8, Min: 2}.Start()
+	time.Sleep(1e8)
+	c.Assert(a.Next(), Equals, true)
+	c.Assert(a.HasNext(), Equals, true)
+	c.Assert(a.Next(), Equals, true)
+	c.Assert(a.HasNext(), Equals, false)
+	c.Assert(a.Next(), Equals, false)
+}

+ 445 - 0
Godeps/_workspace/src/github.com/mitchellh/goamz/aws/aws.go

@@ -0,0 +1,445 @@
+//
+// goamz - Go packages to interact with the Amazon Web Services.
+//
+//   https://wiki.ubuntu.com/goamz
+//
+// Copyright (c) 2011 Canonical Ltd.
+//
+// Written by Gustavo Niemeyer <gustavo.niemeyer@canonical.com>
+//
+package aws
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"os"
+
+	"github.com/coreos/flannel/Godeps/_workspace/src/github.com/vaughan0/go-ini"
+)
+
+// Region defines the URLs where AWS services may be accessed.
+//
+// See http://goo.gl/d8BP1 for more details.
+type Region struct {
+	Name                 string // the canonical name of this region.
+	EC2Endpoint          string
+	S3Endpoint           string
+	S3BucketEndpoint     string // Not needed by AWS S3. Use ${bucket} for bucket name.
+	S3LocationConstraint bool   // true if this region requires a LocationConstraint declaration.
+	S3LowercaseBucket    bool   // true if the region requires bucket names to be lower case.
+	SDBEndpoint          string
+	SNSEndpoint          string
+	SQSEndpoint          string
+	IAMEndpoint          string
+	ELBEndpoint          string
+	AutoScalingEndpoint  string
+	RdsEndpoint          string
+	Route53Endpoint      string
+}
+
+var USGovWest = Region{
+	"us-gov-west-1",
+	"https://ec2.us-gov-west-1.amazonaws.com",
+	"https://s3-fips-us-gov-west-1.amazonaws.com",
+	"",
+	true,
+	true,
+	"",
+	"https://sns.us-gov-west-1.amazonaws.com",
+	"https://sqs.us-gov-west-1.amazonaws.com",
+	"https://iam.us-gov.amazonaws.com",
+	"https://elasticloadbalancing.us-gov-west-1.amazonaws.com",
+	"https://autoscaling.us-gov-west-1.amazonaws.com",
+	"https://rds.us-gov-west-1.amazonaws.com",
+	"https://route53.amazonaws.com",
+}
+
+var USEast = Region{
+	"us-east-1",
+	"https://ec2.us-east-1.amazonaws.com",
+	"https://s3.amazonaws.com",
+	"",
+	false,
+	false,
+	"https://sdb.amazonaws.com",
+	"https://sns.us-east-1.amazonaws.com",
+	"https://sqs.us-east-1.amazonaws.com",
+	"https://iam.amazonaws.com",
+	"https://elasticloadbalancing.us-east-1.amazonaws.com",
+	"https://autoscaling.us-east-1.amazonaws.com",
+	"https://rds.us-east-1.amazonaws.com",
+	"https://route53.amazonaws.com",
+}
+
+var USWest = Region{
+	"us-west-1",
+	"https://ec2.us-west-1.amazonaws.com",
+	"https://s3-us-west-1.amazonaws.com",
+	"",
+	true,
+	true,
+	"https://sdb.us-west-1.amazonaws.com",
+	"https://sns.us-west-1.amazonaws.com",
+	"https://sqs.us-west-1.amazonaws.com",
+	"https://iam.amazonaws.com",
+	"https://elasticloadbalancing.us-west-1.amazonaws.com",
+	"https://autoscaling.us-west-1.amazonaws.com",
+	"https://rds.us-west-1.amazonaws.com",
+	"https://route53.amazonaws.com",
+}
+
+var USWest2 = Region{
+	"us-west-2",
+	"https://ec2.us-west-2.amazonaws.com",
+	"https://s3-us-west-2.amazonaws.com",
+	"",
+	true,
+	true,
+	"https://sdb.us-west-2.amazonaws.com",
+	"https://sns.us-west-2.amazonaws.com",
+	"https://sqs.us-west-2.amazonaws.com",
+	"https://iam.amazonaws.com",
+	"https://elasticloadbalancing.us-west-2.amazonaws.com",
+	"https://autoscaling.us-west-2.amazonaws.com",
+	"https://rds.us-west-2.amazonaws.com",
+	"https://route53.amazonaws.com",
+}
+
+var EUWest = Region{
+	"eu-west-1",
+	"https://ec2.eu-west-1.amazonaws.com",
+	"https://s3-eu-west-1.amazonaws.com",
+	"",
+	true,
+	true,
+	"https://sdb.eu-west-1.amazonaws.com",
+	"https://sns.eu-west-1.amazonaws.com",
+	"https://sqs.eu-west-1.amazonaws.com",
+	"https://iam.amazonaws.com",
+	"https://elasticloadbalancing.eu-west-1.amazonaws.com",
+	"https://autoscaling.eu-west-1.amazonaws.com",
+	"https://rds.eu-west-1.amazonaws.com",
+	"https://route53.amazonaws.com",
+}
+
+var EUCentral = Region{
+	"eu-central-1",
+	"https://ec2.eu-central-1.amazonaws.com",
+	"https://s3-eu-central-1.amazonaws.com",
+	"",
+	true,
+	true,
+	"",
+	"https://sns.eu-central-1.amazonaws.com",
+	"https://sqs.eu-central-1.amazonaws.com",
+	"https://iam.amazonaws.com",
+	"https://elasticloadbalancing.eu-central-1.amazonaws.com",
+	"https://autoscaling.eu-central-1.amazonaws.com",
+	"https://rds.eu-central-1.amazonaws.com",
+	"https://route53.amazonaws.com",
+}
+
+var APSoutheast = Region{
+	"ap-southeast-1",
+	"https://ec2.ap-southeast-1.amazonaws.com",
+	"https://s3-ap-southeast-1.amazonaws.com",
+	"",
+	true,
+	true,
+	"https://sdb.ap-southeast-1.amazonaws.com",
+	"https://sns.ap-southeast-1.amazonaws.com",
+	"https://sqs.ap-southeast-1.amazonaws.com",
+	"https://iam.amazonaws.com",
+	"https://elasticloadbalancing.ap-southeast-1.amazonaws.com",
+	"https://autoscaling.ap-southeast-1.amazonaws.com",
+	"https://rds.ap-southeast-1.amazonaws.com",
+	"https://route53.amazonaws.com",
+}
+
+var APSoutheast2 = Region{
+	"ap-southeast-2",
+	"https://ec2.ap-southeast-2.amazonaws.com",
+	"https://s3-ap-southeast-2.amazonaws.com",
+	"",
+	true,
+	true,
+	"https://sdb.ap-southeast-2.amazonaws.com",
+	"https://sns.ap-southeast-2.amazonaws.com",
+	"https://sqs.ap-southeast-2.amazonaws.com",
+	"https://iam.amazonaws.com",
+	"https://elasticloadbalancing.ap-southeast-2.amazonaws.com",
+	"https://autoscaling.ap-southeast-2.amazonaws.com",
+	"https://rds.ap-southeast-2.amazonaws.com",
+	"https://route53.amazonaws.com",
+}
+
+var APNortheast = Region{
+	"ap-northeast-1",
+	"https://ec2.ap-northeast-1.amazonaws.com",
+	"https://s3-ap-northeast-1.amazonaws.com",
+	"",
+	true,
+	true,
+	"https://sdb.ap-northeast-1.amazonaws.com",
+	"https://sns.ap-northeast-1.amazonaws.com",
+	"https://sqs.ap-northeast-1.amazonaws.com",
+	"https://iam.amazonaws.com",
+	"https://elasticloadbalancing.ap-northeast-1.amazonaws.com",
+	"https://autoscaling.ap-northeast-1.amazonaws.com",
+	"https://rds.ap-northeast-1.amazonaws.com",
+	"https://route53.amazonaws.com",
+}
+
+var SAEast = Region{
+	"sa-east-1",
+	"https://ec2.sa-east-1.amazonaws.com",
+	"https://s3-sa-east-1.amazonaws.com",
+	"",
+	true,
+	true,
+	"https://sdb.sa-east-1.amazonaws.com",
+	"https://sns.sa-east-1.amazonaws.com",
+	"https://sqs.sa-east-1.amazonaws.com",
+	"https://iam.amazonaws.com",
+	"https://elasticloadbalancing.sa-east-1.amazonaws.com",
+	"https://autoscaling.sa-east-1.amazonaws.com",
+	"https://rds.sa-east-1.amazonaws.com",
+	"https://route53.amazonaws.com",
+}
+
+var CNNorth = Region{
+	"cn-north-1",
+	"https://ec2.cn-north-1.amazonaws.com.cn",
+	"https://s3.cn-north-1.amazonaws.com.cn",
+	"",
+	true,
+	true,
+	"",
+	"https://sns.cn-north-1.amazonaws.com.cn",
+	"https://sqs.cn-north-1.amazonaws.com.cn",
+	"https://iam.cn-north-1.amazonaws.com.cn",
+	"https://elasticloadbalancing.cn-north-1.amazonaws.com.cn",
+	"https://autoscaling.cn-north-1.amazonaws.com.cn",
+	"https://rds.cn-north-1.amazonaws.com.cn",
+	"https://route53.amazonaws.com",
+}
+
+var Regions = map[string]Region{
+	APNortheast.Name:  APNortheast,
+	APSoutheast.Name:  APSoutheast,
+	APSoutheast2.Name: APSoutheast2,
+	EUWest.Name:       EUWest,
+	EUCentral.Name:    EUCentral,
+	USEast.Name:       USEast,
+	USWest.Name:       USWest,
+	USWest2.Name:      USWest2,
+	SAEast.Name:       SAEast,
+	USGovWest.Name:    USGovWest,
+	CNNorth.Name:      CNNorth,
+}
+
+type Auth struct {
+	AccessKey, SecretKey, Token string
+}
+
+var unreserved = make([]bool, 128)
+var hex = "0123456789ABCDEF"
+
+func init() {
+	// RFC3986
+	u := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890-_.~"
+	for _, c := range u {
+		unreserved[c] = true
+	}
+}
+
+type credentials struct {
+	Code            string
+	LastUpdated     string
+	Type            string
+	AccessKeyId     string
+	SecretAccessKey string
+	Token           string
+	Expiration      string
+}
+
+// GetMetaData retrieves instance metadata about the current machine.
+//
+// See http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AESDG-chapter-instancedata.html for more details.
+func GetMetaData(path string) (contents []byte, err error) {
+	url := "http://169.254.169.254/latest/meta-data/" + path
+
+	resp, err := RetryingClient.Get(url)
+	if err != nil {
+		return
+	}
+	defer resp.Body.Close()
+
+	if resp.StatusCode != 200 {
+		err = fmt.Errorf("Code %d returned for url %s", resp.StatusCode, url)
+		return
+	}
+
+	body, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return
+	}
+	return []byte(body), err
+}
+
+func getInstanceCredentials() (cred credentials, err error) {
+	credentialPath := "iam/security-credentials/"
+
+	// Get the instance role
+	role, err := GetMetaData(credentialPath)
+	if err != nil {
+		return
+	}
+
+	// Get the instance role credentials
+	credentialJSON, err := GetMetaData(credentialPath + string(role))
+	if err != nil {
+		return
+	}
+
+	err = json.Unmarshal([]byte(credentialJSON), &cred)
+	return
+}
+
+// GetAuth creates an Auth based on either passed in credentials,
+// environment information or instance based role credentials.
+func GetAuth(accessKey string, secretKey string) (auth Auth, err error) {
+	// First try passed in credentials
+	if accessKey != "" && secretKey != "" {
+		return Auth{accessKey, secretKey, ""}, nil
+	}
+
+	// Next try to get auth from the environment
+	auth, err = SharedAuth()
+	if err == nil {
+		// Found auth, return
+		return
+	}
+
+	// Next try to get auth from the environment
+	auth, err = EnvAuth()
+	if err == nil {
+		// Found auth, return
+		return
+	}
+
+	// Next try getting auth from the instance role
+	cred, err := getInstanceCredentials()
+	if err == nil {
+		// Found auth, return
+		auth.AccessKey = cred.AccessKeyId
+		auth.SecretKey = cred.SecretAccessKey
+		auth.Token = cred.Token
+		return
+	}
+	err = errors.New("No valid AWS authentication found")
+	return
+}
+
+// SharedAuth creates an Auth based on shared credentials stored in
+// $HOME/.aws/credentials. The AWS_PROFILE environment variables is used to
+// select the profile.
+func SharedAuth() (auth Auth, err error) {
+	var profileName = os.Getenv("AWS_PROFILE")
+
+	if profileName == "" {
+		profileName = "default"
+	}
+
+	var credentialsFile = os.Getenv("AWS_CREDENTIAL_FILE")
+	if credentialsFile == "" {
+		var homeDir = os.Getenv("HOME")
+		if homeDir == "" {
+			err = errors.New("Could not get HOME")
+			return
+		}
+		credentialsFile = homeDir + "/.aws/credentials"
+	}
+
+	file, err := ini.LoadFile(credentialsFile)
+	if err != nil {
+		err = errors.New("Couldn't parse AWS credentials file")
+		return
+	}
+
+	var profile = file[profileName]
+	if profile == nil {
+		err = errors.New("Couldn't find profile in AWS credentials file")
+		return
+	}
+
+	auth.AccessKey = profile["aws_access_key_id"]
+	auth.SecretKey = profile["aws_secret_access_key"]
+
+	if auth.AccessKey == "" {
+		err = errors.New("AWS_ACCESS_KEY_ID not found in environment in credentials file")
+	}
+	if auth.SecretKey == "" {
+		err = errors.New("AWS_SECRET_ACCESS_KEY not found in credentials file")
+	}
+	return
+}
+
+// EnvAuth creates an Auth based on environment information.
+// The AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment
+// For accounts that require a security token, it is read from AWS_SECURITY_TOKEN
+// variables are used.
+func EnvAuth() (auth Auth, err error) {
+	auth.AccessKey = os.Getenv("AWS_ACCESS_KEY_ID")
+	if auth.AccessKey == "" {
+		auth.AccessKey = os.Getenv("AWS_ACCESS_KEY")
+	}
+
+	auth.SecretKey = os.Getenv("AWS_SECRET_ACCESS_KEY")
+	if auth.SecretKey == "" {
+		auth.SecretKey = os.Getenv("AWS_SECRET_KEY")
+	}
+
+	auth.Token = os.Getenv("AWS_SECURITY_TOKEN")
+
+	if auth.AccessKey == "" {
+		err = errors.New("AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY not found in environment")
+	}
+	if auth.SecretKey == "" {
+		err = errors.New("AWS_SECRET_ACCESS_KEY or AWS_SECRET_KEY not found in environment")
+	}
+	return
+}
+
+// Encode takes a string and URI-encodes it in a way suitable
+// to be used in AWS signatures.
+func Encode(s string) string {
+	encode := false
+	for i := 0; i != len(s); i++ {
+		c := s[i]
+		if c > 127 || !unreserved[c] {
+			encode = true
+			break
+		}
+	}
+	if !encode {
+		return s
+	}
+	e := make([]byte, len(s)*3)
+	ei := 0
+	for i := 0; i != len(s); i++ {
+		c := s[i]
+		if c > 127 || !unreserved[c] {
+			e[ei] = '%'
+			e[ei+1] = hex[c>>4]
+			e[ei+2] = hex[c&0xF]
+			ei += 3
+		} else {
+			e[ei] = c
+			ei += 1
+		}
+	}
+	return string(e[:ei])
+}

+ 203 - 0
Godeps/_workspace/src/github.com/mitchellh/goamz/aws/aws_test.go

@@ -0,0 +1,203 @@
+package aws_test
+
+import (
+	"github.com/coreos/flannel/Godeps/_workspace/src/github.com/mitchellh/goamz/aws"
+	. "github.com/motain/gocheck"
+	"io/ioutil"
+	"os"
+	"strings"
+	"testing"
+)
+
+func Test(t *testing.T) {
+	TestingT(t)
+}
+
+var _ = Suite(&S{})
+
+type S struct {
+	environ []string
+}
+
+func (s *S) SetUpSuite(c *C) {
+	s.environ = os.Environ()
+}
+
+func (s *S) TearDownTest(c *C) {
+	os.Clearenv()
+	for _, kv := range s.environ {
+		l := strings.SplitN(kv, "=", 2)
+		os.Setenv(l[0], l[1])
+	}
+}
+
+func (s *S) TestSharedAuthNoHome(c *C) {
+	os.Clearenv()
+	os.Setenv("AWS_PROFILE", "foo")
+	_, err := aws.SharedAuth()
+	c.Assert(err, ErrorMatches, "Could not get HOME")
+}
+
+func (s *S) TestSharedAuthNoCredentialsFile(c *C) {
+	os.Clearenv()
+	os.Setenv("AWS_PROFILE", "foo")
+	os.Setenv("HOME", "/tmp")
+	_, err := aws.SharedAuth()
+	c.Assert(err, ErrorMatches, "Couldn't parse AWS credentials file")
+}
+
+func (s *S) TestSharedAuthNoProfileInFile(c *C) {
+	os.Clearenv()
+	os.Setenv("AWS_PROFILE", "foo")
+
+	d, err := ioutil.TempDir("", "")
+	if err != nil {
+		panic(err)
+	}
+	defer os.RemoveAll(d)
+
+	err = os.Mkdir(d+"/.aws", 0755)
+	if err != nil {
+		panic(err)
+	}
+
+	ioutil.WriteFile(d+"/.aws/credentials", []byte("[bar]\n"), 0644)
+	os.Setenv("HOME", d)
+
+	_, err = aws.SharedAuth()
+	c.Assert(err, ErrorMatches, "Couldn't find profile in AWS credentials file")
+}
+
+func (s *S) TestSharedAuthNoKeysInProfile(c *C) {
+	os.Clearenv()
+	os.Setenv("AWS_PROFILE", "bar")
+
+	d, err := ioutil.TempDir("", "")
+	if err != nil {
+		panic(err)
+	}
+	defer os.RemoveAll(d)
+
+	err = os.Mkdir(d+"/.aws", 0755)
+	if err != nil {
+		panic(err)
+	}
+
+	ioutil.WriteFile(d+"/.aws/credentials", []byte("[bar]\nawsaccesskeyid = AK.."), 0644)
+	os.Setenv("HOME", d)
+
+	_, err = aws.SharedAuth()
+	c.Assert(err, ErrorMatches, "AWS_SECRET_ACCESS_KEY not found in credentials file")
+}
+
+func (s *S) TestSharedAuthDefaultCredentials(c *C) {
+	os.Clearenv()
+
+	d, err := ioutil.TempDir("", "")
+	if err != nil {
+		panic(err)
+	}
+	defer os.RemoveAll(d)
+
+	err = os.Mkdir(d+"/.aws", 0755)
+	if err != nil {
+		panic(err)
+	}
+
+	ioutil.WriteFile(d+"/.aws/credentials", []byte("[default]\naws_access_key_id = access\naws_secret_access_key = secret\n"), 0644)
+	os.Setenv("HOME", d)
+
+	auth, err := aws.SharedAuth()
+	c.Assert(err, IsNil)
+	c.Assert(auth, Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"})
+}
+
+func (s *S) TestSharedAuth(c *C) {
+	os.Clearenv()
+	os.Setenv("AWS_PROFILE", "bar")
+
+	d, err := ioutil.TempDir("", "")
+	if err != nil {
+		panic(err)
+	}
+	defer os.RemoveAll(d)
+
+	err = os.Mkdir(d+"/.aws", 0755)
+	if err != nil {
+		panic(err)
+	}
+
+	ioutil.WriteFile(d+"/.aws/credentials", []byte("[bar]\naws_access_key_id = access\naws_secret_access_key = secret\n"), 0644)
+	os.Setenv("HOME", d)
+
+	auth, err := aws.SharedAuth()
+	c.Assert(err, IsNil)
+	c.Assert(auth, Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"})
+}
+
+func (s *S) TestEnvAuthNoSecret(c *C) {
+	os.Clearenv()
+	_, err := aws.EnvAuth()
+	c.Assert(err, ErrorMatches, "AWS_SECRET_ACCESS_KEY or AWS_SECRET_KEY not found in environment")
+}
+
+func (s *S) TestEnvAuthNoAccess(c *C) {
+	os.Clearenv()
+	os.Setenv("AWS_SECRET_ACCESS_KEY", "foo")
+	_, err := aws.EnvAuth()
+	c.Assert(err, ErrorMatches, "AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY not found in environment")
+}
+
+func (s *S) TestEnvAuth(c *C) {
+	os.Clearenv()
+	os.Setenv("AWS_SECRET_ACCESS_KEY", "secret")
+	os.Setenv("AWS_ACCESS_KEY_ID", "access")
+	auth, err := aws.EnvAuth()
+	c.Assert(err, IsNil)
+	c.Assert(auth, Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"})
+}
+
+func (s *S) TestEnvAuthWithToken(c *C) {
+	os.Clearenv()
+	os.Setenv("AWS_SECRET_ACCESS_KEY", "secret")
+	os.Setenv("AWS_ACCESS_KEY_ID", "access")
+	os.Setenv("AWS_SECURITY_TOKEN", "token")
+	auth, err := aws.EnvAuth()
+	c.Assert(err, IsNil)
+	c.Assert(auth, Equals, aws.Auth{SecretKey: "secret", AccessKey: "access", Token: "token"})
+}
+
+func (s *S) TestEnvAuthAlt(c *C) {
+	os.Clearenv()
+	os.Setenv("AWS_SECRET_KEY", "secret")
+	os.Setenv("AWS_ACCESS_KEY", "access")
+	auth, err := aws.EnvAuth()
+	c.Assert(err, IsNil)
+	c.Assert(auth, Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"})
+}
+
+func (s *S) TestGetAuthStatic(c *C) {
+	auth, err := aws.GetAuth("access", "secret")
+	c.Assert(err, IsNil)
+	c.Assert(auth, Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"})
+}
+
+func (s *S) TestGetAuthEnv(c *C) {
+	os.Clearenv()
+	os.Setenv("AWS_SECRET_ACCESS_KEY", "secret")
+	os.Setenv("AWS_ACCESS_KEY_ID", "access")
+	auth, err := aws.GetAuth("", "")
+	c.Assert(err, IsNil)
+	c.Assert(auth, Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"})
+}
+
+func (s *S) TestEncode(c *C) {
+	c.Assert(aws.Encode("foo"), Equals, "foo")
+	c.Assert(aws.Encode("/"), Equals, "%2F")
+}
+
+func (s *S) TestRegionsAreNamed(c *C) {
+	for n, r := range aws.Regions {
+		c.Assert(n, Equals, r.Name)
+	}
+}

+ 125 - 0
Godeps/_workspace/src/github.com/mitchellh/goamz/aws/client.go

@@ -0,0 +1,125 @@
+package aws
+
+import (
+	"math"
+	"net"
+	"net/http"
+	"time"
+)
+
+type RetryableFunc func(*http.Request, *http.Response, error) bool
+type WaitFunc func(try int)
+type DeadlineFunc func() time.Time
+
+type ResilientTransport struct {
+	// Timeout is the maximum amount of time a dial will wait for
+	// a connect to complete.
+	//
+	// The default is no timeout.
+	//
+	// With or without a timeout, the operating system may impose
+	// its own earlier timeout. For instance, TCP timeouts are
+	// often around 3 minutes.
+	DialTimeout time.Duration
+
+	// MaxTries, if non-zero, specifies the number of times we will retry on
+	// failure. Retries are only attempted for temporary network errors or known
+	// safe failures.
+	MaxTries    int
+	Deadline    DeadlineFunc
+	ShouldRetry RetryableFunc
+	Wait        WaitFunc
+	transport   *http.Transport
+}
+
+// Convenience method for creating an http client
+func NewClient(rt *ResilientTransport) *http.Client {
+	rt.transport = &http.Transport{
+		Dial: func(netw, addr string) (net.Conn, error) {
+			c, err := net.DialTimeout(netw, addr, rt.DialTimeout)
+			if err != nil {
+				return nil, err
+			}
+			c.SetDeadline(rt.Deadline())
+			return c, nil
+		},
+		DisableKeepAlives: true,
+		Proxy:             http.ProxyFromEnvironment,
+	}
+	// TODO: Would be nice is ResilientTransport allowed clients to initialize
+	// with http.Transport attributes.
+	return &http.Client{
+		Transport: rt,
+	}
+}
+
+var retryingTransport = &ResilientTransport{
+	Deadline: func() time.Time {
+		return time.Now().Add(5 * time.Second)
+	},
+	DialTimeout: 10 * time.Second,
+	MaxTries:    3,
+	ShouldRetry: awsRetry,
+	Wait:        ExpBackoff,
+}
+
+// Exported default client
+var RetryingClient = NewClient(retryingTransport)
+
+func (t *ResilientTransport) RoundTrip(req *http.Request) (*http.Response, error) {
+	return t.tries(req)
+}
+
+// Retry a request a maximum of t.MaxTries times.
+// We'll only retry if the proper criteria are met.
+// If a wait function is specified, wait that amount of time
+// In between requests.
+func (t *ResilientTransport) tries(req *http.Request) (res *http.Response, err error) {
+	for try := 0; try < t.MaxTries; try += 1 {
+		res, err = t.transport.RoundTrip(req)
+
+		if !t.ShouldRetry(req, res, err) {
+			break
+		}
+		if res != nil {
+			res.Body.Close()
+		}
+		if t.Wait != nil {
+			t.Wait(try)
+		}
+	}
+
+	return
+}
+
+func ExpBackoff(try int) {
+	time.Sleep(100 * time.Millisecond *
+		time.Duration(math.Exp2(float64(try))))
+}
+
+func LinearBackoff(try int) {
+	time.Sleep(time.Duration(try*100) * time.Millisecond)
+}
+
+// Decide if we should retry a request.
+// In general, the criteria for retrying a request is described here
+// http://docs.aws.amazon.com/general/latest/gr/api-retries.html
+func awsRetry(req *http.Request, res *http.Response, err error) bool {
+	retry := false
+
+	// Retry if there's a temporary network error.
+	if neterr, ok := err.(net.Error); ok {
+		if neterr.Temporary() {
+			retry = true
+		}
+	}
+
+	// Retry if we get a 5xx series error.
+	if res != nil {
+		if res.StatusCode >= 500 && res.StatusCode < 600 {
+			retry = true
+		}
+	}
+
+	return retry
+}

+ 121 - 0
Godeps/_workspace/src/github.com/mitchellh/goamz/aws/client_test.go

@@ -0,0 +1,121 @@
+package aws_test
+
+import (
+	"fmt"
+	"github.com/coreos/flannel/Godeps/_workspace/src/github.com/mitchellh/goamz/aws"
+	"io/ioutil"
+	"net/http"
+	"net/http/httptest"
+	"strings"
+	"testing"
+	"time"
+)
+
+// Retrieve the response from handler using aws.RetryingClient
+func serveAndGet(handler http.HandlerFunc) (body string, err error) {
+	ts := httptest.NewServer(handler)
+	defer ts.Close()
+	resp, err := aws.RetryingClient.Get(ts.URL)
+	if err != nil {
+		return
+	}
+	if resp.StatusCode != 200 {
+		return "", fmt.Errorf("Bad status code: %d", resp.StatusCode)
+	}
+	greeting, err := ioutil.ReadAll(resp.Body)
+	resp.Body.Close()
+	if err != nil {
+		return
+	}
+	return strings.TrimSpace(string(greeting)), nil
+}
+
+func TestClient_expected(t *testing.T) {
+	body := "foo bar"
+
+	resp, err := serveAndGet(func(w http.ResponseWriter, r *http.Request) {
+		fmt.Fprintln(w, body)
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+	if resp != body {
+		t.Fatal("Body not as expected.")
+	}
+}
+
+func TestClient_delay(t *testing.T) {
+	body := "baz"
+	wait := 4
+	resp, err := serveAndGet(func(w http.ResponseWriter, r *http.Request) {
+		if wait < 0 {
+			// If we dipped to zero delay and still failed.
+			t.Fatal("Never succeeded.")
+		}
+		wait -= 1
+		time.Sleep(time.Second * time.Duration(wait))
+		fmt.Fprintln(w, body)
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+	if resp != body {
+		t.Fatal("Body not as expected.", resp)
+	}
+}
+
+func TestClient_no4xxRetry(t *testing.T) {
+	tries := 0
+
+	// Fail once before succeeding.
+	_, err := serveAndGet(func(w http.ResponseWriter, r *http.Request) {
+		tries += 1
+		http.Error(w, "error", 404)
+	})
+
+	if err == nil {
+		t.Fatal("should have error")
+	}
+
+	if tries != 1 {
+		t.Fatalf("should only try once: %d", tries)
+	}
+}
+
+func TestClient_retries(t *testing.T) {
+	body := "biz"
+	failed := false
+	// Fail once before succeeding.
+	resp, err := serveAndGet(func(w http.ResponseWriter, r *http.Request) {
+		if !failed {
+			http.Error(w, "error", 500)
+			failed = true
+		} else {
+			fmt.Fprintln(w, body)
+		}
+	})
+	if failed != true {
+		t.Error("We didn't retry!")
+	}
+	if err != nil {
+		t.Fatal(err)
+	}
+	if resp != body {
+		t.Fatal("Body not as expected.")
+	}
+}
+
+func TestClient_fails(t *testing.T) {
+	tries := 0
+	// Fail 3 times and return the last error.
+	_, err := serveAndGet(func(w http.ResponseWriter, r *http.Request) {
+		tries += 1
+		http.Error(w, "error", 500)
+	})
+	if err == nil {
+		t.Fatal(err)
+	}
+	if tries != 3 {
+		t.Fatal("Didn't retry enough")
+	}
+}

+ 3390 - 0
Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/ec2.go

@@ -0,0 +1,3390 @@
+//
+// goamz - Go packages to interact with the Amazon Web Services.
+//
+//   https://wiki.ubuntu.com/goamz
+//
+// Copyright (c) 2011 Canonical Ltd.
+//
+// Written by Gustavo Niemeyer <gustavo.niemeyer@canonical.com>
+//
+
+package ec2
+
+import (
+	"crypto/rand"
+	"encoding/base64"
+	"encoding/hex"
+	"encoding/xml"
+	"fmt"
+	"log"
+	"net/http"
+	"net/http/httputil"
+	"net/url"
+	"sort"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/coreos/flannel/Godeps/_workspace/src/github.com/mitchellh/goamz/aws"
+)
+
+const debug = false
+
+// The EC2 type encapsulates operations with a specific EC2 region.
+type EC2 struct {
+	aws.Auth
+	aws.Region
+	httpClient *http.Client
+	private    byte // Reserve the right of using private data.
+}
+
+// New creates a new EC2.
+func NewWithClient(auth aws.Auth, region aws.Region, client *http.Client) *EC2 {
+	return &EC2{auth, region, client, 0}
+}
+
+func New(auth aws.Auth, region aws.Region) *EC2 {
+	return NewWithClient(auth, region, aws.RetryingClient)
+}
+
+// ----------------------------------------------------------------------------
+// Filtering helper.
+
+// Filter builds filtering parameters to be used in an EC2 query which supports
+// filtering.  For example:
+//
+//     filter := NewFilter()
+//     filter.Add("architecture", "i386")
+//     filter.Add("launch-index", "0")
+//     resp, err := ec2.Instances(nil, filter)
+//
+type Filter struct {
+	m map[string][]string
+}
+
+// NewFilter creates a new Filter.
+func NewFilter() *Filter {
+	return &Filter{make(map[string][]string)}
+}
+
+// Add appends a filtering parameter with the given name and value(s).
+func (f *Filter) Add(name string, value ...string) {
+	f.m[name] = append(f.m[name], value...)
+}
+
+func (f *Filter) addParams(params map[string]string) {
+	if f != nil {
+		a := make([]string, len(f.m))
+		i := 0
+		for k := range f.m {
+			a[i] = k
+			i++
+		}
+		sort.StringSlice(a).Sort()
+		for i, k := range a {
+			prefix := "Filter." + strconv.Itoa(i+1)
+			params[prefix+".Name"] = k
+			for j, v := range f.m[k] {
+				params[prefix+".Value."+strconv.Itoa(j+1)] = v
+			}
+		}
+	}
+}
+
+// ----------------------------------------------------------------------------
+// Request dispatching logic.
+
+// Error encapsulates an error returned by EC2.
+//
+// See http://goo.gl/VZGuC for more details.
+type Error struct {
+	// HTTP status code (200, 403, ...)
+	StatusCode int
+	// EC2 error code ("UnsupportedOperation", ...)
+	Code string
+	// The human-oriented error message
+	Message   string
+	RequestId string `xml:"RequestID"`
+}
+
+func (err *Error) Error() string {
+	if err.Code == "" {
+		return err.Message
+	}
+
+	return fmt.Sprintf("%s (%s)", err.Message, err.Code)
+}
+
+// For now a single error inst is being exposed. In the future it may be useful
+// to provide access to all of them, but rather than doing it as an array/slice,
+// use a *next pointer, so that it's backward compatible and it continues to be
+// easy to handle the first error, which is what most people will want.
+type xmlErrors struct {
+	RequestId string  `xml:"RequestID"`
+	Errors    []Error `xml:"Errors>Error"`
+}
+
+var timeNow = time.Now
+
+func (ec2 *EC2) query(params map[string]string, resp interface{}) error {
+	params["Version"] = "2014-06-15"
+	params["Timestamp"] = timeNow().In(time.UTC).Format(time.RFC3339)
+	endpoint, err := url.Parse(ec2.Region.EC2Endpoint)
+	if err != nil {
+		return err
+	}
+	if endpoint.Path == "" {
+		endpoint.Path = "/"
+	}
+	sign(ec2.Auth, "GET", endpoint.Path, params, endpoint.Host)
+	endpoint.RawQuery = multimap(params).Encode()
+	if debug {
+		log.Printf("get { %v } -> {\n", endpoint.String())
+	}
+
+	r, err := ec2.httpClient.Get(endpoint.String())
+	if err != nil {
+		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 multimap(p map[string]string) url.Values {
+	q := make(url.Values, len(p))
+	for k, v := range p {
+		q[k] = []string{v}
+	}
+	return q
+}
+
+func buildError(r *http.Response) error {
+	errors := xmlErrors{}
+	xml.NewDecoder(r.Body).Decode(&errors)
+	var err Error
+	if len(errors.Errors) > 0 {
+		err = errors.Errors[0]
+	}
+	err.RequestId = errors.RequestId
+	err.StatusCode = r.StatusCode
+	if err.Message == "" {
+		err.Message = err.Code
+	}
+	return &err
+}
+
+func makeParams(action string) map[string]string {
+	params := make(map[string]string)
+	params["Action"] = action
+	return params
+}
+
+func addParamsList(params map[string]string, label string, ids []string) {
+	for i, id := range ids {
+		params[label+"."+strconv.Itoa(i+1)] = id
+	}
+}
+
+func addBlockDeviceParams(prename string, params map[string]string, blockdevices []BlockDeviceMapping) {
+	for i, k := range blockdevices {
+		// Fixup index since Amazon counts these from 1
+		prefix := prename + "BlockDeviceMapping." + strconv.Itoa(i+1) + "."
+
+		if k.DeviceName != "" {
+			params[prefix+"DeviceName"] = k.DeviceName
+		}
+
+		if k.VirtualName != "" {
+			params[prefix+"VirtualName"] = k.VirtualName
+		} else if k.NoDevice {
+			params[prefix+"NoDevice"] = ""
+		} else {
+			if k.SnapshotId != "" {
+				params[prefix+"Ebs.SnapshotId"] = k.SnapshotId
+			}
+			if k.VolumeType != "" {
+				params[prefix+"Ebs.VolumeType"] = k.VolumeType
+			}
+			if k.IOPS != 0 {
+				params[prefix+"Ebs.Iops"] = strconv.FormatInt(k.IOPS, 10)
+			}
+			if k.VolumeSize != 0 {
+				params[prefix+"Ebs.VolumeSize"] = strconv.FormatInt(k.VolumeSize, 10)
+			}
+			if k.DeleteOnTermination {
+				params[prefix+"Ebs.DeleteOnTermination"] = "true"
+			} else {
+				params[prefix+"Ebs.DeleteOnTermination"] = "false"
+			}
+			if k.Encrypted {
+				params[prefix+"Ebs.Encrypted"] = "true"
+			}
+		}
+	}
+}
+
+// ----------------------------------------------------------------------------
+// Instance management functions and types.
+
+// The RunInstances type encapsulates options for the respective request in EC2.
+//
+// See http://goo.gl/Mcm3b for more details.
+type RunInstances struct {
+	ImageId                  string
+	MinCount                 int
+	MaxCount                 int
+	KeyName                  string
+	InstanceType             string
+	SecurityGroups           []SecurityGroup
+	IamInstanceProfile       string
+	KernelId                 string
+	RamdiskId                string
+	UserData                 []byte
+	AvailZone                string
+	PlacementGroupName       string
+	Monitoring               bool
+	SubnetId                 string
+	AssociatePublicIpAddress bool
+	DisableAPITermination    bool
+	EbsOptimized             bool
+	ShutdownBehavior         string
+	PrivateIPAddress         string
+	BlockDevices             []BlockDeviceMapping
+	Tenancy                  string
+}
+
+// Response to a RunInstances request.
+//
+// See http://goo.gl/Mcm3b for more details.
+type RunInstancesResp struct {
+	RequestId      string          `xml:"requestId"`
+	ReservationId  string          `xml:"reservationId"`
+	OwnerId        string          `xml:"ownerId"`
+	SecurityGroups []SecurityGroup `xml:"groupSet>item"`
+	Instances      []Instance      `xml:"instancesSet>item"`
+}
+
+// BlockDevice represents the association of a block device with an instance.
+type BlockDevice struct {
+	DeviceName          string `xml:"deviceName"`
+	VolumeId            string `xml:"ebs>volumeId"`
+	Status              string `xml:"ebs>status"`
+	AttachTime          string `xml:"ebs>attachTime"`
+	DeleteOnTermination bool   `xml:"ebs>deleteOnTermination"`
+}
+
+// Instance encapsulates a running instance in EC2.
+//
+// See http://goo.gl/OCH8a for more details.
+type Instance struct {
+	InstanceId         string          `xml:"instanceId"`
+	InstanceType       string          `xml:"instanceType"`
+	ImageId            string          `xml:"imageId"`
+	PrivateDNSName     string          `xml:"privateDnsName"`
+	DNSName            string          `xml:"dnsName"`
+	KeyName            string          `xml:"keyName"`
+	AMILaunchIndex     int             `xml:"amiLaunchIndex"`
+	Hypervisor         string          `xml:"hypervisor"`
+	VirtType           string          `xml:"virtualizationType"`
+	Monitoring         string          `xml:"monitoring>state"`
+	AvailZone          string          `xml:"placement>availabilityZone"`
+	Tenancy            string          `xml:"placement>tenancy"`
+	PlacementGroupName string          `xml:"placement>groupName"`
+	State              InstanceState   `xml:"instanceState"`
+	Tags               []Tag           `xml:"tagSet>item"`
+	VpcId              string          `xml:"vpcId"`
+	SubnetId           string          `xml:"subnetId"`
+	IamInstanceProfile string          `xml:"iamInstanceProfile"`
+	PrivateIpAddress   string          `xml:"privateIpAddress"`
+	PublicIpAddress    string          `xml:"ipAddress"`
+	Architecture       string          `xml:"architecture"`
+	LaunchTime         time.Time       `xml:"launchTime"`
+	SourceDestCheck    bool            `xml:"sourceDestCheck"`
+	SecurityGroups     []SecurityGroup `xml:"groupSet>item"`
+	EbsOptimized       string          `xml:"ebsOptimized"`
+	BlockDevices       []BlockDevice   `xml:"blockDeviceMapping>item"`
+	RootDeviceName     string          `xml:"rootDeviceName"`
+}
+
+// RunInstances starts new instances in EC2.
+// If options.MinCount and options.MaxCount are both zero, a single instance
+// will be started; otherwise if options.MaxCount is zero, options.MinCount
+// will be used insteead.
+//
+// See http://goo.gl/Mcm3b for more details.
+func (ec2 *EC2) RunInstances(options *RunInstances) (resp *RunInstancesResp, err error) {
+	params := makeParams("RunInstances")
+	params["ImageId"] = options.ImageId
+	params["InstanceType"] = options.InstanceType
+	var min, max int
+	if options.MinCount == 0 && options.MaxCount == 0 {
+		min = 1
+		max = 1
+	} else if options.MaxCount == 0 {
+		min = options.MinCount
+		max = min
+	} else {
+		min = options.MinCount
+		max = options.MaxCount
+	}
+	params["MinCount"] = strconv.Itoa(min)
+	params["MaxCount"] = strconv.Itoa(max)
+	token, err := clientToken()
+	if err != nil {
+		return nil, err
+	}
+	params["ClientToken"] = token
+
+	if options.KeyName != "" {
+		params["KeyName"] = options.KeyName
+	}
+	if options.KernelId != "" {
+		params["KernelId"] = options.KernelId
+	}
+	if options.RamdiskId != "" {
+		params["RamdiskId"] = options.RamdiskId
+	}
+	if options.UserData != nil {
+		userData := make([]byte, b64.EncodedLen(len(options.UserData)))
+		b64.Encode(userData, options.UserData)
+		params["UserData"] = string(userData)
+	}
+	if options.AvailZone != "" {
+		params["Placement.AvailabilityZone"] = options.AvailZone
+	}
+	if options.PlacementGroupName != "" {
+		params["Placement.GroupName"] = options.PlacementGroupName
+	}
+	if options.Monitoring {
+		params["Monitoring.Enabled"] = "true"
+	}
+	if options.Tenancy != "" {
+		params["Placement.Tenancy"] = options.Tenancy
+	}
+	if options.SubnetId != "" && options.AssociatePublicIpAddress {
+		// If we have a non-default VPC / Subnet specified, we can flag
+		// AssociatePublicIpAddress to get a Public IP assigned. By default these are not provided.
+		// You cannot specify both SubnetId and the NetworkInterface.0.* parameters though, otherwise
+		// you get: Network interfaces and an instance-level subnet ID may not be specified on the same request
+		// You also need to attach Security Groups to the NetworkInterface instead of the instance,
+		// to avoid: Network interfaces and an instance-level security groups may not be specified on
+		// the same request
+		params["NetworkInterface.0.DeviceIndex"] = "0"
+		params["NetworkInterface.0.AssociatePublicIpAddress"] = "true"
+		params["NetworkInterface.0.SubnetId"] = options.SubnetId
+
+		if options.PrivateIPAddress != "" {
+			params["NetworkInterface.0.PrivateIpAddress"] = options.PrivateIPAddress
+		}
+
+		i := 1
+		for _, g := range options.SecurityGroups {
+			// We only have SecurityGroupId's on NetworkInterface's, no SecurityGroup params.
+			if g.Id != "" {
+				params["NetworkInterface.0.SecurityGroupId."+strconv.Itoa(i)] = g.Id
+				i++
+			}
+		}
+	} else {
+		if options.SubnetId != "" {
+			params["SubnetId"] = options.SubnetId
+		}
+
+		if options.PrivateIPAddress != "" {
+			params["PrivateIpAddress"] = options.PrivateIPAddress
+		}
+
+		i, j := 1, 1
+		for _, g := range options.SecurityGroups {
+			if g.Id != "" {
+				params["SecurityGroupId."+strconv.Itoa(i)] = g.Id
+				i++
+			} else {
+				params["SecurityGroup."+strconv.Itoa(j)] = g.Name
+				j++
+			}
+		}
+	}
+	if options.IamInstanceProfile != "" {
+		params["IamInstanceProfile.Name"] = options.IamInstanceProfile
+	}
+	if options.DisableAPITermination {
+		params["DisableApiTermination"] = "true"
+	}
+	if options.EbsOptimized {
+		params["EbsOptimized"] = "true"
+	}
+	if options.ShutdownBehavior != "" {
+		params["InstanceInitiatedShutdownBehavior"] = options.ShutdownBehavior
+	}
+	addBlockDeviceParams("", params, options.BlockDevices)
+
+	resp = &RunInstancesResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+func clientToken() (string, error) {
+	// Maximum EC2 client token size is 64 bytes.
+	// Each byte expands to two when hex encoded.
+	buf := make([]byte, 32)
+	_, err := rand.Read(buf)
+	if err != nil {
+		return "", err
+	}
+	return hex.EncodeToString(buf), nil
+}
+
+// The GetConsoleOutput type encapsulates options for the respective request in EC2.
+//
+// See http://goo.gl/EY70zb for more details.
+type GetConsoleOutput struct {
+	InstanceId string
+}
+
+// Response to a GetConsoleOutput request. Note that Output is base64-encoded,
+// as in the underlying AWS API.
+//
+// See http://goo.gl/EY70zb for more details.
+type GetConsoleOutputResp struct {
+	RequestId  string    `xml:"requestId"`
+	InstanceId string    `xml:"instanceId"`
+	Timestamp  time.Time `xml:"timestamp"`
+	Output     string    `xml:"output"`
+}
+
+// GetConsoleOutput returns the console output for the sepcified instance. Note
+// that console output is base64-encoded, as in the underlying AWS API.
+//
+// See http://goo.gl/EY70zb for more details.
+func (ec2 *EC2) GetConsoleOutput(options *GetConsoleOutput) (resp *GetConsoleOutputResp, err error) {
+	params := makeParams("GetConsoleOutput")
+	params["InstanceId"] = options.InstanceId
+	resp = &GetConsoleOutputResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+// ----------------------------------------------------------------------------
+// Instance events and status functions and types.
+
+// The DescribeInstanceStatus type encapsulates options for the respective request in EC2.
+//
+// See http://goo.gl/DFySJY for more details.
+type EventsSet struct {
+	Code        string `xml:"code"`
+	Description string `xml:"description"`
+	NotBefore   string `xml:"notBefore"`
+	NotAfter    string `xml:"notAfter"`
+}
+
+type StatusDetails struct {
+	Name          string `xml:"name"`
+	Status        string `xml:"status"`
+	ImpairedSince string `xml:"impairedSince"`
+}
+
+type Status struct {
+	Status  string          `xml:"status"`
+	Details []StatusDetails `xml:"details>item"`
+}
+
+type InstanceStatusSet struct {
+	InstanceId       string        `xml:"instanceId"`
+	AvailabilityZone string        `xml:"availabilityZone"`
+	InstanceState    InstanceState `xml:"instanceState"`
+	SystemStatus     Status        `xml:"systemStatus"`
+	InstanceStatus   Status        `xml:"instanceStatus"`
+	Events           []EventsSet   `xml:"eventsSet>item"`
+}
+
+type DescribeInstanceStatusResp struct {
+	RequestId      string              `xml:"requestId"`
+	InstanceStatus []InstanceStatusSet `xml:"instanceStatusSet>item"`
+}
+
+type DescribeInstanceStatus struct {
+	InstanceIds         []string
+	IncludeAllInstances bool
+	MaxResults          int64
+	NextToken           string
+}
+
+func (ec2 *EC2) DescribeInstanceStatus(options *DescribeInstanceStatus, filter *Filter) (resp *DescribeInstanceStatusResp, err error) {
+	params := makeParams("DescribeInstanceStatus")
+	if options.IncludeAllInstances {
+		params["IncludeAllInstances"] = "true"
+	}
+	if len(options.InstanceIds) > 0 {
+		addParamsList(params, "InstanceId", options.InstanceIds)
+	}
+	if options.MaxResults > 0 {
+		params["MaxResults"] = strconv.FormatInt(options.MaxResults, 10)
+	}
+	if options.NextToken != "" {
+		params["NextToken"] = options.NextToken
+	}
+	if filter != nil {
+		filter.addParams(params)
+	}
+
+	resp = &DescribeInstanceStatusResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+
+	return
+}
+
+// ----------------------------------------------------------------------------
+// Spot Instance management functions and types.
+
+// The RequestSpotInstances type encapsulates options for the respective request in EC2.
+//
+// See http://goo.gl/GRZgCD for more details.
+type RequestSpotInstances struct {
+	SpotPrice                string
+	InstanceCount            int
+	Type                     string
+	ImageId                  string
+	KeyName                  string
+	InstanceType             string
+	SecurityGroups           []SecurityGroup
+	IamInstanceProfile       string
+	KernelId                 string
+	RamdiskId                string
+	UserData                 []byte
+	AvailZone                string
+	PlacementGroupName       string
+	Monitoring               bool
+	SubnetId                 string
+	AssociatePublicIpAddress bool
+	PrivateIPAddress         string
+	BlockDevices             []BlockDeviceMapping
+}
+
+type SpotInstanceSpec struct {
+	ImageId                  string
+	KeyName                  string
+	InstanceType             string
+	SecurityGroups           []SecurityGroup
+	IamInstanceProfile       string
+	KernelId                 string
+	RamdiskId                string
+	UserData                 []byte
+	AvailZone                string
+	PlacementGroupName       string
+	Monitoring               bool
+	SubnetId                 string
+	AssociatePublicIpAddress bool
+	PrivateIPAddress         string
+	BlockDevices             []BlockDeviceMapping
+}
+
+type SpotLaunchSpec struct {
+	ImageId            string               `xml:"imageId"`
+	KeyName            string               `xml:"keyName"`
+	InstanceType       string               `xml:"instanceType"`
+	SecurityGroups     []SecurityGroup      `xml:"groupSet>item"`
+	IamInstanceProfile string               `xml:"iamInstanceProfile"`
+	KernelId           string               `xml:"kernelId"`
+	RamdiskId          string               `xml:"ramdiskId"`
+	PlacementGroupName string               `xml:"placement>groupName"`
+	Monitoring         bool                 `xml:"monitoring>enabled"`
+	SubnetId           string               `xml:"subnetId"`
+	BlockDevices       []BlockDeviceMapping `xml:"blockDeviceMapping>item"`
+}
+
+type SpotStatus struct {
+	Code       string `xml:"code"`
+	UpdateTime string `xml:"updateTime"`
+	Message    string `xml:"message"`
+}
+
+type SpotRequestResult struct {
+	SpotRequestId  string         `xml:"spotInstanceRequestId"`
+	SpotPrice      string         `xml:"spotPrice"`
+	Type           string         `xml:"type"`
+	AvailZone      string         `xml:"launchedAvailabilityZone"`
+	InstanceId     string         `xml:"instanceId"`
+	State          string         `xml:"state"`
+	Status         SpotStatus     `xml:"status"`
+	SpotLaunchSpec SpotLaunchSpec `xml:"launchSpecification"`
+	CreateTime     string         `xml:"createTime"`
+	Tags           []Tag          `xml:"tagSet>item"`
+}
+
+// Response to a RequestSpotInstances request.
+//
+// See http://goo.gl/GRZgCD for more details.
+type RequestSpotInstancesResp struct {
+	RequestId          string              `xml:"requestId"`
+	SpotRequestResults []SpotRequestResult `xml:"spotInstanceRequestSet>item"`
+}
+
+// RequestSpotInstances requests a new spot instances in EC2.
+func (ec2 *EC2) RequestSpotInstances(options *RequestSpotInstances) (resp *RequestSpotInstancesResp, err error) {
+	params := makeParams("RequestSpotInstances")
+	prefix := "LaunchSpecification" + "."
+
+	params["SpotPrice"] = options.SpotPrice
+	params[prefix+"ImageId"] = options.ImageId
+	params[prefix+"InstanceType"] = options.InstanceType
+
+	if options.InstanceCount != 0 {
+		params["InstanceCount"] = strconv.Itoa(options.InstanceCount)
+	}
+	if options.KeyName != "" {
+		params[prefix+"KeyName"] = options.KeyName
+	}
+	if options.KernelId != "" {
+		params[prefix+"KernelId"] = options.KernelId
+	}
+	if options.RamdiskId != "" {
+		params[prefix+"RamdiskId"] = options.RamdiskId
+	}
+	if options.UserData != nil {
+		userData := make([]byte, b64.EncodedLen(len(options.UserData)))
+		b64.Encode(userData, options.UserData)
+		params[prefix+"UserData"] = string(userData)
+	}
+	if options.AvailZone != "" {
+		params[prefix+"Placement.AvailabilityZone"] = options.AvailZone
+	}
+	if options.PlacementGroupName != "" {
+		params[prefix+"Placement.GroupName"] = options.PlacementGroupName
+	}
+	if options.Monitoring {
+		params[prefix+"Monitoring.Enabled"] = "true"
+	}
+	if options.SubnetId != "" && options.AssociatePublicIpAddress {
+		// If we have a non-default VPC / Subnet specified, we can flag
+		// AssociatePublicIpAddress to get a Public IP assigned. By default these are not provided.
+		// You cannot specify both SubnetId and the NetworkInterface.0.* parameters though, otherwise
+		// you get: Network interfaces and an instance-level subnet ID may not be specified on the same request
+		// You also need to attach Security Groups to the NetworkInterface instead of the instance,
+		// to avoid: Network interfaces and an instance-level security groups may not be specified on
+		// the same request
+		params[prefix+"NetworkInterface.0.DeviceIndex"] = "0"
+		params[prefix+"NetworkInterface.0.AssociatePublicIpAddress"] = "true"
+		params[prefix+"NetworkInterface.0.SubnetId"] = options.SubnetId
+
+		i := 1
+		for _, g := range options.SecurityGroups {
+			// We only have SecurityGroupId's on NetworkInterface's, no SecurityGroup params.
+			if g.Id != "" {
+				params[prefix+"NetworkInterface.0.SecurityGroupId."+strconv.Itoa(i)] = g.Id
+				i++
+			}
+		}
+	} else {
+		if options.SubnetId != "" {
+			params[prefix+"SubnetId"] = options.SubnetId
+		}
+
+		i, j := 1, 1
+		for _, g := range options.SecurityGroups {
+			if g.Id != "" {
+				params[prefix+"SecurityGroupId."+strconv.Itoa(i)] = g.Id
+				i++
+			} else {
+				params[prefix+"SecurityGroup."+strconv.Itoa(j)] = g.Name
+				j++
+			}
+		}
+	}
+	if options.IamInstanceProfile != "" {
+		params[prefix+"IamInstanceProfile.Name"] = options.IamInstanceProfile
+	}
+	if options.PrivateIPAddress != "" {
+		params[prefix+"PrivateIpAddress"] = options.PrivateIPAddress
+	}
+	addBlockDeviceParams(prefix, params, options.BlockDevices)
+
+	resp = &RequestSpotInstancesResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+// Response to a DescribeSpotInstanceRequests request.
+//
+// See http://goo.gl/KsKJJk for more details.
+type SpotRequestsResp struct {
+	RequestId          string              `xml:"requestId"`
+	SpotRequestResults []SpotRequestResult `xml:"spotInstanceRequestSet>item"`
+}
+
+// DescribeSpotInstanceRequests returns details about spot requests in EC2.  Both parameters
+// are optional, and if provided will limit the spot requests returned to those
+// matching the given spot request ids or filtering rules.
+//
+// See http://goo.gl/KsKJJk for more details.
+func (ec2 *EC2) DescribeSpotRequests(spotrequestIds []string, filter *Filter) (resp *SpotRequestsResp, err error) {
+	params := makeParams("DescribeSpotInstanceRequests")
+	addParamsList(params, "SpotInstanceRequestId", spotrequestIds)
+	filter.addParams(params)
+	resp = &SpotRequestsResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+// Response to a CancelSpotInstanceRequests request.
+//
+// See http://goo.gl/3BKHj for more details.
+type CancelSpotRequestResult struct {
+	SpotRequestId string `xml:"spotInstanceRequestId"`
+	State         string `xml:"state"`
+}
+type CancelSpotRequestsResp struct {
+	RequestId                string                    `xml:"requestId"`
+	CancelSpotRequestResults []CancelSpotRequestResult `xml:"spotInstanceRequestSet>item"`
+}
+
+// CancelSpotRequests requests the cancellation of spot requests when the given ids.
+//
+// See http://goo.gl/3BKHj for more details.
+func (ec2 *EC2) CancelSpotRequests(spotrequestIds []string) (resp *CancelSpotRequestsResp, err error) {
+	params := makeParams("CancelSpotInstanceRequests")
+	addParamsList(params, "SpotInstanceRequestId", spotrequestIds)
+	resp = &CancelSpotRequestsResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+type DescribeSpotPriceHistory struct {
+	InstanceType       []string
+	ProductDescription []string
+	AvailabilityZone   string
+	StartTime, EndTime time.Time
+}
+
+// Response to a DescribeSpotPriceHisotyr request.
+//
+// See http://goo.gl/3BKHj for more details.
+type DescribeSpotPriceHistoryResp struct {
+	RequestId string             `xml:"requestId"`
+	History   []SpotPriceHistory `xml:"spotPriceHistorySet>item"`
+}
+
+type SpotPriceHistory struct {
+	InstanceType       string    `xml:"instanceType"`
+	ProductDescription string    `xml:"productDescription"`
+	SpotPrice          string    `xml:"spotPrice"`
+	Timestamp          time.Time `xml:"timestamp"`
+	AvailabilityZone   string    `xml:"availabilityZone"`
+}
+
+// DescribeSpotPriceHistory gets the spot pricing history.
+//
+// See http://goo.gl/3BKHj for more details.
+func (ec2 *EC2) DescribeSpotPriceHistory(o *DescribeSpotPriceHistory) (resp *DescribeSpotPriceHistoryResp, err error) {
+	params := makeParams("DescribeSpotPriceHistory")
+	if o.AvailabilityZone != "" {
+		params["AvailabilityZone"] = o.AvailabilityZone
+	}
+
+	if !o.StartTime.IsZero() {
+		params["StartTime"] = o.StartTime.In(time.UTC).Format(time.RFC3339)
+	}
+	if !o.EndTime.IsZero() {
+		params["EndTime"] = o.EndTime.In(time.UTC).Format(time.RFC3339)
+	}
+
+	if len(o.InstanceType) > 0 {
+		addParamsList(params, "InstanceType", o.InstanceType)
+	}
+	if len(o.ProductDescription) > 0 {
+		addParamsList(params, "ProductDescription", o.ProductDescription)
+	}
+
+	resp = &DescribeSpotPriceHistoryResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+
+	return
+}
+
+// Response to a TerminateInstances request.
+//
+// See http://goo.gl/3BKHj for more details.
+type TerminateInstancesResp struct {
+	RequestId    string                `xml:"requestId"`
+	StateChanges []InstanceStateChange `xml:"instancesSet>item"`
+}
+
+// InstanceState encapsulates the state of an instance in EC2.
+//
+// See http://goo.gl/y3ZBq for more details.
+type InstanceState struct {
+	Code int    `xml:"code"` // Watch out, bits 15-8 have unpublished meaning.
+	Name string `xml:"name"`
+}
+
+// InstanceStateChange informs of the previous and current states
+// for an instance when a state change is requested.
+type InstanceStateChange struct {
+	InstanceId    string        `xml:"instanceId"`
+	CurrentState  InstanceState `xml:"currentState"`
+	PreviousState InstanceState `xml:"previousState"`
+}
+
+// TerminateInstances requests the termination of instances when the given ids.
+//
+// See http://goo.gl/3BKHj for more details.
+func (ec2 *EC2) TerminateInstances(instIds []string) (resp *TerminateInstancesResp, err error) {
+	params := makeParams("TerminateInstances")
+	addParamsList(params, "InstanceId", instIds)
+	resp = &TerminateInstancesResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+// Response to a DescribeInstances request.
+//
+// See http://goo.gl/mLbmw for more details.
+type InstancesResp struct {
+	RequestId    string        `xml:"requestId"`
+	Reservations []Reservation `xml:"reservationSet>item"`
+}
+
+// Reservation represents details about a reservation in EC2.
+//
+// See http://goo.gl/0ItPT for more details.
+type Reservation struct {
+	ReservationId  string          `xml:"reservationId"`
+	OwnerId        string          `xml:"ownerId"`
+	RequesterId    string          `xml:"requesterId"`
+	SecurityGroups []SecurityGroup `xml:"groupSet>item"`
+	Instances      []Instance      `xml:"instancesSet>item"`
+}
+
+// Instances returns details about instances in EC2.  Both parameters
+// are optional, and if provided will limit the instances returned to those
+// matching the given instance ids or filtering rules.
+//
+// See http://goo.gl/4No7c for more details.
+func (ec2 *EC2) Instances(instIds []string, filter *Filter) (resp *InstancesResp, err error) {
+	params := makeParams("DescribeInstances")
+	addParamsList(params, "InstanceId", instIds)
+	filter.addParams(params)
+	resp = &InstancesResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+// ----------------------------------------------------------------------------
+// Volume management
+
+// The CreateVolume request parameters
+//
+// See http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-CreateVolume.html
+type CreateVolume struct {
+	AvailZone  string
+	Size       int64
+	SnapshotId string
+	VolumeType string
+	IOPS       int64
+	Encrypted  bool
+}
+
+// Response to an AttachVolume request
+type AttachVolumeResp struct {
+	RequestId  string `xml:"requestId"`
+	VolumeId   string `xml:"volumeId"`
+	InstanceId string `xml:"instanceId"`
+	Device     string `xml:"device"`
+	Status     string `xml:"status"`
+	AttachTime string `xml:"attachTime"`
+}
+
+// Response to a CreateVolume request
+type CreateVolumeResp struct {
+	RequestId  string `xml:"requestId"`
+	VolumeId   string `xml:"volumeId"`
+	Size       int64  `xml:"size"`
+	SnapshotId string `xml:"snapshotId"`
+	AvailZone  string `xml:"availabilityZone"`
+	Status     string `xml:"status"`
+	CreateTime string `xml:"createTime"`
+	VolumeType string `xml:"volumeType"`
+	IOPS       int64  `xml:"iops"`
+	Encrypted  bool   `xml:"encrypted"`
+}
+
+// Volume is a single volume.
+type Volume struct {
+	VolumeId    string             `xml:"volumeId"`
+	Size        string             `xml:"size"`
+	SnapshotId  string             `xml:"snapshotId"`
+	AvailZone   string             `xml:"availabilityZone"`
+	Status      string             `xml:"status"`
+	Attachments []VolumeAttachment `xml:"attachmentSet>item"`
+	VolumeType  string             `xml:"volumeType"`
+	IOPS        int64              `xml:"iops"`
+	Encrypted   bool               `xml:"encrypted"`
+	Tags        []Tag              `xml:"tagSet>item"`
+}
+
+type VolumeAttachment struct {
+	VolumeId   string `xml:"volumeId"`
+	InstanceId string `xml:"instanceId"`
+	Device     string `xml:"device"`
+	Status     string `xml:"status"`
+}
+
+// Response to a DescribeVolumes request
+type VolumesResp struct {
+	RequestId string   `xml:"requestId"`
+	Volumes   []Volume `xml:"volumeSet>item"`
+}
+
+// Attach a volume.
+func (ec2 *EC2) AttachVolume(volumeId string, instanceId string, device string) (resp *AttachVolumeResp, err error) {
+	params := makeParams("AttachVolume")
+	params["VolumeId"] = volumeId
+	params["InstanceId"] = instanceId
+	params["Device"] = device
+
+	resp = &AttachVolumeResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+
+	return
+}
+
+// Create a new volume.
+func (ec2 *EC2) CreateVolume(options *CreateVolume) (resp *CreateVolumeResp, err error) {
+	params := makeParams("CreateVolume")
+	params["AvailabilityZone"] = options.AvailZone
+	if options.Size > 0 {
+		params["Size"] = strconv.FormatInt(options.Size, 10)
+	}
+
+	if options.SnapshotId != "" {
+		params["SnapshotId"] = options.SnapshotId
+	}
+
+	if options.VolumeType != "" {
+		params["VolumeType"] = options.VolumeType
+	}
+
+	if options.IOPS > 0 {
+		params["Iops"] = strconv.FormatInt(options.IOPS, 10)
+	}
+
+	if options.Encrypted {
+		params["Encrypted"] = "true"
+	}
+
+	resp = &CreateVolumeResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+
+	return
+}
+
+// Delete an EBS volume.
+func (ec2 *EC2) DeleteVolume(id string) (resp *SimpleResp, err error) {
+	params := makeParams("DeleteVolume")
+	params["VolumeId"] = id
+
+	resp = &SimpleResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+// Detaches an EBS volume.
+func (ec2 *EC2) DetachVolume(id string) (resp *SimpleResp, err error) {
+	params := makeParams("DetachVolume")
+	params["VolumeId"] = id
+
+	resp = &SimpleResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+// Finds or lists all volumes.
+func (ec2 *EC2) Volumes(volIds []string, filter *Filter) (resp *VolumesResp, err error) {
+	params := makeParams("DescribeVolumes")
+	addParamsList(params, "VolumeId", volIds)
+	filter.addParams(params)
+	resp = &VolumesResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+// ----------------------------------------------------------------------------
+// Availability zone management functions and types.
+// See http://goo.gl/ylxT4R for more details.
+
+// DescribeAvailabilityZonesResp represents a response to a DescribeAvailabilityZones
+// request in EC2.
+type DescribeAvailabilityZonesResp struct {
+	RequestId string                 `xml:"requestId"`
+	Zones     []AvailabilityZoneInfo `xml:"availabilityZoneInfo>item"`
+}
+
+// AvailabilityZoneInfo encapsulates details for an availability zone in EC2.
+type AvailabilityZoneInfo struct {
+	AvailabilityZone
+	State      string   `xml:"zoneState"`
+	MessageSet []string `xml:"messageSet>item"`
+}
+
+// AvailabilityZone represents an EC2 availability zone.
+type AvailabilityZone struct {
+	Name   string `xml:"zoneName"`
+	Region string `xml:"regionName"`
+}
+
+// DescribeAvailabilityZones returns details about availability zones in EC2.
+// The filter parameter is optional, and if provided will limit the
+// availability zones returned to those matching the given filtering
+// rules.
+//
+// See http://goo.gl/ylxT4R for more details.
+func (ec2 *EC2) DescribeAvailabilityZones(filter *Filter) (resp *DescribeAvailabilityZonesResp, err error) {
+	params := makeParams("DescribeAvailabilityZones")
+	filter.addParams(params)
+	resp = &DescribeAvailabilityZonesResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+// ----------------------------------------------------------------------------
+// ElasticIp management (for VPC)
+
+// The AllocateAddress request parameters
+//
+// see http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-AllocateAddress.html
+type AllocateAddress struct {
+	Domain string
+}
+
+// Response to an AllocateAddress request
+type AllocateAddressResp struct {
+	RequestId    string `xml:"requestId"`
+	PublicIp     string `xml:"publicIp"`
+	Domain       string `xml:"domain"`
+	AllocationId string `xml:"allocationId"`
+}
+
+// The AssociateAddress request parameters
+//
+// http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-AssociateAddress.html
+type AssociateAddress struct {
+	InstanceId         string
+	PublicIp           string
+	AllocationId       string
+	AllowReassociation bool
+}
+
+// Response to an AssociateAddress request
+type AssociateAddressResp struct {
+	RequestId     string `xml:"requestId"`
+	Return        bool   `xml:"return"`
+	AssociationId string `xml:"associationId"`
+}
+
+// Address represents an Elastic IP Address
+// See http://goo.gl/uxCjp7 for more details
+type Address struct {
+	PublicIp                string `xml:"publicIp"`
+	AllocationId            string `xml:"allocationId"`
+	Domain                  string `xml:"domain"`
+	InstanceId              string `xml:"instanceId"`
+	AssociationId           string `xml:"associationId"`
+	NetworkInterfaceId      string `xml:"networkInterfaceId"`
+	NetworkInterfaceOwnerId string `xml:"networkInterfaceOwnerId"`
+	PrivateIpAddress        string `xml:"privateIpAddress"`
+}
+
+type DescribeAddressesResp struct {
+	RequestId string    `xml:"requestId"`
+	Addresses []Address `xml:"addressesSet>item"`
+}
+
+// Allocate a new Elastic IP.
+func (ec2 *EC2) AllocateAddress(options *AllocateAddress) (resp *AllocateAddressResp, err error) {
+	params := makeParams("AllocateAddress")
+	params["Domain"] = options.Domain
+
+	resp = &AllocateAddressResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+
+	return
+}
+
+// Release an Elastic IP (VPC).
+func (ec2 *EC2) ReleaseAddress(id string) (resp *SimpleResp, err error) {
+	params := makeParams("ReleaseAddress")
+	params["AllocationId"] = id
+
+	resp = &SimpleResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+
+	return
+}
+
+// Release an Elastic IP (Public)
+func (ec2 *EC2) ReleasePublicAddress(publicIp string) (resp *SimpleResp, err error) {
+	params := makeParams("ReleaseAddress")
+	params["PublicIp"] = publicIp
+
+	resp = &SimpleResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+
+	return
+}
+
+// Associate an address with a VPC instance.
+func (ec2 *EC2) AssociateAddress(options *AssociateAddress) (resp *AssociateAddressResp, err error) {
+	params := makeParams("AssociateAddress")
+	params["InstanceId"] = options.InstanceId
+	if options.PublicIp != "" {
+		params["PublicIp"] = options.PublicIp
+	}
+	if options.AllocationId != "" {
+		params["AllocationId"] = options.AllocationId
+	}
+	if options.AllowReassociation {
+		params["AllowReassociation"] = "true"
+	}
+
+	resp = &AssociateAddressResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+
+	return
+}
+
+// Disassociate an address from a VPC instance.
+func (ec2 *EC2) DisassociateAddress(id string) (resp *SimpleResp, err error) {
+	params := makeParams("DisassociateAddress")
+	params["AssociationId"] = id
+
+	resp = &SimpleResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+
+	return
+}
+
+// Disassociate an address from a VPC instance.
+func (ec2 *EC2) DisassociateAddressClassic(ip string) (resp *SimpleResp, err error) {
+	params := makeParams("DisassociateAddress")
+	params["PublicIp"] = ip
+
+	resp = &SimpleResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+
+	return
+}
+
+// DescribeAddresses returns details about one or more
+// Elastic IP Addresses. Returned addresses can be
+// filtered by Public IP, Allocation ID or multiple filters
+//
+// See http://goo.gl/zW7J4p for more details.
+func (ec2 *EC2) Addresses(publicIps []string, allocationIds []string, filter *Filter) (resp *DescribeAddressesResp, err error) {
+	params := makeParams("DescribeAddresses")
+	addParamsList(params, "PublicIp", publicIps)
+	addParamsList(params, "AllocationId", allocationIds)
+	filter.addParams(params)
+	resp = &DescribeAddressesResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+// ----------------------------------------------------------------------------
+// Image and snapshot management functions and types.
+
+// The CreateImage request parameters.
+//
+// See http://goo.gl/cxU41 for more details.
+type CreateImage struct {
+	InstanceId   string
+	Name         string
+	Description  string
+	NoReboot     bool
+	BlockDevices []BlockDeviceMapping
+}
+
+// Response to a CreateImage request.
+//
+// See http://goo.gl/cxU41 for more details.
+type CreateImageResp struct {
+	RequestId string `xml:"requestId"`
+	ImageId   string `xml:"imageId"`
+}
+
+// Response to a DescribeImages request.
+//
+// See http://goo.gl/hLnyg for more details.
+type ImagesResp struct {
+	RequestId string  `xml:"requestId"`
+	Images    []Image `xml:"imagesSet>item"`
+}
+
+// Response to a DescribeImageAttribute request.
+//
+// See http://goo.gl/bHO3zT for more details.
+type ImageAttributeResp struct {
+	RequestId    string               `xml:"requestId"`
+	ImageId      string               `xml:"imageId"`
+	Kernel       string               `xml:"kernel>value"`
+	RamDisk      string               `xml:"ramdisk>value"`
+	Description  string               `xml:"description>value"`
+	Group        string               `xml:"launchPermission>item>group"`
+	UserIds      []string             `xml:"launchPermission>item>userId"`
+	ProductCodes []string             `xml:"productCodes>item>productCode"`
+	BlockDevices []BlockDeviceMapping `xml:"blockDeviceMapping>item"`
+}
+
+// The RegisterImage request parameters.
+type RegisterImage struct {
+	ImageLocation   string
+	Name            string
+	Description     string
+	Architecture    string
+	KernelId        string
+	RamdiskId       string
+	RootDeviceName  string
+	VirtType        string
+	SriovNetSupport string
+	BlockDevices    []BlockDeviceMapping
+}
+
+// Response to a RegisterImage request.
+type RegisterImageResp struct {
+	RequestId string `xml:"requestId"`
+	ImageId   string `xml:"imageId"`
+}
+
+// Response to a DegisterImage request.
+//
+// See http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DeregisterImage.html
+type DeregisterImageResp struct {
+	RequestId string `xml:"requestId"`
+	Return    bool   `xml:"return"`
+}
+
+// BlockDeviceMapping represents the association of a block device with an image.
+//
+// See http://goo.gl/wnDBf for more details.
+type BlockDeviceMapping struct {
+	DeviceName          string `xml:"deviceName"`
+	VirtualName         string `xml:"virtualName"`
+	SnapshotId          string `xml:"ebs>snapshotId"`
+	VolumeType          string `xml:"ebs>volumeType"`
+	VolumeSize          int64  `xml:"ebs>volumeSize"`
+	DeleteOnTermination bool   `xml:"ebs>deleteOnTermination"`
+	Encrypted           bool   `xml:"ebs>encrypted"`
+	NoDevice            bool   `xml:"noDevice"`
+
+	// The number of I/O operations per second (IOPS) that the volume supports.
+	IOPS int64 `xml:"ebs>iops"`
+}
+
+// Image represents details about an image.
+//
+// See http://goo.gl/iSqJG for more details.
+type Image struct {
+	Id                 string               `xml:"imageId"`
+	Name               string               `xml:"name"`
+	Description        string               `xml:"description"`
+	Type               string               `xml:"imageType"`
+	State              string               `xml:"imageState"`
+	Location           string               `xml:"imageLocation"`
+	Public             bool                 `xml:"isPublic"`
+	Architecture       string               `xml:"architecture"`
+	Platform           string               `xml:"platform"`
+	ProductCodes       []string             `xml:"productCode>item>productCode"`
+	KernelId           string               `xml:"kernelId"`
+	RamdiskId          string               `xml:"ramdiskId"`
+	StateReason        string               `xml:"stateReason"`
+	OwnerId            string               `xml:"imageOwnerId"`
+	OwnerAlias         string               `xml:"imageOwnerAlias"`
+	RootDeviceType     string               `xml:"rootDeviceType"`
+	RootDeviceName     string               `xml:"rootDeviceName"`
+	VirtualizationType string               `xml:"virtualizationType"`
+	Hypervisor         string               `xml:"hypervisor"`
+	BlockDevices       []BlockDeviceMapping `xml:"blockDeviceMapping>item"`
+	Tags               []Tag                `xml:"tagSet>item"`
+}
+
+// The ModifyImageAttribute request parameters.
+type ModifyImageAttribute struct {
+	AddUsers     []string
+	RemoveUsers  []string
+	AddGroups    []string
+	RemoveGroups []string
+	ProductCodes []string
+	Description  string
+}
+
+// The CopyImage request parameters.
+//
+// See http://goo.gl/hQwPCK for more details.
+type CopyImage struct {
+	SourceRegion  string
+	SourceImageId string
+	Name          string
+	Description   string
+	ClientToken   string
+}
+
+// Response to a CopyImage request.
+//
+// See http://goo.gl/hQwPCK for more details.
+type CopyImageResp struct {
+	RequestId string `xml:"requestId"`
+	ImageId   string `xml:"imageId"`
+}
+
+// Creates an Amazon EBS-backed AMI from an Amazon EBS-backed instance
+// that is either running or stopped.
+//
+// See http://goo.gl/cxU41 for more details.
+func (ec2 *EC2) CreateImage(options *CreateImage) (resp *CreateImageResp, err error) {
+	params := makeParams("CreateImage")
+	params["InstanceId"] = options.InstanceId
+	params["Name"] = options.Name
+	if options.Description != "" {
+		params["Description"] = options.Description
+	}
+	if options.NoReboot {
+		params["NoReboot"] = "true"
+	}
+	addBlockDeviceParams("", params, options.BlockDevices)
+
+	resp = &CreateImageResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+
+	return
+}
+
+// Images returns details about available images.
+// The ids and filter parameters, if provided, will limit the images returned.
+// For example, to get all the private images associated with this account set
+// the boolean filter "is-public" to 0.
+// For list of filters: http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DescribeImages.html
+//
+// Note: calling this function with nil ids and filter parameters will result in
+// a very large number of images being returned.
+//
+// See http://goo.gl/SRBhW for more details.
+func (ec2 *EC2) Images(ids []string, filter *Filter) (resp *ImagesResp, err error) {
+	params := makeParams("DescribeImages")
+	for i, id := range ids {
+		params["ImageId."+strconv.Itoa(i+1)] = id
+	}
+	filter.addParams(params)
+
+	resp = &ImagesResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+// ImagesByOwners returns details about available images.
+// The ids, owners, and filter parameters, if provided, will limit the images returned.
+// For example, to get all the private images associated with this account set
+// the boolean filter "is-public" to 0.
+// For list of filters: http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DescribeImages.html
+//
+// Note: calling this function with nil ids and filter parameters will result in
+// a very large number of images being returned.
+//
+// See http://goo.gl/SRBhW for more details.
+func (ec2 *EC2) ImagesByOwners(ids []string, owners []string, filter *Filter) (resp *ImagesResp, err error) {
+	params := makeParams("DescribeImages")
+	for i, id := range ids {
+		params["ImageId."+strconv.Itoa(i+1)] = id
+	}
+	for i, owner := range owners {
+		params[fmt.Sprintf("Owner.%d", i+1)] = owner
+	}
+
+	filter.addParams(params)
+
+	resp = &ImagesResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+// ImageAttribute describes an attribute of an AMI.
+// You can specify only one attribute at a time.
+// Valid attributes are:
+//    description | kernel | ramdisk | launchPermission | productCodes | blockDeviceMapping
+//
+// See http://goo.gl/bHO3zT for more details.
+func (ec2 *EC2) ImageAttribute(imageId, attribute string) (resp *ImageAttributeResp, err error) {
+	params := makeParams("DescribeImageAttribute")
+	params["ImageId"] = imageId
+	params["Attribute"] = attribute
+
+	resp = &ImageAttributeResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+// ModifyImageAttribute sets attributes for an image.
+//
+// See http://goo.gl/YUjO4G for more details.
+func (ec2 *EC2) ModifyImageAttribute(imageId string, options *ModifyImageAttribute) (resp *SimpleResp, err error) {
+	params := makeParams("ModifyImageAttribute")
+	params["ImageId"] = imageId
+	if options.Description != "" {
+		params["Description.Value"] = options.Description
+	}
+
+	if options.AddUsers != nil {
+		for i, user := range options.AddUsers {
+			p := fmt.Sprintf("LaunchPermission.Add.%d.UserId", i+1)
+			params[p] = user
+		}
+	}
+
+	if options.RemoveUsers != nil {
+		for i, user := range options.RemoveUsers {
+			p := fmt.Sprintf("LaunchPermission.Remove.%d.UserId", i+1)
+			params[p] = user
+		}
+	}
+
+	if options.AddGroups != nil {
+		for i, group := range options.AddGroups {
+			p := fmt.Sprintf("LaunchPermission.Add.%d.Group", i+1)
+			params[p] = group
+		}
+	}
+
+	if options.RemoveGroups != nil {
+		for i, group := range options.RemoveGroups {
+			p := fmt.Sprintf("LaunchPermission.Remove.%d.Group", i+1)
+			params[p] = group
+		}
+	}
+
+	if options.ProductCodes != nil {
+		addParamsList(params, "ProductCode", options.ProductCodes)
+	}
+
+	resp = &SimpleResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		resp = nil
+	}
+
+	return
+}
+
+// Registers a new AMI with EC2.
+//
+// See: http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-RegisterImage.html
+func (ec2 *EC2) RegisterImage(options *RegisterImage) (resp *RegisterImageResp, err error) {
+	params := makeParams("RegisterImage")
+	params["Name"] = options.Name
+	if options.ImageLocation != "" {
+		params["ImageLocation"] = options.ImageLocation
+	}
+
+	if options.Description != "" {
+		params["Description"] = options.Description
+	}
+
+	if options.Architecture != "" {
+		params["Architecture"] = options.Architecture
+	}
+
+	if options.KernelId != "" {
+		params["KernelId"] = options.KernelId
+	}
+
+	if options.RamdiskId != "" {
+		params["RamdiskId"] = options.RamdiskId
+	}
+
+	if options.RootDeviceName != "" {
+		params["RootDeviceName"] = options.RootDeviceName
+	}
+
+	if options.VirtType != "" {
+		params["VirtualizationType"] = options.VirtType
+	}
+
+	if options.SriovNetSupport != "" {
+		params["SriovNetSupport"] = "simple"
+	}
+
+	addBlockDeviceParams("", params, options.BlockDevices)
+
+	resp = &RegisterImageResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+
+	return
+}
+
+// Degisters an image. Note that this does not delete the backing stores of the AMI.
+//
+// See http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DeregisterImage.html
+func (ec2 *EC2) DeregisterImage(imageId string) (resp *DeregisterImageResp, err error) {
+	params := makeParams("DeregisterImage")
+	params["ImageId"] = imageId
+
+	resp = &DeregisterImageResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+
+	return
+}
+
+// Copy and Image from one region to another.
+//
+// See http://goo.gl/hQwPCK for more details.
+func (ec2 *EC2) CopyImage(options *CopyImage) (resp *CopyImageResp, err error) {
+	params := makeParams("CopyImage")
+
+	if options.SourceRegion != "" {
+		params["SourceRegion"] = options.SourceRegion
+	}
+
+	if options.SourceImageId != "" {
+		params["SourceImageId"] = options.SourceImageId
+	}
+
+	if options.Name != "" {
+		params["Name"] = options.Name
+	}
+
+	if options.Description != "" {
+		params["Description"] = options.Description
+	}
+
+	if options.ClientToken != "" {
+		params["ClientToken"] = options.ClientToken
+	}
+
+	resp = &CopyImageResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+
+	return
+}
+
+// Response to a CreateSnapshot request.
+//
+// See http://goo.gl/ttcda for more details.
+type CreateSnapshotResp struct {
+	RequestId string `xml:"requestId"`
+	Snapshot
+}
+
+// CreateSnapshot creates a volume snapshot and stores it in S3.
+//
+// See http://goo.gl/ttcda for more details.
+func (ec2 *EC2) CreateSnapshot(volumeId, description string) (resp *CreateSnapshotResp, err error) {
+	params := makeParams("CreateSnapshot")
+	params["VolumeId"] = volumeId
+	params["Description"] = description
+
+	resp = &CreateSnapshotResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+// DeleteSnapshots deletes the volume snapshots with the given ids.
+//
+// Note: If you make periodic snapshots of a volume, the snapshots are
+// incremental so that only the blocks on the device that have changed
+// since your last snapshot are incrementally saved in the new snapshot.
+// Even though snapshots are saved incrementally, the snapshot deletion
+// process is designed so that you need to retain only the most recent
+// snapshot in order to restore the volume.
+//
+// See http://goo.gl/vwU1y for more details.
+func (ec2 *EC2) DeleteSnapshots(ids []string) (resp *SimpleResp, err error) {
+	params := makeParams("DeleteSnapshot")
+	for i, id := range ids {
+		params["SnapshotId."+strconv.Itoa(i+1)] = id
+	}
+
+	resp = &SimpleResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+// Response to a DescribeSnapshots request.
+//
+// See http://goo.gl/nClDT for more details.
+type SnapshotsResp struct {
+	RequestId string     `xml:"requestId"`
+	Snapshots []Snapshot `xml:"snapshotSet>item"`
+}
+
+// Snapshot represents details about a volume snapshot.
+//
+// See http://goo.gl/nkovs for more details.
+type Snapshot struct {
+	Id          string `xml:"snapshotId"`
+	VolumeId    string `xml:"volumeId"`
+	VolumeSize  string `xml:"volumeSize"`
+	Status      string `xml:"status"`
+	StartTime   string `xml:"startTime"`
+	Description string `xml:"description"`
+	Progress    string `xml:"progress"`
+	OwnerId     string `xml:"ownerId"`
+	OwnerAlias  string `xml:"ownerAlias"`
+	Encrypted   bool   `xml:"encrypted"`
+	Tags        []Tag  `xml:"tagSet>item"`
+}
+
+// Snapshots returns details about volume snapshots available to the user.
+// The ids and filter parameters, if provided, limit the snapshots returned.
+//
+// See http://goo.gl/ogJL4 for more details.
+func (ec2 *EC2) Snapshots(ids []string, filter *Filter) (resp *SnapshotsResp, err error) {
+	params := makeParams("DescribeSnapshots")
+	for i, id := range ids {
+		params["SnapshotId."+strconv.Itoa(i+1)] = id
+	}
+	filter.addParams(params)
+
+	resp = &SnapshotsResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+// ----------------------------------------------------------------------------
+// KeyPair management functions and types.
+
+type KeyPair struct {
+	Name        string `xml:"keyName"`
+	Fingerprint string `xml:"keyFingerprint"`
+}
+
+type KeyPairsResp struct {
+	RequestId string    `xml:"requestId"`
+	Keys      []KeyPair `xml:"keySet>item"`
+}
+
+type CreateKeyPairResp struct {
+	RequestId      string `xml:"requestId"`
+	KeyName        string `xml:"keyName"`
+	KeyFingerprint string `xml:"keyFingerprint"`
+	KeyMaterial    string `xml:"keyMaterial"`
+}
+
+type ImportKeyPairResponse struct {
+	RequestId      string `xml:"requestId"`
+	KeyName        string `xml:"keyName"`
+	KeyFingerprint string `xml:"keyFingerprint"`
+}
+
+// CreateKeyPair creates a new key pair and returns the private key contents.
+//
+// See http://goo.gl/0S6hV
+func (ec2 *EC2) CreateKeyPair(keyName string) (resp *CreateKeyPairResp, err error) {
+	params := makeParams("CreateKeyPair")
+	params["KeyName"] = keyName
+
+	resp = &CreateKeyPairResp{}
+	err = ec2.query(params, resp)
+	if err == nil {
+		resp.KeyFingerprint = strings.TrimSpace(resp.KeyFingerprint)
+	}
+	return
+}
+
+// DeleteKeyPair deletes a key pair.
+//
+// See http://goo.gl/0bqok
+func (ec2 *EC2) DeleteKeyPair(name string) (resp *SimpleResp, err error) {
+	params := makeParams("DeleteKeyPair")
+	params["KeyName"] = name
+
+	resp = &SimpleResp{}
+	err = ec2.query(params, resp)
+	return
+}
+
+// KeyPairs returns list of key pairs for this account
+//
+// See http://goo.gl/Apzsfz
+func (ec2 *EC2) KeyPairs(keynames []string, filter *Filter) (resp *KeyPairsResp, err error) {
+	params := makeParams("DescribeKeyPairs")
+	for i, name := range keynames {
+		params["KeyName."+strconv.Itoa(i)] = name
+	}
+	filter.addParams(params)
+
+	resp = &KeyPairsResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+
+	return resp, nil
+}
+
+// ImportKeyPair imports a key into AWS
+//
+// See http://goo.gl/NbZUvw
+func (ec2 *EC2) ImportKeyPair(keyname string, key string) (resp *ImportKeyPairResponse, err error) {
+	params := makeParams("ImportKeyPair")
+	params["KeyName"] = keyname
+
+	// Oddly, AWS requires the key material to be base64-encoded, even if it was
+	// already encoded. So, we force another round of encoding...
+	// c.f. https://groups.google.com/forum/?fromgroups#!topic/boto-dev/IczrStO9Q8M
+	params["PublicKeyMaterial"] = base64.StdEncoding.EncodeToString([]byte(key))
+
+	resp = &ImportKeyPairResponse{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return resp, nil
+}
+
+// ----------------------------------------------------------------------------
+// Security group management functions and types.
+
+// SimpleResp represents a response to an EC2 request which on success will
+// return no other information besides a request id.
+type SimpleResp struct {
+	XMLName   xml.Name
+	RequestId string `xml:"requestId"`
+}
+
+// CreateSecurityGroupResp represents a response to a CreateSecurityGroup request.
+type CreateSecurityGroupResp struct {
+	SecurityGroup
+	RequestId string `xml:"requestId"`
+}
+
+// CreateSecurityGroup run a CreateSecurityGroup request in EC2, with the provided
+// name and description.
+//
+// See http://goo.gl/Eo7Yl for more details.
+func (ec2 *EC2) CreateSecurityGroup(group SecurityGroup) (resp *CreateSecurityGroupResp, err error) {
+	params := makeParams("CreateSecurityGroup")
+	params["GroupName"] = group.Name
+	params["GroupDescription"] = group.Description
+	if group.VpcId != "" {
+		params["VpcId"] = group.VpcId
+	}
+
+	resp = &CreateSecurityGroupResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	resp.Name = group.Name
+	return resp, nil
+}
+
+// SecurityGroupsResp represents a response to a DescribeSecurityGroups
+// request in EC2.
+//
+// See http://goo.gl/k12Uy for more details.
+type SecurityGroupsResp struct {
+	RequestId string              `xml:"requestId"`
+	Groups    []SecurityGroupInfo `xml:"securityGroupInfo>item"`
+}
+
+// SecurityGroup encapsulates details for a security group in EC2.
+//
+// See http://goo.gl/CIdyP for more details.
+type SecurityGroupInfo struct {
+	SecurityGroup
+	OwnerId       string   `xml:"ownerId"`
+	Description   string   `xml:"groupDescription"`
+	IPPerms       []IPPerm `xml:"ipPermissions>item"`
+	IPPermsEgress []IPPerm `xml:"ipPermissionsEgress>item"`
+}
+
+// IPPerm represents an allowance within an EC2 security group.
+//
+// See http://goo.gl/4oTxv for more details.
+type IPPerm struct {
+	Protocol     string              `xml:"ipProtocol"`
+	FromPort     int                 `xml:"fromPort"`
+	ToPort       int                 `xml:"toPort"`
+	SourceIPs    []string            `xml:"ipRanges>item>cidrIp"`
+	SourceGroups []UserSecurityGroup `xml:"groups>item"`
+}
+
+// UserSecurityGroup holds a security group and the owner
+// of that group.
+type UserSecurityGroup struct {
+	Id      string `xml:"groupId"`
+	Name    string `xml:"groupName"`
+	OwnerId string `xml:"userId"`
+}
+
+// SecurityGroup represents an EC2 security group.
+// If SecurityGroup is used as a parameter, then one of Id or Name
+// may be empty. If both are set, then Id is used.
+type SecurityGroup struct {
+	Id          string `xml:"groupId"`
+	Name        string `xml:"groupName"`
+	Description string `xml:"groupDescription"`
+	VpcId       string `xml:"vpcId"`
+	Tags        []Tag  `xml:"tagSet>item"`
+}
+
+// SecurityGroupNames is a convenience function that
+// returns a slice of security groups with the given names.
+func SecurityGroupNames(names ...string) []SecurityGroup {
+	g := make([]SecurityGroup, len(names))
+	for i, name := range names {
+		g[i] = SecurityGroup{Name: name}
+	}
+	return g
+}
+
+// SecurityGroupNames is a convenience function that
+// returns a slice of security groups with the given ids.
+func SecurityGroupIds(ids ...string) []SecurityGroup {
+	g := make([]SecurityGroup, len(ids))
+	for i, id := range ids {
+		g[i] = SecurityGroup{Id: id}
+	}
+	return g
+}
+
+// SecurityGroups returns details about security groups in EC2.  Both parameters
+// are optional, and if provided will limit the security groups returned to those
+// matching the given groups or filtering rules.
+//
+// See http://goo.gl/k12Uy for more details.
+func (ec2 *EC2) SecurityGroups(groups []SecurityGroup, filter *Filter) (resp *SecurityGroupsResp, err error) {
+	params := makeParams("DescribeSecurityGroups")
+	i, j := 1, 1
+	for _, g := range groups {
+		if g.Id != "" {
+			params["GroupId."+strconv.Itoa(i)] = g.Id
+			i++
+		} else {
+			params["GroupName."+strconv.Itoa(j)] = g.Name
+			j++
+		}
+	}
+	filter.addParams(params)
+
+	resp = &SecurityGroupsResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return resp, nil
+}
+
+// DeleteSecurityGroup removes the given security group in EC2.
+//
+// See http://goo.gl/QJJDO for more details.
+func (ec2 *EC2) DeleteSecurityGroup(group SecurityGroup) (resp *SimpleResp, err error) {
+	params := makeParams("DeleteSecurityGroup")
+	if group.Id != "" {
+		params["GroupId"] = group.Id
+	} else {
+		params["GroupName"] = group.Name
+	}
+
+	resp = &SimpleResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return resp, nil
+}
+
+// AuthorizeSecurityGroup creates an allowance for clients matching the provided
+// rules to access instances within the given security group.
+//
+// See http://goo.gl/u2sDJ for more details.
+func (ec2 *EC2) AuthorizeSecurityGroup(group SecurityGroup, perms []IPPerm) (resp *SimpleResp, err error) {
+	return ec2.authOrRevoke("AuthorizeSecurityGroupIngress", group, perms)
+}
+
+// AuthorizeSecurityGroupEgress creates an allowance for clients matching the provided
+// rules for egress access.
+//
+// See http://goo.gl/UHnH4L for more details.
+func (ec2 *EC2) AuthorizeSecurityGroupEgress(group SecurityGroup, perms []IPPerm) (resp *SimpleResp, err error) {
+	return ec2.authOrRevoke("AuthorizeSecurityGroupEgress", group, perms)
+}
+
+// RevokeSecurityGroup revokes permissions from a group.
+//
+// See http://goo.gl/ZgdxA for more details.
+func (ec2 *EC2) RevokeSecurityGroup(group SecurityGroup, perms []IPPerm) (resp *SimpleResp, err error) {
+	return ec2.authOrRevoke("RevokeSecurityGroupIngress", group, perms)
+}
+
+// RevokeSecurityGroupEgress revokes egress permissions from a group
+//
+// see http://goo.gl/Zv4wh8
+func (ec2 *EC2) RevokeSecurityGroupEgress(group SecurityGroup, perms []IPPerm) (resp *SimpleResp, err error) {
+	return ec2.authOrRevoke("RevokeSecurityGroupEgress", group, perms)
+}
+
+func (ec2 *EC2) authOrRevoke(op string, group SecurityGroup, perms []IPPerm) (resp *SimpleResp, err error) {
+	params := makeParams(op)
+	if group.Id != "" {
+		params["GroupId"] = group.Id
+	} else {
+		params["GroupName"] = group.Name
+	}
+
+	for i, perm := range perms {
+		prefix := "IpPermissions." + strconv.Itoa(i+1)
+		params[prefix+".IpProtocol"] = perm.Protocol
+		params[prefix+".FromPort"] = strconv.Itoa(perm.FromPort)
+		params[prefix+".ToPort"] = strconv.Itoa(perm.ToPort)
+		for j, ip := range perm.SourceIPs {
+			params[prefix+".IpRanges."+strconv.Itoa(j+1)+".CidrIp"] = ip
+		}
+		for j, g := range perm.SourceGroups {
+			subprefix := prefix + ".Groups." + strconv.Itoa(j+1)
+			if g.OwnerId != "" {
+				params[subprefix+".UserId"] = g.OwnerId
+			}
+			if g.Id != "" {
+				params[subprefix+".GroupId"] = g.Id
+			} else {
+				params[subprefix+".GroupName"] = g.Name
+			}
+		}
+	}
+
+	resp = &SimpleResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return resp, nil
+}
+
+// ResourceTag represents key-value metadata used to classify and organize
+// EC2 instances.
+//
+// See http://goo.gl/bncl3 for more details
+type Tag struct {
+	Key   string `xml:"key"`
+	Value string `xml:"value"`
+}
+
+// CreateTags adds or overwrites one or more tags for the specified taggable resources.
+// For a list of tagable resources, see: http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Using_Tags.html
+//
+// See http://goo.gl/Vmkqc for more details
+func (ec2 *EC2) CreateTags(resourceIds []string, tags []Tag) (resp *SimpleResp, err error) {
+	params := makeParams("CreateTags")
+	addParamsList(params, "ResourceId", resourceIds)
+
+	for j, tag := range tags {
+		params["Tag."+strconv.Itoa(j+1)+".Key"] = tag.Key
+		params["Tag."+strconv.Itoa(j+1)+".Value"] = tag.Value
+	}
+
+	resp = &SimpleResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return resp, nil
+}
+
+// DeleteTags deletes tags.
+func (ec2 *EC2) DeleteTags(resourceIds []string, tags []Tag) (resp *SimpleResp, err error) {
+	params := makeParams("DeleteTags")
+	addParamsList(params, "ResourceId", resourceIds)
+
+	for j, tag := range tags {
+		params["Tag."+strconv.Itoa(j+1)+".Key"] = tag.Key
+
+		if tag.Value != "" {
+			params["Tag."+strconv.Itoa(j+1)+".Value"] = tag.Value
+		}
+	}
+
+	resp = &SimpleResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+
+	return resp, nil
+}
+
+type TagsResp struct {
+	RequestId string        `xml:"requestId"`
+	Tags      []ResourceTag `xml:"tagSet>item"`
+}
+
+type ResourceTag struct {
+	Tag
+	ResourceId   string `xml:"resourceId"`
+	ResourceType string `xml:"resourceType"`
+}
+
+func (ec2 *EC2) Tags(filter *Filter) (*TagsResp, error) {
+	params := makeParams("DescribeTags")
+	filter.addParams(params)
+
+	resp := &TagsResp{}
+	if err := ec2.query(params, resp); err != nil {
+		return nil, err
+	}
+
+	return resp, nil
+}
+
+// Response to a StartInstances request.
+//
+// See http://goo.gl/awKeF for more details.
+type StartInstanceResp struct {
+	RequestId    string                `xml:"requestId"`
+	StateChanges []InstanceStateChange `xml:"instancesSet>item"`
+}
+
+// Response to a StopInstances request.
+//
+// See http://goo.gl/436dJ for more details.
+type StopInstanceResp struct {
+	RequestId    string                `xml:"requestId"`
+	StateChanges []InstanceStateChange `xml:"instancesSet>item"`
+}
+
+// StartInstances starts an Amazon EBS-backed AMI that you've previously stopped.
+//
+// See http://goo.gl/awKeF for more details.
+func (ec2 *EC2) StartInstances(ids ...string) (resp *StartInstanceResp, err error) {
+	params := makeParams("StartInstances")
+	addParamsList(params, "InstanceId", ids)
+	resp = &StartInstanceResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return resp, nil
+}
+
+// StopInstances requests stopping one or more Amazon EBS-backed instances.
+//
+// See http://goo.gl/436dJ for more details.
+func (ec2 *EC2) StopInstances(ids ...string) (resp *StopInstanceResp, err error) {
+	params := makeParams("StopInstances")
+	addParamsList(params, "InstanceId", ids)
+	resp = &StopInstanceResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return resp, nil
+}
+
+// RebootInstance requests a reboot of one or more instances. This operation is asynchronous;
+// it only queues a request to reboot the specified instance(s). The operation will succeed
+// if the instances are valid and belong to you.
+//
+// Requests to reboot terminated instances are ignored.
+//
+// See http://goo.gl/baoUf for more details.
+func (ec2 *EC2) RebootInstances(ids ...string) (resp *SimpleResp, err error) {
+	params := makeParams("RebootInstances")
+	addParamsList(params, "InstanceId", ids)
+	resp = &SimpleResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return resp, nil
+}
+
+// The ModifyInstanceAttribute request parameters.
+type ModifyInstance struct {
+	InstanceType          string
+	BlockDevices          []BlockDeviceMapping
+	DisableAPITermination bool
+	EbsOptimized          bool
+	SecurityGroups        []SecurityGroup
+	ShutdownBehavior      string
+	KernelId              string
+	RamdiskId             string
+	SourceDestCheck       bool
+	SriovNetSupport       bool
+	UserData              []byte
+
+	SetSourceDestCheck bool
+}
+
+// Response to a ModifyInstanceAttribute request.
+//
+// http://goo.gl/icuXh5 for more details.
+type ModifyInstanceResp struct {
+	RequestId string `xml:"requestId"`
+	Return    bool   `xml:"return"`
+}
+
+// ModifyImageAttribute modifies the specified attribute of the specified instance.
+// You can specify only one attribute at a time. To modify some attributes, the
+// instance must be stopped.
+//
+// See http://goo.gl/icuXh5 for more details.
+func (ec2 *EC2) ModifyInstance(instId string, options *ModifyInstance) (resp *ModifyInstanceResp, err error) {
+	params := makeParams("ModifyInstanceAttribute")
+	params["InstanceId"] = instId
+	addBlockDeviceParams("", params, options.BlockDevices)
+
+	if options.InstanceType != "" {
+		params["InstanceType.Value"] = options.InstanceType
+	}
+
+	if options.DisableAPITermination {
+		params["DisableApiTermination.Value"] = "true"
+	}
+
+	if options.EbsOptimized {
+		params["EbsOptimized"] = "true"
+	}
+
+	if options.ShutdownBehavior != "" {
+		params["InstanceInitiatedShutdownBehavior.Value"] = options.ShutdownBehavior
+	}
+
+	if options.KernelId != "" {
+		params["Kernel.Value"] = options.KernelId
+	}
+
+	if options.RamdiskId != "" {
+		params["Ramdisk.Value"] = options.RamdiskId
+	}
+
+	if options.SourceDestCheck || options.SetSourceDestCheck {
+		if options.SourceDestCheck {
+			params["SourceDestCheck.Value"] = "true"
+		} else {
+			params["SourceDestCheck.Value"] = "false"
+		}
+	}
+
+	if options.SriovNetSupport {
+		params["SriovNetSupport.Value"] = "simple"
+	}
+
+	if options.UserData != nil {
+		userData := make([]byte, b64.EncodedLen(len(options.UserData)))
+		b64.Encode(userData, options.UserData)
+		params["UserData"] = string(userData)
+	}
+
+	i := 1
+	for _, g := range options.SecurityGroups {
+		if g.Id != "" {
+			params["GroupId."+strconv.Itoa(i)] = g.Id
+			i++
+		}
+	}
+
+	resp = &ModifyInstanceResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		resp = nil
+	}
+	return
+}
+
+// ----------------------------------------------------------------------------
+// VPC management functions and types.
+
+// The CreateVpc request parameters
+//
+// See http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-CreateVpc.html
+type CreateVpc struct {
+	CidrBlock       string
+	InstanceTenancy string
+}
+
+// Response to a CreateVpc request
+type CreateVpcResp struct {
+	RequestId string `xml:"requestId"`
+	VPC       VPC    `xml:"vpc"`
+}
+
+// The ModifyVpcAttribute request parameters.
+//
+// See http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/index.html?ApiReference-query-DescribeVpcAttribute.html for more details.
+type ModifyVpcAttribute struct {
+	EnableDnsSupport   bool
+	EnableDnsHostnames bool
+
+	SetEnableDnsSupport   bool
+	SetEnableDnsHostnames bool
+}
+
+// Response to a DescribeVpcAttribute request.
+//
+// See http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/index.html?ApiReference-query-DescribeVpcAttribute.html for more details.
+type VpcAttributeResp struct {
+	RequestId          string `xml:"requestId"`
+	VpcId              string `xml:"vpcId"`
+	EnableDnsSupport   bool   `xml:"enableDnsSupport>value"`
+	EnableDnsHostnames bool   `xml:"enableDnsHostnames>value"`
+}
+
+// CreateInternetGateway request parameters.
+//
+// http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-CreateInternetGateway.html
+type CreateInternetGateway struct{}
+
+// CreateInternetGateway response
+type CreateInternetGatewayResp struct {
+	RequestId       string          `xml:"requestId"`
+	InternetGateway InternetGateway `xml:"internetGateway"`
+}
+
+// The CreateVpcPeeringConnection request parameters
+//
+// http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CreateVpcPeeringConnection.html
+type CreateVpcPeeringConnection struct {
+	PeerOwnerId string
+	PeerVpcId   string
+	VpcId       string
+}
+
+// Response to a CreateVpcPeeringConnection
+type CreateVpcPeeringConnectionResp struct {
+	RequestId            string               `xml:"requestId"`
+	VpcPeeringConnection VpcPeeringConnection `xml:"vpcPeeringConnection"`
+}
+
+// The AcceptVpcPeeringConnection request parameters
+//
+// http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_AcceptVpcPeeringConnection.html
+type AcceptVpcPeeringConnection struct {
+	VpcPeeringConnectionId string `xml:"vpcPeeringConnectionId"`
+}
+
+// Response to a AcceptVpcPeeringConnection request.
+type AcceptVpcPeeringConnectionResp struct {
+	RequestId            string               `xml:"requestId"`
+	VpcPeeringConnection VpcPeeringConnection `xml:"vpcPeeringConnection"`
+}
+
+// The DeleteVpcPeeringConnection request
+//
+// http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DeleteVpcPeeringConnection.html
+type DeleteVpcPeeringConnection struct {
+	VpcPeeringConnectionId string `xml:"vpcPeeringConnectionId"`
+}
+
+// Response to a DeleteVpcPeeringConnection request.
+type DeleteVpcPeeringConnectionResp struct {
+	RequestId string `xml:"requestId"`
+}
+
+// Response to a DescribeVpcPeeringConnection request.
+type DescribeVpcPeeringConnectionResp struct {
+	RequestId             string                 `xml:"requestId"`
+	VpcPeeringConnections []VpcPeeringConnection `xml:"vpcPeeringConnectionSet>item"`
+}
+
+// The RejectVpcPeeringConnection request
+//
+// http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_RejectVpcPeeringConnection.html
+type RejectVpcPeeringConnection struct {
+	VpcPeeringConnectionId string `xml:"vpcPeeringConnectionId"`
+}
+
+// Response to a RejectVpcPeeringConnection request.
+type RejectVpcPeeringConnectionResp struct {
+	RequestId string `xml:"requestId"`
+}
+
+// The CreateRouteTable request parameters.
+//
+// http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-CreateRouteTable.html
+type CreateRouteTable struct {
+	VpcId string
+}
+
+// Response to a CreateRouteTable request.
+type CreateRouteTableResp struct {
+	RequestId  string     `xml:"requestId"`
+	RouteTable RouteTable `xml:"routeTable"`
+}
+
+// CreateRoute request parameters
+//
+// http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-CreateRoute.html
+type CreateRoute struct {
+	RouteTableId           string
+	DestinationCidrBlock   string
+	GatewayId              string
+	InstanceId             string
+	NetworkInterfaceId     string
+	VpcPeeringConnectionId string
+}
+type ReplaceRoute struct {
+	RouteTableId           string
+	DestinationCidrBlock   string
+	GatewayId              string
+	InstanceId             string
+	NetworkInterfaceId     string
+	VpcPeeringConnectionId string
+}
+
+type AssociateRouteTableResp struct {
+	RequestId     string `xml:"requestId"`
+	AssociationId string `xml:"associationId"`
+}
+type ReassociateRouteTableResp struct {
+	RequestId     string `xml:"requestId"`
+	AssociationId string `xml:"newAssociationId"`
+}
+
+// The CreateDhcpOptions request parameters
+//
+// http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CreateDhcpOptions.html
+type CreateDhcpOptions struct {
+	DomainNameServers  string
+	DomainName         string
+	NtpServers         string
+	NetbiosNameServers string
+	NetbiosNodeType    string
+}
+
+// Response to a CreateDhcpOptions request
+type CreateDhcpOptionsResp struct {
+	RequestId   string      `xml:"requestId"`
+	DhcpOptions DhcpOptions `xml:"dhcpOptions"`
+}
+
+// The CreateSubnet request parameters
+//
+// http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-CreateSubnet.html
+type CreateSubnet struct {
+	VpcId            string
+	CidrBlock        string
+	AvailabilityZone string
+}
+
+// Response to a CreateSubnet request
+type CreateSubnetResp struct {
+	RequestId string `xml:"requestId"`
+	Subnet    Subnet `xml:"subnet"`
+}
+
+// The ModifySubnetAttribute request parameters
+//
+// http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-ModifySubnetAttribute.html
+type ModifySubnetAttribute struct {
+	SubnetId            string
+	MapPublicIpOnLaunch bool
+}
+
+type ModifySubnetAttributeResp struct {
+	RequestId string `xml:"requestId"`
+	Return    bool   `xml:"return"`
+}
+
+// The CreateNetworkAcl request parameters
+//
+// http://goo.gl/BZmCRF
+type CreateNetworkAcl struct {
+	VpcId string
+}
+
+// Response to a CreateNetworkAcl request
+type CreateNetworkAclResp struct {
+	RequestId  string     `xml:"requestId"`
+	NetworkAcl NetworkAcl `xml:"networkAcl"`
+}
+
+// Response to CreateNetworkAclEntry request
+type CreateNetworkAclEntryResp struct {
+	RequestId string `xml:"requestId"`
+	Return    bool   `xml:"return"`
+}
+
+// Response to a DescribeInternetGateways request.
+type InternetGatewaysResp struct {
+	RequestId        string            `xml:"requestId"`
+	InternetGateways []InternetGateway `xml:"internetGatewaySet>item"`
+}
+
+// Response to a DescribeRouteTables request.
+type RouteTablesResp struct {
+	RequestId   string       `xml:"requestId"`
+	RouteTables []RouteTable `xml:"routeTableSet>item"`
+}
+
+// Response to a DescribeVpcs request.
+type VpcsResp struct {
+	RequestId string `xml:"requestId"`
+	VPCs      []VPC  `xml:"vpcSet>item"`
+}
+
+// Internet Gateway
+type InternetGateway struct {
+	InternetGatewayId string                      `xml:"internetGatewayId"`
+	Attachments       []InternetGatewayAttachment `xml:"attachmentSet>item"`
+	Tags              []Tag                       `xml:"tagSet>item"`
+}
+
+type InternetGatewayAttachment struct {
+	VpcId string `xml:"vpcId"`
+	State string `xml:"state"`
+}
+
+// vpc peering
+type VpcPeeringConnection struct {
+	AccepterVpcInfo        VpcPeeringConnectionVpcInfo     `xml:"accepterVpcInfo"`
+	ExpirationTime         string                          `xml:"expirationTime"`
+	RequesterVpcInfo       VpcPeeringConnectionVpcInfo     `xml:"requesterVpcInfo"`
+	Status                 VpcPeeringConnectionStateReason `xml:"status"`
+	Tags                   []Tag                           `xml:"tagSet>item"`
+	VpcPeeringConnectionId string                          `xml:"vpcPeeringConnectionId"`
+}
+
+type VpcPeeringConnectionVpcInfo struct {
+	CidrBlock string `xml:"cidrBlock"`
+	OwnerId   string `xml:"ownerId"`
+	VpcId     string `xml:"vpcId"`
+}
+
+type VpcPeeringConnectionStateReason struct {
+	Code    string `xml:"code"`
+	Message string `xml:"message"`
+}
+
+// Routing Table
+type RouteTable struct {
+	RouteTableId string                  `xml:"routeTableId"`
+	VpcId        string                  `xml:"vpcId"`
+	Associations []RouteTableAssociation `xml:"associationSet>item"`
+	Routes       []Route                 `xml:"routeSet>item"`
+	Tags         []Tag                   `xml:"tagSet>item"`
+}
+
+type RouteTableAssociation struct {
+	AssociationId string `xml:"routeTableAssociationId"`
+	RouteTableId  string `xml:"routeTableId"`
+	SubnetId      string `xml:"subnetId"`
+	Main          bool   `xml:"main"`
+}
+
+type Route struct {
+	DestinationCidrBlock   string `xml:"destinationCidrBlock"`
+	GatewayId              string `xml:"gatewayId"`
+	InstanceId             string `xml:"instanceId"`
+	InstanceOwnerId        string `xml:"instanceOwnerId"`
+	NetworkInterfaceId     string `xml:"networkInterfaceId"`
+	State                  string `xml:"state"`
+	Origin                 string `xml:"origin"`
+	VpcPeeringConnectionId string `xml:"vpcPeeringConnectionId"`
+}
+
+// Subnet
+type Subnet struct {
+	SubnetId                string `xml:"subnetId"`
+	State                   string `xml:"state"`
+	VpcId                   string `xml:"vpcId"`
+	CidrBlock               string `xml:"cidrBlock"`
+	AvailableIpAddressCount int    `xml:"availableIpAddressCount"`
+	AvailabilityZone        string `xml:"availabilityZone"`
+	DefaultForAZ            bool   `xml:"defaultForAz"`
+	MapPublicIpOnLaunch     bool   `xml:"mapPublicIpOnLaunch"`
+	Tags                    []Tag  `xml:"tagSet>item"`
+}
+
+// DhcpOptions
+type DhcpOptions struct {
+	DhcpOptionsId         string               `xml:"dhcpOptionsId"`
+	DhcpConfigurationSets DhcpConfigurationSet `xml:"dhcpConfigurationSet"`
+}
+
+type DhcpConfigurationSet struct {
+	Tags []Tag `xml:"dhcpConfigurationSet>item"`
+}
+
+// NetworkAcl represent network acl
+type NetworkAcl struct {
+	NetworkAclId   string                  `xml:"networkAclId"`
+	VpcId          string                  `xml:"vpcId"`
+	Default        string                  `xml:"default"`
+	EntrySet       []NetworkAclEntry       `xml:"entrySet>item"`
+	AssociationSet []NetworkAclAssociation `xml:"associationSet>item"`
+	Tags           []Tag                   `xml:"tagSet>item"`
+}
+
+// NetworkAclAssociation
+type NetworkAclAssociation struct {
+	NetworkAclAssociationId string `xml:"networkAclAssociationId"`
+	NetworkAclId            string `xml:"networkAclId"`
+	SubnetId                string `xml:"subnetId"`
+}
+
+// NetworkAclEntry represent a rule within NetworkAcl
+type NetworkAclEntry struct {
+	RuleNumber int       `xml:"ruleNumber"`
+	Protocol   int       `xml:"protocol"`
+	RuleAction string    `xml:"ruleAction"`
+	Egress     bool      `xml:"egress"`
+	CidrBlock  string    `xml:"cidrBlock"`
+	IcmpCode   IcmpCode  `xml:"icmpTypeCode"`
+	PortRange  PortRange `xml:"portRange"`
+}
+
+// IcmpCode
+type IcmpCode struct {
+	Code int `xml:"code"`
+	Type int `xml:"type"`
+}
+
+// PortRange
+type PortRange struct {
+	From int `xml:"from"`
+	To   int `xml:"to"`
+}
+
+// Response to describe NetworkAcls
+type NetworkAclsResp struct {
+	RequestId   string       `xml:"requestId"`
+	NetworkAcls []NetworkAcl `xml:"networkAclSet>item"`
+}
+
+// VPC represents a single VPC.
+type VPC struct {
+	VpcId           string `xml:"vpcId"`
+	State           string `xml:"state"`
+	CidrBlock       string `xml:"cidrBlock"`
+	DHCPOptionsID   string `xml:"dhcpOptionsId"`
+	InstanceTenancy string `xml:"instanceTenancy"`
+	IsDefault       bool   `xml:"isDefault"`
+	Tags            []Tag  `xml:"tagSet>item"`
+}
+
+// Response to a DescribeSubnets request.
+type SubnetsResp struct {
+	RequestId string   `xml:"requestId"`
+	Subnets   []Subnet `xml:"subnetSet>item"`
+}
+
+// Create a new VPC.
+func (ec2 *EC2) CreateVpc(options *CreateVpc) (resp *CreateVpcResp, err error) {
+	params := makeParams("CreateVpc")
+	params["CidrBlock"] = options.CidrBlock
+
+	if options.InstanceTenancy != "" {
+		params["InstanceTenancy"] = options.InstanceTenancy
+	}
+
+	resp = &CreateVpcResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+
+	return
+}
+
+// Delete a VPC.
+func (ec2 *EC2) DeleteVpc(id string) (resp *SimpleResp, err error) {
+	params := makeParams("DeleteVpc")
+	params["VpcId"] = id
+
+	resp = &SimpleResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+// DescribeVpcs
+//
+// See http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DescribeVpcs.html
+func (ec2 *EC2) DescribeVpcs(ids []string, filter *Filter) (resp *VpcsResp, err error) {
+	params := makeParams("DescribeVpcs")
+	addParamsList(params, "VpcId", ids)
+	filter.addParams(params)
+	resp = &VpcsResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+
+	return
+}
+
+// VpcAttribute describes an attribute of a VPC.
+// You can specify only one attribute at a time.
+// Valid attributes are:
+//    enableDnsSupport | enableDnsHostnames
+//
+// See http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/index.html?ApiReference-query-DescribeVpcAttribute.html for more details.
+func (ec2 *EC2) VpcAttribute(vpcId, attribute string) (resp *VpcAttributeResp, err error) {
+	params := makeParams("DescribeVpcAttribute")
+	params["VpcId"] = vpcId
+	params["Attribute"] = attribute
+
+	resp = &VpcAttributeResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+// ModifyVpcAttribute modifies the specified attribute of the specified VPC.
+//
+// See http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/index.html?ApiReference-query-ModifyVpcAttribute.html for more details.
+func (ec2 *EC2) ModifyVpcAttribute(vpcId string, options *ModifyVpcAttribute) (*SimpleResp, error) {
+	params := makeParams("ModifyVpcAttribute")
+
+	params["VpcId"] = vpcId
+
+	if options.SetEnableDnsSupport {
+		params["EnableDnsSupport.Value"] = strconv.FormatBool(options.EnableDnsSupport)
+	}
+
+	if options.SetEnableDnsHostnames {
+		params["EnableDnsHostnames.Value"] = strconv.FormatBool(options.EnableDnsHostnames)
+	}
+
+	resp := &SimpleResp{}
+	if err := ec2.query(params, resp); err != nil {
+		return nil, err
+	}
+
+	return resp, nil
+}
+
+// Create a new subnet.
+func (ec2 *EC2) CreateSubnet(options *CreateSubnet) (resp *CreateSubnetResp, err error) {
+	params := makeParams("CreateSubnet")
+	params["AvailabilityZone"] = options.AvailabilityZone
+	params["CidrBlock"] = options.CidrBlock
+	params["VpcId"] = options.VpcId
+
+	resp = &CreateSubnetResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+
+	return
+}
+
+// Delete a Subnet.
+func (ec2 *EC2) DeleteSubnet(id string) (resp *SimpleResp, err error) {
+	params := makeParams("DeleteSubnet")
+	params["SubnetId"] = id
+
+	resp = &SimpleResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+// ModifySubnetAttribute
+//
+// http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-ModifySubnetAttribute.html
+func (ec2 *EC2) ModifySubnetAttribute(options *ModifySubnetAttribute) (resp *ModifySubnetAttributeResp, err error) {
+	params := makeParams("ModifySubnetAttribute")
+	params["SubnetId"] = options.SubnetId
+	if options.MapPublicIpOnLaunch {
+		params["MapPublicIpOnLaunch.Value"] = "true"
+	} else {
+		params["MapPublicIpOnLaunch.Value"] = "false"
+	}
+
+	resp = &ModifySubnetAttributeResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+// DescribeSubnets
+//
+// http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DescribeSubnets.html
+func (ec2 *EC2) DescribeSubnets(ids []string, filter *Filter) (resp *SubnetsResp, err error) {
+	params := makeParams("DescribeSubnets")
+	addParamsList(params, "SubnetId", ids)
+	filter.addParams(params)
+
+	resp = &SubnetsResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+
+	return
+}
+
+// Create DhcpOptions.
+func (ec2 *EC2) CreateDhcpOptions(options *CreateDhcpOptions) (resp *CreateDhcpOptionsResp, err error) {
+	params := makeParams("CreateDhcpOptions")
+	params["DomainNameServers"] = options.DomainNameServers
+	params["DomainName"] = options.DomainName
+	params["NtpServers"] = options.NtpServers
+	params["NetbiosNameServers"] = options.NetbiosNameServers
+	params["NetbiosNodeType"] = options.NetbiosNodeType
+
+	resp = &CreateDhcpOptionsResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+
+	return
+}
+
+// Delete DhcpOptions.
+func (ec2 *EC2) DeleteDhcpOptions(id string) (resp *SimpleResp, err error) {
+	params := makeParams("DeleteDhcpOptions")
+	params["DhcpOptionsId"] = id
+
+	resp = &SimpleResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+// Associate DhcpOptions to a VPC.
+func (ec2 *EC2) AssociateDhcpOptions(dhcpOptionsId string, vpcId string) (resp *SimpleResp, err error) {
+	params := makeParams("AssociateDhcpOptions")
+	params["DhcpOptionsId"] = dhcpOptionsId
+	params["VpcId"] = vpcId
+
+	resp = &SimpleResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+// CreateNetworkAcl creates a network ACL in a VPC.
+//
+// http://goo.gl/51X7db
+func (ec2 *EC2) CreateNetworkAcl(options *CreateNetworkAcl) (resp *CreateNetworkAclResp, err error) {
+	params := makeParams("CreateNetworkAcl")
+	params["VpcId"] = options.VpcId
+
+	resp = &CreateNetworkAclResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+
+	return
+}
+
+// CreateNetworkAclEntry creates an entry (a rule) in a network ACL with the specified rule number.
+//
+// http://goo.gl/BtXhtj
+func (ec2 *EC2) CreateNetworkAclEntry(networkAclId string, options *NetworkAclEntry) (resp *CreateNetworkAclEntryResp, err error) {
+
+	params := makeParams("CreateNetworkAclEntry")
+	params["NetworkAclId"] = networkAclId
+	params["RuleNumber"] = strconv.Itoa(options.RuleNumber)
+	params["Protocol"] = strconv.Itoa(options.Protocol)
+	params["RuleAction"] = options.RuleAction
+	params["Egress"] = strconv.FormatBool(options.Egress)
+	params["CidrBlock"] = options.CidrBlock
+	if params["Protocol"] == "-1" {
+		params["Icmp.Type"] = strconv.Itoa(options.IcmpCode.Type)
+		params["Icmp.Code"] = strconv.Itoa(options.IcmpCode.Code)
+	}
+	params["PortRange.From"] = strconv.Itoa(options.PortRange.From)
+	params["PortRange.To"] = strconv.Itoa(options.PortRange.To)
+
+	resp = &CreateNetworkAclEntryResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+
+	return resp, nil
+}
+
+// NetworkAcls describes one or more of your network ACLs for given filter.
+//
+// http://goo.gl/mk9RsV
+func (ec2 *EC2) NetworkAcls(networkAclIds []string, filter *Filter) (resp *NetworkAclsResp, err error) {
+	params := makeParams("DescribeNetworkAcls")
+	addParamsList(params, "NetworkAclId", networkAclIds)
+	filter.addParams(params)
+	resp = &NetworkAclsResp{}
+	if err = ec2.query(params, resp); err != nil {
+		return nil, err
+	}
+
+	return resp, nil
+}
+
+// Response to a DeleteNetworkAcl request.
+type DeleteNetworkAclResp struct {
+	RequestId string `xml:"requestId"`
+	Return    bool   `xml:"return"`
+}
+
+// DeleteNetworkAcl deletes the network ACL with specified id.
+//
+// http://goo.gl/nC78Wx
+func (ec2 *EC2) DeleteNetworkAcl(id string) (resp *DeleteNetworkAclResp, err error) {
+	params := makeParams("DeleteNetworkAcl")
+	params["NetworkAclId"] = id
+
+	resp = &DeleteNetworkAclResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return resp, nil
+}
+
+// Response to a DeleteNetworkAclEntry request.
+type DeleteNetworkAclEntryResp struct {
+	RequestId string `xml:"requestId"`
+	Return    bool   `xml:"return"`
+}
+
+// DeleteNetworkAclEntry deletes the specified ingress or egress entry (rule) from the specified network ACL.
+//
+// http://goo.gl/moQbE2
+func (ec2 *EC2) DeleteNetworkAclEntry(id string, ruleNumber int, egress bool) (resp *DeleteNetworkAclEntryResp, err error) {
+	params := makeParams("DeleteNetworkAclEntry")
+	params["NetworkAclId"] = id
+	params["RuleNumber"] = strconv.Itoa(ruleNumber)
+	params["Egress"] = strconv.FormatBool(egress)
+
+	resp = &DeleteNetworkAclEntryResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return resp, nil
+}
+
+type ReplaceNetworkAclAssociationResponse struct {
+	RequestId        string `xml:"requestId"`
+	NewAssociationId string `xml:"newAssociationId"`
+}
+
+// ReplaceNetworkAclAssociation changes which network ACL a subnet is associated with.
+//
+// http://goo.gl/ar0MH5
+func (ec2 *EC2) ReplaceNetworkAclAssociation(associationId string, networkAclId string) (resp *ReplaceNetworkAclAssociationResponse, err error) {
+	params := makeParams("ReplaceNetworkAclAssociation")
+	params["NetworkAclId"] = networkAclId
+	params["AssociationId"] = associationId
+
+	resp = &ReplaceNetworkAclAssociationResponse{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return resp, nil
+}
+
+// Create a new internet gateway.
+func (ec2 *EC2) CreateInternetGateway(
+	options *CreateInternetGateway) (resp *CreateInternetGatewayResp, err error) {
+	params := makeParams("CreateInternetGateway")
+
+	resp = &CreateInternetGatewayResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+
+	return
+}
+
+// Attach an InternetGateway.
+func (ec2 *EC2) AttachInternetGateway(id, vpcId string) (resp *SimpleResp, err error) {
+	params := makeParams("AttachInternetGateway")
+	params["InternetGatewayId"] = id
+	params["VpcId"] = vpcId
+
+	resp = &SimpleResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+// Detach an InternetGateway.
+func (ec2 *EC2) DetachInternetGateway(id, vpcId string) (resp *SimpleResp, err error) {
+	params := makeParams("DetachInternetGateway")
+	params["InternetGatewayId"] = id
+	params["VpcId"] = vpcId
+
+	resp = &SimpleResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+// Delete an InternetGateway.
+func (ec2 *EC2) DeleteInternetGateway(id string) (resp *SimpleResp, err error) {
+	params := makeParams("DeleteInternetGateway")
+	params["InternetGatewayId"] = id
+
+	resp = &SimpleResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+// DescribeInternetGateways
+//
+// http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DescribeInternetGateways.html
+func (ec2 *EC2) DescribeInternetGateways(ids []string, filter *Filter) (resp *InternetGatewaysResp, err error) {
+	params := makeParams("DescribeInternetGateways")
+	addParamsList(params, "InternetGatewayId", ids)
+	filter.addParams(params)
+
+	resp = &InternetGatewaysResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+
+	return
+}
+
+// Create a new routing table.
+func (ec2 *EC2) CreateRouteTable(
+	options *CreateRouteTable) (resp *CreateRouteTableResp, err error) {
+	params := makeParams("CreateRouteTable")
+	params["VpcId"] = options.VpcId
+
+	resp = &CreateRouteTableResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+
+	return
+}
+
+// Delete a RouteTable.
+func (ec2 *EC2) DeleteRouteTable(id string) (resp *SimpleResp, err error) {
+	params := makeParams("DeleteRouteTable")
+	params["RouteTableId"] = id
+
+	resp = &SimpleResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+// DescribeRouteTables
+//
+// http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DescribeRouteTables.html
+func (ec2 *EC2) DescribeRouteTables(ids []string, filter *Filter) (resp *RouteTablesResp, err error) {
+	params := makeParams("DescribeRouteTables")
+	addParamsList(params, "RouteTableId", ids)
+	filter.addParams(params)
+
+	resp = &RouteTablesResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+
+	return
+}
+
+// Associate a routing table.
+func (ec2 *EC2) AssociateRouteTable(id, subnetId string) (*AssociateRouteTableResp, error) {
+	params := makeParams("AssociateRouteTable")
+	params["RouteTableId"] = id
+	params["SubnetId"] = subnetId
+
+	resp := &AssociateRouteTableResp{}
+	err := ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return resp, nil
+}
+
+// Disassociate a routing table.
+func (ec2 *EC2) DisassociateRouteTable(id string) (*SimpleResp, error) {
+	params := makeParams("DisassociateRouteTable")
+	params["AssociationId"] = id
+
+	resp := &SimpleResp{}
+	err := ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return resp, nil
+}
+
+// Re-associate a routing table.
+func (ec2 *EC2) ReassociateRouteTable(id, routeTableId string) (*ReassociateRouteTableResp, error) {
+	params := makeParams("ReplaceRouteTableAssociation")
+	params["AssociationId"] = id
+	params["RouteTableId"] = routeTableId
+
+	resp := &ReassociateRouteTableResp{}
+	err := ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return resp, nil
+}
+
+// Create a new route.
+func (ec2 *EC2) CreateRoute(options *CreateRoute) (resp *SimpleResp, err error) {
+	params := makeParams("CreateRoute")
+	params["RouteTableId"] = options.RouteTableId
+	params["DestinationCidrBlock"] = options.DestinationCidrBlock
+
+	if v := options.GatewayId; v != "" {
+		params["GatewayId"] = v
+	}
+	if v := options.InstanceId; v != "" {
+		params["InstanceId"] = v
+	}
+	if v := options.NetworkInterfaceId; v != "" {
+		params["NetworkInterfaceId"] = v
+	}
+	if v := options.VpcPeeringConnectionId; v != "" {
+		params["VpcPeeringConnectionId"] = v
+	}
+
+	resp = &SimpleResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+// Delete a Route.
+func (ec2 *EC2) DeleteRoute(routeTableId, cidr string) (resp *SimpleResp, err error) {
+	params := makeParams("DeleteRoute")
+	params["RouteTableId"] = routeTableId
+	params["DestinationCidrBlock"] = cidr
+
+	resp = &SimpleResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+// Replace a new route.
+func (ec2 *EC2) ReplaceRoute(options *ReplaceRoute) (resp *SimpleResp, err error) {
+	params := makeParams("ReplaceRoute")
+	params["RouteTableId"] = options.RouteTableId
+	params["DestinationCidrBlock"] = options.DestinationCidrBlock
+
+	if v := options.GatewayId; v != "" {
+		params["GatewayId"] = v
+	}
+	if v := options.InstanceId; v != "" {
+		params["InstanceId"] = v
+	}
+	if v := options.NetworkInterfaceId; v != "" {
+		params["NetworkInterfaceId"] = v
+	}
+	if v := options.VpcPeeringConnectionId; v != "" {
+		params["VpcPeeringConnectionId"] = v
+	}
+
+	resp = &SimpleResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+// Create a vpc peering connection
+func (ec2 *EC2) CreateVpcPeeringConnection(
+	options *CreateVpcPeeringConnection) (resp *CreateVpcPeeringConnectionResp, err error) {
+	params := makeParams("CreateVpcPeeringConnection")
+	params["PeerOwnerId"] = options.PeerOwnerId
+	params["PeerVpcId"] = options.PeerVpcId
+	params["VpcId"] = options.VpcId
+
+	resp = &CreateVpcPeeringConnectionResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+// AcceptVpcPeeringConnection
+func (ec2 *EC2) AcceptVpcPeeringConnection(id string) (resp *AcceptVpcPeeringConnectionResp, err error) {
+	params := makeParams("AcceptVpcPeeringConnection")
+	params["VpcPeeringConnectionId"] = id
+
+	resp = &AcceptVpcPeeringConnectionResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+func (ec2 *EC2) DeleteVpcPeeringConnection(id string) (resp *DeleteVpcPeeringConnectionResp, err error) {
+	params := makeParams("DeleteVpcPeeringConnection")
+	params["VpcPeeringConnectionId"] = id
+
+	resp = &DeleteVpcPeeringConnectionResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+// DescribeVpcPeeringConnection
+func (ec2 *EC2) DescribeVpcPeeringConnection(ids []string, filter *Filter) (resp *DescribeVpcPeeringConnectionResp, err error) {
+	params := makeParams("DescribeVpcPeeringConnections")
+	addParamsList(params, "VpcPeeringConnectionId", ids)
+	filter.addParams(params)
+	resp = &DescribeVpcPeeringConnectionResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+
+	return
+}
+
+// RejectVpcPeeringConnection
+func (ec2 *EC2) RejectVpcPeeringConnection(id string) (resp *RejectVpcPeeringConnectionResp, err error) {
+	params := makeParams("RejectVpcPeeringConnection")
+	params["VpcPeeringConnectionId"] = id
+
+	resp = &RejectVpcPeeringConnectionResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+// The ResetImageAttribute request parameters.
+type ResetImageAttribute struct {
+	Attribute string
+}
+
+// ResetImageAttribute resets an attribute of an AMI to its default value.
+//
+// http://goo.gl/r6ZCPm for more details.
+func (ec2 *EC2) ResetImageAttribute(imageId string, options *ResetImageAttribute) (resp *SimpleResp, err error) {
+	params := makeParams("ResetImageAttribute")
+	params["ImageId"] = imageId
+
+	if options.Attribute != "" {
+		params["Attribute"] = options.Attribute
+	}
+
+	resp = &SimpleResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+type CreateCustomerGateway struct {
+	Type      string
+	IpAddress string
+	BgpAsn    int
+}
+
+// Response to a CreateCustomerGateway request
+type CreateCustomerGatewayResp struct {
+	RequestId       string          `xml:"requestId"`
+	CustomerGateway CustomerGateway `xml:"customerGateway"`
+}
+
+type CustomerGateway struct {
+	CustomerGatewayId string `xml:"customerGatewayId"`
+	State             string `xml:"state"`
+	Type              string `xml:"type"`
+	IpAddress         string `xml:"ipAddress"`
+	BgpAsn            int    `xml:"bgpAsn"`
+	Tags              []Tag  `xml:"tagSet>item"`
+}
+
+type DescribeCustomerGatewaysResp struct {
+	RequestId        string            `xml:"requestId"`
+	CustomerGateways []CustomerGateway `xml:"customerGatewaySet>item"`
+}
+
+//Create a customer gateway
+func (ec2 *EC2) CreateCustomerGateway(options *CreateCustomerGateway) (resp *CreateCustomerGatewayResp, err error) {
+	params := makeParams("CreateCustomerGateway")
+	params["Type"] = options.Type
+	params["IpAddress"] = options.IpAddress
+	if options.BgpAsn != 0 {
+		params["BgpAsn"] = strconv.Itoa(options.BgpAsn)
+	}
+
+	resp = &CreateCustomerGatewayResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+func (ec2 *EC2) DescribeCustomerGateways(ids []string, filter *Filter) (resp *DescribeCustomerGatewaysResp, err error) {
+	params := makeParams("DescribeCustomerGateways")
+	addParamsList(params, "CustomerGatewayId", ids)
+	filter.addParams(params)
+
+	resp = &DescribeCustomerGatewaysResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+
+	return
+}
+
+type DeleteCustomerGatewayResp struct {
+	RequestId string `xml:"requestId"`
+	Return    bool   `xml:"return"`
+}
+
+func (ec2 *EC2) DeleteCustomerGateway(customerGatewayId string) (resp *DeleteCustomerGatewayResp, err error) {
+	params := makeParams("DeleteCustomerGateway")
+	params["CustomerGatewayId"] = customerGatewayId
+
+	resp = &DeleteCustomerGatewayResp{}
+	err = ec2.query(params, resp)
+	if err != nil {
+		return nil, err
+	}
+
+	return
+}

+ 1501 - 0
Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/ec2_test.go

@@ -0,0 +1,1501 @@
+package ec2_test
+
+import (
+	"testing"
+
+	"github.com/coreos/flannel/Godeps/_workspace/src/github.com/mitchellh/goamz/aws"
+	"github.com/coreos/flannel/Godeps/_workspace/src/github.com/mitchellh/goamz/ec2"
+	"github.com/mitchellh/goamz/testutil"
+	. "github.com/motain/gocheck"
+)
+
+func Test(t *testing.T) {
+	TestingT(t)
+}
+
+var _ = Suite(&S{})
+
+type S struct {
+	ec2 *ec2.EC2
+}
+
+var testServer = testutil.NewHTTPServer()
+
+func (s *S) SetUpSuite(c *C) {
+	testServer.Start()
+	auth := aws.Auth{"abc", "123", ""}
+	s.ec2 = ec2.NewWithClient(
+		auth,
+		aws.Region{EC2Endpoint: testServer.URL},
+		testutil.DefaultClient,
+	)
+}
+
+func (s *S) TearDownTest(c *C) {
+	testServer.Flush()
+}
+
+func (s *S) TestRunInstancesErrorDump(c *C) {
+	testServer.Response(400, nil, ErrorDump)
+
+	options := ec2.RunInstances{
+		ImageId:      "ami-a6f504cf", // Ubuntu Maverick, i386, instance store
+		InstanceType: "t1.micro",     // Doesn't work with micro, results in 400.
+	}
+
+	msg := `AMIs with an instance-store root device are not supported for the instance type 't1\.micro'\.`
+
+	resp, err := s.ec2.RunInstances(&options)
+
+	testServer.WaitRequest()
+
+	c.Assert(resp, IsNil)
+	c.Assert(err, ErrorMatches, msg+` \(UnsupportedOperation\)`)
+
+	ec2err, ok := err.(*ec2.Error)
+	c.Assert(ok, Equals, true)
+	c.Assert(ec2err.StatusCode, Equals, 400)
+	c.Assert(ec2err.Code, Equals, "UnsupportedOperation")
+	c.Assert(ec2err.Message, Matches, msg)
+	c.Assert(ec2err.RequestId, Equals, "0503f4e9-bbd6-483c-b54f-c4ae9f3b30f4")
+}
+
+func (s *S) TestRequestSpotInstancesErrorDump(c *C) {
+	testServer.Response(400, nil, ErrorDump)
+
+	options := ec2.RequestSpotInstances{
+		SpotPrice:    "0.01",
+		ImageId:      "ami-a6f504cf", // Ubuntu Maverick, i386, instance store
+		InstanceType: "t1.micro",     // Doesn't work with micro, results in 400.
+	}
+
+	msg := `AMIs with an instance-store root device are not supported for the instance type 't1\.micro'\.`
+
+	resp, err := s.ec2.RequestSpotInstances(&options)
+
+	testServer.WaitRequest()
+
+	c.Assert(resp, IsNil)
+	c.Assert(err, ErrorMatches, msg+` \(UnsupportedOperation\)`)
+
+	ec2err, ok := err.(*ec2.Error)
+	c.Assert(ok, Equals, true)
+	c.Assert(ec2err.StatusCode, Equals, 400)
+	c.Assert(ec2err.Code, Equals, "UnsupportedOperation")
+	c.Assert(ec2err.Message, Matches, msg)
+	c.Assert(ec2err.RequestId, Equals, "0503f4e9-bbd6-483c-b54f-c4ae9f3b30f4")
+}
+
+func (s *S) TestRunInstancesErrorWithoutXML(c *C) {
+	testServer.Responses(5, 500, nil, "")
+	options := ec2.RunInstances{ImageId: "image-id"}
+
+	resp, err := s.ec2.RunInstances(&options)
+
+	testServer.WaitRequest()
+
+	c.Assert(resp, IsNil)
+	c.Assert(err, ErrorMatches, "")
+
+	ec2err, ok := err.(*ec2.Error)
+	c.Assert(ok, Equals, true)
+	c.Assert(ec2err.StatusCode, Equals, 500)
+	c.Assert(ec2err.Code, Equals, "")
+	c.Assert(ec2err.Message, Equals, "")
+	c.Assert(ec2err.RequestId, Equals, "")
+}
+
+func (s *S) TestRequestSpotInstancesErrorWithoutXML(c *C) {
+	testServer.Responses(5, 500, nil, "")
+	options := ec2.RequestSpotInstances{SpotPrice: "spot-price", ImageId: "image-id"}
+
+	resp, err := s.ec2.RequestSpotInstances(&options)
+
+	testServer.WaitRequest()
+
+	c.Assert(resp, IsNil)
+	c.Assert(err, ErrorMatches, "")
+
+	ec2err, ok := err.(*ec2.Error)
+	c.Assert(ok, Equals, true)
+	c.Assert(ec2err.StatusCode, Equals, 500)
+	c.Assert(ec2err.Code, Equals, "")
+	c.Assert(ec2err.Message, Equals, "")
+	c.Assert(ec2err.RequestId, Equals, "")
+}
+
+func (s *S) TestRunInstancesExample(c *C) {
+	testServer.Response(200, nil, RunInstancesExample)
+
+	options := ec2.RunInstances{
+		KeyName:               "my-keys",
+		ImageId:               "image-id",
+		InstanceType:          "inst-type",
+		SecurityGroups:        []ec2.SecurityGroup{{Name: "g1"}, {Id: "g2"}, {Name: "g3"}, {Id: "g4"}},
+		UserData:              []byte("1234"),
+		KernelId:              "kernel-id",
+		RamdiskId:             "ramdisk-id",
+		AvailZone:             "zone",
+		Tenancy:               "dedicated",
+		PlacementGroupName:    "group",
+		Monitoring:            true,
+		SubnetId:              "subnet-id",
+		DisableAPITermination: true,
+		EbsOptimized:          true,
+		ShutdownBehavior:      "terminate",
+		PrivateIPAddress:      "10.0.0.25",
+		BlockDevices: []ec2.BlockDeviceMapping{
+			{DeviceName: "/dev/sdb", VirtualName: "ephemeral0"},
+			{DeviceName: "/dev/sdc", SnapshotId: "snap-a08912c9", DeleteOnTermination: true},
+		},
+	}
+	resp, err := s.ec2.RunInstances(&options)
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["Action"], DeepEquals, []string{"RunInstances"})
+	c.Assert(req.Form["ImageId"], DeepEquals, []string{"image-id"})
+	c.Assert(req.Form["MinCount"], DeepEquals, []string{"1"})
+	c.Assert(req.Form["MaxCount"], DeepEquals, []string{"1"})
+	c.Assert(req.Form["KeyName"], DeepEquals, []string{"my-keys"})
+	c.Assert(req.Form["InstanceType"], DeepEquals, []string{"inst-type"})
+	c.Assert(req.Form["SecurityGroup.1"], DeepEquals, []string{"g1"})
+	c.Assert(req.Form["SecurityGroup.2"], DeepEquals, []string{"g3"})
+	c.Assert(req.Form["SecurityGroupId.1"], DeepEquals, []string{"g2"})
+	c.Assert(req.Form["SecurityGroupId.2"], DeepEquals, []string{"g4"})
+	c.Assert(req.Form["UserData"], DeepEquals, []string{"MTIzNA=="})
+	c.Assert(req.Form["KernelId"], DeepEquals, []string{"kernel-id"})
+	c.Assert(req.Form["RamdiskId"], DeepEquals, []string{"ramdisk-id"})
+	c.Assert(req.Form["Placement.AvailabilityZone"], DeepEquals, []string{"zone"})
+	c.Assert(req.Form["Placement.GroupName"], DeepEquals, []string{"group"})
+	c.Assert(req.Form["Monitoring.Enabled"], DeepEquals, []string{"true"})
+	c.Assert(req.Form["SubnetId"], DeepEquals, []string{"subnet-id"})
+	c.Assert(req.Form["DisableApiTermination"], DeepEquals, []string{"true"})
+	c.Assert(req.Form["EbsOptimized"], DeepEquals, []string{"true"})
+	c.Assert(req.Form["InstanceInitiatedShutdownBehavior"], DeepEquals, []string{"terminate"})
+	c.Assert(req.Form["PrivateIpAddress"], DeepEquals, []string{"10.0.0.25"})
+	c.Assert(req.Form["BlockDeviceMapping.1.DeviceName"], DeepEquals, []string{"/dev/sdb"})
+	c.Assert(req.Form["BlockDeviceMapping.1.VirtualName"], DeepEquals, []string{"ephemeral0"})
+	c.Assert(req.Form["BlockDeviceMapping.2.Ebs.SnapshotId"], DeepEquals, []string{"snap-a08912c9"})
+	c.Assert(req.Form["BlockDeviceMapping.2.Ebs.DeleteOnTermination"], DeepEquals, []string{"true"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+	c.Assert(resp.ReservationId, Equals, "r-47a5402e")
+	c.Assert(resp.OwnerId, Equals, "999988887777")
+	c.Assert(resp.SecurityGroups, DeepEquals, []ec2.SecurityGroup{{Name: "default", Id: "sg-67ad940e"}})
+	c.Assert(resp.Instances, HasLen, 3)
+
+	i0 := resp.Instances[0]
+	c.Assert(i0.InstanceId, Equals, "i-2ba64342")
+	c.Assert(i0.InstanceType, Equals, "m1.small")
+	c.Assert(i0.ImageId, Equals, "ami-60a54009")
+	c.Assert(i0.Monitoring, Equals, "enabled")
+	c.Assert(i0.KeyName, Equals, "example-key-name")
+	c.Assert(i0.AMILaunchIndex, Equals, 0)
+	c.Assert(i0.VirtType, Equals, "paravirtual")
+	c.Assert(i0.Hypervisor, Equals, "xen")
+
+	i1 := resp.Instances[1]
+	c.Assert(i1.InstanceId, Equals, "i-2bc64242")
+	c.Assert(i1.InstanceType, Equals, "m1.small")
+	c.Assert(i1.ImageId, Equals, "ami-60a54009")
+	c.Assert(i1.Monitoring, Equals, "enabled")
+	c.Assert(i1.KeyName, Equals, "example-key-name")
+	c.Assert(i1.AMILaunchIndex, Equals, 1)
+	c.Assert(i1.VirtType, Equals, "paravirtual")
+	c.Assert(i1.Hypervisor, Equals, "xen")
+
+	i2 := resp.Instances[2]
+	c.Assert(i2.InstanceId, Equals, "i-2be64332")
+	c.Assert(i2.InstanceType, Equals, "m1.small")
+	c.Assert(i2.ImageId, Equals, "ami-60a54009")
+	c.Assert(i2.Monitoring, Equals, "enabled")
+	c.Assert(i2.KeyName, Equals, "example-key-name")
+	c.Assert(i2.AMILaunchIndex, Equals, 2)
+	c.Assert(i2.VirtType, Equals, "paravirtual")
+	c.Assert(i2.Hypervisor, Equals, "xen")
+}
+
+func (s *S) TestRequestSpotInstancesExample(c *C) {
+	testServer.Response(200, nil, RequestSpotInstancesExample)
+
+	options := ec2.RequestSpotInstances{
+		SpotPrice:          "0.5",
+		KeyName:            "my-keys",
+		ImageId:            "image-id",
+		InstanceType:       "inst-type",
+		SecurityGroups:     []ec2.SecurityGroup{{Name: "g1"}, {Id: "g2"}, {Name: "g3"}, {Id: "g4"}},
+		UserData:           []byte("1234"),
+		KernelId:           "kernel-id",
+		RamdiskId:          "ramdisk-id",
+		AvailZone:          "zone",
+		PlacementGroupName: "group",
+		Monitoring:         true,
+		SubnetId:           "subnet-id",
+		PrivateIPAddress:   "10.0.0.25",
+		BlockDevices: []ec2.BlockDeviceMapping{
+			{DeviceName: "/dev/sdb", VirtualName: "ephemeral0"},
+			{DeviceName: "/dev/sdc", SnapshotId: "snap-a08912c9", DeleteOnTermination: true},
+		},
+	}
+	resp, err := s.ec2.RequestSpotInstances(&options)
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["Action"], DeepEquals, []string{"RequestSpotInstances"})
+	c.Assert(req.Form["SpotPrice"], DeepEquals, []string{"0.5"})
+	c.Assert(req.Form["LaunchSpecification.ImageId"], DeepEquals, []string{"image-id"})
+	c.Assert(req.Form["LaunchSpecification.KeyName"], DeepEquals, []string{"my-keys"})
+	c.Assert(req.Form["LaunchSpecification.InstanceType"], DeepEquals, []string{"inst-type"})
+	c.Assert(req.Form["LaunchSpecification.SecurityGroup.1"], DeepEquals, []string{"g1"})
+	c.Assert(req.Form["LaunchSpecification.SecurityGroup.2"], DeepEquals, []string{"g3"})
+	c.Assert(req.Form["LaunchSpecification.SecurityGroupId.1"], DeepEquals, []string{"g2"})
+	c.Assert(req.Form["LaunchSpecification.SecurityGroupId.2"], DeepEquals, []string{"g4"})
+	c.Assert(req.Form["LaunchSpecification.UserData"], DeepEquals, []string{"MTIzNA=="})
+	c.Assert(req.Form["LaunchSpecification.KernelId"], DeepEquals, []string{"kernel-id"})
+	c.Assert(req.Form["LaunchSpecification.RamdiskId"], DeepEquals, []string{"ramdisk-id"})
+	c.Assert(req.Form["LaunchSpecification.Placement.AvailabilityZone"], DeepEquals, []string{"zone"})
+	c.Assert(req.Form["LaunchSpecification.Placement.GroupName"], DeepEquals, []string{"group"})
+	c.Assert(req.Form["LaunchSpecification.Monitoring.Enabled"], DeepEquals, []string{"true"})
+	c.Assert(req.Form["LaunchSpecification.SubnetId"], DeepEquals, []string{"subnet-id"})
+	c.Assert(req.Form["LaunchSpecification.PrivateIpAddress"], DeepEquals, []string{"10.0.0.25"})
+	c.Assert(req.Form["LaunchSpecification.BlockDeviceMapping.1.DeviceName"], DeepEquals, []string{"/dev/sdb"})
+	c.Assert(req.Form["LaunchSpecification.BlockDeviceMapping.1.VirtualName"], DeepEquals, []string{"ephemeral0"})
+	c.Assert(req.Form["LaunchSpecification.BlockDeviceMapping.2.Ebs.SnapshotId"], DeepEquals, []string{"snap-a08912c9"})
+	c.Assert(req.Form["LaunchSpecification.BlockDeviceMapping.2.Ebs.DeleteOnTermination"], DeepEquals, []string{"true"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+	c.Assert(resp.SpotRequestResults[0].SpotRequestId, Equals, "sir-1a2b3c4d")
+	c.Assert(resp.SpotRequestResults[0].SpotPrice, Equals, "0.5")
+	c.Assert(resp.SpotRequestResults[0].State, Equals, "open")
+	c.Assert(resp.SpotRequestResults[0].SpotLaunchSpec.ImageId, Equals, "ami-1a2b3c4d")
+	c.Assert(resp.SpotRequestResults[0].Status.Code, Equals, "pending-evaluation")
+	c.Assert(resp.SpotRequestResults[0].Status.UpdateTime, Equals, "2008-05-07T12:51:50.000Z")
+	c.Assert(resp.SpotRequestResults[0].Status.Message, Equals, "Your Spot request has been submitted for review, and is pending evaluation.")
+}
+
+func (s *S) TestCancelSpotRequestsExample(c *C) {
+	testServer.Response(200, nil, CancelSpotRequestsExample)
+
+	resp, err := s.ec2.CancelSpotRequests([]string{"s-1", "s-2"})
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["Action"], DeepEquals, []string{"CancelSpotInstanceRequests"})
+	c.Assert(req.Form["SpotInstanceRequestId.1"], DeepEquals, []string{"s-1"})
+	c.Assert(req.Form["SpotInstanceRequestId.2"], DeepEquals, []string{"s-2"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+	c.Assert(resp.CancelSpotRequestResults[0].SpotRequestId, Equals, "sir-1a2b3c4d")
+	c.Assert(resp.CancelSpotRequestResults[0].State, Equals, "cancelled")
+}
+
+func (s *S) TestTerminateInstancesExample(c *C) {
+	testServer.Response(200, nil, TerminateInstancesExample)
+
+	resp, err := s.ec2.TerminateInstances([]string{"i-1", "i-2"})
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["Action"], DeepEquals, []string{"TerminateInstances"})
+	c.Assert(req.Form["InstanceId.1"], DeepEquals, []string{"i-1"})
+	c.Assert(req.Form["InstanceId.2"], DeepEquals, []string{"i-2"})
+	c.Assert(req.Form["UserData"], IsNil)
+	c.Assert(req.Form["KernelId"], IsNil)
+	c.Assert(req.Form["RamdiskId"], IsNil)
+	c.Assert(req.Form["Placement.AvailabilityZone"], IsNil)
+	c.Assert(req.Form["Placement.GroupName"], IsNil)
+	c.Assert(req.Form["Monitoring.Enabled"], IsNil)
+	c.Assert(req.Form["SubnetId"], IsNil)
+	c.Assert(req.Form["DisableApiTermination"], IsNil)
+	c.Assert(req.Form["EbsOptimized"], IsNil)
+	c.Assert(req.Form["InstanceInitiatedShutdownBehavior"], IsNil)
+	c.Assert(req.Form["PrivateIpAddress"], IsNil)
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+	c.Assert(resp.StateChanges, HasLen, 1)
+	c.Assert(resp.StateChanges[0].InstanceId, Equals, "i-3ea74257")
+	c.Assert(resp.StateChanges[0].CurrentState.Code, Equals, 32)
+	c.Assert(resp.StateChanges[0].CurrentState.Name, Equals, "shutting-down")
+	c.Assert(resp.StateChanges[0].PreviousState.Code, Equals, 16)
+	c.Assert(resp.StateChanges[0].PreviousState.Name, Equals, "running")
+}
+
+func (s *S) TestDescribeSpotRequestsExample(c *C) {
+	testServer.Response(200, nil, DescribeSpotRequestsExample)
+
+	filter := ec2.NewFilter()
+	filter.Add("key1", "value1")
+	filter.Add("key2", "value2", "value3")
+
+	resp, err := s.ec2.DescribeSpotRequests([]string{"s-1", "s-2"}, filter)
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeSpotInstanceRequests"})
+	c.Assert(req.Form["SpotInstanceRequestId.1"], DeepEquals, []string{"s-1"})
+	c.Assert(req.Form["SpotInstanceRequestId.2"], DeepEquals, []string{"s-2"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "b1719f2a-5334-4479-b2f1-26926EXAMPLE")
+	c.Assert(resp.SpotRequestResults[0].SpotRequestId, Equals, "sir-1a2b3c4d")
+	c.Assert(resp.SpotRequestResults[0].State, Equals, "active")
+	c.Assert(resp.SpotRequestResults[0].SpotPrice, Equals, "0.5")
+	c.Assert(resp.SpotRequestResults[0].SpotLaunchSpec.ImageId, Equals, "ami-1a2b3c4d")
+	c.Assert(resp.SpotRequestResults[0].Status.Code, Equals, "fulfilled")
+	c.Assert(resp.SpotRequestResults[0].Status.UpdateTime, Equals, "2008-05-07T12:51:50.000Z")
+	c.Assert(resp.SpotRequestResults[0].Status.Message, Equals, "Your Spot request is fulfilled.")
+}
+
+func (s *S) TestDescribeInstancesExample1(c *C) {
+	testServer.Response(200, nil, DescribeInstancesExample1)
+
+	filter := ec2.NewFilter()
+	filter.Add("key1", "value1")
+	filter.Add("key2", "value2", "value3")
+
+	resp, err := s.ec2.Instances([]string{"i-1", "i-2"}, nil)
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeInstances"})
+	c.Assert(req.Form["InstanceId.1"], DeepEquals, []string{"i-1"})
+	c.Assert(req.Form["InstanceId.2"], DeepEquals, []string{"i-2"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "98e3c9a4-848c-4d6d-8e8a-b1bdEXAMPLE")
+	c.Assert(resp.Reservations, HasLen, 2)
+
+	r0 := resp.Reservations[0]
+	c.Assert(r0.ReservationId, Equals, "r-b27e30d9")
+	c.Assert(r0.OwnerId, Equals, "999988887777")
+	c.Assert(r0.RequesterId, Equals, "854251627541")
+	c.Assert(r0.SecurityGroups, DeepEquals, []ec2.SecurityGroup{{Name: "default", Id: "sg-67ad940e"}})
+	c.Assert(r0.Instances, HasLen, 1)
+
+	r0i := r0.Instances[0]
+	c.Assert(r0i.InstanceId, Equals, "i-c5cd56af")
+	c.Assert(r0i.PrivateDNSName, Equals, "domU-12-31-39-10-56-34.compute-1.internal")
+	c.Assert(r0i.DNSName, Equals, "ec2-174-129-165-232.compute-1.amazonaws.com")
+	c.Assert(r0i.AvailZone, Equals, "us-east-1b")
+	c.Assert(r0i.RootDeviceName, Equals, "/dev/sda1")
+
+	b0 := r0i.BlockDevices[0]
+	c.Assert(b0.DeviceName, Equals, "/dev/sda1")
+	c.Assert(b0.VolumeId, Equals, "vol-a082c1c9")
+	c.Assert(b0.Status, Equals, "attached")
+	c.Assert(b0.AttachTime, Equals, "2010-08-17T01:15:21.000Z")
+	c.Assert(b0.DeleteOnTermination, Equals, false)
+}
+
+func (s *S) TestDescribeInstancesExample2(c *C) {
+	testServer.Response(200, nil, DescribeInstancesExample2)
+
+	filter := ec2.NewFilter()
+	filter.Add("key1", "value1")
+	filter.Add("key2", "value2", "value3")
+
+	resp, err := s.ec2.Instances([]string{"i-1", "i-2"}, filter)
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeInstances"})
+	c.Assert(req.Form["InstanceId.1"], DeepEquals, []string{"i-1"})
+	c.Assert(req.Form["InstanceId.2"], DeepEquals, []string{"i-2"})
+	c.Assert(req.Form["Filter.1.Name"], DeepEquals, []string{"key1"})
+	c.Assert(req.Form["Filter.1.Value.1"], DeepEquals, []string{"value1"})
+	c.Assert(req.Form["Filter.1.Value.2"], IsNil)
+	c.Assert(req.Form["Filter.2.Name"], DeepEquals, []string{"key2"})
+	c.Assert(req.Form["Filter.2.Value.1"], DeepEquals, []string{"value2"})
+	c.Assert(req.Form["Filter.2.Value.2"], DeepEquals, []string{"value3"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+	c.Assert(resp.Reservations, HasLen, 1)
+
+	r0 := resp.Reservations[0]
+	r0i := r0.Instances[0]
+	c.Assert(r0i.State.Code, Equals, 16)
+	c.Assert(r0i.State.Name, Equals, "running")
+
+	r0t0 := r0i.Tags[0]
+	r0t1 := r0i.Tags[1]
+	c.Assert(r0t0.Key, Equals, "webserver")
+	c.Assert(r0t0.Value, Equals, "")
+	c.Assert(r0t1.Key, Equals, "stack")
+	c.Assert(r0t1.Value, Equals, "Production")
+}
+
+func (s *S) TestCreateImageExample(c *C) {
+	testServer.Response(200, nil, CreateImageExample)
+
+	options := &ec2.CreateImage{
+		InstanceId:  "i-123456",
+		Name:        "foo",
+		Description: "Test CreateImage",
+		NoReboot:    true,
+		BlockDevices: []ec2.BlockDeviceMapping{
+			{DeviceName: "/dev/sdb", VirtualName: "ephemeral0"},
+			{DeviceName: "/dev/sdc", SnapshotId: "snap-a08912c9", DeleteOnTermination: true},
+		},
+	}
+
+	resp, err := s.ec2.CreateImage(options)
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["Action"], DeepEquals, []string{"CreateImage"})
+	c.Assert(req.Form["InstanceId"], DeepEquals, []string{options.InstanceId})
+	c.Assert(req.Form["Name"], DeepEquals, []string{options.Name})
+	c.Assert(req.Form["Description"], DeepEquals, []string{options.Description})
+	c.Assert(req.Form["NoReboot"], DeepEquals, []string{"true"})
+	c.Assert(req.Form["BlockDeviceMapping.1.DeviceName"], DeepEquals, []string{"/dev/sdb"})
+	c.Assert(req.Form["BlockDeviceMapping.1.VirtualName"], DeepEquals, []string{"ephemeral0"})
+	c.Assert(req.Form["BlockDeviceMapping.2.DeviceName"], DeepEquals, []string{"/dev/sdc"})
+	c.Assert(req.Form["BlockDeviceMapping.2.Ebs.SnapshotId"], DeepEquals, []string{"snap-a08912c9"})
+	c.Assert(req.Form["BlockDeviceMapping.2.Ebs.DeleteOnTermination"], DeepEquals, []string{"true"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+	c.Assert(resp.ImageId, Equals, "ami-4fa54026")
+}
+
+func (s *S) TestDescribeImagesExample(c *C) {
+	testServer.Response(200, nil, DescribeImagesExample)
+
+	filter := ec2.NewFilter()
+	filter.Add("key1", "value1")
+	filter.Add("key2", "value2", "value3")
+
+	resp, err := s.ec2.Images([]string{"ami-1", "ami-2"}, filter)
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeImages"})
+	c.Assert(req.Form["ImageId.1"], DeepEquals, []string{"ami-1"})
+	c.Assert(req.Form["ImageId.2"], DeepEquals, []string{"ami-2"})
+	c.Assert(req.Form["Filter.1.Name"], DeepEquals, []string{"key1"})
+	c.Assert(req.Form["Filter.1.Value.1"], DeepEquals, []string{"value1"})
+	c.Assert(req.Form["Filter.1.Value.2"], IsNil)
+	c.Assert(req.Form["Filter.2.Name"], DeepEquals, []string{"key2"})
+	c.Assert(req.Form["Filter.2.Value.1"], DeepEquals, []string{"value2"})
+	c.Assert(req.Form["Filter.2.Value.2"], DeepEquals, []string{"value3"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "4a4a27a2-2e7c-475d-b35b-ca822EXAMPLE")
+	c.Assert(resp.Images, HasLen, 1)
+
+	i0 := resp.Images[0]
+	c.Assert(i0.Id, Equals, "ami-a2469acf")
+	c.Assert(i0.Type, Equals, "machine")
+	c.Assert(i0.Name, Equals, "example-marketplace-amzn-ami.1")
+	c.Assert(i0.Description, Equals, "Amazon Linux AMI i386 EBS")
+	c.Assert(i0.Location, Equals, "aws-marketplace/example-marketplace-amzn-ami.1")
+	c.Assert(i0.State, Equals, "available")
+	c.Assert(i0.Public, Equals, true)
+	c.Assert(i0.OwnerId, Equals, "123456789999")
+	c.Assert(i0.OwnerAlias, Equals, "aws-marketplace")
+	c.Assert(i0.Architecture, Equals, "i386")
+	c.Assert(i0.KernelId, Equals, "aki-805ea7e9")
+	c.Assert(i0.RootDeviceType, Equals, "ebs")
+	c.Assert(i0.RootDeviceName, Equals, "/dev/sda1")
+	c.Assert(i0.VirtualizationType, Equals, "paravirtual")
+	c.Assert(i0.Hypervisor, Equals, "xen")
+
+	c.Assert(i0.BlockDevices, HasLen, 1)
+	c.Assert(i0.BlockDevices[0].DeviceName, Equals, "/dev/sda1")
+	c.Assert(i0.BlockDevices[0].SnapshotId, Equals, "snap-787e9403")
+	c.Assert(i0.BlockDevices[0].VolumeSize, Equals, int64(8))
+	c.Assert(i0.BlockDevices[0].DeleteOnTermination, Equals, true)
+
+	testServer.Response(200, nil, DescribeImagesExample)
+	resp2, err := s.ec2.ImagesByOwners([]string{"ami-1", "ami-2"}, []string{"123456789999", "id2"}, filter)
+
+	req2 := testServer.WaitRequest()
+	c.Assert(req2.Form["Action"], DeepEquals, []string{"DescribeImages"})
+	c.Assert(req2.Form["ImageId.1"], DeepEquals, []string{"ami-1"})
+	c.Assert(req2.Form["ImageId.2"], DeepEquals, []string{"ami-2"})
+	c.Assert(req2.Form["Owner.1"], DeepEquals, []string{"123456789999"})
+	c.Assert(req2.Form["Owner.2"], DeepEquals, []string{"id2"})
+	c.Assert(req2.Form["Filter.1.Name"], DeepEquals, []string{"key1"})
+	c.Assert(req2.Form["Filter.1.Value.1"], DeepEquals, []string{"value1"})
+	c.Assert(req2.Form["Filter.1.Value.2"], IsNil)
+	c.Assert(req2.Form["Filter.2.Name"], DeepEquals, []string{"key2"})
+	c.Assert(req2.Form["Filter.2.Value.1"], DeepEquals, []string{"value2"})
+	c.Assert(req2.Form["Filter.2.Value.2"], DeepEquals, []string{"value3"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp2.RequestId, Equals, "4a4a27a2-2e7c-475d-b35b-ca822EXAMPLE")
+	c.Assert(resp2.Images, HasLen, 1)
+
+	i1 := resp2.Images[0]
+	c.Assert(i1.Id, Equals, "ami-a2469acf")
+	c.Assert(i1.Type, Equals, "machine")
+	c.Assert(i1.Name, Equals, "example-marketplace-amzn-ami.1")
+	c.Assert(i1.Description, Equals, "Amazon Linux AMI i386 EBS")
+	c.Assert(i1.Location, Equals, "aws-marketplace/example-marketplace-amzn-ami.1")
+	c.Assert(i1.State, Equals, "available")
+	c.Assert(i1.Public, Equals, true)
+	c.Assert(i1.OwnerId, Equals, "123456789999")
+	c.Assert(i1.OwnerAlias, Equals, "aws-marketplace")
+	c.Assert(i1.Architecture, Equals, "i386")
+	c.Assert(i1.KernelId, Equals, "aki-805ea7e9")
+	c.Assert(i1.RootDeviceType, Equals, "ebs")
+	c.Assert(i1.RootDeviceName, Equals, "/dev/sda1")
+	c.Assert(i1.VirtualizationType, Equals, "paravirtual")
+	c.Assert(i1.Hypervisor, Equals, "xen")
+
+	c.Assert(i1.BlockDevices, HasLen, 1)
+	c.Assert(i1.BlockDevices[0].DeviceName, Equals, "/dev/sda1")
+	c.Assert(i1.BlockDevices[0].SnapshotId, Equals, "snap-787e9403")
+	c.Assert(i1.BlockDevices[0].VolumeSize, Equals, int64(8))
+	c.Assert(i1.BlockDevices[0].DeleteOnTermination, Equals, true)
+}
+
+func (s *S) TestImageAttributeExample(c *C) {
+	testServer.Response(200, nil, ImageAttributeExample)
+
+	resp, err := s.ec2.ImageAttribute("ami-61a54008", "launchPermission")
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeImageAttribute"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+	c.Assert(resp.ImageId, Equals, "ami-61a54008")
+	c.Assert(resp.Group, Equals, "all")
+	c.Assert(resp.UserIds[0], Equals, "495219933132")
+}
+
+func (s *S) TestCreateSnapshotExample(c *C) {
+	testServer.Response(200, nil, CreateSnapshotExample)
+
+	resp, err := s.ec2.CreateSnapshot("vol-4d826724", "Daily Backup")
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["Action"], DeepEquals, []string{"CreateSnapshot"})
+	c.Assert(req.Form["VolumeId"], DeepEquals, []string{"vol-4d826724"})
+	c.Assert(req.Form["Description"], DeepEquals, []string{"Daily Backup"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+	c.Assert(resp.Snapshot.Id, Equals, "snap-78a54011")
+	c.Assert(resp.Snapshot.VolumeId, Equals, "vol-4d826724")
+	c.Assert(resp.Snapshot.Status, Equals, "pending")
+	c.Assert(resp.Snapshot.StartTime, Equals, "2008-05-07T12:51:50.000Z")
+	c.Assert(resp.Snapshot.Progress, Equals, "60%")
+	c.Assert(resp.Snapshot.OwnerId, Equals, "111122223333")
+	c.Assert(resp.Snapshot.VolumeSize, Equals, "10")
+	c.Assert(resp.Snapshot.Description, Equals, "Daily Backup")
+}
+
+func (s *S) TestDeleteSnapshotsExample(c *C) {
+	testServer.Response(200, nil, DeleteSnapshotExample)
+
+	resp, err := s.ec2.DeleteSnapshots([]string{"snap-78a54011"})
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["Action"], DeepEquals, []string{"DeleteSnapshot"})
+	c.Assert(req.Form["SnapshotId.1"], DeepEquals, []string{"snap-78a54011"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+}
+
+func (s *S) TestDescribeSnapshotsExample(c *C) {
+	testServer.Response(200, nil, DescribeSnapshotsExample)
+
+	filter := ec2.NewFilter()
+	filter.Add("key1", "value1")
+	filter.Add("key2", "value2", "value3")
+
+	resp, err := s.ec2.Snapshots([]string{"snap-1", "snap-2"}, filter)
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeSnapshots"})
+	c.Assert(req.Form["SnapshotId.1"], DeepEquals, []string{"snap-1"})
+	c.Assert(req.Form["SnapshotId.2"], DeepEquals, []string{"snap-2"})
+	c.Assert(req.Form["Filter.1.Name"], DeepEquals, []string{"key1"})
+	c.Assert(req.Form["Filter.1.Value.1"], DeepEquals, []string{"value1"})
+	c.Assert(req.Form["Filter.1.Value.2"], IsNil)
+	c.Assert(req.Form["Filter.2.Name"], DeepEquals, []string{"key2"})
+	c.Assert(req.Form["Filter.2.Value.1"], DeepEquals, []string{"value2"})
+	c.Assert(req.Form["Filter.2.Value.2"], DeepEquals, []string{"value3"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+	c.Assert(resp.Snapshots, HasLen, 1)
+
+	s0 := resp.Snapshots[0]
+	c.Assert(s0.Id, Equals, "snap-1a2b3c4d")
+	c.Assert(s0.VolumeId, Equals, "vol-8875daef")
+	c.Assert(s0.VolumeSize, Equals, "15")
+	c.Assert(s0.Status, Equals, "pending")
+	c.Assert(s0.StartTime, Equals, "2010-07-29T04:12:01.000Z")
+	c.Assert(s0.Progress, Equals, "30%")
+	c.Assert(s0.OwnerId, Equals, "111122223333")
+	c.Assert(s0.Description, Equals, "Daily Backup")
+
+	c.Assert(s0.Tags, HasLen, 1)
+	c.Assert(s0.Tags[0].Key, Equals, "Purpose")
+	c.Assert(s0.Tags[0].Value, Equals, "demo_db_14_backup")
+}
+
+func (s *S) TestModifyImageAttributeExample(c *C) {
+	testServer.Response(200, nil, ModifyImageAttributeExample)
+
+	options := ec2.ModifyImageAttribute{
+		Description: "Test Description",
+	}
+
+	resp, err := s.ec2.ModifyImageAttribute("ami-4fa54026", &options)
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["Action"], DeepEquals, []string{"ModifyImageAttribute"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+}
+
+func (s *S) TestModifyImageAttributeExample_complex(c *C) {
+	testServer.Response(200, nil, ModifyImageAttributeExample)
+
+	options := ec2.ModifyImageAttribute{
+		AddUsers:     []string{"u1", "u2"},
+		RemoveUsers:  []string{"u3"},
+		AddGroups:    []string{"g1", "g3"},
+		RemoveGroups: []string{"g2"},
+		Description:  "Test Description",
+	}
+
+	resp, err := s.ec2.ModifyImageAttribute("ami-4fa54026", &options)
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["Action"], DeepEquals, []string{"ModifyImageAttribute"})
+	c.Assert(req.Form["LaunchPermission.Add.1.UserId"], DeepEquals, []string{"u1"})
+	c.Assert(req.Form["LaunchPermission.Add.2.UserId"], DeepEquals, []string{"u2"})
+	c.Assert(req.Form["LaunchPermission.Remove.1.UserId"], DeepEquals, []string{"u3"})
+	c.Assert(req.Form["LaunchPermission.Add.1.Group"], DeepEquals, []string{"g1"})
+	c.Assert(req.Form["LaunchPermission.Add.2.Group"], DeepEquals, []string{"g3"})
+	c.Assert(req.Form["LaunchPermission.Remove.1.Group"], DeepEquals, []string{"g2"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+}
+
+func (s *S) TestCopyImageExample(c *C) {
+	testServer.Response(200, nil, CopyImageExample)
+
+	options := ec2.CopyImage{
+		SourceRegion:  "us-west-2",
+		SourceImageId: "ami-1a2b3c4d",
+		Description:   "Test Description",
+	}
+
+	resp, err := s.ec2.CopyImage(&options)
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["Action"], DeepEquals, []string{"CopyImage"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "60bc441d-fa2c-494d-b155-5d6a3EXAMPLE")
+}
+
+func (s *S) TestCreateKeyPairExample(c *C) {
+	testServer.Response(200, nil, CreateKeyPairExample)
+
+	resp, err := s.ec2.CreateKeyPair("foo")
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["Action"], DeepEquals, []string{"CreateKeyPair"})
+	c.Assert(req.Form["KeyName"], DeepEquals, []string{"foo"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+	c.Assert(resp.KeyName, Equals, "foo")
+	c.Assert(resp.KeyFingerprint, Equals, "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00")
+}
+
+func (s *S) TestDeleteKeyPairExample(c *C) {
+	testServer.Response(200, nil, DeleteKeyPairExample)
+
+	resp, err := s.ec2.DeleteKeyPair("foo")
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["Action"], DeepEquals, []string{"DeleteKeyPair"})
+	c.Assert(req.Form["KeyName"], DeepEquals, []string{"foo"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+}
+
+func (s *S) TestCreateSecurityGroupExample(c *C) {
+	testServer.Response(200, nil, CreateSecurityGroupExample)
+
+	resp, err := s.ec2.CreateSecurityGroup(ec2.SecurityGroup{Name: "websrv", Description: "Web Servers"})
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["Action"], DeepEquals, []string{"CreateSecurityGroup"})
+	c.Assert(req.Form["GroupName"], DeepEquals, []string{"websrv"})
+	c.Assert(req.Form["GroupDescription"], DeepEquals, []string{"Web Servers"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+	c.Assert(resp.Name, Equals, "websrv")
+	c.Assert(resp.Id, Equals, "sg-67ad940e")
+}
+
+func (s *S) TestDescribeSecurityGroupsExample(c *C) {
+	testServer.Response(200, nil, DescribeSecurityGroupsExample)
+
+	resp, err := s.ec2.SecurityGroups([]ec2.SecurityGroup{{Name: "WebServers"}, {Name: "RangedPortsBySource"}}, nil)
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeSecurityGroups"})
+	c.Assert(req.Form["GroupName.1"], DeepEquals, []string{"WebServers"})
+	c.Assert(req.Form["GroupName.2"], DeepEquals, []string{"RangedPortsBySource"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+	c.Assert(resp.Groups, HasLen, 2)
+
+	g0 := resp.Groups[0]
+	c.Assert(g0.OwnerId, Equals, "999988887777")
+	c.Assert(g0.Name, Equals, "WebServers")
+	c.Assert(g0.Id, Equals, "sg-67ad940e")
+	c.Assert(g0.Description, Equals, "Web Servers")
+	c.Assert(g0.IPPerms, HasLen, 1)
+	c.Assert(g0.IPPermsEgress, HasLen, 1)
+
+	g0ipp := g0.IPPerms[0]
+	c.Assert(g0ipp.Protocol, Equals, "tcp")
+	c.Assert(g0ipp.FromPort, Equals, 80)
+	c.Assert(g0ipp.ToPort, Equals, 80)
+	c.Assert(g0ipp.SourceIPs, DeepEquals, []string{"0.0.0.0/0"})
+
+	g0ippe := g0.IPPermsEgress[0]
+	c.Assert(g0ippe.Protocol, Equals, "tcp")
+	c.Assert(g0ippe.FromPort, Equals, 80)
+	c.Assert(g0ippe.ToPort, Equals, 80)
+	c.Assert(g0ippe.SourceIPs, DeepEquals, []string{"0.0.0.0/0"})
+
+	g1 := resp.Groups[1]
+	c.Assert(g1.OwnerId, Equals, "999988887777")
+	c.Assert(g1.Name, Equals, "RangedPortsBySource")
+	c.Assert(g1.Id, Equals, "sg-76abc467")
+	c.Assert(g1.Description, Equals, "Group A")
+	c.Assert(g1.IPPerms, HasLen, 1)
+
+	g1ipp := g1.IPPerms[0]
+	c.Assert(g1ipp.Protocol, Equals, "tcp")
+	c.Assert(g1ipp.FromPort, Equals, 6000)
+	c.Assert(g1ipp.ToPort, Equals, 7000)
+	c.Assert(g1ipp.SourceIPs, IsNil)
+}
+
+func (s *S) TestDescribeSecurityGroupsExampleWithFilter(c *C) {
+	testServer.Response(200, nil, DescribeSecurityGroupsExample)
+
+	filter := ec2.NewFilter()
+	filter.Add("ip-permission.protocol", "tcp")
+	filter.Add("ip-permission.from-port", "22")
+	filter.Add("ip-permission.to-port", "22")
+	filter.Add("ip-permission.group-name", "app_server_group", "database_group")
+
+	_, err := s.ec2.SecurityGroups(nil, filter)
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeSecurityGroups"})
+	c.Assert(req.Form["Filter.1.Name"], DeepEquals, []string{"ip-permission.from-port"})
+	c.Assert(req.Form["Filter.1.Value.1"], DeepEquals, []string{"22"})
+	c.Assert(req.Form["Filter.2.Name"], DeepEquals, []string{"ip-permission.group-name"})
+	c.Assert(req.Form["Filter.2.Value.1"], DeepEquals, []string{"app_server_group"})
+	c.Assert(req.Form["Filter.2.Value.2"], DeepEquals, []string{"database_group"})
+	c.Assert(req.Form["Filter.3.Name"], DeepEquals, []string{"ip-permission.protocol"})
+	c.Assert(req.Form["Filter.3.Value.1"], DeepEquals, []string{"tcp"})
+	c.Assert(req.Form["Filter.4.Name"], DeepEquals, []string{"ip-permission.to-port"})
+	c.Assert(req.Form["Filter.4.Value.1"], DeepEquals, []string{"22"})
+
+	c.Assert(err, IsNil)
+}
+
+func (s *S) TestDescribeSecurityGroupsDumpWithGroup(c *C) {
+	testServer.Response(200, nil, DescribeSecurityGroupsDump)
+
+	resp, err := s.ec2.SecurityGroups(nil, nil)
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeSecurityGroups"})
+	c.Assert(err, IsNil)
+	c.Check(resp.Groups, HasLen, 1)
+	c.Check(resp.Groups[0].IPPerms, HasLen, 2)
+
+	ipp0 := resp.Groups[0].IPPerms[0]
+	c.Assert(ipp0.SourceIPs, IsNil)
+	c.Check(ipp0.Protocol, Equals, "icmp")
+	c.Assert(ipp0.SourceGroups, HasLen, 1)
+	c.Check(ipp0.SourceGroups[0].OwnerId, Equals, "12345")
+	c.Check(ipp0.SourceGroups[0].Name, Equals, "default")
+	c.Check(ipp0.SourceGroups[0].Id, Equals, "sg-67ad940e")
+
+	ipp1 := resp.Groups[0].IPPerms[1]
+	c.Check(ipp1.Protocol, Equals, "tcp")
+	c.Assert(ipp0.SourceIPs, IsNil)
+	c.Assert(ipp0.SourceGroups, HasLen, 1)
+	c.Check(ipp1.SourceGroups[0].Id, Equals, "sg-76abc467")
+	c.Check(ipp1.SourceGroups[0].OwnerId, Equals, "12345")
+	c.Check(ipp1.SourceGroups[0].Name, Equals, "other")
+}
+
+func (s *S) TestDeleteSecurityGroupExample(c *C) {
+	testServer.Response(200, nil, DeleteSecurityGroupExample)
+
+	resp, err := s.ec2.DeleteSecurityGroup(ec2.SecurityGroup{Name: "websrv"})
+	req := testServer.WaitRequest()
+
+	c.Assert(req.Form["Action"], DeepEquals, []string{"DeleteSecurityGroup"})
+	c.Assert(req.Form["GroupName"], DeepEquals, []string{"websrv"})
+	c.Assert(req.Form["GroupId"], IsNil)
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+}
+
+func (s *S) TestDeleteSecurityGroupExampleWithId(c *C) {
+	testServer.Response(200, nil, DeleteSecurityGroupExample)
+
+	// ignore return and error - we're only want to check the parameter handling.
+	s.ec2.DeleteSecurityGroup(ec2.SecurityGroup{Id: "sg-67ad940e", Name: "ignored"})
+	req := testServer.WaitRequest()
+
+	c.Assert(req.Form["GroupName"], IsNil)
+	c.Assert(req.Form["GroupId"], DeepEquals, []string{"sg-67ad940e"})
+}
+
+func (s *S) TestAuthorizeSecurityGroupExample1(c *C) {
+	testServer.Response(200, nil, AuthorizeSecurityGroupIngressExample)
+
+	perms := []ec2.IPPerm{{
+		Protocol:  "tcp",
+		FromPort:  80,
+		ToPort:    80,
+		SourceIPs: []string{"205.192.0.0/16", "205.159.0.0/16"},
+	}}
+	resp, err := s.ec2.AuthorizeSecurityGroup(ec2.SecurityGroup{Name: "websrv"}, perms)
+
+	req := testServer.WaitRequest()
+
+	c.Assert(req.Form["Action"], DeepEquals, []string{"AuthorizeSecurityGroupIngress"})
+	c.Assert(req.Form["GroupName"], DeepEquals, []string{"websrv"})
+	c.Assert(req.Form["IpPermissions.1.IpProtocol"], DeepEquals, []string{"tcp"})
+	c.Assert(req.Form["IpPermissions.1.FromPort"], DeepEquals, []string{"80"})
+	c.Assert(req.Form["IpPermissions.1.ToPort"], DeepEquals, []string{"80"})
+	c.Assert(req.Form["IpPermissions.1.IpRanges.1.CidrIp"], DeepEquals, []string{"205.192.0.0/16"})
+	c.Assert(req.Form["IpPermissions.1.IpRanges.2.CidrIp"], DeepEquals, []string{"205.159.0.0/16"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+}
+
+func (s *S) TestAuthorizeSecurityGroupEgress(c *C) {
+	testServer.Response(200, nil, AuthorizeSecurityGroupEgressExample)
+
+	perms := []ec2.IPPerm{{
+		Protocol:  "tcp",
+		FromPort:  80,
+		ToPort:    80,
+		SourceIPs: []string{"205.192.0.0/16", "205.159.0.0/16"},
+	}}
+	resp, err := s.ec2.AuthorizeSecurityGroupEgress(ec2.SecurityGroup{Name: "websrv"}, perms)
+
+	req := testServer.WaitRequest()
+
+	c.Assert(req.Form["Action"], DeepEquals, []string{"AuthorizeSecurityGroupEgress"})
+	c.Assert(req.Form["GroupName"], DeepEquals, []string{"websrv"})
+	c.Assert(req.Form["IpPermissions.1.IpProtocol"], DeepEquals, []string{"tcp"})
+	c.Assert(req.Form["IpPermissions.1.FromPort"], DeepEquals, []string{"80"})
+	c.Assert(req.Form["IpPermissions.1.ToPort"], DeepEquals, []string{"80"})
+	c.Assert(req.Form["IpPermissions.1.IpRanges.1.CidrIp"], DeepEquals, []string{"205.192.0.0/16"})
+	c.Assert(req.Form["IpPermissions.1.IpRanges.2.CidrIp"], DeepEquals, []string{"205.159.0.0/16"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+}
+
+func (s *S) TestAuthorizeSecurityGroupExample1WithId(c *C) {
+	testServer.Response(200, nil, AuthorizeSecurityGroupIngressExample)
+
+	perms := []ec2.IPPerm{{
+		Protocol:  "tcp",
+		FromPort:  80,
+		ToPort:    80,
+		SourceIPs: []string{"205.192.0.0/16", "205.159.0.0/16"},
+	}}
+	// ignore return and error - we're only want to check the parameter handling.
+	s.ec2.AuthorizeSecurityGroup(ec2.SecurityGroup{Id: "sg-67ad940e", Name: "ignored"}, perms)
+
+	req := testServer.WaitRequest()
+
+	c.Assert(req.Form["GroupName"], IsNil)
+	c.Assert(req.Form["GroupId"], DeepEquals, []string{"sg-67ad940e"})
+}
+
+func (s *S) TestAuthorizeSecurityGroupExample2(c *C) {
+	testServer.Response(200, nil, AuthorizeSecurityGroupIngressExample)
+
+	perms := []ec2.IPPerm{{
+		Protocol: "tcp",
+		FromPort: 80,
+		ToPort:   81,
+		SourceGroups: []ec2.UserSecurityGroup{
+			{OwnerId: "999988887777", Name: "OtherAccountGroup"},
+			{Id: "sg-67ad940e"},
+		},
+	}}
+	resp, err := s.ec2.AuthorizeSecurityGroup(ec2.SecurityGroup{Name: "websrv"}, perms)
+
+	req := testServer.WaitRequest()
+
+	c.Assert(req.Form["Action"], DeepEquals, []string{"AuthorizeSecurityGroupIngress"})
+	c.Assert(req.Form["GroupName"], DeepEquals, []string{"websrv"})
+	c.Assert(req.Form["IpPermissions.1.IpProtocol"], DeepEquals, []string{"tcp"})
+	c.Assert(req.Form["IpPermissions.1.FromPort"], DeepEquals, []string{"80"})
+	c.Assert(req.Form["IpPermissions.1.ToPort"], DeepEquals, []string{"81"})
+	c.Assert(req.Form["IpPermissions.1.Groups.1.UserId"], DeepEquals, []string{"999988887777"})
+	c.Assert(req.Form["IpPermissions.1.Groups.1.GroupName"], DeepEquals, []string{"OtherAccountGroup"})
+	c.Assert(req.Form["IpPermissions.1.Groups.2.UserId"], IsNil)
+	c.Assert(req.Form["IpPermissions.1.Groups.2.GroupName"], IsNil)
+	c.Assert(req.Form["IpPermissions.1.Groups.2.GroupId"], DeepEquals, []string{"sg-67ad940e"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+}
+
+func (s *S) TestRevokeSecurityGroupExample(c *C) {
+	// RevokeSecurityGroup is implemented by the same code as AuthorizeSecurityGroup
+	// so there's no need to duplicate all the tests.
+	testServer.Response(200, nil, RevokeSecurityGroupIngressExample)
+
+	resp, err := s.ec2.RevokeSecurityGroup(ec2.SecurityGroup{Name: "websrv"}, nil)
+
+	req := testServer.WaitRequest()
+
+	c.Assert(req.Form["Action"], DeepEquals, []string{"RevokeSecurityGroupIngress"})
+	c.Assert(req.Form["GroupName"], DeepEquals, []string{"websrv"})
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+}
+
+func (s *S) TestCreateTags(c *C) {
+	testServer.Response(200, nil, CreateTagsExample)
+
+	resp, err := s.ec2.CreateTags([]string{"ami-1a2b3c4d", "i-7f4d3a2b"}, []ec2.Tag{{"webserver", ""}, {"stack", "Production"}})
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["ResourceId.1"], DeepEquals, []string{"ami-1a2b3c4d"})
+	c.Assert(req.Form["ResourceId.2"], DeepEquals, []string{"i-7f4d3a2b"})
+	c.Assert(req.Form["Tag.1.Key"], DeepEquals, []string{"webserver"})
+	c.Assert(req.Form["Tag.1.Value"], DeepEquals, []string{""})
+	c.Assert(req.Form["Tag.2.Key"], DeepEquals, []string{"stack"})
+	c.Assert(req.Form["Tag.2.Value"], DeepEquals, []string{"Production"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+}
+
+func (s *S) TestStartInstances(c *C) {
+	testServer.Response(200, nil, StartInstancesExample)
+
+	resp, err := s.ec2.StartInstances("i-10a64379")
+	req := testServer.WaitRequest()
+
+	c.Assert(req.Form["Action"], DeepEquals, []string{"StartInstances"})
+	c.Assert(req.Form["InstanceId.1"], DeepEquals, []string{"i-10a64379"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+
+	s0 := resp.StateChanges[0]
+	c.Assert(s0.InstanceId, Equals, "i-10a64379")
+	c.Assert(s0.CurrentState.Code, Equals, 0)
+	c.Assert(s0.CurrentState.Name, Equals, "pending")
+	c.Assert(s0.PreviousState.Code, Equals, 80)
+	c.Assert(s0.PreviousState.Name, Equals, "stopped")
+}
+
+func (s *S) TestStopInstances(c *C) {
+	testServer.Response(200, nil, StopInstancesExample)
+
+	resp, err := s.ec2.StopInstances("i-10a64379")
+	req := testServer.WaitRequest()
+
+	c.Assert(req.Form["Action"], DeepEquals, []string{"StopInstances"})
+	c.Assert(req.Form["InstanceId.1"], DeepEquals, []string{"i-10a64379"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+
+	s0 := resp.StateChanges[0]
+	c.Assert(s0.InstanceId, Equals, "i-10a64379")
+	c.Assert(s0.CurrentState.Code, Equals, 64)
+	c.Assert(s0.CurrentState.Name, Equals, "stopping")
+	c.Assert(s0.PreviousState.Code, Equals, 16)
+	c.Assert(s0.PreviousState.Name, Equals, "running")
+}
+
+func (s *S) TestRebootInstances(c *C) {
+	testServer.Response(200, nil, RebootInstancesExample)
+
+	resp, err := s.ec2.RebootInstances("i-10a64379")
+	req := testServer.WaitRequest()
+
+	c.Assert(req.Form["Action"], DeepEquals, []string{"RebootInstances"})
+	c.Assert(req.Form["InstanceId.1"], DeepEquals, []string{"i-10a64379"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+}
+
+func (s *S) TestSignatureWithEndpointPath(c *C) {
+	ec2.FakeTime(true)
+	defer ec2.FakeTime(false)
+
+	testServer.Response(200, nil, RebootInstancesExample)
+
+	// https://bugs.launchpad.net/goamz/+bug/1022749
+	ec2 := ec2.NewWithClient(s.ec2.Auth, aws.Region{EC2Endpoint: testServer.URL + "/services/Cloud"}, testutil.DefaultClient)
+
+	_, err := ec2.RebootInstances("i-10a64379")
+	c.Assert(err, IsNil)
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["Signature"], DeepEquals, []string{"tyOTQ0c0T5ujskCPTWa5ATMtv7UyErgT339cU8O2+Q8="})
+}
+
+func (s *S) TestDescribeInstanceStatusExample(c *C) {
+	testServer.Response(200, nil, DescribeInstanceStatusExample)
+	options := &ec2.DescribeInstanceStatus{}
+	resp, err := s.ec2.DescribeInstanceStatus(options, nil)
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeInstanceStatus"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "3be1508e-c444-4fef-89cc-0b1223c4f02fEXAMPLE")
+	c.Assert(resp.InstanceStatus[0].InstanceId, Equals, "i-1a2b3c4d")
+	c.Assert(resp.InstanceStatus[0].InstanceState.Code, Equals, 16)
+	c.Assert(resp.InstanceStatus[0].SystemStatus.Status, Equals, "impaired")
+	c.Assert(resp.InstanceStatus[0].SystemStatus.Details[0].Name, Equals, "reachability")
+	c.Assert(resp.InstanceStatus[0].SystemStatus.Details[0].Status, Equals, "failed")
+	c.Assert(resp.InstanceStatus[0].SystemStatus.Details[0].ImpairedSince, Equals, "YYYY-MM-DDTHH:MM:SS.000Z")
+	c.Assert(resp.InstanceStatus[0].InstanceStatus.Details[0].Name, Equals, "reachability")
+	c.Assert(resp.InstanceStatus[0].InstanceStatus.Details[0].Status, Equals, "failed")
+	c.Assert(resp.InstanceStatus[0].InstanceStatus.Details[0].ImpairedSince, Equals, "YYYY-MM-DDTHH:MM:SS.000Z")
+	c.Assert(resp.InstanceStatus[0].Events[0].Code, Equals, "instance-retirement")
+	c.Assert(resp.InstanceStatus[0].Events[0].Description, Equals, "The instance is running on degraded hardware")
+	c.Assert(resp.InstanceStatus[0].Events[0].NotBefore, Equals, "YYYY-MM-DDTHH:MM:SS+0000")
+	c.Assert(resp.InstanceStatus[0].Events[0].NotAfter, Equals, "YYYY-MM-DDTHH:MM:SS+0000")
+}
+
+func (s *S) TestAllocateAddressExample(c *C) {
+	testServer.Response(200, nil, AllocateAddressExample)
+
+	options := &ec2.AllocateAddress{
+		Domain: "vpc",
+	}
+
+	resp, err := s.ec2.AllocateAddress(options)
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["Action"], DeepEquals, []string{"AllocateAddress"})
+	c.Assert(req.Form["Domain"], DeepEquals, []string{"vpc"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+	c.Assert(resp.PublicIp, Equals, "198.51.100.1")
+	c.Assert(resp.Domain, Equals, "vpc")
+	c.Assert(resp.AllocationId, Equals, "eipalloc-5723d13e")
+}
+
+func (s *S) TestReleaseAddressExample(c *C) {
+	testServer.Response(200, nil, ReleaseAddressExample)
+
+	resp, err := s.ec2.ReleaseAddress("eipalloc-5723d13e")
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["Action"], DeepEquals, []string{"ReleaseAddress"})
+	c.Assert(req.Form["AllocationId"], DeepEquals, []string{"eipalloc-5723d13e"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+}
+
+func (s *S) TestAssociateAddressExample(c *C) {
+	testServer.Response(200, nil, AssociateAddressExample)
+
+	options := &ec2.AssociateAddress{
+		InstanceId:         "i-4fd2431a",
+		AllocationId:       "eipalloc-5723d13e",
+		AllowReassociation: true,
+	}
+
+	resp, err := s.ec2.AssociateAddress(options)
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["Action"], DeepEquals, []string{"AssociateAddress"})
+	c.Assert(req.Form["InstanceId"], DeepEquals, []string{"i-4fd2431a"})
+	c.Assert(req.Form["AllocationId"], DeepEquals, []string{"eipalloc-5723d13e"})
+	c.Assert(req.Form["AllowReassociation"], DeepEquals, []string{"true"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+	c.Assert(resp.AssociationId, Equals, "eipassoc-fc5ca095")
+}
+
+func (s *S) TestDisassociateAddressExample(c *C) {
+	testServer.Response(200, nil, DisassociateAddressExample)
+
+	resp, err := s.ec2.DisassociateAddress("eipassoc-aa7486c3")
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["Action"], DeepEquals, []string{"DisassociateAddress"})
+	c.Assert(req.Form["AssociationId"], DeepEquals, []string{"eipassoc-aa7486c3"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+}
+
+func (s *S) TestModifyInstance(c *C) {
+	testServer.Response(200, nil, ModifyInstanceExample)
+
+	options := ec2.ModifyInstance{
+		InstanceType:          "m1.small",
+		DisableAPITermination: true,
+		EbsOptimized:          true,
+		SecurityGroups:        []ec2.SecurityGroup{{Id: "g1"}, {Id: "g2"}},
+		ShutdownBehavior:      "terminate",
+		KernelId:              "kernel-id",
+		RamdiskId:             "ramdisk-id",
+		SourceDestCheck:       true,
+		SriovNetSupport:       true,
+		UserData:              []byte("1234"),
+		BlockDevices: []ec2.BlockDeviceMapping{
+			{DeviceName: "/dev/sda1", SnapshotId: "snap-a08912c9", DeleteOnTermination: true},
+		},
+	}
+
+	resp, err := s.ec2.ModifyInstance("i-2ba64342", &options)
+	req := testServer.WaitRequest()
+
+	c.Assert(req.Form["Action"], DeepEquals, []string{"ModifyInstanceAttribute"})
+	c.Assert(req.Form["InstanceId"], DeepEquals, []string{"i-2ba64342"})
+	c.Assert(req.Form["InstanceType.Value"], DeepEquals, []string{"m1.small"})
+	c.Assert(req.Form["BlockDeviceMapping.1.DeviceName"], DeepEquals, []string{"/dev/sda1"})
+	c.Assert(req.Form["BlockDeviceMapping.1.Ebs.SnapshotId"], DeepEquals, []string{"snap-a08912c9"})
+	c.Assert(req.Form["BlockDeviceMapping.1.Ebs.DeleteOnTermination"], DeepEquals, []string{"true"})
+	c.Assert(req.Form["DisableApiTermination.Value"], DeepEquals, []string{"true"})
+	c.Assert(req.Form["EbsOptimized"], DeepEquals, []string{"true"})
+	c.Assert(req.Form["GroupId.1"], DeepEquals, []string{"g1"})
+	c.Assert(req.Form["GroupId.2"], DeepEquals, []string{"g2"})
+	c.Assert(req.Form["InstanceInitiatedShutdownBehavior.Value"], DeepEquals, []string{"terminate"})
+	c.Assert(req.Form["Kernel.Value"], DeepEquals, []string{"kernel-id"})
+	c.Assert(req.Form["Ramdisk.Value"], DeepEquals, []string{"ramdisk-id"})
+	c.Assert(req.Form["SourceDestCheck.Value"], DeepEquals, []string{"true"})
+	c.Assert(req.Form["SriovNetSupport.Value"], DeepEquals, []string{"simple"})
+	c.Assert(req.Form["UserData"], DeepEquals, []string{"MTIzNA=="})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+}
+
+func (s *S) TestCreateVpc(c *C) {
+	testServer.Response(200, nil, CreateVpcExample)
+
+	options := &ec2.CreateVpc{
+		CidrBlock: "foo",
+	}
+
+	resp, err := s.ec2.CreateVpc(options)
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["CidrBlock"], DeepEquals, []string{"foo"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "7a62c49f-347e-4fc4-9331-6e8eEXAMPLE")
+	c.Assert(resp.VPC.VpcId, Equals, "vpc-1a2b3c4d")
+	c.Assert(resp.VPC.State, Equals, "pending")
+	c.Assert(resp.VPC.CidrBlock, Equals, "10.0.0.0/16")
+	c.Assert(resp.VPC.DHCPOptionsID, Equals, "dopt-1a2b3c4d2")
+	c.Assert(resp.VPC.InstanceTenancy, Equals, "default")
+}
+
+func (s *S) TestDescribeVpcs(c *C) {
+	testServer.Response(200, nil, DescribeVpcsExample)
+
+	filter := ec2.NewFilter()
+	filter.Add("key1", "value1")
+	filter.Add("key2", "value2", "value3")
+
+	resp, err := s.ec2.DescribeVpcs([]string{"id1", "id2"}, filter)
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeVpcs"})
+	c.Assert(req.Form["VpcId.1"], DeepEquals, []string{"id1"})
+	c.Assert(req.Form["VpcId.2"], DeepEquals, []string{"id2"})
+	c.Assert(req.Form["Filter.1.Name"], DeepEquals, []string{"key1"})
+	c.Assert(req.Form["Filter.1.Value.1"], DeepEquals, []string{"value1"})
+	c.Assert(req.Form["Filter.1.Value.2"], IsNil)
+	c.Assert(req.Form["Filter.2.Name"], DeepEquals, []string{"key2"})
+	c.Assert(req.Form["Filter.2.Value.1"], DeepEquals, []string{"value2"})
+	c.Assert(req.Form["Filter.2.Value.2"], DeepEquals, []string{"value3"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "7a62c49f-347e-4fc4-9331-6e8eEXAMPLE")
+	c.Assert(resp.VPCs, HasLen, 1)
+}
+
+func (s *S) TestCreateSubnet(c *C) {
+	testServer.Response(200, nil, CreateSubnetExample)
+
+	options := &ec2.CreateSubnet{
+		AvailabilityZone: "baz",
+		CidrBlock:        "foo",
+		VpcId:            "bar",
+	}
+
+	resp, err := s.ec2.CreateSubnet(options)
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["VpcId"], DeepEquals, []string{"bar"})
+	c.Assert(req.Form["CidrBlock"], DeepEquals, []string{"foo"})
+	c.Assert(req.Form["AvailabilityZone"], DeepEquals, []string{"baz"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "7a62c49f-347e-4fc4-9331-6e8eEXAMPLE")
+	c.Assert(resp.Subnet.SubnetId, Equals, "subnet-9d4a7b6c")
+	c.Assert(resp.Subnet.State, Equals, "pending")
+	c.Assert(resp.Subnet.VpcId, Equals, "vpc-1a2b3c4d")
+	c.Assert(resp.Subnet.CidrBlock, Equals, "10.0.1.0/24")
+	c.Assert(resp.Subnet.AvailableIpAddressCount, Equals, 251)
+}
+
+func (s *S) TestModifySubnetAttribute(c *C) {
+	testServer.Response(200, nil, ModifySubnetAttributeExample)
+
+	options := &ec2.ModifySubnetAttribute{
+		SubnetId:            "foo",
+		MapPublicIpOnLaunch: true,
+	}
+
+	resp, err := s.ec2.ModifySubnetAttribute(options)
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["SubnetId"], DeepEquals, []string{"foo"})
+	c.Assert(req.Form["MapPublicIpOnLaunch.Value"], DeepEquals, []string{"true"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+}
+
+func (s *S) TestResetImageAttribute(c *C) {
+	testServer.Response(200, nil, ResetImageAttributeExample)
+
+	options := ec2.ResetImageAttribute{Attribute: "launchPermission"}
+	resp, err := s.ec2.ResetImageAttribute("i-2ba64342", &options)
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["Action"], DeepEquals, []string{"ResetImageAttribute"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+}
+
+func (s *S) TestDescribeAvailabilityZonesExample1(c *C) {
+	testServer.Response(200, nil, DescribeAvailabilityZonesExample1)
+
+	resp, err := s.ec2.DescribeAvailabilityZones(nil)
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeAvailabilityZones"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+	c.Assert(resp.Zones, HasLen, 4)
+
+	z0 := resp.Zones[0]
+	c.Assert(z0.Name, Equals, "us-east-1a")
+	c.Assert(z0.Region, Equals, "us-east-1")
+	c.Assert(z0.State, Equals, "available")
+	c.Assert(z0.MessageSet, HasLen, 0)
+
+	z1 := resp.Zones[1]
+	c.Assert(z1.Name, Equals, "us-east-1b")
+	c.Assert(z1.Region, Equals, "us-east-1")
+	c.Assert(z1.State, Equals, "available")
+	c.Assert(z1.MessageSet, HasLen, 0)
+
+	z2 := resp.Zones[2]
+	c.Assert(z2.Name, Equals, "us-east-1c")
+	c.Assert(z2.Region, Equals, "us-east-1")
+	c.Assert(z2.State, Equals, "available")
+	c.Assert(z2.MessageSet, HasLen, 0)
+
+	z3 := resp.Zones[3]
+	c.Assert(z3.Name, Equals, "us-east-1d")
+	c.Assert(z3.Region, Equals, "us-east-1")
+	c.Assert(z3.State, Equals, "available")
+	c.Assert(z3.MessageSet, HasLen, 0)
+}
+
+func (s *S) TestDescribeAvailabilityZonesExample2(c *C) {
+	testServer.Response(200, nil, DescribeAvailabilityZonesExample2)
+
+	resp, err := s.ec2.DescribeAvailabilityZones(nil)
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeAvailabilityZones"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+	c.Assert(resp.Zones, HasLen, 2)
+
+	z0 := resp.Zones[0]
+	c.Assert(z0.Name, Equals, "us-east-1a")
+	c.Assert(z0.Region, Equals, "us-east-1")
+	c.Assert(z0.State, Equals, "impaired")
+	c.Assert(z0.MessageSet, HasLen, 0)
+
+	z1 := resp.Zones[1]
+	c.Assert(z1.Name, Equals, "us-east-1b")
+	c.Assert(z1.Region, Equals, "us-east-1")
+	c.Assert(z1.State, Equals, "unavailable")
+	c.Assert(z1.MessageSet, DeepEquals, []string{"us-east-1b is currently down for maintenance."})
+}
+
+func (s *S) TestCreateNetworkAcl(c *C) {
+	testServer.Response(200, nil, CreateNetworkAclExample)
+
+	options := &ec2.CreateNetworkAcl{
+		VpcId: "vpc-11ad4878",
+	}
+
+	resp, err := s.ec2.CreateNetworkAcl(options)
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["VpcId"], DeepEquals, []string{"vpc-11ad4878"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+	c.Assert(resp.NetworkAcl.VpcId, Equals, "vpc-11ad4878")
+	c.Assert(resp.NetworkAcl.NetworkAclId, Equals, "acl-5fb85d36")
+	c.Assert(resp.NetworkAcl.Default, Equals, "false")
+	c.Assert(resp.NetworkAcl.EntrySet, HasLen, 2)
+	c.Assert(resp.NetworkAcl.EntrySet[0].RuleNumber, Equals, 32767)
+	c.Assert(resp.NetworkAcl.EntrySet[0].Protocol, Equals, -1)
+	c.Assert(resp.NetworkAcl.EntrySet[0].RuleAction, Equals, "deny")
+	c.Assert(resp.NetworkAcl.EntrySet[0].Egress, Equals, true)
+	c.Assert(resp.NetworkAcl.EntrySet[0].CidrBlock, Equals, "0.0.0.0/0")
+}
+
+func (s *S) TestCreateNetworkAclEntry(c *C) {
+	testServer.Response(200, nil, CreateNetworkAclEntryRespExample)
+
+	options := &ec2.NetworkAclEntry{
+		RuleNumber: 32767,
+		Protocol:   6,
+		RuleAction: "deny",
+		Egress:     true,
+		CidrBlock:  "0.0.0.0/0",
+		PortRange: ec2.PortRange{
+			To:   22,
+			From: 22,
+		},
+	}
+
+	resp, err := s.ec2.CreateNetworkAclEntry("acl-11ad4878", options)
+
+	req := testServer.WaitRequest()
+
+	c.Assert(req.Form["NetworkAclId"], DeepEquals, []string{"acl-11ad4878"})
+	c.Assert(req.Form["RuleNumber"], DeepEquals, []string{"32767"})
+	c.Assert(req.Form["Protocol"], DeepEquals, []string{"6"})
+	c.Assert(req.Form["RuleAction"], DeepEquals, []string{"deny"})
+	c.Assert(req.Form["Egress"], DeepEquals, []string{"true"})
+	c.Assert(req.Form["CidrBlock"], DeepEquals, []string{"0.0.0.0/0"})
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+}
+
+func (s *S) TestDescribeNetworkAcls(c *C) {
+	testServer.Response(200, nil, DescribeNetworkAclsExample)
+
+	filter := ec2.NewFilter()
+	filter.Add("vpc-id", "vpc-5266953b")
+
+	resp, err := s.ec2.NetworkAcls([]string{"acl-5566953c", "acl-5d659634"}, filter)
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+	c.Assert(resp.NetworkAcls, HasLen, 2)
+	c.Assert(resp.NetworkAcls[1].AssociationSet, HasLen, 2)
+	c.Assert(resp.NetworkAcls[1].AssociationSet[0].NetworkAclAssociationId, Equals, "aclassoc-5c659635")
+	c.Assert(resp.NetworkAcls[1].AssociationSet[0].NetworkAclId, Equals, "acl-5d659634")
+	c.Assert(resp.NetworkAcls[1].AssociationSet[0].SubnetId, Equals, "subnet-ff669596")
+}
+
+func (s *S) TestReplaceNetworkAclAssociation(c *C) {
+	testServer.Response(200, nil, ReplaceNetworkAclAssociationResponseExample)
+
+	resp, err := s.ec2.ReplaceNetworkAclAssociation("aclassoc-e5b95c8c", "acl-5fb85d36")
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
+	c.Assert(resp.NewAssociationId, Equals, "aclassoc-17b85d7e")
+}
+
+func (s *S) TestCreateCustomerGateway(c *C) {
+	testServer.Response(200, nil, CreateCustomerGatewayResponseExample)
+
+	options := &ec2.CreateCustomerGateway{
+		Type:      "ipsec.1",
+		IpAddress: "10.0.0.20",
+		BgpAsn:    65534,
+	}
+
+	resp, err := s.ec2.CreateCustomerGateway(options)
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["Type"], DeepEquals, []string{"ipsec.1"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "7a62c49f-347e-4fc4-9331-6e8eEXAMPLE")
+	c.Assert(resp.CustomerGateway.Type, Equals, "ipsec.1")
+	c.Assert(resp.CustomerGateway.State, Equals, "pending")
+	c.Assert(resp.CustomerGateway.BgpAsn, Equals, 65534)
+	c.Assert(resp.CustomerGateway.IpAddress, Equals, "10.0.0.20")
+}
+
+func (s *S) TestDescribeCustomerGateways(c *C) {
+	testServer.Response(200, nil, DescribeCustomerGatewaysResponseExample)
+
+	filter := ec2.NewFilter()
+	filter.Add("state", "pending")
+
+	resp, err := s.ec2.DescribeCustomerGateways([]string{"cgw-b4dc3961", "cgw-b4dc3962"}, filter)
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["Filter.1.Name"], DeepEquals, []string{"state"})
+	c.Assert(req.Form["Filter.1.Value.1"], DeepEquals, []string{"pending"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "7a62c49f-347e-4fc4-9331-6e8eEXAMPLE")
+	c.Assert(resp.CustomerGateways, HasLen, 2)
+	c.Assert(resp.CustomerGateways[0].CustomerGatewayId, Equals, "cgw-b4dc3961")
+	c.Assert(resp.CustomerGateways[1].CustomerGatewayId, Equals, "cgw-b4dc3962")
+}
+
+func (s *S) TestDeleteCustomerGateway(c *C) {
+	testServer.Response(200, nil, DeleteCustomerGatewayResponseExample)
+
+	resp, err := s.ec2.DeleteCustomerGateway("cgw-b4dc3961")
+
+	req := testServer.WaitRequest()
+	c.Assert(req.Form["CustomerGatewayId"], DeepEquals, []string{"cgw-b4dc3961"})
+
+	c.Assert(err, IsNil)
+	c.Assert(resp.RequestId, Equals, "7a62c49f-347e-4fc4-9331-6e8eEXAMPLE")
+	c.Assert(resp.Return, Equals, true)
+}

+ 204 - 0
Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/ec2i_test.go

@@ -0,0 +1,204 @@
+package ec2_test
+
+import (
+	"crypto/rand"
+	"fmt"
+	"github.com/coreos/flannel/Godeps/_workspace/src/github.com/mitchellh/goamz/aws"
+	"github.com/coreos/flannel/Godeps/_workspace/src/github.com/mitchellh/goamz/ec2"
+	"github.com/mitchellh/goamz/testutil"
+	. "github.com/motain/gocheck"
+)
+
+// AmazonServer represents an Amazon EC2 server.
+type AmazonServer struct {
+	auth aws.Auth
+}
+
+func (s *AmazonServer) SetUp(c *C) {
+	auth, err := aws.EnvAuth()
+	if err != nil {
+		c.Fatal(err.Error())
+	}
+	s.auth = auth
+}
+
+// Suite cost per run: 0.02 USD
+var _ = Suite(&AmazonClientSuite{})
+
+// AmazonClientSuite tests the client against a live EC2 server.
+type AmazonClientSuite struct {
+	srv AmazonServer
+	ClientTests
+}
+
+func (s *AmazonClientSuite) SetUpSuite(c *C) {
+	if !testutil.Amazon {
+		c.Skip("AmazonClientSuite tests not enabled")
+	}
+	s.srv.SetUp(c)
+	s.ec2 = ec2.NewWithClient(s.srv.auth, aws.USEast, testutil.DefaultClient)
+}
+
+// ClientTests defines integration tests designed to test the client.
+// It is not used as a test suite in itself, but embedded within
+// another type.
+type ClientTests struct {
+	ec2 *ec2.EC2
+}
+
+var imageId = "ami-ccf405a5" // Ubuntu Maverick, i386, EBS store
+
+// Cost: 0.00 USD
+func (s *ClientTests) TestRunInstancesError(c *C) {
+	options := ec2.RunInstances{
+		ImageId:      "ami-a6f504cf", // Ubuntu Maverick, i386, instance store
+		InstanceType: "t1.micro",     // Doesn't work with micro, results in 400.
+	}
+
+	resp, err := s.ec2.RunInstances(&options)
+
+	c.Assert(resp, IsNil)
+	c.Assert(err, ErrorMatches, "AMI.*root device.*not supported.*")
+
+	ec2err, ok := err.(*ec2.Error)
+	c.Assert(ok, Equals, true)
+	c.Assert(ec2err.StatusCode, Equals, 400)
+	c.Assert(ec2err.Code, Equals, "UnsupportedOperation")
+	c.Assert(ec2err.Message, Matches, "AMI.*root device.*not supported.*")
+	c.Assert(ec2err.RequestId, Matches, ".+")
+}
+
+// Cost: 0.02 USD
+func (s *ClientTests) TestRunAndTerminate(c *C) {
+	options := ec2.RunInstances{
+		ImageId:      imageId,
+		InstanceType: "t1.micro",
+	}
+	resp1, err := s.ec2.RunInstances(&options)
+	c.Assert(err, IsNil)
+	c.Check(resp1.ReservationId, Matches, "r-[0-9a-f]*")
+	c.Check(resp1.OwnerId, Matches, "[0-9]+")
+	c.Check(resp1.Instances, HasLen, 1)
+	c.Check(resp1.Instances[0].InstanceType, Equals, "t1.micro")
+
+	instId := resp1.Instances[0].InstanceId
+
+	resp2, err := s.ec2.Instances([]string{instId}, nil)
+	c.Assert(err, IsNil)
+	if c.Check(resp2.Reservations, HasLen, 1) && c.Check(len(resp2.Reservations[0].Instances), Equals, 1) {
+		inst := resp2.Reservations[0].Instances[0]
+		c.Check(inst.InstanceId, Equals, instId)
+	}
+
+	resp3, err := s.ec2.TerminateInstances([]string{instId})
+	c.Assert(err, IsNil)
+	c.Check(resp3.StateChanges, HasLen, 1)
+	c.Check(resp3.StateChanges[0].InstanceId, Equals, instId)
+	c.Check(resp3.StateChanges[0].CurrentState.Name, Equals, "shutting-down")
+	c.Check(resp3.StateChanges[0].CurrentState.Code, Equals, 32)
+}
+
+// Cost: 0.00 USD
+func (s *ClientTests) TestSecurityGroups(c *C) {
+	name := "goamz-test"
+	descr := "goamz security group for tests"
+
+	// Clean it up, if a previous test left it around and avoid leaving it around.
+	s.ec2.DeleteSecurityGroup(ec2.SecurityGroup{Name: name})
+	defer s.ec2.DeleteSecurityGroup(ec2.SecurityGroup{Name: name})
+
+	resp1, err := s.ec2.CreateSecurityGroup(ec2.SecurityGroup{Name: name, Description: descr})
+	c.Assert(err, IsNil)
+	c.Assert(resp1.RequestId, Matches, ".+")
+	c.Assert(resp1.Name, Equals, name)
+	c.Assert(resp1.Id, Matches, ".+")
+
+	resp1, err = s.ec2.CreateSecurityGroup(ec2.SecurityGroup{Name: name, Description: descr})
+	ec2err, _ := err.(*ec2.Error)
+	c.Assert(resp1, IsNil)
+	c.Assert(ec2err, NotNil)
+	c.Assert(ec2err.Code, Equals, "InvalidGroup.Duplicate")
+
+	perms := []ec2.IPPerm{{
+		Protocol:  "tcp",
+		FromPort:  0,
+		ToPort:    1024,
+		SourceIPs: []string{"127.0.0.1/24"},
+	}}
+
+	resp2, err := s.ec2.AuthorizeSecurityGroup(ec2.SecurityGroup{Name: name}, perms)
+	c.Assert(err, IsNil)
+	c.Assert(resp2.RequestId, Matches, ".+")
+
+	resp3, err := s.ec2.SecurityGroups(ec2.SecurityGroupNames(name), nil)
+	c.Assert(err, IsNil)
+	c.Assert(resp3.RequestId, Matches, ".+")
+	c.Assert(resp3.Groups, HasLen, 1)
+
+	g0 := resp3.Groups[0]
+	c.Assert(g0.Name, Equals, name)
+	c.Assert(g0.Description, Equals, descr)
+	c.Assert(g0.IPPerms, HasLen, 1)
+	c.Assert(g0.IPPerms[0].Protocol, Equals, "tcp")
+	c.Assert(g0.IPPerms[0].FromPort, Equals, 0)
+	c.Assert(g0.IPPerms[0].ToPort, Equals, 1024)
+	c.Assert(g0.IPPerms[0].SourceIPs, DeepEquals, []string{"127.0.0.1/24"})
+
+	resp2, err = s.ec2.DeleteSecurityGroup(ec2.SecurityGroup{Name: name})
+	c.Assert(err, IsNil)
+	c.Assert(resp2.RequestId, Matches, ".+")
+}
+
+var sessionId = func() string {
+	buf := make([]byte, 8)
+	// if we have no randomness, we'll just make do, so ignore the error.
+	rand.Read(buf)
+	return fmt.Sprintf("%x", buf)
+}()
+
+// sessionName reutrns a name that is probably
+// unique to this test session.
+func sessionName(prefix string) string {
+	return prefix + "-" + sessionId
+}
+
+var allRegions = []aws.Region{
+	aws.USEast,
+	aws.USWest,
+	aws.EUWest,
+	aws.EUCentral,
+	aws.APSoutheast,
+	aws.APNortheast,
+}
+
+// Communicate with all EC2 endpoints to see if they are alive.
+func (s *ClientTests) TestRegions(c *C) {
+	name := sessionName("goamz-region-test")
+	perms := []ec2.IPPerm{{
+		Protocol:  "tcp",
+		FromPort:  80,
+		ToPort:    80,
+		SourceIPs: []string{"127.0.0.1/32"},
+	}}
+	errs := make(chan error, len(allRegions))
+	for _, region := range allRegions {
+		go func(r aws.Region) {
+			e := ec2.NewWithClient(s.ec2.Auth, r, testutil.DefaultClient)
+			_, err := e.AuthorizeSecurityGroup(ec2.SecurityGroup{Name: name}, perms)
+			errs <- err
+		}(region)
+	}
+	for _ = range allRegions {
+		err := <-errs
+		if err != nil {
+			ec2_err, ok := err.(*ec2.Error)
+			if ok {
+				c.Check(ec2_err.Code, Matches, "InvalidGroup.NotFound")
+			} else {
+				c.Errorf("Non-EC2 error: %s", err)
+			}
+		} else {
+			c.Errorf("Test should have errored but it seems to have succeeded")
+		}
+	}
+}

+ 580 - 0
Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/ec2t_test.go

@@ -0,0 +1,580 @@
+package ec2_test
+
+import (
+	"fmt"
+	"github.com/coreos/flannel/Godeps/_workspace/src/github.com/mitchellh/goamz/aws"
+	"github.com/coreos/flannel/Godeps/_workspace/src/github.com/mitchellh/goamz/ec2"
+	"github.com/coreos/flannel/Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/ec2test"
+	"github.com/mitchellh/goamz/testutil"
+	. "github.com/motain/gocheck"
+	"regexp"
+	"sort"
+)
+
+// LocalServer represents a local ec2test fake server.
+type LocalServer struct {
+	auth   aws.Auth
+	region aws.Region
+	srv    *ec2test.Server
+}
+
+func (s *LocalServer) SetUp(c *C) {
+	srv, err := ec2test.NewServer()
+	c.Assert(err, IsNil)
+	c.Assert(srv, NotNil)
+
+	s.srv = srv
+	s.region = aws.Region{EC2Endpoint: srv.URL()}
+}
+
+// LocalServerSuite defines tests that will run
+// against the local ec2test server. It includes
+// selected tests from ClientTests;
+// when the ec2test functionality is sufficient, it should
+// include all of them, and ClientTests can be simply embedded.
+type LocalServerSuite struct {
+	srv LocalServer
+	ServerTests
+	clientTests ClientTests
+}
+
+var _ = Suite(&LocalServerSuite{})
+
+func (s *LocalServerSuite) SetUpSuite(c *C) {
+	s.srv.SetUp(c)
+	s.ServerTests.ec2 = ec2.NewWithClient(s.srv.auth, s.srv.region, testutil.DefaultClient)
+	s.clientTests.ec2 = ec2.NewWithClient(s.srv.auth, s.srv.region, testutil.DefaultClient)
+}
+
+func (s *LocalServerSuite) TestRunAndTerminate(c *C) {
+	s.clientTests.TestRunAndTerminate(c)
+}
+
+func (s *LocalServerSuite) TestSecurityGroups(c *C) {
+	s.clientTests.TestSecurityGroups(c)
+}
+
+// TestUserData is not defined on ServerTests because it
+// requires the ec2test server to function.
+func (s *LocalServerSuite) TestUserData(c *C) {
+	data := make([]byte, 256)
+	for i := range data {
+		data[i] = byte(i)
+	}
+	inst, err := s.ec2.RunInstances(&ec2.RunInstances{
+		ImageId:      imageId,
+		InstanceType: "t1.micro",
+		UserData:     data,
+	})
+	c.Assert(err, IsNil)
+	c.Assert(inst, NotNil)
+	c.Assert(inst.Instances[0].DNSName, Equals, inst.Instances[0].InstanceId+".example.com")
+
+	id := inst.Instances[0].InstanceId
+
+	defer s.ec2.TerminateInstances([]string{id})
+
+	tinst := s.srv.srv.Instance(id)
+	c.Assert(tinst, NotNil)
+	c.Assert(tinst.UserData, DeepEquals, data)
+}
+
+// AmazonServerSuite runs the ec2test server tests against a live EC2 server.
+// It will only be activated if the -all flag is specified.
+type AmazonServerSuite struct {
+	srv AmazonServer
+	ServerTests
+}
+
+var _ = Suite(&AmazonServerSuite{})
+
+func (s *AmazonServerSuite) SetUpSuite(c *C) {
+	if !testutil.Amazon {
+		c.Skip("AmazonServerSuite tests not enabled")
+	}
+	s.srv.SetUp(c)
+	s.ServerTests.ec2 = ec2.NewWithClient(s.srv.auth, aws.USEast, testutil.DefaultClient)
+}
+
+// ServerTests defines a set of tests designed to test
+// the ec2test local fake ec2 server.
+// It is not used as a test suite in itself, but embedded within
+// another type.
+type ServerTests struct {
+	ec2 *ec2.EC2
+}
+
+func terminateInstances(c *C, e *ec2.EC2, insts []*ec2.Instance) {
+	var ids []string
+	for _, inst := range insts {
+		if inst != nil {
+			ids = append(ids, inst.InstanceId)
+		}
+	}
+	_, err := e.TerminateInstances(ids)
+	c.Check(err, IsNil, Commentf("%d INSTANCES LEFT RUNNING!!!", len(ids)))
+}
+
+func (s *ServerTests) makeTestGroup(c *C, name, descr string) ec2.SecurityGroup {
+	// Clean it up if a previous test left it around.
+	_, err := s.ec2.DeleteSecurityGroup(ec2.SecurityGroup{Name: name})
+	if err != nil && err.(*ec2.Error).Code != "InvalidGroup.NotFound" {
+		c.Fatalf("delete security group: %v", err)
+	}
+
+	resp, err := s.ec2.CreateSecurityGroup(ec2.SecurityGroup{Name: name, Description: descr})
+	c.Assert(err, IsNil)
+	c.Assert(resp.Name, Equals, name)
+	return resp.SecurityGroup
+}
+
+func (s *ServerTests) TestIPPerms(c *C) {
+	g0 := s.makeTestGroup(c, "goamz-test0", "ec2test group 0")
+	defer s.ec2.DeleteSecurityGroup(g0)
+
+	g1 := s.makeTestGroup(c, "goamz-test1", "ec2test group 1")
+	defer s.ec2.DeleteSecurityGroup(g1)
+
+	resp, err := s.ec2.SecurityGroups([]ec2.SecurityGroup{g0, g1}, nil)
+	c.Assert(err, IsNil)
+	c.Assert(resp.Groups, HasLen, 2)
+	c.Assert(resp.Groups[0].IPPerms, HasLen, 0)
+	c.Assert(resp.Groups[1].IPPerms, HasLen, 0)
+
+	ownerId := resp.Groups[0].OwnerId
+
+	// test some invalid parameters
+	// TODO more
+	_, err = s.ec2.AuthorizeSecurityGroup(g0, []ec2.IPPerm{{
+		Protocol:  "tcp",
+		FromPort:  0,
+		ToPort:    1024,
+		SourceIPs: []string{"z127.0.0.1/24"},
+	}})
+	c.Assert(err, NotNil)
+	c.Check(err.(*ec2.Error).Code, Equals, "InvalidPermission.Malformed")
+
+	// Check that AuthorizeSecurityGroup adds the correct authorizations.
+	_, err = s.ec2.AuthorizeSecurityGroup(g0, []ec2.IPPerm{{
+		Protocol:  "tcp",
+		FromPort:  2000,
+		ToPort:    2001,
+		SourceIPs: []string{"127.0.0.0/24"},
+		SourceGroups: []ec2.UserSecurityGroup{{
+			Name: g1.Name,
+		}, {
+			Id: g0.Id,
+		}},
+	}, {
+		Protocol:  "tcp",
+		FromPort:  2000,
+		ToPort:    2001,
+		SourceIPs: []string{"200.1.1.34/32"},
+	}})
+	c.Assert(err, IsNil)
+
+	resp, err = s.ec2.SecurityGroups([]ec2.SecurityGroup{g0}, nil)
+	c.Assert(err, IsNil)
+	c.Assert(resp.Groups, HasLen, 1)
+	c.Assert(resp.Groups[0].IPPerms, HasLen, 1)
+
+	perm := resp.Groups[0].IPPerms[0]
+	srcg := perm.SourceGroups
+	c.Assert(srcg, HasLen, 2)
+
+	// Normalize so we don't care about returned order.
+	if srcg[0].Name == g1.Name {
+		srcg[0], srcg[1] = srcg[1], srcg[0]
+	}
+	c.Check(srcg[0].Name, Equals, g0.Name)
+	c.Check(srcg[0].Id, Equals, g0.Id)
+	c.Check(srcg[0].OwnerId, Equals, ownerId)
+	c.Check(srcg[1].Name, Equals, g1.Name)
+	c.Check(srcg[1].Id, Equals, g1.Id)
+	c.Check(srcg[1].OwnerId, Equals, ownerId)
+
+	sort.Strings(perm.SourceIPs)
+	c.Check(perm.SourceIPs, DeepEquals, []string{"127.0.0.0/24", "200.1.1.34/32"})
+
+	// Check that we can't delete g1 (because g0 is using it)
+	_, err = s.ec2.DeleteSecurityGroup(g1)
+	c.Assert(err, NotNil)
+	c.Check(err.(*ec2.Error).Code, Equals, "InvalidGroup.InUse")
+
+	_, err = s.ec2.RevokeSecurityGroup(g0, []ec2.IPPerm{{
+		Protocol:     "tcp",
+		FromPort:     2000,
+		ToPort:       2001,
+		SourceGroups: []ec2.UserSecurityGroup{{Id: g1.Id}},
+	}, {
+		Protocol:  "tcp",
+		FromPort:  2000,
+		ToPort:    2001,
+		SourceIPs: []string{"200.1.1.34/32"},
+	}})
+	c.Assert(err, IsNil)
+
+	resp, err = s.ec2.SecurityGroups([]ec2.SecurityGroup{g0}, nil)
+	c.Assert(err, IsNil)
+	c.Assert(resp.Groups, HasLen, 1)
+	c.Assert(resp.Groups[0].IPPerms, HasLen, 1)
+
+	perm = resp.Groups[0].IPPerms[0]
+	srcg = perm.SourceGroups
+	c.Assert(srcg, HasLen, 1)
+	c.Check(srcg[0].Name, Equals, g0.Name)
+	c.Check(srcg[0].Id, Equals, g0.Id)
+	c.Check(srcg[0].OwnerId, Equals, ownerId)
+
+	c.Check(perm.SourceIPs, DeepEquals, []string{"127.0.0.0/24"})
+
+	// We should be able to delete g1 now because we've removed its only use.
+	_, err = s.ec2.DeleteSecurityGroup(g1)
+	c.Assert(err, IsNil)
+
+	_, err = s.ec2.DeleteSecurityGroup(g0)
+	c.Assert(err, IsNil)
+
+	f := ec2.NewFilter()
+	f.Add("group-id", g0.Id, g1.Id)
+	resp, err = s.ec2.SecurityGroups(nil, f)
+	c.Assert(err, IsNil)
+	c.Assert(resp.Groups, HasLen, 0)
+}
+
+func (s *ServerTests) TestDuplicateIPPerm(c *C) {
+	name := "goamz-test"
+	descr := "goamz security group for tests"
+
+	// Clean it up, if a previous test left it around and avoid leaving it around.
+	s.ec2.DeleteSecurityGroup(ec2.SecurityGroup{Name: name})
+	defer s.ec2.DeleteSecurityGroup(ec2.SecurityGroup{Name: name})
+
+	resp1, err := s.ec2.CreateSecurityGroup(ec2.SecurityGroup{Name: name, Description: descr})
+	c.Assert(err, IsNil)
+	c.Assert(resp1.Name, Equals, name)
+
+	perms := []ec2.IPPerm{{
+		Protocol:  "tcp",
+		FromPort:  200,
+		ToPort:    1024,
+		SourceIPs: []string{"127.0.0.1/24"},
+	}, {
+		Protocol:  "tcp",
+		FromPort:  0,
+		ToPort:    100,
+		SourceIPs: []string{"127.0.0.1/24"},
+	}}
+
+	_, err = s.ec2.AuthorizeSecurityGroup(ec2.SecurityGroup{Name: name}, perms[0:1])
+	c.Assert(err, IsNil)
+
+	_, err = s.ec2.AuthorizeSecurityGroup(ec2.SecurityGroup{Name: name}, perms[0:2])
+	c.Assert(err, ErrorMatches, `.*\(InvalidPermission.Duplicate\)`)
+}
+
+type filterSpec struct {
+	name   string
+	values []string
+}
+
+func (s *ServerTests) TestInstanceFiltering(c *C) {
+	groupResp, err := s.ec2.CreateSecurityGroup(ec2.SecurityGroup{Name: sessionName("testgroup1"), Description: "testgroup one description"})
+	c.Assert(err, IsNil)
+	group1 := groupResp.SecurityGroup
+	defer s.ec2.DeleteSecurityGroup(group1)
+
+	groupResp, err = s.ec2.CreateSecurityGroup(ec2.SecurityGroup{Name: sessionName("testgroup2"), Description: "testgroup two description"})
+	c.Assert(err, IsNil)
+	group2 := groupResp.SecurityGroup
+	defer s.ec2.DeleteSecurityGroup(group2)
+
+	insts := make([]*ec2.Instance, 3)
+	inst, err := s.ec2.RunInstances(&ec2.RunInstances{
+		MinCount:       2,
+		ImageId:        imageId,
+		InstanceType:   "t1.micro",
+		SecurityGroups: []ec2.SecurityGroup{group1},
+	})
+	c.Assert(err, IsNil)
+	insts[0] = &inst.Instances[0]
+	insts[1] = &inst.Instances[1]
+	defer terminateInstances(c, s.ec2, insts)
+
+	imageId2 := "ami-e358958a" // Natty server, i386, EBS store
+	inst, err = s.ec2.RunInstances(&ec2.RunInstances{
+		ImageId:        imageId2,
+		InstanceType:   "t1.micro",
+		SecurityGroups: []ec2.SecurityGroup{group2},
+	})
+	c.Assert(err, IsNil)
+	insts[2] = &inst.Instances[0]
+
+	ids := func(indices ...int) (instIds []string) {
+		for _, index := range indices {
+			instIds = append(instIds, insts[index].InstanceId)
+		}
+		return
+	}
+
+	tests := []struct {
+		about       string
+		instanceIds []string     // instanceIds argument to Instances method.
+		filters     []filterSpec // filters argument to Instances method.
+		resultIds   []string     // set of instance ids of expected results.
+		allowExtra  bool         // resultIds may be incomplete.
+		err         string       // expected error.
+	}{
+		{
+			about:      "check that Instances returns all instances",
+			resultIds:  ids(0, 1, 2),
+			allowExtra: true,
+		}, {
+			about:       "check that specifying two instance ids returns them",
+			instanceIds: ids(0, 2),
+			resultIds:   ids(0, 2),
+		}, {
+			about:       "check that specifying a non-existent instance id gives an error",
+			instanceIds: append(ids(0), "i-deadbeef"),
+			err:         `.*\(InvalidInstanceID\.NotFound\)`,
+		}, {
+			about: "check that a filter allowed both instances returns both of them",
+			filters: []filterSpec{
+				{"instance-id", ids(0, 2)},
+			},
+			resultIds: ids(0, 2),
+		}, {
+			about: "check that a filter allowing only one instance returns it",
+			filters: []filterSpec{
+				{"instance-id", ids(1)},
+			},
+			resultIds: ids(1),
+		}, {
+			about: "check that a filter allowing no instances returns none",
+			filters: []filterSpec{
+				{"instance-id", []string{"i-deadbeef12345"}},
+			},
+		}, {
+			about: "check that filtering on group id works",
+			filters: []filterSpec{
+				{"group-id", []string{group1.Id}},
+			},
+			resultIds: ids(0, 1),
+		}, {
+			about: "check that filtering on group name works",
+			filters: []filterSpec{
+				{"group-name", []string{group1.Name}},
+			},
+			resultIds: ids(0, 1),
+		}, {
+			about: "check that filtering on image id works",
+			filters: []filterSpec{
+				{"image-id", []string{imageId}},
+			},
+			resultIds:  ids(0, 1),
+			allowExtra: true,
+		}, {
+			about: "combination filters 1",
+			filters: []filterSpec{
+				{"image-id", []string{imageId, imageId2}},
+				{"group-name", []string{group1.Name}},
+			},
+			resultIds: ids(0, 1),
+		}, {
+			about: "combination filters 2",
+			filters: []filterSpec{
+				{"image-id", []string{imageId2}},
+				{"group-name", []string{group1.Name}},
+			},
+		},
+	}
+	for i, t := range tests {
+		c.Logf("%d. %s", i, t.about)
+		var f *ec2.Filter
+		if t.filters != nil {
+			f = ec2.NewFilter()
+			for _, spec := range t.filters {
+				f.Add(spec.name, spec.values...)
+			}
+		}
+		resp, err := s.ec2.Instances(t.instanceIds, f)
+		if t.err != "" {
+			c.Check(err, ErrorMatches, t.err)
+			continue
+		}
+		c.Assert(err, IsNil)
+		insts := make(map[string]*ec2.Instance)
+		for _, r := range resp.Reservations {
+			for j := range r.Instances {
+				inst := &r.Instances[j]
+				c.Check(insts[inst.InstanceId], IsNil, Commentf("duplicate instance id: %q", inst.InstanceId))
+				insts[inst.InstanceId] = inst
+			}
+		}
+		if !t.allowExtra {
+			c.Check(insts, HasLen, len(t.resultIds), Commentf("expected %d instances got %#v", len(t.resultIds), insts))
+		}
+		for j, id := range t.resultIds {
+			c.Check(insts[id], NotNil, Commentf("instance id %d (%q) not found; got %#v", j, id, insts))
+		}
+	}
+}
+
+func idsOnly(gs []ec2.SecurityGroup) []ec2.SecurityGroup {
+	for i := range gs {
+		gs[i].Name = ""
+	}
+	return gs
+}
+
+func namesOnly(gs []ec2.SecurityGroup) []ec2.SecurityGroup {
+	for i := range gs {
+		gs[i].Id = ""
+	}
+	return gs
+}
+
+func (s *ServerTests) TestGroupFiltering(c *C) {
+	g := make([]ec2.SecurityGroup, 4)
+	for i := range g {
+		resp, err := s.ec2.CreateSecurityGroup(ec2.SecurityGroup{Name: sessionName(fmt.Sprintf("testgroup%d", i)), Description: fmt.Sprintf("testdescription%d", i)})
+		c.Assert(err, IsNil)
+		g[i] = resp.SecurityGroup
+		c.Logf("group %d: %v", i, g[i])
+		defer s.ec2.DeleteSecurityGroup(g[i])
+	}
+
+	perms := [][]ec2.IPPerm{
+		{{
+			Protocol:  "tcp",
+			FromPort:  100,
+			ToPort:    200,
+			SourceIPs: []string{"1.2.3.4/32"},
+		}},
+		{{
+			Protocol:     "tcp",
+			FromPort:     200,
+			ToPort:       300,
+			SourceGroups: []ec2.UserSecurityGroup{{Id: g[1].Id}},
+		}},
+		{{
+			Protocol:     "udp",
+			FromPort:     200,
+			ToPort:       400,
+			SourceGroups: []ec2.UserSecurityGroup{{Id: g[1].Id}},
+		}},
+	}
+	for i, ps := range perms {
+		_, err := s.ec2.AuthorizeSecurityGroup(g[i], ps)
+		c.Assert(err, IsNil)
+	}
+
+	groups := func(indices ...int) (gs []ec2.SecurityGroup) {
+		for _, index := range indices {
+			gs = append(gs, g[index])
+		}
+		return
+	}
+
+	type groupTest struct {
+		about      string
+		groups     []ec2.SecurityGroup // groupIds argument to SecurityGroups method.
+		filters    []filterSpec        // filters argument to SecurityGroups method.
+		results    []ec2.SecurityGroup // set of expected result groups.
+		allowExtra bool                // specified results may be incomplete.
+		err        string              // expected error.
+	}
+	filterCheck := func(name, val string, gs []ec2.SecurityGroup) groupTest {
+		return groupTest{
+			about:      "filter check " + name,
+			filters:    []filterSpec{{name, []string{val}}},
+			results:    gs,
+			allowExtra: true,
+		}
+	}
+	tests := []groupTest{
+		{
+			about:      "check that SecurityGroups returns all groups",
+			results:    groups(0, 1, 2, 3),
+			allowExtra: true,
+		}, {
+			about:   "check that specifying two group ids returns them",
+			groups:  idsOnly(groups(0, 2)),
+			results: groups(0, 2),
+		}, {
+			about:   "check that specifying names only works",
+			groups:  namesOnly(groups(0, 2)),
+			results: groups(0, 2),
+		}, {
+			about:  "check that specifying a non-existent group id gives an error",
+			groups: append(groups(0), ec2.SecurityGroup{Id: "sg-eeeeeeeee"}),
+			err:    `.*\(InvalidGroup\.NotFound\)`,
+		}, {
+			about: "check that a filter allowed two groups returns both of them",
+			filters: []filterSpec{
+				{"group-id", []string{g[0].Id, g[2].Id}},
+			},
+			results: groups(0, 2),
+		},
+		{
+			about:  "check that the previous filter works when specifying a list of ids",
+			groups: groups(1, 2),
+			filters: []filterSpec{
+				{"group-id", []string{g[0].Id, g[2].Id}},
+			},
+			results: groups(2),
+		}, {
+			about: "check that a filter allowing no groups returns none",
+			filters: []filterSpec{
+				{"group-id", []string{"sg-eeeeeeeee"}},
+			},
+		},
+		filterCheck("description", "testdescription1", groups(1)),
+		filterCheck("group-name", g[2].Name, groups(2)),
+		filterCheck("ip-permission.cidr", "1.2.3.4/32", groups(0)),
+		filterCheck("ip-permission.group-name", g[1].Name, groups(1, 2)),
+		filterCheck("ip-permission.protocol", "udp", groups(2)),
+		filterCheck("ip-permission.from-port", "200", groups(1, 2)),
+		filterCheck("ip-permission.to-port", "200", groups(0)),
+		// TODO owner-id
+	}
+	for i, t := range tests {
+		c.Logf("%d. %s", i, t.about)
+		var f *ec2.Filter
+		if t.filters != nil {
+			f = ec2.NewFilter()
+			for _, spec := range t.filters {
+				f.Add(spec.name, spec.values...)
+			}
+		}
+		resp, err := s.ec2.SecurityGroups(t.groups, f)
+		if t.err != "" {
+			c.Check(err, ErrorMatches, t.err)
+			continue
+		}
+		c.Assert(err, IsNil)
+		groups := make(map[string]*ec2.SecurityGroup)
+		for j := range resp.Groups {
+			group := &resp.Groups[j].SecurityGroup
+			c.Check(groups[group.Id], IsNil, Commentf("duplicate group id: %q", group.Id))
+
+			groups[group.Id] = group
+		}
+		// If extra groups may be returned, eliminate all groups that
+		// we did not create in this session apart from the default group.
+		if t.allowExtra {
+			namePat := regexp.MustCompile(sessionName("testgroup[0-9]"))
+			for id, g := range groups {
+				if !namePat.MatchString(g.Name) {
+					delete(groups, id)
+				}
+			}
+		}
+		c.Check(groups, HasLen, len(t.results))
+		for j, g := range t.results {
+			rg := groups[g.Id]
+			c.Assert(rg, NotNil, Commentf("group %d (%v) not found; got %#v", j, g, groups))
+			c.Check(rg.Name, Equals, g.Name, Commentf("group %d (%v)", j, g))
+		}
+	}
+}

+ 84 - 0
Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/ec2test/filter.go

@@ -0,0 +1,84 @@
+package ec2test
+
+import (
+	"fmt"
+	"net/url"
+	"strings"
+)
+
+// filter holds an ec2 filter.  A filter maps an attribute to a set of
+// possible values for that attribute. For an item to pass through the
+// filter, every attribute of the item mentioned in the filter must match
+// at least one of its given values.
+type filter map[string][]string
+
+// newFilter creates a new filter from the Filter fields in the url form.
+//
+// The filtering is specified through a map of name=>values, where the
+// name is a well-defined key identifying the data to be matched,
+// and the list of values holds the possible values the filtered
+// item can take for the key to be included in the
+// result set. For example:
+//
+//   Filter.1.Name=instance-type
+//   Filter.1.Value.1=m1.small
+//   Filter.1.Value.2=m1.large
+//
+func newFilter(form url.Values) filter {
+	// TODO return an error if the fields are not well formed?
+	names := make(map[int]string)
+	values := make(map[int][]string)
+	maxId := 0
+	for name, fvalues := range form {
+		var rest string
+		var id int
+		if x, _ := fmt.Sscanf(name, "Filter.%d.%s", &id, &rest); x != 2 {
+			continue
+		}
+		if id > maxId {
+			maxId = id
+		}
+		if rest == "Name" {
+			names[id] = fvalues[0]
+			continue
+		}
+		if !strings.HasPrefix(rest, "Value.") {
+			continue
+		}
+		values[id] = append(values[id], fvalues[0])
+	}
+
+	f := make(filter)
+	for id, name := range names {
+		f[name] = values[id]
+	}
+	return f
+}
+
+func notDigit(r rune) bool {
+	return r < '0' || r > '9'
+}
+
+// filterable represents an object that can be passed through a filter.
+type filterable interface {
+	// matchAttr returns true if given attribute of the
+	// object matches value. It returns an error if the
+	// attribute is not recognised or the value is malformed.
+	matchAttr(attr, value string) (bool, error)
+}
+
+// ok returns true if x passes through the filter.
+func (f filter) ok(x filterable) (bool, error) {
+next:
+	for a, vs := range f {
+		for _, v := range vs {
+			if ok, err := x.matchAttr(a, v); ok {
+				continue next
+			} else if err != nil {
+				return false, fmt.Errorf("bad attribute or value %q=%q for type %T: %v", a, v, x, err)
+			}
+		}
+		return false, nil
+	}
+	return true, nil
+}

+ 993 - 0
Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/ec2test/server.go

@@ -0,0 +1,993 @@
+// The ec2test package implements a fake EC2 provider with
+// the capability of inducing errors on any given operation,
+// and retrospectively determining what operations have been
+// carried out.
+package ec2test
+
+import (
+	"encoding/base64"
+	"encoding/xml"
+	"fmt"
+	"github.com/coreos/flannel/Godeps/_workspace/src/github.com/mitchellh/goamz/ec2"
+	"io"
+	"net"
+	"net/http"
+	"net/url"
+	"regexp"
+	"strconv"
+	"strings"
+	"sync"
+)
+
+var b64 = base64.StdEncoding
+
+// Action represents a request that changes the ec2 state.
+type Action struct {
+	RequestId string
+
+	// Request holds the requested action as a url.Values instance
+	Request url.Values
+
+	// If the action succeeded, Response holds the value that
+	// was marshalled to build the XML response for the request.
+	Response interface{}
+
+	// If the action failed, Err holds an error giving details of the failure.
+	Err *ec2.Error
+}
+
+// TODO possible other things:
+// - some virtual time stamp interface, so a client
+// can ask for all actions after a certain virtual time.
+
+// Server implements an EC2 simulator for use in testing.
+type Server struct {
+	url      string
+	listener net.Listener
+	mu       sync.Mutex
+	reqs     []*Action
+
+	instances            map[string]*Instance      // id -> instance
+	reservations         map[string]*reservation   // id -> reservation
+	groups               map[string]*securityGroup // id -> group
+	maxId                counter
+	reqId                counter
+	reservationId        counter
+	groupId              counter
+	initialInstanceState ec2.InstanceState
+}
+
+// reservation holds a simulated ec2 reservation.
+type reservation struct {
+	id        string
+	instances map[string]*Instance
+	groups    []*securityGroup
+}
+
+// instance holds a simulated ec2 instance
+type Instance struct {
+	// UserData holds the data that was passed to the RunInstances request
+	// when the instance was started.
+	UserData    []byte
+	id          string
+	imageId     string
+	reservation *reservation
+	instType    string
+	state       ec2.InstanceState
+}
+
+// permKey represents permission for a given security
+// group or IP address (but not both) to access a given range of
+// ports. Equality of permKeys is used in the implementation of
+// permission sets, relying on the uniqueness of securityGroup
+// instances.
+type permKey struct {
+	protocol string
+	fromPort int
+	toPort   int
+	group    *securityGroup
+	ipAddr   string
+}
+
+// securityGroup holds a simulated ec2 security group.
+// Instances of securityGroup should only be created through
+// Server.createSecurityGroup to ensure that groups can be
+// compared by pointer value.
+type securityGroup struct {
+	id          string
+	name        string
+	description string
+
+	perms map[permKey]bool
+}
+
+func (g *securityGroup) ec2SecurityGroup() ec2.SecurityGroup {
+	return ec2.SecurityGroup{
+		Name: g.name,
+		Id:   g.id,
+	}
+}
+
+func (g *securityGroup) matchAttr(attr, value string) (ok bool, err error) {
+	switch attr {
+	case "description":
+		return g.description == value, nil
+	case "group-id":
+		return g.id == value, nil
+	case "group-name":
+		return g.name == value, nil
+	case "ip-permission.cidr":
+		return g.hasPerm(func(k permKey) bool { return k.ipAddr == value }), nil
+	case "ip-permission.group-name":
+		return g.hasPerm(func(k permKey) bool {
+			return k.group != nil && k.group.name == value
+		}), nil
+	case "ip-permission.from-port":
+		port, err := strconv.Atoi(value)
+		if err != nil {
+			return false, err
+		}
+		return g.hasPerm(func(k permKey) bool { return k.fromPort == port }), nil
+	case "ip-permission.to-port":
+		port, err := strconv.Atoi(value)
+		if err != nil {
+			return false, err
+		}
+		return g.hasPerm(func(k permKey) bool { return k.toPort == port }), nil
+	case "ip-permission.protocol":
+		return g.hasPerm(func(k permKey) bool { return k.protocol == value }), nil
+	case "owner-id":
+		return value == ownerId, nil
+	}
+	return false, fmt.Errorf("unknown attribute %q", attr)
+}
+
+func (g *securityGroup) hasPerm(test func(k permKey) bool) bool {
+	for k := range g.perms {
+		if test(k) {
+			return true
+		}
+	}
+	return false
+}
+
+// ec2Perms returns the list of EC2 permissions granted
+// to g. It groups permissions by port range and protocol.
+func (g *securityGroup) ec2Perms() (perms []ec2.IPPerm) {
+	// The grouping is held in result. We use permKey for convenience,
+	// (ensuring that the group and ipAddr of each key is zero). For
+	// each protocol/port range combination, we build up the permission
+	// set in the associated value.
+	result := make(map[permKey]*ec2.IPPerm)
+	for k := range g.perms {
+		groupKey := k
+		groupKey.group = nil
+		groupKey.ipAddr = ""
+
+		ec2p := result[groupKey]
+		if ec2p == nil {
+			ec2p = &ec2.IPPerm{
+				Protocol: k.protocol,
+				FromPort: k.fromPort,
+				ToPort:   k.toPort,
+			}
+			result[groupKey] = ec2p
+		}
+		if k.group != nil {
+			ec2p.SourceGroups = append(ec2p.SourceGroups,
+				ec2.UserSecurityGroup{
+					Id:      k.group.id,
+					Name:    k.group.name,
+					OwnerId: ownerId,
+				})
+		} else {
+			ec2p.SourceIPs = append(ec2p.SourceIPs, k.ipAddr)
+		}
+	}
+	for _, ec2p := range result {
+		perms = append(perms, *ec2p)
+	}
+	return
+}
+
+var actions = map[string]func(*Server, http.ResponseWriter, *http.Request, string) interface{}{
+	"RunInstances":                  (*Server).runInstances,
+	"TerminateInstances":            (*Server).terminateInstances,
+	"DescribeInstances":             (*Server).describeInstances,
+	"CreateSecurityGroup":           (*Server).createSecurityGroup,
+	"DescribeSecurityGroups":        (*Server).describeSecurityGroups,
+	"DeleteSecurityGroup":           (*Server).deleteSecurityGroup,
+	"AuthorizeSecurityGroupIngress": (*Server).authorizeSecurityGroupIngress,
+	"RevokeSecurityGroupIngress":    (*Server).revokeSecurityGroupIngress,
+}
+
+const ownerId = "9876"
+
+// newAction allocates a new action and adds it to the
+// recorded list of server actions.
+func (srv *Server) newAction() *Action {
+	srv.mu.Lock()
+	defer srv.mu.Unlock()
+
+	a := new(Action)
+	srv.reqs = append(srv.reqs, a)
+	return a
+}
+
+// NewServer returns a new server.
+func NewServer() (*Server, error) {
+	srv := &Server{
+		instances:            make(map[string]*Instance),
+		groups:               make(map[string]*securityGroup),
+		reservations:         make(map[string]*reservation),
+		initialInstanceState: Pending,
+	}
+
+	// Add default security group.
+	g := &securityGroup{
+		name:        "default",
+		description: "default group",
+		id:          fmt.Sprintf("sg-%d", srv.groupId.next()),
+	}
+	g.perms = map[permKey]bool{
+		permKey{
+			protocol: "icmp",
+			fromPort: -1,
+			toPort:   -1,
+			group:    g,
+		}: true,
+		permKey{
+			protocol: "tcp",
+			fromPort: 0,
+			toPort:   65535,
+			group:    g,
+		}: true,
+		permKey{
+			protocol: "udp",
+			fromPort: 0,
+			toPort:   65535,
+			group:    g,
+		}: true,
+	}
+	srv.groups[g.id] = g
+
+	l, err := net.Listen("tcp", "localhost:0")
+	if err != nil {
+		return nil, fmt.Errorf("cannot listen on localhost: %v", err)
+	}
+	srv.listener = l
+
+	srv.url = "http://" + l.Addr().String()
+
+	// we use HandlerFunc rather than *Server directly so that we
+	// can avoid exporting HandlerFunc from *Server.
+	go http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+		srv.serveHTTP(w, req)
+	}))
+	return srv, nil
+}
+
+// Quit closes down the server.
+func (srv *Server) Quit() {
+	srv.listener.Close()
+}
+
+// SetInitialInstanceState sets the state that any new instances will be started in.
+func (srv *Server) SetInitialInstanceState(state ec2.InstanceState) {
+	srv.mu.Lock()
+	srv.initialInstanceState = state
+	srv.mu.Unlock()
+}
+
+// URL returns the URL of the server.
+func (srv *Server) URL() string {
+	return srv.url
+}
+
+// serveHTTP serves the EC2 protocol.
+func (srv *Server) serveHTTP(w http.ResponseWriter, req *http.Request) {
+	req.ParseForm()
+
+	a := srv.newAction()
+	a.RequestId = fmt.Sprintf("req%d", srv.reqId.next())
+	a.Request = req.Form
+
+	// Methods on Server that deal with parsing user data
+	// may fail. To save on error handling code, we allow these
+	// methods to call fatalf, which will panic with an *ec2.Error
+	// which will be caught here and returned
+	// to the client as a properly formed EC2 error.
+	defer func() {
+		switch err := recover().(type) {
+		case *ec2.Error:
+			a.Err = err
+			err.RequestId = a.RequestId
+			writeError(w, err)
+		case nil:
+		default:
+			panic(err)
+		}
+	}()
+
+	f := actions[req.Form.Get("Action")]
+	if f == nil {
+		fatalf(400, "InvalidParameterValue", "Unrecognized Action")
+	}
+
+	response := f(srv, w, req, a.RequestId)
+	a.Response = response
+
+	w.Header().Set("Content-Type", `xml version="1.0" encoding="UTF-8"`)
+	xmlMarshal(w, response)
+}
+
+// Instance returns the instance for the given instance id.
+// It returns nil if there is no such instance.
+func (srv *Server) Instance(id string) *Instance {
+	srv.mu.Lock()
+	defer srv.mu.Unlock()
+	return srv.instances[id]
+}
+
+// writeError writes an appropriate error response.
+// TODO how should we deal with errors when the
+// error itself is potentially generated by backend-agnostic
+// code?
+func writeError(w http.ResponseWriter, err *ec2.Error) {
+	// Error encapsulates an error returned by EC2.
+	// TODO merge with ec2.Error when xml supports ignoring a field.
+	type ec2error struct {
+		Code      string // EC2 error code ("UnsupportedOperation", ...)
+		Message   string // The human-oriented error message
+		RequestId string
+	}
+
+	type Response struct {
+		RequestId string
+		Errors    []ec2error `xml:"Errors>Error"`
+	}
+
+	w.Header().Set("Content-Type", `xml version="1.0" encoding="UTF-8"`)
+	w.WriteHeader(err.StatusCode)
+	xmlMarshal(w, Response{
+		RequestId: err.RequestId,
+		Errors: []ec2error{{
+			Code:    err.Code,
+			Message: err.Message,
+		}},
+	})
+}
+
+// xmlMarshal is the same as xml.Marshal except that
+// it panics on error. The marshalling should not fail,
+// but we want to know if it does.
+func xmlMarshal(w io.Writer, x interface{}) {
+	if err := xml.NewEncoder(w).Encode(x); err != nil {
+		panic(fmt.Errorf("error marshalling %#v: %v", x, err))
+	}
+}
+
+// formToGroups parses a set of SecurityGroup form values
+// as found in a RunInstances request, and returns the resulting
+// slice of security groups.
+// It calls fatalf if a group is not found.
+func (srv *Server) formToGroups(form url.Values) []*securityGroup {
+	var groups []*securityGroup
+	for name, values := range form {
+		switch {
+		case strings.HasPrefix(name, "SecurityGroupId."):
+			if g := srv.groups[values[0]]; g != nil {
+				groups = append(groups, g)
+			} else {
+				fatalf(400, "InvalidGroup.NotFound", "unknown group id %q", values[0])
+			}
+		case strings.HasPrefix(name, "SecurityGroup."):
+			var found *securityGroup
+			for _, g := range srv.groups {
+				if g.name == values[0] {
+					found = g
+				}
+			}
+			if found == nil {
+				fatalf(400, "InvalidGroup.NotFound", "unknown group name %q", values[0])
+			}
+			groups = append(groups, found)
+		}
+	}
+	return groups
+}
+
+// runInstances implements the EC2 RunInstances entry point.
+func (srv *Server) runInstances(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
+	min := atoi(req.Form.Get("MinCount"))
+	max := atoi(req.Form.Get("MaxCount"))
+	if min < 0 || max < 1 {
+		fatalf(400, "InvalidParameterValue", "bad values for MinCount or MaxCount")
+	}
+	if min > max {
+		fatalf(400, "InvalidParameterCombination", "MinCount is greater than MaxCount")
+	}
+	var userData []byte
+	if data := req.Form.Get("UserData"); data != "" {
+		var err error
+		userData, err = b64.DecodeString(data)
+		if err != nil {
+			fatalf(400, "InvalidParameterValue", "bad UserData value: %v", err)
+		}
+	}
+
+	// TODO attributes still to consider:
+	//    ImageId:                  accept anything, we can verify later
+	//    KeyName                   ?
+	//    InstanceType              ?
+	//    KernelId                  ?
+	//    RamdiskId                 ?
+	//    AvailZone                 ?
+	//    GroupName                 tag
+	//    Monitoring                ignore?
+	//    SubnetId                  ?
+	//    DisableAPITermination     bool
+	//    ShutdownBehavior          string
+	//    PrivateIPAddress          string
+
+	srv.mu.Lock()
+	defer srv.mu.Unlock()
+
+	// make sure that form fields are correct before creating the reservation.
+	instType := req.Form.Get("InstanceType")
+	imageId := req.Form.Get("ImageId")
+
+	r := srv.newReservation(srv.formToGroups(req.Form))
+
+	var resp ec2.RunInstancesResp
+	resp.RequestId = reqId
+	resp.ReservationId = r.id
+	resp.OwnerId = ownerId
+
+	for i := 0; i < max; i++ {
+		inst := srv.newInstance(r, instType, imageId, srv.initialInstanceState)
+		inst.UserData = userData
+		resp.Instances = append(resp.Instances, inst.ec2instance())
+	}
+	return &resp
+}
+
+func (srv *Server) group(group ec2.SecurityGroup) *securityGroup {
+	if group.Id != "" {
+		return srv.groups[group.Id]
+	}
+	for _, g := range srv.groups {
+		if g.name == group.Name {
+			return g
+		}
+	}
+	return nil
+}
+
+// NewInstances creates n new instances in srv with the given instance type,
+// image ID,  initial state and security groups. If any group does not already
+// exist, it will be created. NewInstances returns the ids of the new instances.
+func (srv *Server) NewInstances(n int, instType string, imageId string, state ec2.InstanceState, groups []ec2.SecurityGroup) []string {
+	srv.mu.Lock()
+	defer srv.mu.Unlock()
+
+	rgroups := make([]*securityGroup, len(groups))
+	for i, group := range groups {
+		g := srv.group(group)
+		if g == nil {
+			fatalf(400, "InvalidGroup.NotFound", "no such group %v", g)
+		}
+		rgroups[i] = g
+	}
+	r := srv.newReservation(rgroups)
+
+	ids := make([]string, n)
+	for i := 0; i < n; i++ {
+		inst := srv.newInstance(r, instType, imageId, state)
+		ids[i] = inst.id
+	}
+	return ids
+}
+
+func (srv *Server) newInstance(r *reservation, instType string, imageId string, state ec2.InstanceState) *Instance {
+	inst := &Instance{
+		id:          fmt.Sprintf("i-%d", srv.maxId.next()),
+		instType:    instType,
+		imageId:     imageId,
+		state:       state,
+		reservation: r,
+	}
+	srv.instances[inst.id] = inst
+	r.instances[inst.id] = inst
+	return inst
+}
+
+func (srv *Server) newReservation(groups []*securityGroup) *reservation {
+	r := &reservation{
+		id:        fmt.Sprintf("r-%d", srv.reservationId.next()),
+		instances: make(map[string]*Instance),
+		groups:    groups,
+	}
+
+	srv.reservations[r.id] = r
+	return r
+}
+
+func (srv *Server) terminateInstances(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
+	srv.mu.Lock()
+	defer srv.mu.Unlock()
+	var resp ec2.TerminateInstancesResp
+	resp.RequestId = reqId
+	var insts []*Instance
+	for attr, vals := range req.Form {
+		if strings.HasPrefix(attr, "InstanceId.") {
+			id := vals[0]
+			inst := srv.instances[id]
+			if inst == nil {
+				fatalf(400, "InvalidInstanceID.NotFound", "no such instance id %q", id)
+			}
+			insts = append(insts, inst)
+		}
+	}
+	for _, inst := range insts {
+		resp.StateChanges = append(resp.StateChanges, inst.terminate())
+	}
+	return &resp
+}
+
+func (inst *Instance) terminate() (d ec2.InstanceStateChange) {
+	d.PreviousState = inst.state
+	inst.state = ShuttingDown
+	d.CurrentState = inst.state
+	d.InstanceId = inst.id
+	return d
+}
+
+func (inst *Instance) ec2instance() ec2.Instance {
+	return ec2.Instance{
+		InstanceId:   inst.id,
+		InstanceType: inst.instType,
+		ImageId:      inst.imageId,
+		DNSName:      fmt.Sprintf("%s.example.com", inst.id),
+		// TODO the rest
+	}
+}
+
+func (inst *Instance) matchAttr(attr, value string) (ok bool, err error) {
+	switch attr {
+	case "architecture":
+		return value == "i386", nil
+	case "instance-id":
+		return inst.id == value, nil
+	case "group-id":
+		for _, g := range inst.reservation.groups {
+			if g.id == value {
+				return true, nil
+			}
+		}
+		return false, nil
+	case "group-name":
+		for _, g := range inst.reservation.groups {
+			if g.name == value {
+				return true, nil
+			}
+		}
+		return false, nil
+	case "image-id":
+		return value == inst.imageId, nil
+	case "instance-state-code":
+		code, err := strconv.Atoi(value)
+		if err != nil {
+			return false, err
+		}
+		return code&0xff == inst.state.Code, nil
+	case "instance-state-name":
+		return value == inst.state.Name, nil
+	}
+	return false, fmt.Errorf("unknown attribute %q", attr)
+}
+
+var (
+	Pending      = ec2.InstanceState{0, "pending"}
+	Running      = ec2.InstanceState{16, "running"}
+	ShuttingDown = ec2.InstanceState{32, "shutting-down"}
+	Terminated   = ec2.InstanceState{16, "terminated"}
+	Stopped      = ec2.InstanceState{16, "stopped"}
+)
+
+func (srv *Server) createSecurityGroup(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
+	name := req.Form.Get("GroupName")
+	if name == "" {
+		fatalf(400, "InvalidParameterValue", "empty security group name")
+	}
+	srv.mu.Lock()
+	defer srv.mu.Unlock()
+	if srv.group(ec2.SecurityGroup{Name: name}) != nil {
+		fatalf(400, "InvalidGroup.Duplicate", "group %q already exists", name)
+	}
+	g := &securityGroup{
+		name:        name,
+		description: req.Form.Get("GroupDescription"),
+		id:          fmt.Sprintf("sg-%d", srv.groupId.next()),
+		perms:       make(map[permKey]bool),
+	}
+	srv.groups[g.id] = g
+	// we define a local type for this because ec2.CreateSecurityGroupResp
+	// contains SecurityGroup, but the response to this request
+	// should not contain the security group name.
+	type CreateSecurityGroupResponse struct {
+		RequestId string `xml:"requestId"`
+		Return    bool   `xml:"return"`
+		GroupId   string `xml:"groupId"`
+	}
+	r := &CreateSecurityGroupResponse{
+		RequestId: reqId,
+		Return:    true,
+		GroupId:   g.id,
+	}
+	return r
+}
+
+func (srv *Server) notImplemented(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
+	fatalf(500, "InternalError", "not implemented")
+	panic("not reached")
+}
+
+func (srv *Server) describeInstances(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
+	srv.mu.Lock()
+	defer srv.mu.Unlock()
+	insts := make(map[*Instance]bool)
+	for name, vals := range req.Form {
+		if !strings.HasPrefix(name, "InstanceId.") {
+			continue
+		}
+		inst := srv.instances[vals[0]]
+		if inst == nil {
+			fatalf(400, "InvalidInstanceID.NotFound", "instance %q not found", vals[0])
+		}
+		insts[inst] = true
+	}
+
+	f := newFilter(req.Form)
+
+	var resp ec2.InstancesResp
+	resp.RequestId = reqId
+	for _, r := range srv.reservations {
+		var instances []ec2.Instance
+		for _, inst := range r.instances {
+			if len(insts) > 0 && !insts[inst] {
+				continue
+			}
+			ok, err := f.ok(inst)
+			if ok {
+				instances = append(instances, inst.ec2instance())
+			} else if err != nil {
+				fatalf(400, "InvalidParameterValue", "describe instances: %v", err)
+			}
+		}
+		if len(instances) > 0 {
+			var groups []ec2.SecurityGroup
+			for _, g := range r.groups {
+				groups = append(groups, g.ec2SecurityGroup())
+			}
+			resp.Reservations = append(resp.Reservations, ec2.Reservation{
+				ReservationId:  r.id,
+				OwnerId:        ownerId,
+				Instances:      instances,
+				SecurityGroups: groups,
+			})
+		}
+	}
+	return &resp
+}
+
+func (srv *Server) describeSecurityGroups(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
+	// BUG similar bug to describeInstances, but for GroupName and GroupId
+	srv.mu.Lock()
+	defer srv.mu.Unlock()
+
+	var groups []*securityGroup
+	for name, vals := range req.Form {
+		var g ec2.SecurityGroup
+		switch {
+		case strings.HasPrefix(name, "GroupName."):
+			g.Name = vals[0]
+		case strings.HasPrefix(name, "GroupId."):
+			g.Id = vals[0]
+		default:
+			continue
+		}
+		sg := srv.group(g)
+		if sg == nil {
+			fatalf(400, "InvalidGroup.NotFound", "no such group %v", g)
+		}
+		groups = append(groups, sg)
+	}
+	if len(groups) == 0 {
+		for _, g := range srv.groups {
+			groups = append(groups, g)
+		}
+	}
+
+	f := newFilter(req.Form)
+	var resp ec2.SecurityGroupsResp
+	resp.RequestId = reqId
+	for _, group := range groups {
+		ok, err := f.ok(group)
+		if ok {
+			resp.Groups = append(resp.Groups, ec2.SecurityGroupInfo{
+				OwnerId:       ownerId,
+				SecurityGroup: group.ec2SecurityGroup(),
+				Description:   group.description,
+				IPPerms:       group.ec2Perms(),
+			})
+		} else if err != nil {
+			fatalf(400, "InvalidParameterValue", "describe security groups: %v", err)
+		}
+	}
+	return &resp
+}
+
+func (srv *Server) authorizeSecurityGroupIngress(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
+	srv.mu.Lock()
+	defer srv.mu.Unlock()
+	g := srv.group(ec2.SecurityGroup{
+		Name: req.Form.Get("GroupName"),
+		Id:   req.Form.Get("GroupId"),
+	})
+	if g == nil {
+		fatalf(400, "InvalidGroup.NotFound", "group not found")
+	}
+	perms := srv.parsePerms(req)
+
+	for _, p := range perms {
+		if g.perms[p] {
+			fatalf(400, "InvalidPermission.Duplicate", "Permission has already been authorized on the specified group")
+		}
+	}
+	for _, p := range perms {
+		g.perms[p] = true
+	}
+	return &ec2.SimpleResp{
+		XMLName:   xml.Name{"", "AuthorizeSecurityGroupIngressResponse"},
+		RequestId: reqId,
+	}
+}
+
+func (srv *Server) revokeSecurityGroupIngress(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
+	srv.mu.Lock()
+	defer srv.mu.Unlock()
+	g := srv.group(ec2.SecurityGroup{
+		Name: req.Form.Get("GroupName"),
+		Id:   req.Form.Get("GroupId"),
+	})
+	if g == nil {
+		fatalf(400, "InvalidGroup.NotFound", "group not found")
+	}
+	perms := srv.parsePerms(req)
+
+	// Note EC2 does not give an error if asked to revoke an authorization
+	// that does not exist.
+	for _, p := range perms {
+		delete(g.perms, p)
+	}
+	return &ec2.SimpleResp{
+		XMLName:   xml.Name{"", "RevokeSecurityGroupIngressResponse"},
+		RequestId: reqId,
+	}
+}
+
+var secGroupPat = regexp.MustCompile(`^sg-[a-z0-9]+$`)
+var ipPat = regexp.MustCompile(`^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/[0-9]+$`)
+var ownerIdPat = regexp.MustCompile(`^[0-9]+$`)
+
+// parsePerms returns a slice of permKey values extracted
+// from the permission fields in req.
+func (srv *Server) parsePerms(req *http.Request) []permKey {
+	// perms maps an index found in the form to its associated
+	// IPPerm. For instance, the form value with key
+	// "IpPermissions.3.FromPort" will be stored in perms[3].FromPort
+	perms := make(map[int]ec2.IPPerm)
+
+	type subgroupKey struct {
+		id1, id2 int
+	}
+	// Each IPPerm can have many source security groups.  The form key
+	// for a source security group contains two indices: the index
+	// of the IPPerm and the sub-index of the security group. The
+	// sourceGroups map maps from a subgroupKey containing these
+	// two indices to the associated security group. For instance,
+	// the form value with key "IPPermissions.3.Groups.2.GroupName"
+	// will be stored in sourceGroups[subgroupKey{3, 2}].Name.
+	sourceGroups := make(map[subgroupKey]ec2.UserSecurityGroup)
+
+	// For each value in the form we store its associated information in the
+	// above maps. The maps are necessary because the form keys may
+	// arrive in any order, and the indices are not
+	// necessarily sequential or even small.
+	for name, vals := range req.Form {
+		val := vals[0]
+		var id1 int
+		var rest string
+		if x, _ := fmt.Sscanf(name, "IpPermissions.%d.%s", &id1, &rest); x != 2 {
+			continue
+		}
+		ec2p := perms[id1]
+		switch {
+		case rest == "FromPort":
+			ec2p.FromPort = atoi(val)
+		case rest == "ToPort":
+			ec2p.ToPort = atoi(val)
+		case rest == "IpProtocol":
+			switch val {
+			case "tcp", "udp", "icmp":
+				ec2p.Protocol = val
+			default:
+				// check it's a well formed number
+				atoi(val)
+				ec2p.Protocol = val
+			}
+		case strings.HasPrefix(rest, "Groups."):
+			k := subgroupKey{id1: id1}
+			if x, _ := fmt.Sscanf(rest[len("Groups."):], "%d.%s", &k.id2, &rest); x != 2 {
+				continue
+			}
+			g := sourceGroups[k]
+			switch rest {
+			case "UserId":
+				// BUG if the user id is blank, this does not conform to the
+				// way that EC2 handles it - a specified but blank owner id
+				// can cause RevokeSecurityGroupIngress to fail with
+				// "group not found" even if the security group id has been
+				// correctly specified.
+				// By failing here, we ensure that we fail early in this case.
+				if !ownerIdPat.MatchString(val) {
+					fatalf(400, "InvalidUserID.Malformed", "Invalid user ID: %q", val)
+				}
+				g.OwnerId = val
+			case "GroupName":
+				g.Name = val
+			case "GroupId":
+				if !secGroupPat.MatchString(val) {
+					fatalf(400, "InvalidGroupId.Malformed", "Invalid group ID: %q", val)
+				}
+				g.Id = val
+			default:
+				fatalf(400, "UnknownParameter", "unknown parameter %q", name)
+			}
+			sourceGroups[k] = g
+		case strings.HasPrefix(rest, "IpRanges."):
+			var id2 int
+			if x, _ := fmt.Sscanf(rest[len("IpRanges."):], "%d.%s", &id2, &rest); x != 2 {
+				continue
+			}
+			switch rest {
+			case "CidrIp":
+				if !ipPat.MatchString(val) {
+					fatalf(400, "InvalidPermission.Malformed", "Invalid IP range: %q", val)
+				}
+				ec2p.SourceIPs = append(ec2p.SourceIPs, val)
+			default:
+				fatalf(400, "UnknownParameter", "unknown parameter %q", name)
+			}
+		default:
+			fatalf(400, "UnknownParameter", "unknown parameter %q", name)
+		}
+		perms[id1] = ec2p
+	}
+	// Associate each set of source groups with its IPPerm.
+	for k, g := range sourceGroups {
+		p := perms[k.id1]
+		p.SourceGroups = append(p.SourceGroups, g)
+		perms[k.id1] = p
+	}
+
+	// Now that we have built up the IPPerms we need, we check for
+	// parameter errors and build up a permKey for each permission,
+	// looking up security groups from srv as we do so.
+	var result []permKey
+	for _, p := range perms {
+		if p.FromPort > p.ToPort {
+			fatalf(400, "InvalidParameterValue", "invalid port range")
+		}
+		k := permKey{
+			protocol: p.Protocol,
+			fromPort: p.FromPort,
+			toPort:   p.ToPort,
+		}
+		for _, g := range p.SourceGroups {
+			if g.OwnerId != "" && g.OwnerId != ownerId {
+				fatalf(400, "InvalidGroup.NotFound", "group %q not found", g.Name)
+			}
+			var ec2g ec2.SecurityGroup
+			switch {
+			case g.Id != "":
+				ec2g.Id = g.Id
+			case g.Name != "":
+				ec2g.Name = g.Name
+			}
+			k.group = srv.group(ec2g)
+			if k.group == nil {
+				fatalf(400, "InvalidGroup.NotFound", "group %v not found", g)
+			}
+			result = append(result, k)
+		}
+		k.group = nil
+		for _, ip := range p.SourceIPs {
+			k.ipAddr = ip
+			result = append(result, k)
+		}
+	}
+	return result
+}
+
+func (srv *Server) deleteSecurityGroup(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
+	srv.mu.Lock()
+	defer srv.mu.Unlock()
+	g := srv.group(ec2.SecurityGroup{
+		Name: req.Form.Get("GroupName"),
+		Id:   req.Form.Get("GroupId"),
+	})
+	if g == nil {
+		fatalf(400, "InvalidGroup.NotFound", "group not found")
+	}
+	for _, r := range srv.reservations {
+		for _, h := range r.groups {
+			if h == g && r.hasRunningMachine() {
+				fatalf(500, "InvalidGroup.InUse", "group is currently in use by a running instance")
+			}
+		}
+	}
+	for _, sg := range srv.groups {
+		// If a group refers to itself, it's ok to delete it.
+		if sg == g {
+			continue
+		}
+		for k := range sg.perms {
+			if k.group == g {
+				fatalf(500, "InvalidGroup.InUse", "group is currently in use by group %q", sg.id)
+			}
+		}
+	}
+
+	delete(srv.groups, g.id)
+	return &ec2.SimpleResp{
+		XMLName:   xml.Name{"", "DeleteSecurityGroupResponse"},
+		RequestId: reqId,
+	}
+}
+
+func (r *reservation) hasRunningMachine() bool {
+	for _, inst := range r.instances {
+		if inst.state.Code != ShuttingDown.Code && inst.state.Code != Terminated.Code {
+			return true
+		}
+	}
+	return false
+}
+
+type counter int
+
+func (c *counter) next() (i int) {
+	i = int(*c)
+	(*c)++
+	return
+}
+
+// atoi is like strconv.Atoi but is fatal if the
+// string is not well formed.
+func atoi(s string) int {
+	i, err := strconv.Atoi(s)
+	if err != nil {
+		fatalf(400, "InvalidParameterValue", "bad number: %v", err)
+	}
+	return i
+}
+
+func fatalf(statusCode int, code string, f string, a ...interface{}) {
+	panic(&ec2.Error{
+		StatusCode: statusCode,
+		Code:       code,
+		Message:    fmt.Sprintf(f, a...),
+	})
+}

+ 22 - 0
Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/export_test.go

@@ -0,0 +1,22 @@
+package ec2
+
+import (
+	"github.com/coreos/flannel/Godeps/_workspace/src/github.com/mitchellh/goamz/aws"
+	"time"
+)
+
+func Sign(auth aws.Auth, method, path string, params map[string]string, host string) {
+	sign(auth, method, path, params, host)
+}
+
+func fixedTime() time.Time {
+	return time.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC)
+}
+
+func FakeTime(fakeIt bool) {
+	if fakeIt {
+		timeNow = fixedTime
+	} else {
+		timeNow = time.Now
+	}
+}

+ 1263 - 0
Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/responses_test.go

@@ -0,0 +1,1263 @@
+package ec2_test
+
+var ErrorDump = `
+<?xml version="1.0" encoding="UTF-8"?>
+<Response><Errors><Error><Code>UnsupportedOperation</Code>
+<Message>AMIs with an instance-store root device are not supported for the instance type 't1.micro'.</Message>
+</Error></Errors><RequestID>0503f4e9-bbd6-483c-b54f-c4ae9f3b30f4</RequestID></Response>
+`
+
+// http://goo.gl/Mcm3b
+var RunInstancesExample = `
+<RunInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2011-12-15/">
+  <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+  <reservationId>r-47a5402e</reservationId>
+  <ownerId>999988887777</ownerId>
+  <groupSet>
+      <item>
+          <groupId>sg-67ad940e</groupId>
+          <groupName>default</groupName>
+      </item>
+  </groupSet>
+  <instancesSet>
+    <item>
+      <instanceId>i-2ba64342</instanceId>
+      <imageId>ami-60a54009</imageId>
+      <instanceState>
+        <code>0</code>
+        <name>pending</name>
+      </instanceState>
+      <privateDnsName></privateDnsName>
+      <dnsName></dnsName>
+      <keyName>example-key-name</keyName>
+      <amiLaunchIndex>0</amiLaunchIndex>
+      <instanceType>m1.small</instanceType>
+      <launchTime>2007-08-07T11:51:50.000Z</launchTime>
+      <placement>
+        <availabilityZone>us-east-1b</availabilityZone>
+      </placement>
+      <monitoring>
+        <state>enabled</state>
+      </monitoring>
+      <virtualizationType>paravirtual</virtualizationType>
+      <clientToken/>
+      <tagSet/>
+      <hypervisor>xen</hypervisor>
+    </item>
+    <item>
+      <instanceId>i-2bc64242</instanceId>
+      <imageId>ami-60a54009</imageId>
+      <instanceState>
+        <code>0</code>
+        <name>pending</name>
+      </instanceState>
+      <privateDnsName></privateDnsName>
+      <dnsName></dnsName>
+      <keyName>example-key-name</keyName>
+      <amiLaunchIndex>1</amiLaunchIndex>
+      <instanceType>m1.small</instanceType>
+      <launchTime>2007-08-07T11:51:50.000Z</launchTime>
+      <placement>
+         <availabilityZone>us-east-1b</availabilityZone>
+      </placement>
+      <monitoring>
+        <state>enabled</state>
+      </monitoring>
+      <virtualizationType>paravirtual</virtualizationType>
+      <clientToken/>
+      <tagSet/>
+      <hypervisor>xen</hypervisor>
+    </item>
+    <item>
+      <instanceId>i-2be64332</instanceId>
+      <imageId>ami-60a54009</imageId>
+      <instanceState>
+        <code>0</code>
+        <name>pending</name>
+      </instanceState>
+      <privateDnsName></privateDnsName>
+      <dnsName></dnsName>
+      <keyName>example-key-name</keyName>
+      <amiLaunchIndex>2</amiLaunchIndex>
+      <instanceType>m1.small</instanceType>
+      <launchTime>2007-08-07T11:51:50.000Z</launchTime>
+      <placement>
+         <availabilityZone>us-east-1b</availabilityZone>
+      </placement>
+      <monitoring>
+        <state>enabled</state>
+      </monitoring>
+      <virtualizationType>paravirtual</virtualizationType>
+      <clientToken/>
+      <tagSet/>
+      <hypervisor>xen</hypervisor>
+    </item>
+  </instancesSet>
+</RunInstancesResponse>
+`
+
+// http://goo.gl/GRZgCD
+var RequestSpotInstancesExample = `
+<RequestSpotInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2014-02-01/">
+  <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+  <spotInstanceRequestSet>
+    <item>
+      <spotInstanceRequestId>sir-1a2b3c4d</spotInstanceRequestId>
+      <spotPrice>0.5</spotPrice>
+      <type>one-time</type>
+      <state>open</state>
+      <status>
+        <code>pending-evaluation</code>
+        <updateTime>2008-05-07T12:51:50.000Z</updateTime>
+        <message>Your Spot request has been submitted for review, and is pending evaluation.</message>
+      </status>
+      <availabilityZoneGroup>MyAzGroup</availabilityZoneGroup>
+      <launchSpecification>
+        <imageId>ami-1a2b3c4d</imageId>
+        <keyName>gsg-keypair</keyName>
+        <groupSet>
+          <item>
+            <groupId>sg-1a2b3c4d</groupId>
+            <groupName>websrv</groupName>
+          </item>
+        </groupSet>
+        <instanceType>m1.small</instanceType>
+        <blockDeviceMapping/>
+        <monitoring>
+          <enabled>false</enabled>
+        </monitoring>
+        <ebsOptimized>false</ebsOptimized>
+      </launchSpecification>
+      <createTime>YYYY-MM-DDTHH:MM:SS.000Z</createTime>
+      <productDescription>Linux/UNIX</productDescription>
+    </item>
+ </spotInstanceRequestSet>
+</RequestSpotInstancesResponse>
+`
+
+// http://goo.gl/KsKJJk
+var DescribeSpotRequestsExample = `
+<DescribeSpotInstanceRequestsResponse xmlns="http://ec2.amazonaws.com/doc/2014-02-01/">
+  <requestId>b1719f2a-5334-4479-b2f1-26926EXAMPLE</requestId>
+  <spotInstanceRequestSet>
+    <item>
+      <spotInstanceRequestId>sir-1a2b3c4d</spotInstanceRequestId>
+      <spotPrice>0.5</spotPrice>
+      <type>one-time</type>
+      <state>active</state>
+      <status>
+        <code>fulfilled</code>
+        <updateTime>2008-05-07T12:51:50.000Z</updateTime>
+        <message>Your Spot request is fulfilled.</message>
+      </status>
+      <launchSpecification>
+        <imageId>ami-1a2b3c4d</imageId>
+        <keyName>gsg-keypair</keyName>
+        <groupSet>
+          <item>
+            <groupId>sg-1a2b3c4d</groupId>
+            <groupName>websrv</groupName>
+          </item>
+        </groupSet>
+        <instanceType>m1.small</instanceType>
+        <monitoring>
+          <enabled>false</enabled>
+        </monitoring>
+        <ebsOptimized>false</ebsOptimized>
+      </launchSpecification>
+      <instanceId>i-1a2b3c4d</instanceId>
+      <createTime>YYYY-MM-DDTHH:MM:SS.000Z</createTime>
+      <productDescription>Linux/UNIX</productDescription>
+      <launchedAvailabilityZone>us-east-1a</launchedAvailabilityZone>
+    </item>
+  </spotInstanceRequestSet>
+</DescribeSpotInstanceRequestsResponse>
+`
+
+// http://goo.gl/DcfFgJ
+var CancelSpotRequestsExample = `
+<CancelSpotInstanceRequestsResponse xmlns="http://ec2.amazonaws.com/doc/2014-02-01/">
+  <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+  <spotInstanceRequestSet>
+    <item>
+      <spotInstanceRequestId>sir-1a2b3c4d</spotInstanceRequestId>
+      <state>cancelled</state>
+    </item>
+  </spotInstanceRequestSet>
+</CancelSpotInstanceRequestsResponse>
+`
+
+// http://goo.gl/3BKHj
+var TerminateInstancesExample = `
+<TerminateInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2011-12-15/">
+  <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+  <instancesSet>
+    <item>
+      <instanceId>i-3ea74257</instanceId>
+      <currentState>
+        <code>32</code>
+        <name>shutting-down</name>
+      </currentState>
+      <previousState>
+        <code>16</code>
+        <name>running</name>
+      </previousState>
+    </item>
+  </instancesSet>
+</TerminateInstancesResponse>
+`
+
+// http://goo.gl/mLbmw
+var DescribeInstancesExample1 = `
+<DescribeInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2011-12-15/">
+  <requestId>98e3c9a4-848c-4d6d-8e8a-b1bdEXAMPLE</requestId>
+  <reservationSet>
+    <item>
+      <reservationId>r-b27e30d9</reservationId>
+      <ownerId>999988887777</ownerId>
+      <groupSet>
+        <item>
+          <groupId>sg-67ad940e</groupId>
+          <groupName>default</groupName>
+        </item>
+      </groupSet>
+      <instancesSet>
+        <item>
+          <instanceId>i-c5cd56af</instanceId>
+          <imageId>ami-1a2b3c4d</imageId>
+          <instanceState>
+            <code>16</code>
+            <name>running</name>
+          </instanceState>
+          <privateDnsName>domU-12-31-39-10-56-34.compute-1.internal</privateDnsName>
+          <dnsName>ec2-174-129-165-232.compute-1.amazonaws.com</dnsName>
+          <reason/>
+          <keyName>GSG_Keypair</keyName>
+          <amiLaunchIndex>0</amiLaunchIndex>
+          <productCodes/>
+          <instanceType>m1.small</instanceType>
+          <launchTime>2010-08-17T01:15:18.000Z</launchTime>
+          <placement>
+            <availabilityZone>us-east-1b</availabilityZone>
+            <groupName/>
+          </placement>
+          <kernelId>aki-94c527fd</kernelId>
+          <ramdiskId>ari-96c527ff</ramdiskId>
+          <monitoring>
+            <state>disabled</state>
+          </monitoring>
+          <privateIpAddress>10.198.85.190</privateIpAddress>
+          <ipAddress>174.129.165.232</ipAddress>
+          <architecture>i386</architecture>
+          <rootDeviceType>ebs</rootDeviceType>
+          <rootDeviceName>/dev/sda1</rootDeviceName>
+          <blockDeviceMapping>
+            <item>
+              <deviceName>/dev/sda1</deviceName>
+              <ebs>
+                <volumeId>vol-a082c1c9</volumeId>
+                <status>attached</status>
+                <attachTime>2010-08-17T01:15:21.000Z</attachTime>
+                <deleteOnTermination>false</deleteOnTermination>
+              </ebs>
+            </item>
+          </blockDeviceMapping>
+          <instanceLifecycle>spot</instanceLifecycle>
+          <spotInstanceRequestId>sir-7a688402</spotInstanceRequestId>
+          <virtualizationType>paravirtual</virtualizationType>
+          <clientToken/>
+          <tagSet/>
+          <hypervisor>xen</hypervisor>
+       </item>
+      </instancesSet>
+      <requesterId>854251627541</requesterId>
+    </item>
+    <item>
+      <reservationId>r-b67e30dd</reservationId>
+      <ownerId>999988887777</ownerId>
+      <groupSet>
+        <item>
+          <groupId>sg-67ad940e</groupId>
+          <groupName>default</groupName>
+        </item>
+      </groupSet>
+      <instancesSet>
+        <item>
+          <instanceId>i-d9cd56b3</instanceId>
+          <imageId>ami-1a2b3c4d</imageId>
+          <instanceState>
+            <code>16</code>
+            <name>running</name>
+          </instanceState>
+          <privateDnsName>domU-12-31-39-10-54-E5.compute-1.internal</privateDnsName>
+          <dnsName>ec2-184-73-58-78.compute-1.amazonaws.com</dnsName>
+          <reason/>
+          <keyName>GSG_Keypair</keyName>
+          <amiLaunchIndex>0</amiLaunchIndex>
+          <productCodes/>
+          <instanceType>m1.large</instanceType>
+          <launchTime>2010-08-17T01:15:19.000Z</launchTime>
+          <placement>
+            <availabilityZone>us-east-1b</availabilityZone>
+            <groupName/>
+          </placement>
+          <kernelId>aki-94c527fd</kernelId>
+          <ramdiskId>ari-96c527ff</ramdiskId>
+          <monitoring>
+            <state>disabled</state>
+          </monitoring>
+          <privateIpAddress>10.198.87.19</privateIpAddress>
+          <ipAddress>184.73.58.78</ipAddress>
+          <architecture>i386</architecture>
+          <rootDeviceType>ebs</rootDeviceType>
+          <rootDeviceName>/dev/sda1</rootDeviceName>
+          <blockDeviceMapping>
+            <item>
+              <deviceName>/dev/sda1</deviceName>
+              <ebs>
+                <volumeId>vol-a282c1cb</volumeId>
+                <status>attached</status>
+                <attachTime>2010-08-17T01:15:23.000Z</attachTime>
+                <deleteOnTermination>false</deleteOnTermination>
+              </ebs>
+            </item>
+          </blockDeviceMapping>
+          <instanceLifecycle>spot</instanceLifecycle>
+          <spotInstanceRequestId>sir-55a3aa02</spotInstanceRequestId>
+          <virtualizationType>paravirtual</virtualizationType>
+          <clientToken/>
+          <tagSet/>
+          <hypervisor>xen</hypervisor>
+       </item>
+      </instancesSet>
+      <requesterId>854251627541</requesterId>
+    </item>
+  </reservationSet>
+</DescribeInstancesResponse>
+`
+
+// http://goo.gl/mLbmw
+var DescribeInstancesExample2 = `
+<DescribeInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2011-12-15/">
+  <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+  <reservationSet>
+    <item>
+      <reservationId>r-bc7e30d7</reservationId>
+      <ownerId>999988887777</ownerId>
+      <groupSet>
+        <item>
+          <groupId>sg-67ad940e</groupId>
+          <groupName>default</groupName>
+        </item>
+      </groupSet>
+      <instancesSet>
+        <item>
+          <instanceId>i-c7cd56ad</instanceId>
+          <imageId>ami-b232d0db</imageId>
+          <instanceState>
+            <code>16</code>
+            <name>running</name>
+          </instanceState>
+          <privateDnsName>domU-12-31-39-01-76-06.compute-1.internal</privateDnsName>
+          <dnsName>ec2-72-44-52-124.compute-1.amazonaws.com</dnsName>
+          <keyName>GSG_Keypair</keyName>
+          <amiLaunchIndex>0</amiLaunchIndex>
+          <productCodes/>
+          <instanceType>m1.small</instanceType>
+          <launchTime>2010-08-17T01:15:16.000Z</launchTime>
+          <placement>
+              <availabilityZone>us-east-1b</availabilityZone>
+          </placement>
+          <kernelId>aki-94c527fd</kernelId>
+          <ramdiskId>ari-96c527ff</ramdiskId>
+          <monitoring>
+              <state>disabled</state>
+          </monitoring>
+          <privateIpAddress>10.255.121.240</privateIpAddress>
+          <ipAddress>72.44.52.124</ipAddress>
+          <architecture>i386</architecture>
+          <rootDeviceType>ebs</rootDeviceType>
+          <rootDeviceName>/dev/sda1</rootDeviceName>
+          <blockDeviceMapping>
+              <item>
+                 <deviceName>/dev/sda1</deviceName>
+                 <ebs>
+                    <volumeId>vol-a482c1cd</volumeId>
+                    <status>attached</status>
+                    <attachTime>2010-08-17T01:15:26.000Z</attachTime>
+                    <deleteOnTermination>true</deleteOnTermination>
+                </ebs>
+             </item>
+          </blockDeviceMapping>
+          <virtualizationType>paravirtual</virtualizationType>
+          <clientToken/>
+          <tagSet>
+              <item>
+                    <key>webserver</key>
+                    <value></value>
+             </item>
+              <item>
+                    <key>stack</key>
+                    <value>Production</value>
+             </item>
+          </tagSet>
+          <hypervisor>xen</hypervisor>
+        </item>
+      </instancesSet>
+    </item>
+  </reservationSet>
+</DescribeInstancesResponse>
+`
+
+// http://goo.gl/cxU41
+var CreateImageExample = `
+<CreateImageResponse xmlns="http://ec2.amazonaws.com/doc/2013-02-01/">
+   <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+   <imageId>ami-4fa54026</imageId>
+</CreateImageResponse>
+`
+
+// http://goo.gl/V0U25
+var DescribeImagesExample = `
+<DescribeImagesResponse xmlns="http://ec2.amazonaws.com/doc/2012-08-15/">
+         <requestId>4a4a27a2-2e7c-475d-b35b-ca822EXAMPLE</requestId>
+    <imagesSet>
+        <item>
+            <imageId>ami-a2469acf</imageId>
+            <imageLocation>aws-marketplace/example-marketplace-amzn-ami.1</imageLocation>
+            <imageState>available</imageState>
+            <imageOwnerId>123456789999</imageOwnerId>
+            <isPublic>true</isPublic>
+            <productCodes>
+                <item>
+                    <productCode>a1b2c3d4e5f6g7h8i9j10k11</productCode>
+                    <type>marketplace</type>
+                </item>
+            </productCodes>
+            <architecture>i386</architecture>
+            <imageType>machine</imageType>
+            <kernelId>aki-805ea7e9</kernelId>
+            <imageOwnerAlias>aws-marketplace</imageOwnerAlias>
+            <name>example-marketplace-amzn-ami.1</name>
+            <description>Amazon Linux AMI i386 EBS</description>
+            <rootDeviceType>ebs</rootDeviceType>
+            <rootDeviceName>/dev/sda1</rootDeviceName>
+            <blockDeviceMapping>
+                <item>
+                    <deviceName>/dev/sda1</deviceName>
+                    <ebs>
+                        <snapshotId>snap-787e9403</snapshotId>
+                        <volumeSize>8</volumeSize>
+                        <deleteOnTermination>true</deleteOnTermination>
+                    </ebs>
+                </item>
+            </blockDeviceMapping>
+            <virtualizationType>paravirtual</virtualizationType>
+            <hypervisor>xen</hypervisor>
+        </item>
+    </imagesSet>
+</DescribeImagesResponse>
+`
+
+// http://goo.gl/bHO3z
+var ImageAttributeExample = `
+<DescribeImageAttributeResponse xmlns="http://ec2.amazonaws.com/doc/2013-07-15/">
+   <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+   <imageId>ami-61a54008</imageId>
+   <launchPermission>
+      <item>
+         <group>all</group>
+      </item>
+      <item>
+         <userId>495219933132</userId>
+      </item>
+   </launchPermission>
+</DescribeImageAttributeResponse>
+`
+
+// http://goo.gl/ttcda
+var CreateSnapshotExample = `
+<CreateSnapshotResponse xmlns="http://ec2.amazonaws.com/doc/2012-10-01/">
+  <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+  <snapshotId>snap-78a54011</snapshotId>
+  <volumeId>vol-4d826724</volumeId>
+  <status>pending</status>
+  <startTime>2008-05-07T12:51:50.000Z</startTime>
+  <progress>60%</progress>
+  <ownerId>111122223333</ownerId>
+  <volumeSize>10</volumeSize>
+  <description>Daily Backup</description>
+</CreateSnapshotResponse>
+`
+
+// http://goo.gl/vwU1y
+var DeleteSnapshotExample = `
+<DeleteSnapshotResponse xmlns="http://ec2.amazonaws.com/doc/2012-10-01/">
+  <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+  <return>true</return>
+</DeleteSnapshotResponse>
+`
+
+// http://goo.gl/nkovs
+var DescribeSnapshotsExample = `
+<DescribeSnapshotsResponse xmlns="http://ec2.amazonaws.com/doc/2012-10-01/">
+   <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+   <snapshotSet>
+      <item>
+         <snapshotId>snap-1a2b3c4d</snapshotId>
+         <volumeId>vol-8875daef</volumeId>
+         <status>pending</status>
+         <startTime>2010-07-29T04:12:01.000Z</startTime>
+         <progress>30%</progress>
+         <ownerId>111122223333</ownerId>
+         <volumeSize>15</volumeSize>
+         <description>Daily Backup</description>
+         <tagSet>
+            <item>
+               <key>Purpose</key>
+               <value>demo_db_14_backup</value>
+            </item>
+         </tagSet>
+      </item>
+   </snapshotSet>
+</DescribeSnapshotsResponse>
+`
+
+// http://goo.gl/YUjO4G
+var ModifyImageAttributeExample = `
+<ModifyImageAttributeResponse xmlns="http://ec2.amazonaws.com/doc/2013-06-15/">
+  <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+  <return>true</return>
+</ModifyImageAttributeResponse>
+`
+
+// http://goo.gl/hQwPCK
+var CopyImageExample = `
+<CopyImageResponse xmlns="http://ec2.amazonaws.com/doc/2013-06-15/">
+   <requestId>60bc441d-fa2c-494d-b155-5d6a3EXAMPLE</requestId>
+   <imageId>ami-4d3c2b1a</imageId>
+</CopyImageResponse>
+`
+
+var CreateKeyPairExample = `
+<CreateKeyPairResponse xmlns="http://ec2.amazonaws.com/doc/2013-02-01/">
+  <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+  <keyName>foo</keyName>
+  <keyFingerprint>
+     00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
+  </keyFingerprint>
+  <keyMaterial>---- BEGIN RSA PRIVATE KEY ----
+MIICiTCCAfICCQD6m7oRw0uXOjANBgkqhkiG9w0BAQUFADCBiDELMAkGA1UEBhMC
+VVMxCzAJBgNVBAgTAldBMRAwDgYDVQQHEwdTZWF0dGxlMQ8wDQYDVQQKEwZBbWF6
+b24xFDASBgNVBAsTC0lBTSBDb25zb2xlMRIwEAYDVQQDEwlUZXN0Q2lsYWMxHzAd
+BgkqhkiG9w0BCQEWEG5vb25lQGFtYXpvbi5jb20wHhcNMTEwNDI1MjA0NTIxWhcN
+MTIwNDI0MjA0NTIxWjCBiDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAldBMRAwDgYD
+VQQHEwdTZWF0dGxlMQ8wDQYDVQQKEwZBbWF6b24xFDASBgNVBAsTC0lBTSBDb25z
+b2xlMRIwEAYDVQQDEwlUZXN0Q2lsYWMxHzAdBgkqhkiG9w0BCQEWEG5vb25lQGFt
+YXpvbi5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMaK0dn+a4GmWIWJ
+21uUSfwfEvySWtC2XADZ4nB+BLYgVIk60CpiwsZ3G93vUEIO3IyNoH/f0wYK8m9T
+rDHudUZg3qX4waLG5M43q7Wgc/MbQITxOUSQv7c7ugFFDzQGBzZswY6786m86gpE
+Ibb3OhjZnzcvQAaRHhdlQWIMm2nrAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAtCu4
+nUhVVxYUntneD9+h8Mg9q6q+auNKyExzyLwaxlAoo7TJHidbtS4J5iNmZgXL0Fkb
+FFBjvSfpJIlJ00zbhNYS5f6GuoEDmFJl0ZxBHjJnyp378OD8uTs7fLvjx79LjSTb
+NYiytVbZPQUQ5Yaxu2jXnimvw3rrszlaEXAMPLE=
+-----END RSA PRIVATE KEY-----
+</keyMaterial>
+</CreateKeyPairResponse>
+`
+
+var DeleteKeyPairExample = `
+<DeleteKeyPairResponse xmlns="http://ec2.amazonaws.com/doc/2013-02-01/">
+  <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+  <return>true</return>
+</DeleteKeyPairResponse>
+`
+
+// http://goo.gl/Eo7Yl
+var CreateSecurityGroupExample = `
+<CreateSecurityGroupResponse xmlns="http://ec2.amazonaws.com/doc/2011-12-15/">
+   <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+   <return>true</return>
+   <groupId>sg-67ad940e</groupId>
+</CreateSecurityGroupResponse>
+`
+
+// http://goo.gl/k12Uy
+var DescribeSecurityGroupsExample = `
+<DescribeSecurityGroupsResponse xmlns="http://ec2.amazonaws.com/doc/2011-12-15/">
+  <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+  <securityGroupInfo>
+    <item>
+      <ownerId>999988887777</ownerId>
+      <groupName>WebServers</groupName>
+      <groupId>sg-67ad940e</groupId>
+      <groupDescription>Web Servers</groupDescription>
+      <ipPermissions>
+        <item>
+           <ipProtocol>tcp</ipProtocol>
+           <fromPort>80</fromPort>
+           <toPort>80</toPort>
+           <groups/>
+           <ipRanges>
+             <item>
+               <cidrIp>0.0.0.0/0</cidrIp>
+             </item>
+           </ipRanges>
+        </item>
+      </ipPermissions>
+      <ipPermissionsEgress>
+        <item>
+          <ipProtocol>tcp</ipProtocol>
+          <fromPort>80</fromPort>
+          <toPort>80</toPort>
+          <groups/>
+          <ipRanges>
+            <item>
+              <cidrIp>0.0.0.0/0</cidrIp>
+            </item>
+          </ipRanges>
+        </item>
+      </ipPermissionsEgress>
+    </item>
+    <item>
+      <ownerId>999988887777</ownerId>
+      <groupName>RangedPortsBySource</groupName>
+      <groupId>sg-76abc467</groupId>
+      <groupDescription>Group A</groupDescription>
+      <ipPermissions>
+        <item>
+           <ipProtocol>tcp</ipProtocol>
+           <fromPort>6000</fromPort>
+           <toPort>7000</toPort>
+           <groups/>
+           <ipRanges/>
+        </item>
+      </ipPermissions>
+    </item>
+  </securityGroupInfo>
+</DescribeSecurityGroupsResponse>
+`
+
+// A dump which includes groups within ip permissions.
+var DescribeSecurityGroupsDump = `
+<?xml version="1.0" encoding="UTF-8"?>
+<DescribeSecurityGroupsResponse xmlns="http://ec2.amazonaws.com/doc/2011-12-15/">
+    <requestId>87b92b57-cc6e-48b2-943f-f6f0e5c9f46c</requestId>
+    <securityGroupInfo>
+        <item>
+            <ownerId>12345</ownerId>
+            <groupName>default</groupName>
+            <groupDescription>default group</groupDescription>
+            <ipPermissions>
+                <item>
+                    <ipProtocol>icmp</ipProtocol>
+                    <fromPort>-1</fromPort>
+                    <toPort>-1</toPort>
+                    <groups>
+                        <item>
+                            <userId>12345</userId>
+                            <groupName>default</groupName>
+                            <groupId>sg-67ad940e</groupId>
+                        </item>
+                    </groups>
+                    <ipRanges/>
+                </item>
+                <item>
+                    <ipProtocol>tcp</ipProtocol>
+                    <fromPort>0</fromPort>
+                    <toPort>65535</toPort>
+                    <groups>
+                        <item>
+                            <userId>12345</userId>
+                            <groupName>other</groupName>
+                            <groupId>sg-76abc467</groupId>
+                        </item>
+                    </groups>
+                    <ipRanges/>
+                </item>
+            </ipPermissions>
+        </item>
+    </securityGroupInfo>
+</DescribeSecurityGroupsResponse>
+`
+
+// http://goo.gl/QJJDO
+var DeleteSecurityGroupExample = `
+<DeleteSecurityGroupResponse xmlns="http://ec2.amazonaws.com/doc/2011-12-15/">
+   <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+   <return>true</return>
+</DeleteSecurityGroupResponse>
+`
+
+// http://goo.gl/u2sDJ
+var AuthorizeSecurityGroupIngressExample = `
+<AuthorizeSecurityGroupIngressResponse xmlns="http://ec2.amazonaws.com/doc/2011-12-15/">
+  <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+  <return>true</return>
+</AuthorizeSecurityGroupIngressResponse>
+`
+
+// http://goo.gl/u2sDJ
+var AuthorizeSecurityGroupEgressExample = `
+<AuthorizeSecurityGroupEgressResponse xmlns="http://ec2.amazonaws.com/doc/2014-06-15/">
+   <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+   <return>true</return>
+</AuthorizeSecurityGroupEgressResponse>
+`
+
+// http://goo.gl/Mz7xr
+var RevokeSecurityGroupIngressExample = `
+<RevokeSecurityGroupIngressResponse xmlns="http://ec2.amazonaws.com/doc/2011-12-15/">
+  <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+  <return>true</return>
+</RevokeSecurityGroupIngressResponse>
+`
+
+// http://goo.gl/Vmkqc
+var CreateTagsExample = `
+<CreateTagsResponse xmlns="http://ec2.amazonaws.com/doc/2011-12-15/">
+   <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+   <return>true</return>
+</CreateTagsResponse>
+`
+
+// http://goo.gl/awKeF
+var StartInstancesExample = `
+<StartInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2011-12-15/">
+  <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+  <instancesSet>
+    <item>
+      <instanceId>i-10a64379</instanceId>
+      <currentState>
+          <code>0</code>
+          <name>pending</name>
+      </currentState>
+      <previousState>
+          <code>80</code>
+          <name>stopped</name>
+      </previousState>
+    </item>
+  </instancesSet>
+</StartInstancesResponse>
+`
+
+// http://goo.gl/436dJ
+var StopInstancesExample = `
+<StopInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2011-12-15/">
+  <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+  <instancesSet>
+    <item>
+      <instanceId>i-10a64379</instanceId>
+      <currentState>
+          <code>64</code>
+          <name>stopping</name>
+      </currentState>
+      <previousState>
+          <code>16</code>
+          <name>running</name>
+      </previousState>
+    </item>
+  </instancesSet>
+</StopInstancesResponse>
+`
+
+// http://goo.gl/baoUf
+var RebootInstancesExample = `
+<RebootInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2011-12-15/">
+  <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+  <return>true</return>
+</RebootInstancesResponse>
+`
+
+// http://goo.gl/9rprDN
+var AllocateAddressExample = `
+<AllocateAddressResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
+   <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+   <publicIp>198.51.100.1</publicIp>
+   <domain>vpc</domain>
+   <allocationId>eipalloc-5723d13e</allocationId>
+</AllocateAddressResponse>
+`
+
+// http://goo.gl/DFySJY
+var DescribeInstanceStatusExample = `
+<DescribeInstanceStatusResponse xmlns="http://ec2.amazonaws.com/doc/2014-06-15/">
+    <requestId>3be1508e-c444-4fef-89cc-0b1223c4f02fEXAMPLE</requestId>
+    <instanceStatusSet>
+        <item>
+            <instanceId>i-1a2b3c4d</instanceId>
+            <availabilityZone>us-east-1d</availabilityZone>
+            <instanceState>
+                <code>16</code>
+                <name>running</name>
+            </instanceState>
+            <systemStatus>
+                <status>impaired</status>
+                <details>
+                    <item>
+                        <name>reachability</name>
+                        <status>failed</status>
+                        <impairedSince>YYYY-MM-DDTHH:MM:SS.000Z</impairedSince>
+                    </item>
+                </details>
+            </systemStatus>
+            <instanceStatus>
+                <status>impaired</status>
+                <details>
+                    <item>
+                        <name>reachability</name>
+                        <status>failed</status>
+                        <impairedSince>YYYY-MM-DDTHH:MM:SS.000Z</impairedSince>
+                    </item>
+                </details>
+            </instanceStatus>
+            <eventsSet>
+              <item>
+                <code>instance-retirement</code>
+                <description>The instance is running on degraded hardware</description>
+                <notBefore>YYYY-MM-DDTHH:MM:SS+0000</notBefore>
+                <notAfter>YYYY-MM-DDTHH:MM:SS+0000</notAfter>
+              </item>
+            </eventsSet>
+        </item>
+        <item>
+            <instanceId>i-2a2b3c4d</instanceId>
+            <availabilityZone>us-east-1d</availabilityZone>
+            <instanceState>
+                <code>16</code>
+                <name>running</name>
+            </instanceState>
+            <systemStatus>
+                <status>ok</status>
+                <details>
+                    <item>
+                        <name>reachability</name>
+                        <status>passed</status>
+                    </item>
+                </details>
+            </systemStatus>
+            <instanceStatus>
+                <status>ok</status>
+                <details>
+                    <item>
+                        <name>reachability</name>
+                        <status>passed</status>
+                    </item>
+                </details>
+            </instanceStatus>
+            <eventsSet>
+              <item>
+                <code>instance-reboot</code>
+                <description>The instance is scheduled for a reboot</description>
+                <notBefore>YYYY-MM-DDTHH:MM:SS+0000</notBefore>
+                <notAfter>YYYY-MM-DDTHH:MM:SS+0000</notAfter>
+              </item>
+            </eventsSet>
+        </item>
+        <item>
+            <instanceId>i-3a2b3c4d</instanceId>
+            <availabilityZone>us-east-1c</availabilityZone>
+            <instanceState>
+                <code>16</code>
+                <name>running</name>
+            </instanceState>
+            <systemStatus>
+                <status>ok</status>
+                <details>
+                    <item>
+                        <name>reachability</name>
+                        <status>passed</status>
+                    </item>
+                </details>
+            </systemStatus>
+            <instanceStatus>
+                <status>ok</status>
+                <details>
+                    <item>
+                        <name>reachability</name>
+                        <status>passed</status>
+                    </item>
+                </details>
+            </instanceStatus>
+        </item>
+        <item>
+            <instanceId>i-4a2b3c4d</instanceId>
+            <availabilityZone>us-east-1c</availabilityZone>
+            <instanceState>
+                <code>16</code>
+                <name>running</name>
+            </instanceState>
+            <systemStatus>
+                <status>ok</status>
+                <details>
+                    <item>
+                        <name>reachability</name>
+                        <status>passed</status>
+                    </item>
+                </details>
+            </systemStatus>
+            <instanceStatus>
+                <status>insufficient-data</status>
+                <details>
+                    <item>
+                        <name>reachability</name>
+                        <status>insufficient-data</status>
+                    </item>
+                </details>
+            </instanceStatus>
+         </item>
+    </instanceStatusSet>
+</DescribeInstanceStatusResponse>
+`
+
+// http://goo.gl/3Q0oCc
+var ReleaseAddressExample = `
+<ReleaseAddressResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
+   <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+   <return>true</return>
+</ReleaseAddressResponse>
+`
+
+// http://goo.gl/uOSQE
+var AssociateAddressExample = `
+<AssociateAddressResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
+   <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+   <return>true</return>
+   <associationId>eipassoc-fc5ca095</associationId>
+</AssociateAddressResponse>
+`
+
+// http://goo.gl/LrOa0
+var DisassociateAddressExample = `
+<DisassociateAddressResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
+   <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+   <return>true</return>
+</DisassociateAddressResponse>
+`
+
+// http://goo.gl/icuXh5
+var ModifyInstanceExample = `
+<ModifyImageAttributeResponse xmlns="http://ec2.amazonaws.com/doc/2013-06-15/">
+  <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+  <return>true</return>
+</ModifyImageAttributeResponse>
+`
+
+var CreateVpcExample = `
+<CreateVpcResponse xmlns="http://ec2.amazonaws.com/doc/2014-06-15/">
+   <requestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</requestId>
+   <vpc>
+      <vpcId>vpc-1a2b3c4d</vpcId>
+      <state>pending</state>
+      <cidrBlock>10.0.0.0/16</cidrBlock>
+      <dhcpOptionsId>dopt-1a2b3c4d2</dhcpOptionsId>
+      <instanceTenancy>default</instanceTenancy>
+      <tagSet/>
+   </vpc>
+</CreateVpcResponse>
+`
+
+var DescribeVpcsExample = `
+<DescribeVpcsResponse xmlns="http://ec2.amazonaws.com/doc/2014-06-15/">
+  <requestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</requestId>
+  <vpcSet>
+    <item>
+      <vpcId>vpc-1a2b3c4d</vpcId>
+      <state>available</state>
+      <cidrBlock>10.0.0.0/23</cidrBlock>
+      <dhcpOptionsId>dopt-7a8b9c2d</dhcpOptionsId>
+      <instanceTenancy>default</instanceTenancy>
+      <isDefault>false</isDefault>
+      <tagSet/>
+    </item>
+  </vpcSet>
+</DescribeVpcsResponse>
+`
+
+var CreateSubnetExample = `
+<CreateSubnetResponse xmlns="http://ec2.amazonaws.com/doc/2014-06-15/">
+  <requestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</requestId>
+  <subnet>
+    <subnetId>subnet-9d4a7b6c</subnetId>
+    <state>pending</state>
+    <vpcId>vpc-1a2b3c4d</vpcId>
+    <cidrBlock>10.0.1.0/24</cidrBlock>
+    <availableIpAddressCount>251</availableIpAddressCount>
+    <availabilityZone>us-east-1a</availabilityZone>
+    <tagSet/>
+  </subnet>
+</CreateSubnetResponse>
+`
+
+// http://goo.gl/tu2Kxm
+var ModifySubnetAttributeExample = `
+<ModifySubnetAttributeResponse xmlns="http://ec2.amazonaws.com/doc/2014-06-15/">
+   <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+   <return>true</return>
+</ModifySubnetAttributeResponse>
+`
+
+// http://goo.gl/r6ZCPm
+var ResetImageAttributeExample = `
+<ResetImageAttributeResponse xmlns="http://ec2.amazonaws.com/doc/2014-06-15/">
+  <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+  <return>true</return>
+</ResetImageAttributeResponse>
+`
+
+// http://goo.gl/ylxT4R
+var DescribeAvailabilityZonesExample1 = `
+<DescribeAvailabilityZonesResponse xmlns="http://ec2.amazonaws.com/doc/2014-05-01/">
+   <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+   <availabilityZoneInfo>
+   <item>
+      <zoneName>us-east-1a</zoneName>
+      <zoneState>available</zoneState>
+      <regionName>us-east-1</regionName>
+      <messageSet/>
+   </item>
+   <item>
+      <zoneName>us-east-1b</zoneName>
+      <zoneState>available</zoneState>
+      <regionName>us-east-1</regionName>
+      <messageSet/>
+   </item>
+   <item>
+      <zoneName>us-east-1c</zoneName>
+      <zoneState>available</zoneState>
+      <regionName>us-east-1</regionName>
+      <messageSet/>
+   </item>
+   <item>
+      <zoneName>us-east-1d</zoneName>
+      <zoneState>available</zoneState>
+      <regionName>us-east-1</regionName>
+      <messageSet/>
+   </item>
+   </availabilityZoneInfo>
+</DescribeAvailabilityZonesResponse>
+`
+
+// http://goo.gl/ylxT4R
+var DescribeAvailabilityZonesExample2 = `
+<DescribeAvailabilityZonesResponse xmlns="http://ec2.amazonaws.com/doc/2014-05-01/">
+   <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+   <availabilityZoneInfo>
+   <item>
+      <zoneName>us-east-1a</zoneName>
+      <zoneState>impaired</zoneState>
+      <regionName>us-east-1</regionName>
+      <messageSet/>
+   </item>
+   <item>
+      <zoneName>us-east-1b</zoneName>
+      <zoneState>unavailable</zoneState>
+      <regionName>us-east-1</regionName>
+      <messageSet>
+         <item>us-east-1b is currently down for maintenance.</item>
+      </messageSet>
+   </item>
+   </availabilityZoneInfo>
+</DescribeAvailabilityZonesResponse>
+`
+
+// http://goo.gl/sdomyE
+var CreateNetworkAclExample = `
+<CreateNetworkAclResponse xmlns="http://ec2.amazonaws.com/doc/2014-10-01/">
+   <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+   <networkAcl>
+      <networkAclId>acl-5fb85d36</networkAclId>
+      <vpcId>vpc-11ad4878</vpcId>
+      <default>false</default>
+      <entrySet>
+         <item>
+            <ruleNumber>32767</ruleNumber>
+            <protocol>-1</protocol>
+            <ruleAction>deny</ruleAction>
+            <egress>true</egress>
+            <cidrBlock>0.0.0.0/0</cidrBlock>
+         </item>
+         <item>
+            <ruleNumber>32767</ruleNumber>
+            <protocol>-1</protocol>
+            <ruleAction>deny</ruleAction>
+            <egress>false</egress>
+            <cidrBlock>0.0.0.0/0</cidrBlock>
+         </item>
+      </entrySet>
+      <associationSet/>
+      <tagSet/>
+   </networkAcl>
+</CreateNetworkAclResponse>
+`
+
+// http://goo.gl/6sYloC
+var CreateNetworkAclEntryRespExample = `
+<CreateNetworkAclEntryResponse xmlns="http://ec2.amazonaws.com/doc/2014-10-01/">
+   <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+   <return>true</return>
+</CreateNetworkAclEntryResponse>
+`
+
+// http://goo.gl/5tqceF
+var DescribeNetworkAclsExample = `
+<DescribeNetworkAclsResponse xmlns="http://ec2.amazonaws.com/doc/2014-10-01/">
+   <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+   <networkAclSet>
+   <item>
+     <networkAclId>acl-5566953c</networkAclId>
+     <vpcId>vpc-5266953b</vpcId>
+     <default>true</default>
+     <entrySet>
+       <item>
+         <ruleNumber>100</ruleNumber>
+         <protocol>-1</protocol>
+         <ruleAction>allow</ruleAction>
+         <egress>true</egress>
+         <cidrBlock>0.0.0.0/0</cidrBlock>
+       </item>
+       <item>
+         <ruleNumber>32767</ruleNumber>
+         <protocol>-1</protocol>
+         <ruleAction>deny</ruleAction>
+         <egress>true</egress>
+         <cidrBlock>0.0.0.0/0</cidrBlock>
+       </item>
+       <item>
+         <ruleNumber>100</ruleNumber>
+         <protocol>-1</protocol>
+         <ruleAction>allow</ruleAction>
+         <egress>false</egress>
+         <cidrBlock>0.0.0.0/0</cidrBlock>
+       </item>
+       <item>
+         <ruleNumber>32767</ruleNumber>
+         <protocol>-1</protocol>
+         <ruleAction>deny</ruleAction>
+         <egress>false</egress>
+         <cidrBlock>0.0.0.0/0</cidrBlock>
+       </item>
+     </entrySet>
+     <associationSet/>
+     <tagSet/>
+   </item>
+   <item>
+     <networkAclId>acl-5d659634</networkAclId>
+     <vpcId>vpc-5266953b</vpcId>
+     <default>false</default>
+     <entrySet>
+       <item>
+         <ruleNumber>110</ruleNumber>
+         <protocol>6</protocol>
+         <ruleAction>allow</ruleAction>
+         <egress>true</egress>
+         <cidrBlock>0.0.0.0/0</cidrBlock>
+         <portRange>
+           <from>49152</from>
+           <to>65535</to>
+         </portRange>
+       </item>
+       <item>
+         <ruleNumber>32767</ruleNumber>
+         <protocol>-1</protocol>
+         <ruleAction>deny</ruleAction>
+         <egress>true</egress>
+         <cidrBlock>0.0.0.0/0</cidrBlock>
+       </item>
+       <item>
+         <ruleNumber>110</ruleNumber>
+         <protocol>6</protocol>
+         <ruleAction>allow</ruleAction>
+         <egress>false</egress>
+         <cidrBlock>0.0.0.0/0</cidrBlock>
+         <portRange>
+           <from>80</from>
+           <to>80</to>
+         </portRange>
+       </item>
+       <item>
+         <ruleNumber>120</ruleNumber>
+         <protocol>6</protocol>
+         <ruleAction>allow</ruleAction>
+         <egress>false</egress>
+         <cidrBlock>0.0.0.0/0</cidrBlock>
+         <portRange>
+           <from>443</from>
+           <to>443</to>
+         </portRange>
+       </item>
+       <item>
+         <ruleNumber>32767</ruleNumber>
+         <protocol>-1</protocol>
+         <ruleAction>deny</ruleAction>
+         <egress>false</egress>
+         <cidrBlock>0.0.0.0/0</cidrBlock>
+       </item>
+     </entrySet>
+     <associationSet>
+       <item>
+         <networkAclAssociationId>aclassoc-5c659635</networkAclAssociationId>
+         <networkAclId>acl-5d659634</networkAclId>
+         <subnetId>subnet-ff669596</subnetId>
+       </item>
+       <item>
+         <networkAclAssociationId>aclassoc-c26596ab</networkAclAssociationId>
+         <networkAclId>acl-5d659634</networkAclId>
+         <subnetId>subnet-f0669599</subnetId>
+       </item>
+     </associationSet>
+     <tagSet/>
+   </item>
+ </networkAclSet>
+</DescribeNetworkAclsResponse>
+`
+
+var ReplaceNetworkAclAssociationResponseExample = `
+<ReplaceNetworkAclAssociationResponse xmlns="http://ec2.amazonaws.com/doc/2014-10-01/">
+   <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+   <newAssociationId>aclassoc-17b85d7e</newAssociationId>
+</ReplaceNetworkAclAssociationResponse>
+`
+
+var CreateCustomerGatewayResponseExample = `
+<CreateCustomerGatewayResponse xmlns="http://ec2.amazonaws.com/doc/2014-06-15/">
+   <requestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</requestId>
+   <customerGateway>
+      <customerGatewayId>cgw-b4dc3961</customerGatewayId>
+      <state>pending</state>
+      <type>ipsec.1</type>
+      <ipAddress>10.0.0.20</ipAddress>
+      <bgpAsn>65534</bgpAsn>
+      <tagSet/>
+   </customerGateway>
+</CreateCustomerGatewayResponse>
+`
+
+var DescribeCustomerGatewaysResponseExample = `
+<DescribeCustomerGatewaysResponse xmlns="http://ec2.amazonaws.com/doc/2014-06-15/">
+  <requestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</requestId>
+  <customerGatewaySet>
+    <item>
+      <customerGatewayId>cgw-b4dc3961</customerGatewayId>
+      <state>available</state>
+      <type>ipsec.1</type>
+      <ipAddress>12.1.2.3</ipAddress>
+      <bgpAsn>65534</bgpAsn>
+      <tagSet/>
+    </item>
+    <item>
+      <customerGatewayId>cgw-b4dc3962</customerGatewayId>
+      <state>pending</state>
+      <type>ipsec.1</type>
+      <ipAddress>12.1.2.4</ipAddress>
+      <bgpAsn>65500</bgpAsn>
+      <tagSet/>
+    </item>
+  </customerGatewaySet>
+</DescribeCustomerGatewaysResponse>
+`
+var DeleteCustomerGatewayResponseExample = `
+<DeleteCustomerGatewayResponse xmlns="http://ec2.amazonaws.com/doc/2014-06-15/">
+   <requestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</requestId>
+   <return>true</return>
+</DeleteCustomerGatewayResponse>`

+ 45 - 0
Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/sign.go

@@ -0,0 +1,45 @@
+package ec2
+
+import (
+	"crypto/hmac"
+	"crypto/sha256"
+	"encoding/base64"
+	"github.com/coreos/flannel/Godeps/_workspace/src/github.com/mitchellh/goamz/aws"
+	"sort"
+	"strings"
+)
+
+// ----------------------------------------------------------------------------
+// EC2 signing (http://goo.gl/fQmAN)
+
+var b64 = base64.StdEncoding
+
+func sign(auth aws.Auth, method, path string, params map[string]string, host string) {
+	params["AWSAccessKeyId"] = auth.AccessKey
+	params["SignatureVersion"] = "2"
+	params["SignatureMethod"] = "HmacSHA256"
+	if auth.Token != "" {
+		params["SecurityToken"] = auth.Token
+	}
+
+	// AWS specifies that the parameters in a signed request must
+	// be provided in the natural order of the keys. This is distinct
+	// from the natural order of the encoded value of key=value.
+	// Percent and equals affect the sorting order.
+	var keys, sarray []string
+	for k, _ := range params {
+		keys = append(keys, k)
+	}
+	sort.Strings(keys)
+	for _, k := range keys {
+		sarray = append(sarray, aws.Encode(k)+"="+aws.Encode(params[k]))
+	}
+	joined := strings.Join(sarray, "&")
+	payload := method + "\n" + host + "\n" + path + "\n" + joined
+	hash := hmac.New(sha256.New, []byte(auth.SecretKey))
+	hash.Write([]byte(payload))
+	signature := make([]byte, b64.EncodedLen(hash.Size()))
+	b64.Encode(signature, hash.Sum(nil))
+
+	params["Signature"] = string(signature)
+}

+ 68 - 0
Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/sign_test.go

@@ -0,0 +1,68 @@
+package ec2_test
+
+import (
+	"github.com/coreos/flannel/Godeps/_workspace/src/github.com/mitchellh/goamz/aws"
+	"github.com/coreos/flannel/Godeps/_workspace/src/github.com/mitchellh/goamz/ec2"
+	. "github.com/motain/gocheck"
+)
+
+// EC2 ReST authentication docs: http://goo.gl/fQmAN
+
+var testAuth = aws.Auth{"user", "secret", ""}
+
+func (s *S) TestBasicSignature(c *C) {
+	params := map[string]string{}
+	ec2.Sign(testAuth, "GET", "/path", params, "localhost")
+	c.Assert(params["SignatureVersion"], Equals, "2")
+	c.Assert(params["SignatureMethod"], Equals, "HmacSHA256")
+	expected := "6lSe5QyXum0jMVc7cOUz32/52ZnL7N5RyKRk/09yiK4="
+	c.Assert(params["Signature"], Equals, expected)
+}
+
+func (s *S) TestParamSignature(c *C) {
+	params := map[string]string{
+		"param1": "value1",
+		"param2": "value2",
+		"param3": "value3",
+	}
+	ec2.Sign(testAuth, "GET", "/path", params, "localhost")
+	expected := "XWOR4+0lmK8bD8CGDGZ4kfuSPbb2JibLJiCl/OPu1oU="
+	c.Assert(params["Signature"], Equals, expected)
+}
+
+func (s *S) TestManyParams(c *C) {
+	params := map[string]string{
+		"param1":  "value10",
+		"param2":  "value2",
+		"param3":  "value3",
+		"param4":  "value4",
+		"param5":  "value5",
+		"param6":  "value6",
+		"param7":  "value7",
+		"param8":  "value8",
+		"param9":  "value9",
+		"param10": "value1",
+	}
+	ec2.Sign(testAuth, "GET", "/path", params, "localhost")
+	expected := "di0sjxIvezUgQ1SIL6i+C/H8lL+U0CQ9frLIak8jkVg="
+	c.Assert(params["Signature"], Equals, expected)
+}
+
+func (s *S) TestEscaping(c *C) {
+	params := map[string]string{"Nonce": "+ +"}
+	ec2.Sign(testAuth, "GET", "/path", params, "localhost")
+	c.Assert(params["Nonce"], Equals, "+ +")
+	expected := "bqffDELReIqwjg/W0DnsnVUmfLK4wXVLO4/LuG+1VFA="
+	c.Assert(params["Signature"], Equals, expected)
+}
+
+func (s *S) TestSignatureExample1(c *C) {
+	params := map[string]string{
+		"Timestamp": "2009-02-01T12:53:20+00:00",
+		"Version":   "2007-11-07",
+		"Action":    "ListDomains",
+	}
+	ec2.Sign(aws.Auth{"access", "secret", ""}, "GET", "/", params, "sdb.amazonaws.com")
+	expected := "okj96/5ucWBSc1uR2zXVfm6mDHtgfNv657rRtt/aunQ="
+	c.Assert(params["Signature"], Equals, expected)
+}

+ 14 - 0
Godeps/_workspace/src/github.com/vaughan0/go-ini/LICENSE

@@ -0,0 +1,14 @@
+Copyright (c) 2013 Vaughan Newton
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
+persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
+Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 70 - 0
Godeps/_workspace/src/github.com/vaughan0/go-ini/README.md

@@ -0,0 +1,70 @@
+go-ini
+======
+
+INI parsing library for Go (golang).
+
+View the API documentation [here](http://godoc.org/github.com/vaughan0/go-ini).
+
+Usage
+-----
+
+Parse an INI file:
+
+```go
+import "github.com/vaughan0/go-ini"
+
+file, err := ini.LoadFile("myfile.ini")
+```
+
+Get data from the parsed file:
+
+```go
+name, ok := file.Get("person", "name")
+if !ok {
+  panic("'name' variable missing from 'person' section")
+}
+```
+
+Iterate through values in a section:
+
+```go
+for key, value := range file["mysection"] {
+  fmt.Printf("%s => %s\n", key, value)
+}
+```
+
+Iterate through sections in a file:
+
+```go
+for name, section := range file {
+  fmt.Printf("Section name: %s\n", name)
+}
+```
+
+File Format
+-----------
+
+INI files are parsed by go-ini line-by-line. Each line may be one of the following:
+
+  * A section definition: [section-name]
+  * A property: key = value
+  * A comment: #blahblah _or_ ;blahblah
+  * Blank. The line will be ignored.
+
+Properties defined before any section headers are placed in the default section, which has
+the empty string as it's key.
+
+Example:
+
+```ini
+# I am a comment
+; So am I!
+
+[apples]
+colour = red or green
+shape = applish
+
+[oranges]
+shape = square
+colour = blue
+```

+ 123 - 0
Godeps/_workspace/src/github.com/vaughan0/go-ini/ini.go

@@ -0,0 +1,123 @@
+// Package ini provides functions for parsing INI configuration files.
+package ini
+
+import (
+	"bufio"
+	"fmt"
+	"io"
+	"os"
+	"regexp"
+	"strings"
+)
+
+var (
+	sectionRegex = regexp.MustCompile(`^\[(.*)\]$`)
+	assignRegex  = regexp.MustCompile(`^([^=]+)=(.*)$`)
+)
+
+// ErrSyntax is returned when there is a syntax error in an INI file.
+type ErrSyntax struct {
+	Line   int
+	Source string // The contents of the erroneous line, without leading or trailing whitespace
+}
+
+func (e ErrSyntax) Error() string {
+	return fmt.Sprintf("invalid INI syntax on line %d: %s", e.Line, e.Source)
+}
+
+// A File represents a parsed INI file.
+type File map[string]Section
+
+// A Section represents a single section of an INI file.
+type Section map[string]string
+
+// Returns a named Section. A Section will be created if one does not already exist for the given name.
+func (f File) Section(name string) Section {
+	section := f[name]
+	if section == nil {
+		section = make(Section)
+		f[name] = section
+	}
+	return section
+}
+
+// Looks up a value for a key in a section and returns that value, along with a boolean result similar to a map lookup.
+func (f File) Get(section, key string) (value string, ok bool) {
+	if s := f[section]; s != nil {
+		value, ok = s[key]
+	}
+	return
+}
+
+// Loads INI data from a reader and stores the data in the File.
+func (f File) Load(in io.Reader) (err error) {
+	bufin, ok := in.(*bufio.Reader)
+	if !ok {
+		bufin = bufio.NewReader(in)
+	}
+	return parseFile(bufin, f)
+}
+
+// Loads INI data from a named file and stores the data in the File.
+func (f File) LoadFile(file string) (err error) {
+	in, err := os.Open(file)
+	if err != nil {
+		return
+	}
+	defer in.Close()
+	return f.Load(in)
+}
+
+func parseFile(in *bufio.Reader, file File) (err error) {
+	section := ""
+	lineNum := 0
+	for done := false; !done; {
+		var line string
+		if line, err = in.ReadString('\n'); err != nil {
+			if err == io.EOF {
+				done = true
+			} else {
+				return
+			}
+		}
+		lineNum++
+		line = strings.TrimSpace(line)
+		if len(line) == 0 {
+			// Skip blank lines
+			continue
+		}
+		if line[0] == ';' || line[0] == '#' {
+			// Skip comments
+			continue
+		}
+
+		if groups := assignRegex.FindStringSubmatch(line); groups != nil {
+			key, val := groups[1], groups[2]
+			key, val = strings.TrimSpace(key), strings.TrimSpace(val)
+			file.Section(section)[key] = val
+		} else if groups := sectionRegex.FindStringSubmatch(line); groups != nil {
+			name := strings.TrimSpace(groups[1])
+			section = name
+			// Create the section if it does not exist
+			file.Section(section)
+		} else {
+			return ErrSyntax{lineNum, line}
+		}
+
+	}
+	return nil
+}
+
+// Loads and returns a File from a reader.
+func Load(in io.Reader) (File, error) {
+	file := make(File)
+	err := file.Load(in)
+	return file, err
+}
+
+// Loads and returns an INI File from a file on disk.
+func LoadFile(filename string) (File, error) {
+	file := make(File)
+	err := file.LoadFile(filename)
+	return file, err
+}

+ 43 - 0
Godeps/_workspace/src/github.com/vaughan0/go-ini/ini_linux_test.go

@@ -0,0 +1,43 @@
+package ini
+
+import (
+	"reflect"
+	"syscall"
+	"testing"
+)
+
+func TestLoadFile(t *testing.T) {
+	originalOpenFiles := numFilesOpen(t)
+
+	file, err := LoadFile("test.ini")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if originalOpenFiles != numFilesOpen(t) {
+		t.Error("test.ini not closed")
+	}
+
+	if !reflect.DeepEqual(file, File{"default": {"stuff": "things"}}) {
+		t.Error("file not read correctly")
+	}
+}
+
+func numFilesOpen(t *testing.T) (num uint64) {
+	var rlimit syscall.Rlimit
+	err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlimit)
+	if err != nil {
+		t.Fatal(err)
+	}
+	maxFds := int(rlimit.Cur)
+
+	var stat syscall.Stat_t
+	for i := 0; i < maxFds; i++ {
+		if syscall.Fstat(i, &stat) == nil {
+			num++
+		} else {
+			return
+		}
+	}
+	return
+}

+ 89 - 0
Godeps/_workspace/src/github.com/vaughan0/go-ini/ini_test.go

@@ -0,0 +1,89 @@
+package ini
+
+import (
+	"reflect"
+	"strings"
+	"testing"
+)
+
+func TestLoad(t *testing.T) {
+	src := `
+  # Comments are ignored
+
+  herp = derp
+
+  [foo]
+  hello=world
+  whitespace should   =   not matter   
+  ; sneaky semicolon-style comment
+  multiple = equals = signs
+
+  [bar]
+  this = that`
+
+	file, err := Load(strings.NewReader(src))
+	if err != nil {
+		t.Fatal(err)
+	}
+	check := func(section, key, expect string) {
+		if value, _ := file.Get(section, key); value != expect {
+			t.Errorf("Get(%q, %q): expected %q, got %q", section, key, expect, value)
+		}
+	}
+
+	check("", "herp", "derp")
+	check("foo", "hello", "world")
+	check("foo", "whitespace should", "not matter")
+	check("foo", "multiple", "equals = signs")
+	check("bar", "this", "that")
+}
+
+func TestSyntaxError(t *testing.T) {
+	src := `
+  # Line 2
+  [foo]
+  bar = baz
+  # Here's an error on line 6:
+  wut?
+  herp = derp`
+	_, err := Load(strings.NewReader(src))
+	t.Logf("%T: %v", err, err)
+	if err == nil {
+		t.Fatal("expected an error, got nil")
+	}
+	syntaxErr, ok := err.(ErrSyntax)
+	if !ok {
+		t.Fatal("expected an error of type ErrSyntax")
+	}
+	if syntaxErr.Line != 6 {
+		t.Fatal("incorrect line number")
+	}
+	if syntaxErr.Source != "wut?" {
+		t.Fatal("incorrect source")
+	}
+}
+
+func TestDefinedSectionBehaviour(t *testing.T) {
+	check := func(src string, expect File) {
+		file, err := Load(strings.NewReader(src))
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !reflect.DeepEqual(file, expect) {
+			t.Errorf("expected %v, got %v", expect, file)
+		}
+	}
+	// No sections for an empty file
+	check("", File{})
+	// Default section only if there are actually values for it
+	check("foo=bar", File{"": {"foo": "bar"}})
+	// User-defined sections should always be present, even if empty
+	check("[a]\n[b]\nfoo=bar", File{
+		"a": {},
+		"b": {"foo": "bar"},
+	})
+	check("foo=bar\n[a]\nthis=that", File{
+		"":  {"foo": "bar"},
+		"a": {"this": "that"},
+	})
+}

+ 2 - 0
Godeps/_workspace/src/github.com/vaughan0/go-ini/test.ini

@@ -0,0 +1,2 @@
+[default]
+stuff = things

+ 10 - 0
README.md

@@ -80,6 +80,16 @@ of available backends and the keys that can be put into the this dictionary are
   flannel.
   * ```Type``` (string): ```host-gw```
 
+* aws-vpc: create IP routes in an [Amazon VPC route table](http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Route_Tables.html).
+  Requires running on an EC2 instance that is in an Amazon VPC.
+  * ```Type``` (string): ```aws-vpc```
+  * ```RouteTableID``` (string): The ID of the VPC route table to add routes to. This must be in the
+  same region as the EC2 instance that flannel is running on.
+
+  Authentication is handled via environment variables. Ensure that the ```AWS_ACCESS_KEY_ID```,
+  ```AWS_SECRET_ACCESS_KEY``` and optionally ```AWS_SECURITY_TOKEN``` environment variables are set
+  when running the flannel process.
+
 ### Example configuration JSON
 
 The following configuration illustrates the use of most options.

+ 132 - 0
backend/awsvpc/awsvpc.go

@@ -0,0 +1,132 @@
+// Copyright 2015 CoreOS, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package awsvpc
+
+import (
+	"encoding/json"
+	"fmt"
+	"net"
+	"sync"
+
+	"github.com/coreos/flannel/Godeps/_workspace/src/github.com/mitchellh/goamz/aws"
+	"github.com/coreos/flannel/Godeps/_workspace/src/github.com/mitchellh/goamz/ec2"
+
+	"github.com/coreos/flannel/backend"
+	"github.com/coreos/flannel/pkg/ip"
+	"github.com/coreos/flannel/pkg/task"
+	"github.com/coreos/flannel/subnet"
+)
+
+type AwsVpcBackend struct {
+	sm     *subnet.SubnetManager
+	rawCfg json.RawMessage
+	cfg    struct {
+		RouteTableID string
+	}
+	stop chan bool
+	wg   sync.WaitGroup
+}
+
+func New(sm *subnet.SubnetManager, config json.RawMessage) backend.Backend {
+	be := AwsVpcBackend{
+		sm:     sm,
+		rawCfg: config,
+		stop:   make(chan bool),
+	}
+	return &be
+}
+
+func (m *AwsVpcBackend) Init(extIface *net.Interface, extIP net.IP) (*backend.SubnetDef, error) {
+	// Parse our configuration
+	if len(m.rawCfg) > 0 {
+		if err := json.Unmarshal(m.rawCfg, &m.cfg); err != nil {
+			return nil, fmt.Errorf("error decoding VPC backend config: %v", err)
+		}
+	}
+
+	// Acquire the lease form subnet manager
+	attrs := subnet.LeaseAttrs{
+		PublicIP: ip.FromIP(extIP),
+	}
+
+	sn, err := m.sm.AcquireLease(&attrs, m.stop)
+	if err != nil {
+		if err == task.ErrCanceled {
+			return nil, err
+		} else {
+			return nil, fmt.Errorf("failed to acquire lease: %v", err)
+		}
+	}
+
+	// Figure out this machine's EC2 instance ID and region
+	identity, err := getInstanceIdentity()
+	if err != nil {
+		return nil, fmt.Errorf("error getting EC2 instance identity: %v", err)
+	}
+
+	instanceID, ok := identity["instanceId"].(string)
+	if !ok {
+		return nil, fmt.Errorf("invalid EC2 instance ID: %v", identity["instanceId"])
+	}
+
+	regionVal, _ := identity["region"].(string)
+	region, ok := aws.Regions[regionVal]
+	if !ok {
+		return nil, fmt.Errorf("invalid AWS region: %v", identity["region"])
+	}
+
+	// Setup the EC2 client
+	auth, err := aws.EnvAuth()
+	if err != nil {
+		return nil, fmt.Errorf("error getting AWS credentials from environment: %v", err)
+	}
+	ec2c := ec2.New(auth, region)
+
+	// Delete route for this machine's subnet if it already exists
+	if _, err := ec2c.DeleteRoute(m.cfg.RouteTableID, sn.String()); err != nil {
+		if ec2err, ok := err.(*ec2.Error); !ok || ec2err.Code != "InvalidRoute.NotFound" {
+			// an error other than the route not already existing occurred
+			return nil, fmt.Errorf("error deleting existing route for %s: %v", sn.String(), err)
+		}
+	}
+
+	// Add the route for this machine's subnet
+	route := &ec2.CreateRoute{
+		RouteTableId:         m.cfg.RouteTableID,
+		InstanceId:           instanceID,
+		DestinationCidrBlock: sn.String(),
+	}
+
+	if _, err := ec2c.CreateRoute(route); err != nil {
+		return nil, fmt.Errorf("unable to add route %+v: %v", route, err)
+	}
+
+	return &backend.SubnetDef{
+		Net: sn,
+		MTU: extIface.MTU,
+	}, nil
+}
+
+func (m *AwsVpcBackend) Run() {
+	m.sm.LeaseRenewer(m.stop)
+}
+
+func (m *AwsVpcBackend) Stop() {
+	close(m.stop)
+}
+
+func (m *AwsVpcBackend) Name() string {
+	return "aws-vpc"
+}

+ 46 - 0
backend/awsvpc/ec2.go

@@ -0,0 +1,46 @@
+// Copyright 2015 CoreOS, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package awsvpc
+
+import (
+	"encoding/json"
+	"fmt"
+
+	"github.com/coreos/flannel/Godeps/_workspace/src/github.com/mitchellh/goamz/aws"
+)
+
+func getInstanceIdentity() (map[string]interface{}, error) {
+	url := "http://169.254.169.254/latest/dynamic/instance-identity/document"
+
+	resp, err := aws.RetryingClient.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	defer resp.Body.Close()
+
+	if resp.StatusCode != 200 {
+		err = fmt.Errorf("Code %d returned for url %s", resp.StatusCode, url)
+		return nil, err
+	}
+
+	dec := json.NewDecoder(resp.Body)
+	identity := make(map[string]interface{})
+
+	if err := dec.Decode(&identity); err != nil {
+		return nil, err
+	}
+
+	return identity, nil
+}

+ 3 - 0
main.go

@@ -31,6 +31,7 @@ import (
 
 	"github.com/coreos/flannel/backend"
 	"github.com/coreos/flannel/backend/alloc"
+	"github.com/coreos/flannel/backend/awsvpc"
 	"github.com/coreos/flannel/backend/hostgw"
 	"github.com/coreos/flannel/backend/udp"
 	"github.com/coreos/flannel/backend/vxlan"
@@ -196,6 +197,8 @@ func newBackend(sm *subnet.SubnetManager) (backend.Backend, error) {
 		return hostgw.New(sm), nil
 	case "vxlan":
 		return vxlan.New(sm, config.Backend), nil
+	case "aws-vpc":
+		return awsvpc.New(sm, config.Backend), nil
 	default:
 		return nil, fmt.Errorf("'%v': unknown backend type", bt.Type)
 	}