Add checkpoint interface for custom checkpoints (#29)

To allow other checkpoint backends we extract a checkpoint interface
which future checkpoints can implement.

* Add checkpoint interface for custom checkpoints
* Create RedisCheckpoint to implement checkpoint interface
* Swap out hosie redis library for go-redis

Minor changes

* Allow configuration of Redis endpoint with env var `REDIS_URL`
* Replace gvt with govendor
This commit is contained in:
Harlow Ward 2016-12-04 00:08:06 -08:00 committed by GitHub
parent cc936aed04
commit fedb6812fb
10 changed files with 469 additions and 271 deletions

2
.gitignore vendored
View file

@ -38,7 +38,7 @@ tags*
# Vendored files
vendor/**
!vendor/manifest
!vendor/vendor.json
# Benchmark files
prof.cpu

View file

@ -36,6 +36,14 @@ func main() {
}
```
### Config
The default behavior for checkpointing uses Redis on localhost. To set a custom Redis URL use ENV vars:
```
REDIS_URL=my-custom-redis-server.com:6379
```
### Logging
[Apex Log](https://medium.com/@tjholowaychuk/apex-log-e8d9627f4a9a#.5x1uo1767) is used for logging Info. Override the logs format with other [Log Handlers](https://github.com/apex/log/tree/master/_examples). For example using the "json" log handler:
@ -70,14 +78,14 @@ Get the package source:
### Fetching Dependencies
Install `gvt`:
Install `govendor`:
$ export GO15VENDOREXPERIMENT=1
$ go get github.com/FiloSottile/gvt
$ go get -u github.com/kardianos/govendor
Install dependencies into `./vendor/`:
$ gvt restore
$ govendor sync
### Examples
@ -86,7 +94,6 @@ Use the [seed stream](https://github.com/harlow/kinesis-connectors/tree/master/e
* [Firehose](https://github.com/harlow/kinesis-connectors/tree/master/examples/firehose)
* [S3](https://github.com/harlow/kinesis-connectors/tree/master/examples/s3)
## Contributing
Please see [CONTRIBUTING.md] for more information. Thank you, [contributors]!

View file

@ -1,48 +1,9 @@
package connector
import (
"fmt"
"github.com/hoisie/redis"
)
// RedisCheckpoint implements the Checkpont interface.
// This class is used to enable the Pipeline.ProcessShard to checkpoint their progress.
type Checkpoint struct {
AppName string
StreamName string
client redis.Client
sequenceNumber string
}
// CheckpointExists determines if a checkpoint for a particular Shard exists.
// Typically used to determine whether we should start processing the shard with
// TRIM_HORIZON or AFTER_SEQUENCE_NUMBER (if checkpoint exists).
func (c *Checkpoint) CheckpointExists(shardID string) bool {
val, _ := c.client.Get(c.key(shardID))
if val != nil && string(val) != "" {
c.sequenceNumber = string(val)
return true
}
return false
}
// SequenceNumber returns the current checkpoint stored for the specified shard.
func (c *Checkpoint) SequenceNumber() string {
return c.sequenceNumber
}
// SetCheckpoint stores a checkpoint for a shard (e.g. sequence number of last record processed by application).
// Upon failover, record processing is resumed from this point.
func (c *Checkpoint) SetCheckpoint(shardID string, sequenceNumber string) {
c.client.Set(c.key(shardID), []byte(sequenceNumber))
c.sequenceNumber = sequenceNumber
}
// key generates a unique Redis key for storage of Checkpoint.
func (c *Checkpoint) key(shardID string) string {
return fmt.Sprintf("%v:checkpoint:%v:%v", c.AppName, c.StreamName, shardID)
// Checkpoint interface for functions that checkpoints need to
// implement in order to track consumer progress.
type Checkpoint interface {
CheckpointExists(shardID string) bool
SequenceNumber() string
SetCheckpoint(shardID string, sequenceNumber string)
}

View file

@ -1,46 +0,0 @@
package connector
import (
"testing"
"github.com/bmizerany/assert"
"github.com/hoisie/redis"
)
func Test_key(t *testing.T) {
c := Checkpoint{
AppName: "app",
StreamName: "stream",
}
k := c.key("shard")
assert.Equal(t, k, "app:checkpoint:stream:shard")
}
func Test_CheckpointExists(t *testing.T) {
var rc redis.Client
rc.Set("app:checkpoint:stream:shard", []byte("testSeqNum"))
c := Checkpoint{
AppName: "app",
StreamName: "stream",
}
r := c.CheckpointExists("shard")
assert.Equal(t, r, true)
rc.Del("app:checkpoint:stream:shard")
}
func Test_SetCheckpoint(t *testing.T) {
var rc redis.Client
c := Checkpoint{
AppName: "app",
StreamName: "stream",
}
c.SetCheckpoint("shard", "testSeqNum")
r, _ := rc.Get("app:checkpoint:stream:shard")
assert.Equal(t, string(r), "testSeqNum")
rc.Del("app:checkpoint:stream:shard")
}

View file

@ -4,15 +4,19 @@ import (
"os"
"time"
redis "gopkg.in/redis.v5"
"github.com/apex/log"
)
const (
defaultBufferSize = 500
defaultRedisAddr = "127.0.0.1:6379"
)
// Config vars for the application
type Config struct {
// AppName is the application name.
// AppName is the application name and checkpoint namespace.
AppName string
// StreamName is the Kinesis stream.
@ -26,6 +30,9 @@ type Config struct {
// Logger is the logger used. Defaults to log.Log.
Logger log.Interface
// Checkpoint for tracking progress of consumer.
Checkpoint Checkpoint
}
// defaults for configuration.
@ -60,4 +67,32 @@ func (c *Config) setDefaults() {
if c.FlushInterval == 0 {
c.FlushInterval = time.Second
}
if c.Checkpoint == nil {
client, err := redisClient()
if err != nil {
c.Logger.WithError(err).Error("Redis connection failed")
os.Exit(1)
}
c.Checkpoint = &RedisCheckpoint{
AppName: c.AppName,
StreamName: c.StreamName,
client: client,
}
}
}
func redisClient() (*redis.Client, error) {
redisURL := os.Getenv("REDIS_URL")
if redisURL == "" {
redisURL = defaultRedisAddr
}
client := redis.NewClient(&redis.Options{
Addr: redisURL,
})
_, err := client.Ping().Result()
if err != nil {
return nil, err
}
return client, nil
}

View file

@ -54,19 +54,14 @@ func (c *Consumer) handlerLoop(shardID string, handler Handler) {
MaxRecordCount: c.BufferSize,
}
checkpoint := &Checkpoint{
AppName: c.AppName,
StreamName: c.StreamName,
}
params := &kinesis.GetShardIteratorInput{
ShardId: aws.String(shardID),
StreamName: aws.String(c.StreamName),
}
if checkpoint.CheckpointExists(shardID) {
if c.Checkpoint.CheckpointExists(shardID) {
params.ShardIteratorType = aws.String("AFTER_SEQUENCE_NUMBER")
params.StartingSequenceNumber = aws.String(checkpoint.SequenceNumber())
params.StartingSequenceNumber = aws.String(c.Checkpoint.SequenceNumber())
} else {
params.ShardIteratorType = aws.String("TRIM_HORIZON")
}
@ -103,7 +98,7 @@ func (c *Consumer) handlerLoop(shardID string, handler Handler) {
if buf.ShouldFlush() {
handler.HandleRecords(*buf)
ctx.WithField("count", buf.RecordCount()).Info("flushed")
checkpoint.SetCheckpoint(shardID, buf.LastSeq())
c.Checkpoint.SetCheckpoint(shardID, buf.LastSeq())
buf.Flush()
}
}

53
redis_checkpoint.go Normal file
View file

@ -0,0 +1,53 @@
package connector
import (
"fmt"
"log"
"gopkg.in/redis.v5"
)
// RedisCheckpoint implements the Checkpont interface.
// Used to enable the Pipeline.ProcessShard to checkpoint it's progress
// while reading records from Kinesis stream.
type RedisCheckpoint struct {
AppName string
StreamName string
client *redis.Client
sequenceNumber string
}
// CheckpointExists determines if a checkpoint for a particular Shard exists.
// Typically used to determine whether we should start processing the shard with
// TRIM_HORIZON or AFTER_SEQUENCE_NUMBER (if checkpoint exists).
func (c *RedisCheckpoint) CheckpointExists(shardID string) bool {
val, _ := c.client.Get(c.key(shardID)).Result()
if val != "" {
c.sequenceNumber = val
return true
}
return false
}
// SequenceNumber returns the current checkpoint stored for the specified shard.
func (c *RedisCheckpoint) SequenceNumber() string {
return c.sequenceNumber
}
// SetCheckpoint stores a checkpoint for a shard (e.g. sequence number of last record processed by application).
// Upon failover, record processing is resumed from this point.
func (c *RedisCheckpoint) SetCheckpoint(shardID string, sequenceNumber string) {
err := c.client.Set(c.key(shardID), sequenceNumber, 0).Err()
if err != nil {
log.Printf("redis checkpoint set error: %v", err)
}
c.sequenceNumber = sequenceNumber
}
// key generates a unique Redis key for storage of Checkpoint.
func (c *RedisCheckpoint) key(shardID string) string {
return fmt.Sprintf("%v:checkpoint:%v:%v", c.AppName, c.StreamName, shardID)
}

50
redis_checkpoint_test.go Normal file
View file

@ -0,0 +1,50 @@
package connector
import (
"testing"
"gopkg.in/redis.v5"
)
var defaultAddr = "127.0.0.1:6379"
func Test_CheckpointLifecycle(t *testing.T) {
client := redis.NewClient(&redis.Options{Addr: defaultAddr})
c := RedisCheckpoint{
AppName: "app",
StreamName: "stream",
client: client,
}
// set checkpoint
c.SetCheckpoint("shard_id", "testSeqNum")
// checkpoint exists
if val := c.CheckpointExists("shard_id"); val != true {
t.Fatalf("checkpoint exists expected true, got %t", val)
}
// get checkpoint
if val := c.SequenceNumber(); val != "testSeqNum" {
t.Fatalf("checkpoint exists expected %s, got %s", "testSeqNum", val)
}
client.Del("app:checkpoint:stream:shard_id")
}
func Test_key(t *testing.T) {
client := redis.NewClient(&redis.Options{Addr: defaultAddr})
c := &RedisCheckpoint{
AppName: "app",
StreamName: "stream",
client: client,
}
expected := "app:checkpoint:stream:shard"
if val := c.key("shard"); val != expected {
t.Fatalf("checkpoint exists expected %s, got %s", expected, val)
}
}

166
vendor/manifest vendored
View file

@ -1,166 +0,0 @@
{
"version": 0,
"dependencies": [
{
"importpath": "github.com/apex/log",
"repository": "https://github.com/apex/log",
"vcs": "git",
"revision": "c20bfcdb771b706fdd57a954ee9da2d0ff97b02d",
"branch": "master",
"notests": true
},
{
"importpath": "github.com/apex/log/handlers/discard",
"repository": "https://github.com/apex/log",
"vcs": "git",
"revision": "c20bfcdb771b706fdd57a954ee9da2d0ff97b02d",
"branch": "master",
"path": "/handlers/discard",
"notests": true
},
{
"importpath": "github.com/aws/aws-sdk-go/aws",
"repository": "https://github.com/aws/aws-sdk-go",
"vcs": "git",
"revision": "966f1a968ba8e260628dfa6be07bcd48d8e98ef6",
"branch": "master",
"path": "/aws",
"notests": true
},
{
"importpath": "github.com/aws/aws-sdk-go/aws/session",
"repository": "https://github.com/aws/aws-sdk-go",
"vcs": "git",
"revision": "966f1a968ba8e260628dfa6be07bcd48d8e98ef6",
"branch": "master",
"path": "/aws/session",
"notests": true
},
{
"importpath": "github.com/aws/aws-sdk-go/private/endpoints",
"repository": "https://github.com/aws/aws-sdk-go",
"vcs": "git",
"revision": "966f1a968ba8e260628dfa6be07bcd48d8e98ef6",
"branch": "master",
"path": "/private/endpoints",
"notests": true
},
{
"importpath": "github.com/aws/aws-sdk-go/private/protocol",
"repository": "https://github.com/aws/aws-sdk-go",
"vcs": "git",
"revision": "966f1a968ba8e260628dfa6be07bcd48d8e98ef6",
"branch": "master",
"path": "/private/protocol",
"notests": true
},
{
"importpath": "github.com/aws/aws-sdk-go/private/signer/v4",
"repository": "https://github.com/aws/aws-sdk-go",
"vcs": "git",
"revision": "966f1a968ba8e260628dfa6be07bcd48d8e98ef6",
"branch": "master",
"path": "/private/signer/v4",
"notests": true
},
{
"importpath": "github.com/aws/aws-sdk-go/private/waiter",
"repository": "https://github.com/aws/aws-sdk-go",
"vcs": "git",
"revision": "966f1a968ba8e260628dfa6be07bcd48d8e98ef6",
"branch": "master",
"path": "/private/waiter",
"notests": true
},
{
"importpath": "github.com/aws/aws-sdk-go/service/kinesis",
"repository": "https://github.com/aws/aws-sdk-go",
"vcs": "git",
"revision": "966f1a968ba8e260628dfa6be07bcd48d8e98ef6",
"branch": "master",
"path": "/service/kinesis",
"notests": true
},
{
"importpath": "github.com/go-ini/ini",
"repository": "https://github.com/go-ini/ini",
"vcs": "git",
"revision": "12f418cc7edc5a618a51407b7ac1f1f512139df3",
"branch": "master",
"notests": true
},
{
"importpath": "github.com/go-logfmt/logfmt",
"repository": "https://github.com/go-logfmt/logfmt",
"vcs": "git",
"revision": "08ab82a63ef462ac643ec79e659f023891f588f5",
"branch": "master",
"notests": true
},
{
"importpath": "github.com/hoisie/redis",
"repository": "https://github.com/hoisie/redis",
"vcs": "git",
"revision": "788f01e396a99c96c8f56338383926f16841ebae",
"branch": "master",
"notests": true
},
{
"importpath": "github.com/jmespath/go-jmespath",
"repository": "https://github.com/jmespath/go-jmespath",
"vcs": "git",
"revision": "0b12d6b521d83fc7f755e7cfc1b1fbdd35a01a74",
"branch": "master",
"notests": true
},
{
"importpath": "github.com/jpillora/backoff",
"repository": "https://github.com/jpillora/backoff",
"vcs": "git",
"revision": "0496a6c14df020789376f4d4a261273d5ddb36ec",
"branch": "master",
"notests": true
},
{
"importpath": "github.com/kr/pretty",
"repository": "https://github.com/kr/pretty",
"vcs": "git",
"revision": "add1dbc86daf0f983cd4a48ceb39deb95c729b67",
"branch": "master",
"notests": true
},
{
"importpath": "github.com/kr/text",
"repository": "https://github.com/kr/text",
"vcs": "git",
"revision": "bb797dc4fb8320488f47bf11de07a733d7233e1f",
"branch": "master",
"notests": true
},
{
"importpath": "github.com/rogpeppe/fastuuid",
"repository": "https://github.com/rogpeppe/fastuuid",
"vcs": "git",
"revision": "6724a57986aff9bff1a1770e9347036def7c89f6",
"branch": "master",
"notests": true
},
{
"importpath": "github.com/tj/go-elastic/batch",
"repository": "https://github.com/tj/go-elastic",
"vcs": "git",
"revision": "6923cdab333be878d768a2b563cd25bc816c3dab",
"branch": "master",
"path": "/batch",
"notests": true
},
{
"importpath": "github.com/tj/go-kinesis",
"repository": "https://github.com/tj/go-kinesis",
"vcs": "git",
"revision": "677d118cb62c634d6d8dc7c49e979c4d40fac58b",
"branch": "master",
"notests": true
}
]
}

309
vendor/vendor.json vendored Normal file
View file

@ -0,0 +1,309 @@
{
"comment": "",
"ignore": "test",
"package": [
{
"checksumSHA1": "Ur88QI//9Ue82g83qvBSakGlzVg=",
"path": "github.com/apex/log",
"revision": "4ea85e918cc8389903d5f12d7ccac5c23ab7d89b",
"revisionTime": "2016-09-05T15:13:04Z"
},
{
"checksumSHA1": "o5a5xWoaGDKEnNy0W7TikB66lMc=",
"path": "github.com/apex/log/handlers/text",
"revision": "4ea85e918cc8389903d5f12d7ccac5c23ab7d89b",
"revisionTime": "2016-09-05T15:13:04Z"
},
{
"checksumSHA1": "dSo0vFXJGuTtd6H80q8ZczLszJM=",
"path": "github.com/aws/aws-sdk-go/aws",
"revision": "f34b74c96bfd27df35643adeb14d8431ca047df5",
"revisionTime": "2016-08-17T18:35:19Z"
},
{
"checksumSHA1": "Y9W+4GimK4Fuxq+vyIskVYFRnX4=",
"path": "github.com/aws/aws-sdk-go/aws/awserr",
"revision": "f34b74c96bfd27df35643adeb14d8431ca047df5",
"revisionTime": "2016-08-17T18:35:19Z"
},
{
"checksumSHA1": "dkfyy7aRNZ6BmUZ4ZdLIcMMXiPA=",
"path": "github.com/aws/aws-sdk-go/aws/awsutil",
"revision": "f34b74c96bfd27df35643adeb14d8431ca047df5",
"revisionTime": "2016-08-17T18:35:19Z"
},
{
"checksumSHA1": "H/tMKHZU+Qka6RtYiGB50s2uA0s=",
"path": "github.com/aws/aws-sdk-go/aws/client",
"revision": "f34b74c96bfd27df35643adeb14d8431ca047df5",
"revisionTime": "2016-08-17T18:35:19Z"
},
{
"checksumSHA1": "ieAJ+Cvp/PKv1LpUEnUXpc3OI6E=",
"path": "github.com/aws/aws-sdk-go/aws/client/metadata",
"revision": "f34b74c96bfd27df35643adeb14d8431ca047df5",
"revisionTime": "2016-08-17T18:35:19Z"
},
{
"checksumSHA1": "gNWirlrTfSLbOe421hISBAhTqa4=",
"path": "github.com/aws/aws-sdk-go/aws/corehandlers",
"revision": "f34b74c96bfd27df35643adeb14d8431ca047df5",
"revisionTime": "2016-08-17T18:35:19Z"
},
{
"checksumSHA1": "dNZNaOPfBPnzE2CBnfhXXZ9g9jU=",
"path": "github.com/aws/aws-sdk-go/aws/credentials",
"revision": "f34b74c96bfd27df35643adeb14d8431ca047df5",
"revisionTime": "2016-08-17T18:35:19Z"
},
{
"checksumSHA1": "KQiUK/zr3mqnAXD7x/X55/iNme0=",
"path": "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds",
"revision": "f34b74c96bfd27df35643adeb14d8431ca047df5",
"revisionTime": "2016-08-17T18:35:19Z"
},
{
"checksumSHA1": "NUJUTWlc1sV8b7WjfiYc4JZbXl0=",
"path": "github.com/aws/aws-sdk-go/aws/credentials/endpointcreds",
"revision": "f34b74c96bfd27df35643adeb14d8431ca047df5",
"revisionTime": "2016-08-17T18:35:19Z"
},
{
"checksumSHA1": "4Ipx+5xN0gso+cENC2MHMWmQlR4=",
"path": "github.com/aws/aws-sdk-go/aws/credentials/stscreds",
"revision": "f34b74c96bfd27df35643adeb14d8431ca047df5",
"revisionTime": "2016-08-17T18:35:19Z"
},
{
"checksumSHA1": "nCMd1XKjgV21bEl7J8VZFqTV8PE=",
"path": "github.com/aws/aws-sdk-go/aws/defaults",
"revision": "f34b74c96bfd27df35643adeb14d8431ca047df5",
"revisionTime": "2016-08-17T18:35:19Z"
},
{
"checksumSHA1": "U0SthWum+t9ACanK7SDJOg3dO6M=",
"path": "github.com/aws/aws-sdk-go/aws/ec2metadata",
"revision": "f34b74c96bfd27df35643adeb14d8431ca047df5",
"revisionTime": "2016-08-17T18:35:19Z"
},
{
"checksumSHA1": "NyUg1P8ZS/LHAAQAk/4C5O4X3og=",
"path": "github.com/aws/aws-sdk-go/aws/request",
"revision": "f34b74c96bfd27df35643adeb14d8431ca047df5",
"revisionTime": "2016-08-17T18:35:19Z"
},
{
"checksumSHA1": "tBdFneml1Vn7uvezcktsa+hUsGg=",
"path": "github.com/aws/aws-sdk-go/aws/session",
"revision": "f34b74c96bfd27df35643adeb14d8431ca047df5",
"revisionTime": "2016-08-17T18:35:19Z"
},
{
"checksumSHA1": "7lla+sckQeF18wORAGuU2fFMlp4=",
"path": "github.com/aws/aws-sdk-go/aws/signer/v4",
"revision": "f34b74c96bfd27df35643adeb14d8431ca047df5",
"revisionTime": "2016-08-17T18:35:19Z"
},
{
"checksumSHA1": "Bm6UrYb2QCzpYseLwwgw6aetgRc=",
"path": "github.com/aws/aws-sdk-go/private/endpoints",
"revision": "f34b74c96bfd27df35643adeb14d8431ca047df5",
"revisionTime": "2016-08-17T18:35:19Z"
},
{
"checksumSHA1": "wk7EyvDaHwb5qqoOP/4d3cV0708=",
"path": "github.com/aws/aws-sdk-go/private/protocol",
"revision": "f34b74c96bfd27df35643adeb14d8431ca047df5",
"revisionTime": "2016-08-17T18:35:19Z"
},
{
"checksumSHA1": "L7xWYwx0jNQnzlYHwBS+1q6DcCI=",
"path": "github.com/aws/aws-sdk-go/private/protocol/json/jsonutil",
"revision": "f34b74c96bfd27df35643adeb14d8431ca047df5",
"revisionTime": "2016-08-17T18:35:19Z"
},
{
"checksumSHA1": "H9TymcQkQnXSXSVfjggiiS4bpzM=",
"path": "github.com/aws/aws-sdk-go/private/protocol/jsonrpc",
"revision": "f34b74c96bfd27df35643adeb14d8431ca047df5",
"revisionTime": "2016-08-17T18:35:19Z"
},
{
"checksumSHA1": "isoix7lTx4qIq2zI2xFADtti5SI=",
"path": "github.com/aws/aws-sdk-go/private/protocol/query",
"revision": "f34b74c96bfd27df35643adeb14d8431ca047df5",
"revisionTime": "2016-08-17T18:35:19Z"
},
{
"checksumSHA1": "5xzix1R8prUyWxgLnzUQoxTsfik=",
"path": "github.com/aws/aws-sdk-go/private/protocol/query/queryutil",
"revision": "f34b74c96bfd27df35643adeb14d8431ca047df5",
"revisionTime": "2016-08-17T18:35:19Z"
},
{
"checksumSHA1": "TW/7U+/8ormL7acf6z2rv2hDD+s=",
"path": "github.com/aws/aws-sdk-go/private/protocol/rest",
"revision": "f34b74c96bfd27df35643adeb14d8431ca047df5",
"revisionTime": "2016-08-17T18:35:19Z"
},
{
"checksumSHA1": "Y6Db2GGfGD9LPpcJIPj8vXE8BbQ=",
"path": "github.com/aws/aws-sdk-go/private/protocol/restxml",
"revision": "f34b74c96bfd27df35643adeb14d8431ca047df5",
"revisionTime": "2016-08-17T18:35:19Z"
},
{
"checksumSHA1": "eUEkjyMPAuekKBE4ou+nM9tXEas=",
"path": "github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil",
"revision": "f34b74c96bfd27df35643adeb14d8431ca047df5",
"revisionTime": "2016-08-17T18:35:19Z"
},
{
"checksumSHA1": "Eo9yODN5U99BK0pMzoqnBm7PCrY=",
"path": "github.com/aws/aws-sdk-go/private/waiter",
"revision": "f34b74c96bfd27df35643adeb14d8431ca047df5",
"revisionTime": "2016-08-17T18:35:19Z"
},
{
"checksumSHA1": "TtIAgZ+evpkKB5bBYCB69k0wZoU=",
"path": "github.com/aws/aws-sdk-go/service/firehose",
"revision": "f34b74c96bfd27df35643adeb14d8431ca047df5",
"revisionTime": "2016-08-17T18:35:19Z"
},
{
"checksumSHA1": "2n5/m0ClE4OyQRNdjfLwg+nSY3o=",
"path": "github.com/aws/aws-sdk-go/service/kinesis",
"revision": "f34b74c96bfd27df35643adeb14d8431ca047df5",
"revisionTime": "2016-08-17T18:35:19Z"
},
{
"checksumSHA1": "fgZ1cdh2T0cWRorIZkMGFDADMQw=",
"path": "github.com/aws/aws-sdk-go/service/kinesis/kinesisiface",
"revision": "f34b74c96bfd27df35643adeb14d8431ca047df5",
"revisionTime": "2016-08-17T18:35:19Z"
},
{
"checksumSHA1": "imxJucuPrgaPRMPtAgsu+Y7soB4=",
"path": "github.com/aws/aws-sdk-go/service/s3",
"revision": "f34b74c96bfd27df35643adeb14d8431ca047df5",
"revisionTime": "2016-08-17T18:35:19Z"
},
{
"checksumSHA1": "nH/itbdeFHpl4ysegdtgww9bFSA=",
"path": "github.com/aws/aws-sdk-go/service/sts",
"revision": "f34b74c96bfd27df35643adeb14d8431ca047df5",
"revisionTime": "2016-08-17T18:35:19Z"
},
{
"checksumSHA1": "zUyQXVCSaV97bbiVZbX1qn8UWm4=",
"path": "github.com/bmizerany/assert",
"revision": "e17e99893cb6509f428e1728281c2ad60a6b31e3",
"revisionTime": "2012-07-16T20:56:30Z"
},
{
"checksumSHA1": "i7BD7wKsIrix92VtlJ4zQRP4G8c=",
"path": "github.com/crowdmob/goamz/aws",
"revision": "3a06871fe9fc0281ca90f3a7d97258d042ed64c0",
"revisionTime": "2015-01-28T19:49:25Z"
},
{
"checksumSHA1": "qijq0UWIx8EKPT+GbsbuaZMw/gA=",
"path": "github.com/crowdmob/goamz/s3",
"revision": "3a06871fe9fc0281ca90f3a7d97258d042ed64c0",
"revisionTime": "2015-01-28T19:49:25Z"
},
{
"checksumSHA1": "FCeEm2BWZV/n4oTy+SGd/k0Ab5c=",
"origin": "github.com/aws/aws-sdk-go/vendor/github.com/go-ini/ini",
"path": "github.com/go-ini/ini",
"revision": "f34b74c96bfd27df35643adeb14d8431ca047df5",
"revisionTime": "2016-08-17T18:35:19Z"
},
{
"checksumSHA1": "0ZrwvB6KoGPj2PoDNSEJwxQ6Mog=",
"origin": "github.com/aws/aws-sdk-go/vendor/github.com/jmespath/go-jmespath",
"path": "github.com/jmespath/go-jmespath",
"revision": "f34b74c96bfd27df35643adeb14d8431ca047df5",
"revisionTime": "2016-08-17T18:35:19Z"
},
{
"checksumSHA1": "IXVypCQsDOlWf8dqyFogbOsRdvM=",
"path": "github.com/jpillora/backoff",
"revision": "0496a6c14df020789376f4d4a261273d5ddb36ec",
"revisionTime": "2016-04-14T05:52:04Z"
},
{
"checksumSHA1": "1YeGotQXMZMqk+mmm8sbBVJywpw=",
"path": "github.com/kr/pretty",
"revision": "e6ac2fc51e89a3249e82157fa0bb7a18ef9dd5bb",
"revisionTime": "2015-05-20T16:35:14Z"
},
{
"checksumSHA1": "uulQHQ7IsRKqDudBC8Go9J0gtAc=",
"path": "github.com/kr/text",
"revision": "bb797dc4fb8320488f47bf11de07a733d7233e1f",
"revisionTime": "2015-09-05T22:45:08Z"
},
{
"checksumSHA1": "Tivm2ueYu71B9YxTEyGxe+8ZWgk=",
"path": "github.com/lib/pq",
"revision": "f59175c2986495ff94109dee3835c504a96c3e81",
"revisionTime": "2016-01-27T22:38:42Z"
},
{
"checksumSHA1": "xppHi82MLqVx1eyQmbhTesAEjx8=",
"path": "github.com/lib/pq/oid",
"revision": "f59175c2986495ff94109dee3835c504a96c3e81",
"revisionTime": "2016-01-27T22:38:42Z"
},
{
"checksumSHA1": "QI1tJqI+jMmFrCAKcXs+LefgES4=",
"path": "github.com/tj/go-kinesis",
"revision": "817ff40136c6d4909bcff1021e58fdedf788ba23",
"revisionTime": "2016-06-02T03:00:41Z"
},
{
"checksumSHA1": "+4r0PnLwwyhO5/jvU5R/TEJb4kA=",
"path": "gopkg.in/bsm/ratelimit.v1",
"revision": "db14e161995a5177acef654cb0dd785e8ee8bc22",
"revisionTime": "2016-02-20T15:49:07Z"
},
{
"checksumSHA1": "JtXTQXRlxRB///NYmPDuMpEpvNI=",
"path": "gopkg.in/redis.v5",
"revision": "854c88a72c8bb9c09936145aef886b7697d6b995",
"revisionTime": "2016-12-03T15:45:52Z"
},
{
"checksumSHA1": "vQSE4FOH4EvyzYA72w60XOetmVY=",
"path": "gopkg.in/redis.v5/internal",
"revision": "854c88a72c8bb9c09936145aef886b7697d6b995",
"revisionTime": "2016-12-03T15:45:52Z"
},
{
"checksumSHA1": "2Ek4SixeRSKOX3mUiBMs3Aw+Guc=",
"path": "gopkg.in/redis.v5/internal/consistenthash",
"revision": "854c88a72c8bb9c09936145aef886b7697d6b995",
"revisionTime": "2016-12-03T15:45:52Z"
},
{
"checksumSHA1": "rJYVKcBrwYUGl7nuuusmZGrt8mY=",
"path": "gopkg.in/redis.v5/internal/hashtag",
"revision": "854c88a72c8bb9c09936145aef886b7697d6b995",
"revisionTime": "2016-12-03T15:45:52Z"
},
{
"checksumSHA1": "VnsHRPAMRMuhz7/n/85MZwMrchQ=",
"path": "gopkg.in/redis.v5/internal/pool",
"revision": "854c88a72c8bb9c09936145aef886b7697d6b995",
"revisionTime": "2016-12-03T15:45:52Z"
},
{
"checksumSHA1": "604uyPTNWLBNAnAyNRMiwYHXknA=",
"path": "gopkg.in/redis.v5/internal/proto",
"revision": "854c88a72c8bb9c09936145aef886b7697d6b995",
"revisionTime": "2016-12-03T15:45:52Z"
}
],
"rootPath": "github.com/harlow/kinesis-connectors"
}