├── .gitignore ├── LICENSE ├── README.md ├── cache.go ├── cache_test.go ├── data_set.go ├── go.mod ├── go.sum ├── listener.go └── pool.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 fengyun.rui 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## K8SCACHE 2 | 3 | local cache for kubernetes apiserver cache 4 | 5 | `k8scache is based on client-go informer` 6 | 7 | ### Feature 8 | 9 | **support resource list:** 10 | 11 | - namespace 12 | - pod 13 | - node 14 | - service 15 | - replicaSet 16 | - deployment 17 | - daemonSet 18 | - statefulSet 19 | - event 20 | - endpoints 21 | 22 | ### Usage 23 | 24 | ```go 25 | func TestSimple(t *testing.T) { 26 | cli, err := NewClient() 27 | assert.Equal(t, err, nil) 28 | 29 | err = cli.BuildForKubecfg() 30 | assert.Equal(t, err, nil) 31 | 32 | cc, err := NewClusterCache(cli) 33 | assert.Equal(t, err, nil) 34 | 35 | pods, err := cc.ListPods() 36 | assert.Equal(t, err, nil) 37 | assert.Greater(t, len(pods.Items), 0) 38 | 39 | cc.Start() 40 | cc.SyncCache() 41 | 42 | nslist, err := cc.GetNamespaces() 43 | assert.Equal(t, err, nil) 44 | assert.Greater(t, len(nslist), 0) 45 | fmt.Printf("namespace list %v \n\n", nslist) 46 | 47 | nodes, err := cc.GetNodes() 48 | assert.Equal(t, err, nil) 49 | assert.Greater(t, len(nodes), 0) 50 | fmt.Printf("nodes list %v \n\n", nodes) 51 | 52 | mpods, err := cc.GetPodsWithNS("default") 53 | assert.Equal(t, err, nil) 54 | assert.Greater(t, len(mpods), 0) 55 | fmt.Printf("default pods list %v \n\n", mpods) 56 | 57 | svcs, err := cc.GetServicesWithNS("default") 58 | assert.Equal(t, err, nil) 59 | assert.Greater(t, len(svcs), 0) 60 | fmt.Printf("default services list %v \n\n", svcs) 61 | 62 | reps, err := cc.GetReplicasWithNS("default") 63 | assert.Equal(t, err, nil) 64 | assert.Greater(t, len(reps), 0) 65 | fmt.Printf("default replicas list %v \n\n", reps) 66 | 67 | dms, err := cc.GetDeploymentsWithNS("default") 68 | assert.Equal(t, err, nil) 69 | assert.Greater(t, len(dms), 0) 70 | fmt.Printf("default deployments list %v \n\n", dms) 71 | 72 | time.AfterFunc(1*time.Second, func() { 73 | cc.Stop() 74 | }) 75 | cc.Wait() 76 | } 77 | ``` 78 | -------------------------------------------------------------------------------- /cache.go: -------------------------------------------------------------------------------- 1 | package k8scache 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "path" 7 | "sync" 8 | "time" 9 | 10 | "github.com/mitchellh/go-homedir" 11 | 12 | v1 "k8s.io/api/apps/v1" 13 | corev1 "k8s.io/api/core/v1" 14 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 15 | "k8s.io/apimachinery/pkg/labels" 16 | "k8s.io/apimachinery/pkg/watch" 17 | "k8s.io/client-go/informers" 18 | "k8s.io/client-go/kubernetes" 19 | listappv1 "k8s.io/client-go/listers/apps/v1" 20 | listcorev1 "k8s.io/client-go/listers/core/v1" 21 | "k8s.io/client-go/rest" 22 | "k8s.io/client-go/tools/cache" 23 | "k8s.io/client-go/tools/clientcmd" 24 | ) 25 | 26 | const ( 27 | TypeNamespace = "Namespace" 28 | TypePod = "Pod" 29 | TypeNode = "Node" 30 | TypeService = "Service" 31 | TypeReplicaSet = "ReplicaSet" 32 | TypeDeployment = "Deployment" 33 | TypeDaemonSet = "DaemonSet" 34 | TypeStatefulSet = "StatefulSet" 35 | TypeEvent = "Event" 36 | TypeEndpoint = "Endpoint" 37 | ) 38 | 39 | var ( 40 | ErrNotFoundNamespace = errors.New("not found namespace") 41 | ErrNotFoundName = errors.New("not found name") 42 | ErrSyncResourceTimeout = errors.New("sync resource timeout") 43 | ErrCacheNotReady = errors.New("cache not ready, wait to sync full cache in the beginning") 44 | ErrBeyondExpireInterval = errors.New("beyond expire interval") 45 | ErrNotSupportResource = errors.New("not support resource") 46 | 47 | allResources = []string{ 48 | TypeNamespace, 49 | TypePod, 50 | TypeNode, 51 | TypeService, 52 | TypeReplicaSet, 53 | TypeDeployment, 54 | TypeDaemonSet, 55 | TypeStatefulSet, 56 | TypeEvent, 57 | TypeEndpoint, 58 | } 59 | ) 60 | 61 | type Client struct { 62 | client *kubernetes.Clientset 63 | } 64 | 65 | func NewClient() (*Client, error) { 66 | return &Client{}, nil 67 | } 68 | 69 | func (cs *Client) SetClient(cli *kubernetes.Clientset) { 70 | cs.client = cli 71 | } 72 | 73 | func (cs *Client) GetClient() *kubernetes.Clientset { 74 | return cs.client 75 | } 76 | 77 | func (cs *Client) BuildForKubecfg() error { 78 | dir, err := homedir.Dir() 79 | if err != nil { 80 | return err 81 | } 82 | 83 | kubecfg := path.Join(dir, "/.kube/config") 84 | return cs.BuildForCustomKubecfg(kubecfg) 85 | } 86 | 87 | func (cs *Client) BuildForCustomKubecfg(kubecfg string) error { 88 | config, err := clientcmd.BuildConfigFromFlags("", kubecfg) 89 | if err != nil { 90 | return err 91 | } 92 | 93 | cs.client, err = kubernetes.NewForConfig(config) 94 | return err 95 | } 96 | 97 | func (cs *Client) BuildForInCluster(kubecfg string) error { 98 | config, err := rest.InClusterConfig() 99 | if err != nil { 100 | return err 101 | } 102 | 103 | cs.client, err = kubernetes.NewForConfig(config) 104 | return err 105 | } 106 | 107 | func (cs *Client) BuildForParams(addr, token, cert, key, ca string) error { 108 | var insecure bool 109 | if token != "" { 110 | insecure = true 111 | } 112 | 113 | var cfg = &rest.Config{ 114 | Host: addr, 115 | ContentConfig: rest.ContentConfig{}, 116 | BearerToken: token, 117 | Impersonate: rest.ImpersonationConfig{}, 118 | TLSClientConfig: rest.TLSClientConfig{ 119 | Insecure: insecure, 120 | CertData: []byte(cert), 121 | KeyData: []byte(key), 122 | CAData: []byte(ca), 123 | }, 124 | } 125 | clientSet, err := kubernetes.NewForConfig(cfg) 126 | if err != nil { 127 | return err 128 | } 129 | 130 | cs.client = clientSet 131 | return nil 132 | } 133 | 134 | type OptionFunc func(*ClusterCache) error 135 | 136 | func WithNamespace(ns string) OptionFunc { 137 | return func(o *ClusterCache) error { 138 | o.namespace = ns 139 | return nil 140 | } 141 | } 142 | 143 | func WithContext(ctx context.Context) OptionFunc { 144 | return func(o *ClusterCache) error { 145 | o.ctx = ctx 146 | return nil 147 | } 148 | } 149 | 150 | func WithStopper(stopper chan struct{}) OptionFunc { 151 | return func(o *ClusterCache) error { 152 | o.stopper = stopper 153 | return nil 154 | } 155 | } 156 | 157 | func WithFollowResource(rlist []string) OptionFunc { 158 | return func(o *ClusterCache) error { 159 | mm := map[string]bool{} 160 | for _, res := range rlist { 161 | mm[res] = true 162 | } 163 | o.followResource = mm // cover old val 164 | return nil 165 | } 166 | } 167 | 168 | func WithNotFollowResource(rlist []string) OptionFunc { 169 | return func(o *ClusterCache) error { 170 | for _, res := range rlist { 171 | delete(o.followResource, res) 172 | } 173 | return nil 174 | } 175 | } 176 | 177 | func WithFollowNamespaces(nlist []string) OptionFunc { 178 | return func(o *ClusterCache) error { 179 | mm := map[string]bool{} 180 | for _, ns := range nlist { 181 | mm[ns] = true 182 | } 183 | o.followNamespaces = mm // cover old val 184 | return nil 185 | } 186 | } 187 | 188 | func WithNotFollowNamespaces(nlist []string) OptionFunc { 189 | return func(o *ClusterCache) error { 190 | for _, ns := range nlist { 191 | delete(o.followNamespaces, ns) 192 | } 193 | return nil 194 | } 195 | } 196 | 197 | func NewClusterCache(client *Client, opts ...OptionFunc) (*ClusterCache, error) { 198 | cc := &ClusterCache{ 199 | k8sClient: client, 200 | stopper: make(chan struct{}, 0), 201 | cache: make(map[string]*DataSet, 5), 202 | nodes: make(map[string]*corev1.Node, 5), 203 | namespaces: make(map[string]*corev1.Namespace, 5), 204 | followResource: getAllResources(), 205 | followNamespaces: make(map[string]bool, 0), 206 | listeners: newListenerPool(), 207 | } 208 | for _, opt := range opts { 209 | err := opt(cc) 210 | if err != nil { 211 | return cc, err 212 | } 213 | } 214 | return cc, nil 215 | } 216 | 217 | type ClusterCache struct { 218 | ctx context.Context 219 | cancel context.CancelFunc 220 | namespace string 221 | k8sClient *Client 222 | followResource map[string]bool 223 | followNamespaces map[string]bool 224 | isReady bool // todo: all resource -> map[resource]bool 225 | isExpired bool 226 | listeners *ListenerPool 227 | 228 | stopper chan struct{} 229 | stopOnce sync.Once 230 | 231 | factory informers.SharedInformerFactory 232 | namespaceInformer cache.SharedIndexInformer 233 | namespaceLister listcorev1.NamespaceLister 234 | podInformer cache.SharedIndexInformer 235 | podLister listcorev1.PodLister 236 | nodeInformer cache.SharedIndexInformer 237 | nodeLister listcorev1.NodeLister 238 | serviceInformer cache.SharedIndexInformer 239 | serviceLister listcorev1.ServiceLister 240 | deploymentInformer cache.SharedIndexInformer 241 | deploymentLister listappv1.DeploymentLister 242 | replicaInformer cache.SharedIndexInformer 243 | replicaLister listappv1.ReplicaSetLister 244 | daemonInformer cache.SharedIndexInformer 245 | daemonLister listappv1.DaemonSetLister 246 | statefulInformer cache.SharedIndexInformer 247 | statefulLister listappv1.StatefulSetLister 248 | eventInformer cache.SharedIndexInformer 249 | eventLister listcorev1.EventLister 250 | endpointInformer cache.SharedIndexInformer 251 | endpointLister listcorev1.EndpointsLister 252 | 253 | nodes map[string]*corev1.Node 254 | namespaces map[string]*corev1.Namespace 255 | cache map[string]*DataSet // key: namespace, val: *DataSet 256 | cacheMutex sync.RWMutex 257 | } 258 | 259 | func (c *ClusterCache) Start() { 260 | c.factory = informers.NewSharedInformerFactory(c.k8sClient.client, 0) 261 | c.bindResourceInformer() 262 | c.factory.Start(c.stopper) 263 | go c.healthScaner() 264 | } 265 | 266 | func (c *ClusterCache) Wait() { 267 | select { 268 | case <-c.stopper: 269 | } 270 | } 271 | 272 | func (c *ClusterCache) Stop() { 273 | select { 274 | case <-c.stopper: // avoid close stopper (withStopper) ouside 275 | return 276 | default: 277 | } 278 | 279 | c.stopOnce.Do(func() { 280 | close(c.stopper) 281 | }) 282 | } 283 | 284 | func (c *ClusterCache) Reset() { 285 | c.stopper = make(chan struct{}, 0) 286 | c.stopOnce = sync.Once{} 287 | } 288 | 289 | func (c *ClusterCache) RegisterListener(genre string, lis Listener) { 290 | c.listeners.add(genre, lis) 291 | } 292 | 293 | func (c *ClusterCache) UnRegisterListener(genre string, lis Listener) { 294 | c.listeners.del(genre, lis) 295 | } 296 | 297 | func (c *ClusterCache) healthScaner() { 298 | for { 299 | select { 300 | case <-c.stopper: 301 | return 302 | case <-time.After(5 * time.Second): 303 | } 304 | 305 | _, err := c.k8sClient.client.CoreV1().Namespaces().Get(metav1.NamespaceDefault, metav1.GetOptions{}) 306 | if err != nil { 307 | c.isExpired = true 308 | } 309 | } 310 | } 311 | 312 | func (c *ClusterCache) isClosed() bool { 313 | select { 314 | case <-c.stopper: 315 | return true 316 | default: 317 | return false 318 | } 319 | } 320 | 321 | func (c *ClusterCache) isFollowResource(res string) bool { 322 | _, ok := c.followResource[res] 323 | return ok 324 | } 325 | 326 | func (c *ClusterCache) isFollowNamespace(ns string) bool { 327 | if len(c.followNamespaces) == 0 { // unset 328 | return true 329 | } 330 | 331 | _, ok := c.followNamespaces[ns] 332 | return ok 333 | } 334 | 335 | func (c *ClusterCache) bindResourceInformer() { 336 | c.bindNamespaceInformer() 337 | c.bindPodInformer() 338 | c.bindNodesInformer() 339 | c.bindReplicaInformer() 340 | c.bindServiceInformer() 341 | c.bindDeploymentInformer() 342 | c.bindStatefulInformer() 343 | c.bindDaemonInformer() 344 | c.bindEventInformer() 345 | c.bindEndpointsInformer() 346 | } 347 | 348 | var defaultSyncTimeout = time.Duration(30 * time.Second) 349 | 350 | func (c *ClusterCache) timeoutChan(ts time.Duration) (chan struct{}, *time.Timer) { 351 | done := make(chan struct{}) 352 | if ts == 0 { 353 | return done, time.NewTimer(0) 354 | } 355 | 356 | timer := time.AfterFunc(ts, func() { 357 | close(done) 358 | }) 359 | return done, timer 360 | } 361 | 362 | func (c *ClusterCache) SyncCache() error { 363 | return c.SyncCacheWithTimeout(defaultSyncTimeout) 364 | } 365 | 366 | func (c *ClusterCache) SyncCacheWithTimeout(timeout time.Duration) error { 367 | var ( 368 | funcs = []func(time.Duration) error{} 369 | err error 370 | ) 371 | 372 | funcs = append(funcs, 373 | c.SyncNamespacesCache, 374 | c.SyncPodsCache, 375 | c.SyncNodesCache, 376 | c.SyncServicesCache, 377 | c.SyncDeploymentsCache, 378 | c.SyncReplicasCache, 379 | c.SyncDaemonCache, 380 | c.SyncStatefulCache, 381 | c.SyncEventsCache, 382 | c.SyncEndpointsCache, 383 | ) 384 | 385 | wg := sync.WaitGroup{} // concurrent sync 386 | for _, fn := range funcs { 387 | fn := fn // avoid go for range bug 388 | 389 | wg.Add(1) 390 | go func() { 391 | defer wg.Done() 392 | cerr := fn(timeout) 393 | 394 | c.cacheMutex.Lock() 395 | defer c.cacheMutex.Unlock() 396 | if cerr != nil { 397 | err = cerr 398 | } 399 | }() 400 | } 401 | wg.Wait() 402 | 403 | if err == nil { 404 | c.isReady = true 405 | } 406 | return err 407 | } 408 | 409 | func (c *ClusterCache) ToLabels(mm map[string]string) labels.Selector { 410 | return labels.Set(mm).AsSelector() 411 | } 412 | 413 | func (c *ClusterCache) bindNamespaceInformer() { 414 | c.namespaceInformer = c.factory.Core().V1().Namespaces().Informer() 415 | c.namespaceLister = c.factory.Core().V1().Namespaces().Lister() 416 | add := func(obj interface{}) { 417 | ns, ok := obj.(*corev1.Namespace) 418 | if !ok { 419 | return 420 | } 421 | if !c.isFollowNamespace(ns.Namespace) { 422 | return 423 | } 424 | 425 | c.cacheMutex.Lock() 426 | defer c.cacheMutex.Unlock() 427 | 428 | c.namespaces[ns.Name] = ns 429 | } 430 | update := func(oldObj, newObj interface{}) { 431 | add(newObj) 432 | } 433 | del := func(obj interface{}) { 434 | ns, ok := obj.(*corev1.Namespace) 435 | if !ok { 436 | return 437 | } 438 | if !c.isFollowNamespace(ns.Namespace) { 439 | return 440 | } 441 | 442 | c.cacheMutex.Lock() 443 | defer c.cacheMutex.Unlock() 444 | 445 | delete(c.namespaces, ns.Name) 446 | } 447 | 448 | c.namespaceInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ 449 | AddFunc: add, 450 | UpdateFunc: update, 451 | DeleteFunc: del, 452 | }) 453 | } 454 | 455 | func (c *ClusterCache) SyncNamespacesCache(timeout time.Duration) error { 456 | if !c.isFollowResource(TypePod) { 457 | return nil 458 | } 459 | 460 | done, timer := c.timeoutChan(timeout) 461 | defer timer.Stop() 462 | 463 | if !cache.WaitForCacheSync(done, c.namespaceInformer.HasSynced) { 464 | return ErrSyncResourceTimeout 465 | } 466 | return nil 467 | } 468 | 469 | func (c *ClusterCache) bindPodInformer() { 470 | if !c.isFollowResource(TypePod) { 471 | return 472 | } 473 | 474 | c.podInformer = c.factory.Core().V1().Pods().Informer() 475 | c.podLister = c.factory.Core().V1().Pods().Lister() 476 | 477 | add := func(obj interface{}) { 478 | c.listeners.xrange(TypePod, obj) 479 | } 480 | update := func(oldObj, newObj interface{}) { 481 | add(newObj) 482 | } 483 | del := func(obj interface{}) { 484 | c.listeners.xrange(TypePod, obj) 485 | } 486 | 487 | c.podInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ 488 | AddFunc: add, 489 | UpdateFunc: update, 490 | DeleteFunc: del, 491 | }) 492 | } 493 | 494 | func (c *ClusterCache) SyncPodsCache(timeout time.Duration) error { 495 | if !c.isFollowResource(TypePod) { 496 | return nil 497 | } 498 | 499 | done, timer := c.timeoutChan(timeout) 500 | defer timer.Stop() 501 | 502 | if !cache.WaitForCacheSync(done, c.podInformer.HasSynced) { 503 | return ErrSyncResourceTimeout 504 | } 505 | return nil 506 | } 507 | 508 | func (c *ClusterCache) bindNodesInformer() { 509 | if !c.isFollowResource(TypeNode) { 510 | return 511 | } 512 | 513 | c.nodeInformer = c.factory.Core().V1().Nodes().Informer() 514 | c.nodeLister = c.factory.Core().V1().Nodes().Lister() 515 | 516 | c.nodeInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ 517 | AddFunc: nil, 518 | UpdateFunc: nil, 519 | DeleteFunc: nil, 520 | }) 521 | } 522 | 523 | func (c *ClusterCache) SyncNodesCache(timeout time.Duration) error { 524 | done, timer := c.timeoutChan(timeout) 525 | defer timer.Stop() 526 | 527 | if !cache.WaitForCacheSync(done, c.nodeInformer.HasSynced) { 528 | return ErrSyncResourceTimeout 529 | } 530 | return nil 531 | } 532 | 533 | func (c *ClusterCache) bindServiceInformer() { 534 | if !c.isFollowResource(TypeService) { 535 | return 536 | } 537 | 538 | c.serviceInformer = c.factory.Core().V1().Services().Informer() 539 | c.serviceLister = c.factory.Core().V1().Services().Lister() 540 | 541 | add := func(obj interface{}) { 542 | c.listeners.xrange(TypeService, obj) 543 | } 544 | update := func(oldObj, newObj interface{}) { 545 | add(newObj) 546 | } 547 | del := func(obj interface{}) { 548 | c.listeners.xrange(TypeService, obj) 549 | } 550 | 551 | c.serviceInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ 552 | AddFunc: add, 553 | UpdateFunc: update, 554 | DeleteFunc: del, 555 | }) 556 | } 557 | 558 | func (c *ClusterCache) SyncServicesCache(timeout time.Duration) error { 559 | if !c.isFollowResource(TypeService) { 560 | return nil 561 | } 562 | 563 | done, timer := c.timeoutChan(timeout) 564 | defer timer.Stop() 565 | 566 | if !cache.WaitForCacheSync(done, c.serviceInformer.HasSynced) { 567 | return ErrSyncResourceTimeout 568 | } 569 | return nil 570 | } 571 | 572 | func (c *ClusterCache) bindDeploymentInformer() { 573 | if !c.isFollowResource(TypeDeployment) { 574 | return 575 | } 576 | 577 | c.deploymentInformer = c.factory.Apps().V1().Deployments().Informer() 578 | c.deploymentLister = c.factory.Apps().V1().Deployments().Lister() 579 | 580 | add := func(obj interface{}) { 581 | c.listeners.xrange(TypeDeployment, obj) 582 | } 583 | update := func(oldObj, newObj interface{}) { 584 | add(newObj) 585 | } 586 | del := func(obj interface{}) { 587 | c.listeners.xrange(TypeDeployment, obj) 588 | } 589 | 590 | c.deploymentInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ 591 | AddFunc: add, 592 | UpdateFunc: update, 593 | DeleteFunc: del, 594 | }) 595 | } 596 | 597 | func (c *ClusterCache) SyncDeploymentsCache(timeout time.Duration) error { 598 | if !c.isFollowResource(TypeDeployment) { 599 | return nil 600 | } 601 | 602 | done, timer := c.timeoutChan(timeout) 603 | defer timer.Stop() 604 | 605 | if !cache.WaitForCacheSync(done, c.deploymentInformer.HasSynced) { 606 | return ErrSyncResourceTimeout 607 | } 608 | return nil 609 | } 610 | 611 | func (c *ClusterCache) bindReplicaInformer() { 612 | if !c.isFollowResource(TypeReplicaSet) { 613 | return 614 | } 615 | 616 | c.replicaInformer = c.factory.Apps().V1().ReplicaSets().Informer() 617 | c.replicaLister = c.factory.Apps().V1().ReplicaSets().Lister() 618 | 619 | add := func(obj interface{}) { 620 | c.listeners.xrange(TypeReplicaSet, obj) 621 | } 622 | update := func(oldObj, newObj interface{}) { 623 | add(newObj) 624 | } 625 | del := func(obj interface{}) { 626 | c.listeners.xrange(TypeReplicaSet, obj) 627 | } 628 | 629 | c.replicaInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ 630 | AddFunc: add, 631 | UpdateFunc: update, 632 | DeleteFunc: del, 633 | }) 634 | } 635 | 636 | func (c *ClusterCache) SyncReplicasCache(timeout time.Duration) error { 637 | if !c.isFollowResource(TypeReplicaSet) { 638 | return nil 639 | } 640 | 641 | done, timer := c.timeoutChan(timeout) 642 | defer timer.Stop() 643 | 644 | if !cache.WaitForCacheSync(done, c.replicaInformer.HasSynced) { 645 | return ErrSyncResourceTimeout 646 | } 647 | return nil 648 | } 649 | 650 | func (c *ClusterCache) bindStatefulInformer() { 651 | if !c.isFollowResource(TypeStatefulSet) { 652 | return 653 | } 654 | 655 | c.statefulInformer = c.factory.Apps().V1().StatefulSets().Informer() 656 | c.statefulLister = c.factory.Apps().V1().StatefulSets().Lister() 657 | 658 | add := func(obj interface{}) { 659 | c.listeners.xrange(TypeStatefulSet, obj) 660 | } 661 | update := func(oldObj, newObj interface{}) { 662 | add(newObj) 663 | } 664 | del := func(obj interface{}) { 665 | c.listeners.xrange(TypeStatefulSet, obj) 666 | } 667 | 668 | c.statefulInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ 669 | AddFunc: add, 670 | UpdateFunc: update, 671 | DeleteFunc: del, 672 | }) 673 | } 674 | 675 | func (c *ClusterCache) SyncStatefulCache(timeout time.Duration) error { 676 | if !c.isFollowResource(TypeStatefulSet) { 677 | return nil 678 | } 679 | 680 | done, timer := c.timeoutChan(timeout) 681 | defer timer.Stop() 682 | 683 | if !cache.WaitForCacheSync(done, c.statefulInformer.HasSynced) { 684 | return ErrSyncResourceTimeout 685 | } 686 | return nil 687 | } 688 | 689 | func (c *ClusterCache) bindDaemonInformer() { 690 | if !c.isFollowResource(TypeDaemonSet) { 691 | return 692 | } 693 | 694 | c.daemonInformer = c.factory.Apps().V1().DaemonSets().Informer() 695 | c.daemonLister = c.factory.Apps().V1().DaemonSets().Lister() 696 | 697 | add := func(obj interface{}) { 698 | c.listeners.xrange(TypeDaemonSet, obj) 699 | } 700 | update := func(oldObj, newObj interface{}) { 701 | add(newObj) 702 | } 703 | del := func(obj interface{}) { 704 | c.listeners.xrange(TypeDaemonSet, obj) 705 | } 706 | 707 | c.daemonInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ 708 | AddFunc: add, 709 | UpdateFunc: update, 710 | DeleteFunc: del, 711 | }) 712 | } 713 | 714 | func (c *ClusterCache) SyncDaemonCache(timeout time.Duration) error { 715 | if !c.isFollowResource(TypeDaemonSet) { 716 | return nil 717 | } 718 | 719 | done, timer := c.timeoutChan(timeout) 720 | defer timer.Stop() 721 | 722 | if !cache.WaitForCacheSync(done, c.daemonInformer.HasSynced) { 723 | return ErrSyncResourceTimeout 724 | } 725 | return nil 726 | } 727 | 728 | func (c *ClusterCache) bindEventInformer() { 729 | if !c.isFollowResource(TypePod) { 730 | return 731 | } 732 | 733 | c.eventInformer = c.factory.Core().V1().Events().Informer() 734 | c.eventLister = c.factory.Core().V1().Events().Lister() 735 | 736 | add := func(obj interface{}) { 737 | c.listeners.xrange(TypeEvent, obj) 738 | } 739 | update := func(oldObj, newObj interface{}) { 740 | add(newObj) 741 | } 742 | del := func(obj interface{}) { 743 | c.listeners.xrange(TypeEvent, obj) 744 | } 745 | 746 | c.eventInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ 747 | AddFunc: add, 748 | UpdateFunc: update, 749 | DeleteFunc: del, 750 | }) 751 | } 752 | 753 | func (c *ClusterCache) SyncEventsCache(timeout time.Duration) error { 754 | if !c.isFollowResource(TypeEvent) { 755 | return nil 756 | } 757 | 758 | done, timer := c.timeoutChan(timeout) 759 | defer timer.Stop() 760 | 761 | if !cache.WaitForCacheSync(done, c.eventInformer.HasSynced) { 762 | return ErrSyncResourceTimeout 763 | } 764 | return nil 765 | } 766 | 767 | func (c *ClusterCache) bindEndpointsInformer() { 768 | if !c.isFollowResource(TypeEndpoint) { 769 | return 770 | } 771 | 772 | c.endpointInformer = c.factory.Core().V1().Endpoints().Informer() 773 | c.endpointLister = c.factory.Core().V1().Endpoints().Lister() 774 | 775 | add := func(obj interface{}) { 776 | c.listeners.xrange(TypeEndpoint, obj) 777 | } 778 | update := func(oldObj, newObj interface{}) { 779 | add(newObj) 780 | } 781 | del := func(obj interface{}) { 782 | c.listeners.xrange(TypeEndpoint, obj) 783 | } 784 | 785 | c.endpointInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ 786 | AddFunc: add, 787 | UpdateFunc: update, 788 | DeleteFunc: del, 789 | }) 790 | } 791 | 792 | func (c *ClusterCache) SyncEndpointsCache(timeout time.Duration) error { 793 | if !c.isFollowResource(TypeEndpoint) { 794 | return nil 795 | } 796 | 797 | done, timer := c.timeoutChan(timeout) 798 | defer timer.Stop() 799 | 800 | if !cache.WaitForCacheSync(done, c.endpointInformer.HasSynced) { 801 | return ErrSyncResourceTimeout 802 | } 803 | return nil 804 | } 805 | 806 | func (c *ClusterCache) ListDeployments(ns string) (*v1.DeploymentList, error) { 807 | items, err := c.k8sClient.client.AppsV1().Deployments(ns).List(metav1.ListOptions{}) 808 | return items, err 809 | } 810 | 811 | func (c *ClusterCache) WatchDeployments(ns string) (<-chan watch.Event, error) { 812 | watcher, err := c.k8sClient.client.AppsV1().Deployments(ns).Watch(metav1.ListOptions{}) 813 | if err != nil { 814 | return nil, err 815 | } 816 | return watcher.ResultChan(), nil 817 | } 818 | 819 | func (c *ClusterCache) ListReplicaSets(ns string) (*v1.ReplicaSetList, error) { 820 | items, err := c.k8sClient.client.AppsV1().ReplicaSets(ns).List(metav1.ListOptions{}) 821 | return items, err 822 | } 823 | 824 | func (c *ClusterCache) WatchReplicaSets(ns string) (<-chan watch.Event, error) { 825 | watcher, err := c.k8sClient.client.AppsV1().ReplicaSets(ns).Watch(metav1.ListOptions{}) 826 | if err != nil { 827 | return nil, err 828 | } 829 | 830 | return watcher.ResultChan(), nil 831 | } 832 | 833 | func (c *ClusterCache) ListPods(ns string) (*corev1.PodList, error) { 834 | items, err := c.k8sClient.client.CoreV1().Pods(c.namespace).List(metav1.ListOptions{}) 835 | return items, err 836 | } 837 | 838 | func (c *ClusterCache) WatchPods(ns string) (<-chan watch.Event, error) { 839 | watcher, err := c.k8sClient.client.CoreV1().Pods(ns).Watch(metav1.ListOptions{}) 840 | if err != nil { 841 | return nil, err 842 | } 843 | return watcher.ResultChan(), nil 844 | } 845 | 846 | func (c *ClusterCache) ListServices(ns string) (*corev1.ServiceList, error) { 847 | items, err := c.k8sClient.client.CoreV1().Services(ns).List(metav1.ListOptions{}) 848 | return items, err 849 | } 850 | 851 | func (c *ClusterCache) WatchServices(ns string) (<-chan watch.Event, error) { 852 | watcher, err := c.k8sClient.client.CoreV1().Services(ns).Watch(metav1.ListOptions{}) 853 | if err != nil { 854 | return nil, err 855 | } 856 | 857 | return watcher.ResultChan(), nil 858 | } 859 | 860 | func (c *ClusterCache) ListStatefuls(ns string) (*v1.StatefulSetList, error) { 861 | items, err := c.k8sClient.client.AppsV1().StatefulSets(ns).List(metav1.ListOptions{}) 862 | return items, err 863 | } 864 | 865 | func (c *ClusterCache) WatchStatefuls(ns string) (<-chan watch.Event, error) { 866 | watcher, err := c.k8sClient.client.AppsV1().StatefulSets(ns).Watch(metav1.ListOptions{}) 867 | if err != nil { 868 | return nil, err 869 | } 870 | 871 | return watcher.ResultChan(), nil 872 | } 873 | 874 | func (c *ClusterCache) ListDaemons(ns string) (*v1.DaemonSetList, error) { 875 | items, err := c.k8sClient.client.AppsV1().DaemonSets(ns).List(metav1.ListOptions{}) 876 | return items, err 877 | } 878 | 879 | func (c *ClusterCache) WatchDaemons(ns string) (<-chan watch.Event, error) { 880 | watcher, err := c.k8sClient.client.AppsV1().DaemonSets(ns).Watch(metav1.ListOptions{}) 881 | if err != nil { 882 | return nil, err 883 | } 884 | 885 | return watcher.ResultChan(), nil 886 | } 887 | 888 | func (c *ClusterCache) ListEvents(ns string) (*corev1.EventList, error) { 889 | items, err := c.k8sClient.client.CoreV1().Events(ns).List(metav1.ListOptions{}) 890 | return items, err 891 | } 892 | 893 | func (c *ClusterCache) WatchEvents(ns string) (<-chan watch.Event, error) { 894 | watcher, err := c.k8sClient.client.CoreV1().Events(ns).Watch(metav1.ListOptions{}) 895 | if err != nil { 896 | return nil, err 897 | } 898 | 899 | return watcher.ResultChan(), nil 900 | } 901 | 902 | func (c *ClusterCache) beforeValidate() error { 903 | if !c.isReady { 904 | return ErrCacheNotReady 905 | } 906 | if c.isExpired { 907 | return ErrBeyondExpireInterval 908 | } 909 | return nil 910 | } 911 | 912 | func (c *ClusterCache) GetNode(name string) (*corev1.Node, error) { 913 | if err := c.beforeValidate(); err != nil { 914 | return nil, err 915 | } 916 | 917 | return c.nodeLister.Get(name) 918 | } 919 | 920 | func (c *ClusterCache) GetNodesWithLabels(filter labels.Selector) ([]*corev1.Node, error) { 921 | if err := c.beforeValidate(); err != nil { 922 | return nil, err 923 | } 924 | 925 | return c.nodeLister.List(filter) 926 | } 927 | 928 | func (c *ClusterCache) GetNodes() ([]*corev1.Node, error) { 929 | if err := c.beforeValidate(); err != nil { 930 | return nil, err 931 | } 932 | 933 | return c.nodeLister.List(labels.Everything()) 934 | } 935 | 936 | func (c *ClusterCache) GetUpdateTime(ns string) (time.Time, error) { 937 | c.cacheMutex.RLock() 938 | defer c.cacheMutex.RUnlock() 939 | 940 | data, ok := c.cache[ns] 941 | if !ok { 942 | return time.Time{}, ErrNotFoundNamespace 943 | } 944 | 945 | return data.UpdateAT, nil 946 | } 947 | 948 | func (c *ClusterCache) GetNamespace(ns string) (*corev1.Namespace, error) { 949 | if err := c.beforeValidate(); err != nil { 950 | return nil, err 951 | } 952 | 953 | return c.namespaceLister.Get(ns) 954 | } 955 | 956 | func (c *ClusterCache) GetNamespacesWithLabels(filter labels.Selector) ([]*corev1.Namespace, error) { 957 | if err := c.beforeValidate(); err != nil { 958 | return nil, err 959 | } 960 | 961 | return c.namespaceLister.List(filter) 962 | } 963 | 964 | func (c *ClusterCache) GetNamespaces() ([]*corev1.Namespace, error) { 965 | if err := c.beforeValidate(); err != nil { 966 | return nil, err 967 | } 968 | 969 | return c.namespaceLister.List(labels.Everything()) 970 | } 971 | 972 | func (c *ClusterCache) GetAllPods(ns string) ([]*corev1.Pod, error) { 973 | if err := c.beforeValidate(); err != nil { 974 | return nil, err 975 | } 976 | 977 | return c.podLister.Pods(ns).List(labels.Everything()) 978 | } 979 | 980 | func (c *ClusterCache) GetPodsWithLabels(ns string, filter labels.Selector) ([]*corev1.Pod, error) { 981 | if err := c.beforeValidate(); err != nil { 982 | return nil, err 983 | } 984 | 985 | return c.podLister.Pods(ns).List(filter) 986 | } 987 | 988 | func (c *ClusterCache) GetPod(ns string, name string) (*corev1.Pod, error) { 989 | if err := c.beforeValidate(); err != nil { 990 | return nil, err 991 | } 992 | 993 | return c.podLister.Pods(ns).Get(name) 994 | } 995 | 996 | func (c *ClusterCache) GetServicesWithLabels(ns string, filter labels.Selector) ([]*corev1.Service, error) { 997 | if err := c.beforeValidate(); err != nil { 998 | return nil, err 999 | } 1000 | 1001 | return c.serviceLister.Services(ns).List(filter) 1002 | } 1003 | 1004 | func (c *ClusterCache) GetAllServices(ns string) ([]*corev1.Service, error) { 1005 | if err := c.beforeValidate(); err != nil { 1006 | return nil, err 1007 | } 1008 | 1009 | return c.serviceLister.Services(ns).List(labels.Everything()) 1010 | } 1011 | 1012 | func (c *ClusterCache) GetService(ns string, name string) (*corev1.Service, error) { 1013 | if err := c.beforeValidate(); err != nil { 1014 | return nil, err 1015 | } 1016 | 1017 | return c.serviceLister.Services(ns).Get(name) 1018 | } 1019 | 1020 | func (c *ClusterCache) GetAllDeployments(ns string) ([]*v1.Deployment, error) { 1021 | if err := c.beforeValidate(); err != nil { 1022 | return nil, err 1023 | } 1024 | 1025 | return c.deploymentLister.Deployments(ns).List(labels.Everything()) 1026 | } 1027 | 1028 | func (c *ClusterCache) GetDeploymentsWithLabels(ns string, filter labels.Selector) ([]*v1.Deployment, error) { 1029 | if err := c.beforeValidate(); err != nil { 1030 | return nil, err 1031 | } 1032 | 1033 | return c.deploymentLister.Deployments(ns).List(filter) 1034 | } 1035 | 1036 | func (c *ClusterCache) GetDeployment(ns string, name string) (*v1.Deployment, error) { 1037 | if err := c.beforeValidate(); err != nil { 1038 | return nil, err 1039 | } 1040 | 1041 | return c.deploymentLister.Deployments(ns).Get(name) 1042 | } 1043 | 1044 | func (c *ClusterCache) GetReplicasWithLabels(ns string, filter labels.Selector) ([]*v1.ReplicaSet, error) { 1045 | if err := c.beforeValidate(); err != nil { 1046 | return nil, err 1047 | } 1048 | 1049 | return c.replicaLister.ReplicaSets(ns).List(filter) 1050 | } 1051 | 1052 | func (c *ClusterCache) GetAllReplicas(ns string) ([]*v1.ReplicaSet, error) { 1053 | if err := c.beforeValidate(); err != nil { 1054 | return nil, err 1055 | } 1056 | 1057 | return c.replicaLister.ReplicaSets(ns).List(labels.Everything()) 1058 | } 1059 | 1060 | func (c *ClusterCache) GetReplica(ns string, name string) (*v1.ReplicaSet, error) { 1061 | if err := c.beforeValidate(); err != nil { 1062 | return nil, err 1063 | } 1064 | 1065 | return c.replicaLister.ReplicaSets(ns).Get(name) 1066 | } 1067 | 1068 | func (c *ClusterCache) GetAllDaemons(ns string) ([]*v1.DaemonSet, error) { 1069 | if err := c.beforeValidate(); err != nil { 1070 | return nil, err 1071 | } 1072 | 1073 | return c.daemonLister.DaemonSets(ns).List(labels.Everything()) 1074 | } 1075 | 1076 | func (c *ClusterCache) GetAllDaemonsWithLabels(ns string, filter labels.Selector) ([]*v1.DaemonSet, error) { 1077 | if err := c.beforeValidate(); err != nil { 1078 | return nil, err 1079 | } 1080 | 1081 | return c.daemonLister.DaemonSets(ns).List(filter) 1082 | } 1083 | 1084 | func (c *ClusterCache) GetDaemon(ns string, name string) (*v1.DaemonSet, error) { 1085 | if err := c.beforeValidate(); err != nil { 1086 | return nil, err 1087 | } 1088 | 1089 | return c.daemonLister.DaemonSets(ns).Get(name) 1090 | } 1091 | 1092 | func (c *ClusterCache) GetAllStatefuls(ns string) ([]*v1.StatefulSet, error) { 1093 | if err := c.beforeValidate(); err != nil { 1094 | return nil, err 1095 | } 1096 | 1097 | return c.statefulLister.StatefulSets(ns).List(labels.Everything()) 1098 | } 1099 | 1100 | func (c *ClusterCache) GetAllStatefulsWithLabels(ns string, filter labels.Selector) ([]*v1.StatefulSet, error) { 1101 | if err := c.beforeValidate(); err != nil { 1102 | return nil, err 1103 | } 1104 | 1105 | return c.statefulLister.StatefulSets(ns).List(nil) 1106 | } 1107 | 1108 | func (c *ClusterCache) GetStateful(ns string, name string) (*v1.StatefulSet, error) { 1109 | if err := c.beforeValidate(); err != nil { 1110 | return nil, err 1111 | } 1112 | 1113 | return c.statefulLister.StatefulSets(ns).Get(name) 1114 | } 1115 | 1116 | func (c *ClusterCache) GetAllEvents(ns string) ([]*corev1.Event, error) { 1117 | if err := c.beforeValidate(); err != nil { 1118 | return nil, err 1119 | } 1120 | 1121 | return c.eventLister.Events(ns).List(labels.Everything()) 1122 | } 1123 | 1124 | func (c *ClusterCache) GetAllEventsWithLabels(ns string, filter labels.Selector) ([]*corev1.Event, error) { 1125 | if err := c.beforeValidate(); err != nil { 1126 | return nil, err 1127 | } 1128 | 1129 | return c.eventLister.Events(ns).List(filter) 1130 | } 1131 | 1132 | func (c *ClusterCache) GetEvents(ns, name string) (*corev1.Event, error) { 1133 | if err := c.beforeValidate(); err != nil { 1134 | return nil, err 1135 | } 1136 | 1137 | return c.eventLister.Events(ns).Get(name) 1138 | } 1139 | 1140 | func (c *ClusterCache) GetAllEndpoints(ns string) ([]*corev1.Endpoints, error) { 1141 | if err := c.beforeValidate(); err != nil { 1142 | return nil, err 1143 | } 1144 | 1145 | return c.endpointLister.Endpoints(ns).List(labels.Everything()) 1146 | } 1147 | 1148 | func (c *ClusterCache) GetAllEndpointsWithLabels(ns string, filter labels.Selector) ([]*corev1.Endpoints, error) { 1149 | if err := c.beforeValidate(); err != nil { 1150 | return nil, err 1151 | } 1152 | 1153 | return c.endpointLister.Endpoints(ns).List(filter) 1154 | } 1155 | 1156 | func (c *ClusterCache) GetEndpoints(ns string, name string) (*corev1.Endpoints, error) { 1157 | if err := c.beforeValidate(); err != nil { 1158 | return nil, err 1159 | } 1160 | 1161 | return c.endpointLister.Endpoints(ns).Get(name) 1162 | } 1163 | 1164 | func int32Ptr2(i int32) *int32 { 1165 | return &i 1166 | } 1167 | 1168 | func getAllResources() map[string]bool { 1169 | ret := map[string]bool{} 1170 | for _, res := range allResources { 1171 | ret[res] = true 1172 | } 1173 | return ret 1174 | } 1175 | 1176 | func isSupportedResource(genre string) bool { 1177 | for _, res := range allResources { 1178 | if res == genre { 1179 | return true 1180 | } 1181 | } 1182 | return false 1183 | } 1184 | -------------------------------------------------------------------------------- /cache_test.go: -------------------------------------------------------------------------------- 1 | package k8scache 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "testing" 7 | "time" 8 | 9 | "github.com/stretchr/testify/assert" 10 | v1 "k8s.io/api/core/v1" 11 | ) 12 | 13 | func TestTimout(t *testing.T) { 14 | cc, err := NewClusterCache(nil) 15 | assert.Equal(t, err, nil) 16 | 17 | start := time.Now() 18 | done, _ := cc.timeoutChan(1 * time.Second) 19 | select { 20 | case <-done: 21 | } 22 | assert.Less(t, time.Since(start).Seconds(), float64(3)) 23 | 24 | start2 := time.Now() 25 | done2, _ := cc.timeoutChan(0) 26 | select { 27 | case <-done2: 28 | case <-time.After(1 * time.Second): 29 | } 30 | assert.Less(t, float64(0), time.Since(start2).Seconds()) 31 | } 32 | 33 | func TestNotReady(t *testing.T) { 34 | cc, err := NewClusterCache(nil) 35 | assert.Equal(t, err, nil) 36 | 37 | _, err = cc.GetNamespaces() 38 | assert.Equal(t, err, ErrCacheNotReady) 39 | } 40 | 41 | func newTestClient(t *testing.T) *ClusterCache { 42 | cli, err := NewClient() 43 | assert.Equal(t, err, nil) 44 | 45 | err = cli.BuildForKubecfg() 46 | assert.Equal(t, err, nil) 47 | 48 | cc, err := NewClusterCache(cli) 49 | assert.Equal(t, err, nil) 50 | return cc 51 | } 52 | 53 | func TestList(t *testing.T) { 54 | cc := newTestClient(t) 55 | pods, err := cc.ListPods("default") 56 | assert.Equal(t, err, nil) 57 | assert.Greater(t, len(pods.Items), 0) 58 | 59 | lis := NewAnyListener("default", "nginx-deployment-77c8657d4f-5qzxw", nil) 60 | cc.RegisterListener(TypePod, lis) 61 | 62 | go func() { 63 | for { 64 | select { 65 | case ev, ok := <-lis.Receive(): 66 | if !ok { 67 | assert.Equal(t, lis.err, nil) 68 | return 69 | } 70 | 71 | obj := ev.(*v1.Pod) 72 | fmt.Printf("ns: %s, podname: %s \n", obj.Namespace, obj.Name) 73 | } 74 | } 75 | }() 76 | 77 | time.AfterFunc(3*time.Second, func() { 78 | lis.Stop() 79 | }) 80 | 81 | cc.Start() 82 | cc.SyncCache() 83 | 84 | <-lis.Receive() 85 | } 86 | 87 | func TestSimple(t *testing.T) { 88 | cc := newTestClient(t) 89 | pods, err := cc.ListPods("default") 90 | assert.Equal(t, err, nil) 91 | assert.Greater(t, len(pods.Items), 0) 92 | 93 | cc.Start() 94 | cc.SyncCache() 95 | 96 | // evs, err := cc.ListEvents("default") 97 | 98 | evs, err := cc.GetAllEvents("default") 99 | assert.Equal(t, err, nil) 100 | assert.Greater(t, len(evs), 0) 101 | bs, err := json.Marshal(evs) 102 | fmt.Println(string(bs)) 103 | 104 | ends, err := cc.GetAllEndpoints("default") 105 | assert.Equal(t, err, nil) 106 | assert.Greater(t, len(ends), 0) 107 | 108 | nslist, err := cc.GetNamespaces() 109 | assert.Equal(t, err, nil) 110 | assert.Greater(t, len(nslist), 0) 111 | // fmt.Printf("namespace list %v \n\n", nslist) 112 | 113 | nodes, err := cc.GetNodes() 114 | assert.Equal(t, err, nil) 115 | assert.Greater(t, len(nodes), 0) 116 | // fmt.Printf("nodes list %v \n\n", nodes) 117 | 118 | mpods, err := cc.GetAllPods("default") 119 | assert.Equal(t, err, nil) 120 | assert.Greater(t, len(mpods), 0) 121 | // fmt.Printf("default pods list %v \n\n", mpods) 122 | 123 | svcs, err := cc.GetAllServices("default") 124 | assert.Equal(t, err, nil) 125 | assert.Greater(t, len(svcs), 0) 126 | // fmt.Printf("default services list %v \n\n", svcs) 127 | 128 | reps, err := cc.GetAllReplicas("default") 129 | assert.Equal(t, err, nil) 130 | assert.Greater(t, len(reps), 0) 131 | // fmt.Printf("default replicas list %v \n\n", reps) 132 | 133 | dms, err := cc.GetAllDeployments("default") 134 | assert.Equal(t, err, nil) 135 | assert.Greater(t, len(dms), 0) 136 | // fmt.Printf("default deployments list %v \n\n", dms) 137 | 138 | time.AfterFunc(1*time.Second, func() { 139 | cc.Stop() 140 | }) 141 | cc.Wait() 142 | } 143 | -------------------------------------------------------------------------------- /data_set.go: -------------------------------------------------------------------------------- 1 | package k8scache 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | v1 "k8s.io/api/apps/v1" 8 | corev1 "k8s.io/api/core/v1" 9 | ) 10 | 11 | func newDataSet(ns string) *DataSet { 12 | return &DataSet{ 13 | Namespace: ns, 14 | Pods: make(map[string]*corev1.Pod, 10), 15 | Services: make(map[string]*corev1.Service, 10), 16 | Deployments: make(map[string]*v1.Deployment, 10), 17 | Replicas: make(map[string]*v1.ReplicaSet, 10), 18 | Daemons: make(map[string]*v1.DaemonSet, 10), 19 | StatefulSets: make(map[string]*v1.StatefulSet, 10), 20 | Events: make(map[string]map[string][]*corev1.Event, 10), 21 | Endpoints: make(map[string]*corev1.Endpoints, 10), 22 | } 23 | } 24 | 25 | type DataSet struct { 26 | Pods map[string]*corev1.Pod // key: pod.name value: pod body 27 | Services map[string]*corev1.Service 28 | Deployments map[string]*v1.Deployment 29 | Replicas map[string]*v1.ReplicaSet 30 | Daemons map[string]*v1.DaemonSet 31 | StatefulSets map[string]*v1.StatefulSet 32 | Endpoints map[string]*corev1.Endpoints 33 | Events map[string]map[string][]*corev1.Event // key: Kind, key inside: name 34 | Namespace string // current ns 35 | UpdateAT time.Time 36 | 37 | sync.RWMutex // todo 38 | } 39 | 40 | func (d *DataSet) upsertPod(pod *corev1.Pod) { 41 | d.Pods[pod.Name] = pod 42 | d.updateTime() 43 | } 44 | 45 | func (d *DataSet) deletePod(pod *corev1.Pod) { 46 | delete(d.Pods, pod.Name) 47 | d.updateTime() 48 | } 49 | 50 | func (d *DataSet) upsertService(data *corev1.Service) { 51 | d.Services[data.Name] = data 52 | d.updateTime() 53 | } 54 | 55 | func (d *DataSet) deleteService(data *corev1.Service) { 56 | delete(d.Services, data.Name) 57 | d.updateTime() 58 | } 59 | 60 | func (d *DataSet) upsertDeployment(data *v1.Deployment) { 61 | d.Deployments[data.Name] = data 62 | d.updateTime() 63 | } 64 | 65 | func (d *DataSet) deleteDeployment(data *v1.Deployment) { 66 | delete(d.Deployments, data.Name) 67 | d.updateTime() 68 | } 69 | 70 | func (d *DataSet) upsertReplica(data *v1.ReplicaSet) { 71 | d.Replicas[data.Name] = data 72 | d.updateTime() 73 | } 74 | 75 | func (d *DataSet) deleteReplica(data *v1.ReplicaSet) { 76 | delete(d.Replicas, data.Name) 77 | d.updateTime() 78 | } 79 | 80 | func (d *DataSet) upsertDaemon(data *v1.DaemonSet) { 81 | d.Daemons[data.Name] = data 82 | d.updateTime() 83 | } 84 | 85 | func (d *DataSet) deleteDaemon(data *v1.DaemonSet) { 86 | delete(d.Daemons, data.Name) 87 | d.updateTime() 88 | } 89 | 90 | func (d *DataSet) upsertStateful(data *v1.StatefulSet) { 91 | d.StatefulSets[data.Name] = data 92 | d.updateTime() 93 | } 94 | 95 | func (d *DataSet) deleteStateful(data *v1.StatefulSet) { 96 | delete(d.StatefulSets, data.Name) 97 | d.updateTime() 98 | } 99 | 100 | func (d *DataSet) upsertEvent(data *corev1.Event) { 101 | kind := data.InvolvedObject.Kind 102 | kinds, ok := d.Events[kind] 103 | if !ok { 104 | kinds = make(map[string][]*corev1.Event, 5) 105 | } 106 | 107 | events, ok := kinds[data.Name] 108 | if !ok { 109 | events = make([]*corev1.Event, 0, 5) 110 | } 111 | // reduce 112 | if len(events) > 50 { 113 | events = events[20:] 114 | } 115 | events = append(events, data) 116 | kinds[data.Name] = events 117 | d.Events[kind] = kinds 118 | d.updateTime() 119 | } 120 | 121 | func (d *DataSet) deleteEvent(data *corev1.Event) { 122 | delete(d.Events, data.Kind) 123 | d.updateTime() 124 | } 125 | 126 | func (d *DataSet) upsertEndpoint(data *corev1.Endpoints) { 127 | d.Endpoints[data.Name] = data 128 | d.updateTime() 129 | } 130 | 131 | func (d *DataSet) deleteEndpoint(data *corev1.Endpoints) { 132 | delete(d.Endpoints, data.Name) 133 | d.updateTime() 134 | } 135 | 136 | func (d *DataSet) updateTime() { 137 | d.UpdateAT = time.Now() 138 | } 139 | 140 | func (d *DataSet) CopyPods() map[string]*corev1.Pod { 141 | resp := make(map[string]*corev1.Pod, len(d.Pods)) 142 | for key, val := range d.Pods { 143 | resp[key] = val 144 | } 145 | return resp 146 | } 147 | 148 | func (d *DataSet) CopyServices() map[string]*corev1.Service { 149 | resp := make(map[string]*corev1.Service, len(d.Services)) 150 | for key, val := range d.Services { 151 | resp[key] = val 152 | } 153 | return resp 154 | } 155 | 156 | func (d *DataSet) CopyReplicas() map[string]*v1.ReplicaSet { 157 | resp := make(map[string]*v1.ReplicaSet, len(d.Replicas)) 158 | for key, val := range d.Replicas { 159 | resp[key] = val 160 | } 161 | return resp 162 | } 163 | 164 | func (d *DataSet) CopyDeployments() map[string]*v1.Deployment { 165 | resp := make(map[string]*v1.Deployment, len(d.Deployments)) 166 | for key, val := range d.Deployments { 167 | resp[key] = val 168 | } 169 | return resp 170 | } 171 | 172 | func (d *DataSet) CopyStatefuls() map[string]*v1.StatefulSet { 173 | resp := make(map[string]*v1.StatefulSet, len(d.StatefulSets)) 174 | for key, val := range d.StatefulSets { 175 | resp[key] = val 176 | } 177 | return resp 178 | } 179 | 180 | func (d *DataSet) CopyDaemons() map[string]*v1.DaemonSet { 181 | resp := make(map[string]*v1.DaemonSet, len(d.Daemons)) 182 | for key, val := range d.Daemons { 183 | resp[key] = val 184 | } 185 | return resp 186 | } 187 | 188 | func (d *DataSet) CopyEvents() map[string]map[string][]*corev1.Event { 189 | resp := make(map[string]map[string][]*corev1.Event, len(d.Events)) 190 | for kind, names := range d.Events { 191 | resp[kind] = make(map[string][]*corev1.Event, len(names)) 192 | for name, evs := range names { 193 | resp[kind][name] = evs 194 | } 195 | } 196 | return resp 197 | } 198 | 199 | func (d *DataSet) CopyEndpoints() map[string]*corev1.Endpoints { 200 | resp := make(map[string]*corev1.Endpoints, len(d.Daemons)) 201 | for key, val := range d.Endpoints { 202 | resp[key] = val 203 | } 204 | return resp 205 | } 206 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/rfyiamcool/k8scache 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/gogo/protobuf v1.3.1 // indirect 7 | github.com/golang/protobuf v1.3.5 // indirect 8 | github.com/google/go-cmp v0.4.0 // indirect 9 | github.com/googleapis/gnostic v0.2.0 // indirect 10 | github.com/imdario/mergo v0.3.8 // indirect 11 | github.com/json-iterator/go v1.1.9 // indirect 12 | github.com/mitchellh/go-homedir v1.1.0 13 | github.com/stretchr/testify v1.5.1 14 | golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 // indirect 15 | golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 // indirect 16 | golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f // indirect 17 | golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect 18 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect 19 | k8s.io/api v0.17.3 20 | k8s.io/apimachinery v0.17.3 21 | k8s.io/client-go v0.17.3 22 | k8s.io/utils v0.0.0-20200414100711-2df71ebbae66 // indirect 23 | ) 24 | 25 | replace ( 26 | k8s.io/api => k8s.io/api v0.17.3 27 | k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.17.3 28 | k8s.io/apimachinery => k8s.io/apimachinery v0.17.4-beta.0 29 | k8s.io/apiserver => k8s.io/apiserver v0.17.3 30 | k8s.io/cli-runtime => k8s.io/cli-runtime v0.17.3 31 | k8s.io/client-go => k8s.io/client-go v0.17.3 32 | k8s.io/cloud-provider => k8s.io/cloud-provider v0.17.3 33 | k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.17.3 34 | k8s.io/code-generator => k8s.io/code-generator v0.17.4-beta.0 35 | k8s.io/component-base => k8s.io/component-base v0.17.3 36 | k8s.io/cri-api => k8s.io/cri-api v0.17.4-beta.0 37 | k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.17.3 38 | k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.17.3 39 | k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.17.3 40 | k8s.io/kube-proxy => k8s.io/kube-proxy v0.17.3 41 | k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.17.3 42 | k8s.io/kubectl => k8s.io/kubectl v0.17.3 43 | k8s.io/kubelet => k8s.io/kubelet v0.17.3 44 | k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.17.3 45 | k8s.io/metrics => k8s.io/metrics v0.17.3 46 | k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.17.3 47 | ) 48 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= 5 | github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= 6 | github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= 7 | github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= 8 | github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= 9 | github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= 10 | github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= 11 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 12 | github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= 13 | github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 14 | github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= 15 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 16 | github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 17 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 18 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 19 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 20 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 21 | github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= 22 | github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= 23 | github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= 24 | github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 25 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 26 | github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 27 | github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg= 28 | github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= 29 | github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= 30 | github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= 31 | github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= 32 | github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= 33 | github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 34 | github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= 35 | github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 36 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= 37 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 38 | github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903 h1:LbsanbbD6LieFkXbj9YNNBupiGHJgFeLpO0j0Fza1h8= 39 | github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 40 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 41 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 42 | github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 43 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 44 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 45 | github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls= 46 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 47 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 48 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 49 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 50 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 51 | github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= 52 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 53 | github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= 54 | github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= 55 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 56 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 57 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 58 | github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= 59 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 60 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 61 | github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= 62 | github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g= 63 | github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= 64 | github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= 65 | github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 66 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 67 | github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= 68 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 69 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 70 | github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 71 | github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= 72 | github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 73 | github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 74 | github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 75 | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= 76 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 77 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 78 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 79 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 80 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 81 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 82 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 83 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 84 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 85 | github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 86 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 87 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 88 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 89 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 90 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 91 | github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 92 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 93 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 94 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 95 | github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 96 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= 97 | github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 98 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 99 | github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 100 | github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= 101 | github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 102 | github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= 103 | github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 104 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 105 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 106 | github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= 107 | github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 108 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 109 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 110 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 111 | github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 112 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 113 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 114 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 115 | github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= 116 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 117 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 118 | golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 119 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 120 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 121 | golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo= 122 | golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 123 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 124 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 125 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 126 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 127 | golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 128 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 129 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 130 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 131 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 132 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 133 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 134 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 135 | golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 136 | golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 h1:Jcxah/M+oLZ/R4/z5RzfPzGbPXnVDPkEDtf2JnuxN+U= 137 | golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 138 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= 139 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 140 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 141 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= 142 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 143 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 144 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 145 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 146 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 147 | golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 148 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 149 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 150 | golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 151 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 152 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 153 | golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 154 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 155 | golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f h1:gWF768j/LaZugp8dyS4UwsslYCYz9XgFxvlgsn0n9H8= 156 | golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 157 | golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 158 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 159 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 160 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 161 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 162 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 163 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 164 | golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= 165 | golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 166 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 167 | golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 168 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 169 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 170 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 171 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 172 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 173 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 174 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 175 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 176 | google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= 177 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 178 | google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= 179 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 180 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 181 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 182 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 183 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 184 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 185 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 186 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 187 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 188 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 189 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 190 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 191 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 192 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 193 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 194 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 195 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 196 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 197 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 198 | k8s.io/api v0.17.3 h1:XAm3PZp3wnEdzekNkcmj/9Y1zdmQYJ1I4GKSBBZ8aG0= 199 | k8s.io/api v0.17.3/go.mod h1:YZ0OTkuw7ipbe305fMpIdf3GLXZKRigjtZaV5gzC2J0= 200 | k8s.io/apimachinery v0.17.4-beta.0 h1:MooC4gqziXNFqqqCJEsgKMkZjlPIeoHAVlHkX3NwrFk= 201 | k8s.io/apimachinery v0.17.4-beta.0/go.mod h1:gxLnyZcGNdZTCLnq3fgzyg2A5BVCHTNDFrw8AmuJ+0g= 202 | k8s.io/client-go v0.17.3 h1:deUna1Ksx05XeESH6XGCyONNFfiQmDdqeqUvicvP6nU= 203 | k8s.io/client-go v0.17.3/go.mod h1:cLXlTMtWHkuK4tD360KpWz2gG2KtdWEr/OT02i3emRQ= 204 | k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= 205 | k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 206 | k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 207 | k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= 208 | k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= 209 | k8s.io/klog/v2 v2.0.0 h1:Foj74zO6RbjjP4hBEKjnYtjjAhGg4jNynUdYF6fJrok= 210 | k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= 211 | k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= 212 | k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= 213 | k8s.io/utils v0.0.0-20200414100711-2df71ebbae66 h1:Ly1Oxdu5p5ZFmiVT71LFgeZETvMfZ1iBIGeOenT2JeM= 214 | k8s.io/utils v0.0.0-20200414100711-2df71ebbae66/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= 215 | sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= 216 | sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= 217 | sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= 218 | -------------------------------------------------------------------------------- /listener.go: -------------------------------------------------------------------------------- 1 | package k8scache 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | "time" 7 | 8 | v1 "k8s.io/api/apps/v1" 9 | corev1 "k8s.io/api/core/v1" 10 | ) 11 | 12 | var ( 13 | ErrFullQueue = errors.New("channel is full") 14 | ErrCauseTimeout = errors.New("timeout") 15 | ) 16 | 17 | type Listener interface { 18 | Receive() chan interface{} 19 | Notify(interface{}) 20 | IsDone() bool 21 | Equal(interface{}) bool 22 | Stop() 23 | } 24 | 25 | type ListenerPool struct { 26 | sync.Mutex 27 | 28 | data map[string]map[Listener]bool // all resources 29 | } 30 | 31 | func newListenerPool() *ListenerPool { 32 | return &ListenerPool{ 33 | data: make(map[string]map[Listener]bool, 10), 34 | } 35 | } 36 | 37 | func (lp *ListenerPool) add(genre string, lis Listener) error { 38 | lp.Lock() 39 | defer lp.Unlock() 40 | 41 | if !isSupportedResource(genre) { 42 | return ErrNotSupportResource 43 | } 44 | 45 | listeners, ok := lp.data[genre] 46 | if !ok { 47 | listeners = make(map[Listener]bool, 5) 48 | } 49 | listeners[lis] = true 50 | lp.data[genre] = listeners 51 | return nil 52 | } 53 | 54 | func (lp *ListenerPool) del(genre string, lis Listener) { 55 | lp.Lock() 56 | defer lp.Unlock() 57 | 58 | listeners, ok := lp.data[genre] 59 | if !ok { 60 | return 61 | } 62 | delete(listeners, lis) 63 | } 64 | 65 | func (lp *ListenerPool) copy(genre string) map[Listener]bool { 66 | lp.Lock() 67 | defer lp.Unlock() 68 | 69 | listeners, ok := lp.data[genre] 70 | if !ok { 71 | return map[Listener]bool{} 72 | } 73 | 74 | ndata := make(map[Listener]bool, len(listeners)) 75 | for k, v := range listeners { 76 | ndata[k] = v 77 | } 78 | return ndata 79 | } 80 | 81 | func (lp *ListenerPool) xrange(genre string, obj interface{}) { 82 | for lis, _ := range lp.copy(genre) { // reduce lock 83 | if lis.IsDone() { 84 | lp.del(genre, lis) 85 | continue 86 | } 87 | if !lis.Equal(obj) { 88 | continue 89 | } 90 | 91 | lis.Notify(obj) 92 | } 93 | } 94 | 95 | type AnyListener struct { 96 | Name string 97 | Namespace string 98 | isDone bool 99 | deadline *time.Timer 100 | err error 101 | 102 | sync.Mutex 103 | queue chan interface{} 104 | equalFunc func(interface{}) bool 105 | } 106 | 107 | // NewAnyListener if ns and name are null, always return true. 108 | func NewAnyListener(ns, name string, uequal func(interface{}) bool) *AnyListener { 109 | lis := &AnyListener{ 110 | Name: name, 111 | Namespace: ns, 112 | queue: make(chan interface{}, 5), 113 | equalFunc: uequal, 114 | } 115 | lis.deadline = time.AfterFunc(60*time.Second, func() { 116 | lis.Stop() 117 | if lis.err == nil { 118 | lis.err = ErrCauseTimeout 119 | } 120 | }) 121 | return lis 122 | } 123 | 124 | func (p *AnyListener) Equal(obj interface{}) bool { 125 | if p.equalFunc != nil { 126 | return p.equalFunc(obj) 127 | } 128 | if p.Namespace == "" && p.Name == "" { // all match 129 | return true 130 | } 131 | if p.Namespace != "" && p.Name == "" { // match all resource in namespace 132 | return true 133 | } 134 | 135 | var ( 136 | ns string 137 | name string 138 | ) 139 | 140 | switch obj.(type) { 141 | case *corev1.Pod: 142 | pod := obj.(*corev1.Pod) 143 | ns = pod.Namespace 144 | name = pod.Name 145 | 146 | case *corev1.Service: 147 | srv := obj.(*corev1.Service) 148 | ns = srv.Namespace 149 | name = srv.Name 150 | 151 | case *v1.Deployment: 152 | dm := obj.(*v1.Deployment) 153 | ns = dm.Namespace 154 | name = dm.Name 155 | 156 | case *v1.ReplicaSet: 157 | value := obj.(*v1.ReplicaSet) 158 | ns = value.Namespace 159 | name = value.Name 160 | 161 | case *v1.StatefulSet: 162 | value := obj.(*v1.StatefulSet) 163 | ns = value.Namespace 164 | name = value.Name 165 | 166 | case *v1.DaemonSet: 167 | value := obj.(*v1.DaemonSet) 168 | ns = value.Namespace 169 | name = value.Name 170 | 171 | case *corev1.Event: 172 | value := obj.(*corev1.Event) 173 | ns = value.Namespace 174 | name = value.Name 175 | 176 | case *corev1.Endpoints: 177 | value := obj.(*corev1.Endpoints) 178 | ns = value.Namespace 179 | name = value.Name 180 | } 181 | 182 | if ns != "" && name == "" { // match all resource in namespace 183 | return true 184 | } 185 | 186 | if ns == p.Namespace && name == p.Name { 187 | return true 188 | } 189 | return false 190 | } 191 | 192 | func (p *AnyListener) Error() error { 193 | return p.err 194 | } 195 | 196 | func (p *AnyListener) IsDone() bool { 197 | return p.isDone 198 | } 199 | 200 | func (p *AnyListener) Receive() chan interface{} { 201 | return p.queue 202 | } 203 | 204 | func (p *AnyListener) Notify(ev interface{}) { 205 | p.Lock() 206 | defer p.Unlock() 207 | 208 | if p.isDone { 209 | return 210 | } 211 | 212 | select { 213 | case p.queue <- ev: 214 | default: 215 | p.isDone = true 216 | p.err = ErrFullQueue 217 | close(p.queue) 218 | } 219 | } 220 | 221 | func (p *AnyListener) Stop() { 222 | p.Lock() 223 | defer p.Unlock() 224 | 225 | if p.isDone { 226 | return 227 | } 228 | p.isDone = true 229 | close(p.queue) 230 | } 231 | -------------------------------------------------------------------------------- /pool.go: -------------------------------------------------------------------------------- 1 | package k8scache 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type ClusterCachePool struct { 8 | sync.RWMutex 9 | data map[string]*ClusterCache // key: k8s cluster 10 | } 11 | 12 | func NewClusterCachePool() *ClusterCachePool { 13 | return &ClusterCachePool{ 14 | data: make(map[string]*ClusterCache, 2), 15 | } 16 | } 17 | 18 | func (cp *ClusterCachePool) Set(id string, cc *ClusterCache) { 19 | cp.Lock() 20 | defer cp.Unlock() 21 | 22 | cp.data[id] = cc 23 | } 24 | 25 | func (cp *ClusterCachePool) Get(id string) (*ClusterCache, bool) { 26 | cp.RLock() 27 | defer cp.RUnlock() 28 | 29 | val, ok := cp.data[id] 30 | return val, ok 31 | } 32 | --------------------------------------------------------------------------------