From 090afb02641c2033c4d3df6f58861d3a3468e999 Mon Sep 17 00:00:00 2001 From: Srini Brahmaroutu Date: Sun, 22 Nov 2020 22:00:48 -0800 Subject: [PATCH] Adding Controller Framework that is used by Controller and Provisioner as common code --- Makefile | 7 + controller/controller.go | 522 +++++++++++++++++++++++++++++++++++++++ controller/interfaces.go | 96 +++++++ go.mod | 3 + go.sum | 11 + 5 files changed, 639 insertions(+) create mode 100644 controller/controller.go create mode 100644 controller/interfaces.go diff --git a/Makefile b/Makefile index c4e28bdd..20cecfe4 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +PROJECTNAME := $(shell basename "$(PWD)") +GOFILES := $(wildcard controller/*.go) +GOBIN := $(GOBASE)/bin + + #CMDS=cosi-controller-manager all: unit build #.PHONY: reltools @@ -26,6 +31,8 @@ release-tools/build.make: build: + @echo " > Building binary..." + go build $(GOFILES) test: unit: codegen: diff --git a/controller/controller.go b/controller/controller.go new file mode 100644 index 00000000..49415fa7 --- /dev/null +++ b/controller/controller.go @@ -0,0 +1,522 @@ +package controller + +import ( + "context" + "fmt" + "io/ioutil" + "os" + "regexp" + "strings" + "sync" + "time" + + "golang.org/x/time/rate" + + // objectstorage + v1alpha1 "github.com/kubernetes-sigs/container-object-storage-interface-api/apis/objectstorage.k8s.io/v1alpha1" + bucketclientset "github.com/kubernetes-sigs/container-object-storage-interface-api/clientset" + + // k8s api + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/wait" + + // k8s client + kubeclientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/scheme" + corev1 "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/tools/leaderelection" + "k8s.io/client-go/tools/leaderelection/resourcelock" + "k8s.io/client-go/tools/record" + "k8s.io/client-go/util/workqueue" + + // logging + "github.com/golang/glog" + + // config + "github.com/spf13/viper" +) + +type addFunc func(ctx context.Context, obj interface{}) error +type updateFunc func(ctx context.Context, old, new interface{}) error +type deleteFunc func(ctx context.Context, obj interface{}) error + +type addOp struct { + Object interface{} + AddFunc *addFunc + + Key string +} + +func (a addOp) String() string { + return a.Key +} + +type updateOp struct { + OldObject interface{} + NewObject interface{} + UpdateFunc *updateFunc + + Key string +} + +func (u updateOp) String() string { + return u.Key +} + +type deleteOp struct { + Object interface{} + DeleteFunc *deleteFunc + + Key string +} + +func (d deleteOp) String() string { + return d.Key +} + +type ObjectStorageController struct { + LeaseDuration time.Duration + RenewDeadline time.Duration + RetryPeriod time.Duration + + // Controller + ResyncPeriod time.Duration + queue workqueue.RateLimitingInterface + threadiness int + + // Listeners + BucketListener BucketListener + BucketClassListener BucketClassListener + BucketRequestListener BucketRequestListener + BucketAccessListener BucketAccessListener + BucketAccessClassListener BucketAccessClassListener + BucketAccessRequestListener BucketAccessRequestListener + + // leader election + leaderLock string + identity string + + // internal + initialized bool + bucketClient bucketclientset.Interface + kubeClient kubeclientset.Interface + + locker map[string]*sync.Mutex + lockerLock sync.Mutex +} + +func NewDefaultObjectStorageController(identity string, leaderLockName string, threads int) (*ObjectStorageController, error) { + rateLimit := workqueue.NewMaxOfRateLimiter( + workqueue.NewItemExponentialFailureRateLimiter(100*time.Millisecond, 600*time.Second), + &workqueue.BucketRateLimiter{Limiter: rate.NewLimiter(rate.Limit(10), 100)}, + ) + return NewObjectStorageController(identity, leaderLockName, threads, rateLimit) +} + +func NewObjectStorageController(identity string, leaderLockName string, threads int, limiter workqueue.RateLimiter) (*ObjectStorageController, error) { + cfg, err := func() (*rest.Config, error) { + kubeConfig := viper.GetString("kube-config") + + if kubeConfig != "" { + return clientcmd.BuildConfigFromFlags("", kubeConfig) + } + return rest.InClusterConfig() + }() + if err != nil { + return nil, err + } + + kubeClient, err := kubeclientset.NewForConfig(cfg) + if err != nil { + return nil, err + } + bucketClient, err := bucketclientset.NewForConfig(cfg) + if err != nil { + return nil, err + } + + id := identity + if id == "" { + id, err = os.Hostname() + if err != nil { + return nil, err + } + } + + return &ObjectStorageController{ + identity: id, + kubeClient: kubeClient, + bucketClient: bucketClient, + initialized: false, + leaderLock: leaderLockName, + queue: workqueue.NewRateLimitingQueue(limiter), + threadiness: threads, + + ResyncPeriod: 30 * time.Second, + LeaseDuration: 15 * time.Second, + RenewDeadline: 10 * time.Second, + RetryPeriod: 5 * time.Second, + }, nil +} + +// Run - runs the controller. Note that ctx must be cancellable i.e. ctx.Done() should not return nil +func (c *ObjectStorageController) Run(ctx context.Context) error { + if !c.initialized { + fmt.Errorf("Uninitialized controller. Atleast 1 listener should be added") + } + + ns := func() string { + if ns := os.Getenv("POD_NAMESPACE"); ns != "" { + return ns + } + + if data, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace"); err == nil { + if ns := strings.TrimSpace(string(data)); len(ns) > 0 { + return ns + } + } + return "default" + }() + + sanitize := func(n string) string { + re := regexp.MustCompile("[^a-zA-Z0-9-]") + name := strings.ToLower(re.ReplaceAllString(n, "-")) + if name[len(name)-1] == '-' { + // name must not end with '-' + name = name + "X" + } + return name + } + + leader := sanitize(fmt.Sprintf("%s/%s", c.leaderLock, c.identity)) + id, err := os.Hostname() + if err != nil { + return fmt.Errorf("error getting the default leader identity: %v", err) + } + + recorder := record.NewBroadcaster() + recorder.StartRecordingToSink(&corev1.EventSinkImpl{Interface: c.kubeClient.CoreV1().Events(ns)}) + eRecorder := recorder.NewRecorder(scheme.Scheme, v1.EventSource{Component: leader}) + + rlConfig := resourcelock.ResourceLockConfig{ + Identity: sanitize(id), + EventRecorder: eRecorder, + } + + l, err := resourcelock.New(resourcelock.LeasesResourceLock, ns, leader, c.kubeClient.CoreV1(), c.kubeClient.CoordinationV1(), rlConfig) + if err != nil { + return err + } + + leaderConfig := leaderelection.LeaderElectionConfig{ + Lock: l, + LeaseDuration: c.LeaseDuration, + RenewDeadline: c.RenewDeadline, + RetryPeriod: c.RetryPeriod, + Callbacks: leaderelection.LeaderCallbacks{ + OnStartedLeading: func(ctx context.Context) { + glog.V(2).Info("became leader, starting") + c.runController(ctx) + }, + OnStoppedLeading: func() { + glog.Fatal("stopped leading") + }, + OnNewLeader: func(identity string) { + glog.V(3).Infof("new leader detected, current leader: %s", identity) + }, + }, + } + + leaderelection.RunOrDie(ctx, leaderConfig) + return nil // should never reach here +} + +func (c *ObjectStorageController) runWorker(ctx context.Context) { + for c.processNextItem(ctx) { + } +} + +func (c *ObjectStorageController) processNextItem(ctx context.Context) bool { + // Wait until there is a new item in the working queue + op, quit := c.queue.Get() + if quit { + return false + } + + // With the lock below in place, we can safely tell the queue that we are done + // processing this item. The lock will ensure that multiple items of the same + // name and kind do not get processed simultaneously + defer c.queue.Done(op) + + // Ensure that multiple operations on different versions of the same object + // do not happen in parallel + c.OpLock(op) + defer c.OpUnlock(op) + + var err error + switch o := op.(type) { + case addOp: + add := *o.AddFunc + err = add(ctx, o.Object) + case updateOp: + update := *o.UpdateFunc + err = update(ctx, o.OldObject, o.NewObject) + case deleteOp: + delete := *o.DeleteFunc + err = delete(ctx, o.Object) + default: + panic("unknown item in queue") + } + + // Handle the error if something went wrong + c.handleErr(err, op) + return true +} + +func (c *ObjectStorageController) OpLock(op interface{}) { + c.GetOpLock(op).Lock() +} + +func (c *ObjectStorageController) OpUnlock(op interface{}) { + c.GetOpLock(op).Unlock() +} + +func (c *ObjectStorageController) GetOpLock(op interface{}) *sync.Mutex { + var key string + var ext string + + switch o := op.(type) { + case addOp: + key = o.Key + ext = fmt.Sprintf("%v", o.AddFunc) + case updateOp: + key = o.Key + ext = fmt.Sprintf("%v", o.UpdateFunc) + case deleteOp: + key = o.Key + ext = fmt.Sprintf("%v", o.DeleteFunc) + default: + panic("unknown item in queue") + } + + lockKey := fmt.Sprintf("%s/%s", key, ext) + if c.locker == nil { + c.locker = map[string]*sync.Mutex{} + } + + c.lockerLock.Lock() + defer c.lockerLock.Unlock() + + if _, ok := c.locker[lockKey]; !ok { + c.locker[lockKey] = &sync.Mutex{} + } + return c.locker[lockKey] +} + +// handleErr checks if an error happened and makes sure we will retry later. +func (c *ObjectStorageController) handleErr(err error, op interface{}) { + if err == nil { + // Forget about the #AddRateLimited history of the op on every successful synchronization. + // This ensures that future processing of updates for this op is not delayed because of + // an outdated error history. + c.queue.Forget(op) + return + } + + /* TODO: Determine if there is a maxium number of retries or time allowed before giving up + // This controller retries 5 times if something goes wrong. After that, it stops trying. + if c.queue.NumRequeues(op) < 5 { + klog.Infof("Error syncing op %v: %v", key, err) + + // Re-enqueue the key rate limited. Based on the rate limiter on the + // queue and the re-enqueue history, the op will be processed later again. + c.queue.AddRateLimited(op) + return + } + + c.queue.Forget(key) + // Report to an external entity that, even after several retries, we could not successfully process this op + utilruntime.HandleError(err) + klog.Infof("Dropping op %+v out of the queue: %v", op, err) + */ + glog.V(5).Infof("Error executing operation %+v: %+v", op, err) + c.queue.AddRateLimited(op) +} + +func (c *ObjectStorageController) runController(ctx context.Context) { + controllerFor := func(name string, objType runtime.Object, add addFunc, update updateFunc, delete deleteFunc) { + indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{}) + resyncPeriod := c.ResyncPeriod + + lw := cache.NewListWatchFromClient(c.bucketClient.ObjectstorageV1alpha1().RESTClient(), name, "", fields.Everything()) + cfg := &cache.Config{ + Queue: cache.NewDeltaFIFOWithOptions(cache.DeltaFIFOOptions{ + KnownObjects: indexer, + EmitDeltaTypeReplaced: true, + }), + ListerWatcher: lw, + ObjectType: objType, + FullResyncPeriod: resyncPeriod, + RetryOnError: true, + Process: func(obj interface{}) error { + for _, d := range obj.(cache.Deltas) { + switch d.Type { + case cache.Sync, cache.Replaced, cache.Added, cache.Updated: + if old, exists, err := indexer.Get(d.Object); err == nil && exists { + key, err := cache.MetaNamespaceKeyFunc(d.Object) + if err != nil { + panic(err) + } + + c.queue.Add(updateOp{ + OldObject: old, + NewObject: d.Object, + UpdateFunc: &update, + Key: key, + }) + return indexer.Update(d.Object) + } else { + key, err := cache.MetaNamespaceKeyFunc(d.Object) + if err != nil { + panic(err) + } + + c.queue.Add(addOp{ + Object: d.Object, + AddFunc: &add, + Key: key, + }) + return indexer.Add(d.Object) + } + case cache.Deleted: + key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(d.Object) + if err != nil { + panic(err) + } + + c.queue.Add(deleteOp{ + Object: d.Object, + DeleteFunc: &delete, + Key: key, + }) + return indexer.Delete(d.Object) + } + } + return nil + }, + } + ctrlr := cache.New(cfg) + + defer utilruntime.HandleCrash() + defer c.queue.ShutDown() + + glog.V(3).Infof("Starting %s controller", name) + go ctrlr.Run(ctx.Done()) + + if !cache.WaitForCacheSync(ctx.Done(), ctrlr.HasSynced) { + utilruntime.HandleError(fmt.Errorf("Timed out waiting for caches to sync")) + return + } + + for i := 0; i < c.threadiness; i++ { + go wait.UntilWithContext(ctx, c.runWorker, time.Second) + } + + <-ctx.Done() + glog.V(3).Infof("Stopping %s controller", name) + } + + if c.BucketListener != nil { + c.BucketListener.InitializeKubeClient(c.kubeClient) + c.BucketListener.InitializeBucketClient(c.bucketClient) + addFunc := func(ctx context.Context, obj interface{}) error { + return c.BucketListener.Add(ctx, obj.(*v1alpha1.Bucket)) + } + updateFunc := func(ctx context.Context, old interface{}, new interface{}) error { + return c.BucketListener.Update(ctx, old.(*v1alpha1.Bucket), new.(*v1alpha1.Bucket)) + } + deleteFunc := func(ctx context.Context, obj interface{}) error { + return c.BucketListener.Delete(ctx, obj.(*v1alpha1.Bucket)) + } + go controllerFor("Buckets", &v1alpha1.Bucket{}, addFunc, updateFunc, deleteFunc) + } + if c.BucketRequestListener != nil { + c.BucketRequestListener.InitializeKubeClient(c.kubeClient) + c.BucketRequestListener.InitializeBucketClient(c.bucketClient) + addFunc := func(ctx context.Context, obj interface{}) error { + return c.BucketRequestListener.Add(ctx, obj.(*v1alpha1.BucketRequest)) + } + updateFunc := func(ctx context.Context, old interface{}, new interface{}) error { + return c.BucketRequestListener.Update(ctx, old.(*v1alpha1.BucketRequest), new.(*v1alpha1.BucketRequest)) + } + deleteFunc := func(ctx context.Context, obj interface{}) error { + return c.BucketRequestListener.Delete(ctx, obj.(*v1alpha1.BucketRequest)) + } + go controllerFor("BucketRequests", &v1alpha1.BucketRequest{}, addFunc, updateFunc, deleteFunc) + } + if c.BucketClassListener != nil { + c.BucketClassListener.InitializeKubeClient(c.kubeClient) + c.BucketClassListener.InitializeBucketClient(c.bucketClient) + addFunc := func(ctx context.Context, obj interface{}) error { + return c.BucketClassListener.Add(ctx, obj.(*v1alpha1.BucketClass)) + } + updateFunc := func(ctx context.Context, old interface{}, new interface{}) error { + return c.BucketClassListener.Update(ctx, old.(*v1alpha1.BucketClass), new.(*v1alpha1.BucketClass)) + } + deleteFunc := func(ctx context.Context, obj interface{}) error { + return c.BucketClassListener.Delete(ctx, obj.(*v1alpha1.BucketClass)) + } + go controllerFor("BucketClasses", &v1alpha1.BucketClass{}, addFunc, updateFunc, deleteFunc) + } + + if c.BucketAccessListener != nil { + c.BucketAccessListener.InitializeKubeClient(c.kubeClient) + c.BucketAccessListener.InitializeBucketClient(c.bucketClient) + addFunc := func(ctx context.Context, obj interface{}) error { + return c.BucketAccessListener.Add(ctx, obj.(*v1alpha1.BucketAccess)) + } + updateFunc := func(ctx context.Context, old interface{}, new interface{}) error { + return c.BucketAccessListener.Update(ctx, old.(*v1alpha1.BucketAccess), new.(*v1alpha1.BucketAccess)) + } + deleteFunc := func(ctx context.Context, obj interface{}) error { + return c.BucketAccessListener.Delete(ctx, obj.(*v1alpha1.BucketAccess)) + } + go controllerFor("BucketAccesses", &v1alpha1.BucketAccess{}, addFunc, updateFunc, deleteFunc) + } + if c.BucketAccessRequestListener != nil { + c.BucketAccessRequestListener.InitializeKubeClient(c.kubeClient) + c.BucketAccessRequestListener.InitializeBucketClient(c.bucketClient) + addFunc := func(ctx context.Context, obj interface{}) error { + return c.BucketAccessRequestListener.Add(ctx, obj.(*v1alpha1.BucketAccessRequest)) + } + updateFunc := func(ctx context.Context, old interface{}, new interface{}) error { + return c.BucketAccessRequestListener.Update(ctx, old.(*v1alpha1.BucketAccessRequest), new.(*v1alpha1.BucketAccessRequest)) + } + deleteFunc := func(ctx context.Context, obj interface{}) error { + return c.BucketAccessRequestListener.Delete(ctx, obj.(*v1alpha1.BucketAccessRequest)) + } + go controllerFor("BucketAccessRequests", &v1alpha1.BucketAccessRequest{}, addFunc, updateFunc, deleteFunc) + } + if c.BucketAccessClassListener != nil { + c.BucketAccessClassListener.InitializeKubeClient(c.kubeClient) + c.BucketAccessClassListener.InitializeBucketClient(c.bucketClient) + addFunc := func(ctx context.Context, obj interface{}) error { + return c.BucketAccessClassListener.Add(ctx, obj.(*v1alpha1.BucketAccessClass)) + } + updateFunc := func(ctx context.Context, old interface{}, new interface{}) error { + return c.BucketAccessClassListener.Update(ctx, old.(*v1alpha1.BucketAccessClass), new.(*v1alpha1.BucketAccessClass)) + } + deleteFunc := func(ctx context.Context, obj interface{}) error { + return c.BucketAccessClassListener.Delete(ctx, obj.(*v1alpha1.BucketAccessClass)) + } + go controllerFor("BucketAccessClasses", &v1alpha1.BucketAccessClass{}, addFunc, updateFunc, deleteFunc) + } + + <-ctx.Done() +} diff --git a/controller/interfaces.go b/controller/interfaces.go new file mode 100644 index 00000000..a641772e --- /dev/null +++ b/controller/interfaces.go @@ -0,0 +1,96 @@ +package controller + +import ( + "context" + + // storage + "github.com/kubernetes-sigs/container-object-storage-interface-api/apis/objectstorage.k8s.io/v1alpha1" + bucketclientset "github.com/kubernetes-sigs/container-object-storage-interface-api/clientset" + + // k8s client + kubeclientset "k8s.io/client-go/kubernetes" +) + +// Set the clients for each of the listeners +type GenericListener interface { + InitializeKubeClient(kubeclientset.Interface) + InitializeBucketClient(bucketclientset.Interface) +} + +type BucketListener interface { + GenericListener + + Add(ctx context.Context, b *v1alpha1.Bucket) error + Update(ctx context.Context, old *v1alpha1.Bucket, new *v1alpha1.Bucket) error + Delete(ctx context.Context, b *v1alpha1.Bucket) error +} + +func (c *ObjectStorageController) AddBucketListener(b BucketListener) { + c.initialized = true + c.BucketListener = b +} + +type BucketClassListener interface { + GenericListener + + Add(ctx context.Context, b *v1alpha1.BucketClass) error + Update(ctx context.Context, old *v1alpha1.BucketClass, new *v1alpha1.BucketClass) error + Delete(ctx context.Context, b *v1alpha1.BucketClass) error +} + +func (c *ObjectStorageController) AddBucketClassListener(b BucketClassListener) { + c.initialized = true + c.BucketClassListener = b +} + +type BucketRequestListener interface { + GenericListener + + Add(ctx context.Context, b *v1alpha1.BucketRequest) error + Update(ctx context.Context, old *v1alpha1.BucketRequest, new *v1alpha1.BucketRequest) error + Delete(ctx context.Context, b *v1alpha1.BucketRequest) error +} + +func (c *ObjectStorageController) AddBucketRequestListener(b BucketRequestListener) { + c.initialized = true + c.BucketRequestListener = b +} + +type BucketAccessListener interface { + GenericListener + + Add(ctx context.Context, b *v1alpha1.BucketAccess) error + Update(ctx context.Context, old *v1alpha1.BucketAccess, new *v1alpha1.BucketAccess) error + Delete(ctx context.Context, b *v1alpha1.BucketAccess) error +} + +func (c *ObjectStorageController) AddBucketAccessListener(b BucketAccessListener) { + c.initialized = true + c.BucketAccessListener = b +} + +type BucketAccessClassListener interface { + GenericListener + + Add(ctx context.Context, b *v1alpha1.BucketAccessClass) error + Update(ctx context.Context, old *v1alpha1.BucketAccessClass, new *v1alpha1.BucketAccessClass) error + Delete(ctx context.Context, b *v1alpha1.BucketAccessClass) error +} + +func (c *ObjectStorageController) AddBucketAccessClassListener(b BucketAccessClassListener) { + c.initialized = true + c.BucketAccessClassListener = b +} + +type BucketAccessRequestListener interface { + GenericListener + + Add(ctx context.Context, b *v1alpha1.BucketAccessRequest) error + Update(ctx context.Context, old *v1alpha1.BucketAccessRequest, new *v1alpha1.BucketAccessRequest) error + Delete(ctx context.Context, b *v1alpha1.BucketAccessRequest) error +} + +func (c *ObjectStorageController) AddBucketAccessRequestListener(b BucketAccessRequestListener) { + c.initialized = true + c.BucketAccessRequestListener = b +} diff --git a/go.mod b/go.mod index 94b8c9a0..90ca4e27 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,10 @@ go 1.15 require ( github.com/go-openapi/spec v0.19.12 + github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b github.com/onsi/ginkgo v1.14.2 // indirect + github.com/spf13/viper v1.3.2 + golang.org/x/time v0.0.0-20191024005414-555d28b269f0 k8s.io/api v0.19.4 k8s.io/apimachinery v0.19.4 k8s.io/client-go v0.19.4 diff --git a/go.sum b/go.sum index d97fa26b..b59dde7f 100644 --- a/go.sum +++ b/go.sum @@ -23,6 +23,7 @@ github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxB github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= @@ -152,6 +153,7 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -210,10 +212,12 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= @@ -237,6 +241,7 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -250,6 +255,7 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= @@ -277,6 +283,7 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -304,16 +311,20 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=