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": [
|
||||
{
|
||||
"ImportPath": "code.google.com/p/gcfg",
|
||||
"Rev": "c2d3050044d05357eaf6c3547249ba57c5e235cb"
|
||||
},
|
||||
{
|
||||
"ImportPath": "code.google.com/p/go-uuid/uuid",
|
||||
"Comment": "null-12",
|
||||
@@ -68,6 +72,14 @@
|
||||
"ImportPath": "github.com/google/gofuzz",
|
||||
"Rev": "aef70dacbc78771e35beb261bb3a72986adf7906"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mitchellh/goamz/aws",
|
||||
"Rev": "9cad7da945e699385c1a3e115aa255211921c9bb"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mitchellh/goamz/ec2",
|
||||
"Rev": "9cad7da945e699385c1a3e115aa255211921c9bb"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/stretchr/objx",
|
||||
"Rev": "d40df0cc104c06eae2dfe03d7dddb83802d52f9a"
|
||||
@@ -80,6 +92,10 @@
|
||||
"ImportPath": "github.com/stretchr/testify/mock",
|
||||
"Rev": "37614ac27794505bf7867ca93aac883cadb6a5f7"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/vaughan0/go-ini",
|
||||
"Rev": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1"
|
||||
},
|
||||
{
|
||||
"ImportPath": "gopkg.in/v1/yaml",
|
||||
"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
|
||||
// given binary target.
|
||||
import (
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/aws"
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/gce"
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/vagrant"
|
||||
_ "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