From 299cbf2dbc4942ab26cbb0750a15ff46600253e4 Mon Sep 17 00:00:00 2001 From: Kevin Pike Date: Thu, 29 Oct 2020 09:28:43 -0700 Subject: [PATCH] Handle recoverable logging errors. Report log delivery exception metrics --- cfn/cfn.go | 1 + cfn/logging/cloudwatchlogs.go | 105 ++++++++-------- cfn/logging/cloudwatchlogs_test.go | 186 ++++++++++++++++------------- cfn/logging/failure_publisher.go | 31 +++++ cfn/metrics/publisher.go | 5 +- go.mod | 2 +- go.sum | 11 ++ 7 files changed, 203 insertions(+), 138 deletions(-) create mode 100644 cfn/logging/failure_publisher.go diff --git a/cfn/cfn.go b/cfn/cfn.go index bb69385b..f2e159fa 100644 --- a/cfn/cfn.go +++ b/cfn/cfn.go @@ -92,6 +92,7 @@ func makeEventFunc(h Handler) eventFunc { once.Do(func() { l, err := logging.NewCloudWatchLogsProvider( cloudwatchlogs.New(ps), + m, event.RequestData.ProviderLogGroupName, ) if err != nil { diff --git a/cfn/logging/cloudwatchlogs.go b/cfn/logging/cloudwatchlogs.go index 5357a331..cb6fa432 100644 --- a/cfn/logging/cloudwatchlogs.go +++ b/cfn/logging/cloudwatchlogs.go @@ -7,6 +7,7 @@ import ( "time" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/cloudwatchlogs" "github.com/aws/aws-sdk-go/service/cloudwatchlogs/cloudwatchlogsiface" @@ -31,37 +32,39 @@ import ( // // pushed through the Write func and sent to CloudWatch Logs // log.SetOutput(provider) // log.Printf("Eric loves pineapple pizza!") -func NewCloudWatchLogsProvider(client cloudwatchlogsiface.CloudWatchLogsAPI, logGroupName string) (io.Writer, error) { +func NewCloudWatchLogsProvider( + client cloudwatchlogsiface.CloudWatchLogsAPI, + metricPublisher metricFailurePublisher, + logGroupName string, +) (io.Writer, error) { logger := New("internal: ") + fp := &failurePublisher{ + metricPublisher: metricPublisher, + } // If we're running in SAM CLI, we can return the stdout if len(os.Getenv("AWS_SAM_LOCAL")) > 0 && len(os.Getenv("AWS_FORCE_INTEGRATIONS")) == 0 { return stdErr, nil } - ok, err := CloudWatchLogGroupExists(client, logGroupName) - if err != nil { + if err := CreateNewCloudWatchLogGroup(client, logGroupName); err != nil { + fp.Publish("CreateLogGroup", err) return nil, err } - if !ok { - logger.Printf("Need to create loggroup: %v", logGroupName) - if err := CreateNewCloudWatchLogGroup(client, logGroupName); err != nil { - return nil, err - } - } - - logStreamName := ksuid.New() + logStreamName := ksuid.New().String() // need to create logstream - if err := CreateNewLogStream(client, logGroupName, logStreamName.String()); err != nil { + if err := CreateNewLogStream(client, logGroupName, logStreamName); err != nil { + fp.Publish("CreateLogStream", err) return nil, err } provider := &cloudWatchLogsProvider{ - client: client, + client: client, + failurePublisher: fp, logGroupName: logGroupName, - logStreamName: logStreamName.String(), + logStreamName: logStreamName, logger: logger, } @@ -74,7 +77,8 @@ func NewCloudWatchLogsProvider(client cloudwatchlogsiface.CloudWatchLogsAPI, log } type cloudWatchLogsProvider struct { - client cloudwatchlogsiface.CloudWatchLogsAPI + client cloudwatchlogsiface.CloudWatchLogsAPI + failurePublisher *failurePublisher logGroupName string logStreamName string @@ -106,6 +110,15 @@ func (p *cloudWatchLogsProvider) Write(b []byte) (int, error) { resp, err := p.client.PutLogEvents(input) if err != nil { + switch v := err.(type) { + case *cloudwatchlogs.DataAlreadyAcceptedException: + p.sequence = *v.ExpectedSequenceToken + case *cloudwatchlogs.InvalidSequenceTokenException: + p.sequence = *v.ExpectedSequenceToken + } + + p.logger.Printf("An error occurred while putting log events [%s] to resource owner account, with error: %s", string(b), err) + p.failurePublisher.Publish("PutLogEvents", err) return 0, err } @@ -114,43 +127,11 @@ func (p *cloudWatchLogsProvider) Write(b []byte) (int, error) { return len(b), nil } -// CloudWatchLogGroupExists checks if a log group exists -// -// Using the client provided, it will check the CloudWatch Logs -// service to verify the log group -// -// sess := session.Must(aws.NewConfig()) -// svc := cloudwatchlogs.New(sess) -// -// // checks if the pineapple-pizza log group exists -// ok, err := LogGroupExists(svc, "pineapple-pizza") -// if err != nil { -// panic(err) -// } -// if ok { -// // do something -// } -func CloudWatchLogGroupExists(client cloudwatchlogsiface.CloudWatchLogsAPI, logGroupName string) (bool, error) { - resp, err := client.DescribeLogGroups(&cloudwatchlogs.DescribeLogGroupsInput{ - Limit: aws.Int64(1), - LogGroupNamePrefix: aws.String(logGroupName), - }) - - if err != nil { - return false, err - } - - if len(resp.LogGroups) == 0 || *resp.LogGroups[0].LogGroupName != logGroupName { - return false, nil - } - - return true, nil -} - // CreateNewCloudWatchLogGroup creates a log group in CloudWatch Logs. // // Using a passed in client to create the call to the service, it -// will create a log group of the specified name +// will create a log group of the specified name. If the log group +// already exists, no erorr is returned. // // sess := session.Must(aws.NewConfig()) // svc := cloudwatchlogs.New(sess) @@ -159,21 +140,37 @@ func CloudWatchLogGroupExists(client cloudwatchlogsiface.CloudWatchLogsAPI, logG // panic("Unable to create log group", err) // } func CreateNewCloudWatchLogGroup(client cloudwatchlogsiface.CloudWatchLogsAPI, logGroupName string) error { - if _, err := client.CreateLogGroup(&cloudwatchlogs.CreateLogGroupInput{ + _, err := client.CreateLogGroup(&cloudwatchlogs.CreateLogGroupInput{ LogGroupName: aws.String(logGroupName), - }); err != nil { - return err + }) + if err == nil { + return nil + } + + awsErr, ok := err.(awserr.Error) + if ok && awsErr.Code() == cloudwatchlogs.ErrCodeResourceAlreadyExistsException { + return nil } - return nil + return err } -// CreateNewLogStream creates a log stream inside of a LogGroup +// CreateNewLogStream creates a log stream inside of a LogGroup. +// If the log stream already exists, no error is returned. func CreateNewLogStream(client cloudwatchlogsiface.CloudWatchLogsAPI, logGroupName string, logStreamName string) error { _, err := client.CreateLogStream(&cloudwatchlogs.CreateLogStreamInput{ LogGroupName: aws.String(logGroupName), LogStreamName: aws.String(logStreamName), }) + if err == nil { + return nil + } + + awsErr, ok := err.(awserr.Error) + if ok && awsErr.Code() == cloudwatchlogs.ErrCodeResourceAlreadyExistsException { + return nil + } + return err } diff --git a/cfn/logging/cloudwatchlogs_test.go b/cfn/logging/cloudwatchlogs_test.go index 4e8c8174..3d99af5f 100644 --- a/cfn/logging/cloudwatchlogs_test.go +++ b/cfn/logging/cloudwatchlogs_test.go @@ -1,7 +1,10 @@ package logging import ( + "errors" + "strings" "testing" + "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" @@ -12,14 +15,6 @@ import ( func TestCloudWatchLogProvider(t *testing.T) { t.Run("Init", func(t *testing.T) { client := CallbackCloudWatchLogs{ - DescribeLogGroupsFn: func(input *cloudwatchlogs.DescribeLogGroupsInput) (*cloudwatchlogs.DescribeLogGroupsOutput, error) { - return &cloudwatchlogs.DescribeLogGroupsOutput{ - LogGroups: []*cloudwatchlogs.LogGroup{ - &cloudwatchlogs.LogGroup{LogGroupName: input.LogGroupNamePrefix}, - }, - }, nil - }, - CreateLogGroupFn: func(input *cloudwatchlogs.CreateLogGroupInput) (*cloudwatchlogs.CreateLogGroupOutput, error) { return nil, nil }, @@ -34,21 +29,18 @@ func TestCloudWatchLogProvider(t *testing.T) { }, nil }, } - - _, err := NewCloudWatchLogsProvider(client, "pineapple-pizza") + fp := &fakeExceptionPublisher{} + _, err := NewCloudWatchLogsProvider(client, fp, "pineapple-pizza") if err != nil { t.Fatalf("Error returned: %v", err) } + fp.equalPublishes(t, nil) }) - t.Run("Init Error Exists", func(t *testing.T) { + t.Run("Init Log Group Exists", func(t *testing.T) { client := CallbackCloudWatchLogs{ - DescribeLogGroupsFn: func(input *cloudwatchlogs.DescribeLogGroupsInput) (*cloudwatchlogs.DescribeLogGroupsOutput, error) { - return nil, awserr.New("Invalid", "Invalid", nil) - }, - CreateLogGroupFn: func(input *cloudwatchlogs.CreateLogGroupInput) (*cloudwatchlogs.CreateLogGroupOutput, error) { - return nil, nil + return nil, awserr.New(cloudwatchlogs.ErrCodeResourceAlreadyExistsException, "", errors.New("")) }, CreateLogStreamFn: func(input *cloudwatchlogs.CreateLogStreamInput) (*cloudwatchlogs.CreateLogStreamOutput, error) { @@ -61,21 +53,40 @@ func TestCloudWatchLogProvider(t *testing.T) { }, nil }, } - - _, err := NewCloudWatchLogsProvider(client, "pineapple-pizza") - if err == nil { + fp := &fakeExceptionPublisher{} + _, err := NewCloudWatchLogsProvider(client, fp, "pineapple-pizza") + if err != nil { t.Fatalf("Error returned: %v", err) } + fp.equalPublishes(t, nil) }) - t.Run("Init Error Unable to Create", func(t *testing.T) { + t.Run("Init Log Stream Exists", func(t *testing.T) { client := CallbackCloudWatchLogs{ - DescribeLogGroupsFn: func(input *cloudwatchlogs.DescribeLogGroupsInput) (*cloudwatchlogs.DescribeLogGroupsOutput, error) { - return &cloudwatchlogs.DescribeLogGroupsOutput{ - LogGroups: []*cloudwatchlogs.LogGroup{}, + CreateLogGroupFn: func(input *cloudwatchlogs.CreateLogGroupInput) (*cloudwatchlogs.CreateLogGroupOutput, error) { + return nil, nil + }, + + CreateLogStreamFn: func(input *cloudwatchlogs.CreateLogStreamInput) (*cloudwatchlogs.CreateLogStreamOutput, error) { + return nil, awserr.New(cloudwatchlogs.ErrCodeResourceAlreadyExistsException, "", errors.New("")) + }, + + PutLogEventsFn: func(input *cloudwatchlogs.PutLogEventsInput) (*cloudwatchlogs.PutLogEventsOutput, error) { + return &cloudwatchlogs.PutLogEventsOutput{ + NextSequenceToken: aws.String("zomg"), }, nil }, + } + fp := &fakeExceptionPublisher{} + _, err := NewCloudWatchLogsProvider(client, fp, "pineapple-pizza") + if err != nil { + t.Fatalf("Error returned: %v", err) + } + fp.equalPublishes(t, nil) + }) + t.Run("Init Error Unable to Create Log Group", func(t *testing.T) { + client := CallbackCloudWatchLogs{ CreateLogGroupFn: func(input *cloudwatchlogs.CreateLogGroupInput) (*cloudwatchlogs.CreateLogGroupOutput, error) { return nil, awserr.New("Invalid", "Invalid", nil) }, @@ -90,23 +101,44 @@ func TestCloudWatchLogProvider(t *testing.T) { }, nil }, } - - _, err := NewCloudWatchLogsProvider(client, "pineapple-pizza") + fp := &fakeExceptionPublisher{} + _, err := NewCloudWatchLogsProvider(client, fp, "pineapple-pizza") if err == nil { t.Fatalf("Error returned: %v", err) } + fp.equalPublishes(t, map[string]int{ + "CreateLogGroup": 1, + }) }) - t.Run("Write", func(t *testing.T) { + t.Run("Init Error Unable to Create Log Stream", func(t *testing.T) { client := CallbackCloudWatchLogs{ - DescribeLogGroupsFn: func(input *cloudwatchlogs.DescribeLogGroupsInput) (*cloudwatchlogs.DescribeLogGroupsOutput, error) { - return &cloudwatchlogs.DescribeLogGroupsOutput{ - LogGroups: []*cloudwatchlogs.LogGroup{ - &cloudwatchlogs.LogGroup{LogGroupName: input.LogGroupNamePrefix}, - }, + CreateLogGroupFn: func(input *cloudwatchlogs.CreateLogGroupInput) (*cloudwatchlogs.CreateLogGroupOutput, error) { + return nil, nil + }, + + CreateLogStreamFn: func(input *cloudwatchlogs.CreateLogStreamInput) (*cloudwatchlogs.CreateLogStreamOutput, error) { + return nil, awserr.New("Invalid", "Invalid", nil) + }, + + PutLogEventsFn: func(input *cloudwatchlogs.PutLogEventsInput) (*cloudwatchlogs.PutLogEventsOutput, error) { + return &cloudwatchlogs.PutLogEventsOutput{ + NextSequenceToken: aws.String("zomg"), }, nil }, + } + fp := &fakeExceptionPublisher{} + _, err := NewCloudWatchLogsProvider(client, fp, "pineapple-pizza") + if err == nil { + t.Fatalf("Error returned: %v", err) + } + fp.equalPublishes(t, map[string]int{ + "CreateLogStream": 1, + }) + }) + t.Run("Write", func(t *testing.T) { + client := CallbackCloudWatchLogs{ CreateLogGroupFn: func(input *cloudwatchlogs.CreateLogGroupInput) (*cloudwatchlogs.CreateLogGroupOutput, error) { return nil, nil }, @@ -121,8 +153,8 @@ func TestCloudWatchLogProvider(t *testing.T) { }, nil }, } - - p, err := NewCloudWatchLogsProvider(client, "pineapple-pizza") + fp := &fakeExceptionPublisher{} + p, err := NewCloudWatchLogsProvider(client, fp, "pineapple-pizza") if err != nil { t.Fatalf("Error returned: %v", err) } @@ -136,19 +168,12 @@ func TestCloudWatchLogProvider(t *testing.T) { if c != len(line) { t.Fatalf("Didn't write the same content") } + fp.equalPublishes(t, nil) }) t.Run("Write Error", func(t *testing.T) { writeCount := 0 client := CallbackCloudWatchLogs{ - DescribeLogGroupsFn: func(input *cloudwatchlogs.DescribeLogGroupsInput) (*cloudwatchlogs.DescribeLogGroupsOutput, error) { - return &cloudwatchlogs.DescribeLogGroupsOutput{ - LogGroups: []*cloudwatchlogs.LogGroup{ - &cloudwatchlogs.LogGroup{LogGroupName: input.LogGroupNamePrefix}, - }, - }, nil - }, - CreateLogGroupFn: func(input *cloudwatchlogs.CreateLogGroupInput) (*cloudwatchlogs.CreateLogGroupOutput, error) { return nil, nil }, @@ -168,47 +193,21 @@ func TestCloudWatchLogProvider(t *testing.T) { return nil, awserr.New("Invalid", "Invalid", nil) }, } - - p, err := NewCloudWatchLogsProvider(client, "pineapple-pizza") + fp := &fakeExceptionPublisher{} + p, err := NewCloudWatchLogsProvider(client, fp, "pineapple-pizza") if err != nil { t.Fatalf("Error returned: %v", err) } + fp.equalPublishes(t, nil) line := "Eric loves pineapple pizza" c, err := p.Write([]byte(line)) if err == nil && c != 0 { t.Fatalf("Error not returned") } - }) -} - -func TestCloudWatchLogGroupExists(t *testing.T) { - t.Run("Success", func(t *testing.T) { - client := CallbackCloudWatchLogs{ - DescribeLogGroupsFn: func(input *cloudwatchlogs.DescribeLogGroupsInput) (*cloudwatchlogs.DescribeLogGroupsOutput, error) { - return &cloudwatchlogs.DescribeLogGroupsOutput{ - LogGroups: []*cloudwatchlogs.LogGroup{ - &cloudwatchlogs.LogGroup{LogGroupName: input.LogGroupNamePrefix}, - }, - }, nil - }, - } - - if _, err := CloudWatchLogGroupExists(client, "pineapple-pizza"); err != nil { - t.Fatalf("Error returned: %v", err) - } - }) - - t.Run("Error", func(t *testing.T) { - client := CallbackCloudWatchLogs{ - DescribeLogGroupsFn: func(input *cloudwatchlogs.DescribeLogGroupsInput) (*cloudwatchlogs.DescribeLogGroupsOutput, error) { - return nil, awserr.New("Invalid", "Invalid", nil) - }, - } - - if _, err := CloudWatchLogGroupExists(client, "pineapple-pizza"); err == nil { - t.Fatalf("Error not returned") - } + fp.equalPublishes(t, map[string]int{ + "PutLogEvents": 1, + }) }) } @@ -241,14 +240,9 @@ func TestCreateCloudWatchLogGroup(t *testing.T) { type CallbackCloudWatchLogs struct { cloudwatchlogsiface.CloudWatchLogsAPI - DescribeLogGroupsFn func(input *cloudwatchlogs.DescribeLogGroupsInput) (*cloudwatchlogs.DescribeLogGroupsOutput, error) - CreateLogGroupFn func(input *cloudwatchlogs.CreateLogGroupInput) (*cloudwatchlogs.CreateLogGroupOutput, error) - CreateLogStreamFn func(input *cloudwatchlogs.CreateLogStreamInput) (*cloudwatchlogs.CreateLogStreamOutput, error) - PutLogEventsFn func(input *cloudwatchlogs.PutLogEventsInput) (*cloudwatchlogs.PutLogEventsOutput, error) -} - -func (cwl CallbackCloudWatchLogs) DescribeLogGroups(input *cloudwatchlogs.DescribeLogGroupsInput) (*cloudwatchlogs.DescribeLogGroupsOutput, error) { - return cwl.DescribeLogGroupsFn(input) + CreateLogGroupFn func(input *cloudwatchlogs.CreateLogGroupInput) (*cloudwatchlogs.CreateLogGroupOutput, error) + CreateLogStreamFn func(input *cloudwatchlogs.CreateLogStreamInput) (*cloudwatchlogs.CreateLogStreamOutput, error) + PutLogEventsFn func(input *cloudwatchlogs.PutLogEventsInput) (*cloudwatchlogs.PutLogEventsOutput, error) } func (cwl CallbackCloudWatchLogs) CreateLogGroup(input *cloudwatchlogs.CreateLogGroupInput) (*cloudwatchlogs.CreateLogGroupOutput, error) { @@ -262,3 +256,33 @@ func (cwl CallbackCloudWatchLogs) CreateLogStream(input *cloudwatchlogs.CreateLo func (cwl CallbackCloudWatchLogs) PutLogEvents(input *cloudwatchlogs.PutLogEventsInput) (*cloudwatchlogs.PutLogEventsOutput, error) { return cwl.PutLogEventsFn(input) } + +type fakeExceptionPublisher struct { + publishesByOperation map[string]int +} + +func (p *fakeExceptionPublisher) PublishExceptionMetric(date time.Time, action string, e error) { + if p.publishesByOperation == nil { + p.publishesByOperation = make(map[string]int) + } + + operation := strings.Split(e.Error(), ":")[0] + + p.publishesByOperation[operation]++ +} + +func (p fakeExceptionPublisher) equalPublishes(t *testing.T, expected map[string]int) { + if expected == nil && len(p.publishesByOperation) > 0 { + t.Fatalf("expected no exceptions to be published: %v", p.publishesByOperation) + } + + if len(expected) != len(p.publishesByOperation) { + t.Fatalf("expected %v, got: %v", expected, p.publishesByOperation) + } + + for operation, count := range expected { + if p.publishesByOperation[operation] != count { + t.Fatalf("expected %q to have count %d, got %d. actual map: %v", operation, count, p.publishesByOperation[operation], p.publishesByOperation) + } + } +} diff --git a/cfn/logging/failure_publisher.go b/cfn/logging/failure_publisher.go new file mode 100644 index 00000000..f7945857 --- /dev/null +++ b/cfn/logging/failure_publisher.go @@ -0,0 +1,31 @@ +package logging + +import ( + "fmt" + "time" + + "github.com/aws/aws-sdk-go/aws/awserr" +) + +// FailurePublisher leverages metricFailurePublisher to publish log failures +type failurePublisher struct { + metricPublisher metricFailurePublisher +} + +type metricFailurePublisher interface { + PublishExceptionMetric(date time.Time, action string, e error) +} + +func (p *failurePublisher) Publish(operation string, err error) { + p.metricPublisher.PublishExceptionMetric(time.Now(), "ProviderLogDelivery", errFromLogFailure(operation, err)) +} + +func errFromLogFailure(operation string, err error) error { + awsErr, ok := err.(awserr.Error) + if !ok { + // avoid %w for compatiblility + return fmt.Errorf("%s: %s", operation, err) + } + + return fmt.Errorf("%s: %s", operation, awsErr.Code()) +} diff --git a/cfn/metrics/publisher.go b/cfn/metrics/publisher.go index 654d594e..11cf809a 100644 --- a/cfn/metrics/publisher.go +++ b/cfn/metrics/publisher.go @@ -97,12 +97,13 @@ func (p *Publisher) publishMetric(metricName string, data map[string]string, uni d = append(d, dim) } md := []*cloudwatch.MetricDatum{ - &cloudwatch.MetricDatum{ + { MetricName: aws.String(metricName), Unit: aws.String(unit), Value: aws.Float64(value), Dimensions: d, - Timestamp: &date}, + Timestamp: aws.Time(date.UTC()), + }, } pi := cloudwatch.PutMetricDataInput{ Namespace: aws.String(p.namespace), diff --git a/go.mod b/go.mod index 79d072dd..1044668b 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.13 require ( github.com/avast/retry-go v2.6.0+incompatible github.com/aws/aws-lambda-go v1.13.3 - github.com/aws/aws-sdk-go v1.25.37 + github.com/aws/aws-sdk-go v1.35.10 github.com/google/go-cmp v0.3.1 github.com/google/go-github v17.0.0+incompatible github.com/google/go-querystring v1.0.0 // indirect diff --git a/go.sum b/go.sum index cf869761..07115f9a 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ github.com/aws/aws-lambda-go v1.13.3 h1:SuCy7H3NLyp+1Mrfp+m80jcbi9KYWAs9/BXwppwR github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.25.37 h1:gBtB/F3dophWpsUQKN/Kni+JzYEH2mGHF4hWNtfED1w= github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.35.10 h1:FsJtrOS7P+Qmq1rPTGgS/+qC1Y9eGuAJHvAZpZlhmb4= +github.com/aws/aws-sdk-go v1.35.10/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -21,6 +23,10 @@ github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -31,12 +37,16 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -45,3 +55,4 @@ gopkg.in/validator.v2 v2.0.0-20191107172027-c3144fdedc21 h1:2QQcyaEBdpfjjYkF0MXc gopkg.in/validator.v2 v2.0.0-20191107172027-c3144fdedc21/go.mod h1:o4V0GXN9/CAmCsvJ0oXYZvrZOe7syiDZSN1GWGZTGzc= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=