Merge pull request #1229 from ragnard/aws-provider
Initial impl. of cloud provider for AWS
This commit is contained in:
16
Godeps/Godeps.json
generated
16
Godeps/Godeps.json
generated
@@ -5,6 +5,10 @@
|
|||||||
"./..."
|
"./..."
|
||||||
],
|
],
|
||||||
"Deps": [
|
"Deps": [
|
||||||
|
{
|
||||||
|
"ImportPath": "code.google.com/p/gcfg",
|
||||||
|
"Rev": "c2d3050044d05357eaf6c3547249ba57c5e235cb"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "code.google.com/p/go-uuid/uuid",
|
"ImportPath": "code.google.com/p/go-uuid/uuid",
|
||||||
"Comment": "null-12",
|
"Comment": "null-12",
|
||||||
@@ -68,6 +72,14 @@
|
|||||||
"ImportPath": "github.com/google/gofuzz",
|
"ImportPath": "github.com/google/gofuzz",
|
||||||
"Rev": "aef70dacbc78771e35beb261bb3a72986adf7906"
|
"Rev": "aef70dacbc78771e35beb261bb3a72986adf7906"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/mitchellh/goamz/aws",
|
||||||
|
"Rev": "9cad7da945e699385c1a3e115aa255211921c9bb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/mitchellh/goamz/ec2",
|
||||||
|
"Rev": "9cad7da945e699385c1a3e115aa255211921c9bb"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/stretchr/objx",
|
"ImportPath": "github.com/stretchr/objx",
|
||||||
"Rev": "d40df0cc104c06eae2dfe03d7dddb83802d52f9a"
|
"Rev": "d40df0cc104c06eae2dfe03d7dddb83802d52f9a"
|
||||||
@@ -80,6 +92,10 @@
|
|||||||
"ImportPath": "github.com/stretchr/testify/mock",
|
"ImportPath": "github.com/stretchr/testify/mock",
|
||||||
"Rev": "37614ac27794505bf7867ca93aac883cadb6a5f7"
|
"Rev": "37614ac27794505bf7867ca93aac883cadb6a5f7"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/vaughan0/go-ini",
|
||||||
|
"Rev": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "gopkg.in/v1/yaml",
|
"ImportPath": "gopkg.in/v1/yaml",
|
||||||
"Rev": "1b9791953ba4027efaeb728c7355e542a203be5e"
|
"Rev": "1b9791953ba4027efaeb728c7355e542a203be5e"
|
||||||
|
74
Godeps/_workspace/src/github.com/mitchellh/goamz/aws/attempt.go
generated
vendored
Normal file
74
Godeps/_workspace/src/github.com/mitchellh/goamz/aws/attempt.go
generated
vendored
Normal file
@@ -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
Godeps/_workspace/src/github.com/mitchellh/goamz/aws/attempt_test.go
generated
vendored
Normal file
57
Godeps/_workspace/src/github.com/mitchellh/goamz/aws/attempt_test.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package aws_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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)
|
||||||
|
}
|
423
Godeps/_workspace/src/github.com/mitchellh/goamz/aws/aws.go
generated
vendored
Normal file
423
Godeps/_workspace/src/github.com/mitchellh/goamz/aws/aws.go
generated
vendored
Normal file
@@ -0,0 +1,423 @@
|
|||||||
|
//
|
||||||
|
// 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"
|
||||||
|
"github.com/vaughan0/go-ini"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 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,
|
||||||
|
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 homeDir = os.Getenv("HOME")
|
||||||
|
if homeDir == "" {
|
||||||
|
err = errors.New("Could not get HOME")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var 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
Godeps/_workspace/src/github.com/mitchellh/goamz/aws/aws_test.go
generated
vendored
Normal file
203
Godeps/_workspace/src/github.com/mitchellh/goamz/aws/aws_test.go
generated
vendored
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
package aws_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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
Godeps/_workspace/src/github.com/mitchellh/goamz/aws/client.go
generated
vendored
Normal file
125
Godeps/_workspace/src/github.com/mitchellh/goamz/aws/client.go
generated
vendored
Normal file
@@ -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
Godeps/_workspace/src/github.com/mitchellh/goamz/aws/client_test.go
generated
vendored
Normal file
121
Godeps/_workspace/src/github.com/mitchellh/goamz/aws/client_test.go
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
package aws_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"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")
|
||||||
|
}
|
||||||
|
}
|
2599
Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/ec2.go
generated
vendored
Normal file
2599
Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/ec2.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1243
Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/ec2_test.go
generated
vendored
Normal file
1243
Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/ec2_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
203
Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/ec2i_test.go
generated
vendored
Normal file
203
Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/ec2i_test.go
generated
vendored
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
package ec2_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/goamz/aws"
|
||||||
|
"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.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
Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/ec2t_test.go
generated
vendored
Normal file
580
Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/ec2t_test.go
generated
vendored
Normal file
@@ -0,0 +1,580 @@
|
|||||||
|
package ec2_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/goamz/aws"
|
||||||
|
"github.com/mitchellh/goamz/ec2"
|
||||||
|
"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
Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/ec2test/filter.go
generated
vendored
Normal file
84
Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/ec2test/filter.go
generated
vendored
Normal file
@@ -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
Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/ec2test/server.go
generated
vendored
Normal file
993
Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/ec2test/server.go
generated
vendored
Normal file
@@ -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/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
Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/export_test.go
generated
vendored
Normal file
22
Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/export_test.go
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package ec2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
}
|
854
Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/responses_test.go
generated
vendored
Normal file
854
Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/responses_test.go
generated
vendored
Normal file
@@ -0,0 +1,854 @@
|
|||||||
|
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>
|
||||||
|
</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/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/r6ZCPm
|
||||||
|
var ResetImageAttributeExample = `
|
||||||
|
<ResetImageAttributeResponse xmlns="http://ec2.amazonaws.com/doc/2014-06-15/">
|
||||||
|
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
|
||||||
|
<return>true</return>
|
||||||
|
</ResetImageAttributeResponse>
|
||||||
|
`
|
45
Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/sign.go
generated
vendored
Normal file
45
Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/sign.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package ec2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"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
Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/sign_test.go
generated
vendored
Normal file
68
Godeps/_workspace/src/github.com/mitchellh/goamz/ec2/sign_test.go
generated
vendored
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package ec2_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mitchellh/goamz/aws"
|
||||||
|
"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
Godeps/_workspace/src/github.com/vaughan0/go-ini/LICENSE
generated
vendored
Normal file
14
Godeps/_workspace/src/github.com/vaughan0/go-ini/LICENSE
generated
vendored
Normal file
@@ -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
Godeps/_workspace/src/github.com/vaughan0/go-ini/README.md
generated
vendored
Normal file
70
Godeps/_workspace/src/github.com/vaughan0/go-ini/README.md
generated
vendored
Normal file
@@ -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
Godeps/_workspace/src/github.com/vaughan0/go-ini/ini.go
generated
vendored
Normal file
123
Godeps/_workspace/src/github.com/vaughan0/go-ini/ini.go
generated
vendored
Normal file
@@ -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
Godeps/_workspace/src/github.com/vaughan0/go-ini/ini_linux_test.go
generated
vendored
Normal file
43
Godeps/_workspace/src/github.com/vaughan0/go-ini/ini_linux_test.go
generated
vendored
Normal file
@@ -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
Godeps/_workspace/src/github.com/vaughan0/go-ini/ini_test.go
generated
vendored
Normal file
89
Godeps/_workspace/src/github.com/vaughan0/go-ini/ini_test.go
generated
vendored
Normal file
@@ -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
Godeps/_workspace/src/github.com/vaughan0/go-ini/test.ini
generated
vendored
Normal file
2
Godeps/_workspace/src/github.com/vaughan0/go-ini/test.ini
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[default]
|
||||||
|
stuff = things
|
@@ -20,6 +20,7 @@ package main
|
|||||||
// This should probably be part of some configuration fed into the build for a
|
// This should probably be part of some configuration fed into the build for a
|
||||||
// given binary target.
|
// given binary target.
|
||||||
import (
|
import (
|
||||||
|
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/aws"
|
||||||
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/gce"
|
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/gce"
|
||||||
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/vagrant"
|
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/vagrant"
|
||||||
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/ovirt"
|
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/ovirt"
|
||||||
|
181
pkg/cloudprovider/aws/aws.go
Normal file
181
pkg/cloudprovider/aws/aws.go
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
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 aws_cloud
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"code.google.com/p/gcfg"
|
||||||
|
"github.com/mitchellh/goamz/aws"
|
||||||
|
"github.com/mitchellh/goamz/ec2"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EC2 interface {
|
||||||
|
Instances(instIds []string, filter *ec2.Filter) (resp *ec2.InstancesResp, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AWSCloud is an implementation of Interface, TCPLoadBalancer and Instances for Amazon Web Services.
|
||||||
|
type AWSCloud struct {
|
||||||
|
ec2 EC2
|
||||||
|
cfg *AWSCloudConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type AWSCloudConfig struct {
|
||||||
|
Global struct {
|
||||||
|
Region string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthFunc func() (auth aws.Auth, err error)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cloudprovider.RegisterCloudProvider("aws", func(config io.Reader) (cloudprovider.Interface, error) {
|
||||||
|
return newAWSCloud(config, getAuth)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAuth() (auth aws.Auth, err error) {
|
||||||
|
return aws.GetAuth("", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// readAWSCloudConfig reads an instance of AWSCloudConfig from config reader.
|
||||||
|
func readAWSCloudConfig(config io.Reader) (*AWSCloudConfig, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, fmt.Errorf("No AWS cloud provider config file given")
|
||||||
|
}
|
||||||
|
|
||||||
|
var cfg AWSCloudConfig
|
||||||
|
err := gcfg.ReadInto(&cfg, config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Global.Region == "" {
|
||||||
|
return nil, fmt.Errorf("No region specified in configuration file")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newAWSCloud creates a new instance of AWSCloud.
|
||||||
|
func newAWSCloud(config io.Reader, authFunc AuthFunc) (*AWSCloud, error) {
|
||||||
|
cfg, err := readAWSCloudConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to read AWS cloud provider config file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
auth, err := authFunc()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
region, ok := aws.Regions[cfg.Global.Region]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("Not a valid AWS region: %s", cfg.Global.Region)
|
||||||
|
}
|
||||||
|
|
||||||
|
ec2 := ec2.New(auth, region)
|
||||||
|
return &AWSCloud{
|
||||||
|
ec2: ec2,
|
||||||
|
cfg: cfg,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TCPLoadBalancer returns an implementation of TCPLoadBalancer for Amazon Web Services.
|
||||||
|
func (aws *AWSCloud) TCPLoadBalancer() (cloudprovider.TCPLoadBalancer, bool) {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instances returns an implementation of Instances for Amazon Web Services.
|
||||||
|
func (aws *AWSCloud) Instances() (cloudprovider.Instances, bool) {
|
||||||
|
return aws, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zones returns an implementation of Zones for Amazon Web Services.
|
||||||
|
func (aws *AWSCloud) Zones() (cloudprovider.Zones, bool) {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPAddress is an implementation of Instances.IPAddress.
|
||||||
|
func (aws *AWSCloud) IPAddress(name string) (net.IP, error) {
|
||||||
|
f := ec2.NewFilter()
|
||||||
|
f.Add("private-dns-name", name)
|
||||||
|
|
||||||
|
resp, err := aws.ec2.Instances(nil, f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(resp.Reservations) == 0 {
|
||||||
|
return nil, fmt.Errorf("No reservations found for host: %s", name)
|
||||||
|
}
|
||||||
|
if len(resp.Reservations) > 1 {
|
||||||
|
return nil, fmt.Errorf("Multiple reservations found for host: %s", name)
|
||||||
|
}
|
||||||
|
if len(resp.Reservations[0].Instances) == 0 {
|
||||||
|
return nil, fmt.Errorf("No instances found for host: %s", name)
|
||||||
|
}
|
||||||
|
if len(resp.Reservations[0].Instances) > 1 {
|
||||||
|
return nil, fmt.Errorf("Multiple instances found for host: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
ipAddress := resp.Reservations[0].Instances[0].PrivateIpAddress
|
||||||
|
ip := net.ParseIP(ipAddress)
|
||||||
|
if ip == nil {
|
||||||
|
return nil, fmt.Errorf("Invalid network IP: %s", ipAddress)
|
||||||
|
}
|
||||||
|
return ip, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a list of instances matching regex string.
|
||||||
|
func (aws *AWSCloud) getInstancesByRegex(regex string) ([]string, error) {
|
||||||
|
resp, err := aws.ec2.Instances(nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return []string{}, err
|
||||||
|
}
|
||||||
|
if resp == nil {
|
||||||
|
return []string{}, fmt.Errorf("No InstanceResp returned")
|
||||||
|
}
|
||||||
|
|
||||||
|
re, err := regexp.Compile(regex)
|
||||||
|
if err != nil {
|
||||||
|
return []string{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
instances := []string{}
|
||||||
|
for _, reservation := range resp.Reservations {
|
||||||
|
for _, instance := range reservation.Instances {
|
||||||
|
for _, tag := range instance.Tags {
|
||||||
|
if tag.Key == "Name" && re.MatchString(tag.Value) {
|
||||||
|
instances = append(instances, instance.PrivateDNSName)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return instances, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// List is an implementation of Instances.List.
|
||||||
|
func (aws *AWSCloud) List(filter string) ([]string, error) {
|
||||||
|
// TODO: Should really use tag query. No need to go regexp.
|
||||||
|
return aws.getInstancesByRegex(filter)
|
||||||
|
}
|
157
pkg/cloudprovider/aws/aws_test.go
Normal file
157
pkg/cloudprovider/aws/aws_test.go
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
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 aws_cloud
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mitchellh/goamz/aws"
|
||||||
|
"github.com/mitchellh/goamz/ec2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestReadAWSCloudConfig(t *testing.T) {
|
||||||
|
_, err1 := readAWSCloudConfig(nil)
|
||||||
|
if err1 == nil {
|
||||||
|
t.Errorf("Should error when no config reader is given")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err2 := readAWSCloudConfig(strings.NewReader(""))
|
||||||
|
if err2 == nil {
|
||||||
|
t.Errorf("Should error when config is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err3 := readAWSCloudConfig(strings.NewReader("[global]\n"))
|
||||||
|
if err3 == nil {
|
||||||
|
t.Errorf("Should error when no region is specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err4 := readAWSCloudConfig(strings.NewReader("[global]\nregion = eu-west-1"))
|
||||||
|
if err4 != nil {
|
||||||
|
t.Errorf("Should succeed when a region is specified: %s", err4)
|
||||||
|
}
|
||||||
|
if cfg.Global.Region != "eu-west-1" {
|
||||||
|
t.Errorf("Should read region from config")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewAWSCloud(t *testing.T) {
|
||||||
|
fakeAuthFunc := func() (auth aws.Auth, err error) {
|
||||||
|
return aws.Auth{"", "", ""}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err1 := newAWSCloud(nil, fakeAuthFunc)
|
||||||
|
if err1 == nil {
|
||||||
|
t.Errorf("Should error when no config reader is given")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err2 := newAWSCloud(strings.NewReader(
|
||||||
|
"[global]\nregion = blahonga"),
|
||||||
|
fakeAuthFunc)
|
||||||
|
if err2 == nil {
|
||||||
|
t.Errorf("Should error when config specifies invalid region")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err3 := newAWSCloud(
|
||||||
|
strings.NewReader("[global]\nregion = eu-west-1"),
|
||||||
|
fakeAuthFunc)
|
||||||
|
if err3 != nil {
|
||||||
|
t.Errorf("Should succeed when a valid region is specified: %s", err3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type FakeEC2 struct {
|
||||||
|
instances func(instanceIds []string, filter *ec2.Filter) (resp *ec2.InstancesResp, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec2 *FakeEC2) Instances(instanceIds []string, filter *ec2.Filter) (resp *ec2.InstancesResp, err error) {
|
||||||
|
return ec2.instances(instanceIds, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mockInstancesResp(instances []ec2.Instance) (aws *AWSCloud) {
|
||||||
|
return &AWSCloud{
|
||||||
|
&FakeEC2{
|
||||||
|
func(instanceIds []string, filter *ec2.Filter) (resp *ec2.InstancesResp, err error) {
|
||||||
|
return &ec2.InstancesResp{"",
|
||||||
|
[]ec2.Reservation{
|
||||||
|
ec2.Reservation{"", "", "", nil, instances}}}, nil
|
||||||
|
}},
|
||||||
|
nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestList(t *testing.T) {
|
||||||
|
instances := make([]ec2.Instance, 4)
|
||||||
|
instances[0].Tags = []ec2.Tag{ec2.Tag{"Name", "foo"}}
|
||||||
|
instances[0].PrivateDNSName = "instance1"
|
||||||
|
instances[1].Tags = []ec2.Tag{ec2.Tag{"Name", "bar"}}
|
||||||
|
instances[1].PrivateDNSName = "instance2"
|
||||||
|
instances[2].Tags = []ec2.Tag{ec2.Tag{"Name", "baz"}}
|
||||||
|
instances[2].PrivateDNSName = "instance3"
|
||||||
|
instances[3].Tags = []ec2.Tag{ec2.Tag{"Name", "quux"}}
|
||||||
|
instances[3].PrivateDNSName = "instance4"
|
||||||
|
|
||||||
|
aws := mockInstancesResp(instances)
|
||||||
|
|
||||||
|
table := []struct {
|
||||||
|
input string
|
||||||
|
expect []string
|
||||||
|
}{
|
||||||
|
{"blahonga", []string{}},
|
||||||
|
{"quux", []string{"instance4"}},
|
||||||
|
{"a", []string{"instance2", "instance3"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range table {
|
||||||
|
result, err := aws.List(item.input)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected call with %v to succeed, failed with %s", item.input, err)
|
||||||
|
}
|
||||||
|
if e, a := item.expect, result; !reflect.DeepEqual(e, a) {
|
||||||
|
t.Errorf("Expected %v, got %v", e, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIPAddress(t *testing.T) {
|
||||||
|
instances := make([]ec2.Instance, 2)
|
||||||
|
instances[0].PrivateDNSName = "instance1"
|
||||||
|
instances[0].PrivateIpAddress = "192.168.0.1"
|
||||||
|
instances[1].PrivateDNSName = "instance2"
|
||||||
|
instances[1].PrivateIpAddress = "192.168.0.2"
|
||||||
|
|
||||||
|
aws1 := mockInstancesResp([]ec2.Instance{})
|
||||||
|
_, err1 := aws1.IPAddress("instance")
|
||||||
|
if err1 == nil {
|
||||||
|
t.Errorf("Should error when no instance found")
|
||||||
|
}
|
||||||
|
|
||||||
|
aws2 := mockInstancesResp(instances)
|
||||||
|
_, err2 := aws2.IPAddress("instance1")
|
||||||
|
if err2 == nil {
|
||||||
|
t.Errorf("Should error when multiple instances found")
|
||||||
|
}
|
||||||
|
|
||||||
|
aws3 := mockInstancesResp(instances[0:1])
|
||||||
|
ip3, err3 := aws3.IPAddress("instance1")
|
||||||
|
if err3 != nil {
|
||||||
|
t.Errorf("Should not error when instance found")
|
||||||
|
}
|
||||||
|
if e, a := instances[0].PrivateIpAddress, ip3.String(); e != a {
|
||||||
|
t.Errorf("Expected %v, got %v", e, a)
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user