├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── controller.go ├── go.mod ├── go.sum ├── main.go ├── signals └── signal.go ├── sinks ├── cloudwatch.go ├── eventdata.go ├── interface.go └── stdoutsink.go └── yaml └── event-exporter.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | event-exporter 2 | .vscode 3 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gcr.io/google-containers/debian-base-amd64:0.3.2 2 | 3 | RUN clean-install ca-certificates 4 | 5 | COPY event-exporter / -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: container 2 | 3 | ENVVAR = GOOS=linux GOARCH=amd64 CGO_ENABLED=0 4 | BINARY_NAME = event-exporter 5 | 6 | PREFIX = nithmu 7 | IMAGE_NAME = k8s-event-exporter 8 | TAG = v0.1.0 9 | 10 | build: 11 | ${ENVVAR} go build -a -o ${BINARY_NAME} 12 | 13 | test: 14 | ${ENVVAR} go test ./... 15 | 16 | container: build 17 | docker build --pull -t ${PREFIX}/${IMAGE_NAME}:${TAG} . 18 | 19 | push: container 20 | gcloud docker -- push ${PREFIX}/${IMAGE_NAME}:${TAG} 21 | 22 | clean: 23 | rm -rf ${BINARY_NAME} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Event Exporter 2 | 3 | This tool is used to export Kubernetes events. It effectively runs a watch on 4 | the apiserver, detecting as granular as possible all changes to the event 5 | objects. Event exporter exports only to CloudWatch Logs. 6 | 7 | ## Build 8 | 9 | To build the binary, run 10 | 11 | ```shell 12 | make build 13 | ``` 14 | 15 | To run unit tests, run 16 | 17 | ```shell 18 | make test 19 | ``` 20 | 21 | To build the container, run 22 | 23 | ```shell 24 | make container 25 | ``` 26 | 27 | ## Run 28 | 29 | Event exporter require following environment variables: 30 | 31 | ``` 32 | CW_LOG_GROUP_NAME string 33 | CW_LOG_STREAM_NAME string 34 | AWS_REGION string 35 | ``` 36 | 37 | ## Deploy 38 | 39 | ``` 40 | export REGION="us-west-2" 41 | cat https://raw.githubusercontent.com/nithu0115/event-exporter/master/yaml/event-exporter.yaml | sed -e "s/REGION/$REGION/g" |kubectl apply -f - 42 | ``` 43 | 44 | ## Notes 45 | ### ClusterRoleBinding 46 | This pod's service account should be authorized to get events, you 47 | might need to set up ClusterRoleBinding in order to make it possible. Complete 48 | example with the service account and the cluster role binding you can find in 49 | the `yaml` directory. 50 | 51 | ### "resourceVersion for the provided watch is too old" 52 | On a system with few/no events, you may see "The resourceVersion for the provided 53 | watch is too old" warnings. These can be ignored. This is due to compacted resource 54 | versions being referenced. -------------------------------------------------------------------------------- /controller.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | sinks "github.com/event-exporter/sinks" 8 | log "k8s.io/klog" 9 | 10 | v1 "k8s.io/api/core/v1" 11 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 12 | coreinformers "k8s.io/client-go/informers/core/v1" 13 | "k8s.io/client-go/kubernetes" 14 | corelisters "k8s.io/client-go/listers/core/v1" 15 | "k8s.io/client-go/tools/cache" 16 | ) 17 | 18 | // EventRouter is responsible for maintaining a stream of kubernetes 19 | // system Events and pushing them to another channel for storage 20 | type EventRouter struct { 21 | // client is the main kubernetes interface 22 | client kubernetes.Interface 23 | 24 | // store of events populated by the shared informer 25 | lister corelisters.EventLister 26 | 27 | // returns true if the event store has been synced 28 | listerSynched cache.InformerSynced 29 | 30 | // event sink 31 | sink sinks.EventSinkInterface 32 | } 33 | 34 | // NewEventRouter will create a new event router using the input params 35 | func newEventRouter(kubeClient kubernetes.Interface, eventsInformer coreinformers.EventInformer) *EventRouter { 36 | var ctx context.Context 37 | 38 | er := &EventRouter{ 39 | client: kubeClient, 40 | sink: sinks.ManufactureSink(ctx), 41 | } 42 | 43 | eventsInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ 44 | AddFunc: er.addEvent, 45 | UpdateFunc: er.updateEvent, 46 | DeleteFunc: er.deleteEvent, 47 | }) 48 | er.lister = eventsInformer.Lister() 49 | er.listerSynched = eventsInformer.Informer().HasSynced 50 | return er 51 | } 52 | 53 | // Run starts the EventRouter/Controller. 54 | func (er *EventRouter) Run(stopCh <-chan struct{}) { 55 | defer utilruntime.HandleCrash() 56 | defer log.Infof("Shutting down EventRouter") 57 | 58 | log.Infof("Starting EventRouter") 59 | 60 | // here is where we kick the caches into gear 61 | if !cache.WaitForCacheSync(stopCh, er.listerSynched) { 62 | utilruntime.HandleError(fmt.Errorf("timed out waiting for caches to sync")) 63 | return 64 | } 65 | <-stopCh 66 | } 67 | 68 | // addEvent is called when an event is created, or during the initial list 69 | func (er *EventRouter) addEvent(obj interface{}) { 70 | event := obj.(*v1.Event) 71 | //fmt.Println(event) 72 | er.sink.UpdateEvents(event, nil) 73 | } 74 | 75 | // updateEvent is called any time there is an update to an existing event 76 | func (er *EventRouter) updateEvent(objOld interface{}, objNew interface{}) { 77 | oldEvent := objOld.(*v1.Event) 78 | newEvent := objNew.(*v1.Event) 79 | er.sink.UpdateEvents(newEvent, oldEvent) 80 | } 81 | 82 | // deleteEvent should only occur when the system garbage collects events via TTL expiration 83 | func (er *EventRouter) deleteEvent(obj interface{}) { 84 | event, ok := obj.(*v1.Event) 85 | 86 | if !ok { 87 | tombstone, ok := obj.(cache.DeletedFinalStateUnknown) 88 | if !ok { 89 | log.V(2).Info("Object is neither event nor tombstone: %+v", obj) 90 | return 91 | } 92 | event, ok = tombstone.Obj.(*v1.Event) 93 | if !ok { 94 | log.V(2).Info("Tombstone contains object that is not a pod: %+v", obj) 95 | return 96 | } 97 | } 98 | // NOTE: This should *only* happen on TTL expiration there 99 | // is no reason to push this to a sink 100 | log.V(5).Info("Event Deleted from the system:\n%v", event) 101 | } 102 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/event-exporter 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/aws/aws-sdk-go v1.26.4 7 | github.com/crewjam/rfc5424 v0.0.0-20180723152949-c25bdd3a0ba2 8 | github.com/eapache/channels v1.1.0 9 | github.com/eapache/queue v1.1.0 // indirect 10 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b 11 | github.com/google/uuid v1.1.1 12 | github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect 13 | github.com/imdario/mergo v0.3.8 // indirect 14 | github.com/json-iterator/go v1.1.8 15 | github.com/nytlabs/gojsonexplode v0.0.0-20160201065013-0f3fe6bb573f 16 | github.com/satori/go.uuid v1.2.0 17 | github.com/sethgrid/pester v0.0.0-20190127155807-68a33a018ad0 18 | github.com/sirupsen/logrus v1.2.0 19 | github.com/spf13/viper v1.5.0 20 | golang.org/x/oauth2 v0.0.0-20191122200657-5d9234df094c // indirect 21 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect 22 | gopkg.in/mcuadros/go-syslog.v2 v2.3.0 23 | k8s.io/api v0.0.0-20191121015604-11707872ac1c 24 | k8s.io/apimachinery v0.0.0-20191123233150-4c4803ed55e3 25 | k8s.io/client-go v0.0.0-20190620085101-78d2af792bab 26 | k8s.io/klog v1.0.0 27 | k8s.io/utils v0.0.0-20191114200735-6ca3b61696b6 // indirect 28 | ) 29 | -------------------------------------------------------------------------------- /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 | github.com/Azure/go-autorest v11.1.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= 4 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 5 | github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= 6 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 7 | github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 8 | github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= 9 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 10 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 11 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 12 | github.com/aws/aws-sdk-go v1.25.47 h1:Y13LHLosjP35FPWae95teJC4eQH2YeKD0I0dVFZ4CUM= 13 | github.com/aws/aws-sdk-go v1.25.47/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= 14 | github.com/aws/aws-sdk-go v1.26.4 h1:vQ1XmULJriCx8QTmvtEl511rskbZeTkr0xq59ky3kfI= 15 | github.com/aws/aws-sdk-go v1.26.4/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= 16 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 17 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 18 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 19 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 20 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 21 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 22 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 23 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 24 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 25 | github.com/crewjam/rfc5424 v0.0.0-20180723152949-c25bdd3a0ba2 h1:ikTypaS8gho3dBf1gySXxxv+NkB8vyYgqMPYv51LD4U= 26 | github.com/crewjam/rfc5424 v0.0.0-20180723152949-c25bdd3a0ba2/go.mod h1:+E6hJ4dnJi+OtRGvE3sIOIwMivXJTbRqZfQkWeANo6I= 27 | github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 28 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 29 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 30 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 31 | github.com/dgrijalva/jwt-go v0.0.0-20160705203006-01aeca54ebda/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 32 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 33 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 34 | github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= 35 | github.com/eapache/channels v1.1.0 h1:F1taHcn7/F0i8DYqKXJnyhJcVpp2kgFcNePxXtnyu4k= 36 | github.com/eapache/channels v1.1.0/go.mod h1:jMm2qB5Ubtg9zLd+inMZd2/NUvXgzmWXsDaLyQIGfH0= 37 | github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= 38 | github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= 39 | github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= 40 | github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= 41 | github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 42 | github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 43 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 44 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 45 | github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 46 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 47 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 48 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 49 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 50 | github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= 51 | github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= 52 | github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= 53 | github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= 54 | github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= 55 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 56 | github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 57 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 58 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 59 | github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I= 60 | github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 61 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= 62 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 63 | github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 64 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 65 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 66 | github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 67 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 68 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 69 | github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= 70 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 71 | github.com/google/btree v0.0.0-20160524151835-7d79101e329e/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 72 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 73 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 74 | github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= 75 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 76 | github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= 77 | github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= 78 | github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= 79 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 80 | github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 81 | github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= 82 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 83 | github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d h1:7XGaL1e6bYS1yIonGp9761ExpPPV1ui0SAC59Yube9k= 84 | github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= 85 | github.com/gophercloud/gophercloud v0.0.0-20190126172459-c818fa66e4c8/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4= 86 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 87 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 88 | github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 89 | github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 90 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 91 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 92 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 93 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 94 | github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= 95 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 96 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 97 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 98 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 99 | github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 100 | github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= 101 | github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 102 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= 103 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= 104 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 105 | github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 106 | github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 107 | github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok= 108 | github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 109 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 110 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 111 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 112 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 113 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 114 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 115 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 116 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 117 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 118 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 119 | github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= 120 | github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 121 | github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 122 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 123 | github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= 124 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 125 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 126 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 127 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 128 | github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 129 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 130 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 131 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 132 | github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 133 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 134 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= 135 | github.com/nytlabs/gojsonexplode v0.0.0-20160201065013-0f3fe6bb573f h1:QprIMH86OebshvSxWUmDHn7w8SKAhyXAQyts7ZuOyWo= 136 | github.com/nytlabs/gojsonexplode v0.0.0-20160201065013-0f3fe6bb573f/go.mod h1:oVmnO+LczepuilmxAKaD0a5ItmJLmELEVVDOdU5HQA0= 137 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 138 | github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 139 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 140 | github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 141 | github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= 142 | github.com/onsi/gomega v0.0.0-20190113212917-5533ce8a0da3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 143 | github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 144 | github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= 145 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 146 | github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= 147 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 148 | github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 149 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 150 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 151 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 152 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 153 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 154 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 155 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 156 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 157 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 158 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 159 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 160 | github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= 161 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 162 | github.com/sethgrid/pester v0.0.0-20190127155807-68a33a018ad0/go.mod h1:Ad7IjTpvzZO8Fl0vh9AzQ+j/jYZfyp2diGwI8m5q+ns= 163 | github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= 164 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 165 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 166 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 167 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 168 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 169 | github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= 170 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 171 | github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= 172 | github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= 173 | github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= 174 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 175 | github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= 176 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 177 | github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 178 | github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 179 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 180 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 181 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 182 | github.com/spf13/viper v1.5.0 h1:GpsTwfsQ27oS/Aha/6d1oD7tpKIqWnOA6tgOX9HHkt4= 183 | github.com/spf13/viper v1.5.0/go.mod h1:AkYRkVJF8TkSG/xet6PzXX+l39KhhXa2pdqVSxnTcn4= 184 | github.com/spf13/viper v1.6.1 h1:VPZzIkznI1YhVMRi6vNFLHSwhnhReBfgTxIPccpfdZk= 185 | github.com/spf13/viper v1.6.1/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k= 186 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 187 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 188 | github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 189 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 190 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 191 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 192 | github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= 193 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= 194 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 195 | github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= 196 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 197 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 198 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 199 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 200 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 201 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 202 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 203 | golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 204 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= 205 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 206 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 207 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 208 | golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 209 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 210 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 211 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 212 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 213 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 214 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 215 | golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 216 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 217 | golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 218 | golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI= 219 | golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 220 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= 221 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 222 | golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 223 | golang.org/x/oauth2 v0.0.0-20191122200657-5d9234df094c h1:HjRaKPaiWks0f5tA6ELVF7ZfqSppfPwOEEAvsrKUTO4= 224 | golang.org/x/oauth2 v0.0.0-20191122200657-5d9234df094c/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 225 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 226 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 227 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 228 | golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 229 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 230 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 231 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 232 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 233 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 234 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 235 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 236 | golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 h1:ng0gs1AKnRRuEMZoTLLlbOd+C17zUDepwGQBb/n+JVg= 237 | golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 238 | golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 239 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 240 | golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 241 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 242 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 243 | golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 244 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= 245 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 246 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= 247 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 248 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 249 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 250 | golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 251 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 252 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 253 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 254 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 255 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 256 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 257 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 258 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 259 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 260 | google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 261 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 262 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 263 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 264 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 265 | gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 266 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 267 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 268 | gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= 269 | gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 270 | gopkg.in/mcuadros/go-syslog.v2 v2.3.0/go.mod h1:l5LPIyOOyIdQquNg+oU6Z3524YwrcqEm0aKH+5zpt2U= 271 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 272 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 273 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 274 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 275 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 276 | gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= 277 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 278 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 279 | k8s.io/api v0.0.0-20190620084959-7cf5895f2711/go.mod h1:TBhBqb1AWbBQbW3XRusr7n7E4v2+5ZY8r8sAMnyFC5A= 280 | k8s.io/api v0.0.0-20191121015604-11707872ac1c h1:Z87my3sF4WhG0OMxzARkWY/IKBtOr+MhXZAb4ts6qFc= 281 | k8s.io/api v0.0.0-20191121015604-11707872ac1c/go.mod h1:R/s4gKT0V/cWEnbQa9taNRJNbWUK57/Dx6cPj6MD3A0= 282 | k8s.io/api v0.0.0-20191206001707-7edad22604e1 h1:Mcy2UO0z79WhIbPaHHqk6xSiR+Qn/5iWCxRcC5MnS+A= 283 | k8s.io/api v0.0.0-20191206001707-7edad22604e1/go.mod h1:WxerFZ1DOp5g/hA844ZoiGxrDSkaeY1Y4pBD58zoMsk= 284 | k8s.io/apimachinery v0.0.0-20190612205821-1799e75a0719/go.mod h1:I4A+glKBHiTgiEjQiCCQfCAIcIMFGt291SmsvcrFzJA= 285 | k8s.io/apimachinery v0.0.0-20191121015412-41065c7a8c2a/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= 286 | k8s.io/apimachinery v0.0.0-20191123233150-4c4803ed55e3 h1:FErmbNIJruD5GT2oVEjtPn5Ar5+rcWJsC8/PPUkR0s4= 287 | k8s.io/apimachinery v0.0.0-20191123233150-4c4803ed55e3/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= 288 | k8s.io/apimachinery v0.0.0-20191203211716-adc6f4cd9e7d h1:q+OZmYewHJeMCzwpHkXlNTtk5bvaUMPCikKvf77RBlo= 289 | k8s.io/apimachinery v0.0.0-20191203211716-adc6f4cd9e7d/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= 290 | k8s.io/client-go v0.0.0-20190620085101-78d2af792bab h1:E8Fecph0qbNsAbijJJQryKu4Oi9QTp5cVpjTE+nqg6g= 291 | k8s.io/client-go v0.0.0-20190620085101-78d2af792bab/go.mod h1:E95RaSlHr79aHaX0aGSwcPNfygDiPKOVXdmivCIZT0k= 292 | k8s.io/client-go v11.0.0+incompatible h1:LBbX2+lOwY9flffWlJM7f1Ct8V2SRNiMRDFeiwnJo9o= 293 | k8s.io/client-go v11.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= 294 | k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= 295 | k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 296 | k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 297 | k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 298 | k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= 299 | k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= 300 | k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= 301 | k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= 302 | k8s.io/utils v0.0.0-20190221042446-c2654d5206da/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0= 303 | k8s.io/utils v0.0.0-20191114200735-6ca3b61696b6 h1:p0Ai3qVtkbCG/Af26dBmU0E1W58NID3hSSh7cMyylpM= 304 | k8s.io/utils v0.0.0-20191114200735-6ca3b61696b6/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= 305 | sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= 306 | sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= 307 | sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= 308 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "os" 6 | "sync" 7 | 8 | "k8s.io/client-go/informers" 9 | "k8s.io/client-go/kubernetes" 10 | "k8s.io/client-go/tools/clientcmd" 11 | log "k8s.io/klog" 12 | 13 | "github.com/event-exporter/signals" 14 | ) 15 | 16 | var ( 17 | kubeconfigPath string 18 | apiServerAddr string 19 | ) 20 | 21 | func newKubernetesClient(kubeconfigPath, apiServerAddr string) (kubernetes.Interface, error) { 22 | config, err := clientcmd.BuildConfigFromFlags(apiServerAddr, kubeconfigPath) 23 | if err != nil { 24 | return nil, err 25 | } 26 | config.ContentType = "application/vnd.kubernetes.protobuf" 27 | return kubernetes.NewForConfig(config) 28 | } 29 | 30 | func init() { 31 | flag.StringVar(&apiServerAddr, "apiServerAddr", "", "The address of the Kubernetes API server (overrides any value in kubeconfig).") 32 | flag.StringVar(&kubeconfigPath, "kubeconfigPath", "", "Path to kubeconfig file with authorization and master location information.") 33 | } 34 | 35 | func main() { 36 | flag.Set("logtostderr", "true") 37 | defer log.Flush() 38 | flag.Parse() 39 | client, err := newKubernetesClient(kubeconfigPath, apiServerAddr) 40 | 41 | if err != nil { 42 | log.Fatal("Failed to initialize Kubernetes client: ", err) 43 | } 44 | 45 | sharedInformers := informers.NewSharedInformerFactory(client, 0) 46 | eventsInformer := sharedInformers.Core().V1().Events() 47 | 48 | eventExporter := newEventRouter(client, eventsInformer) 49 | stopCh := signals.SigHandler() 50 | 51 | if err != nil { 52 | log.Errorf("Error: %v", err) 53 | } 54 | wg := sync.WaitGroup{} 55 | wg.Add(1) 56 | go func() { 57 | defer wg.Done() 58 | eventExporter.Run(stopCh) 59 | }() 60 | 61 | // Startup the Informer(s) 62 | log.Infof("Starting shared Informer(s)") 63 | sharedInformers.Start(stopCh) 64 | wg.Wait() 65 | log.Warningf("Exiting main()") 66 | os.Exit(1) 67 | } 68 | -------------------------------------------------------------------------------- /signals/signal.go: -------------------------------------------------------------------------------- 1 | package signals 2 | 3 | import ( 4 | "os" 5 | "os/signal" 6 | "syscall" 7 | 8 | log "k8s.io/klog" 9 | ) 10 | 11 | // SigHandler a signal hander to gracefully exit 12 | func SigHandler() <-chan struct{} { 13 | stop := make(chan struct{}) 14 | go func() { 15 | c := make(chan os.Signal, 1) 16 | signal.Notify(c, 17 | syscall.SIGINT, // Ctrl+C 18 | syscall.SIGTERM, // Termination Request 19 | syscall.SIGSEGV, // FullDerp 20 | syscall.SIGABRT, // Abnormal termination 21 | syscall.SIGILL, // illegal instruction 22 | syscall.SIGFPE) // floating point - this is why we can't have nice things 23 | sig := <-c 24 | log.Warningf("Signal (%v) Detected, Shutting Down", sig) 25 | close(stop) 26 | }() 27 | return stop 28 | } 29 | -------------------------------------------------------------------------------- /sinks/cloudwatch.go: -------------------------------------------------------------------------------- 1 | package sinks 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "sort" 7 | "strings" 8 | "time" 9 | "unicode/utf8" 10 | 11 | "github.com/aws/aws-sdk-go/aws" 12 | "github.com/aws/aws-sdk-go/aws/awserr" 13 | "github.com/aws/aws-sdk-go/aws/session" 14 | "github.com/aws/aws-sdk-go/service/cloudwatchlogs" 15 | "github.com/eapache/channels" 16 | "github.com/golang/glog" 17 | v1 "k8s.io/api/core/v1" 18 | log "k8s.io/klog" 19 | ) 20 | 21 | const perEventBytes = 26 22 | const maxMessageSize = 1046528 23 | const logStreamInactivityTimeout = time.Hour 24 | 25 | /* 26 | CWLSink is the sink that uploads the kubernetes events as json object stored in a file. 27 | The sinker uploads it to CloudWatch Logs if any of the below criteria gets fullfilled 28 | 1) Time(uploadInterval): If the specfied time has passed since the last upload it uploads 29 | 2) [TODO] Data size: If the total data getting uploaded becomes greater than N bytes 30 | */ 31 | type CWLSink struct { 32 | //client from aws which makes the API call to CWL 33 | client LogsClient 34 | logGroupName string 35 | logStreamName string 36 | streams map[string]*logStream 37 | 38 | // lastUploadTimestamp stores the timestamp when the last upload to CWL happened 39 | lastUploadTimestamp int64 40 | 41 | // uploadInterval tells after how many seconds the next upload can happen 42 | // sink waits till this time is passed before next upload can happen 43 | uploadInterval time.Duration 44 | // eventCh is used to interact eventRouter and the sharedInformer 45 | eventCh channels.Channel 46 | 47 | // bodyBuf stores all the event captured data in a buffer before upload 48 | bodyBuf *bytes.Buffer 49 | } 50 | 51 | // LogsClient contains the CloudWatch API calls used by this plugin 52 | type LogsClient interface { 53 | PutLogEvents(input *cloudwatchlogs.PutLogEventsInput) (*cloudwatchlogs.PutLogEventsOutput, error) 54 | } 55 | 56 | type logStream struct { 57 | logEvents []*cloudwatchlogs.InputLogEvent 58 | currentByteLength int 59 | nextSequenceToken *string 60 | logStreamName string 61 | expiration time.Time 62 | } 63 | 64 | // NewCWLSink is the factory method constructing a new S3Sink 65 | func NewCWLSink(logGroupName string, logStreamName string, uploadInterval int, overflow bool, bufferSize int) (*CWLSink, error) { 66 | sess := session.Must(session.NewSessionWithOptions(session.Options{ 67 | SharedConfigState: session.SharedConfigEnable, 68 | })) 69 | 70 | client := cloudwatchlogs.New(sess) 71 | 72 | cwl := &CWLSink{ 73 | logGroupName: logGroupName, 74 | logStreamName: logStreamName, 75 | client: client, 76 | uploadInterval: time.Second * time.Duration(uploadInterval), 77 | streams: make(map[string]*logStream), 78 | bodyBuf: bytes.NewBuffer(make([]byte, 0, 4096)), 79 | } 80 | 81 | if overflow { 82 | cwl.eventCh = channels.NewOverflowingChannel(channels.BufferCap(bufferSize)) 83 | } else { 84 | cwl.eventCh = channels.NewNativeChannel(channels.BufferCap(bufferSize)) 85 | } 86 | 87 | return cwl, nil 88 | } 89 | 90 | // UpdateEvents implements the EventSinkInterface. It really just writes the 91 | // event data to the event OverflowingChannel, which should never block. 92 | // Messages that are buffered beyond the bufferSize specified for this HTTPSink 93 | // are discarded. 94 | func (cwl *CWLSink) UpdateEvents(eNew *v1.Event, eOld *v1.Event) { 95 | cwl.eventCh.In() <- NewEventData(eNew, eOld) 96 | } 97 | 98 | // Run sits in a loop, waiting for data to come in through h.eventCh, 99 | // and forwarding them to the HTTP sink. If multiple events have happened 100 | // between loop iterations, it puts all of them in one request instead of 101 | // making a single request per event. 102 | func (cwl *CWLSink) Run(stopCh <-chan bool) { 103 | loop: 104 | for { 105 | select { 106 | case e := <-cwl.eventCh.Out(): 107 | var evt EventData 108 | var ok bool 109 | if evt, ok = e.(EventData); !ok { 110 | glog.Warningf("Invalid type sent through event channel: %T", e) 111 | continue loop 112 | } 113 | 114 | // Start with just this event... 115 | arr := []EventData{evt} 116 | 117 | // Consume all buffered events into an array, in case more have been written 118 | // since we last forwarded them 119 | numEvents := cwl.eventCh.Len() 120 | for i := 0; i < numEvents; i++ { 121 | e := <-cwl.eventCh.Out() 122 | if evt, ok = e.(EventData); ok { 123 | arr = append(arr, evt) 124 | } else { 125 | glog.Warningf("Invalid type sent through event channel: %T", e) 126 | } 127 | } 128 | 129 | cwl.drainEvents(arr) 130 | case <-stopCh: 131 | break loop 132 | } 133 | } 134 | } 135 | 136 | // drainEvents takes an array of event data and sends it to s3 137 | func (cwl *CWLSink) drainEvents(events []EventData) { 138 | 139 | timestamp := time.Now() 140 | s := &logStream{ 141 | logStreamName: cwl.logStreamName, 142 | } 143 | 144 | for _, evt := range events { 145 | var messageSize int 146 | eJSONBytes, err := json.Marshal(evt) 147 | if err != nil { 148 | glog.Warningf("Failed to flatten json: %v", err) 149 | return 150 | } 151 | messageSize += len(eJSONBytes) 152 | //fmt.Println("size =====", messageSize) 153 | s.logEvents = append(s.logEvents, &cloudwatchlogs.InputLogEvent{ 154 | Message: aws.String(string(eJSONBytes)), 155 | Timestamp: aws.Int64(timestamp.UnixNano() / 1e6), // CloudWatch uses milliseconds since epoch 156 | }) 157 | s.currentByteLength += cloudwatchLen(string(eJSONBytes)) 158 | } 159 | 160 | if cwl.canUpload() == false { 161 | return 162 | } 163 | 164 | err := cwl.upload(s) 165 | if err != nil { 166 | log.Warning(err) 167 | } 168 | } 169 | 170 | // canUpload verifies the conditions suitable for a new file upload and upload the data 171 | func (cwl *CWLSink) canUpload() bool { 172 | now := time.Now().UnixNano() 173 | if (cwl.lastUploadTimestamp + cwl.uploadInterval.Nanoseconds()) < now { 174 | return true 175 | } 176 | return false 177 | } 178 | 179 | // upload uploads the events stored in buffer to s3 in the specified key 180 | // and clears the buffer 181 | func (cwl *CWLSink) upload(stream *logStream) error { 182 | // Reuse the body buffer for each request 183 | cwl.bodyBuf.Truncate(0) 184 | 185 | now := time.Now() 186 | stream.updateExpiration() 187 | // Log events in a single PutLogEvents request must be in chronological order. 188 | sort.Slice(stream.logEvents, func(i, j int) bool { 189 | return aws.Int64Value(stream.logEvents[i].Timestamp) < aws.Int64Value(stream.logEvents[j].Timestamp) 190 | }) 191 | 192 | log.Infof("Uploading LogEvents to CloudWatch Logs...") 193 | response, err := cwl.client.PutLogEvents(&cloudwatchlogs.PutLogEventsInput{ 194 | LogEvents: stream.logEvents, 195 | LogGroupName: aws.String(cwl.logGroupName), 196 | LogStreamName: aws.String(stream.logStreamName), 197 | SequenceToken: stream.nextSequenceToken, 198 | }) 199 | 200 | if err != nil { 201 | if awsErr, ok := err.(awserr.Error); ok { 202 | if awsErr.Code() == cloudwatchlogs.ErrCodeDataAlreadyAcceptedException { 203 | // already submitted, just grab the correct sequence token 204 | parts := strings.Split(awsErr.Message(), " ") 205 | stream.nextSequenceToken = &parts[len(parts)-1] 206 | stream.logEvents = stream.logEvents[:0] 207 | stream.currentByteLength = 0 208 | log.Infof("[cloudwatch] Encountered error %v; data already accepted, ignoring error\n", awsErr) 209 | return nil 210 | } else if awsErr.Code() == cloudwatchlogs.ErrCodeInvalidSequenceTokenException { 211 | // sequence code is bad, grab the correct one and retry 212 | parts := strings.Split(awsErr.Message(), " ") 213 | stream.nextSequenceToken = &parts[len(parts)-1] 214 | 215 | return cwl.upload(stream) 216 | } else { 217 | return err 218 | } 219 | } else { 220 | return err 221 | } 222 | } 223 | cwl.processRejectedEventsInfo(response) 224 | cwl.lastUploadTimestamp = now.UnixNano() 225 | stream.logEvents = stream.logEvents[:0] 226 | log.Infof("Uploaded to CloudWatch %v bytes", stream.currentByteLength) 227 | return nil 228 | } 229 | 230 | func (cwl *CWLSink) processRejectedEventsInfo(response *cloudwatchlogs.PutLogEventsOutput) { 231 | if response.RejectedLogEventsInfo != nil { 232 | if response.RejectedLogEventsInfo.ExpiredLogEventEndIndex != nil { 233 | log.Warning("[cloudwatch] %d log events were marked as expired by CloudWatch\n", aws.Int64Value(response.RejectedLogEventsInfo.ExpiredLogEventEndIndex)) 234 | } 235 | if response.RejectedLogEventsInfo.TooNewLogEventStartIndex != nil { 236 | log.Warning("[cloudwatch %d] %d log events were marked as too new by CloudWatch\n", aws.Int64Value(response.RejectedLogEventsInfo.TooNewLogEventStartIndex)) 237 | } 238 | if response.RejectedLogEventsInfo.TooOldLogEventEndIndex != nil { 239 | log.Warning("[cloudwatch] %d log events were marked as too old by CloudWatch\n", aws.Int64Value(response.RejectedLogEventsInfo.TooOldLogEventEndIndex)) 240 | } 241 | } 242 | } 243 | 244 | // effectiveLen counts the effective number of bytes in the string, after 245 | // UTF-8 normalization. UTF-8 normalization includes replacing bytes that do 246 | // not constitute valid UTF-8 encoded Unicode codepoints with the Unicode 247 | // replacement codepoint U+FFFD (a 3-byte UTF-8 sequence, represented in Go as 248 | // utf8.RuneError) 249 | func effectiveLen(line string) int { 250 | effectiveBytes := 0 251 | for _, rune := range line { 252 | effectiveBytes += utf8.RuneLen(rune) 253 | } 254 | return effectiveBytes 255 | } 256 | 257 | func cloudwatchLen(event string) int { 258 | return effectiveLen(event) + perEventBytes 259 | } 260 | 261 | func (stream *logStream) updateExpiration() { 262 | stream.expiration = time.Now().Add(logStreamInactivityTimeout) 263 | } 264 | 265 | /* ToDo auto create log group and log stream 266 | // CreateLogGroup 267 | func createLogGroup() (logGroupName string, err error) { 268 | logGroup := "kubernetes/Event_Exporter_Log_Group" 269 | 270 | sess := session.Must(session.NewSessionWithOptions(session.Options{ 271 | SharedConfigState: session.SharedConfigEnable, 272 | })) 273 | 274 | //session 275 | client := cloudwatchlogs.New(sess) 276 | 277 | //Input 278 | logGroupNameInput := &cloudwatchlogs.CreateLogGroupInput{ 279 | LogGroupName: aws.String(logGroup), 280 | } 281 | //create 282 | _, err = client.CreateLogGroup(logGroupNameInput) 283 | 284 | if err != nil { 285 | if awsErr, ok := err.(awserr.Error); ok { 286 | if awsErr.Code() != cloudwatchlogs.ErrCodeResourceAlreadyExistsException { 287 | return "", err 288 | } 289 | log.V(2).Infof("cloudwatch Log group already exists\n") 290 | } else { 291 | return "", err 292 | } 293 | } 294 | return logGroup, nil 295 | } 296 | 297 | func createLogStream(logGroupName string) (logStreamName string, err error) { 298 | 299 | //generate UUID 300 | id, err := uuid.NewRandom() 301 | if err != nil { 302 | log.Exit("Unable to generate Unique ID") 303 | } 304 | 305 | logstreamOutputName := "eventData" + id.String() 306 | 307 | sess := session.Must(session.NewSessionWithOptions(session.Options{ 308 | SharedConfigState: session.SharedConfigEnable, 309 | })) 310 | 311 | //session 312 | client := cloudwatchlogs.New(sess) 313 | 314 | //Input 315 | logStreamNameInput := &cloudwatchlogs.CreateLogStreamInput{ 316 | LogGroupName: aws.String(logGroupName), 317 | LogStreamName: aws.String(logstreamOutputName), 318 | } 319 | //create 320 | _, err = client.CreateLogStream(logStreamNameInput) 321 | if err != nil { 322 | if awsErr, ok := err.(awserr.Error); ok { 323 | if awsErr.Code() != cloudwatchlogs.ErrCodeResourceAlreadyExistsException { 324 | return "", err 325 | } 326 | log.Infof("cloudwatch Log Stream %s already exists\n", logstreamOutputName) 327 | } else { 328 | return "", err 329 | } 330 | } 331 | return logstreamOutputName, nil 332 | } 333 | */ 334 | -------------------------------------------------------------------------------- /sinks/eventdata.go: -------------------------------------------------------------------------------- 1 | package sinks 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/crewjam/rfc5424" 9 | jsoniter "github.com/json-iterator/go" 10 | "github.com/json-iterator/go/extra" 11 | v1 "k8s.io/api/core/v1" 12 | 13 | "github.com/nytlabs/gojsonexplode" 14 | ) 15 | 16 | // EventData encodes an eventrouter event and previous event, with a verb for 17 | // whether the event is created or updated. 18 | type EventData struct { 19 | Verb string `json:"verb"` 20 | Event *v1.Event `json:"event"` 21 | OldEvent *v1.Event `json:"old_event,omitempty"` 22 | } 23 | 24 | // NewEventData constructs an EventData struct from an old and new event, 25 | // setting the verb accordingly 26 | func NewEventData(eNew *v1.Event, eOld *v1.Event) EventData { 27 | var eData EventData 28 | if eOld == nil { 29 | eData = EventData{ 30 | Verb: "ADDED", 31 | Event: eNew, 32 | } 33 | } else { 34 | eData = EventData{ 35 | Verb: "UPDATED", 36 | Event: eNew, 37 | OldEvent: eOld, 38 | } 39 | } 40 | 41 | return eData 42 | } 43 | 44 | // WriteRFC5424 writes the current event data to the given io.Writer using 45 | // RFC5424 (syslog over TCP) syntax. 46 | func (e *EventData) WriteRFC5424(w io.Writer) (int64, error) { 47 | var eJSONBytes []byte 48 | var err error 49 | if eJSONBytes, err = json.Marshal(e); err != nil { 50 | return 0, fmt.Errorf("failed to json serialize event: %v", err) 51 | } 52 | 53 | // Each message should look like an RFC5424 syslog message: 54 | // 55 | // 56 | // Note: There are some restrictions on length and character space for 57 | // Hostname and AppName, see 58 | // https://github.com/crewjam/rfc5424/blob/master/marshal.go#L90. There's no 59 | // attempt at trying to clean them up here because hostnames and component 60 | // names already adhere to this convention in practice. 61 | msg := rfc5424.Message{ 62 | Priority: rfc5424.Daemon, 63 | Timestamp: e.Event.LastTimestamp.Time, 64 | Hostname: e.Event.Source.Host, 65 | AppName: e.Event.Source.Component, 66 | Message: eJSONBytes, 67 | } 68 | 69 | return msg.WriteTo(w) 70 | } 71 | 72 | // WriteFlattenedJSON writes the json to the file in the below format 73 | // 1) Flattens the json into a not nested key:value 74 | // 2) Convert the json into snake format 75 | // Eg: {"event_involved_object_kind":"pod", "event_metadata_namespace":"kube-system"} 76 | func (e *EventData) WriteFlattenedJSON(w io.Writer) (int64, error) { 77 | var eJSONBytes []byte 78 | var err error 79 | extra.SetNamingStrategy(extra.LowerCaseWithUnderscores) 80 | if eJSONBytes, err = jsoniter.Marshal(e); err != nil { 81 | return 0, fmt.Errorf("failed to json serialize event: %v", err) 82 | } 83 | 84 | result, err := gojsonexplode.Explodejsonstr(string(eJSONBytes), "_") 85 | if err != nil { 86 | return 0, fmt.Errorf("failed to flatten json: %v", err) 87 | } 88 | 89 | written, err := w.Write([]byte(result)) 90 | return int64(written), err 91 | } 92 | -------------------------------------------------------------------------------- /sinks/interface.go: -------------------------------------------------------------------------------- 1 | package sinks 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "os" 7 | 8 | "github.com/spf13/viper" 9 | v1 "k8s.io/api/core/v1" 10 | log "k8s.io/klog" 11 | ) 12 | 13 | const ( 14 | sink string = "SINK" 15 | logGroupNameEnv string = "CW_LOG_GROUP_NAME" 16 | logStreamNameEnv string = "CW_LOG_STREAM_NAME" 17 | ) 18 | 19 | // EventSinkInterface is the interface used to shunt events 20 | type EventSinkInterface interface { 21 | UpdateEvents(eNew *v1.Event, eOld *v1.Event) 22 | } 23 | 24 | // ManufactureSink will manufacture a sink according to viper configs 25 | func ManufactureSink(ctx context.Context) (e EventSinkInterface) { 26 | s, ok := os.LookupEnv(sink) 27 | if !ok || s == "" { 28 | log.Warningf("SINK is not set! Setting it to CloudWatchLogs") 29 | viper.SetDefault("SINK", "CWL") 30 | s = viper.GetString("SINK") 31 | } 32 | log.Infof("Sink is [%v]", s) 33 | switch s { 34 | case "stdoutsink": 35 | e = NewStdoutSink(ctx) 36 | 37 | case "CWL": 38 | logGroupName, ok := os.LookupEnv(logGroupNameEnv) 39 | if !ok || logGroupName == "" { 40 | log.Exitf("Missing CWL Log Group, please set CW_LOG_GROUP_NAME Env variable") 41 | } 42 | 43 | logStreamName, ok := os.LookupEnv(logStreamNameEnv) 44 | if !ok || logGroupName == "" { 45 | log.Exitf("Missing CWL Log Stream, please set CW_LOG_STREAM_NAME Env variable") 46 | } 47 | // By default we buffer up to 1500 events, and drop messages if more than 48 | // 1500 have come in without getting consumed 49 | viper.SetDefault("sinkBufferSize", 1500) 50 | viper.SetDefault("sinkDiscardMessages", true) 51 | 52 | viper.SetDefault("sinkUploadInterval", 5) 53 | uploadInterval := viper.GetInt("sinkUploadInterval") 54 | 55 | bufferSize := viper.GetInt("sinkBufferSize") 56 | overflow := viper.GetBool("sinkDiscardMessages") 57 | 58 | cwl, err := NewCWLSink(logGroupName, logStreamName, uploadInterval, overflow, bufferSize) 59 | if err != nil { 60 | log.Fatal(err.Error()) 61 | } 62 | 63 | go cwl.Run(make(chan bool)) 64 | return cwl 65 | 66 | // case "logfile" 67 | default: 68 | err := errors.New("Invalid Sink Specified") 69 | log.Fatalf("%v, Sink variable not set, exiting program...", err.Error()) 70 | } 71 | return e 72 | } 73 | -------------------------------------------------------------------------------- /sinks/stdoutsink.go: -------------------------------------------------------------------------------- 1 | package sinks 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sync" 7 | 8 | jsoniter "github.com/json-iterator/go" 9 | 10 | v1 "k8s.io/api/core/v1" 11 | log "k8s.io/klog" 12 | ) 13 | 14 | // let's have few go routines to emit out glog. 15 | const concurrency = 5 16 | 17 | // UpdateEvent could be send via channels too 18 | type UpdateEvent struct { 19 | eNew *v1.Event 20 | eOld *v1.Event 21 | } 22 | 23 | // StdOutSink is the most basic sink 24 | type StdOutSink struct { 25 | updateChan chan UpdateEvent 26 | } 27 | 28 | // NewStdoutSink will create a new 29 | func NewStdoutSink(ctx context.Context) EventSinkInterface { 30 | ss := &StdOutSink{ 31 | updateChan: make(chan UpdateEvent), 32 | } 33 | 34 | var wg sync.WaitGroup 35 | wg.Add(concurrency) 36 | log.V(3).Infof("Starting glog sink with concurrency=%d", concurrency) 37 | // let's have couple of parallel routines 38 | go func() { 39 | defer wg.Done() 40 | ss.updateEvents(ctx) 41 | }() 42 | 43 | // wait 44 | go func() { 45 | wg.Wait() 46 | log.V(3).Info("Stopping glog sink WaitGroup") 47 | close(ss.updateChan) 48 | }() 49 | 50 | return ss 51 | } 52 | 53 | // UpdateEvents implements the EventSinkInterface. 54 | // This is not a non-blocking call because the channel could get full. But ATM I do not care because 55 | // glog just logs the message. It is CPU heavy (JSON Marshalling) and has no I/O. So the time complexity of the 56 | // blocking call is very minimal. Also we could spawn more routines of updateEvents to make it concurrent. 57 | func (ss *StdOutSink) UpdateEvents(eNew *v1.Event, eOld *v1.Event) { 58 | ss.updateChan <- UpdateEvent{ 59 | eNew: eNew, 60 | eOld: eOld, 61 | } 62 | } 63 | 64 | func (ss *StdOutSink) updateEvents(ctx context.Context) { 65 | var json = jsoniter.ConfigCompatibleWithStandardLibrary 66 | for { 67 | select { 68 | case event := <-ss.updateChan: 69 | eData := NewEventData(event.eNew, event.eOld) 70 | if eJSONBytes, err := json.Marshal(eData); err == nil { 71 | fmt.Println(string(eJSONBytes)) 72 | } else { 73 | log.Warningf("Failed to json serialize event: %v", err) 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /yaml/event-exporter.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: event-exporter-sa 5 | namespace: kube-system 6 | --- 7 | apiVersion: v1 8 | data: 9 | CW_LOG_GROUP_NAME: "kubernetes/Event_Exporter_Log_Group" 10 | CW_LOG_STREAM_NAME: "eventData-12f6d78" 11 | AWS_REGION: "REGION" 12 | kind: ConfigMap 13 | metadata: 14 | name: event-exporter-cm 15 | namespace: kube-system 16 | --- 17 | apiVersion: rbac.authorization.k8s.io/v1beta1 18 | kind: ClusterRoleBinding 19 | metadata: 20 | name: event-exporter-rb 21 | roleRef: 22 | apiGroup: rbac.authorization.k8s.io 23 | kind: ClusterRole 24 | name: view 25 | subjects: 26 | - kind: ServiceAccount 27 | name: event-exporter-sa 28 | namespace: kube-system 29 | --- 30 | apiVersion: apps/v1 31 | kind: Deployment 32 | metadata: 33 | name: event-exporter-deployment 34 | namespace: kube-system 35 | spec: 36 | replicas: 1 37 | selector: 38 | matchLabels: 39 | app: event-exporter 40 | template: 41 | metadata: 42 | labels: 43 | app: event-exporter 44 | spec: 45 | serviceAccountName: event-exporter-sa 46 | containers: 47 | - name: event-exporter 48 | image: nithmu/k8s-event-exporter:v0.1.0 49 | command: 50 | - '/event-exporter' 51 | envFrom: 52 | - configMapRef: 53 | name: event-exporter-cm 54 | --------------------------------------------------------------------------------