fix template-set credentials
update tests with mocked session
This commit is contained in:
parent
9573013d3a
commit
0924a316b7
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||||
"github.com/aws/aws-sdk-go/aws/ec2metadata"
|
"github.com/aws/aws-sdk-go/aws/ec2metadata"
|
||||||
"github.com/aws/aws-sdk-go/aws/session"
|
"github.com/aws/aws-sdk-go/aws/session"
|
||||||
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
"github.com/hashicorp/go-cleanhttp"
|
"github.com/hashicorp/go-cleanhttp"
|
||||||
"github.com/hashicorp/packer/template/interpolate"
|
"github.com/hashicorp/packer/template/interpolate"
|
||||||
)
|
)
|
||||||
|
@ -38,7 +39,6 @@ func (c *AccessConfig) Session() (*session.Session, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
config := aws.NewConfig().WithCredentialsChainVerboseErrors(true)
|
config := aws.NewConfig().WithCredentialsChainVerboseErrors(true)
|
||||||
|
|
||||||
staticCreds := credentials.NewStaticCredentials(c.AccessKey, c.SecretKey, c.Token)
|
staticCreds := credentials.NewStaticCredentials(c.AccessKey, c.SecretKey, c.Token)
|
||||||
if _, err := staticCreds.Get(); err != credentials.ErrStaticCredentialsEmpty {
|
if _, err := staticCreds.Get(); err != credentials.ErrStaticCredentialsEmpty {
|
||||||
config.WithCredentials(staticCreds)
|
config.WithCredentials(staticCreds)
|
||||||
|
@ -148,8 +148,12 @@ func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.RawRegion != "" && !c.SkipValidation {
|
if c.RawRegion != "" && !c.SkipValidation {
|
||||||
ec2conn := getValidationSession()
|
sess, err := c.Session()
|
||||||
err := ValidateRegion(c.RawRegion, ec2conn)
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
ec2conn := ec2.New(sess)
|
||||||
|
err = ValidateRegion(c.RawRegion, ec2conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = append(errs, fmt.Errorf("error validating region: %s", err.Error()))
|
errs = append(errs, fmt.Errorf("error validating region: %s", err.Error()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
|
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
|
||||||
"github.com/hashicorp/packer/template/interpolate"
|
"github.com/hashicorp/packer/template/interpolate"
|
||||||
)
|
)
|
||||||
|
@ -57,7 +58,11 @@ func (c *AMIConfig) Prepare(accessConfig *AccessConfig, ctx *interpolate.Context
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ec2conn := getValidationSession()
|
sess, err := accessConfig.Session()
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
ec2conn := ec2.New(sess)
|
||||||
errs = c.prepareRegions(ec2conn, accessConfig, errs)
|
errs = c.prepareRegions(ec2conn, accessConfig, errs)
|
||||||
|
|
||||||
if len(c.AMIUsers) > 0 && c.AMIEncryptBootVolume {
|
if len(c.AMIUsers) > 0 && c.AMIEncryptBootVolume {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/awstesting/mock"
|
||||||
"github.com/aws/aws-sdk-go/service/ec2"
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
|
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
|
||||||
)
|
)
|
||||||
|
@ -19,18 +20,20 @@ func testAMIConfig() *AMIConfig {
|
||||||
func getFakeAccessConfig(region string) *AccessConfig {
|
func getFakeAccessConfig(region string) *AccessConfig {
|
||||||
return &AccessConfig{
|
return &AccessConfig{
|
||||||
RawRegion: region,
|
RawRegion: region,
|
||||||
|
session: mock.Session,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAMIConfigPrepare_name(t *testing.T) {
|
func TestAMIConfigPrepare_name(t *testing.T) {
|
||||||
c := testAMIConfig()
|
c := testAMIConfig()
|
||||||
|
accessConf := getFakeAccessConfig("wherever")
|
||||||
c.AMISkipRegionValidation = true
|
c.AMISkipRegionValidation = true
|
||||||
if err := c.Prepare(nil, nil); err != nil {
|
if err := c.Prepare(accessConf, nil); err != nil {
|
||||||
t.Fatalf("shouldn't have err: %s", err)
|
t.Fatalf("shouldn't have err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.AMIName = ""
|
c.AMIName = ""
|
||||||
if err := c.Prepare(nil, nil); err == nil {
|
if err := c.Prepare(accessConf, nil); err == nil {
|
||||||
t.Fatal("should have error")
|
t.Fatal("should have error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,7 +144,8 @@ func TestAMIConfigPrepare_regions(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
c.AMISkipRegionValidation = true
|
c.AMISkipRegionValidation = true
|
||||||
if err := c.Prepare(nil, nil); err == nil {
|
accessConf := getFakeAccessConfig("wherever")
|
||||||
|
if err := c.Prepare(accessConf, nil); err == nil {
|
||||||
t.Fatal("should have error b/c theres a region in in ami_regions that isn't in the key map")
|
t.Fatal("should have error b/c theres a region in in ami_regions that isn't in the key map")
|
||||||
}
|
}
|
||||||
c.AMISkipRegionValidation = false
|
c.AMISkipRegionValidation = false
|
||||||
|
@ -159,7 +163,7 @@ func TestAMIConfigPrepare_regions(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// allow rawregion to exist in ami_regions list.
|
// allow rawregion to exist in ami_regions list.
|
||||||
accessConf := getFakeAccessConfig("us-east-1")
|
accessConf = getFakeAccessConfig("us-east-1")
|
||||||
c.AMIRegions = []string{"us-east-1", "us-west-1", "us-east-2"}
|
c.AMIRegions = []string{"us-east-1", "us-west-1", "us-east-2"}
|
||||||
c.AMIRegionKMSKeyIDs = nil
|
c.AMIRegionKMSKeyIDs = nil
|
||||||
if errs = c.prepareRegions(mockConn, accessConf, errs); len(errs) > 0 {
|
if errs = c.prepareRegions(mockConn, accessConf, errs); len(errs) > 0 {
|
||||||
|
@ -173,13 +177,15 @@ func TestAMIConfigPrepare_Share_EncryptedBoot(t *testing.T) {
|
||||||
c.AMIUsers = []string{"testAccountID"}
|
c.AMIUsers = []string{"testAccountID"}
|
||||||
c.AMIEncryptBootVolume = true
|
c.AMIEncryptBootVolume = true
|
||||||
|
|
||||||
|
accessConf := getFakeAccessConfig("wherever")
|
||||||
|
|
||||||
c.AMIKmsKeyId = ""
|
c.AMIKmsKeyId = ""
|
||||||
if err := c.Prepare(nil, nil); err == nil {
|
if err := c.Prepare(accessConf, nil); err == nil {
|
||||||
t.Fatal("shouldn't be able to share ami with encrypted boot volume")
|
t.Fatal("shouldn't be able to share ami with encrypted boot volume")
|
||||||
}
|
}
|
||||||
|
|
||||||
c.AMIKmsKeyId = "89c3fb9a-de87-4f2a-aedc-fddc5138193c"
|
c.AMIKmsKeyId = "89c3fb9a-de87-4f2a-aedc-fddc5138193c"
|
||||||
if err := c.Prepare(nil, nil); err == nil {
|
if err := c.Prepare(accessConf, nil); err == nil {
|
||||||
t.Fatal("shouldn't be able to share ami with encrypted boot volume")
|
t.Fatal("shouldn't be able to share ami with encrypted boot volume")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -187,8 +193,10 @@ func TestAMIConfigPrepare_Share_EncryptedBoot(t *testing.T) {
|
||||||
func TestAMINameValidation(t *testing.T) {
|
func TestAMINameValidation(t *testing.T) {
|
||||||
c := testAMIConfig()
|
c := testAMIConfig()
|
||||||
|
|
||||||
|
accessConf := getFakeAccessConfig("wherever")
|
||||||
|
|
||||||
c.AMIName = "aa"
|
c.AMIName = "aa"
|
||||||
if err := c.Prepare(nil, nil); err == nil {
|
if err := c.Prepare(accessConf, nil); err == nil {
|
||||||
t.Fatal("shouldn't be able to have an ami name with less than 3 characters")
|
t.Fatal("shouldn't be able to have an ami name with less than 3 characters")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,22 +205,22 @@ func TestAMINameValidation(t *testing.T) {
|
||||||
longAmiName += "a"
|
longAmiName += "a"
|
||||||
}
|
}
|
||||||
c.AMIName = longAmiName
|
c.AMIName = longAmiName
|
||||||
if err := c.Prepare(nil, nil); err == nil {
|
if err := c.Prepare(accessConf, nil); err == nil {
|
||||||
t.Fatal("shouldn't be able to have an ami name with great than 128 characters")
|
t.Fatal("shouldn't be able to have an ami name with great than 128 characters")
|
||||||
}
|
}
|
||||||
|
|
||||||
c.AMIName = "+aaa"
|
c.AMIName = "+aaa"
|
||||||
if err := c.Prepare(nil, nil); err == nil {
|
if err := c.Prepare(accessConf, nil); err == nil {
|
||||||
t.Fatal("shouldn't be able to have an ami name with invalid characters")
|
t.Fatal("shouldn't be able to have an ami name with invalid characters")
|
||||||
}
|
}
|
||||||
|
|
||||||
c.AMIName = "fooBAR1()[] ./-'@_"
|
c.AMIName = "fooBAR1()[] ./-'@_"
|
||||||
if err := c.Prepare(nil, nil); err != nil {
|
if err := c.Prepare(accessConf, nil); err != nil {
|
||||||
t.Fatal("should be able to use all of the allowed AMI characters")
|
t.Fatal("should be able to use all of the allowed AMI characters")
|
||||||
}
|
}
|
||||||
|
|
||||||
c.AMIName = `xyz-base-2017-04-05-1934`
|
c.AMIName = `xyz-base-2017-04-05-1934`
|
||||||
if err := c.Prepare(nil, nil); err != nil {
|
if err := c.Prepare(accessConf, nil); err != nil {
|
||||||
t.Fatalf("expected `xyz-base-2017-04-05-1934` to pass validation.")
|
t.Fatalf("expected `xyz-base-2017-04-05-1934` to pass validation.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,20 +2,9 @@ package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/aws/aws-sdk-go/aws/session"
|
|
||||||
"github.com/aws/aws-sdk-go/service/ec2"
|
|
||||||
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
|
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getValidationSession() *ec2.EC2 {
|
|
||||||
sess := session.Must(session.NewSessionWithOptions(session.Options{
|
|
||||||
SharedConfigState: session.SharedConfigEnable,
|
|
||||||
}))
|
|
||||||
|
|
||||||
ec2conn := ec2.New(sess)
|
|
||||||
return ec2conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func listEC2Regions(ec2conn ec2iface.EC2API) ([]string, error) {
|
func listEC2Regions(ec2conn ec2iface.EC2API) ([]string, error) {
|
||||||
var regions []string
|
var regions []string
|
||||||
resultRegions, err := ec2conn.DescribeRegions(nil)
|
resultRegions, err := ec2conn.DescribeRegions(nil)
|
||||||
|
|
|
@ -0,0 +1,212 @@
|
||||||
|
package awstesting
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Match is a testing helper to test for testing error by comparing expected
|
||||||
|
// with a regular expression.
|
||||||
|
func Match(t *testing.T, regex, expected string) {
|
||||||
|
if !regexp.MustCompile(regex).Match([]byte(expected)) {
|
||||||
|
t.Errorf("%q\n\tdoes not match /%s/", expected, regex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssertURL verifies the expected URL is matches the actual.
|
||||||
|
func AssertURL(t *testing.T, expect, actual string, msgAndArgs ...interface{}) bool {
|
||||||
|
expectURL, err := url.Parse(expect)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf(errMsg("unable to parse expected URL", err, msgAndArgs))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
actualURL, err := url.Parse(actual)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf(errMsg("unable to parse actual URL", err, msgAndArgs))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
equal(t, expectURL.Host, actualURL.Host, msgAndArgs...)
|
||||||
|
equal(t, expectURL.Scheme, actualURL.Scheme, msgAndArgs...)
|
||||||
|
equal(t, expectURL.Path, actualURL.Path, msgAndArgs...)
|
||||||
|
|
||||||
|
return AssertQuery(t, expectURL.Query().Encode(), actualURL.Query().Encode(), msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
var queryMapKey = regexp.MustCompile("(.*?)\\.[0-9]+\\.key")
|
||||||
|
|
||||||
|
// AssertQuery verifies the expect HTTP query string matches the actual.
|
||||||
|
func AssertQuery(t *testing.T, expect, actual string, msgAndArgs ...interface{}) bool {
|
||||||
|
expectQ, err := url.ParseQuery(expect)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf(errMsg("unable to parse expected Query", err, msgAndArgs))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
actualQ, err := url.ParseQuery(actual)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf(errMsg("unable to parse actual Query", err, msgAndArgs))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the keys are the same
|
||||||
|
if !equal(t, queryValueKeys(expectQ), queryValueKeys(actualQ), msgAndArgs...) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
keys := map[string][]string{}
|
||||||
|
for key, v := range expectQ {
|
||||||
|
if queryMapKey.Match([]byte(key)) {
|
||||||
|
submatch := queryMapKey.FindStringSubmatch(key)
|
||||||
|
keys[submatch[1]] = append(keys[submatch[1]], v...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range keys {
|
||||||
|
// clear all keys that have prefix
|
||||||
|
for key := range expectQ {
|
||||||
|
if strings.HasPrefix(key, k) {
|
||||||
|
delete(expectQ, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(v)
|
||||||
|
for i, value := range v {
|
||||||
|
expectQ[fmt.Sprintf("%s.%d.key", k, i+1)] = []string{value}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, expectQVals := range expectQ {
|
||||||
|
sort.Strings(expectQVals)
|
||||||
|
actualQVals := actualQ[k]
|
||||||
|
sort.Strings(actualQVals)
|
||||||
|
if !equal(t, expectQVals, actualQVals, msgAndArgs...) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssertJSON verifies that the expect json string matches the actual.
|
||||||
|
func AssertJSON(t *testing.T, expect, actual string, msgAndArgs ...interface{}) bool {
|
||||||
|
expectVal := map[string]interface{}{}
|
||||||
|
if err := json.Unmarshal([]byte(expect), &expectVal); err != nil {
|
||||||
|
t.Errorf(errMsg("unable to parse expected JSON", err, msgAndArgs...))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
actualVal := map[string]interface{}{}
|
||||||
|
if err := json.Unmarshal([]byte(actual), &actualVal); err != nil {
|
||||||
|
t.Errorf(errMsg("unable to parse actual JSON", err, msgAndArgs...))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return equal(t, expectVal, actualVal, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssertXML verifies that the expect xml string matches the actual.
|
||||||
|
func AssertXML(t *testing.T, expect, actual string, container interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
expectVal := container
|
||||||
|
if err := xml.Unmarshal([]byte(expect), &expectVal); err != nil {
|
||||||
|
t.Errorf(errMsg("unable to parse expected XML", err, msgAndArgs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
actualVal := container
|
||||||
|
if err := xml.Unmarshal([]byte(actual), &actualVal); err != nil {
|
||||||
|
t.Errorf(errMsg("unable to parse actual XML", err, msgAndArgs...))
|
||||||
|
}
|
||||||
|
return equal(t, expectVal, actualVal, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DidPanic returns if the function paniced and returns true if the function paniced.
|
||||||
|
func DidPanic(fn func()) (bool, interface{}) {
|
||||||
|
var paniced bool
|
||||||
|
var msg interface{}
|
||||||
|
func() {
|
||||||
|
defer func() {
|
||||||
|
if msg = recover(); msg != nil {
|
||||||
|
paniced = true
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
fn()
|
||||||
|
}()
|
||||||
|
|
||||||
|
return paniced, msg
|
||||||
|
}
|
||||||
|
|
||||||
|
// objectsAreEqual determines if two objects are considered equal.
|
||||||
|
//
|
||||||
|
// This function does no assertion of any kind.
|
||||||
|
//
|
||||||
|
// Based on github.com/stretchr/testify/assert.ObjectsAreEqual
|
||||||
|
// Copied locally to prevent non-test build dependencies on testify
|
||||||
|
func objectsAreEqual(expected, actual interface{}) bool {
|
||||||
|
if expected == nil || actual == nil {
|
||||||
|
return expected == actual
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflect.DeepEqual(expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal asserts that two objects are equal.
|
||||||
|
//
|
||||||
|
// assert.Equal(t, 123, 123, "123 and 123 should be equal")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
//
|
||||||
|
// Based on github.com/stretchr/testify/assert.Equal
|
||||||
|
// Copied locally to prevent non-test build dependencies on testify
|
||||||
|
func equal(t *testing.T, expected, actual interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
if !objectsAreEqual(expected, actual) {
|
||||||
|
t.Errorf("%s\n%s", messageFromMsgAndArgs(msgAndArgs),
|
||||||
|
SprintExpectActual(expected, actual))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func errMsg(baseMsg string, err error, msgAndArgs ...interface{}) string {
|
||||||
|
message := messageFromMsgAndArgs(msgAndArgs)
|
||||||
|
if message != "" {
|
||||||
|
message += ", "
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s%s, %v", message, baseMsg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Based on github.com/stretchr/testify/assert.messageFromMsgAndArgs
|
||||||
|
// Copied locally to prevent non-test build dependencies on testify
|
||||||
|
func messageFromMsgAndArgs(msgAndArgs []interface{}) string {
|
||||||
|
if len(msgAndArgs) == 0 || msgAndArgs == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if len(msgAndArgs) == 1 {
|
||||||
|
return msgAndArgs[0].(string)
|
||||||
|
}
|
||||||
|
if len(msgAndArgs) > 1 {
|
||||||
|
return fmt.Sprintf(msgAndArgs[0].(string), msgAndArgs[1:]...)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryValueKeys(v url.Values) []string {
|
||||||
|
keys := make([]string, 0, len(v))
|
||||||
|
for k := range v {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
// SprintExpectActual returns a string for test failure cases when the actual
|
||||||
|
// value is not the same as the expected.
|
||||||
|
func SprintExpectActual(expect, actual interface{}) string {
|
||||||
|
return fmt.Sprintf("expect: %+v\nactual: %+v\n", expect, actual)
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package awstesting
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/client"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/client/metadata"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/defaults"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewClient creates and initializes a generic service client for testing.
|
||||||
|
func NewClient(cfgs ...*aws.Config) *client.Client {
|
||||||
|
info := metadata.ClientInfo{
|
||||||
|
Endpoint: "http://endpoint",
|
||||||
|
SigningName: "",
|
||||||
|
}
|
||||||
|
def := defaults.Get()
|
||||||
|
def.Config.MergeIn(cfgs...)
|
||||||
|
|
||||||
|
if v := aws.StringValue(def.Config.Endpoint); len(v) > 0 {
|
||||||
|
info.Endpoint = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.New(*def.Config, info, def.Handlers)
|
||||||
|
}
|
|
@ -0,0 +1,199 @@
|
||||||
|
package awstesting
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func availableLocalAddr(ip string) (string, error) {
|
||||||
|
l, err := net.Listen("tcp", ip+":0")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
return l.Addr().String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTLSServer will create the TLS server on an open port using the
|
||||||
|
// certificate and key. The address will be returned that the server is running on.
|
||||||
|
func CreateTLSServer(cert, key string, mux *http.ServeMux) (string, error) {
|
||||||
|
addr, err := availableLocalAddr("127.0.0.1")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if mux == nil {
|
||||||
|
mux = http.NewServeMux()
|
||||||
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {})
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := http.ListenAndServeTLS(addr, cert, key, mux); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for i := 0; i < 60; i++ {
|
||||||
|
if _, err := http.Get("https://" + addr); err != nil && !strings.Contains(err.Error(), "connection refused") {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
return "https://" + addr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTLSBundleFiles returns the temporary filenames for the certificate
|
||||||
|
// key, and CA PEM content. These files should be deleted when no longer
|
||||||
|
// needed. CleanupTLSBundleFiles can be used for this cleanup.
|
||||||
|
func CreateTLSBundleFiles() (cert, key, ca string, err error) {
|
||||||
|
cert, err = createTmpFile(TLSBundleCert)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err = createTmpFile(TLSBundleKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
ca, err = createTmpFile(TLSBundleCA)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cert, key, ca, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanupTLSBundleFiles takes variadic list of files to be deleted.
|
||||||
|
func CleanupTLSBundleFiles(files ...string) error {
|
||||||
|
for _, file := range files {
|
||||||
|
if err := os.Remove(file); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTmpFile(b []byte) (string, error) {
|
||||||
|
bundleFile, err := ioutil.TempFile(os.TempDir(), "aws-sdk-go-session-test")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = bundleFile.Write(b)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer bundleFile.Close()
|
||||||
|
return bundleFile.Name(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cert generation steps
|
||||||
|
# Create the CA key
|
||||||
|
openssl genrsa -des3 -out ca.key 1024
|
||||||
|
|
||||||
|
# Create the CA Cert
|
||||||
|
openssl req -new -sha256 -x509 -days 3650 \
|
||||||
|
-subj "/C=GO/ST=Gopher/O=Testing ROOT CA" \
|
||||||
|
-key ca.key -out ca.crt
|
||||||
|
|
||||||
|
# Create config
|
||||||
|
cat > csr_details.txt <<-EOF
|
||||||
|
|
||||||
|
[req]
|
||||||
|
default_bits = 1024
|
||||||
|
prompt = no
|
||||||
|
default_md = sha256
|
||||||
|
req_extensions = SAN
|
||||||
|
distinguished_name = dn
|
||||||
|
|
||||||
|
[ dn ]
|
||||||
|
C=GO
|
||||||
|
ST=Gopher
|
||||||
|
O=Testing Certificate
|
||||||
|
OU=Testing IP
|
||||||
|
|
||||||
|
[SAN]
|
||||||
|
subjectAltName = IP:127.0.0.1
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Create certificate signing request
|
||||||
|
openssl req -new -sha256 -nodes -newkey rsa:1024 \
|
||||||
|
-config <( cat csr_details.txt ) \
|
||||||
|
-keyout ia.key -out ia.csr
|
||||||
|
|
||||||
|
# Create a signed certificate
|
||||||
|
openssl x509 -req -days 3650 \
|
||||||
|
-CAcreateserial \
|
||||||
|
-extfile <( cat csr_details.txt ) \
|
||||||
|
-extensions SAN \
|
||||||
|
-CA ca.crt -CAkey ca.key -in ia.csr -out ia.crt
|
||||||
|
|
||||||
|
# Verify
|
||||||
|
openssl req -noout -text -in ia.csr
|
||||||
|
openssl x509 -noout -text -in ia.crt
|
||||||
|
*/
|
||||||
|
var (
|
||||||
|
// TLSBundleCA ca.crt
|
||||||
|
TLSBundleCA = []byte(`-----BEGIN CERTIFICATE-----
|
||||||
|
MIICiTCCAfKgAwIBAgIJAJ5X1olt05XjMA0GCSqGSIb3DQEBCwUAMDgxCzAJBgNV
|
||||||
|
BAYTAkdPMQ8wDQYDVQQIEwZHb3BoZXIxGDAWBgNVBAoTD1Rlc3RpbmcgUk9PVCBD
|
||||||
|
QTAeFw0xNzAzMDkwMDAyMDZaFw0yNzAzMDcwMDAyMDZaMDgxCzAJBgNVBAYTAkdP
|
||||||
|
MQ8wDQYDVQQIEwZHb3BoZXIxGDAWBgNVBAoTD1Rlc3RpbmcgUk9PVCBDQTCBnzAN
|
||||||
|
BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAw/8DN+t9XQR60jx42rsQ2WE2Dx85rb3n
|
||||||
|
GQxnKZZLNddsT8rDyxJNP18aFalbRbFlyln5fxWxZIblu9Xkm/HRhOpbSimSqo1y
|
||||||
|
uDx21NVZ1YsOvXpHby71jx3gPrrhSc/t/zikhi++6D/C6m1CiIGuiJ0GBiJxtrub
|
||||||
|
UBMXT0QtI2ECAwEAAaOBmjCBlzAdBgNVHQ4EFgQU8XG3X/YHBA6T04kdEkq6+4GV
|
||||||
|
YykwaAYDVR0jBGEwX4AU8XG3X/YHBA6T04kdEkq6+4GVYymhPKQ6MDgxCzAJBgNV
|
||||||
|
BAYTAkdPMQ8wDQYDVQQIEwZHb3BoZXIxGDAWBgNVBAoTD1Rlc3RpbmcgUk9PVCBD
|
||||||
|
QYIJAJ5X1olt05XjMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADgYEAeILv
|
||||||
|
z49+uxmPcfOZzonuOloRcpdvyjiXblYxbzz6ch8GsE7Q886FTZbvwbgLhzdwSVgG
|
||||||
|
G8WHkodDUsymVepdqAamS3f8PdCUk8xIk9mop8LgaB9Ns0/TssxDvMr3sOD2Grb3
|
||||||
|
xyWymTWMcj6uCiEBKtnUp4rPiefcvCRYZ17/hLE=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
`)
|
||||||
|
|
||||||
|
// TLSBundleCert ai.crt
|
||||||
|
TLSBundleCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||||
|
MIICGjCCAYOgAwIBAgIJAIIu+NOoxxM0MA0GCSqGSIb3DQEBBQUAMDgxCzAJBgNV
|
||||||
|
BAYTAkdPMQ8wDQYDVQQIEwZHb3BoZXIxGDAWBgNVBAoTD1Rlc3RpbmcgUk9PVCBD
|
||||||
|
QTAeFw0xNzAzMDkwMDAzMTRaFw0yNzAzMDcwMDAzMTRaMFExCzAJBgNVBAYTAkdP
|
||||||
|
MQ8wDQYDVQQIDAZHb3BoZXIxHDAaBgNVBAoME1Rlc3RpbmcgQ2VydGlmaWNhdGUx
|
||||||
|
EzARBgNVBAsMClRlc3RpbmcgSVAwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGB
|
||||||
|
AN1hWHeioo/nASvbrjwCQzXCiWiEzGkw353NxsAB54/NqDL3LXNATtiSJu8kJBrm
|
||||||
|
Ah12IFLtWLGXjGjjYlHbQWnOR6awveeXnQZukJyRWh7m/Qlt9Ho0CgZE1U+832ac
|
||||||
|
5GWVldNxW1Lz4I+W9/ehzqe8I80RS6eLEKfUFXGiW+9RAgMBAAGjEzARMA8GA1Ud
|
||||||
|
EQQIMAaHBH8AAAEwDQYJKoZIhvcNAQEFBQADgYEAdF4WQHfVdPCbgv9sxgJjcR1H
|
||||||
|
Hgw9rZ47gO1IiIhzglnLXQ6QuemRiHeYFg4kjcYBk1DJguxzDTGnUwhUXOibAB+S
|
||||||
|
zssmrkdYYvn9aUhjc3XK3tjAoDpsPpeBeTBamuUKDHoH/dNRXxerZ8vu6uPR3Pgs
|
||||||
|
5v/KCV6IAEcvNyOXMPo=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
`)
|
||||||
|
|
||||||
|
// TLSBundleKey ai.key
|
||||||
|
TLSBundleKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIICXAIBAAKBgQDdYVh3oqKP5wEr2648AkM1wolohMxpMN+dzcbAAeePzagy9y1z
|
||||||
|
QE7YkibvJCQa5gIddiBS7Vixl4xo42JR20FpzkemsL3nl50GbpCckVoe5v0JbfR6
|
||||||
|
NAoGRNVPvN9mnORllZXTcVtS8+CPlvf3oc6nvCPNEUunixCn1BVxolvvUQIDAQAB
|
||||||
|
AoGBAMISrcirddGrlLZLLrKC1ULS2T0cdkqdQtwHYn4+7S5+/z42vMx1iumHLsSk
|
||||||
|
rVY7X41OWkX4trFxhvEIrc/O48bo2zw78P7flTxHy14uxXnllU8cLThE29SlUU7j
|
||||||
|
AVBNxJZMsXMlS/DowwD4CjFe+x4Pu9wZcReF2Z9ntzMpySABAkEA+iWoJCPE2JpS
|
||||||
|
y78q3HYYgpNY3gF3JqQ0SI/zTNkb3YyEIUffEYq0Y9pK13HjKtdsSuX4osTIhQkS
|
||||||
|
+UgRp6tCAQJBAOKPYTfQ2FX8ijgUpHZRuEAVaxASAS0UATiLgzXxLvOh/VC2at5x
|
||||||
|
wjOX6sD65pPz/0D8Qj52Cq6Q1TQ+377SDVECQAIy0od+yPweXxvrUjUd1JlRMjbB
|
||||||
|
TIrKZqs8mKbUQapw0bh5KTy+O1elU4MRPS3jNtBxtP25PQnuSnxmZcFTgAECQFzg
|
||||||
|
DiiFcsn9FuRagfkHExMiNJuH5feGxeFaP9WzI144v9GAllrOI6Bm3JNzx2ZLlg4b
|
||||||
|
20Qju8lIEj6yr6JYFaECQHM1VSojGRKpOl9Ox/R4yYSA9RV5Gyn00/aJNxVYyPD5
|
||||||
|
i3acL2joQm2kLD/LO8paJ4+iQdRXCOMMIpjxSNjGQjQ=
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
|
`)
|
||||||
|
)
|
|
@ -0,0 +1,74 @@
|
||||||
|
package mock
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rsa"
|
||||||
|
"math/big"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/client"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/client/metadata"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/session"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Session is a mock session which is used to hit the mock server
|
||||||
|
var Session = func() *session.Session {
|
||||||
|
// server is the mock server that simply writes a 200 status back to the client
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}))
|
||||||
|
|
||||||
|
return session.Must(session.NewSession(&aws.Config{
|
||||||
|
DisableSSL: aws.Bool(true),
|
||||||
|
Endpoint: aws.String(server.URL),
|
||||||
|
}))
|
||||||
|
}()
|
||||||
|
|
||||||
|
// NewMockClient creates and initializes a client that will connect to the
|
||||||
|
// mock server
|
||||||
|
func NewMockClient(cfgs ...*aws.Config) *client.Client {
|
||||||
|
c := Session.ClientConfig("Mock", cfgs...)
|
||||||
|
|
||||||
|
svc := client.New(
|
||||||
|
*c.Config,
|
||||||
|
metadata.ClientInfo{
|
||||||
|
ServiceName: "Mock",
|
||||||
|
SigningRegion: c.SigningRegion,
|
||||||
|
Endpoint: c.Endpoint,
|
||||||
|
APIVersion: "2015-12-08",
|
||||||
|
JSONVersion: "1.1",
|
||||||
|
TargetPrefix: "MockServer",
|
||||||
|
},
|
||||||
|
c.Handlers,
|
||||||
|
)
|
||||||
|
|
||||||
|
return svc
|
||||||
|
}
|
||||||
|
|
||||||
|
// RSAPrivateKey is used for testing functionality that requires some
|
||||||
|
// sort of private key. Taken from crypto/rsa/rsa_test.go
|
||||||
|
//
|
||||||
|
// Credit to golang 1.11
|
||||||
|
var RSAPrivateKey = &rsa.PrivateKey{
|
||||||
|
PublicKey: rsa.PublicKey{
|
||||||
|
N: fromBase10("14314132931241006650998084889274020608918049032671858325988396851334124245188214251956198731333464217832226406088020736932173064754214329009979944037640912127943488972644697423190955557435910767690712778463524983667852819010259499695177313115447116110358524558307947613422897787329221478860907963827160223559690523660574329011927531289655711860504630573766609239332569210831325633840174683944553667352219670930408593321661375473885147973879086994006440025257225431977751512374815915392249179976902953721486040787792801849818254465486633791826766873076617116727073077821584676715609985777563958286637185868165868520557"),
|
||||||
|
E: 3,
|
||||||
|
},
|
||||||
|
D: fromBase10("9542755287494004433998723259516013739278699355114572217325597900889416163458809501304132487555642811888150937392013824621448709836142886006653296025093941418628992648429798282127303704957273845127141852309016655778568546006839666463451542076964744073572349705538631742281931858219480985907271975884773482372966847639853897890615456605598071088189838676728836833012254065983259638538107719766738032720239892094196108713378822882383694456030043492571063441943847195939549773271694647657549658603365629458610273821292232646334717612674519997533901052790334279661754176490593041941863932308687197618671528035670452762731"),
|
||||||
|
Primes: []*big.Int{
|
||||||
|
fromBase10("130903255182996722426771613606077755295583329135067340152947172868415809027537376306193179624298874215608270802054347609836776473930072411958753044562214537013874103802006369634761074377213995983876788718033850153719421695468704276694983032644416930879093914927146648402139231293035971427838068945045019075433"),
|
||||||
|
fromBase10("109348945610485453577574767652527472924289229538286649661240938988020367005475727988253438647560958573506159449538793540472829815903949343191091817779240101054552748665267574271163617694640513549693841337820602726596756351006149518830932261246698766355347898158548465400674856021497190430791824869615170301029"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Taken from crypto/rsa/rsa_test.go
|
||||||
|
//
|
||||||
|
// Credit to golang 1.11
|
||||||
|
func fromBase10(base10 string) *big.Int {
|
||||||
|
i, ok := new(big.Int).SetString(base10, 10)
|
||||||
|
if !ok {
|
||||||
|
panic("bad number: " + base10)
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
package awstesting
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/private/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ZeroReader is a io.Reader which will always write zeros to the byte slice provided.
|
||||||
|
type ZeroReader struct{}
|
||||||
|
|
||||||
|
// Read fills the provided byte slice with zeros returning the number of bytes written.
|
||||||
|
func (r *ZeroReader) Read(b []byte) (int, error) {
|
||||||
|
for i := 0; i < len(b); i++ {
|
||||||
|
b[i] = 0
|
||||||
|
}
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadCloser is a io.ReadCloser for unit testing.
|
||||||
|
// Designed to test for leaks and whether a handle has
|
||||||
|
// been closed
|
||||||
|
type ReadCloser struct {
|
||||||
|
Size int
|
||||||
|
Closed bool
|
||||||
|
set bool
|
||||||
|
FillData func(bool, []byte, int, int)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read will call FillData and fill it with whatever data needed.
|
||||||
|
// Decrements the size until zero, then return io.EOF.
|
||||||
|
func (r *ReadCloser) Read(b []byte) (int, error) {
|
||||||
|
if r.Closed {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
delta := len(b)
|
||||||
|
if delta > r.Size {
|
||||||
|
delta = r.Size
|
||||||
|
}
|
||||||
|
r.Size -= delta
|
||||||
|
|
||||||
|
for i := 0; i < delta; i++ {
|
||||||
|
b[i] = 'a'
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.FillData != nil {
|
||||||
|
r.FillData(r.set, b, r.Size, delta)
|
||||||
|
}
|
||||||
|
r.set = true
|
||||||
|
|
||||||
|
if r.Size > 0 {
|
||||||
|
return delta, nil
|
||||||
|
}
|
||||||
|
return delta, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close sets Closed to true and returns no error
|
||||||
|
func (r *ReadCloser) Close() error {
|
||||||
|
r.Closed = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SortedKeys returns a sorted slice of keys of a map.
|
||||||
|
func SortedKeys(m map[string]interface{}) []string {
|
||||||
|
return util.SortedKeys(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A FakeContext provides a simple stub implementation of a Context
|
||||||
|
type FakeContext struct {
|
||||||
|
Error error
|
||||||
|
DoneCh chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deadline always will return not set
|
||||||
|
func (c *FakeContext) Deadline() (deadline time.Time, ok bool) {
|
||||||
|
return time.Time{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Done returns a read channel for listening to the Done event
|
||||||
|
func (c *FakeContext) Done() <-chan struct{} {
|
||||||
|
return c.DoneCh
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err returns the error, is nil if not set.
|
||||||
|
func (c *FakeContext) Err() error {
|
||||||
|
return c.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value ignores the Value and always returns nil
|
||||||
|
func (c *FakeContext) Value(key interface{}) interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StashEnv stashes the current environment variables and returns an array of
|
||||||
|
// all environment values as key=val strings.
|
||||||
|
func StashEnv() []string {
|
||||||
|
env := os.Environ()
|
||||||
|
os.Clearenv()
|
||||||
|
|
||||||
|
return env
|
||||||
|
}
|
||||||
|
|
||||||
|
// PopEnv takes the list of the environment values and injects them into the
|
||||||
|
// process's environment variable data. Clears any existing environment values
|
||||||
|
// that may already exist.
|
||||||
|
func PopEnv(env []string) {
|
||||||
|
os.Clearenv()
|
||||||
|
|
||||||
|
for _, e := range env {
|
||||||
|
p := strings.SplitN(e, "=", 2)
|
||||||
|
k, v := p[0], ""
|
||||||
|
if len(p) > 1 {
|
||||||
|
v = p[1]
|
||||||
|
}
|
||||||
|
os.Setenv(k, v)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
import "sort"
|
||||||
|
|
||||||
|
// SortedKeys returns a sorted slice of keys of a map.
|
||||||
|
func SortedKeys(m map[string]interface{}) []string {
|
||||||
|
i, sorted := 0, make([]string, len(m))
|
||||||
|
for k := range m {
|
||||||
|
sorted[i] = k
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
sort.Strings(sorted)
|
||||||
|
return sorted
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"go/format"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GoFmt returns the Go formated string of the input.
|
||||||
|
//
|
||||||
|
// Panics if the format fails.
|
||||||
|
func GoFmt(buf string) string {
|
||||||
|
formatted, err := format.Source([]byte(buf))
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("%s\nOriginal code:\n%s", err.Error(), buf))
|
||||||
|
}
|
||||||
|
return string(formatted)
|
||||||
|
}
|
||||||
|
|
||||||
|
var reTrim = regexp.MustCompile(`\s{2,}`)
|
||||||
|
|
||||||
|
// Trim removes all leading and trailing white space.
|
||||||
|
//
|
||||||
|
// All consecutive spaces will be reduced to a single space.
|
||||||
|
func Trim(s string) string {
|
||||||
|
return strings.TrimSpace(reTrim.ReplaceAllString(s, " "))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capitalize capitalizes the first character of the string.
|
||||||
|
func Capitalize(s string) string {
|
||||||
|
if len(s) == 1 {
|
||||||
|
return strings.ToUpper(s)
|
||||||
|
}
|
||||||
|
return strings.ToUpper(s[0:1]) + s[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// SortXML sorts the reader's XML elements
|
||||||
|
func SortXML(r io.Reader) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
d := xml.NewDecoder(r)
|
||||||
|
root, _ := xmlutil.XMLToStruct(d, nil)
|
||||||
|
e := xml.NewEncoder(&buf)
|
||||||
|
xmlutil.StructToXML(e, root, true)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrettyPrint generates a human readable representation of the value v.
|
||||||
|
// All values of v are recursively found and pretty printed also.
|
||||||
|
func PrettyPrint(v interface{}) string {
|
||||||
|
value := reflect.ValueOf(v)
|
||||||
|
switch value.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
str := fullName(value.Type()) + "{\n"
|
||||||
|
for i := 0; i < value.NumField(); i++ {
|
||||||
|
l := string(value.Type().Field(i).Name[0])
|
||||||
|
if strings.ToUpper(l) == l {
|
||||||
|
str += value.Type().Field(i).Name + ": "
|
||||||
|
str += PrettyPrint(value.Field(i).Interface())
|
||||||
|
str += ",\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
str += "}"
|
||||||
|
return str
|
||||||
|
case reflect.Map:
|
||||||
|
str := "map[" + fullName(value.Type().Key()) + "]" + fullName(value.Type().Elem()) + "{\n"
|
||||||
|
for _, k := range value.MapKeys() {
|
||||||
|
str += "\"" + k.String() + "\": "
|
||||||
|
str += PrettyPrint(value.MapIndex(k).Interface())
|
||||||
|
str += ",\n"
|
||||||
|
}
|
||||||
|
str += "}"
|
||||||
|
return str
|
||||||
|
case reflect.Ptr:
|
||||||
|
if e := value.Elem(); e.IsValid() {
|
||||||
|
return "&" + PrettyPrint(e.Interface())
|
||||||
|
}
|
||||||
|
return "nil"
|
||||||
|
case reflect.Slice:
|
||||||
|
str := "[]" + fullName(value.Type().Elem()) + "{\n"
|
||||||
|
for i := 0; i < value.Len(); i++ {
|
||||||
|
str += PrettyPrint(value.Index(i).Interface())
|
||||||
|
str += ",\n"
|
||||||
|
}
|
||||||
|
str += "}"
|
||||||
|
return str
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("%#v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pkgName(t reflect.Type) string {
|
||||||
|
pkg := t.PkgPath()
|
||||||
|
c := strings.Split(pkg, "/")
|
||||||
|
return c[len(c)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func fullName(t reflect.Type) string {
|
||||||
|
if pkg := pkgName(t); pkg != "" {
|
||||||
|
return pkg + "." + t.Name()
|
||||||
|
}
|
||||||
|
return t.Name()
|
||||||
|
}
|
|
@ -433,6 +433,22 @@
|
||||||
"version": "v1.15.54",
|
"version": "v1.15.54",
|
||||||
"versionExact": "v1.15.54"
|
"versionExact": "v1.15.54"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "MxSiiCoPpY1mCRjyEFClUu3e14w=",
|
||||||
|
"path": "github.com/aws/aws-sdk-go/awstesting",
|
||||||
|
"revision": "bf8067ceb6e7f51e150c218972dccfeeed892b85",
|
||||||
|
"revisionTime": "2018-10-12T21:50:02Z",
|
||||||
|
"version": "v1.15.54",
|
||||||
|
"versionExact": "v1.15.54"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "Til1RNskoxuni9LNTwJWzm1GKrU=",
|
||||||
|
"path": "github.com/aws/aws-sdk-go/awstesting/mock",
|
||||||
|
"revision": "bf8067ceb6e7f51e150c218972dccfeeed892b85",
|
||||||
|
"revisionTime": "2018-10-12T21:50:02Z",
|
||||||
|
"version": "v1.15.54",
|
||||||
|
"versionExact": "v1.15.54"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "QvKGojx+wCHTDfXQ1aoOYzH3Y88=",
|
"checksumSHA1": "QvKGojx+wCHTDfXQ1aoOYzH3Y88=",
|
||||||
"path": "github.com/aws/aws-sdk-go/internal/s3err",
|
"path": "github.com/aws/aws-sdk-go/internal/s3err",
|
||||||
|
@ -570,6 +586,14 @@
|
||||||
"version": "v1.15.54",
|
"version": "v1.15.54",
|
||||||
"versionExact": "v1.15.54"
|
"versionExact": "v1.15.54"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "01b4hmyUzoReoOyEDylDinWBSdA=",
|
||||||
|
"path": "github.com/aws/aws-sdk-go/private/util",
|
||||||
|
"revision": "bf8067ceb6e7f51e150c218972dccfeeed892b85",
|
||||||
|
"revisionTime": "2018-10-12T21:50:02Z",
|
||||||
|
"version": "v1.15.54",
|
||||||
|
"versionExact": "v1.15.54"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "Eo9yODN5U99BK0pMzoqnBm7PCrY=",
|
"checksumSHA1": "Eo9yODN5U99BK0pMzoqnBm7PCrY=",
|
||||||
"comment": "v1.7.1",
|
"comment": "v1.7.1",
|
||||||
|
|
Loading…
Reference in New Issue