浏览代码

cmd/devp2p: use AWS-SDK v2 (#22360)

This updates the DNS deployer to use AWS SDK v2. Migration is relatively
seamless, although there were two locations that required a slightly
different approach to achieve the same results. In particular, waiting for
DNS change propagation is very different with SDK v2. 

This change also optimizes DNS updates by publishing all changes before
waiting for propagation.
Quest Henkart 4 年之前
父节点
当前提交
e3a3f7cd64
共有 4 个文件被更改,包括 153 次插入87 次删除
  1. 87 49
      cmd/devp2p/dns_route53.go
  2. 36 36
      cmd/devp2p/dns_route53_test.go
  3. 5 2
      go.mod
  4. 25 0
      go.sum

+ 87 - 49
cmd/devp2p/dns_route53.go

@@ -17,16 +17,19 @@
 package main
 
 import (
+	"context"
 	"errors"
 	"fmt"
 	"sort"
 	"strconv"
 	"strings"
+	"time"
 
-	"github.com/aws/aws-sdk-go/aws"
-	"github.com/aws/aws-sdk-go/aws/credentials"
-	"github.com/aws/aws-sdk-go/aws/session"
-	"github.com/aws/aws-sdk-go/service/route53"
+	"github.com/aws/aws-sdk-go-v2/aws"
+	"github.com/aws/aws-sdk-go-v2/config"
+	"github.com/aws/aws-sdk-go-v2/credentials"
+	"github.com/aws/aws-sdk-go-v2/service/route53"
+	"github.com/aws/aws-sdk-go-v2/service/route53/types"
 	"github.com/ethereum/go-ethereum/log"
 	"github.com/ethereum/go-ethereum/p2p/dnsdisc"
 	"gopkg.in/urfave/cli.v1"
@@ -38,6 +41,7 @@ const (
 	// https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DNSLimitations.html#limits-api-requests-changeresourcerecordsets
 	route53ChangeSizeLimit  = 32000
 	route53ChangeCountLimit = 1000
+	maxRetryLimit           = 60
 )
 
 var (
@@ -58,7 +62,7 @@ var (
 )
 
 type route53Client struct {
-	api    *route53.Route53
+	api    *route53.Client
 	zoneID string
 }
 
@@ -74,13 +78,13 @@ func newRoute53Client(ctx *cli.Context) *route53Client {
 	if akey == "" || asec == "" {
 		exit(fmt.Errorf("need Route53 Access Key ID and secret proceed"))
 	}
-	config := &aws.Config{Credentials: credentials.NewStaticCredentials(akey, asec, "")}
-	session, err := session.NewSession(config)
+	creds := aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider(akey, asec, ""))
+	cfg, err := config.LoadDefaultConfig(context.Background(), config.WithCredentialsProvider(creds))
 	if err != nil {
-		exit(fmt.Errorf("can't create AWS session: %v", err))
+		exit(fmt.Errorf("can't initialize AWS configuration: %v", err))
 	}
 	return &route53Client{
-		api:    route53.New(session),
+		api:    route53.NewFromConfig(cfg),
 		zoneID: ctx.String(route53ZoneIDFlag.Name),
 	}
 }
@@ -105,25 +109,43 @@ func (c *route53Client) deploy(name string, t *dnsdisc.Tree) error {
 		return nil
 	}
 
-	// Submit change batches.
+	// Submit all change batches.
 	batches := splitChanges(changes, route53ChangeSizeLimit, route53ChangeCountLimit)
+	changesToCheck := make([]*route53.ChangeResourceRecordSetsOutput, len(batches))
 	for i, changes := range batches {
 		log.Info(fmt.Sprintf("Submitting %d changes to Route53", len(changes)))
-		batch := new(route53.ChangeBatch)
-		batch.SetChanges(changes)
-		batch.SetComment(fmt.Sprintf("enrtree update %d/%d of %s at seq %d", i+1, len(batches), name, t.Seq()))
+		batch := &types.ChangeBatch{
+			Changes: changes,
+			Comment: aws.String(fmt.Sprintf("enrtree update %d/%d of %s at seq %d", i+1, len(batches), name, t.Seq())),
+		}
 		req := &route53.ChangeResourceRecordSetsInput{HostedZoneId: &c.zoneID, ChangeBatch: batch}
-		resp, err := c.api.ChangeResourceRecordSets(req)
+		changesToCheck[i], err = c.api.ChangeResourceRecordSets(context.TODO(), req)
 		if err != nil {
 			return err
 		}
+	}
 
-		log.Info(fmt.Sprintf("Waiting for change request %s", *resp.ChangeInfo.Id))
-		wreq := &route53.GetChangeInput{Id: resp.ChangeInfo.Id}
-		if err := c.api.WaitUntilResourceRecordSetsChanged(wreq); err != nil {
-			return err
+	// wait for all change batches to propagate
+	for _, change := range changesToCheck {
+		log.Info(fmt.Sprintf("Waiting for change request %s", *change.ChangeInfo.Id))
+		wreq := &route53.GetChangeInput{Id: change.ChangeInfo.Id}
+		var count int
+		for {
+			wresp, err := c.api.GetChange(context.TODO(), wreq)
+			if err != nil {
+				return err
+			}
+
+			count++
+
+			if wresp.ChangeInfo.Status == types.ChangeStatusInsync || count >= maxRetryLimit {
+				break
+			}
+
+			time.Sleep(30 * time.Second)
 		}
 	}
+
 	return nil
 }
 
@@ -140,7 +162,7 @@ func (c *route53Client) findZoneID(name string) (string, error) {
 	log.Info(fmt.Sprintf("Finding Route53 Zone ID for %s", name))
 	var req route53.ListHostedZonesByNameInput
 	for {
-		resp, err := c.api.ListHostedZonesByName(&req)
+		resp, err := c.api.ListHostedZonesByName(context.TODO(), &req)
 		if err != nil {
 			return "", err
 		}
@@ -149,7 +171,7 @@ func (c *route53Client) findZoneID(name string) (string, error) {
 				return *zone.Id, nil
 			}
 		}
-		if !*resp.IsTruncated {
+		if !resp.IsTruncated {
 			break
 		}
 		req.DNSName = resp.NextDNSName
@@ -159,7 +181,7 @@ func (c *route53Client) findZoneID(name string) (string, error) {
 }
 
 // computeChanges creates DNS changes for the given record.
-func (c *route53Client) computeChanges(name string, records map[string]string, existing map[string]recordSet) []*route53.Change {
+func (c *route53Client) computeChanges(name string, records map[string]string, existing map[string]recordSet) []types.Change {
 	// Convert all names to lowercase.
 	lrecords := make(map[string]string, len(records))
 	for name, r := range records {
@@ -167,7 +189,7 @@ func (c *route53Client) computeChanges(name string, records map[string]string, e
 	}
 	records = lrecords
 
-	var changes []*route53.Change
+	var changes []types.Change
 	for path, val := range records {
 		ttl := int64(rootTTL)
 		if path != name {
@@ -204,21 +226,21 @@ func (c *route53Client) computeChanges(name string, records map[string]string, e
 }
 
 // sortChanges ensures DNS changes are in leaf-added -> root-changed -> leaf-deleted order.
-func sortChanges(changes []*route53.Change) {
+func sortChanges(changes []types.Change) {
 	score := map[string]int{"CREATE": 1, "UPSERT": 2, "DELETE": 3}
 	sort.Slice(changes, func(i, j int) bool {
-		if *changes[i].Action == *changes[j].Action {
+		if changes[i].Action == changes[j].Action {
 			return *changes[i].ResourceRecordSet.Name < *changes[j].ResourceRecordSet.Name
 		}
-		return score[*changes[i].Action] < score[*changes[j].Action]
+		return score[string(changes[i].Action)] < score[string(changes[j].Action)]
 	})
 }
 
 // splitChanges splits up DNS changes such that each change batch
 // is smaller than the given RDATA limit.
-func splitChanges(changes []*route53.Change, sizeLimit, countLimit int) [][]*route53.Change {
+func splitChanges(changes []types.Change, sizeLimit, countLimit int) [][]types.Change {
 	var (
-		batches    [][]*route53.Change
+		batches    [][]types.Change
 		batchSize  int
 		batchCount int
 	)
@@ -241,7 +263,7 @@ func splitChanges(changes []*route53.Change, sizeLimit, countLimit int) [][]*rou
 }
 
 // changeSize returns the RDATA size of a DNS change.
-func changeSize(ch *route53.Change) int {
+func changeSize(ch types.Change) int {
 	size := 0
 	for _, rr := range ch.ResourceRecordSet.ResourceRecords {
 		if rr.Value != nil {
@@ -251,8 +273,8 @@ func changeSize(ch *route53.Change) int {
 	return size
 }
 
-func changeCount(ch *route53.Change) int {
-	if *ch.Action == "UPSERT" {
+func changeCount(ch types.Change) int {
+	if ch.Action == types.ChangeActionUpsert {
 		return 2
 	}
 	return 1
@@ -262,13 +284,19 @@ func changeCount(ch *route53.Change) int {
 func (c *route53Client) collectRecords(name string) (map[string]recordSet, error) {
 	log.Info(fmt.Sprintf("Retrieving existing TXT records on %s (%s)", name, c.zoneID))
 	var req route53.ListResourceRecordSetsInput
-	req.SetHostedZoneId(c.zoneID)
+	req.HostedZoneId = &c.zoneID
 	existing := make(map[string]recordSet)
-	err := c.api.ListResourceRecordSetsPages(&req, func(resp *route53.ListResourceRecordSetsOutput, last bool) bool {
+	for {
+		resp, err := c.api.ListResourceRecordSets(context.TODO(), &req)
+		if err != nil {
+			return existing, err
+		}
+
 		for _, set := range resp.ResourceRecordSets {
-			if !isSubdomain(*set.Name, name) || *set.Type != "TXT" {
+			if !isSubdomain(*set.Name, name) || set.Type != types.RRTypeTxt {
 				continue
 			}
+
 			s := recordSet{ttl: *set.TTL}
 			for _, rec := range set.ResourceRecords {
 				s.values = append(s.values, *rec.Value)
@@ -276,28 +304,38 @@ func (c *route53Client) collectRecords(name string) (map[string]recordSet, error
 			name := strings.TrimSuffix(*set.Name, ".")
 			existing[name] = s
 		}
-		return true
-	})
-	return existing, err
+
+		if !resp.IsTruncated {
+			break
+		}
+
+		// sets the cursor to the next batch
+		req.StartRecordIdentifier = resp.NextRecordIdentifier
+	}
+
+	return existing, nil
 }
 
 // newTXTChange creates a change to a TXT record.
-func newTXTChange(action, name string, ttl int64, values ...string) *route53.Change {
-	var c route53.Change
-	var r route53.ResourceRecordSet
-	var rrs []*route53.ResourceRecord
+func newTXTChange(action, name string, ttl int64, values ...string) types.Change {
+	r := types.ResourceRecordSet{
+		Type: types.RRTypeTxt,
+		Name: &name,
+		TTL:  &ttl,
+	}
+	var rrs []types.ResourceRecord
 	for _, val := range values {
-		rr := new(route53.ResourceRecord)
-		rr.SetValue(val)
+		var rr types.ResourceRecord
+		rr.Value = aws.String(val)
 		rrs = append(rrs, rr)
 	}
-	r.SetType("TXT")
-	r.SetName(name)
-	r.SetTTL(ttl)
-	r.SetResourceRecords(rrs)
-	c.SetAction(action)
-	c.SetResourceRecordSet(&r)
-	return &c
+
+	r.ResourceRecords = rrs
+
+	return types.Change{
+		Action:            types.ChangeAction(action),
+		ResourceRecordSet: &r,
+	}
 }
 
 // isSubdomain returns true if name is a subdomain of domain.

+ 36 - 36
cmd/devp2p/dns_route53_test.go

@@ -20,7 +20,7 @@ import (
 	"reflect"
 	"testing"
 
-	"github.com/aws/aws-sdk-go/service/route53"
+	"github.com/aws/aws-sdk-go-v2/service/route53/types"
 )
 
 // This test checks that computeChanges/splitChanges create DNS changes in
@@ -43,93 +43,93 @@ func TestRoute53ChangeSort(t *testing.T) {
 		"MHTDO6TMUBRIA2XWG5LUDACK24.n": "enr:-HW4QLAYqmrwllBEnzWWs7I5Ev2IAs7x_dZlbYdRdMUx5EyKHDXp7AV5CkuPGUPdvbv1_Ms1CPfhcGCvSElSosZmyoqAgmlkgnY0iXNlY3AyNTZrMaECriawHKWdDRk2xeZkrOXBQ0dfMFLHY4eENZwdufn1S1o",
 	}
 
-	wantChanges := []*route53.Change{
+	wantChanges := []types.Change{
 		{
-			Action: sp("CREATE"),
-			ResourceRecordSet: &route53.ResourceRecordSet{
+			Action: "CREATE",
+			ResourceRecordSet: &types.ResourceRecordSet{
 				Name: sp("2xs2367yhaxjfglzhvawlqd4zy.n"),
-				ResourceRecords: []*route53.ResourceRecord{{
+				ResourceRecords: []types.ResourceRecord{{
 					Value: sp(`"enr:-HW4QOFzoVLaFJnNhbgMoDXPnOvcdVuj7pDpqRvh6BRDO68aVi5ZcjB3vzQRZH2IcLBGHzo8uUN3snqmgTiE56CH3AMBgmlkgnY0iXNlY3AyNTZrMaECC2_24YYkYHEgdzxlSNKQEnHhuNAbNlMlWJxrJxbAFvA"`),
 				}},
 				TTL:  ip(treeNodeTTL),
-				Type: sp("TXT"),
+				Type: "TXT",
 			},
 		},
 		{
-			Action: sp("CREATE"),
-			ResourceRecordSet: &route53.ResourceRecordSet{
+			Action: "CREATE",
+			ResourceRecordSet: &types.ResourceRecordSet{
 				Name: sp("c7hrfpf3blgf3yr4dy5kx3smbe.n"),
-				ResourceRecords: []*route53.ResourceRecord{{
+				ResourceRecords: []types.ResourceRecord{{
 					Value: sp(`"enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org"`),
 				}},
 				TTL:  ip(treeNodeTTL),
-				Type: sp("TXT"),
+				Type: "TXT",
 			},
 		},
 		{
-			Action: sp("CREATE"),
-			ResourceRecordSet: &route53.ResourceRecordSet{
+			Action: "CREATE",
+			ResourceRecordSet: &types.ResourceRecordSet{
 				Name: sp("h4fht4b454p6uxfd7jcyq5pwdy.n"),
-				ResourceRecords: []*route53.ResourceRecord{{
+				ResourceRecords: []types.ResourceRecord{{
 					Value: sp(`"enr:-HW4QAggRauloj2SDLtIHN1XBkvhFZ1vtf1raYQp9TBW2RD5EEawDzbtSmlXUfnaHcvwOizhVYLtr7e6vw7NAf6mTuoCgmlkgnY0iXNlY3AyNTZrMaECjrXI8TLNXU0f8cthpAMxEshUyQlK-AM0PW2wfrnacNI"`),
 				}},
 				TTL:  ip(treeNodeTTL),
-				Type: sp("TXT"),
+				Type: "TXT",
 			},
 		},
 		{
-			Action: sp("CREATE"),
-			ResourceRecordSet: &route53.ResourceRecordSet{
+			Action: "CREATE",
+			ResourceRecordSet: &types.ResourceRecordSet{
 				Name: sp("jwxydbpxywg6fx3gmdibfa6cj4.n"),
-				ResourceRecords: []*route53.ResourceRecord{{
+				ResourceRecords: []types.ResourceRecord{{
 					Value: sp(`"enrtree-branch:2XS2367YHAXJFGLZHVAWLQD4ZY,H4FHT4B454P6UXFD7JCYQ5PWDY,MHTDO6TMUBRIA2XWG5LUDACK24"`),
 				}},
 				TTL:  ip(treeNodeTTL),
-				Type: sp("TXT"),
+				Type: "TXT",
 			},
 		},
 		{
-			Action: sp("CREATE"),
-			ResourceRecordSet: &route53.ResourceRecordSet{
+			Action: "CREATE",
+			ResourceRecordSet: &types.ResourceRecordSet{
 				Name: sp("mhtdo6tmubria2xwg5ludack24.n"),
-				ResourceRecords: []*route53.ResourceRecord{{
+				ResourceRecords: []types.ResourceRecord{{
 					Value: sp(`"enr:-HW4QLAYqmrwllBEnzWWs7I5Ev2IAs7x_dZlbYdRdMUx5EyKHDXp7AV5CkuPGUPdvbv1_Ms1CPfhcGCvSElSosZmyoqAgmlkgnY0iXNlY3AyNTZrMaECriawHKWdDRk2xeZkrOXBQ0dfMFLHY4eENZwdufn1S1o"`),
 				}},
 				TTL:  ip(treeNodeTTL),
-				Type: sp("TXT"),
+				Type: "TXT",
 			},
 		},
 		{
-			Action: sp("UPSERT"),
-			ResourceRecordSet: &route53.ResourceRecordSet{
+			Action: "UPSERT",
+			ResourceRecordSet: &types.ResourceRecordSet{
 				Name: sp("n"),
-				ResourceRecords: []*route53.ResourceRecord{{
+				ResourceRecords: []types.ResourceRecord{{
 					Value: sp(`"enrtree-root:v1 e=JWXYDBPXYWG6FX3GMDIBFA6CJ4 l=C7HRFPF3BLGF3YR4DY5KX3SMBE seq=1 sig=o908WmNp7LibOfPsr4btQwatZJ5URBr2ZAuxvK4UWHlsB9sUOTJQaGAlLPVAhM__XJesCHxLISo94z5Z2a463gA"`),
 				}},
 				TTL:  ip(rootTTL),
-				Type: sp("TXT"),
+				Type: "TXT",
 			},
 		},
 		{
-			Action: sp("DELETE"),
-			ResourceRecordSet: &route53.ResourceRecordSet{
+			Action: "DELETE",
+			ResourceRecordSet: &types.ResourceRecordSet{
 				Name: sp("2kfjogvxdqtxxugbh7gs7naaai.n"),
-				ResourceRecords: []*route53.ResourceRecord{
+				ResourceRecords: []types.ResourceRecord{
 					{Value: sp(`"enr:-HW4QO1ml1DdXLeZLsUxewnthhUy8eROqkDyoMTyavfks9JlYQIlMFEUoM78PovJDPQrAkrb3LRJ-""vtrymDguKCOIAWAgmlkgnY0iXNlY3AyNTZrMaEDffaGfJzgGhUif1JqFruZlYmA31HzathLSWxfbq_QoQ4"`)},
 				},
 				TTL:  ip(3333),
-				Type: sp("TXT"),
+				Type: "TXT",
 			},
 		},
 		{
-			Action: sp("DELETE"),
-			ResourceRecordSet: &route53.ResourceRecordSet{
+			Action: "DELETE",
+			ResourceRecordSet: &types.ResourceRecordSet{
 				Name: sp("fdxn3sn67na5dka4j2gok7bvqi.n"),
-				ResourceRecords: []*route53.ResourceRecord{{
+				ResourceRecords: []types.ResourceRecord{{
 					Value: sp(`"enrtree-branch:"`),
 				}},
 				TTL:  ip(treeNodeTTL),
-				Type: sp("TXT"),
+				Type: "TXT",
 			},
 		},
 	}
@@ -141,7 +141,7 @@ func TestRoute53ChangeSort(t *testing.T) {
 	}
 
 	// Check splitting according to size.
-	wantSplit := [][]*route53.Change{
+	wantSplit := [][]types.Change{
 		wantChanges[:4],
 		wantChanges[4:6],
 		wantChanges[6:],
@@ -152,7 +152,7 @@ func TestRoute53ChangeSort(t *testing.T) {
 	}
 
 	// Check splitting according to count.
-	wantSplit = [][]*route53.Change{
+	wantSplit = [][]types.Change{
 		wantChanges[:5],
 		wantChanges[5:],
 	}

+ 5 - 2
go.mod

@@ -1,11 +1,14 @@
 module github.com/ethereum/go-ethereum
 
-go 1.13
+go 1.15
 
 require (
 	github.com/Azure/azure-storage-blob-go v0.7.0
 	github.com/VictoriaMetrics/fastcache v1.5.7
-	github.com/aws/aws-sdk-go v1.25.48
+	github.com/aws/aws-sdk-go-v2 v1.2.0
+	github.com/aws/aws-sdk-go-v2/config v1.1.1
+	github.com/aws/aws-sdk-go-v2/credentials v1.1.1
+	github.com/aws/aws-sdk-go-v2/service/route53 v1.1.1
 	github.com/btcsuite/btcd v0.20.1-beta
 	github.com/cespare/cp v0.1.0
 	github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9

+ 25 - 0
go.sum

@@ -62,6 +62,24 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
 github.com/aws/aws-sdk-go v1.25.48 h1:J82DYDGZHOKHdhx6hD24Tm30c2C3GchYGfN0mf9iKUk=
 github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
+github.com/aws/aws-sdk-go-v2 v1.2.0 h1:BS+UYpbsElC82gB+2E2jiCBg36i8HlubTB/dO/moQ9c=
+github.com/aws/aws-sdk-go-v2 v1.2.0/go.mod h1:zEQs02YRBw1DjK0PoJv3ygDYOFTre1ejlJWl8FwAuQo=
+github.com/aws/aws-sdk-go-v2/config v1.1.1 h1:ZAoq32boMzcaTW9bcUacBswAmHTbvlvDJICgHFZuECo=
+github.com/aws/aws-sdk-go-v2/config v1.1.1/go.mod h1:0XsVy9lBI/BCXm+2Tuvt39YmdHwS5unDQmxZOYe8F5Y=
+github.com/aws/aws-sdk-go-v2/credentials v1.1.1 h1:NbvWIM1Mx6sNPTxowHgS2ewXCRp+NGTzUYb/96FZJbY=
+github.com/aws/aws-sdk-go-v2/credentials v1.1.1/go.mod h1:mM2iIjwl7LULWtS6JCACyInboHirisUUdkBPoTHMOUo=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.2 h1:EtEU7WRaWliitZh2nmuxEXrN0Cb8EgPUFGIoTMeqbzI=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.2/go.mod h1:3hGg3PpiEjHnrkrlasTfxFqUsZ2GCk/fMUn4CbKgSkM=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.2 h1:4AH9fFjUlVktQMznF+YN33aWNXaR4VgDXyP28qokJC0=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.2/go.mod h1:45MfaXZ0cNbeuT0KQ1XJylq8A6+OpVV2E5kvY/Kq+u8=
+github.com/aws/aws-sdk-go-v2/service/route53 v1.1.1 h1:cKr6St+CtC3/dl/rEBJvlk7A/IN5D5F02GNkGzfbtVU=
+github.com/aws/aws-sdk-go-v2/service/route53 v1.1.1/go.mod h1:rLiOUrPLW/Er5kRcQ7NkwbjlijluLsrIbu/iyl35RO4=
+github.com/aws/aws-sdk-go-v2/service/sso v1.1.1 h1:37QubsarExl5ZuCBlnRP+7l1tNwZPBSTqpTBrPH98RU=
+github.com/aws/aws-sdk-go-v2/service/sso v1.1.1/go.mod h1:SuZJxklHxLAXgLTc1iFXbEWkXs7QRTQpCLGaKIprQW0=
+github.com/aws/aws-sdk-go-v2/service/sts v1.1.1 h1:TJoIfnIFubCX0ACVeJ0w46HEH5MwjwYN4iFhuYIhfIY=
+github.com/aws/aws-sdk-go-v2/service/sts v1.1.1/go.mod h1:Wi0EBZwiz/K44YliU0EKxqTCJGUfYTWXrrBwkq736bM=
+github.com/aws/smithy-go v1.1.0 h1:D6CSsM3gdxaGaqXnPgOBCeL6Mophqzu7KJOu7zW78sU=
+github.com/aws/smithy-go v1.1.0/go.mod h1:EzMw8dbp/YJL4A5/sbhGddag+NPT7q084agLbB9LgIw=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
@@ -193,6 +211,9 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
+github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa h1:Q75Upo5UN4JbPFURXZ8nLKYUvF85dyFRop/vQ0Rv+64=
 github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
@@ -265,6 +286,10 @@ github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1C
 github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
 github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
 github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
+github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
+github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=