├── .dockerignore ├── .github └── workflows │ └── build.yml ├── .gitignore ├── .golangci.yml ├── .protokaf.yaml ├── LICENSE ├── Makefile ├── README.md ├── build └── docker │ ├── Dockerfile │ ├── docker-compose.yaml │ └── kafka_server_jaas.conf ├── cmd ├── cmd_build_test.go ├── cmd_buld.go ├── cmd_consume.go ├── cmd_consume_test.go ├── cmd_list.go ├── cmd_produce.go ├── cmd_produce_test.go ├── cmd_root.go ├── cmd_root_test.go ├── common.go ├── config.go ├── flags.go ├── logger.go └── test_helpers.go ├── go.mod ├── go.sum ├── internal ├── calldata │ ├── calldata.go │ ├── functions.go │ └── functions_test.go ├── kafka │ ├── consumer.go │ ├── dump.go │ ├── dump_test.go │ ├── kafka.go │ ├── producer.go │ ├── sasl.go │ └── scram.go ├── proto │ ├── proto.go │ ├── proto_encoder.go │ ├── proto_test.go │ └── testdata │ │ ├── example.proto │ │ ├── google │ │ └── type │ │ │ └── money.proto │ │ ├── parse_err.proto │ │ ├── types.proto │ │ └── with_local_import.proto ├── tracing │ ├── jaeger.go │ ├── tracing.go │ └── tracing_test.go └── utils │ └── dump │ ├── dump.go │ └── dump_test.go ├── main.go └── scripts ├── system.mk └── system ├── compose.mk ├── env.mk └── help.mk /.dockerignore: -------------------------------------------------------------------------------- 1 | build 2 | README.md 3 | CHANGELOG.md 4 | .gitignore 5 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | name: Build & Test 13 | runs-on: ubuntu-latest 14 | steps: 15 | 16 | - name: Set up Go 1.x 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: ^1.17 20 | 21 | - name: Check out code 22 | uses: actions/checkout@v2 23 | 24 | - name: Download module dependencies 25 | env: 26 | GOPROXY: "https://proxy.golang.org" 27 | run: go mod download 28 | 29 | - name: Test 30 | run: go test -v -coverprofile=coverage.txt ./... 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE 2 | .idea 3 | .vscode 4 | 5 | # Output of the go covetage tool 6 | *.out 7 | 8 | # Test binary `go test -c` 9 | *.test 10 | !.env.test 11 | 12 | protokaf 13 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | # default concurrency is a available CPU number 3 | concurrency: 4 4 | 5 | # timeout for analysis, e.g. 30s, 5m, default is 1m 6 | timeout: 5m 7 | 8 | # exit code when at least one issue was found, default is 1 9 | issues-exit-code: 1 10 | 11 | # include test files or not, default is true 12 | tests: true 13 | 14 | # skip download modules 15 | modules-download-mode: readonly 16 | 17 | # output configuration options 18 | output: 19 | # colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number" 20 | format: colored-line-number 21 | 22 | # print lines of code with issue, default is true 23 | print-issued-lines: true 24 | 25 | # print linter name in the end of issue text, default is true 26 | print-linter-name: true 27 | 28 | linters-settings: 29 | funlen: 30 | lines: 90 31 | golint: 32 | min-confidence: 0.9 33 | lll: 34 | line-length: 140 35 | 36 | linters: 37 | enable: 38 | - govet 39 | - errcheck 40 | - staticcheck 41 | - unused 42 | - gosimple 43 | - structcheck 44 | - varcheck 45 | - ineffassign 46 | - deadcode 47 | - typecheck 48 | - bodyclose 49 | - revive 50 | - stylecheck 51 | - gosec 52 | - unconvert 53 | - dupl 54 | - goconst 55 | - gocyclo 56 | - gocognit 57 | - gofmt 58 | - depguard 59 | - misspell 60 | - lll 61 | - dogsled 62 | - nakedret 63 | - prealloc 64 | - exportloopref 65 | - gocritic 66 | - funlen 67 | - whitespace 68 | 69 | issues: 70 | # List of regexps of issue texts to exclude, empty list by default. 71 | # But independently from this option we use default exclude patterns, 72 | # it can be disabled by `exclude-use-default: false`. To list all 73 | # excluded by default patterns execute `golangci-lint run --help` 74 | exclude: 75 | - Using the variable on range scope `tt` in function literal 76 | -------------------------------------------------------------------------------- /.protokaf.yaml: -------------------------------------------------------------------------------- 1 | debug: true 2 | broker: 0.0.0.0:9092 3 | #broker: 0.0.0.0:9093 # with auth 4 | #kafka-auth-dsn: "SCRAM-SHA-256:admin:secret" 5 | output: json # text 6 | proto: 7 | - internal/proto/testdata/example.proto 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | https://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | Copyright 2021 LLC "Instamart Technologies" 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | https://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include scripts/*.mk 2 | 3 | APP_NAME=protokaf 4 | 5 | run: ## Run protokaf 6 | @go run . 7 | 8 | test: ## Run tests 9 | go test -cover -p 1 -count=1 ./... 10 | 11 | none: 12 | sleep 31536000 13 | 14 | lint: ## Run linter 15 | golangci-lint run 16 | 17 | install: 18 | go install . 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # protokaf 2 | 3 | Kafka producer and consumer tool in protobuf format. 4 | 5 | ## Features 6 | - Consume and produce messages using Protobuf protocol 7 | - Trace messages with Jaeger 8 | - Create custom templates for one or multiple messages and produce them to Kafka 9 | 10 | ## Install 11 | ```sh 12 | go install github.com/kuper-tech/protokaf@latest 13 | ``` 14 | 15 | ## Configuration 16 | Configuration file is optional, so you can skip this section. 17 | 18 | In order for Protokaf to work, it needs to know how to reach your Kafka broker. First option is to provide `--broker` each time you invoke Protokaf. Another option is to use a configuration file. You can provide Protokaf with a configuration file with option `-F ..` on the command line. Or by default Protokaf will search its config files in `.protokaf.yaml` and `$HOME/.protokaf.yaml` respectively. 19 | 20 | **Example of `.protokaf.yaml`** 21 | ```yaml 22 | debug: true 23 | broker: ":" 24 | kafka-auth-dsn: "SCRAM-SHA-512::" 25 | proto: "/" 26 | ``` 27 | 28 | ## Help 29 | ```sh 30 | $ protokaf help 31 | ``` 32 | 33 | ## List metadata 34 | ```sh 35 | $ protokaf list [-t (,...)] 36 | 1 brokers: 37 | broker 1 "127.0.0.1:9093" 38 | 2 topics: 39 | topic "test-topic", partitions: 1 40 | partition 0, leader 1, replicas: [1] (offline: []), isrs: [1] 41 | topic "test", partitions: 1 42 | partition 0, leader 1, replicas: [1] (offline: []), isrs: [1] 43 | ``` 44 | 45 | ## Produce 46 | ### Help 47 | ```sh 48 | $ protokaf produce -h 49 | ``` 50 | 51 | ### Examples 52 | This proto file will be used in the examples below. 53 | 54 | `api/example.proto` 55 | ```protobuf 56 | syntax = "proto3"; 57 | 58 | package example; 59 | 60 | message HelloRequest { 61 | string name = 1; 62 | int32 age = 2; 63 | } 64 | ``` 65 | 66 | **A simple produce message** 67 | ```sh 68 | $ protokaf produce HelloRequest \ 69 | --broker kafka:9092 \ 70 | --proto internal/proto/testdata/example.proto \ 71 | --topic test \ 72 | --data '{"name": "Alice", "age": 11}' 73 | ``` 74 | 75 | **Produce message with headers** 76 | ```sh 77 | $ protokaf produce HelloRequest \ 78 | --broker kafka:9092 \ 79 | --proto internal/proto/testdata/example.proto \ 80 | --topic test \ 81 | --header "priority=high" \ 82 | --header "application=protokaf" \ 83 | --data '{"name": "Alice", "age": 11}' 84 | ``` 85 | 86 | **Produce message with template** 87 | ```sh 88 | $ protokaf produce HelloRequest \ 89 | --broker kafka:9092 \ 90 | --proto internal/proto/testdata/example.proto \ 91 | --topic test \ 92 | --data '{"name": {{randomFemaleName | quote}}, "age": {{randomNumber 10 20}}}' \ 93 | --count 10 \ 94 | --seed 42 95 | ``` 96 | 97 | **Produce message with Kafka auth** 98 | ```sh 99 | $ protokaf produce HelloRequest \ 100 | --broker kafka:9093 \ 101 | --kafka-auth-dsn "SCRAM-SHA-512:login:passwd" \ 102 | --proto internal/proto/testdata/example.proto \ 103 | --topic test \ 104 | --data '{"name": "Alice", "age": 11}' 105 | ``` 106 | 107 | **Read data from stdin or flag** 108 | 109 | Read message `HelloRequest` from `stdin`, produce to `test` topic 110 | ```sh 111 | $ echo '{"name": "Alice", "age": 11}' | protokaf produce HelloRequest -t test 112 | ``` 113 | 114 | Read message `HelloRequest` from `-d` value, produce to `test` topic 115 | ```sh 116 | $ protokaf produce HelloRequest -t test -d '{"name": "Alice", "age": 11}' 117 | ``` 118 | 119 | ### Template 120 | **Template options** 121 | * `--seed ` You can set number greater then zero to produce the same pseudo-random sequence of messages 122 | * `--count ` Useful for generating messages with random data 123 | * `--concurrency ` Number of message senders to run concurrently for const concurrency producing 124 | 125 | **Show all template functions** 126 | ```sh 127 | $ protokaf produce --template-functions-print 128 | ``` 129 | 130 | ## Build json template by proto file 131 | This can be useful for creating body for produce command 132 | ```sh 133 | $ protokaf build HelloRequest --proto internal/proto/testdata/example.proto 134 | ``` 135 | For proto file 136 | ```protobuf 137 | syntax = "proto3"; 138 | 139 | package example; 140 | 141 | message HelloRequest { 142 | enum Status { 143 | PENDING = 0; 144 | COMPLETED = 1; 145 | } 146 | 147 | string name = 1; 148 | int32 age = 2; 149 | optional float amount = 4; 150 | Status status = 5; 151 | repeated string keys = 6; 152 | } 153 | ``` 154 | command will print 155 | ```json 156 | { 157 | "name": "", 158 | "age": 0, 159 | "amount": 0, 160 | "status": "PENDING", 161 | "keys": [ 162 | "" 163 | ] 164 | } 165 | 166 | ``` 167 | 168 | ## Consume 169 | ### Help 170 | ```sh 171 | $ protokaf help consume 172 | ``` 173 | 174 | ### Examples 175 | ```sh 176 | $ protokaf consume HelloRequest \ 177 | --broker kafka:9092 \ 178 | --proto internal/proto/testdata/example.proto \ 179 | --group mygroup \ 180 | --topic test 181 | ``` 182 | 183 | **Read messages from Kafka `test` topic, use group `mygroup`, print to `stdout`** 184 | ```sh 185 | $ protokaf consume HelloRequest -G mygroup -t test 186 | ``` 187 | 188 | **Read the last `10` messages from `test` topic, then exit** 189 | ```sh 190 | $ protokaf consume HelloRequest -G mygroup -t test -c 10 191 | ``` 192 | 193 | **Read from offset `5` messages from `test` topic** 194 | ```sh 195 | $ protokaf consume HelloRequest -G mygroup -t test -o 5 196 | ``` 197 | 198 | ## Testing 199 | 200 | ### Prepare test environment and running tests 201 | ```sh 202 | make docker-dev-up 203 | make kafka-users 204 | make test 205 | make install # optional (you can use 'go run . ') 206 | ``` 207 | -------------------------------------------------------------------------------- /build/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.19 2 | 3 | # copy files for build 4 | COPY . . 5 | 6 | CMD ["make", "run"] 7 | -------------------------------------------------------------------------------- /build/docker/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | app: 5 | container_name: ${APP_PROJECT}_app 6 | build: 7 | context: ../.. 8 | dockerfile: ./build/docker/Dockerfile 9 | args: 10 | - APP_PATH=${APP_PATH} 11 | command: make none 12 | depends_on: 13 | - kafka 14 | links: 15 | - kafka:kafka.local 16 | working_dir: ${APP_PATH} 17 | volumes: 18 | - ../..:${APP_PATH} 19 | - $GOPATH/pkg/mod:/go/pkg/mod 20 | kafka: 21 | container_name: ${APP_PROJECT}_kafka 22 | image: wurstmeister/kafka:2.13-2.8.1 23 | hostname: kafka 24 | ports: 25 | - "9092:9092" 26 | - "9093:9093" 27 | environment: 28 | KAFKA_ZOOKEEPER_CONNECT: "zookeeper:2181" 29 | KAFKA_BROKER_ID: 1 30 | KAFKA_INTER_BROKER_LISTENER_NAME: INSIDE 31 | KAFKA_ADVERTISED_PORT: 9092 32 | KAFKA_NUM_PARTITIONS: 1 33 | KAFKA_DEFAULT_REPLICATION_FACTOR: 1 34 | KAFKA_OFFSETS_TOPIC_NUM_PARTITIONS: 1 35 | KAFKA_ADVERTISED_LISTENERS: INSIDE_SASL://kafka:19093,INSIDE://kafka:19092,OUTSIDE_SASL://${DOCKER_HOST_IP:-127.0.0.1}:9093,OUTSIDE://${DOCKER_HOST_IP:-127.0.0.1}:9092 36 | KAFKA_LISTENERS: INSIDE://:19092,INSIDE_SASL://:19093,OUTSIDE://:9092,OUTSIDE_SASL://:9093 37 | KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INSIDE:PLAINTEXT,INSIDE_SASL:SASL_PLAINTEXT,OUTSIDE_SASL:SASL_PLAINTEXT,OUTSIDE:PLAINTEXT 38 | 39 | # auth related options: 40 | KAFKA_OPTS: "-Djava.security.auth.login.config=/etc/kafka/kafka_server_jaas.conf" 41 | KAFKA_SASL_MECHANISM_INTER_BROKER_PROTOCOL: PLAIN 42 | KAFKA_SASL_ENABLED_MECHANISMS: "PLAIN,SCRAM-SHA-256,SCRAM-SHA-512" 43 | KAFKA_AUTHORIZER_CLASS_NAME: "kafka.security.authorizer.AclAuthorizer" 44 | KAFKA_SUPER_USERS: "User:admin" 45 | KAFKA_ALLOW_EVERYONE_IF_NO_ACL_FOUND: "true" 46 | volumes: 47 | - /var/run/docker.sock:/var/run/docker.sock 48 | - kafka-data:/opt/kafka 49 | - ./kafka_server_jaas.conf:/etc/kafka/kafka_server_jaas.conf 50 | depends_on: 51 | - zookeeper 52 | restart: always 53 | zookeeper: 54 | container_name: ${APP_PROJECT}_zookeeper 55 | image: zookeeper:3.4.14 56 | ports: 57 | - "2181" 58 | platform: "linux/x86_64" 59 | jaeger: 60 | image: jaegertracing/all-in-one:1.58 61 | container_name: ${APP_PROJECT}_jaeger 62 | environment: 63 | COLLECTOR_ZIPKIN_HTTP_PORT: 9411 64 | ports: 65 | - "6831:6831/udp" 66 | - "16686:16686" 67 | - "9411:9411" 68 | 69 | volumes: 70 | kafka-data: 71 | driver: local 72 | 73 | networks: 74 | default: 75 | name: ${APP_PROJECT} 76 | -------------------------------------------------------------------------------- /build/docker/kafka_server_jaas.conf: -------------------------------------------------------------------------------- 1 | KafkaServer { 2 | org.apache.kafka.common.security.scram.ScramLoginModule required 3 | username="admin" 4 | password="secret" 5 | user_admin="secret" 6 | user_client="secret"; 7 | }; 8 | -------------------------------------------------------------------------------- /cmd/cmd_build_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func Test_NewBuildCmd(t *testing.T) { 10 | cmd := NewBuildCmd() 11 | NewFlags(cmd).Init() 12 | cmd.SetArgs([]string{"ExampleMessage", "--proto", "../internal/proto/testdata/types.proto"}) 13 | 14 | expected := `{ 15 | "int32Field": 0, 16 | "int64Field": "0", 17 | "uint32Field": 0, 18 | "uint64Field": "0", 19 | "sint32Field": 0, 20 | "sint64Field": "0", 21 | "fixed32Field": 0, 22 | "fixed64Field": "0", 23 | "sfixed32Field": 0, 24 | "sfixed64Field": "0", 25 | "floatField": 0, 26 | "doubleField": 0, 27 | "boolField": true, 28 | "stringField": "", 29 | "bytesField": "", 30 | "enumField": "UNKNOWN", 31 | "messageField": { 32 | "nestedInt32": 0, 33 | "nestedString": "" 34 | }, 35 | "repeatedInt32Field": [ 36 | 0 37 | ], 38 | "repeatedStringField": [ 39 | "" 40 | ], 41 | "mapStringInt32Field": { 42 | "": 0 43 | }, 44 | "mapInt32MessageField": { 45 | "0": { 46 | "nestedInt32": 0, 47 | "nestedString": "" 48 | } 49 | }, 50 | "option1": 0, 51 | "anyField": null, 52 | "timestampField": "1970-01-01T00:00:00Z", 53 | "durationField": "0s", 54 | "structField": { 55 | "": null 56 | }, 57 | "valueField": null, 58 | "listValueField": [ 59 | null 60 | ], 61 | "boolValueField": true, 62 | "bytesValueField": null, 63 | "doubleValueField": 0, 64 | "floatValueField": 0, 65 | "int32ValueField": 0, 66 | "int64ValueField": "0", 67 | "stringValueField": "", 68 | "uint32ValueField": 0, 69 | "uint64ValueField": "0" 70 | }` 71 | 72 | stdout, stderr, err := getCommandOut(t, cmd) 73 | 74 | require.Nil(t, err) 75 | require.Equal(t, "", stderr) 76 | require.Contains(t, stdout, expected) 77 | } 78 | -------------------------------------------------------------------------------- /cmd/cmd_buld.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/golang/protobuf/jsonpb" 5 | "github.com/jhump/protoreflect/desc" 6 | "github.com/jhump/protoreflect/dynamic" 7 | "github.com/spf13/cobra" 8 | "google.golang.org/protobuf/types/descriptorpb" 9 | "google.golang.org/protobuf/types/known/anypb" 10 | ) 11 | 12 | func NewBuildCmd() *cobra.Command { 13 | cmd := &cobra.Command{ 14 | Use: "build ", 15 | Short: "Build json by proto message", 16 | Args: messageNameRequired, 17 | RunE: func(cmd *cobra.Command, args []string) (err error) { 18 | // parse protofiles & create proto object 19 | p, err := parseProtofiles() 20 | if err != nil { 21 | return 22 | } 23 | 24 | messageDescriptor, err := findMessage(p, args[0]) 25 | if err != nil { 26 | return 27 | } 28 | 29 | msg := buildMessage(dynamic.NewMessage(messageDescriptor)) 30 | 31 | b, err := msg.MarshalJSONPB(&jsonpb.Marshaler{ 32 | EmitDefaults: true, 33 | Indent: " ", 34 | }) 35 | if err != nil { 36 | return 37 | } 38 | 39 | cmd.Println(string(b)) 40 | 41 | return 42 | }, 43 | } 44 | 45 | return cmd 46 | } 47 | 48 | func buildMessage(message *dynamic.Message) *dynamic.Message { 49 | for _, field := range message.GetMessageDescriptor().GetFields() { 50 | switch { 51 | case field.IsRepeated(): 52 | message.SetField(field, []interface{}{buildDefaultValue(field)}) 53 | case field.IsMap(): 54 | message.SetField( 55 | field, 56 | map[interface{}]interface{}{ 57 | buildDefaultValue(field.GetMapKeyType()): buildDefaultValue(field.GetMapValueType()), 58 | }, 59 | ) 60 | case field.GetOneOf() != nil: 61 | oneOfField := field.GetOneOf().GetChoices()[0] 62 | message.SetField(oneOfField, buildDefaultValue(oneOfField)) 63 | default: 64 | message.SetField(field, buildDefaultValue(field)) 65 | } 66 | } 67 | 68 | return message 69 | } 70 | 71 | func buildDefaultValue(field *desc.FieldDescriptor) interface{} { 72 | switch field.GetType() { 73 | case descriptorpb.FieldDescriptorProto_TYPE_FIXED32, 74 | descriptorpb.FieldDescriptorProto_TYPE_UINT32: 75 | return uint32(0) 76 | case descriptorpb.FieldDescriptorProto_TYPE_SFIXED32, 77 | descriptorpb.FieldDescriptorProto_TYPE_INT32, 78 | descriptorpb.FieldDescriptorProto_TYPE_SINT32: 79 | return int32(0) 80 | case descriptorpb.FieldDescriptorProto_TYPE_FIXED64, 81 | descriptorpb.FieldDescriptorProto_TYPE_UINT64: 82 | return uint64(0) 83 | case descriptorpb.FieldDescriptorProto_TYPE_SFIXED64, 84 | descriptorpb.FieldDescriptorProto_TYPE_INT64, 85 | descriptorpb.FieldDescriptorProto_TYPE_SINT64: 86 | return int64(0) 87 | case descriptorpb.FieldDescriptorProto_TYPE_FLOAT: 88 | return float32(0) 89 | case descriptorpb.FieldDescriptorProto_TYPE_DOUBLE: 90 | return float64(0) 91 | case descriptorpb.FieldDescriptorProto_TYPE_BOOL: 92 | return true 93 | case descriptorpb.FieldDescriptorProto_TYPE_BYTES: 94 | return []byte(nil) 95 | case descriptorpb.FieldDescriptorProto_TYPE_STRING: 96 | return "" 97 | case descriptorpb.FieldDescriptorProto_TYPE_ENUM: 98 | return field.GetEnumType().GetValues()[0].GetNumber() 99 | case descriptorpb.FieldDescriptorProto_TYPE_MESSAGE: 100 | if field.GetMessageType().GetFullyQualifiedName() == "google.protobuf.Any" { 101 | val, _ := anypb.New(nil) 102 | 103 | return val 104 | } 105 | 106 | return buildMessage(dynamic.NewMessage(field.GetMessageType())) 107 | } 108 | 109 | return nil 110 | } 111 | -------------------------------------------------------------------------------- /cmd/cmd_consume.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "strconv" 8 | "sync" 9 | 10 | "github.com/Shopify/sarama" 11 | "github.com/jhump/protoreflect/desc" 12 | "github.com/jhump/protoreflect/dynamic" 13 | "github.com/kuper-tech/protokaf/internal/kafka" 14 | "github.com/kuper-tech/protokaf/internal/utils/dump" 15 | "github.com/spf13/cobra" 16 | "github.com/spf13/viper" 17 | ) 18 | 19 | var ( 20 | ErrInvalidOffset = errors.New("invalid offset format") 21 | ErrOffsetNotSet = errors.New("offset not set") 22 | ) 23 | 24 | func NewConsumeCmd() *cobra.Command { 25 | var ( 26 | groupFlag string 27 | topicsFlag []string 28 | countFlag int 29 | noCommit bool 30 | offset string 31 | ) 32 | 33 | cmd := &cobra.Command{ 34 | Use: "consume ", 35 | Short: "Consume mode", 36 | Args: messageNameRequired, 37 | RunE: func(cmd *cobra.Command, args []string) (err error) { 38 | // parse protofiles & create proto object 39 | p, err := parseProtofiles() 40 | if err != nil { 41 | return 42 | } 43 | 44 | // find message descriptor 45 | md, err := findMessage(p, args[0]) 46 | if err != nil { 47 | return 48 | } 49 | 50 | if noCommit { 51 | kafkaConfig.Consumer.Offsets.AutoCommit.Enable = false 52 | } 53 | 54 | // consumer 55 | consumer, err := kafka.NewConsumerGroup(viper.GetStringSlice("broker"), groupFlag, kafkaConfig) 56 | if err != nil { 57 | return 58 | } 59 | defer consumer.Close() 60 | 61 | // start 62 | go func() { 63 | log.Infof("Consume topics: %v", topicsFlag) 64 | if countFlag > 0 { 65 | log.Infof("Message consuming limit: %d", countFlag) 66 | } 67 | 68 | handler := &protoHandler{ 69 | MaxCount: countFlag, 70 | desc: md, 71 | topic: topicsFlag[0], 72 | } 73 | 74 | // set offset 75 | offsetsArg, err := parseOffsetFlag(offset) 76 | if err != nil && !errors.Is(err, ErrOffsetNotSet) { 77 | log.Errorf("Failed to parse offset: %s", err) 78 | return 79 | } 80 | handler.offset = offsetsArg 81 | 82 | err = consumer.Consume(context.Background(), topicsFlag, handler) 83 | 84 | if handler.maximumReached() { 85 | log.Debugf("Message consuming limit reached: %d", countFlag) 86 | return 87 | } 88 | 89 | if err != nil { 90 | log.Errorf("Consume error: %s", err) 91 | } 92 | 93 | consumer.Close() 94 | }() 95 | 96 | // track errors 97 | for err = range consumer.Errors() { 98 | if errors.Is(err, ErrMaximumReached) { 99 | return nil 100 | } 101 | } 102 | 103 | return 104 | }, 105 | } 106 | 107 | flags := cmd.Flags() 108 | 109 | flags.StringVarP(&groupFlag, "group", "G", "", "Consumer group") 110 | flags.StringSliceVarP(&topicsFlag, "topic", "t", []string{}, "Topic to consume from") 111 | flags.IntVarP(&countFlag, "count", "c", 0, "Exit after consuming this number of messages") 112 | flags.BoolVar(&noCommit, "no-commit", false, "Consume messages without commiting offset") 113 | flags.StringVarP(&offset, "offset", "o", "", "Start consuming from this offset (default: newest)") 114 | 115 | _ = cmd.MarkFlagRequired("group") 116 | _ = cmd.MarkFlagRequired("topic") 117 | 118 | return cmd 119 | } 120 | 121 | func parseOffsetFlag(offsetsFlag string) (offset int64, err error) { 122 | if offsetsFlag == "" { 123 | return -1, ErrOffsetNotSet 124 | } 125 | intOffst, err := strconv.ParseInt(offsetsFlag, 10, 64) 126 | if err != nil { 127 | return -1, fmt.Errorf("error with offset '%s': %w", offsetsFlag, ErrInvalidOffset) 128 | } 129 | if intOffst < 0 { 130 | return -1, fmt.Errorf("error negative offset '%s': %w", offsetsFlag, ErrInvalidOffset) 131 | } 132 | return intOffst, nil 133 | } 134 | 135 | type protoHandler struct { 136 | desc *desc.MessageDescriptor 137 | MaxCount, counter int 138 | topic string 139 | partition int32 140 | offset int64 141 | } 142 | 143 | var once sync.Once 144 | 145 | func (p protoHandler) Setup(sess sarama.ConsumerGroupSession) error { 146 | once.Do(func() { 147 | if partFromFlags := flags.Partition; partFromFlags > 0 { 148 | p.partition = partFromFlags 149 | } 150 | 151 | if p.offset >= 0 { 152 | sess.ResetOffset(p.topic, p.partition, p.offset, "") 153 | } 154 | }) 155 | return nil 156 | } 157 | 158 | func (protoHandler) Cleanup(_ sarama.ConsumerGroupSession) error { return nil } 159 | 160 | // ErrMaximumReached error if limit reached 161 | var ErrMaximumReached = errors.New("maximum message reached") 162 | 163 | func (h *protoHandler) ConsumeClaim(sess sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error { 164 | f := dynamic.NewMessageFactoryWithDefaults() 165 | 166 | for msg := range claim.Messages() { 167 | // skip other partitions 168 | if p := flags.Partition; p >= 0 && p != msg.Partition { 169 | continue 170 | } 171 | 172 | m := f.NewDynamicMessage(h.desc) 173 | 174 | if err := m.Unmarshal(msg.Value); err != nil { 175 | log.Errorf("Unmarshal message error: %s", err) 176 | } else { 177 | dump.DynamicMessage(log, "Message consumed", viper.GetString("output"), m) 178 | } 179 | dumpConsumerMessage(msg) 180 | 181 | sess.MarkMessage(msg, "") 182 | 183 | h.counter++ 184 | log.Debugf("Message consuming count: %d", h.counter) 185 | 186 | if h.maximumReached() { 187 | return ErrMaximumReached 188 | } 189 | } 190 | 191 | return nil 192 | } 193 | 194 | func (h protoHandler) maximumReached() bool { 195 | if h.MaxCount != 0 { 196 | return h.counter == h.MaxCount 197 | } 198 | 199 | return false 200 | } 201 | 202 | func dumpConsumerMessage(msg *sarama.ConsumerMessage) { 203 | headers := kafka.NewRecordHeadersFromPointers(msg.Headers) 204 | 205 | pairs := dump.Pairs{ 206 | {Name: "timestamp", Value: msg.Timestamp}, 207 | {Name: "topic", Value: msg.Topic}, 208 | {Name: "partition", Value: msg.Partition}, 209 | {Name: "offset", Value: msg.Offset}, 210 | {Name: "key", Value: string(msg.Key)}, 211 | {Name: "length", Value: len(msg.Value)}, 212 | {Name: "headers", Value: headers.String()}, 213 | {Name: "value", Value: msg.Value}, 214 | } 215 | pairs.Dump(log) 216 | } 217 | -------------------------------------------------------------------------------- /cmd/cmd_consume_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func Test_NewConsumeCmd_NoTopicFlags(t *testing.T) { 10 | cmd := NewConsumeCmd() 11 | cmd.SetArgs([]string{"HelloRequest"}) 12 | 13 | _, _, err := getCommandOut(t, cmd) 14 | 15 | require.Contains(t, err.Error(), `required flag(s) "group", "topic" not set`) 16 | } 17 | 18 | func Test_parseOffsetsFlag(t *testing.T) { 19 | t.Run("offset happy case", func(t *testing.T) { 20 | offset, err := parseOffsetFlag("1") 21 | require.Nil(t, err) 22 | require.EqualValues(t, 1, offset) 23 | }) 24 | 25 | t.Run("error when offset is not a number", func(t *testing.T) { 26 | v, err := parseOffsetFlag("fdgdfg") 27 | require.ErrorIs(t, err, ErrInvalidOffset) 28 | require.EqualValues(t, -1, v) 29 | }) 30 | 31 | t.Run("error empty offset", func(t *testing.T) { 32 | v, err := parseOffsetFlag("") 33 | require.ErrorIs(t, err, ErrOffsetNotSet) 34 | require.EqualValues(t, -1, v) 35 | }) 36 | 37 | t.Run("error negative offset", func(t *testing.T) { 38 | v, err := parseOffsetFlag("-1") 39 | require.ErrorIs(t, err, ErrInvalidOffset) 40 | require.EqualValues(t, -1, v) 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /cmd/cmd_list.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/Shopify/sarama" 7 | "github.com/spf13/cobra" 8 | "github.com/spf13/viper" 9 | ) 10 | 11 | func NewListCmd() *cobra.Command { 12 | var ( 13 | topicsFlag []string 14 | ) 15 | 16 | cmd := &cobra.Command{ 17 | Use: "list ", 18 | Short: "Metadata listing", 19 | RunE: func(cmd *cobra.Command, args []string) (err error) { 20 | // client 21 | client, err := sarama.NewClient(viper.GetStringSlice("broker"), kafkaConfig) 22 | if err != nil { 23 | return 24 | } 25 | defer client.Close() 26 | 27 | // get topics 28 | topics, err := client.Topics() 29 | if err != nil { 30 | return fmt.Errorf("kafka client got error: %s", err) 31 | } 32 | 33 | brokers := client.Brokers() 34 | 35 | // find connected broker 36 | var broker *sarama.Broker 37 | for _, b := range brokers { 38 | if err := b.Open(kafkaConfig); err == nil { 39 | broker = b 40 | break 41 | } 42 | } 43 | if broker == nil { 44 | return fmt.Errorf("failed to connect to any of the given brokers (%v) for metadata request", brokers) 45 | } 46 | 47 | // get metadata from broker 48 | metadata, err := broker.GetMetadata(&sarama.MetadataRequest{ 49 | Topics: topics, 50 | }) 51 | if err != nil { 52 | return fmt.Errorf("get metadata got error: %s", err) 53 | } 54 | 55 | // output 56 | log.Infof("%d brokers:", len(metadata.Brokers)) 57 | for _, b := range metadata.Brokers { 58 | log.Infof(` broker %d "%s"`, b.ID(), b.Addr()) 59 | } 60 | 61 | list := filteredTopics(metadata.Topics, topicsFlag) 62 | 63 | log.Infof("%d topics:", len(list)) 64 | for _, t := range list { 65 | partitions := t.Partitions 66 | 67 | log.Infof(` topic "%s", partitions: %d`, t.Name, len(partitions)) 68 | for _, p := range partitions { 69 | log.Infof( 70 | ` partition %d, leader %d, replicas: %d (offline: %d), isrs: %d`, 71 | p.ID, p.Leader, p.Replicas, p.OfflineReplicas, p.Isr, 72 | ) 73 | } 74 | } 75 | 76 | return 77 | }, 78 | } 79 | 80 | flags := cmd.Flags() 81 | 82 | flags.StringSliceVarP(&topicsFlag, "topic", "t", []string{}, "Topic(s) to query (optional)") 83 | 84 | return cmd 85 | } 86 | 87 | func filteredTopics(mdTopics []*sarama.TopicMetadata, topics []string) (result []*sarama.TopicMetadata) { 88 | if len(topics) == 0 { 89 | return mdTopics 90 | } 91 | 92 | for _, mt := range mdTopics { 93 | for _, lt := range topics { 94 | if mt.Name == lt { 95 | result = append(result, mt) 96 | break 97 | } 98 | } 99 | } 100 | 101 | return 102 | } 103 | -------------------------------------------------------------------------------- /cmd/cmd_produce.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "context" 7 | "io" 8 | "os" 9 | "strings" 10 | "sync/atomic" 11 | "text/template" 12 | "time" 13 | 14 | "github.com/Shopify/sarama" 15 | "github.com/jhump/protoreflect/desc" 16 | "github.com/kuper-tech/protokaf/internal/calldata" 17 | "github.com/kuper-tech/protokaf/internal/kafka" 18 | "github.com/kuper-tech/protokaf/internal/proto" 19 | "github.com/kuper-tech/protokaf/internal/tracing" 20 | "github.com/kuper-tech/protokaf/internal/utils/dump" 21 | "github.com/opentracing/opentracing-go" 22 | "github.com/opentracing/opentracing-go/ext" 23 | "github.com/spf13/cobra" 24 | "github.com/spf13/viper" 25 | jaegerConfig "github.com/uber/jaeger-client-go/config" 26 | jaegerZapLog "github.com/uber/jaeger-client-go/log/zap" 27 | ) 28 | 29 | func NewProduceCmd() *cobra.Command { //nolint:funlen,gocognit 30 | var ( 31 | keyFlag string 32 | dataFlag string 33 | topicFlag string 34 | timeoutStr string 35 | timeoutFlag time.Duration 36 | traceFlag bool 37 | printJaegerConfig bool 38 | printTemplateFunctions bool 39 | countFlag int 40 | concurrencyFlag int 41 | seedFlag int64 42 | headers []string 43 | ) 44 | 45 | printInfo := func() bool { 46 | return printTemplateFunctions || printJaegerConfig 47 | } 48 | 49 | cmd := &cobra.Command{ 50 | Use: "produce ", 51 | Short: "Produce mode", 52 | Args: func(cmd *cobra.Command, args []string) (err error) { 53 | if printInfo() { 54 | return 55 | } 56 | 57 | return messageNameRequired(cmd, args) 58 | }, 59 | PreRunE: func(cmd *cobra.Command, args []string) (err error) { 60 | if printInfo() { 61 | return 62 | } 63 | 64 | err = cmd.MarkFlagRequired("topic") 65 | if err != nil { 66 | return 67 | } 68 | 69 | if timeoutStr != "" { 70 | timeoutFlag, err = time.ParseDuration(timeoutStr) 71 | if err != nil { 72 | return 73 | } 74 | } 75 | 76 | if countFlag < 1 { 77 | countFlag = 1 78 | } 79 | 80 | if concurrencyFlag < 1 { 81 | concurrencyFlag = 1 82 | } 83 | 84 | return 85 | }, 86 | RunE: func(cmd *cobra.Command, args []string) (err error) { 87 | if printTemplateFunctions { 88 | calldata.PrintFuncs(cmd.OutOrStdout()) 89 | return 90 | } 91 | 92 | // create tracer config 93 | jaegerCfg, jaegerCfgErr := tracing.NewJaegerConfig() 94 | if printJaegerConfig { 95 | dump.PrintStruct(log, "Jaeger config", jaegerCfg) 96 | return 97 | } 98 | 99 | // create tracer 100 | if traceFlag { 101 | if jaegerCfgErr != nil { 102 | return jaegerCfgErr 103 | } 104 | 105 | jaegerLogger := zapLog.Named("jaeger") 106 | tracer, closer, err := jaegerCfg.NewTracer( 107 | jaegerConfig.Logger(jaegerZapLog.NewLogger(jaegerLogger)), 108 | ) 109 | if err != nil { 110 | return err 111 | } 112 | 113 | opentracing.SetGlobalTracer(tracer) 114 | log.Debug("Create new tracer") 115 | defer closer.Close() 116 | } 117 | 118 | // parse protofiles & create proto object 119 | p, err := parseProtofiles() 120 | if err != nil { 121 | return 122 | } 123 | 124 | // find message descriptor 125 | md, err := findMessage(p, args[0]) 126 | if err != nil { 127 | return 128 | } 129 | 130 | // read data form stdin or -d flag 131 | data, err := readData(dataFlag) 132 | if err != nil { 133 | return 134 | } 135 | 136 | // set seed for random data 137 | calldata.SetSeeder(seedFlag) 138 | 139 | if countFlag > 1 { 140 | log.Infof("Producing %d messages...", countFlag) 141 | } 142 | 143 | // partition num defined by message field 144 | if p := flags.Partition; p != -1 { 145 | kafkaConfig.Producer.Partitioner = func(topic string) sarama.Partitioner { 146 | return constPartitioner{p} 147 | } 148 | } 149 | 150 | // create producer 151 | producer, err := kafka.NewProducer(viper.GetStringSlice("broker"), kafkaConfig) 152 | if err != nil { 153 | return err 154 | } 155 | defer producer.Close() 156 | 157 | // parse template for data 158 | tmpl, err := calldata.ParseTemplate(data) 159 | if err != nil { 160 | return 161 | } 162 | 163 | // send messages 164 | execCtx, cancel := context.WithCancel(cmd.Context()) 165 | defer cancel() 166 | 167 | workers := newProduceWorker(concurrencyFlag) 168 | workers.Run(execCtx, countFlag) 169 | 170 | go func() { 171 | for i := 0; i < countFlag; i++ { 172 | workers.AddJob(&produceMessage{ 173 | reqNum: i, 174 | key: keyFlag, 175 | topic: topicFlag, 176 | data: data, 177 | headers: headers, 178 | sendTimeout: timeoutFlag, 179 | producer: producer, 180 | traceEnabled: traceFlag, 181 | tracer: opentracing.GlobalTracer(), 182 | tmpl: tmpl, 183 | messageDesc: md, 184 | }) 185 | } 186 | }() 187 | 188 | return workers.Result() 189 | }, 190 | } 191 | 192 | flags := cmd.Flags() 193 | 194 | flags.StringVarP(&keyFlag, "key", "k", "", "Message key") 195 | flags.StringVarP(&dataFlag, "data", "d", "", "Message data") 196 | flags.StringVarP(&topicFlag, "topic", "t", "", "Topic to produce to") 197 | flags.StringArrayVarP(&headers, "header", "H", []string{}, "Add message headers (may be specified multiple times)") 198 | flags.StringVar(&timeoutStr, "timeout", "60s", "Operation timeout") 199 | flags.BoolVar(&traceFlag, "trace", false, "Send OpenTracing spans to Jaeger") 200 | flags.BoolVar(&printJaegerConfig, "jaeger-config-print", false, "Print Jaeger config") 201 | flags.IntVarP(&countFlag, "count", "c", 1, "Producing this number of messages") 202 | flags.IntVar(&concurrencyFlag, "concurrency", 1, "Number of message senders to run concurrently for const concurrency producing") 203 | flags.Int64Var(&seedFlag, "seed", 0, "Set seed for pseudo-random sequence") 204 | flags.BoolVar(&printTemplateFunctions, "template-functions-print", false, "Print functions for using in template") 205 | 206 | tracing.SetJaegerFlags(flags) 207 | 208 | return cmd 209 | } 210 | 211 | func makeProduceHeaders(headers []string) []sarama.RecordHeader { 212 | sep := []byte{'='} 213 | hdrs := make([]sarama.RecordHeader, 0, len(headers)) 214 | for _, h := range headers { 215 | parts := bytes.SplitN([]byte(h), sep, 2) 216 | if len(parts) != 2 { 217 | log.Warnf("Invalid headers pair: %s", h) 218 | continue 219 | } 220 | 221 | hdrs = append(hdrs, sarama.RecordHeader{ 222 | Key: parts[0], 223 | Value: parts[1], 224 | }) 225 | } 226 | 227 | return hdrs 228 | } 229 | 230 | func getProducedMessageData(msg *sarama.ProducerMessage) dump.Pairs { 231 | var keyBytes []byte 232 | if msg.Key != nil { 233 | keyBytes, _ = msg.Key.Encode() 234 | } 235 | 236 | var valuesBytes []byte 237 | valuesBytes, _ = msg.Value.Encode() 238 | 239 | headers := kafka.RecordHeaders(msg.Headers) 240 | 241 | return dump.Pairs{ 242 | {Name: "topic", Value: msg.Topic}, 243 | {Name: "partition", Value: msg.Partition}, 244 | {Name: "offset", Value: msg.Offset}, 245 | {Name: "key", Value: string(keyBytes)}, 246 | {Name: "length", Value: msg.Value.Length()}, 247 | {Name: "headers", Value: headers.String()}, 248 | {Name: "metadata", Value: msg.Metadata}, 249 | {Name: "value", Value: valuesBytes}, 250 | } 251 | } 252 | 253 | func readData(dataFlag string) ([]byte, error) { 254 | if dataFlag != "" { 255 | log.Debugf("Read data from --data value: %s", dataFlag) 256 | 257 | return []byte(dataFlag), nil 258 | } 259 | 260 | // read from stdin 261 | log.Debug("Reading data from stdin...") 262 | 263 | reader := bufio.NewReader(os.Stdin) 264 | data, err := io.ReadAll(reader) 265 | if err != nil { 266 | return nil, err 267 | } 268 | 269 | log.Debugf("Read data from stdin: %s", strings.TrimSpace(string(data))) 270 | 271 | return data, nil 272 | } 273 | 274 | type produceMessage struct { 275 | reqNum int 276 | key string 277 | topic string 278 | data []byte 279 | headers []string 280 | sendTimeout time.Duration 281 | producer *kafka.Producer 282 | traceEnabled bool 283 | tracer opentracing.Tracer 284 | tmpl *template.Template 285 | messageDesc *desc.MessageDescriptor 286 | } 287 | 288 | func (p *produceMessage) Send(parentCtx context.Context) error { 289 | cd := calldata.NewCallData(p.reqNum) 290 | b, err := cd.Execute(p.tmpl) 291 | if err != nil { 292 | return err 293 | } 294 | 295 | // parse data and create message 296 | m, err := proto.Unmarshal(b.Bytes(), p.messageDesc) 297 | if err != nil { 298 | return err 299 | } 300 | log.Debugf("Prepared protobuf message: %v", m) 301 | 302 | // message to send 303 | msg := &sarama.ProducerMessage{ 304 | Topic: p.topic, 305 | Key: sarama.StringEncoder(p.key), 306 | Value: proto.Encoder(m), 307 | Headers: makeProduceHeaders(p.headers), 308 | } 309 | 310 | ctx, cancel := context.WithTimeout(parentCtx, p.sendTimeout) 311 | defer cancel() 312 | 313 | var span opentracing.Span 314 | 315 | if p.traceEnabled { 316 | span, err = tracing.CreateSpan(p.tracer, msg) 317 | if err != nil { 318 | return err 319 | } 320 | 321 | log.Debugf("Create new span: %v", span) 322 | defer span.Finish() 323 | } 324 | 325 | if err := p.producer.SendMessage(ctx, msg); err != nil { 326 | if p.traceEnabled { 327 | ext.LogError(span, err) 328 | } 329 | 330 | return err 331 | } 332 | 333 | dump.DynamicMessage(log, "Message produced", viper.GetString("output"), m) 334 | getProducedMessageData(msg).Dump(log) 335 | 336 | return nil 337 | } 338 | 339 | type produceWorker struct { 340 | executed int64 341 | concurrency int 342 | jobs chan *produceMessage 343 | result chan error 344 | } 345 | 346 | func newProduceWorker(concurrency int) *produceWorker { 347 | return &produceWorker{ 348 | concurrency: concurrency, 349 | jobs: make(chan *produceMessage, concurrency), 350 | result: make(chan error, 1), 351 | } 352 | } 353 | 354 | func (p *produceWorker) AddJob(pm *produceMessage) { 355 | p.jobs <- pm 356 | } 357 | 358 | func (p *produceWorker) Result() error { 359 | return <-p.result 360 | } 361 | 362 | func (p *produceWorker) Run(parentCtx context.Context, count int) { 363 | ctx, cancel := context.WithCancel(parentCtx) 364 | 365 | done := func(err error) { 366 | cancel() 367 | p.result <- err 368 | } 369 | 370 | for i := 0; i < p.concurrency; i++ { 371 | go func() { 372 | for { 373 | select { 374 | case pm := <-p.jobs: 375 | if err := pm.Send(ctx); err != nil { 376 | done(err) 377 | return 378 | } 379 | 380 | if int(atomic.AddInt64(&p.executed, 1)) == count { 381 | done(nil) 382 | return 383 | } 384 | 385 | case <-ctx.Done(): 386 | done(ctx.Err()) 387 | return 388 | } 389 | } 390 | }() 391 | } 392 | } 393 | 394 | type constPartitioner struct { 395 | partition int32 396 | } 397 | 398 | func (p constPartitioner) Partition(_ *sarama.ProducerMessage, _ int32) (int32, error) { 399 | return p.partition, nil 400 | } 401 | 402 | func (p constPartitioner) RequiresConsistency() bool { 403 | return true 404 | } 405 | -------------------------------------------------------------------------------- /cmd/cmd_produce_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func Test_NewProduceCmd_NoTopicFlags(t *testing.T) { 10 | cmd := NewProduceCmd() 11 | cmd.SetArgs([]string{"HelloRequest"}) 12 | 13 | _, _, err := getCommandOut(t, cmd) 14 | 15 | assert.Contains(t, err.Error(), `required flag(s) "topic" not set`) 16 | } 17 | 18 | func Test_NewProduceCmd_TemplateFunctionsPrint(t *testing.T) { 19 | cmd := NewProduceCmd() 20 | cmd.SetArgs([]string{"--template-functions-print"}) 21 | 22 | stdout, stderr, err := getCommandOut(t, cmd) 23 | 24 | assert.Nil(t, err) 25 | assert.Empty(t, stderr) 26 | 27 | assert.Contains(t, stdout, `Template functions:`) 28 | assert.Contains(t, stdout, `randomString`) 29 | } 30 | 31 | func Test_NewProduceCmd_JaegerConfigPrint(t *testing.T) { 32 | cmd := NewProduceCmd() 33 | cmd.SetArgs([]string{"--jaeger-config-print"}) 34 | 35 | stdout, stderr, err := getCommandOut(t, cmd) 36 | 37 | assert.Nil(t, err) 38 | assert.Empty(t, stderr) 39 | 40 | assert.Contains(t, stdout, `Jaeger config`) 41 | assert.Contains(t, stdout, `"LocalAgentHostPort": "0.0.0.0:6831"`) 42 | } 43 | -------------------------------------------------------------------------------- /cmd/cmd_root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/Shopify/sarama" 8 | "github.com/kuper-tech/protokaf/internal/kafka" 9 | "github.com/spf13/cobra" 10 | "github.com/spf13/viper" 11 | "go.uber.org/zap" 12 | ) 13 | 14 | var ( 15 | flags *Flags 16 | kafkaConfig *sarama.Config 17 | ) 18 | 19 | func Execute() { 20 | defer func(err error) { 21 | if err != nil { 22 | log.Errorf("Error: %s", err) 23 | } 24 | _ = log.Sync() 25 | 26 | if err != nil { 27 | os.Exit(1) 28 | } 29 | }(NewRootCmd().Execute()) 30 | } 31 | 32 | func init() { 33 | cobra.EnableCommandSorting = false 34 | 35 | setLogger(os.Stdout, "info", appName) 36 | } 37 | 38 | func NewRootCmd() *cobra.Command { 39 | cmd := &cobra.Command{ 40 | Use: appName, 41 | Version: "1.0.0", 42 | Short: fmt.Sprintf("%s - Kafka producer and consumer tool in protobuf format", appName), 43 | PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) { 44 | configFiles, err := initConfig() 45 | if err != nil { 46 | if _, ok := err.(viper.ConfigFileNotFoundError); !ok { 47 | return 48 | } 49 | } 50 | 51 | err = flags.Prepare() 52 | if err != nil { 53 | return 54 | } 55 | 56 | if viper.GetBool("debug") { 57 | setLogger(cmd.OutOrStdout(), "debug", cmd.CalledAs()) 58 | log.Info("Debugging enabled") 59 | 60 | sarama.Logger = zap.NewStdLog(zapLog.Named("kafka")) 61 | } 62 | 63 | if configFiles != "" { 64 | log.Debugf("Using config file: %s", configFiles) 65 | } 66 | 67 | kafkaConfig, err = kafka.NewConfig(appName, viper.GetString("kafka-auth-dsn")) 68 | if err != nil { 69 | return 70 | } 71 | 72 | return nil 73 | }, 74 | CompletionOptions: cobra.CompletionOptions{ 75 | DisableDefaultCmd: true, 76 | }, 77 | SilenceUsage: true, 78 | SilenceErrors: true, 79 | } 80 | 81 | flags = NewFlags(cmd) 82 | flags.Init() 83 | 84 | cmd.AddCommand( 85 | NewProduceCmd(), 86 | NewConsumeCmd(), 87 | NewListCmd(), 88 | NewBuildCmd(), 89 | ) 90 | 91 | return cmd 92 | } 93 | -------------------------------------------------------------------------------- /cmd/cmd_root_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func Test_NewProduceCmd(t *testing.T) { 13 | cmd := NewRootCmd() 14 | cmd.SetArgs([]string{ 15 | "produce", 16 | "HelloRequest", 17 | "--topic", "Test_NewProduceCmd", 18 | "--proto", "../internal/proto/testdata/example.proto", 19 | "--timeout", "5s", 20 | "-d", `{"name": "Alice", "age": 11}`, 21 | }) 22 | 23 | stdout, stderr, err := getCommandOut(t, cmd) 24 | 25 | assert.Nil(t, err) 26 | assert.Contains(t, stdout, "Message produced") 27 | assert.Empty(t, stderr) 28 | } 29 | 30 | func Test_NewProduceCmd_WithTrace(t *testing.T) { 31 | cmd := NewRootCmd() 32 | cmd.SetArgs([]string{ 33 | "produce", 34 | "HelloRequest", 35 | "--topic", "Test_NewProduceCmd", 36 | "--proto", "../internal/proto/testdata/example.proto", 37 | "--timeout", "5s", 38 | "--trace", 39 | "-d", `{"name": "Alice", "age": 11}`, 40 | }) 41 | 42 | stdout, stderr, err := getCommandOut(t, cmd) 43 | 44 | assert.Nil(t, err) 45 | assert.Contains(t, stdout, "Message produced") 46 | assert.Empty(t, stderr) 47 | } 48 | 49 | func Test_NewProduceCmd_Random(t *testing.T) { 50 | cmd := NewRootCmd() 51 | cmd.SetArgs([]string{ 52 | "produce", 53 | "HelloRequest", 54 | "--topic", "Test_NewProduceCmd", 55 | "--proto", "../internal/proto/testdata/example.proto", 56 | "--timeout", "5s", 57 | "--seed", "1", 58 | "--count", "3", 59 | "-d", `{"name": {{randomString 5 | quote}}, "age": {{randomNumber 1 10}}}`, 60 | }) 61 | 62 | stdout, stderr, err := getCommandOut(t, cmd) 63 | 64 | assert.Nil(t, err) 65 | assert.Contains(t, stdout, "Producing 3 messages...") 66 | assert.Contains(t, stdout, "Prepared protobuf message: name:\"BpLnf\" age:7") 67 | assert.Empty(t, stderr) 68 | } 69 | 70 | func Test_NewConsumeCmd(t *testing.T) { 71 | cmd := NewRootCmd() 72 | cmd.SetArgs([]string{ 73 | "consume", 74 | "HelloRequest", 75 | "--group", "test", 76 | "--topic", "Test_NewConsumeCmd", 77 | "--proto", "../internal/proto/testdata/example.proto", 78 | "--count", "1", 79 | }) 80 | 81 | pr, pw := io.Pipe() 82 | cmd.SetOut(pw) 83 | 84 | setLogger(nopSync{pw}, "debug", "") 85 | go cmd.Execute() //nolint:errcheck 86 | 87 | r := bufio.NewReader(pr) 88 | t.Run("wait for consume", func(t *testing.T) { 89 | for { 90 | line, _, err := r.ReadLine() 91 | if err != nil { 92 | t.Fatal(err) 93 | } 94 | 95 | if strings.Contains(string(line), "Consume topics") { 96 | break 97 | } 98 | } 99 | }) 100 | } 101 | 102 | func Test_NewListCmd(t *testing.T) { 103 | cmd := NewRootCmd() 104 | cmd.SetArgs([]string{ 105 | "list", 106 | }) 107 | 108 | stdout, _, err := getCommandOut(t, cmd) 109 | 110 | assert.Nil(t, err) 111 | assert.Contains(t, stdout, "broker 1 \"127.0.0.1:9092\"") 112 | assert.Contains(t, stdout, "topic \"Test_NewProduceCmd\", partitions: 1") 113 | } 114 | -------------------------------------------------------------------------------- /cmd/common.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/jhump/protoreflect/desc" 8 | "github.com/kuper-tech/protokaf/internal/proto" 9 | "github.com/spf13/cobra" 10 | "github.com/spf13/viper" 11 | ) 12 | 13 | const ( 14 | appName = "protokaf" 15 | ) 16 | 17 | func parseProtofiles() (*proto.Proto, error) { 18 | files := viper.GetStringSlice("proto") 19 | p, err := proto.NewProto(files) 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | log.Debugf(`Proto import paths: "%s"`, strings.Join(p.ImportPaths, ":")) 25 | 26 | if len(files) > 0 { 27 | log.Debugf("Parsed files: %s", strings.Join(files, ", ")) 28 | } 29 | 30 | return p, nil 31 | } 32 | 33 | func findMessage(p *proto.Proto, name string) (*desc.MessageDescriptor, error) { 34 | m, err := p.FindMessage(name) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | log.Debugf("Using proto message: %s", m.GetFullyQualifiedName()) 40 | 41 | return m, nil 42 | } 43 | 44 | func messageNameRequired(cmd *cobra.Command, args []string) error { 45 | if len(args) < 1 { 46 | return fmt.Errorf("required argument not specified") 47 | } 48 | 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /cmd/config.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/spf13/cobra" 7 | "github.com/spf13/viper" 8 | ) 9 | 10 | const ( 11 | defaultConfigName = "." + appName 12 | defaultConfigType = "yaml" 13 | ) 14 | 15 | // initConfig reads in config file and ENV variables if set. 16 | func initConfig() (string, error) { 17 | if flags.Config != "" { 18 | // Use config file from the flag. 19 | viper.SetConfigFile(flags.Config) 20 | } else { 21 | // Find home directory. 22 | home, err := os.UserHomeDir() 23 | cobra.CheckErr(err) 24 | 25 | // Search config in home directory. 26 | viper.AddConfigPath(".") 27 | viper.AddConfigPath(home) 28 | viper.SetConfigType(defaultConfigType) 29 | viper.SetConfigName(defaultConfigName) 30 | } 31 | 32 | viper.AutomaticEnv() // read in environment variables that match 33 | 34 | // If a config file is found, read it in. 35 | err := viper.ReadInConfig() 36 | 37 | return viper.ConfigFileUsed(), err 38 | } 39 | -------------------------------------------------------------------------------- /cmd/flags.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/kuper-tech/protokaf/internal/kafka" 8 | "github.com/spf13/cobra" 9 | "github.com/spf13/viper" 10 | ) 11 | 12 | const ( 13 | // DecodeFlagTextValue is a value of output in text format. 14 | DecodeFlagTextValue = "text" 15 | 16 | // DecodeFlagJSONValue is a value of output in json format. 17 | DecodeFlagJSONValue = "json" 18 | ) 19 | 20 | var decodeFlagValidValues = []string{ 21 | DecodeFlagTextValue, 22 | DecodeFlagJSONValue, 23 | } 24 | 25 | type Flags struct { 26 | Config string 27 | Partition int32 28 | 29 | proto []string 30 | broker []string 31 | kafkaAuthDSN string 32 | debug bool 33 | output string 34 | 35 | parent *cobra.Command 36 | } 37 | 38 | func NewFlags(parent *cobra.Command) *Flags { 39 | return &Flags{parent: parent} 40 | } 41 | 42 | func (f *Flags) Init() { 43 | pf := f.parent.PersistentFlags() 44 | 45 | // app flags 46 | pf.BoolVar(&f.debug, "debug", false, "Enable debugging") 47 | 48 | // kafka 49 | pf.StringSliceVarP(&f.broker, "broker", "b", []string{"0.0.0.0:9092"}, "Bootstrap broker(s) (host[:port],...)") 50 | pf.Int32VarP(&f.Partition, "partition", "p", -1, "Partition number") 51 | pf.StringVarP(&f.kafkaAuthDSN, "kafka-auth-dsn", "X", "", fmt.Sprintf("Kafka auth DSN (%s)", kafka.AuthDSNTemplate)) 52 | 53 | // proto 54 | pf.StringSliceVarP(&f.proto, "proto", "f", []string{}, "Proto files ({file | pattern | url},...)") 55 | pf.StringVar(&f.output, "output", "json", fmt.Sprintf("Output type: %s", strings.Join(decodeFlagValidValues, ", "))) 56 | 57 | // config 58 | pf.StringVarP(&f.Config, "config", "F", "", "Config file (default is $HOME/.protokaf.yaml)") 59 | 60 | for _, name := range []string{ 61 | "proto", 62 | "debug", 63 | "broker", 64 | "output", 65 | "kafka-auth-dsn", 66 | } { 67 | _ = viper.BindPFlag(name, pf.Lookup(name)) 68 | } 69 | } 70 | 71 | func (f *Flags) Prepare() (err error) { 72 | found := false 73 | for _, v := range decodeFlagValidValues { 74 | if v == f.output { 75 | found = true 76 | break 77 | } 78 | } 79 | 80 | if !found { 81 | return fmt.Errorf( 82 | "decode flag has invalid value: %s, use one of %s", 83 | f.output, 84 | strings.Join(decodeFlagValidValues, ", "), 85 | ) 86 | } 87 | 88 | return 89 | } 90 | -------------------------------------------------------------------------------- /cmd/logger.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "io" 5 | 6 | "go.uber.org/zap" 7 | "go.uber.org/zap/zapcore" 8 | ) 9 | 10 | var ( 11 | log *zap.SugaredLogger 12 | zapLog *zap.Logger 13 | logLevel zap.AtomicLevel 14 | ) 15 | 16 | const ( 17 | logInfoLevel = "info" 18 | logDebugLevel = "debug" 19 | ) 20 | 21 | type nopSync struct { 22 | io.Writer 23 | } 24 | 25 | func (nopSync) Sync() error { return nil } 26 | 27 | func getLogger(stdout zapcore.WriteSyncer, levelName string) *zap.Logger { 28 | logLevel = zap.NewAtomicLevel() 29 | logLevel.SetLevel(zap.InfoLevel) 30 | 31 | encoderCfg := zap.NewProductionEncoderConfig() 32 | encoderCfg.LevelKey = "" 33 | encoderCfg.TimeKey = "" 34 | 35 | switch levelName { 36 | case logInfoLevel: 37 | case logDebugLevel: 38 | encoderCfg.TimeKey = "timestamp" 39 | encoderCfg.EncodeTime = zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05") 40 | 41 | logLevel.SetLevel(zap.DebugLevel) 42 | } 43 | 44 | zapLog := zap.New(zapcore.NewCore( 45 | zapcore.NewConsoleEncoder(encoderCfg), 46 | zapcore.Lock(stdout), 47 | logLevel, 48 | )) 49 | 50 | return zapLog 51 | } 52 | 53 | func getStdLogger(out io.Writer, levelName string) *zap.Logger { 54 | var x zapcore.WriteSyncer 55 | 56 | x, ok := out.(zapcore.WriteSyncer) 57 | if !ok { 58 | x = nopSync{out} 59 | } 60 | 61 | return getLogger(x, levelName) 62 | } 63 | 64 | func setLogger(out io.Writer, levelName string, named string) { 65 | zapLog = getStdLogger(out, levelName) 66 | log = zapLog.Sugar() 67 | 68 | if named != "" { 69 | log = log.Named(named) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /cmd/test_helpers.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | func getCommandOut(t *testing.T, cmd *cobra.Command) (stdout string, stderr string, errCmd error) { 11 | o := new(bytes.Buffer) 12 | e := new(bytes.Buffer) 13 | 14 | cmd.SetOut(o) 15 | cmd.SetErr(e) 16 | 17 | setLogger(nopSync{o}, "debug", "") 18 | errCmd = cmd.Execute() 19 | 20 | return o.String(), e.String(), errCmd 21 | } 22 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kuper-tech/protokaf 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/Pallinder/go-randomdata v1.2.0 7 | github.com/Shopify/sarama v1.29.1 8 | github.com/golang/protobuf v1.5.4 9 | github.com/google/uuid v1.3.0 10 | github.com/jhump/protoreflect v1.9.0 11 | github.com/opentracing/opentracing-go v1.2.0 12 | github.com/spf13/cobra v1.2.1 13 | github.com/spf13/pflag v1.0.5 14 | github.com/spf13/viper v1.8.1 15 | github.com/stretchr/testify v1.7.0 16 | github.com/uber/jaeger-client-go v2.29.1+incompatible 17 | github.com/xdg/scram v1.0.3 18 | go.uber.org/zap v1.18.1 19 | google.golang.org/protobuf v1.33.0 20 | ) 21 | 22 | require ( 23 | github.com/HdrHistogram/hdrhistogram-go v1.1.0 // indirect 24 | github.com/davecgh/go-spew v1.1.1 // indirect 25 | github.com/eapache/go-resiliency v1.2.0 // indirect 26 | github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 // indirect 27 | github.com/eapache/queue v1.1.0 // indirect 28 | github.com/fsnotify/fsnotify v1.4.9 // indirect 29 | github.com/golang/snappy v0.0.3 // indirect 30 | github.com/hashicorp/go-uuid v1.0.2 // indirect 31 | github.com/hashicorp/hcl v1.0.0 // indirect 32 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 33 | github.com/jcmturner/aescts/v2 v2.0.0 // indirect 34 | github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect 35 | github.com/jcmturner/gofork v1.0.0 // indirect 36 | github.com/jcmturner/gokrb5/v8 v8.4.2 // indirect 37 | github.com/jcmturner/rpc/v2 v2.0.3 // indirect 38 | github.com/klauspost/compress v1.12.2 // indirect 39 | github.com/magiconair/properties v1.8.5 // indirect 40 | github.com/mitchellh/mapstructure v1.4.1 // indirect 41 | github.com/pelletier/go-toml v1.9.3 // indirect 42 | github.com/pierrec/lz4 v2.6.0+incompatible // indirect 43 | github.com/pkg/errors v0.8.1 // indirect 44 | github.com/pmezard/go-difflib v1.0.0 // indirect 45 | github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect 46 | github.com/spf13/afero v1.6.0 // indirect 47 | github.com/spf13/cast v1.3.1 // indirect 48 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 49 | github.com/subosito/gotenv v1.2.0 // indirect 50 | github.com/uber/jaeger-lib v2.4.1+incompatible // indirect 51 | github.com/xdg/stringprep v1.0.3 // indirect 52 | go.uber.org/atomic v1.9.0 // indirect 53 | go.uber.org/multierr v1.6.0 // indirect 54 | golang.org/x/crypto v0.22.0 // indirect 55 | golang.org/x/net v0.24.0 // indirect 56 | golang.org/x/sys v0.19.0 // indirect 57 | golang.org/x/text v0.14.0 // indirect 58 | google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect 59 | gopkg.in/ini.v1 v1.62.0 // indirect 60 | gopkg.in/yaml.v2 v2.4.0 // indirect 61 | gopkg.in/yaml.v3 v3.0.1 // indirect 62 | ) 63 | -------------------------------------------------------------------------------- /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 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 8 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 9 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= 10 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 11 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= 12 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= 13 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= 14 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= 15 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= 16 | cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= 17 | cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= 18 | cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= 19 | cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= 20 | cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= 21 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 22 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 23 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= 24 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= 25 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= 26 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= 27 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 28 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= 29 | cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= 30 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 31 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 32 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= 33 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= 34 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 35 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 36 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= 37 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= 38 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= 39 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 40 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 41 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 42 | github.com/HdrHistogram/hdrhistogram-go v1.1.0 h1:6dpdDPTRoo78HxAJ6T1HfMiKSnqhgRRqzCuPshRkQ7I= 43 | github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= 44 | github.com/Pallinder/go-randomdata v1.2.0 h1:DZ41wBchNRb/0GfsePLiSwb0PHZmT67XY00lCDlaYPg= 45 | github.com/Pallinder/go-randomdata v1.2.0/go.mod h1:yHmJgulpD2Nfrm0cR9tI/+oAgRqCQQixsA8HyRZfV9Y= 46 | github.com/Shopify/sarama v1.29.1 h1:wBAacXbYVLmWieEA/0X/JagDdCZ8NVFOfS6l6+2u5S0= 47 | github.com/Shopify/sarama v1.29.1/go.mod h1:mdtqvCSg8JOxk8PmpTNGyo6wzd4BMm4QXSfDnTXmgkE= 48 | github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc= 49 | github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= 50 | github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= 51 | github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= 52 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= 53 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 54 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 55 | github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= 56 | github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 57 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 58 | github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= 59 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 60 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 61 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 62 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 63 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 64 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 65 | github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 66 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 67 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 68 | github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 69 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 70 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 71 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 72 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 73 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 74 | github.com/eapache/go-resiliency v1.2.0 h1:v7g92e/KSN71Rq7vSThKaWIq68fL4YHvWyiUKorFR1Q= 75 | github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= 76 | github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw= 77 | github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= 78 | github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= 79 | github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= 80 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 81 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 82 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 83 | github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= 84 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 85 | github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 86 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 87 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 88 | github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= 89 | github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= 90 | github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= 91 | github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY= 92 | github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= 93 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 94 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 95 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 96 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 97 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 98 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 99 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 100 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 101 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 102 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 103 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 104 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 105 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 106 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 107 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 108 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 109 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 110 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 111 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 112 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 113 | github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g= 114 | github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= 115 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 116 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 117 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 118 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 119 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 120 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 121 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 122 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 123 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 124 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 125 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 126 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 127 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 128 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 129 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 130 | github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= 131 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 132 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 133 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 134 | github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= 135 | github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 136 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 137 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 138 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 139 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 140 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 141 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 142 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 143 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 144 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 145 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 146 | github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 147 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 148 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 149 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 150 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 151 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 152 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 153 | github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 154 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 155 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 156 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 157 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 158 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 159 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 160 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 161 | github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 162 | github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 163 | github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 164 | github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 165 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 166 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 167 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 168 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 169 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 170 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 171 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= 172 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 173 | github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= 174 | github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= 175 | github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= 176 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= 177 | github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= 178 | github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= 179 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 180 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 181 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 182 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 183 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 184 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= 185 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 186 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= 187 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 188 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 189 | github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= 190 | github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 191 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= 192 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 193 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 194 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 195 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 196 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 197 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= 198 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= 199 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 200 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 201 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 202 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 203 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 204 | github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= 205 | github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= 206 | github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= 207 | github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= 208 | github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8= 209 | github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= 210 | github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= 211 | github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= 212 | github.com/jcmturner/gokrb5/v8 v8.4.2 h1:6ZIM6b/JJN0X8UM43ZOM6Z4SJzla+a/u7scXFJzodkA= 213 | github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc= 214 | github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= 215 | github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= 216 | github.com/jhump/protoreflect v1.9.0 h1:npqHz788dryJiR/l6K/RUQAyh2SwV91+d1dnh4RjO9w= 217 | github.com/jhump/protoreflect v1.9.0/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg= 218 | github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 219 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 220 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 221 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 222 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 223 | github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= 224 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 225 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 226 | github.com/klauspost/compress v1.12.2 h1:2KCfW3I9M7nSc5wOqXAlW2v2U6v+w6cbjvbfp+OykW8= 227 | github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= 228 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 229 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 230 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= 231 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 232 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 233 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 234 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 235 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 236 | github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= 237 | github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= 238 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 239 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 240 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 241 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 242 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 243 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 244 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= 245 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= 246 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 247 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 248 | github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= 249 | github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 250 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 251 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 252 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 253 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 254 | github.com/nishanths/predeclared v0.0.0-20200524104333-86fad755b4d3/go.mod h1:nt3d53pc1VYcphSCIaYAJtnPYnr3Zyn8fMq2wvPGPso= 255 | github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= 256 | github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= 257 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 258 | github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= 259 | github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= 260 | github.com/pierrec/lz4 v2.6.0+incompatible h1:Ix9yFKn1nSPBLFl/yZknTp8TU5G4Ps0JDmguYK6iH1A= 261 | github.com/pierrec/lz4 v2.6.0+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= 262 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 263 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 264 | github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= 265 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 266 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 267 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 268 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 269 | github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= 270 | github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= 271 | github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 272 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 273 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 274 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 275 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 276 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 277 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= 278 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 279 | github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= 280 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 281 | github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= 282 | github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= 283 | github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= 284 | github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 285 | github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw= 286 | github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= 287 | github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= 288 | github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= 289 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 290 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 291 | github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44= 292 | github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= 293 | github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= 294 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 295 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 296 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 297 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 298 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 299 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 300 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 301 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 302 | github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= 303 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= 304 | github.com/uber/jaeger-client-go v2.29.1+incompatible h1:R9ec3zO3sGpzs0abd43Y+fBZRJ9uiH6lXyR/+u6brW4= 305 | github.com/uber/jaeger-client-go v2.29.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= 306 | github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= 307 | github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= 308 | github.com/xdg/scram v1.0.3 h1:nTadYh2Fs4BK2xdldEa2g5bbaZp0/+1nJMMPtPxS/to= 309 | github.com/xdg/scram v1.0.3/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= 310 | github.com/xdg/stringprep v1.0.3 h1:cmL5Enob4W83ti/ZHuZLuKD/xqJfus4fVPwE+/BDm+4= 311 | github.com/xdg/stringprep v1.0.3/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= 312 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 313 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 314 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 315 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 316 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 317 | go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= 318 | go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= 319 | go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= 320 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 321 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 322 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 323 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 324 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 325 | go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= 326 | go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= 327 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 328 | go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= 329 | go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 330 | go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= 331 | go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= 332 | go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= 333 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 334 | go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= 335 | go.uber.org/zap v1.18.1 h1:CSUJ2mjFszzEWt4CdKISEuChVIXGBn3lAPwkRGyVrc4= 336 | go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= 337 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 338 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 339 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 340 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 341 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 342 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 343 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 344 | golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 345 | golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 346 | golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= 347 | golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= 348 | golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 349 | golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 350 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 351 | golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 352 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 353 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 354 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 355 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 356 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 357 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 358 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 359 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 360 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 361 | golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= 362 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 363 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 364 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 365 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 366 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 367 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 368 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 369 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 370 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 371 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 372 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 373 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 374 | golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 375 | golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= 376 | golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 377 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 378 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 379 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 380 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 381 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 382 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 383 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 384 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 385 | golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 386 | golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 387 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 388 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 389 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 390 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 391 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 392 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 393 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 394 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 395 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 396 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 397 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 398 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 399 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 400 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 401 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 402 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 403 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 404 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 405 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 406 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 407 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 408 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 409 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 410 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 411 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 412 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 413 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 414 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 415 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 416 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 417 | golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 418 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 419 | golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 420 | golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 421 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 422 | golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= 423 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 424 | golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 425 | golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= 426 | golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= 427 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 428 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 429 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 430 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 431 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 432 | golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 433 | golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 434 | golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 435 | golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 436 | golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 437 | golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 438 | golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 439 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 440 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 441 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 442 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 443 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 444 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 445 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 446 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 447 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 448 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 449 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 450 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 451 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 452 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 453 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 454 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 455 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 456 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 457 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 458 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 459 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 460 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 461 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 462 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 463 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 464 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 465 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 466 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 467 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 468 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 469 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 470 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 471 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 472 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 473 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 474 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 475 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 476 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 477 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 478 | golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 479 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 480 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 481 | golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 482 | golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 483 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 484 | golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 485 | golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 486 | golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 487 | golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 488 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 489 | golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 490 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 491 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 492 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 493 | golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= 494 | golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 495 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 496 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 497 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 498 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 499 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 500 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 501 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 502 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 503 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 504 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 505 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 506 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 507 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 508 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 509 | golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 510 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 511 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 512 | golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 513 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 514 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 515 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 516 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 517 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 518 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 519 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 520 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 521 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 522 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 523 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 524 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 525 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 526 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 527 | golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 528 | golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 529 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 530 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 531 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 532 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 533 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 534 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 535 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 536 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 537 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 538 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 539 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 540 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 541 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 542 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 543 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 544 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 545 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 546 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 547 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 548 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 549 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 550 | golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 551 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 552 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 553 | golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 554 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 555 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 556 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 557 | golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= 558 | golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 559 | golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 560 | golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 561 | golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 562 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 563 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 564 | golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 565 | golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= 566 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 567 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 568 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 569 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 570 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 571 | gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= 572 | gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= 573 | gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= 574 | gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= 575 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 576 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 577 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 578 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 579 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 580 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 581 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 582 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 583 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 584 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 585 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 586 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 587 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 588 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 589 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= 590 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 591 | google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= 592 | google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= 593 | google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= 594 | google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= 595 | google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= 596 | google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= 597 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 598 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 599 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 600 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 601 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 602 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 603 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 604 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 605 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 606 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 607 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 608 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 609 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 610 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 611 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 612 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 613 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 614 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 615 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 616 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 617 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 618 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 619 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 620 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 621 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 622 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 623 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 624 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 625 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 626 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 627 | google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 628 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= 629 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 630 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 631 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 632 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 633 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 634 | google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 635 | google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 636 | google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 637 | google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 638 | google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 639 | google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 640 | google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 641 | google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 642 | google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 643 | google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= 644 | google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0= 645 | google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= 646 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 647 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 648 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 649 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 650 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 651 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 652 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 653 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 654 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 655 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 656 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 657 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 658 | google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 659 | google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= 660 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 661 | google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= 662 | google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 663 | google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 664 | google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 665 | google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0= 666 | google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= 667 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 668 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 669 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 670 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 671 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 672 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 673 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 674 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 675 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 676 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 677 | google.golang.org/protobuf v1.25.1-0.20200805231151-a709e31e5d12/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 678 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 679 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 680 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 681 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 682 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 683 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 684 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 685 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 686 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 687 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 688 | gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= 689 | gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 690 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 691 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 692 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 693 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 694 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 695 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 696 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 697 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 698 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 699 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 700 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 701 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 702 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 703 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 704 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 705 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 706 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 707 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 708 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 709 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 710 | -------------------------------------------------------------------------------- /internal/calldata/calldata.go: -------------------------------------------------------------------------------- 1 | package calldata 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "text/tabwriter" 8 | "text/template" 9 | "time" 10 | 11 | "github.com/google/uuid" 12 | ) 13 | 14 | var ( 15 | tmpl *template.Template 16 | ) 17 | 18 | func init() { 19 | fnMap := make(template.FuncMap, len(funcs)) 20 | for _, fn := range funcs { 21 | fnMap[fn.Name] = fn.Func 22 | } 23 | 24 | tmpl = template.New("calldata").Funcs(fnMap) 25 | } 26 | 27 | type Var struct { 28 | Name string 29 | Desc string 30 | } 31 | 32 | var ( 33 | variables = []Var{ 34 | {Name: "RequestNumber", Desc: "Request number for data"}, 35 | {Name: "Timestamp", Desc: "Timestamp in RFC3339 format"}, 36 | {Name: "TimestampUnix", Desc: "Timestamp as unix time in seconds"}, 37 | {Name: "TimestampUnixMilli", Desc: "Timestamp as unix time in milliseconds"}, 38 | {Name: "TimestampUnixNano", Desc: "Timestamp as unix time in nanoseconds"}, 39 | {Name: "UUID", Desc: "Generated UUIDv4"}, 40 | } 41 | ) 42 | 43 | type CallData struct { 44 | RequestNumber int64 45 | Timestamp string 46 | TimestampUnix int64 47 | TimestampUnixMilli int64 48 | TimestampUnixNano int64 49 | UUID string 50 | } 51 | 52 | func ParseTemplate(data []byte) (*template.Template, error) { 53 | t, err := tmpl.Parse(string(data)) 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | return t, nil 59 | } 60 | 61 | // PrintFuncs prints list of functions to output. 62 | func PrintFuncs(output io.Writer) { 63 | w := tabwriter.NewWriter(output, 0, 0, 4, ' ', 0) 64 | 65 | fmt.Fprintln(w, "Template variables:\t") 66 | for _, vr := range variables { 67 | fmt.Fprintf(w, " .%s\t%s\n", vr.Name, vr.Desc) 68 | } 69 | 70 | fmt.Fprintln(w, "\t") 71 | fmt.Fprintln(w, "Template functions:\t") 72 | for _, fn := range funcs { 73 | fmt.Fprintf(w, " %s\t%s\n", fn.Name, fn.Desc) 74 | } 75 | w.Flush() 76 | } 77 | 78 | func NewCallData(reqNum int) *CallData { 79 | now := time.Now() 80 | nowNano := now.UnixNano() 81 | 82 | return &CallData{ 83 | RequestNumber: int64(reqNum), 84 | Timestamp: now.Format(time.RFC3339), 85 | TimestampUnix: now.Unix(), 86 | TimestampUnixMilli: nowNano / 1e6, 87 | TimestampUnixNano: nowNano, 88 | UUID: uuid.NewString(), 89 | } 90 | } 91 | 92 | func (c *CallData) Execute(tmpl *template.Template) (*bytes.Buffer, error) { 93 | b := new(bytes.Buffer) 94 | if err := tmpl.Execute(b, c); err != nil { 95 | return nil, err 96 | } 97 | 98 | return b, nil 99 | } 100 | -------------------------------------------------------------------------------- /internal/calldata/functions.go: -------------------------------------------------------------------------------- 1 | package calldata 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "strconv" 7 | "time" 8 | 9 | "github.com/Pallinder/go-randomdata" 10 | "github.com/google/uuid" 11 | ) 12 | 13 | const ( 14 | minLen = 2 15 | maxLen = 64 16 | defaultCharset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 17 | ) 18 | 19 | type Func struct { 20 | Name string 21 | Desc string 22 | Func interface{} 23 | } 24 | 25 | var ( 26 | funcs = []Func{ 27 | { 28 | "randomNumber", 29 | "Generate a number in range [min=0, max)", 30 | randomdata.Number, 31 | }, 32 | { 33 | "randomDecimal", 34 | "Generate a number in range [min=0, max) with decimal point x", 35 | randomdata.Decimal, 36 | }, 37 | { 38 | "randomBoolean", 39 | "Generate a bool", 40 | randomdata.Boolean, 41 | }, 42 | { 43 | "randomString", 44 | "Generate a string from a-zA-Z0-9. Accepts a length parameter", 45 | randomString, 46 | }, 47 | { 48 | "randomStringWithCharset", 49 | "Generate a string from charset", 50 | randomStringWithCharset, 51 | }, 52 | { 53 | "randomStringSample", 54 | "Generate a string sampled from a list of strings", 55 | randomdata.StringSample, 56 | }, 57 | { 58 | "randomSillyName", 59 | "Generate a silly name", 60 | randomdata.SillyName, 61 | }, 62 | { 63 | "randomMaleName", 64 | "Generate a male name", 65 | func() string { 66 | return randomdata.FirstName(randomdata.Male) 67 | }, 68 | }, 69 | { 70 | "randomFemaleName", 71 | "Generate a female name", 72 | func() string { 73 | return randomdata.FirstName(randomdata.Female) 74 | }, 75 | }, 76 | { 77 | "randomName", 78 | "Generate a human name", 79 | func() string { 80 | return randomdata.FirstName(randomdata.RandomGender) 81 | }, 82 | }, 83 | { 84 | "randomMaleFullName", 85 | "Generate a male full name", 86 | func() string { 87 | return randomdata.FullName(randomdata.Male) 88 | }, 89 | }, 90 | { 91 | "randomFemaleFullName", 92 | "Generate a female full name", 93 | func() string { 94 | return randomdata.FullName(randomdata.Female) 95 | }, 96 | }, 97 | { 98 | "randomFullName", 99 | "Generate a human full name", 100 | func() string { 101 | return randomdata.FullName(randomdata.RandomGender) 102 | }, 103 | }, 104 | { 105 | "randomEmail", 106 | "Generate a email", 107 | randomdata.Email, 108 | }, 109 | { 110 | "randomIpV4Address", 111 | "Generate a valid random IPv4 address", 112 | randomdata.IpV4Address, 113 | }, 114 | { 115 | "randomIpV6Address", 116 | "Generate a valid random IPv6 address", 117 | randomdata.IpV6Address, 118 | }, 119 | { 120 | "randomDateInRange", 121 | "Generate a full date in range", 122 | randomdata.FullDateInRange, 123 | }, 124 | { 125 | "randomPhoneNumber", 126 | "Generate a phone number", 127 | randomdata.PhoneNumber, 128 | }, 129 | { 130 | "string", 131 | "Return the string representation of argument", 132 | func(arg interface{}) string { 133 | return fmt.Sprintf("%v", arg) 134 | }, 135 | }, 136 | { 137 | "quote", 138 | "Return a double-quoted string literal representing string", 139 | strconv.Quote, 140 | }, 141 | { 142 | "uuid", 143 | "Generate a new UUID", 144 | uuid.NewString, 145 | }, 146 | } 147 | 148 | random *rand.Rand 149 | ) 150 | 151 | // SetSeeder sets seed for pseudo-random generator. 152 | func SetSeeder(seed int64) { 153 | if seed == 0 { 154 | seed = time.Now().UnixNano() 155 | } 156 | 157 | random = rand.New(rand.NewSource(seed)) //nolint:gosec 158 | randomdata.CustomRand(random) 159 | } 160 | 161 | func randomStringWithCharset(charset string, length int) string { 162 | if length <= 0 { 163 | length = randomdata.Number(maxLen-minLen+1) + minLen 164 | } 165 | if charset == "" { 166 | charset = defaultCharset 167 | } 168 | 169 | runes := []rune(charset) 170 | count := len(runes) 171 | 172 | data := make([]rune, 0, length) 173 | for i := 0; i < length; i++ { 174 | data = append(data, runes[randomdata.Number(count)]) 175 | } 176 | 177 | return string(data) 178 | } 179 | 180 | func randomString(length int) string { 181 | return randomStringWithCharset(defaultCharset, length) 182 | } 183 | -------------------------------------------------------------------------------- /internal/calldata/functions_test.go: -------------------------------------------------------------------------------- 1 | package calldata 2 | 3 | import ( 4 | "testing" 5 | "unicode/utf8" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func Test_randomStringWithCharset(t *testing.T) { 11 | tests := []struct { 12 | charset string 13 | length int 14 | }{ 15 | {"", 10}, 16 | {"abc", 10}, 17 | {"abc", 0}, 18 | {"abc", -1}, 19 | {"😃😄😁", 0}, 20 | {"абвabc", 6}, 21 | } 22 | for _, tt := range tests { 23 | s := randomStringWithCharset(tt.charset, tt.length) 24 | c := utf8.RuneCountInString(s) 25 | 26 | if tt.length <= 0 { 27 | assert.Greater(t, c, 0) 28 | } else { 29 | assert.Equal(t, tt.length, c) 30 | } 31 | 32 | cs := tt.charset 33 | if cs == "" { 34 | cs = defaultCharset 35 | } 36 | 37 | for _, r := range s { 38 | assert.Contains(t, []rune(cs), r) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /internal/kafka/consumer.go: -------------------------------------------------------------------------------- 1 | package kafka 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/Shopify/sarama" 7 | ) 8 | 9 | type ConsumerGroup struct { 10 | client sarama.ConsumerGroup 11 | } 12 | 13 | // NewConsumerGroup creates new ConsumerGroup. 14 | func NewConsumerGroup(brokers []string, group string, config *sarama.Config) (*ConsumerGroup, error) { 15 | client, err := sarama.NewConsumerGroup(brokers, group, config) 16 | if err != nil { 17 | return nil, err 18 | } 19 | 20 | return &ConsumerGroup{client}, nil 21 | } 22 | 23 | // Close closes consumer. 24 | func (c *ConsumerGroup) Close() error { 25 | return c.client.Close() 26 | } 27 | 28 | // Errors returns errors channel. 29 | func (c *ConsumerGroup) Errors() <-chan error { 30 | return c.client.Errors() 31 | } 32 | 33 | // Consume starts non blocking consumer loop for ConsumerHandle on provided topics list. 34 | // Returned channel will closed as soon as Setup step is happened is called for first handler call or if error happens first. 35 | func (c *ConsumerGroup) Consume(ctx context.Context, topics []string, handler sarama.ConsumerGroupHandler) error { 36 | for { 37 | // `Consume` should be called inside an infinite loop, when a 38 | // server-side rebalance happens, the consumer session will need to be 39 | // recreated to get the new claims 40 | if err := c.client.Consume(ctx, topics, handler); err != nil { 41 | return err 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /internal/kafka/dump.go: -------------------------------------------------------------------------------- 1 | package kafka 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/Shopify/sarama" 7 | ) 8 | 9 | type RecordHeaders []sarama.RecordHeader 10 | 11 | func (p RecordHeaders) String() string { 12 | result := strings.Builder{} 13 | 14 | result.WriteByte('{') 15 | for i, h := range p { 16 | result.Write(h.Key) 17 | result.WriteByte(':') 18 | result.WriteByte('"') 19 | result.Write(h.Value) 20 | result.WriteByte('"') 21 | 22 | if len(p) != (i + 1) { 23 | result.WriteString(", ") 24 | } 25 | } 26 | result.WriteByte('}') 27 | 28 | return result.String() 29 | } 30 | 31 | // NewRecordHeadersFromPointers converts []*sarama.RecordHeader -> []sarama.RecordHeader. 32 | func NewRecordHeadersFromPointers(headers []*sarama.RecordHeader) RecordHeaders { 33 | result := make(RecordHeaders, 0, len(headers)) 34 | 35 | for _, h := range headers { 36 | result = append(result, *h) 37 | } 38 | 39 | return result 40 | } 41 | -------------------------------------------------------------------------------- /internal/kafka/dump_test.go: -------------------------------------------------------------------------------- 1 | package kafka 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/Shopify/sarama" 7 | ) 8 | 9 | func TestRecordHeaders_String(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | p RecordHeaders 13 | want string 14 | }{ 15 | {"empty", RecordHeaders{}, `{}`}, 16 | {"records", RecordHeaders{ 17 | sarama.RecordHeader{ 18 | Key: []byte("abc"), 19 | Value: []byte("data"), 20 | }, 21 | }, `{abc:"data"}`}, 22 | } 23 | for _, tt := range tests { 24 | t.Run(tt.name, func(t *testing.T) { 25 | if got := tt.p.String(); got != tt.want { 26 | t.Errorf("RecordHeaders.String() = %v, want %v", got, tt.want) 27 | } 28 | }) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /internal/kafka/kafka.go: -------------------------------------------------------------------------------- 1 | package kafka 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/Shopify/sarama" 7 | ) 8 | 9 | func NewConfig(appName string, authDSN string) (*sarama.Config, error) { 10 | config := sarama.NewConfig() 11 | config.Version = sarama.V0_11_0_0 // 0.11 - min version for support record headers 12 | config.ClientID = appName 13 | config.Consumer.Return.Errors = true 14 | config.Producer.Return.Successes = true 15 | config.Producer.Return.Errors = true 16 | 17 | if authDSN != "" { 18 | sasl := NewSASL() 19 | 20 | if err := sasl.Parse(authDSN); err != nil { 21 | return nil, err 22 | } 23 | 24 | if err := setupSASL(config, sasl); err != nil { 25 | return nil, err 26 | } 27 | } 28 | 29 | return config, nil 30 | } 31 | 32 | func setupSASL(config *sarama.Config, sasl *SASL) error { 33 | switch sasl.Mechanism { 34 | case sarama.SASLTypeSCRAMSHA256, sarama.SASLTypeSCRAMSHA512, sarama.SASLTypePlaintext: 35 | default: 36 | return fmt.Errorf("unsupported SASL mechanism type: %s", sasl.Mechanism) 37 | } 38 | 39 | config.Net.SASL.Enable = true 40 | config.Net.SASL.User = sasl.User 41 | config.Net.SASL.Password = sasl.Password 42 | config.Net.SASL.Mechanism = sasl.Mechanism 43 | config.Net.SASL.SCRAMClientGeneratorFunc = scramClientForSASLMechanism(sasl.Mechanism) 44 | 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /internal/kafka/producer.go: -------------------------------------------------------------------------------- 1 | package kafka 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/Shopify/sarama" 7 | ) 8 | 9 | // Producer kafka producer. 10 | type Producer struct { 11 | client sarama.AsyncProducer 12 | } 13 | 14 | // NewProducer creates a new Producer using the given broker addresses and configuration. 15 | func NewProducer(brokers []string, config *sarama.Config) (*Producer, error) { 16 | client, err := sarama.NewAsyncProducer(brokers, config) 17 | if err != nil { 18 | return nil, err 19 | } 20 | 21 | return &Producer{client}, nil 22 | } 23 | 24 | // SendMessage produces a given message, and returns only when it either has 25 | // succeeded or failed to produce. It will return the partition and the offset 26 | // of the produced message, or an error if the message failed to produce. 27 | func (p *Producer) SendMessage(ctx context.Context, msg *sarama.ProducerMessage) error { 28 | errs := make(chan error) 29 | 30 | go func() { 31 | select { 32 | case p.client.Input() <- msg: 33 | case <-ctx.Done(): 34 | errs <- ctx.Err() 35 | } 36 | }() 37 | 38 | go func() { 39 | select { 40 | case err := <-p.client.Errors(): 41 | errs <- err 42 | 43 | case m := <-p.client.Successes(): 44 | *msg = *m 45 | errs <- nil 46 | } 47 | }() 48 | 49 | return <-errs 50 | } 51 | 52 | // Close shuts down the producer and waits for any buffered messages to be 53 | // flushed. You must call this function before a producer object passes out of 54 | // scope, as it may otherwise leak memory. You must call this before calling 55 | // Close on the underlying client. 56 | func (p *Producer) Close() error { 57 | return p.client.Close() 58 | } 59 | -------------------------------------------------------------------------------- /internal/kafka/sasl.go: -------------------------------------------------------------------------------- 1 | package kafka 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/Shopify/sarama" 8 | ) 9 | 10 | const ( 11 | // AuthDSNTemplate template for auth DSN. 12 | AuthDSNTemplate = "SASLType:login:password" 13 | ) 14 | 15 | type SASL struct { 16 | Mechanism sarama.SASLMechanism 17 | User string 18 | Password string 19 | } 20 | 21 | func NewSASL() *SASL { 22 | return &SASL{} 23 | } 24 | 25 | // Parse parses DSN and fill SASL struct. 26 | func (s *SASL) Parse(dsn string) error { 27 | parts := strings.SplitN(dsn, ":", 3) 28 | if len(parts) != 3 { 29 | return fmt.Errorf(`incorrect DSN format: expected "%s"`, AuthDSNTemplate) 30 | } 31 | 32 | s.Mechanism = sarama.SASLMechanism(parts[0]) 33 | s.User = parts[1] 34 | s.Password = parts[2] 35 | 36 | return nil 37 | } 38 | -------------------------------------------------------------------------------- /internal/kafka/scram.go: -------------------------------------------------------------------------------- 1 | package kafka 2 | 3 | import ( 4 | "crypto/sha256" 5 | "crypto/sha512" 6 | 7 | "github.com/Shopify/sarama" 8 | "github.com/xdg/scram" 9 | ) 10 | 11 | type XDGSCRAMClient struct { 12 | *scram.ClientConversation 13 | HashGeneratorFcn scram.HashGeneratorFcn 14 | } 15 | 16 | // Begin prepares the client for the SCRAM exchange 17 | // with the server with a user name and a password 18 | func (x *XDGSCRAMClient) Begin(user, password, authzID string) error { 19 | client, err := x.HashGeneratorFcn.NewClient(user, password, authzID) 20 | if err != nil { 21 | return err 22 | } 23 | 24 | x.ClientConversation = client.NewConversation() 25 | 26 | return nil 27 | } 28 | 29 | func scramClientForSASLMechanism(mechanism sarama.SASLMechanism) func() sarama.SCRAMClient { 30 | var hashGen scram.HashGeneratorFcn 31 | 32 | switch mechanism { 33 | case sarama.SASLTypeSCRAMSHA256: 34 | hashGen = sha256.New 35 | 36 | case sarama.SASLTypeSCRAMSHA512: 37 | hashGen = sha512.New 38 | } 39 | 40 | if hashGen != nil { 41 | return func() sarama.SCRAMClient { 42 | return &XDGSCRAMClient{ 43 | HashGeneratorFcn: hashGen, 44 | } 45 | } 46 | } 47 | 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /internal/proto/proto.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "go/build" 7 | "io" 8 | "net/http" 9 | "net/url" 10 | "os" 11 | "path/filepath" 12 | 13 | "github.com/jhump/protoreflect/desc" 14 | "github.com/jhump/protoreflect/desc/protoparse" 15 | ) 16 | 17 | type Proto struct { 18 | descriptors []*desc.FileDescriptor 19 | ImportPaths []string 20 | } 21 | 22 | // NewProto creates a new instance of Proto. May return a Proto or open/parse error. 23 | func NewProto(filenames []string, importPaths ...string) (p *Proto, err error) { 24 | importPaths = append(importPaths, ".") 25 | importPaths = append(importPaths, build.Default.SrcDirs()...) 26 | importPaths = append(importPaths, "/") 27 | 28 | // resolve filenames: local filename save as is, remote files are downloads and saves as temp files 29 | paths := make([]string, 0, len(filenames)) 30 | for _, f := range filenames { 31 | u, err := url.Parse(f) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | if u.Scheme == "http" || u.Scheme == "https" { // url 37 | filename, cleaner, err := httpGet(f) 38 | if err != nil { 39 | return nil, err 40 | } 41 | defer cleaner() 42 | 43 | paths = append(paths, filename) 44 | } else if p, _ := filepath.Glob(f); len(p) > 0 { // check pattern 45 | for k := range p { 46 | importPaths = append(importPaths, filepath.Dir(p[k])) 47 | } 48 | paths = append(paths, p...) 49 | } else { // path 50 | paths = append(paths, f) 51 | } 52 | } 53 | 54 | parser := protoparse.Parser{ 55 | ImportPaths: importPaths, 56 | } 57 | 58 | descriptors, err := parser.ParseFiles(paths...) 59 | if err != nil { 60 | return 61 | } 62 | 63 | return &Proto{ 64 | descriptors: descriptors, 65 | ImportPaths: importPaths, 66 | }, nil 67 | } 68 | 69 | // FindMessage searches for message with given name. 70 | func (p *Proto) FindMessage(name string) (*desc.MessageDescriptor, error) { 71 | if name == "" { 72 | return nil, errors.New("proto: name is empty") 73 | } 74 | 75 | for _, fd := range p.descriptors { 76 | // finds the message with the given fully-qualified name 77 | if d := fd.FindMessage(name); d != nil { 78 | return d, nil 79 | } 80 | 81 | // find just with short-name 82 | for _, d := range fd.GetMessageTypes() { 83 | if d.GetName() == name { 84 | return d, nil 85 | } 86 | } 87 | } 88 | 89 | return nil, fmt.Errorf("proto: message with name %s not found", name) 90 | } 91 | 92 | func httpGet(url string) (filename string, cleaner func(), err error) { 93 | resp, err := http.Get(url) //nolint:gosec 94 | if err != nil { 95 | return 96 | } 97 | defer resp.Body.Close() 98 | 99 | f, err := os.CreateTemp("", "protokaf.*.proto") 100 | if err != nil { 101 | return 102 | } 103 | 104 | _, err = io.Copy(f, resp.Body) 105 | if err != nil { 106 | return 107 | } 108 | 109 | return f.Name(), func() { os.Remove(f.Name()) }, f.Sync() 110 | } 111 | -------------------------------------------------------------------------------- /internal/proto/proto_encoder.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | import ( 4 | "github.com/Shopify/sarama" 5 | "github.com/jhump/protoreflect/desc" 6 | "github.com/jhump/protoreflect/dynamic" 7 | ) 8 | 9 | var _ sarama.Encoder = &protoEncoder{} 10 | 11 | type protoEncoder struct { 12 | data []byte 13 | err error 14 | } 15 | 16 | // Encoder returns sarama.Encoder for protobuf. 17 | func Encoder(m *dynamic.Message) sarama.Encoder { 18 | data, err := m.Marshal() 19 | 20 | return &protoEncoder{ 21 | data: data, 22 | err: err, 23 | } 24 | } 25 | 26 | func (s protoEncoder) Encode() ([]byte, error) { 27 | return s.data, s.err 28 | } 29 | 30 | func (s protoEncoder) Length() int { 31 | return len(s.data) 32 | } 33 | 34 | func Unmarshal(b []byte, md *desc.MessageDescriptor) (*dynamic.Message, error) { 35 | f := dynamic.NewMessageFactoryWithDefaults() 36 | m := f.NewDynamicMessage(md) 37 | 38 | err := m.UnmarshalJSON(b) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | return m, nil 44 | } 45 | -------------------------------------------------------------------------------- /internal/proto/proto_test.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "net/http/httptest" 8 | "os" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | var testfiles = []string{ 15 | "testdata/example.proto", 16 | "testdata/with_local_import.proto", 17 | } 18 | 19 | func TestProto_NewProto_Success(t *testing.T) { 20 | p, err := NewProto(testfiles) 21 | assert.NotNil(t, p) 22 | assert.Nil(t, err) 23 | } 24 | 25 | func TestProto_NewProto_HTTP_Request(t *testing.T) { 26 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 27 | file, err := os.Open(testfiles[0]) 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | 32 | _, err = io.Copy(w, file) 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | })) 37 | defer ts.Close() 38 | 39 | p, err := NewProto([]string{ts.URL}) 40 | assert.NotNil(t, p) 41 | assert.Nil(t, err) 42 | } 43 | 44 | func TestProto_NewProto_NotFound(t *testing.T) { 45 | var files = []string{"testdata/not_found.proto"} 46 | 47 | p, err := NewProto(files) 48 | assert.Nil(t, p) 49 | if assert.Error(t, err) { 50 | assert.Equal(t, fmt.Sprintf("open %s: no such file or directory", files[0]), err.Error()) 51 | } 52 | } 53 | 54 | func TestProto_NewProto_ParseError(t *testing.T) { 55 | var files = []string{"testdata/parse_err.proto"} 56 | 57 | p, err := NewProto(files) 58 | assert.Nil(t, p) 59 | assert.Error(t, err) 60 | } 61 | 62 | func TestProto_FindMessage(t *testing.T) { 63 | type args struct { 64 | name string 65 | } 66 | tests := []struct { 67 | name string 68 | args args 69 | want string 70 | wantErr bool 71 | }{ 72 | {"found", args{"HelloRequest"}, "HelloRequest", false}, 73 | {"found", args{"example.HelloRequest"}, "HelloRequest", false}, 74 | {"found", args{"WithMoney"}, "WithMoney", false}, 75 | {"not found", args{"NotFound"}, "", true}, 76 | } 77 | 78 | p, err := NewProto(testfiles) 79 | assert.Nil(t, err) 80 | 81 | for _, tt := range tests { 82 | t.Run(tt.name, func(t *testing.T) { 83 | got, err := p.FindMessage(tt.args.name) 84 | if (err != nil) != tt.wantErr { 85 | t.Errorf("Proto.FindMessage() error = %v, wantErr %v", err, tt.wantErr) 86 | return 87 | } 88 | 89 | if got != nil && got.GetName() != tt.want { 90 | t.Errorf("Proto.FindMessage() = %v, want %v", got, tt.want) 91 | } 92 | }) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /internal/proto/testdata/example.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package example; 4 | 5 | option go_package = "."; 6 | 7 | import "google/protobuf/timestamp.proto"; 8 | 9 | service Example { 10 | rpc Hello(HelloRequest) returns (HelloResponse) {} 11 | } 12 | 13 | message HelloRequest { 14 | string name = 1; 15 | int32 age = 2; 16 | } 17 | 18 | message HelloResponse { 19 | string answer = 1; 20 | } 21 | 22 | message Num { 23 | int32 num = 1; 24 | } 25 | 26 | message Empty {} -------------------------------------------------------------------------------- /internal/proto/testdata/google/type/money.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | // Represents an amount of money with its currency type. 18 | message Money { 19 | // The three-letter currency code defined in ISO 4217. 20 | string currency_code = 1; 21 | 22 | // The whole units of the amount. 23 | // For example if `currencyCode` is `"USD"`, then 1 unit is one US dollar. 24 | int64 units = 2; 25 | 26 | // Number of nano (10^-9) units of the amount. 27 | // The value must be between -999,999,999 and +999,999,999 inclusive. 28 | // If `units` is positive, `nanos` must be positive or zero. 29 | // If `units` is zero, `nanos` can be positive, zero, or negative. 30 | // If `units` is negative, `nanos` must be negative or zero. 31 | // For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000. 32 | int32 nanos = 3; 33 | } 34 | -------------------------------------------------------------------------------- /internal/proto/testdata/parse_err.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package example; 4 | 5 | option go_package = "."; 6 | 7 | service Example { 8 | rpc Hello(HelloRequest) returns (HelloResponse) {} 9 | 10 | } 11 | 12 | message {} -------------------------------------------------------------------------------- /internal/proto/testdata/types.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package example; 4 | 5 | import "google/protobuf/any.proto"; 6 | import "google/protobuf/timestamp.proto"; 7 | import "google/protobuf/duration.proto"; 8 | import "google/protobuf/struct.proto"; 9 | import "google/protobuf/wrappers.proto"; 10 | 11 | // Enum definition 12 | enum ExampleEnum { 13 | UNKNOWN = 0; 14 | OPTION_ONE = 1; 15 | OPTION_TWO = 2; 16 | } 17 | 18 | // Message definition 19 | message ExampleMessage { 20 | // Scalar types 21 | int32 int32_field = 1; 22 | int64 int64_field = 2; 23 | uint32 uint32_field = 3; 24 | uint64 uint64_field = 4; 25 | sint32 sint32_field = 5; 26 | sint64 sint64_field = 6; 27 | fixed32 fixed32_field = 7; 28 | fixed64 fixed64_field = 8; 29 | sfixed32 sfixed32_field = 9; 30 | sfixed64 sfixed64_field = 10; 31 | float float_field = 11; 32 | double double_field = 12; 33 | bool bool_field = 13; 34 | string string_field = 14; 35 | bytes bytes_field = 15; 36 | 37 | // Enum type 38 | ExampleEnum enum_field = 16; 39 | 40 | // Nested message 41 | message NestedMessage { 42 | int32 nested_int32 = 1; 43 | string nested_string = 2; 44 | } 45 | 46 | // Message type 47 | NestedMessage message_field = 17; 48 | 49 | // Repeated fields 50 | repeated int32 repeated_int32_field = 18; 51 | repeated string repeated_string_field = 19; 52 | 53 | // Map fields 54 | map map_string_int32_field = 20; 55 | map map_int32_message_field = 21; 56 | 57 | // Oneof field 58 | oneof my_oneof { 59 | int32 option1 = 22; 60 | string option2 = 23; 61 | NestedMessage option3 = 24; 62 | } 63 | 64 | // Well-known types 65 | google.protobuf.Any any_field = 25; 66 | google.protobuf.Timestamp timestamp_field = 26; 67 | google.protobuf.Duration duration_field = 27; 68 | google.protobuf.Struct struct_field = 28; 69 | google.protobuf.Value value_field = 29; 70 | google.protobuf.ListValue list_value_field = 30; 71 | google.protobuf.BoolValue bool_value_field = 31; 72 | google.protobuf.BytesValue bytes_value_field = 32; 73 | google.protobuf.DoubleValue double_value_field = 33; 74 | google.protobuf.FloatValue float_value_field = 34; 75 | google.protobuf.Int32Value int32_value_field = 35; 76 | google.protobuf.Int64Value int64_value_field = 36; 77 | google.protobuf.StringValue string_value_field = 37; 78 | google.protobuf.UInt32Value uint32_value_field = 38; 79 | google.protobuf.UInt64Value uint64_value_field = 39; 80 | } 81 | -------------------------------------------------------------------------------- /internal/proto/testdata/with_local_import.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package example; 4 | 5 | option go_package = "."; 6 | 7 | import "google/type/money.proto"; 8 | 9 | message WithMoney { 10 | Money amount = 1; 11 | } 12 | -------------------------------------------------------------------------------- /internal/tracing/jaeger.go: -------------------------------------------------------------------------------- 1 | package tracing 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "github.com/spf13/pflag" 9 | "github.com/spf13/viper" 10 | jaegerConfig "github.com/uber/jaeger-client-go/config" 11 | ) 12 | 13 | type Env struct { 14 | usage string 15 | defaultValue string 16 | } 17 | 18 | //nolint:gofmt 19 | var jaegerEnvs = map[string]*Env{ 20 | "JAEGER_SERVICE_NAME": {"The service name", "protokaf"}, 21 | "JAEGER_TAGS": {"A comma separated list of name = value tracer level tags, which get added to all reported spans", ""}, //nolint:lll 22 | "JAEGER_SAMPLER_TYPE": {"The sampler type", "const"}, 23 | "JAEGER_SAMPLER_PARAM": {"The sampler parameter (number)", "1.0"}, 24 | "JAEGER_SAMPLING_ENDPOINT": {"The url for the remote sampling conf when using sampler type remote", ""}, 25 | "JAEGER_REPORTER_MAX_QUEUE_SIZE": {"The reporter's maximum queue size", ""}, 26 | "JAEGER_REPORTER_FLUSH_INTERVAL": {"The reporter's flush interval (ms)", ""}, 27 | "JAEGER_ENDPOINT": {"Send spans to jaeger-collector at this URL", ""}, 28 | "JAEGER_USER": {"User for basic http authentication when sending spans to jaeger-collector", ""}, 29 | "JAEGER_PASSWORD": {"Password for basic http authentication when sending spans to jaeger-collector", ""}, 30 | "JAEGER_AGENT_HOST": {"The hostname for communicating with agent via UDP", "0.0.0.0"}, 31 | "JAEGER_AGENT_PORT": {"The port for communicating with agent via UDP", "6831"}, 32 | } 33 | 34 | func NewJaegerConfig() (*jaegerConfig.Configuration, error) { 35 | setupJaegerEnv() 36 | 37 | jaegerCfg, err := jaegerConfig.FromEnv() 38 | if err != nil { 39 | return nil, fmt.Errorf("could not parse Jaeger env vars: %w", err) 40 | } 41 | 42 | return jaegerCfg, nil 43 | } 44 | 45 | func SetJaegerFlags(flags *pflag.FlagSet) { 46 | for envName, env := range jaegerEnvs { 47 | var flag string 48 | name := flagName(envName) 49 | 50 | flags.StringVar(&flag, name, env.defaultValue, env.usage) 51 | _ = viper.BindPFlag(name, flags.Lookup(name)) 52 | } 53 | } 54 | 55 | func setupJaegerEnv() { 56 | for envName := range jaegerEnvs { 57 | if os.Getenv(envName) == "" { 58 | name := flagName(envName) 59 | os.Setenv(envName, viper.GetString(name)) 60 | } 61 | } 62 | } 63 | 64 | func flagName(envName string) string { 65 | return strings.ReplaceAll(strings.ToLower(envName), "_", "-") 66 | } 67 | -------------------------------------------------------------------------------- /internal/tracing/tracing.go: -------------------------------------------------------------------------------- 1 | package tracing 2 | 3 | import ( 4 | "github.com/Shopify/sarama" 5 | "github.com/opentracing/opentracing-go" 6 | "github.com/opentracing/opentracing-go/ext" 7 | ) 8 | 9 | const ( 10 | tracingProduceOperationName = "produce" 11 | tracingMessageTopicTag = "kafka.message.topic" 12 | tracingMessageLengthTag = "kafka.message.length" 13 | ) 14 | 15 | func CreateSpan(tracer opentracing.Tracer, msg *sarama.ProducerMessage) (opentracing.Span, error) { 16 | if tracer == nil { 17 | tracer = opentracing.GlobalTracer() 18 | } 19 | 20 | msgValueLen := 0 21 | if v := msg.Value; v != nil { 22 | msgValueLen = v.Length() 23 | } 24 | 25 | tags := opentracing.Tags{ 26 | tracingMessageTopicTag: msg.Topic, 27 | tracingMessageLengthTag: msgValueLen, 28 | } 29 | span := tracer.StartSpan( 30 | tracingProduceOperationName, 31 | tags, 32 | ) 33 | ext.SpanKindProducer.Set(span) 34 | 35 | // get OT header 36 | headers := opentracing.TextMapCarrier{} 37 | err := tracer.Inject(span.Context(), opentracing.TextMap, &headers) 38 | if err != nil { 39 | span.Finish() 40 | return nil, err 41 | } 42 | 43 | // set message headers 44 | msgHeaders := make([]sarama.RecordHeader, 0, len(headers)) 45 | for k, v := range headers { 46 | msgHeaders = append(msgHeaders, sarama.RecordHeader{ 47 | Key: []byte(k), 48 | Value: []byte(v), 49 | }) 50 | } 51 | 52 | msg.Headers = append(msg.Headers, msgHeaders...) 53 | 54 | return span, nil 55 | } 56 | -------------------------------------------------------------------------------- /internal/tracing/tracing_test.go: -------------------------------------------------------------------------------- 1 | package tracing 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/Shopify/sarama" 7 | "github.com/opentracing/opentracing-go/ext" 8 | "github.com/opentracing/opentracing-go/mocktracer" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestCreateSpan(t *testing.T) { 13 | const topic = "test-topic" 14 | 15 | msg := sarama.ProducerMessage{ 16 | Topic: topic, 17 | } 18 | 19 | tracer := mocktracer.New() 20 | defer func() { 21 | finished := tracer.FinishedSpans() 22 | span := finished[0] 23 | 24 | keys := []string{} 25 | for _, h := range msg.Headers { 26 | keys = append(keys, string(h.Key)) 27 | } 28 | 29 | assert.Contains(t, keys, "mockpfx-ids-traceid") 30 | assert.Equal(t, topic, span.Tag(tracingMessageTopicTag)) 31 | assert.Equal(t, ext.SpanKindProducer.Value, span.Tag(string(ext.SpanKind))) 32 | }() 33 | 34 | span, err := CreateSpan(tracer, &msg) 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | defer span.Finish() 39 | } 40 | -------------------------------------------------------------------------------- /internal/utils/dump/dump.go: -------------------------------------------------------------------------------- 1 | package dump 2 | 3 | import ( 4 | "encoding/hex" 5 | "encoding/json" 6 | "fmt" 7 | "strings" 8 | 9 | "github.com/golang/protobuf/jsonpb" 10 | "github.com/jhump/protoreflect/dynamic" 11 | ) 12 | 13 | type Logger interface { 14 | Infof(string, ...interface{}) 15 | Errorf(string, ...interface{}) 16 | Debugf(string, ...interface{}) 17 | Debug(...interface{}) 18 | } 19 | 20 | // Pair is a name, value pair. 21 | type Pair struct { 22 | Name string 23 | Value interface{} 24 | } 25 | 26 | type Pairs []Pair 27 | 28 | // Dump dumps list of pairs with Logger. 29 | func (p Pairs) Dump(log Logger) { 30 | maxLen := 0 31 | for _, m := range p { 32 | if n := len(m.Name); maxLen < n { 33 | maxLen = n 34 | } 35 | } 36 | 37 | nameWithPad := func(n string) string { 38 | pad := strings.Repeat(" ", maxLen-len(n)+1) 39 | return n + pad 40 | } 41 | 42 | log.Debug(titleStd("Dump begin")) 43 | 44 | for _, m := range p { 45 | n, v := m.Name, m.Value 46 | 47 | switch x := v.(type) { 48 | case []byte: 49 | log.Debugf("%s: \n%s", nameWithPad(n), hex.Dump(x)) 50 | 51 | default: 52 | if v == "" { 53 | v = "(empty)" 54 | } 55 | 56 | log.Debugf("%s: %v", nameWithPad(n), v) 57 | } 58 | } 59 | } 60 | 61 | func marshalJSONCustom(msg *dynamic.Message) func() ([]byte, error) { 62 | return func() ([]byte, error) { 63 | return msg.MarshalJSONPB(&jsonpb.Marshaler{Indent: " ", EmitDefaults: true}) 64 | } 65 | } 66 | 67 | // DynamicMessage dumps dynamic.Message. 68 | func DynamicMessage(log Logger, name, output string, msg *dynamic.Message) { 69 | marshaller := marshalJSONCustom(msg) 70 | 71 | switch output { 72 | case "text": 73 | marshaller = msg.MarshalTextIndent 74 | 75 | case "json": 76 | marshaller = marshalJSONCustom(msg) 77 | } 78 | 79 | data, err := marshaller() 80 | if err != nil { 81 | log.Errorf("Error to marshal message: %s", err) 82 | } 83 | 84 | title := titleStd(fmt.Sprintf("%s (%s output)", name, output)) 85 | log.Infof("%s\n%s", title, string(data)) 86 | } 87 | 88 | func PrintStruct(log Logger, title string, i interface{}) { 89 | s, _ := json.MarshalIndent(i, "", " ") 90 | log.Infof("%s\n%s", titleStd(title), s) 91 | } 92 | 93 | func titleStd(t string) string { 94 | return title(t, 45) 95 | } 96 | 97 | // title returns title with left and right fillers. 98 | func title(title string, length int) string { 99 | const filler = "-" 100 | 101 | fillLen := length - len(title) - 2 102 | if fillLen < 2 { 103 | return title 104 | } 105 | 106 | fillRightLen := fillLen / 2 107 | fillLeftLen := fillRightLen + (fillLen % 2) 108 | 109 | return fmt.Sprintf( 110 | "%s %s %s", 111 | strings.Repeat(filler, fillLeftLen), 112 | title, 113 | strings.Repeat(filler, fillRightLen), 114 | ) 115 | } 116 | -------------------------------------------------------------------------------- /internal/utils/dump/dump_test.go: -------------------------------------------------------------------------------- 1 | package dump 2 | 3 | import "testing" 4 | 5 | func Test_title(t *testing.T) { 6 | type args struct { 7 | title string 8 | length int 9 | } 10 | tests := []struct { 11 | name string 12 | args args 13 | want string 14 | }{ 15 | {"1", args{"1234", 10}, "-- 1234 --"}, 16 | {"2", args{"12345", 10}, "-- 12345 -"}, 17 | {"3", args{"123456", 10}, "- 123456 -"}, 18 | {"4", args{"1234567", 10}, "1234567"}, 19 | {"5", args{"123456789", 10}, "123456789"}, 20 | } 21 | for _, tt := range tests { 22 | t.Run(tt.name, func(t *testing.T) { 23 | if got := title(tt.args.title, tt.args.length); got != tt.want { 24 | t.Errorf("title() = %v, want %v", got, tt.want) 25 | } 26 | }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/kuper-tech/protokaf/cmd" 4 | 5 | func main() { 6 | cmd.Execute() 7 | } 8 | -------------------------------------------------------------------------------- /scripts/system.mk: -------------------------------------------------------------------------------- 1 | include scripts/system/*.mk 2 | -------------------------------------------------------------------------------- /scripts/system/compose.mk: -------------------------------------------------------------------------------- 1 | APP_PATH?=/go/src/$(shell grep ^module go.mod | awk '{print $$2}') 2 | APP_PROJECT=${APP_NAME}_${MODE} 3 | 4 | DOCKER_COMPOSE_BUILD_ARGS=\ 5 | GOPATH="${GOPATH}" \ 6 | APP_NAME="${APP_NAME}" \ 7 | APP_PATH="${APP_PATH}" \ 8 | APP_PROJECT="${APP_PROJECT}" \ 9 | MODE="${MODE}" 10 | 11 | DOCKER_COMPOSE_DIR=${PWD}/build/docker 12 | 13 | DOCKER_COMPOSE_CMD=\ 14 | ${DOCKER_COMPOSE_BUILD_ARGS} docker-compose \ 15 | -p ${APP_PROJECT} \ 16 | -f ${DOCKER_COMPOSE_DIR}/docker-compose.yaml 17 | 18 | RED_COLOR=\033[0;31m 19 | NO_COLOR=\033[0m 20 | 21 | 22 | docker-test-up: ## Up compose test docker images 23 | docker-test-up: .mode-test 24 | ${DOCKER_COMPOSE_CMD} up 25 | 26 | docker-test-build: ## Build compose test docker images 27 | docker-test-build: .mode-test 28 | ${DOCKER_COMPOSE_CMD} build 29 | 30 | docker-test: ## Run compose test 31 | docker-test: cmd?=test 32 | docker-test: .mode-test 33 | ${DOCKER_COMPOSE_CMD} exec app make ${cmd} 34 | 35 | docker-test-down: ## Stop docker-compose to test and remove db 36 | docker-test-down: .mode-test 37 | ${DOCKER_COMPOSE_CMD} down -v 38 | 39 | docker-dev-up: ## Up compose development docker images 40 | docker-dev-up: .mode-dev 41 | ${DOCKER_COMPOSE_CMD} up 42 | 43 | docker-dev-build: ## Build compose development docker images 44 | docker-dev-build: .mode-dev 45 | ${DOCKER_COMPOSE_CMD} build 46 | 47 | docker-dev-down: ## Stop development mode docker-compose 48 | docker-dev-down: .mode-dev 49 | ${DOCKER_COMPOSE_CMD} down -v 50 | 51 | docker-dev-reup: ## Down and up compose development docker images 52 | docker-dev-reup: docker-dev-down docker-dev-up 53 | 54 | docker-dev: ## Run compose in development mode with live reload 55 | docker-dev: .mode-dev 56 | ${DOCKER_COMPOSE_CMD} run --rm --name ${MODE}_run_app app make ${cmd} 57 | 58 | .mode-test: 59 | $(eval MODE=test) 60 | 61 | .mode-dev: 62 | $(eval MODE=dev) 63 | 64 | kafka-users: ## Make kafka users 65 | docker exec -it protokaf_dev_kafka /opt/kafka/bin/kafka-configs.sh \ 66 | --bootstrap-server 0.0.0.0:9092 --alter \ 67 | --add-config 'SCRAM-SHA-256=[password=secret],SCRAM-SHA-512=[password=secret]' \ 68 | --entity-type users --entity-name admin 69 | -------------------------------------------------------------------------------- /scripts/system/env.mk: -------------------------------------------------------------------------------- 1 | GOPATH?=$(shell go env GOPATH) 2 | FIRST_GOPATH:=$(firstword $(subst :, ,$(GOPATH))) 3 | GOBIN:=$(FIRST_GOPATH)/bin 4 | GOSRC:=$(FIRST_GOPATH)/src 5 | 6 | # Delete the default suffixes 7 | .SUFFIXES: 8 | -------------------------------------------------------------------------------- /scripts/system/help.mk: -------------------------------------------------------------------------------- 1 | help: pad = 24 # padding for two columns 2 | help: ## Show this help 3 | @echo 4 | @echo "Project ${APP_NAME}" 5 | @echo 6 | @echo "Commands:" 7 | @fgrep -h "##" $(MAKEFILE_LIST) \ 8 | | fgrep -v fgrep \ 9 | | sed -e 's/^/ /' -e 's/:/ /' -e 's/ //g' \ 10 | | sort -k 1 \ 11 | | grep -v '^ #' \ 12 | | awk -F "#" '{printf ("%s% *s%s\n", $$1, $(pad)-length($$1), "", $$3)}' 13 | @echo 14 | 15 | .PHONY: help 16 | --------------------------------------------------------------------------------