From 9912ee6ee950db2b0592c64bc7a96ec0445c295b Mon Sep 17 00:00:00 2001 From: Keith Hatton Date: Mon, 12 Feb 2018 15:10:51 +0000 Subject: [PATCH 1/5] :heavy_plus_sign: Fixing compile errors. Add some dependencies that are required when building with a clean Gopath. --- vendor/vendor.json | 42 +++++++++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/vendor/vendor.json b/vendor/vendor.json index 7fe7d17..dd87571 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -3,7 +3,7 @@ "ignore": "test", "package": [ { - "checksumSHA1": "+sjiZBzrsk91W1A3Md1azCkZadg=", + "checksumSHA1": "+Lyl4SYSRLQ7Hz2MNJlGKsmXL+8=", "path": "github.com/Financial-Times/transactionid-utils-go", "revision": "df2f00c734957c9dd651ce23ab0e0902504c7636", "revisionTime": "2017-03-28T16:39:54Z", @@ -11,7 +11,7 @@ "versionExact": "v0.2.0" }, { - "checksumSHA1": "0QBxcjRhHYiAugAhDKmT0WIBnLk=", + "checksumSHA1": "O/WTQh5sctzo4/59PLsV1VXW50U=", "path": "github.com/Financial-Times/workbalancer", "revision": "1c97e65a6e37db8a2b463f31115a1bbf88e9ce52", "revisionTime": "2018-02-09T12:10:15Z", @@ -19,13 +19,13 @@ "versionExact": "1.0.0" }, { - "checksumSHA1": "CSPbwbyzqA6sfORicn4HFtIhF/c=", + "checksumSHA1": "aENNVxKxF9h9PfvTIstiAS5I3Lg=", "path": "github.com/davecgh/go-spew/spew", "revision": "87df7c60d5820d0f8ae11afede5aa52325c09717", "revisionTime": "2018-01-29T09:57:49Z" }, { - "checksumSHA1": "I7AAXZqD3Dy5KjQ9N+2/iHzlKzc=", + "checksumSHA1": "uEp3WSlpTPjeOSMLHs5C96psK2g=", "path": "github.com/jawher/mow.cli", "revision": "a459d5906bb7a9c5eda7c4d02eec7c541120226e", "revisionTime": "2017-08-20T16:57:35Z", @@ -33,13 +33,13 @@ "versionExact": "v1.0.1" }, { - "checksumSHA1": "LuFv4/jlrmFNnDb/5SCSEPAM9vU=", + "checksumSHA1": "R3yk3OJzuq4MVfVGX5AhkkYv1ko=", "path": "github.com/pmezard/go-difflib/difflib", "revision": "792786c7400a136282c1664665ae0a8db921c6c2", "revisionTime": "2016-01-10T10:55:54Z" }, { - "checksumSHA1": "5+eS1Ticg5wkpsdNoPiST3OQcIg=", + "checksumSHA1": "8nOCTgKSFATcBfOV4+uKtXCrZv4=", "path": "github.com/sirupsen/logrus", "revision": "f006c2ac4710855cf0f916dd6b77acf6b048dc6e", "revisionTime": "2017-08-15T20:20:55Z", @@ -47,24 +47,48 @@ "versionExact": "v1.0.3" }, { - "checksumSHA1": "6thzskcj6XrMO5kW6GlRiuTDYjU=", + "checksumSHA1": "+IkucM/8+JlB9p9Ezp55ftFLo6w=", "path": "github.com/stretchr/objx", "revision": "8a3f7159479fbc75b30357fbc48f380b7320f08e", "revisionTime": "2018-01-29T17:20:03Z" }, { - "checksumSHA1": "5QKvAdxb0E47Nzn7iI25AfgAvOI=", + "checksumSHA1": "fygRczENWW2FPC+Th6W/fIZNiHM=", "path": "github.com/stretchr/testify/assert", "revision": "be8372ae8ec5c6daaed3cc28ebf73c54b737c240", "revisionTime": "2018-02-02T15:35:43Z" }, { - "checksumSHA1": "GxKV+q3Z/H3VsIiZ/dG3u88rnbc=", + "checksumSHA1": "U9/BlhwJvZcI1qVVc3aN7lqWsxk=", "path": "github.com/stretchr/testify/mock", "revision": "12b6f73e6084dad08a7c6e575284b177ecafbc71", "revisionTime": "2018-01-31T22:23:50Z", "version": "v1.2.1", "versionExact": "v1.2.1" + }, + { + "checksumSHA1": "6U7dCaxxIMjf5V02iWgyAwppczw=", + "path": "golang.org/x/crypto/ssh/terminal", + "revision": "9de5f2eaf759b4c4550b3db39fed2e9e5f86f45c", + "revisionTime": "2018-02-11T11:39:43Z" + }, + { + "checksumSHA1": "GtamqiJoL7PGHsN454AoffBFMa8=", + "path": "golang.org/x/net/context", + "revision": "f5dfe339be1d06f81b22525fe34671ee7d2c8904", + "revisionTime": "2018-02-04T03:50:36Z" + }, + { + "checksumSHA1": "CNHEeGnucEUlTHJrLS2kHtfNbws=", + "path": "golang.org/x/sys/unix", + "revision": "37707fdb30a5b38865cfb95e5aab41707daec7fd", + "revisionTime": "2018-02-02T13:35:31Z" + }, + { + "checksumSHA1": "eQq+ZoTWPjyizS9XalhZwfGjQao=", + "path": "golang.org/x/sys/windows", + "revision": "37707fdb30a5b38865cfb95e5aab41707daec7fd", + "revisionTime": "2018-02-02T13:35:31Z" } ], "rootPath": "github.com/Financial-Times/publish-failure-resolver-go" From c5e6b0ea045bb7e9c3813227d71f9c796163228e Mon Sep 17 00:00:00 2001 From: Keith Hatton Date: Mon, 12 Feb 2018 15:13:41 +0000 Subject: [PATCH 2/5] :boom: Exit with non-zero status if anything raised an error. Signal to the caller (i.e. the Jenkins job) with a non-zero status if we were unable to attempt republication of any item in the list. --- main.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index 3304f2e..91528b0 100644 --- a/main.go +++ b/main.go @@ -148,9 +148,13 @@ func main() { uuids := regSplit(*uuidList, "\\s") log.Infof("uuidList=%v", uuids) - parallelRepublisher.Republish(uuids, *republishScope, *transactionIDPrefix) + _, errs := parallelRepublisher.Republish(uuids, *republishScope, *transactionIDPrefix) log.Infof("Dealt with nUuids=%v in duration=%v", len(uuids), time.Duration(time.Now().UnixNano()-start.UnixNano())*time.Nanosecond) + + if len(errs) > 0 { + os.Exit(1) + } } err := app.Run(os.Args) if err != nil { From 64a7aac3264050881f43caa5ff0929fbe5987cad Mon Sep 17 00:00:00 2001 From: Keith Hatton Date: Mon, 12 Feb 2018 15:33:41 +0000 Subject: [PATCH 3/5] :white_check_mark: Clearer assertion in test. I'm still not sure why we set up a rate limit of 1s and then check that the time is within less than 1ms, though :confused:. --- uuidCollectionRepublisher_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/uuidCollectionRepublisher_test.go b/uuidCollectionRepublisher_test.go index ee20ad6..3f3c09f 100644 --- a/uuidCollectionRepublisher_test.go +++ b/uuidCollectionRepublisher_test.go @@ -59,7 +59,6 @@ func TestRepublishNotFound_NotFound(t *testing.T) { } func TestRepublishErrNative_Err(t *testing.T) { - start := time.Now() mockedNativeStoreClient := new(mockNativeStoreClient) mockedNativeStoreClient.On("GetNative", "methode", "f3b3b579-732b-4323-affa-a316aacad213", "tid_123").Return([]byte("native"), false, fmt.Errorf("Error 401 on native client")) mockedNotifierClient := new(mockNotifierClient) @@ -71,12 +70,14 @@ func TestRepublishErrNative_Err(t *testing.T) { scope: "content", } + start := time.Now() msg, wasFound, err := republisher.RepublishUUIDFromCollection("f3b3b579-732b-4323-affa-a316aacad213", "tid_123", collection) + now := time.Now() assert.Error(t, fmt.Errorf("Error 401 on native client"), err) assert.False(t, wasFound) assert.Nil(t, msg) - assert.True(t, time.Now().UnixNano()-start.UnixNano() < 950000, "The time limter should have no effect on native client.") + assert.WithinDuration(t, start, now, time.Nanosecond * 950000, "The time limiter should have no effect on native client.") } func TestRepublishErrNotifier_Err(t *testing.T) { From a1dca23aebc54082512c8bafe451048ab619790b Mon Sep 17 00:00:00 2001 From: Keith Hatton Date: Mon, 12 Feb 2018 16:22:11 +0000 Subject: [PATCH 4/5] Add a sequential republisher and use it unless parallelism > 1. IMO parallel republishing is a red herring; it will complete a bulk republish Jenkins job more quickly, but doesn't actually achieve the publication more quickly through the stack as our Kafka consumers process one message at a time. It's unproductive complexity. --- main.go | 10 +++- parallelRepublisher.go | 4 +- sequentialRepublisher.go | 35 ++++++++++++++ sequentialRepublisher_test.go | 88 +++++++++++++++++++++++++++++++++++ 4 files changed, 133 insertions(+), 4 deletions(-) create mode 100644 sequentialRepublisher.go create mode 100644 sequentialRepublisher_test.go diff --git a/main.go b/main.go index 91528b0..6e6ce9a 100644 --- a/main.go +++ b/main.go @@ -141,14 +141,20 @@ func main() { rateLimit := time.Duration(*rateLimitMs) * time.Millisecond uuidCollectionRepublisher := newNotifyingUCRepublisher(notifierClient, nativeStoreClient, rateLimit) uuidRepublisher := newNotifyingUUIDRepublisher(uuidCollectionRepublisher, docStoreClient, defaultCollections) - parallelRepublisher := newNotifyingParallelRepublisher(uuidRepublisher, *parallelism) + var republisher bulkRepublisher + if *parallelism > 1 { + republisher = newNotifyingParallelRepublisher(uuidRepublisher, *parallelism) + } else { + republisher = newNotifyingSequentialRepublisher(uuidRepublisher) + } + if err != nil { log.Fatalf("Couldn't create notifier client. %v", err) } uuids := regSplit(*uuidList, "\\s") log.Infof("uuidList=%v", uuids) - _, errs := parallelRepublisher.Republish(uuids, *republishScope, *transactionIDPrefix) + _, errs := republisher.Republish(uuids, *republishScope, *transactionIDPrefix) log.Infof("Dealt with nUuids=%v in duration=%v", len(uuids), time.Duration(time.Now().UnixNano()-start.UnixNano())*time.Nanosecond) diff --git a/parallelRepublisher.go b/parallelRepublisher.go index f3d5c66..a11f871 100644 --- a/parallelRepublisher.go +++ b/parallelRepublisher.go @@ -7,7 +7,7 @@ import ( log "github.com/sirupsen/logrus" ) -type parallelRepublisher interface { +type bulkRepublisher interface { Republish(uuids []string, publishScope string, tidPrefix string) ([]*okMsg, []error) } @@ -17,7 +17,7 @@ type notifyingParallelRepublisher struct { parallelism int } -func newNotifyingParallelRepublisher(uuidRepublisher uuidRepublisher, parallelism int) *notifyingParallelRepublisher { +func newNotifyingParallelRepublisher(uuidRepublisher uuidRepublisher, parallelism int) bulkRepublisher { return ¬ifyingParallelRepublisher{ uuidRepublisher: uuidRepublisher, balancer: workbalancer.NewChannelBalancer(parallelism), diff --git a/sequentialRepublisher.go b/sequentialRepublisher.go new file mode 100644 index 0000000..e2fa026 --- /dev/null +++ b/sequentialRepublisher.go @@ -0,0 +1,35 @@ +package main + +import ( + log "github.com/sirupsen/logrus" +) + +type notifyingSequentialRepublisher struct { + uuidRepublisher uuidRepublisher +} + +func newNotifyingSequentialRepublisher(uuidRepublisher uuidRepublisher) bulkRepublisher { + return ¬ifyingSequentialRepublisher{ + uuidRepublisher: uuidRepublisher, + } +} + +func (r *notifyingSequentialRepublisher) Republish(uuids []string, publishScope string, tidPrefix string) ([]*okMsg, []error) { + var msgs []*okMsg + var errs []error + + for _, uuid := range uuids { + tmsgs, terrs := r.uuidRepublisher.Republish(uuid, tidPrefix, publishScope) + + for _, msg := range tmsgs { + log.Info(msg) + msgs = append(msgs, msg) + } + for _, err := range terrs { + log.Error(err) + errs = append(errs, err) + } + } + + return msgs, errs +} diff --git a/sequentialRepublisher_test.go b/sequentialRepublisher_test.go new file mode 100644 index 0000000..b9ce213 --- /dev/null +++ b/sequentialRepublisher_test.go @@ -0,0 +1,88 @@ +package main + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestSequentialRepublishSingle_Ok(t *testing.T) { + mockedUUIDRepublisher := new(mockUUIDRepublisher) + msg := okMsg{ + uuid: "19cf2763-90b1-40db-90e7-e813425ebe81", + tid: "prefix1", + collectionName: "collection1", + collectionOriginSystemID: "originSystemId1", + sizeBytes: 1024, + notifierAppName: "cms-notifier", + } + mockedUUIDRepublisher.On("Republish", "19cf2763-90b1-40db-90e7-e813425ebe81", "prefix1", scopeBoth).Return([]*okMsg{&msg}, []error{}) + + pRepublisher := newNotifyingSequentialRepublisher(mockedUUIDRepublisher) + + pRepublisher.Republish([]string{"19cf2763-90b1-40db-90e7-e813425ebe81"}, scopeBoth, "prefix1") + + mock.AssertExpectationsForObjects(t, mockedUUIDRepublisher) +} + +func TestRepublishMultiple_Ok(t *testing.T) { + mockedUUIDRepublisher := new(mockUUIDRepublisher) + nOk := 10 + nErr := 5 + uuids := []string{} + for i := 0; i < nOk; i++ { + uuids = append(uuids, "19cf2763-90b1-40db-90e7-e813425ebe81") + } + for i := 0; i < nErr; i++ { + uuids = append(uuids, "70357268-04f7-4149-bb17-217d3eb56d49") + } + msg1 := okMsg{ + uuid: "19cf2763-90b1-40db-90e7-e813425ebe81", + tid: "prefix1tid_123", + collectionName: "collection1", + collectionOriginSystemID: "originSystemId1", + sizeBytes: 1024, + notifierAppName: "cms-notifier", + } + msg2 := okMsg{ + uuid: "19cf2763-90b1-40db-90e7-e813425ebe81", + tid: "prefix1tid_456", + collectionName: "collection2", + collectionOriginSystemID: "originSystemId1", + sizeBytes: 1024, + notifierAppName: "cms-notifier", + } + err1 := fmt.Errorf("test some error publishing 1") + err2 := fmt.Errorf("test some error publishing 2") + mockedUUIDRepublisher.On("Republish", "19cf2763-90b1-40db-90e7-e813425ebe81", "prefix1", scopeBoth).Times(nOk).Return([]*okMsg{&msg1, &msg2}, []error{}) + mockedUUIDRepublisher.On("Republish", "70357268-04f7-4149-bb17-217d3eb56d49", "prefix1", scopeBoth).Times(nErr).Return([]*okMsg{}, []error{err1, err2}) + pRepublisher := newNotifyingSequentialRepublisher(mockedUUIDRepublisher) + + actualMsgs, actualErrs := pRepublisher.Republish(uuids, scopeBoth, "prefix1") + + mock.AssertExpectationsForObjects(t, mockedUUIDRepublisher) + assert.Equal(t, 2*nOk, len(actualMsgs)) + var msg1equal, msg2equal int + for _, actualMsg := range actualMsgs { + if msg1 == *actualMsg { + msg1equal++ + } else if msg2 == *actualMsg { + msg2equal++ + } + } + assert.Equal(t, nOk, msg1equal) + assert.Equal(t, nOk, msg2equal) + assert.Equal(t, 2*nErr, len(actualErrs)) + var err1equal, err2equal int + for _, actualErr := range actualErrs { + if err1 == actualErr { + err1equal++ + } else if err2 == actualErr { + err2equal++ + } + } + assert.Equal(t, nErr, err1equal) + assert.Equal(t, nErr, err2equal) +} From 66154eb46ea9790c411fb185997b70dda49ef642 Mon Sep 17 00:00:00 2001 From: Keith Hatton Date: Mon, 12 Feb 2018 16:59:43 +0000 Subject: [PATCH 5/5] :sparkles: Avoid calling the document store for image sets. Using the document store to discover image model UUIDs for republishing is only applicable if the image set has been published successfully to at least one environment. Although simply generating the alternative UUID when it's not found directly may seem less robust, the probability that we find and republish the wrong piece of content in this case is vanishingly small. --- docStoreClient.go | 6 +----- imageSetResolver.go | 22 ++++++++++++++++++++++ imageSetResolver_test.go | 18 ++++++++++++++++++ main.go | 9 +++++++-- uuidRepublisher.go | 16 ++++++++-------- vendor/vendor.json | 18 ++++++++++++++++++ 6 files changed, 74 insertions(+), 15 deletions(-) create mode 100644 imageSetResolver.go create mode 100644 imageSetResolver_test.go diff --git a/docStoreClient.go b/docStoreClient.go index e272d16..2481d5a 100644 --- a/docStoreClient.go +++ b/docStoreClient.go @@ -12,17 +12,13 @@ import ( log "github.com/sirupsen/logrus" ) -type docStoreClient interface { - GetImageSetsModelUUID(setUUID, tid string) (found bool, modelUUID string, err error) -} - type httpDocStore struct { httpClient *http.Client docStoreAddressBase string authHeader string } -func newHTTPDocStore(httpClient *http.Client, docStoreAddressBase, authHeader string) (*httpDocStore, error) { +func newHTTPDocStore(httpClient *http.Client, docStoreAddressBase, authHeader string) (imageSetUUIDResolver, error) { return &httpDocStore{ httpClient: httpClient, docStoreAddressBase: docStoreAddressBase, diff --git a/imageSetResolver.go b/imageSetResolver.go new file mode 100644 index 0000000..d4afdf5 --- /dev/null +++ b/imageSetResolver.go @@ -0,0 +1,22 @@ +package main + +import ( + "github.com/Financial-Times/uuid-utils-go" +) + +type imageSetUUIDResolver interface { + GetImageSetsModelUUID(setUUID, tid string) (found bool, modelUUID string, err error) +} + +type uuidImageSetResolver struct { +} + +func newUUIDImageSetResolver() imageSetUUIDResolver { + return &uuidImageSetResolver{} +} + +func (r *uuidImageSetResolver) GetImageSetsModelUUID(setUUID, tid string) (found bool, modelUUID string, err error) { + requestedUUID, _ := uuidutils.NewUUIDFromString(setUUID) + derivedUUID, _ := uuidutils.NewUUIDDeriverWith(uuidutils.IMAGE_SET).From(requestedUUID) + return true, derivedUUID.String(), nil +} diff --git a/imageSetResolver_test.go b/imageSetResolver_test.go new file mode 100644 index 0000000..9bbcda5 --- /dev/null +++ b/imageSetResolver_test.go @@ -0,0 +1,18 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestUuidImageSetResolver(t *testing.T) { + r := newUUIDImageSetResolver() + + imageSetUUID := "9f365884-0c25-11e8-24ad-bec2279df517" + + found, imageModelUUID, err := r.GetImageSetsModelUUID(imageSetUUID, "tid_test") + assert.True(t, found, "found image model UUID") + assert.Equal(t, "9f365884-0c25-11e8-bacb-2958fde95e5e", imageModelUUID, "image model UUID") + assert.NoError(t, err) +} diff --git a/main.go b/main.go index 6e6ce9a..5674e9d 100644 --- a/main.go +++ b/main.go @@ -137,10 +137,15 @@ func main() { httpClient := setupHTTPClient() nativeStoreClient := newNativeStoreClient(httpClient, "https://"+*sourceEnvHost+"/__nativerw/", "Basic "+base64.StdEncoding.EncodeToString([]byte(*sourceAuth))) notifierClient, err := newHTTPNotifier(httpClient, "https://"+*targetEnvHost+"/__", "Basic "+base64.StdEncoding.EncodeToString([]byte(*targetAuth))) - docStoreClient, err := newHTTPDocStore(httpClient, "https://"+*deliveryEnvHost+"/__document-store-api/content", "Basic "+base64.StdEncoding.EncodeToString([]byte(*deliveryAuth))) + var imageSetResolver imageSetUUIDResolver + if *deliveryEnvHost == "" || *deliveryAuth == "" { + imageSetResolver = newUUIDImageSetResolver() + } else { + imageSetResolver, err = newHTTPDocStore(httpClient, "https://"+*deliveryEnvHost+"/__document-store-api/content", "Basic "+base64.StdEncoding.EncodeToString([]byte(*deliveryAuth))) + } rateLimit := time.Duration(*rateLimitMs) * time.Millisecond uuidCollectionRepublisher := newNotifyingUCRepublisher(notifierClient, nativeStoreClient, rateLimit) - uuidRepublisher := newNotifyingUUIDRepublisher(uuidCollectionRepublisher, docStoreClient, defaultCollections) + uuidRepublisher := newNotifyingUUIDRepublisher(uuidCollectionRepublisher, imageSetResolver, defaultCollections) var republisher bulkRepublisher if *parallelism > 1 { republisher = newNotifyingParallelRepublisher(uuidRepublisher, *parallelism) diff --git a/uuidRepublisher.go b/uuidRepublisher.go index 410fbdb..4201b47 100644 --- a/uuidRepublisher.go +++ b/uuidRepublisher.go @@ -12,16 +12,16 @@ type uuidRepublisher interface { } type notifyingUUIDRepublisher struct { - ucRepublisher uuidCollectionRepublisher - docStoreClient docStoreClient - collections map[string]targetSystem + ucRepublisher uuidCollectionRepublisher + imageSetResolver imageSetUUIDResolver + collections map[string]targetSystem } -func newNotifyingUUIDRepublisher(uuidCollectionRepublisher uuidCollectionRepublisher, docStoreClient docStoreClient, collections map[string]targetSystem) *notifyingUUIDRepublisher { +func newNotifyingUUIDRepublisher(uuidCollectionRepublisher uuidCollectionRepublisher, imageSetResolver imageSetUUIDResolver, collections map[string]targetSystem) *notifyingUUIDRepublisher { return ¬ifyingUUIDRepublisher{ - ucRepublisher: uuidCollectionRepublisher, - docStoreClient: docStoreClient, - collections: collections, + ucRepublisher: uuidCollectionRepublisher, + imageSetResolver: imageSetResolver, + collections: collections, } } @@ -49,7 +49,7 @@ func (r *notifyingUUIDRepublisher) Republish(uuid, tidPrefix string, republishSc if !isFoundInAnyCollection && isScopedInAnyCollection { tid := tidPrefix + transactionidutils.NewTransactionID() - isFoundAsImageSet, imageModelUUID, err := r.docStoreClient.GetImageSetsModelUUID(uuid, tid) + isFoundAsImageSet, imageModelUUID, err := r.imageSetResolver.GetImageSetsModelUUID(uuid, tid) if err != nil { errs = append(errs, fmt.Errorf("couldn't check if it's an ImageSet containing an image inside because of an error uuid=%v tid=%v %v", uuid, tid, err)) return nil, errs diff --git a/vendor/vendor.json b/vendor/vendor.json index dd87571..823d063 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -10,6 +10,14 @@ "version": "v0.2.0", "versionExact": "v0.2.0" }, + { + "checksumSHA1": "SFO+8Q3GAkd8e/LaTdsJRu4OZLA=", + "path": "github.com/Financial-Times/uuid-utils-go", + "revision": "e22658edd0f130936e99079bf844bde7087c914c", + "revisionTime": "2017-05-16T11:04:27Z", + "version": "1.0.1", + "versionExact": "1.0.1" + }, { "checksumSHA1": "O/WTQh5sctzo4/59PLsV1VXW50U=", "path": "github.com/Financial-Times/workbalancer", @@ -66,6 +74,12 @@ "version": "v1.2.1", "versionExact": "v1.2.1" }, + { + "checksumSHA1": "Zqzba4X+lRAiAEn13WdFoQpn+RI=", + "path": "github.com/willf/bitset", + "revision": "1a37ad96e8c1a11b20900a232874843b5174221f", + "revisionTime": "2017-09-05T00:26:39Z" + }, { "checksumSHA1": "6U7dCaxxIMjf5V02iWgyAwppczw=", "path": "golang.org/x/crypto/ssh/terminal", @@ -89,6 +103,10 @@ "path": "golang.org/x/sys/windows", "revision": "37707fdb30a5b38865cfb95e5aab41707daec7fd", "revisionTime": "2018-02-02T13:35:31Z" + }, + { + "path": "math/bits", + "revision": "" } ], "rootPath": "github.com/Financial-Times/publish-failure-resolver-go"