Skip to content

Commit df00985

Browse files
authored
Failure Store: Update go-docappender to respect failure store status (#228)
1 parent c11b826 commit df00985

File tree

6 files changed

+169
-27
lines changed

6 files changed

+169
-27
lines changed

appender.go

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -409,13 +409,13 @@ func (a *Appender) flush(ctx context.Context, bulkIndexer *BulkIndexer) error {
409409
}
410410
return err
411411
}
412-
var (
413-
docsFailed, docsIndexed,
412+
var docsFailed, docsIndexed,
414413
// breakdown of failed docs:
415414
tooManyRequests, // failed after document retries (if it applies) and final status is 429
416415
clientFailed, // failed after document retries (if it applies) and final status is 400s excluding 429
417416
serverFailed int64 // failed after document retries (if it applies) and final status is 500s
418-
)
417+
418+
failureStoreDocs := resp.FailureStoreDocs
419419
docsIndexed = resp.Indexed
420420
var failedCount map[BulkIndexerResponseItem]int
421421
if len(resp.FailedDocs) > 0 {
@@ -483,11 +483,41 @@ func (a *Appender) flush(ctx context.Context, bulkIndexer *BulkIndexer) error {
483483
metric.WithAttributes(attribute.String("status", "FailedServer")),
484484
)
485485
}
486+
if failureStoreDocs.Used > 0 {
487+
a.addCount(failureStoreDocs.Used, nil,
488+
a.metrics.docsIndexed,
489+
metric.WithAttributes(
490+
attribute.String("status", "FailureStore"),
491+
attribute.String("failure_store", string(FailureStoreStatusUsed)),
492+
),
493+
)
494+
}
495+
if failureStoreDocs.Failed > 0 {
496+
a.addCount(failureStoreDocs.Failed, nil,
497+
a.metrics.docsIndexed,
498+
metric.WithAttributes(
499+
attribute.String("status", "FailureStore"),
500+
attribute.String("failure_store", string(FailureStoreStatusFailed)),
501+
),
502+
)
503+
}
504+
if failureStoreDocs.NotEnabled > 0 {
505+
a.addCount(failureStoreDocs.NotEnabled, nil,
506+
a.metrics.docsIndexed,
507+
metric.WithAttributes(
508+
attribute.String("status", "FailureStore"),
509+
attribute.String("failure_store", string(FailureStoreStatusNotEnabled)),
510+
),
511+
)
512+
}
486513
logger.Debug(
487514
"bulk request completed",
488515
zap.Int64("docs_indexed", docsIndexed),
489516
zap.Int64("docs_failed", docsFailed),
490517
zap.Int64("docs_rate_limited", tooManyRequests),
518+
zap.Int64("docs_failure_store_used", failureStoreDocs.Used),
519+
zap.Int64("docs_failure_store_failed", failureStoreDocs.Failed),
520+
zap.Int64("docs_failure_store_not_enabled", failureStoreDocs.NotEnabled),
491521
)
492522
if a.otelTracingEnabled() && span.IsRecording() {
493523
span.SetStatus(codes.Ok, "")

appender_test.go

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -67,18 +67,24 @@ func TestAppender(t *testing.T) {
6767
// "too many requests". These will be recorded as failures in indexing
6868
// stats.
6969
for i := range result.Items {
70-
if i > 2 {
70+
if i > 5 {
7171
break
7272
}
73-
status := http.StatusInternalServerError
74-
switch i {
75-
case 1:
76-
status = http.StatusTooManyRequests
77-
case 2:
78-
status = http.StatusUnauthorized
79-
}
8073
for action, item := range result.Items[i] {
81-
item.Status = status
74+
switch i {
75+
case 0:
76+
item.Status = http.StatusInternalServerError
77+
case 1:
78+
item.Status = http.StatusTooManyRequests
79+
case 2:
80+
item.Status = http.StatusUnauthorized
81+
case 3:
82+
item.FailureStore = string(docappender.FailureStoreStatusUsed)
83+
case 4:
84+
item.FailureStore = string(docappender.FailureStoreStatusFailed)
85+
case 5:
86+
item.FailureStore = string(docappender.FailureStoreStatusNotEnabled)
87+
}
8288
result.Items[i][action] = item
8389
}
8490
}
@@ -157,7 +163,7 @@ loop:
157163
asserted.Add(1)
158164
counter := metric.Data.(metricdata.Sum[int64])
159165
for _, dp := range counter.DataPoints {
160-
metricdatatest.AssertHasAttributes[metricdata.DataPoint[int64]](t, dp, attrs.ToSlice()...)
166+
metricdatatest.AssertHasAttributes(t, dp, attrs.ToSlice()...)
161167
status, exist := dp.Attributes.Value(attribute.Key("status"))
162168
assert.True(t, exist)
163169
switch status.AsString() {
@@ -173,6 +179,19 @@ loop:
173179
case "TooMany":
174180
processedAsserted++
175181
assert.Equal(t, stats.TooManyRequests, dp.Value)
182+
case "FailureStore":
183+
processedAsserted++
184+
fs, exist := dp.Attributes.Value(attribute.Key("failure_store"))
185+
assert.True(t, exist)
186+
assert.Contains(
187+
t,
188+
[]docappender.FailureStoreStatus{
189+
docappender.FailureStoreStatusUsed,
190+
docappender.FailureStoreStatusFailed,
191+
docappender.FailureStoreStatusNotEnabled,
192+
},
193+
docappender.FailureStoreStatus(fs.AsString()),
194+
)
176195
default:
177196
assert.FailNow(t, "Unexpected metric with status: "+status.AsString())
178197
}
@@ -206,7 +225,7 @@ loop:
206225

207226
assert.Empty(t, unexpectedMetrics)
208227
assert.Equal(t, int64(7), asserted.Load())
209-
assert.Equal(t, 4, processedAsserted)
228+
assert.Equal(t, 7, processedAsserted)
210229
}
211230

212231
func TestAppenderRetry(t *testing.T) {
@@ -753,7 +772,6 @@ func TestAppenderFlushRequestError(t *testing.T) {
753772
}
754773
})
755774
assert.Equal(t, int64(1), asserted.Load())
756-
757775
}
758776
t.Run("400", func(t *testing.T) {
759777
test(t, http.StatusBadRequest, "flush failed (400): {\"error\":{\"type\":\"x_content_parse_exception\",\"caused_by\":{\"type\":\"json_parse_exception\"}}}")
@@ -806,7 +824,7 @@ func TestAppenderIndexFailedLogging(t *testing.T) {
806824

807825
core, observed := observer.New(zap.NewAtomicLevelAt(zapcore.DebugLevel))
808826
indexer, err := docappender.New(client, docappender.Config{
809-
FlushBytes: 500,
827+
FlushBytes: 5000,
810828
Logger: zap.New(core),
811829
})
812830
require.NoError(t, err)
@@ -1372,7 +1390,8 @@ func TestAppenderCloseBusyIndexer(t *testing.T) {
13721390
BytesTotal: bytesTotal,
13731391
BytesUncompressedTotal: bytesUncompressedTotal,
13741392
AvailableBulkRequests: 10,
1375-
IndexersActive: 0}, indexer.Stats())
1393+
IndexersActive: 0,
1394+
}, indexer.Stats())
13761395
}
13771396

13781397
func TestAppenderPipeline(t *testing.T) {

bulk_indexer.go

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,16 @@ type BulkIndexerResponseStat struct {
114114
Indexed int64
115115
// RetriedDocs contains the total number of retried documents.
116116
RetriedDocs int64
117+
// FailureStoreDocs contains failure store specific document stats.
118+
FailureStoreDocs struct {
119+
// Used contains the total number of documents indexed to failure store.
120+
Used int64
121+
// Failed contains the total number of documents which failed when indexed to failure store.
122+
Failed int64
123+
// NotEnabled contains the total number of documents which could have been indexed to failure store
124+
// if it was enabled.
125+
NotEnabled int64
126+
}
117127
// GreatestRetry contains the greatest observed retry count in the entire
118128
// bulk request.
119129
GreatestRetry int
@@ -134,6 +144,22 @@ type BulkIndexerResponseItem struct {
134144
} `json:"error,omitempty"`
135145
}
136146

147+
// FailureStoreStatus defines enumeration type for all known failure store statuses.
148+
type FailureStoreStatus string
149+
150+
const (
151+
// FailureStoreStatusUnknown implicit status which represents that there is no information about
152+
// this response or that the failure store is not applicable.
153+
FailureStoreStatusUnknown FailureStoreStatus = "not_applicable_or_unknown"
154+
// FailureStoreStatusUsed status which represents that this document was stored in the failure store successfully.
155+
FailureStoreStatusUsed FailureStoreStatus = "used"
156+
// FailureStoreStatusFailed status which represents that this document was rejected from the failure store.
157+
FailureStoreStatusFailed FailureStoreStatus = "failed"
158+
// FailureStoreStatusNotEnabled status which represents that this document was rejected, but
159+
// it could have ended up in the failure store if it was enabled.
160+
FailureStoreStatusNotEnabled FailureStoreStatus = "not_enabled"
161+
)
162+
137163
func init() {
138164
jsoniter.RegisterTypeDecoderFunc("docappender.BulkIndexerResponseStat", func(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
139165
iter.ReadObjectCB(func(i *jsoniter.Iterator, s string) bool {
@@ -149,6 +175,16 @@ func init() {
149175
item.Index = i.ReadString()
150176
case "status":
151177
item.Status = i.ReadInt()
178+
case "failure_store":
179+
// For the stats track only actionable explicit failure store statuses "used", "failed" and "not_enabled".
180+
switch fs := i.ReadString(); FailureStoreStatus(fs) {
181+
case FailureStoreStatusUsed:
182+
(*((*BulkIndexerResponseStat)(ptr))).FailureStoreDocs.Used++
183+
case FailureStoreStatusFailed:
184+
(*((*BulkIndexerResponseStat)(ptr))).FailureStoreDocs.Failed++
185+
case FailureStoreStatusNotEnabled:
186+
(*((*BulkIndexerResponseStat)(ptr))).FailureStoreDocs.NotEnabled++
187+
}
152188
case "error":
153189
i.ReadObjectCB(func(i *jsoniter.Iterator, s string) bool {
154190
switch s {
@@ -419,7 +455,7 @@ func (b *BulkIndexer) Flush(ctx context.Context) (BulkIndexerResponseStat, error
419455
// See: https://github.com/golang/go/issues/51907
420456
Body: bytes.NewReader(b.buf.Bytes()),
421457
Header: make(http.Header),
422-
FilterPath: []string{"items.*._index", "items.*.status", "items.*.error.type", "items.*.error.reason"},
458+
FilterPath: []string{"items.*._index", "items.*.status", "items.*.failure_store", "items.*.error.type", "items.*.error.reason"},
423459
Pipeline: b.config.Pipeline,
424460
}
425461
if b.requireDataStream {

bulk_indexer_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,3 +295,49 @@ func TestItemRequireDataStream(t *testing.T) {
295295
require.NoError(t, err)
296296
require.Equal(t, int64(2), stat.Indexed)
297297
}
298+
299+
func TestBulkIndexer_FailureStore(t *testing.T) {
300+
client := docappendertest.NewMockElasticsearchClient(t, func(w http.ResponseWriter, r *http.Request) {
301+
_, result := docappendertest.DecodeBulkRequest(r)
302+
var i int
303+
for _, itemsMap := range result.Items {
304+
for k, item := range itemsMap {
305+
switch i % 4 {
306+
case 0:
307+
item.FailureStore = string(docappender.FailureStoreStatusUsed)
308+
case 1:
309+
item.FailureStore = string(docappender.FailureStoreStatusFailed)
310+
case 2:
311+
item.FailureStore = string(docappender.FailureStoreStatusUnknown)
312+
case 3:
313+
item.FailureStore = string(docappender.FailureStoreStatusNotEnabled)
314+
}
315+
itemsMap[k] = item
316+
i++
317+
}
318+
}
319+
err := json.NewEncoder(w).Encode(result)
320+
require.NoError(t, err)
321+
})
322+
indexer, err := docappender.NewBulkIndexer(docappender.BulkIndexerConfig{
323+
Client: client,
324+
})
325+
require.NoError(t, err)
326+
327+
for range 4 {
328+
err = indexer.Add(docappender.BulkIndexerItem{
329+
Index: "testidx",
330+
Body: newJSONReader(map[string]any{
331+
"@timestamp": time.Now().Format(docappendertest.TimestampFormat),
332+
}),
333+
})
334+
require.NoError(t, err)
335+
}
336+
337+
stat, err := indexer.Flush(context.Background())
338+
require.NoError(t, err)
339+
require.Equal(t, int64(4), stat.Indexed)
340+
require.Equal(t, int64(1), stat.FailureStoreDocs.Used)
341+
require.Equal(t, int64(1), stat.FailureStoreDocs.Failed)
342+
require.Equal(t, int64(1), stat.FailureStoreDocs.NotEnabled)
343+
}

go.mod

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
module github.com/elastic/go-docappender/v2
22

3-
go 1.22
3+
go 1.22.0
44

55
require (
6-
github.com/elastic/go-elasticsearch/v8 v8.17.0
6+
github.com/elastic/go-elasticsearch/v8 v8.17.1
77
github.com/json-iterator/go v1.1.12
88
github.com/klauspost/compress v1.18.0
99
github.com/stretchr/testify v1.10.0
@@ -23,22 +23,25 @@ require (
2323
require (
2424
github.com/armon/go-radix v1.0.0 // indirect
2525
github.com/davecgh/go-spew v1.1.1 // indirect
26-
github.com/elastic/elastic-transport-go/v8 v8.6.0 // indirect
26+
github.com/elastic/elastic-transport-go/v8 v8.6.1 // indirect
2727
github.com/elastic/go-sysinfo v1.7.1 // indirect
2828
github.com/elastic/go-windows v1.0.1 // indirect
2929
github.com/go-logr/logr v1.4.2 // indirect
3030
github.com/go-logr/stdr v1.2.2 // indirect
3131
github.com/google/go-cmp v0.6.0 // indirect
3232
github.com/google/uuid v1.6.0 // indirect
3333
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 // indirect
34+
github.com/kr/pretty v0.3.1 // indirect
3435
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
3536
github.com/modern-go/reflect2 v1.0.2 // indirect
3637
github.com/pkg/errors v0.9.1 // indirect
3738
github.com/pmezard/go-difflib v1.0.0 // indirect
3839
github.com/prometheus/procfs v0.7.3 // indirect
40+
github.com/rogpeppe/go-internal v1.13.1 // indirect
3941
go.elastic.co/apm/module/apmhttp/v2 v2.6.3 // indirect
4042
go.uber.org/multierr v1.10.0 // indirect
4143
golang.org/x/sys v0.27.0 // indirect
44+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
4245
gopkg.in/yaml.v3 v3.0.1 // indirect
4346
howett.net/plist v1.0.0 // indirect
4447
)

go.sum

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
22
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
3+
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
34
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
45
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
56
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6-
github.com/elastic/elastic-transport-go/v8 v8.6.0 h1:Y2S/FBjx1LlCv5m6pWAF2kDJAHoSjSRSJCApolgfthA=
7-
github.com/elastic/elastic-transport-go/v8 v8.6.0/go.mod h1:YLHer5cj0csTzNFXoNQ8qhtGY1GTvSqPnKWKaqQE3Hk=
8-
github.com/elastic/go-elasticsearch/v8 v8.17.0 h1:e9cWksE/Fr7urDRmGPGp47Nsp4/mvNOrU8As1l2HQQ0=
9-
github.com/elastic/go-elasticsearch/v8 v8.17.0/go.mod h1:lGMlgKIbYoRvay3xWBeKahAiJOgmFDsjZC39nmO3H64=
7+
github.com/elastic/elastic-transport-go/v8 v8.6.1 h1:h2jQRqH6eLGiBSN4eZbQnJLtL4bC5b4lfVFRjw2R4e4=
8+
github.com/elastic/elastic-transport-go/v8 v8.6.1/go.mod h1:YLHer5cj0csTzNFXoNQ8qhtGY1GTvSqPnKWKaqQE3Hk=
9+
github.com/elastic/go-elasticsearch/v8 v8.17.1 h1:bOXChDoCMB4TIwwGqKd031U8OXssmWLT3UrAr9EGs3Q=
10+
github.com/elastic/go-elasticsearch/v8 v8.17.1/go.mod h1:MVJCtL+gJJ7x5jFeUmA20O7rvipX8GcQmo5iBcmaJn4=
1011
github.com/elastic/go-sysinfo v1.7.1 h1:Wx4DSARcKLllpKT2TnFVdSUJOsybqMYCNQZq1/wO+s0=
1112
github.com/elastic/go-sysinfo v1.7.1/go.mod h1:i1ZYdU10oLNfRzq4vq62BEwD2fH8KaWh6eh0ikPT9F0=
1213
github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU=
@@ -30,8 +31,10 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
3031
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
3132
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
3233
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
33-
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
3434
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
35+
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
36+
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
37+
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
3538
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
3639
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
3740
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -41,6 +44,7 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
4144
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
4245
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
4346
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
47+
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
4448
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
4549
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
4650
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -49,6 +53,9 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
4953
github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
5054
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
5155
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
56+
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
57+
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
58+
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
5259
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
5360
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
5461
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
@@ -90,8 +97,9 @@ golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
9097
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
9198
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
9299
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
93-
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
94100
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
101+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
102+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
95103
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
96104
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
97105
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

0 commit comments

Comments
 (0)