├── .gitignore
├── .vscode
└── launch.json
├── README.md
├── _deploy_to_k8s
├── Dockerfile
├── README.MD
├── deployment.yml
├── krakend.json
└── service.yml
├── _file_server
└── jwk
│ └── symmetric.json
├── _grafana
├── Dockerfile
├── dashboards
│ └── all.yml
├── datasources
│ └── all.yaml
└── krakend
│ └── dashboard.json
├── _img
├── containers.JPG
└── postman_collection.JPG
├── _pacts
└── order-service-customer-service.json
├── _postman_collection
└── Go_Microservices_KrakenD.postman_collection.json
├── _script
├── stop_all_win.bat
└── wait-for-it.sh
├── api_gateway
└── krakend.json
├── db_all.sql
├── docker-compose-infra-only.yml
├── docker-compose.yml
├── go.sum864878837.tmp
├── services.customer
├── Dockerfile
├── README.MD
├── go.mod
├── go.sum
└── src
│ ├── app.env
│ ├── entity
│ ├── basket.go
│ ├── basket_item.go
│ ├── customer.go
│ └── product.go
│ ├── event
│ └── order_created.go
│ ├── event_handler
│ ├── order_completed_handler.go
│ ├── product_created_handler.go
│ └── user_created_handler.go
│ ├── internal
│ ├── api.go
│ ├── handler.go
│ ├── repository.go
│ ├── service.go
│ └── verify_contract_test.go
│ ├── kafka
│ └── consumer.go
│ └── main.go
├── services.identity
├── Dockerfile
├── README.MD
├── go.mod
├── go.sum
└── src
│ ├── app.env
│ ├── entity
│ └── user.go
│ ├── event
│ └── user_created.go
│ ├── internal
│ ├── api.go
│ ├── handler.go
│ ├── repository.go
│ └── service.go
│ ├── jwt
│ └── jwt.go
│ ├── main.go
│ └── services-identity
├── services.notification
├── Dockerfile
├── README.MD
├── go.mod
├── go.sum
└── src
│ ├── app.env
│ ├── kafka
│ └── consumer.go
│ └── main.go
├── services.order
├── Dockerfile
├── README.MD
├── go.mod
├── go.sum
└── src
│ ├── app.env
│ ├── dto
│ └── basket_item_dto.go
│ ├── entity
│ ├── order.go
│ └── order_item.go
│ ├── event
│ └── order_created.go
│ ├── event_handler
│ ├── product_reserve_failed_handler.go
│ └── product_reserved_handler.go
│ ├── http_client
│ ├── customer_contract_test.go
│ └── customer_http_client.go
│ ├── internal
│ ├── api.go
│ ├── handler.go
│ ├── repository.go
│ └── service.go
│ ├── kafka
│ └── consumer.go
│ └── main.go
├── services.product
├── Dockerfile
├── README.MD
├── go.mod
├── go.sum
└── src
│ ├── app.env
│ ├── entity
│ └── product.go
│ ├── event
│ ├── order_created.go
│ └── product_created.go
│ ├── event_handler
│ └── order_created_handler.go
│ ├── internal
│ ├── api.go
│ ├── handler.go
│ ├── repository.go
│ └── service.go
│ ├── kafka
│ └── consumer.go
│ └── main.go
└── shared
├── config
└── config.go
├── go.mod
├── go.sum
├── kafka
└── kafka.go
└── server
└── server.go
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, built with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 |
14 | # Dependency directories (remove the comment below to include it)
15 | # vendor/
16 |
17 | __debug_bin
18 |
19 | *.log
20 | pact.log
21 | _logs/
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "identity",
9 | "type": "go",
10 | "request": "launch",
11 | "mode": "auto",
12 | "program": "${workspaceFolder}\\services.identity\\src",
13 | "port": 8081,
14 | "env": {},
15 | "args": []
16 | },
17 | {
18 | "name": "customer",
19 | "type": "go",
20 | "request": "launch",
21 | "mode": "auto",
22 | "program": "${workspaceFolder}\\services.customer\\src",
23 | "port": 8082,
24 | "env": {},
25 | "args": []
26 | },
27 | {
28 | "name": "product",
29 | "type": "go",
30 | "request": "launch",
31 | "mode": "auto",
32 | "program": "${workspaceFolder}\\services.product\\src",
33 | "port": 8083,
34 | "env": {},
35 | "args": []
36 | },
37 | {
38 | "name": "notification",
39 | "type": "go",
40 | "request": "launch",
41 | "mode": "auto",
42 | "program": "${workspaceFolder}\\services.notification\\src",
43 | "port": 8084,
44 | "env": {},
45 | "args": []
46 | },
47 | {
48 | "name": "order",
49 | "type": "go",
50 | "request": "launch",
51 | "mode": "auto",
52 | "program": "${workspaceFolder}\\services.order\\src",
53 | "port": 8085,
54 | "env": {},
55 | "args": []
56 | }
57 | ],
58 | "compounds": [
59 | {
60 | "name": "All",
61 | "configurations": [
62 | "identity",
63 | "customer",
64 | "product",
65 | "notification",
66 | "order"
67 | ]
68 | }
69 | ]
70 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Go Restful Microservices and KrakenD API Gateway Workshop
2 |
3 | This is a shopping basket workshop that shows how to use KrakenD API Gateway.
4 |
5 | Consist of 5 microservice and API Gateway.
6 |
7 | Token based Authentication.
8 |
9 | Raw access token is created by the Identity Service. (json model)
10 |
11 | This raw access token is signed and verified by KrakendD API Gateway. (bearer token)
12 |
13 | Services communicate with each other asynchronously through Kafka.
14 |
15 | Order Service makes an HTTP Request to Customer Service to get basket items.
16 |
17 | Customer Service keeps a local copy of a subset of user and product data.
18 |
19 | ## Run in Release Mode & Test
20 |
21 | * Run **'docker-compose up'** in the root directory and wait for all containers to get ready.
22 |
23 | * Execute **db_all.sql** to create databases of microservices when the Postgres container get ready. Make sure all DBs are created. (tables will be created by auto-migration)
24 |
25 | * 13 containers will be created. Service containers (customer_service, identity_service, etc.) need to be started manually due to the need to wait for the Postgres container to get ready to accept DB connections. (wait-for-it.sh is used for the only Kafka for waiting zookeeper to get ready.)
26 |
27 | * Use the Postman collection to play with KrakenD. ( _postman_collection/Go_Microservices_KrakenD.postman_collection.json )
28 |
29 | ## Containers
30 |
31 |
32 |
33 | ## Postman Collection
34 |
35 | Use **KrakenD-*** requests to play with KrakenD API Gateway. If you want to debug an individual service, run the service in debug mode and use the requests under the Direct Access folder as shown below.
36 |
37 |
38 |
39 | ## Run in Debug Mode
40 |
41 | In debug mode, you skip KrakenD, so KrakenD is not going to sign the bearer token and not going to inject in the request header as the **'User_Id'** key. You need to add the **'User_Id'** key as a request header for 'Add to Basket' and 'Create Order' requests. (Check the postman collection requests which are under the 'Direct Access' folder. )
42 |
43 | For VsCode Users: Select **'All'** configuration on Debug panel and start debugging. All services will be up and running in debug mode.
44 |
45 | ## Tool Set
46 |
47 | * Go
48 | * GORM
49 | * Gin
50 | * Sarama
51 | * Viper
52 | * jwt-go
53 | * pact-go
54 | * KrakenD API Gateway
55 | * Lwan HTTP/File Server
56 | * PostgreSQL
57 | * Kafka - Zookeeper
58 | * Docker - Docker Compose
59 |
--------------------------------------------------------------------------------
/_deploy_to_k8s/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM devopsfaith/krakend:latest
2 |
3 | COPY krakend.json /etc/krakend/krakend.json
--------------------------------------------------------------------------------
/_deploy_to_k8s/README.MD:
--------------------------------------------------------------------------------
1 | ## Using Mininkube on Windows
2 |
3 | ### Service deploy using kubectl
4 |
5 | 0- Start bash
6 |
7 | 1- eval $(minikube docker-env) // using minikube registery instead of docker desktop.
8 |
9 | 2- docker build -t go_identity_minikube -f services.identity/Dockerfile .
10 |
11 | 3- kubectl run go-identity --image=go_identity_minikube:latest --port=8081 --image-pull-policy='Never'
12 |
13 | 4- kubectl expose deployment go-identity --type=NodePort
14 |
15 | ### KrakenD deploy using kubectl
16 |
17 | 0- cd _deploy_to_k8s
18 |
19 | 1- docker build -t k8s-krakend -f Dockerfile .
20 |
21 | 2- Create deployment and service ymls.
22 |
23 | 3- kubectl create -f deployment.yaml
24 |
25 | 4- kubectl create -f service.yaml
--------------------------------------------------------------------------------
/_deploy_to_k8s/deployment.yml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: krakend-deployment
5 | spec:
6 | selector:
7 | matchLabels:
8 | app: krakend
9 | replicas: 2
10 | template:
11 | metadata:
12 | labels:
13 | app: krakend
14 | spec:
15 | containers:
16 | - name: krakend
17 | image: k8s-krakend
18 | ports:
19 | - containerPort: 8080
20 | imagePullPolicy: Never
21 | command: [ "/usr/bin/krakend" ]
22 | args: [ "run", "-d", "-c", "/etc/krakend/krakend.json", "-p", "8080" ]
--------------------------------------------------------------------------------
/_deploy_to_k8s/krakend.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 2,
3 | "timeout": "3000ms",
4 | "cache_ttl": "300s",
5 | "output_encoding": "json",
6 | "name": "go-microservices",
7 | "port": 5000,
8 | "extra_config": {
9 | "github_com/devopsfaith/krakend-opencensus": {
10 | "sample_rate": 100,
11 | "reporting_period": 1,
12 | "exporters": {
13 | "jaeger": {
14 | "endpoint": "http://jaeger:14268/api/traces",
15 | "service_name": "krakend"
16 | }
17 | }
18 | },
19 | "github_com/devopsfaith/krakend-gologging": {
20 | "level": "ERROR",
21 | "prefix": "[KRAKEND]",
22 | "syslog": false,
23 | "stdout": true,
24 | "format": "default"
25 | }
26 | },
27 | "endpoints": [
28 | {
29 | "endpoint": "/identity-api/sign-in",
30 | "method": "POST",
31 | "backend": [
32 | {
33 | "url_pattern": "/api/identity/sign-in",
34 | "method": "POST",
35 | "host": [
36 | "http://identity-service:8081"
37 | ],
38 | "extra_config": {
39 | "github.com/devopsfaith/krakend/http": {
40 | "return_error_details": "identity"
41 | }
42 | }
43 | }
44 | ],
45 | "extra_config": {
46 | "github.com/devopsfaith/krakend-jose/signer": {
47 | "alg": "HS256",
48 | "kid": "sim1",
49 | "keys-to-sign": [
50 | "access_token"
51 | ],
52 | "jwk-url": "http://file_server:8080/jwk/symmetric.json",
53 | "disable_jwk_security": true
54 | }
55 | }
56 | },
57 | {
58 | "endpoint": "/identity-api/sign-up",
59 | "method": "POST",
60 | "backend": [
61 | {
62 | "url_pattern": "/api/identity/sign-up",
63 | "method": "POST",
64 | "host": [
65 | "http://identity-service:8081"
66 | ],
67 | "extra_config": {
68 | "github.com/devopsfaith/krakend/http": {
69 | "return_error_details": "identity"
70 | }
71 | }
72 | }
73 | ],
74 | "extra_config": {}
75 | },
76 | {
77 | "endpoint": "/customer-api",
78 | "headers_to_pass": [
79 | "Authorization"
80 | ],
81 | "method": "GET",
82 | "output_encoding": "json",
83 | "extra_config": {
84 | "github.com/devopsfaith/krakend-jose/validator": {
85 | "alg": "HS256",
86 | "audience": [
87 | "http://krakend:5000"
88 | ],
89 | "issuer": "http://identity-service:8081",
90 | "jwk-url": "http://file_server:8080/jwk/symmetric.json",
91 | "disable_jwk_security": true
92 | },
93 | "github.com/devopsfaith/krakend-ratelimit/juju/router": {
94 | "maxRate": 1,
95 | "clientMaxRate": 1,
96 | "strategy": "ip"
97 | }
98 | },
99 | "backend": [
100 | {
101 | "url_pattern": "/api/customers",
102 | "encoding": "json",
103 | "sd": "static",
104 | "host": [
105 | "http://customer-service:8082"
106 | ],
107 | "mapping": {
108 | "collection": "customers"
109 | },
110 | "disable_host_sanitize": true,
111 | "is_collection": true,
112 | "target": "",
113 | "extra_config": {
114 | "github.com/devopsfaith/krakend/http": {
115 | "return_error_details": "customer"
116 | }
117 | }
118 | }
119 | ]
120 | },
121 | {
122 | "endpoint": "/customer-api/{id}",
123 | "headers_to_pass": [
124 | "Authorization"
125 | ],
126 | "method": "GET",
127 | "output_encoding": "json",
128 | "extra_config": {
129 | "github.com/devopsfaith/krakend-jose/validator": {
130 | "alg": "HS256",
131 | "audience": [
132 | "http://krakend:5000"
133 | ],
134 | "issuer": "http://identity-service:8081",
135 | "jwk-url": "http://file_server:8080/jwk/symmetric.json",
136 | "disable_jwk_security": true
137 | }
138 | },
139 | "backend": [
140 | {
141 | "url_pattern": "/api/customers/{id}",
142 | "encoding": "json",
143 | "sd": "static",
144 | "host": [
145 | "http://customer-service:8082"
146 | ],
147 | "disable_host_sanitize": true,
148 | "extra_config": {
149 | "github.com/devopsfaith/krakend/http": {
150 | "return_error_details": "customer"
151 | }
152 | }
153 | }
154 | ]
155 | },
156 | {
157 | "endpoint": "/customer-api/basket",
158 | "headers_to_pass": [
159 | "Authorization",
160 | "user_id"
161 | ],
162 | "method": "POST",
163 | "output_encoding": "json",
164 | "extra_config": {
165 | "github.com/devopsfaith/krakend-jose/validator": {
166 | "alg": "HS256",
167 | "audience": [
168 | "http://krakend:5000"
169 | ],
170 | "issuer": "http://identity-service:8081",
171 | "jwk-url": "http://file_server:8080/jwk/symmetric.json",
172 | "disable_jwk_security": true,
173 | "propagate-claims": [
174 | [
175 | "user_id",
176 | "user_id"
177 | ]
178 | ]
179 | }
180 | },
181 | "backend": [
182 | {
183 | "url_pattern": "/api/customer-basket",
184 | "encoding": "json",
185 | "sd": "static",
186 | "method": "POST",
187 | "host": [
188 | "http://customer-service:8082"
189 | ],
190 | "disable_host_sanitize": true,
191 | "extra_config": {
192 | "github.com/devopsfaith/krakend/http": {
193 | "return_error_details": "customer"
194 | }
195 | }
196 | }
197 | ]
198 | },
199 | {
200 | "endpoint": "/product-api",
201 | "headers_to_pass": [
202 | "Authorization"
203 | ],
204 | "method": "POST",
205 | "output_encoding": "json",
206 | "extra_config": {
207 | "github.com/devopsfaith/krakend-jose/validator": {
208 | "alg": "HS256",
209 | "audience": [
210 | "http://krakend:5000"
211 | ],
212 | "issuer": "http://identity-service:8081",
213 | "jwk-url": "http://file_server:8080/jwk/symmetric.json",
214 | "disable_jwk_security": true
215 | }
216 | },
217 | "backend": [
218 | {
219 | "url_pattern": "/api/products",
220 | "encoding": "json",
221 | "sd": "static",
222 | "method": "POST",
223 | "host": [
224 | "http://product-service:8083"
225 | ],
226 | "disable_host_sanitize": true,
227 | "extra_config": {
228 | "github.com/devopsfaith/krakend/http": {
229 | "return_error_details": "customer"
230 | }
231 | }
232 | }
233 | ]
234 | },
235 | {
236 | "endpoint": "/product-api",
237 | "headers_to_pass": [
238 | "Authorization"
239 | ],
240 | "method": "GET",
241 | "output_encoding": "json",
242 | "extra_config": {
243 | "github.com/devopsfaith/krakend-jose/validator": {
244 | "alg": "HS256",
245 | "audience": [
246 | "http://krakend:5000"
247 | ],
248 | "issuer": "http://identity-service:8081",
249 | "jwk-url": "http://file_server:8080/jwk/symmetric.json",
250 | "disable_jwk_security": true
251 | }
252 | },
253 | "backend": [
254 | {
255 | "url_pattern": "/api/products",
256 | "encoding": "json",
257 | "sd": "static",
258 | "host": [
259 | "http://product-service:8083"
260 | ],
261 | "mapping": {
262 | "collection": "products"
263 | },
264 | "disable_host_sanitize": true,
265 | "is_collection": true,
266 | "target": "",
267 | "extra_config": {
268 | "github.com/devopsfaith/krakend/http": {
269 | "return_error_details": "product"
270 | }
271 | }
272 | }
273 | ]
274 | },
275 | {
276 | "endpoint": "/order-api",
277 | "headers_to_pass": [
278 | "Authorization",
279 | "user_id"
280 | ],
281 | "method": "POST",
282 | "output_encoding": "json",
283 | "extra_config": {
284 | "github.com/devopsfaith/krakend-jose/validator": {
285 | "alg": "HS256",
286 | "audience": [
287 | "http://krakend:5000"
288 | ],
289 | "issuer": "http://identity-service:8081",
290 | "jwk-url": "http://file_server:8080/jwk/symmetric.json",
291 | "disable_jwk_security": true,
292 | "propagate-claims": [
293 | [
294 | "user_id",
295 | "user_id"
296 | ]
297 | ]
298 | }
299 | },
300 | "backend": [
301 | {
302 | "url_pattern": "/api/orders",
303 | "encoding": "json",
304 | "sd": "static",
305 | "method": "POST",
306 | "host": [
307 | "http://order-service:8085"
308 | ],
309 | "disable_host_sanitize": true,
310 | "extra_config": {
311 | "github.com/devopsfaith/krakend/http": {
312 | "return_error_details": "customer"
313 | }
314 | }
315 | }
316 | ]
317 | }
318 | ]
319 | }
--------------------------------------------------------------------------------
/_deploy_to_k8s/service.yml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: krakend-service
5 | spec:
6 | type: NodePort
7 | ports:
8 | - name: http
9 | port: 8000
10 | targetPort: 8080
11 | protocol: TCP
12 | selector:
13 | app: krakend
--------------------------------------------------------------------------------
/_file_server/jwk/symmetric.json:
--------------------------------------------------------------------------------
1 | {
2 | "keys": [
3 | {
4 | "kty": "oct",
5 | "alg": "HS256",
6 | "k": "dGVzdFNlY3JldA",
7 | "kid": "sim1"
8 | }
9 | ]
10 | }
--------------------------------------------------------------------------------
/_grafana/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM grafana/grafana:latest
2 |
3 | COPY ./datasources /etc/grafana/provisioning/datasources
4 | COPY ./dashboards /etc/grafana/provisioning/dashboards
5 | COPY ./krakend /var/lib/grafana/dashboards/krakend
--------------------------------------------------------------------------------
/_grafana/dashboards/all.yml:
--------------------------------------------------------------------------------
1 | apiVersion: 1
2 |
3 | providers:
4 | - name: 'default'
5 | orgId: 1
6 | folder: ''
7 | type: file
8 | disableDeletion: false
9 | updateIntervalSeconds: 60
10 | options:
11 | path: /var/lib/grafana/dashboards/krakend
--------------------------------------------------------------------------------
/_grafana/datasources/all.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: 1
2 |
3 | datasources:
4 | - access: 'proxy'
5 | editable: true
6 | is_default: true
7 | name: 'influx'
8 | org_id: 1
9 | type: 'influxdb'
10 | url: 'http://influxdb:8086'
11 | version: 1
12 | user: krakend-dev
13 | password: pas5w0rd
14 | database: krakend
15 |
--------------------------------------------------------------------------------
/_img/containers.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suadev/go-microservices-and-krakend-api-gateway/3d9c321f60b883fd3814fc839f9cbef67c3b3633/_img/containers.JPG
--------------------------------------------------------------------------------
/_img/postman_collection.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suadev/go-microservices-and-krakend-api-gateway/3d9c321f60b883fd3814fc839f9cbef67c3b3633/_img/postman_collection.JPG
--------------------------------------------------------------------------------
/_pacts/order-service-customer-service.json:
--------------------------------------------------------------------------------
1 | {
2 | "consumer": {
3 | "name": "order-service"
4 | },
5 | "provider": {
6 | "name": "customer-service"
7 | },
8 | "interactions": [
9 | {
10 | "description": "A GET request to retrieve customer basket items.",
11 | "providerState": "There is an available basket for the customer",
12 | "request": {
13 | "method": "GET",
14 | "path": "/customers/e153ef59-5708-48a4-848b-a65bd2667ac4/basketItems",
15 | "matchingRules": {
16 | "$.path": {
17 | "match": "regex",
18 | "regex": "\\/customers\\/[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}\\/basketItems"
19 | }
20 | }
21 | },
22 | "response": {
23 | "status": 200,
24 | "headers": {
25 | "Content-Type": "application/json; charset=utf-8"
26 | },
27 | "body": [
28 | {
29 | "ID": "e153ef59-5708-48a4-848b-a65bd2667ac4",
30 | "BasketID": "e153ef59-5708-48a4-848b-a65bd2667ac4",
31 | "ProductID": "e153ef59-5708-48a4-848b-a65bd2667ac4",
32 | "ProductName": "Sample Product",
33 | "UnitPrice": 5,
34 | "Quantity": 1,
35 | "CreatedAt": "0001-01-01T00:00:00Z",
36 | "UpdatedAt": "0001-01-01T00:00:00Z"
37 | }
38 | ],
39 | "matchingRules": {
40 | "$.headers.Content-Type": {
41 | "match": "regex",
42 | "regex": "application\\/json"
43 | },
44 | "$.body": {
45 | "match": "type"
46 | }
47 | }
48 | }
49 | }
50 | ],
51 | "metadata": {
52 | "pactSpecification": {
53 | "version": "2.0.0"
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/_postman_collection/Go_Microservices_KrakenD.postman_collection.json:
--------------------------------------------------------------------------------
1 | {
2 | "info": {
3 | "_postman_id": "0a4b17aa-5d8e-4192-9fc6-a3ac07f18ebc",
4 | "name": "Go_Microservices_KrakenD",
5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
6 | },
7 | "item": [
8 | {
9 | "name": "Direct Access",
10 | "item": [
11 | {
12 | "name": "0-Register User",
13 | "request": {
14 | "method": "POST",
15 | "header": [],
16 | "body": {
17 | "mode": "raw",
18 | "raw": "{\r\n\t\"FirstName\" :\"kose\",\r\n\t\"LastName\" :\"suat\",\r\n \"Email\": \"suat@gmail.com\",\r\n \"Password\": \"12345\"\r\n}",
19 | "options": {
20 | "raw": {
21 | "language": "json"
22 | }
23 | }
24 | },
25 | "url": {
26 | "raw": "http://localhost:8081/api/identity/sign-up",
27 | "protocol": "http",
28 | "host": [
29 | "localhost"
30 | ],
31 | "port": "8081",
32 | "path": [
33 | "api",
34 | "identity",
35 | "sign-up"
36 | ]
37 | }
38 | },
39 | "response": []
40 | },
41 | {
42 | "name": "1-Get Token",
43 | "request": {
44 | "method": "POST",
45 | "header": [
46 | {
47 | "key": "Content-Type",
48 | "name": "Content-Type",
49 | "type": "text",
50 | "value": "application/json"
51 | }
52 | ],
53 | "body": {
54 | "mode": "raw",
55 | "raw": "{\n\t\"Email\" :\"suat@gmail.com\",\n\t\"Password\" :\"12345\"\n}"
56 | },
57 | "url": {
58 | "raw": "http://localhost:8081/api/identity/sign-in",
59 | "protocol": "http",
60 | "host": [
61 | "localhost"
62 | ],
63 | "port": "8081",
64 | "path": [
65 | "api",
66 | "identity",
67 | "sign-in"
68 | ]
69 | }
70 | },
71 | "response": []
72 | },
73 | {
74 | "name": "2-Create Product",
75 | "request": {
76 | "auth": {
77 | "type": "bearer",
78 | "bearer": [
79 | {
80 | "key": "token",
81 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6InN1YWRldkBnbWFpbC5jb20iLCJ1c2VyX2lkIjoiZjNhY2QyZDEtMGRkZC00MGE4LWI3NTgtODM0MmFmYWIxNzRjIiwibmJmIjoxNjExNDg3NjEyLCJleHAiOjE2MTE1NzQwMTIsImlhdCI6MTYxMTQ4NzYxMiwiaXNzIjoibG9jYWxob3N0IiwiYXVkIjoiU3VhdCBLw5ZTRSJ9.uvWueOqDmlPUKPWnnDWNPPId6qdu-mAhTIaQSbhXkeE",
82 | "type": "string"
83 | }
84 | ]
85 | },
86 | "method": "POST",
87 | "header": [
88 | {
89 | "key": "Content-Type",
90 | "name": "Content-Type",
91 | "type": "text",
92 | "value": "application/json",
93 | "disabled": true
94 | },
95 | {
96 | "key": "Authorization",
97 | "type": "text",
98 | "value": "bearer eyJhbGciOiJIUasdsdasdd2l6bG8uYXBpLmRlbW8iLCJhdWQiOiJTb21lQ3VzdG9tQXBwIn0.ea5jAqsU8pI4kQwLQUPgo17au4fVrH9VKHyt8wt_TVA",
99 | "disabled": true
100 | }
101 | ],
102 | "body": {
103 | "mode": "raw",
104 | "raw": "{\n \"name\": \"üzüm\",\n \"quantity\": 100,\n \"price\": 30\n}",
105 | "options": {
106 | "raw": {
107 | "language": "json"
108 | }
109 | }
110 | },
111 | "url": {
112 | "raw": "http://localhost:8083/api/products",
113 | "protocol": "http",
114 | "host": [
115 | "localhost"
116 | ],
117 | "port": "8083",
118 | "path": [
119 | "api",
120 | "products"
121 | ]
122 | }
123 | },
124 | "response": []
125 | },
126 | {
127 | "name": "3-Add To Basket",
128 | "request": {
129 | "auth": {
130 | "type": "bearer",
131 | "bearer": [
132 | {
133 | "key": "token",
134 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6InN1YWRldkBnbWFpbC5jb20iLCJ1c2VyX2lkIjoiZjNhY2QyZDEtMGRkZC00MGE4LWI3NTgtODM0MmFmYWIxNzRjIiwibmJmIjoxNjExNDg3NjEyLCJleHAiOjE2MTE1NzQwMTIsImlhdCI6MTYxMTQ4NzYxMiwiaXNzIjoibG9jYWxob3N0IiwiYXVkIjoiU3VhdCBLw5ZTRSJ9.uvWueOqDmlPUKPWnnDWNPPId6qdu-mAhTIaQSbhXkeE",
135 | "type": "string"
136 | }
137 | ]
138 | },
139 | "method": "POST",
140 | "header": [
141 | {
142 | "key": "Content-Type",
143 | "name": "Content-Type",
144 | "type": "text",
145 | "value": "application/json"
146 | },
147 | {
148 | "key": "User_Id",
149 | "value": "faa2e39d-e751-4a7b-b687-dabb764cec97",
150 | "type": "text"
151 | }
152 | ],
153 | "body": {
154 | "mode": "raw",
155 | "raw": "{\n\t\"ProductId\" :\"4398b878-e28f-4bbb-8539-1527515d345e\",\n\t\"Quantity\":1\n}"
156 | },
157 | "url": {
158 | "raw": "http://localhost:8082/api/customer-basket",
159 | "protocol": "http",
160 | "host": [
161 | "localhost"
162 | ],
163 | "port": "8082",
164 | "path": [
165 | "api",
166 | "customer-basket"
167 | ]
168 | }
169 | },
170 | "response": []
171 | },
172 | {
173 | "name": "4-Create Order",
174 | "request": {
175 | "auth": {
176 | "type": "bearer",
177 | "bearer": [
178 | {
179 | "key": "token",
180 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6InN1YWRldkBnbWFpbC5jb20iLCJ1c2VyX2lkIjoiZjNhY2QyZDEtMGRkZC00MGE4LWI3NTgtODM0MmFmYWIxNzRjIiwibmJmIjoxNjExNDg3NjEyLCJleHAiOjE2MTE1NzQwMTIsImlhdCI6MTYxMTQ4NzYxMiwiaXNzIjoibG9jYWxob3N0IiwiYXVkIjoiU3VhdCBLw5ZTRSJ9.uvWueOqDmlPUKPWnnDWNPPId6qdu-mAhTIaQSbhXkeE",
181 | "type": "string"
182 | }
183 | ]
184 | },
185 | "method": "POST",
186 | "header": [
187 | {
188 | "key": "Content-Type",
189 | "name": "Content-Type",
190 | "value": "application/json",
191 | "type": "text"
192 | },
193 | {
194 | "key": "Authorization",
195 | "value": "bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6InN1YWRldkBnbWFpbC5jb20iLCJDdXN0b21lcklkIjoiMTZmZmQ0NjMtMzE5ZC00MGEyLWJlNjYtYjg0ODdhYjI1ZmFhIiwibmJmIjoxNTUzODgwNjI0LCJleHAiOjE1NTM5NjcwMjQsImlhdCI6MTU1Mzg4MDYyNCwiaXNzIjoid2l6bG8uYXBpLmRlbW8iLCJhdWQiOiJTb21lQ3VzdG9tQXBwIn0.ea5jAqsU8pI4kQwLQUPgo17au4fVrH9VKHyt8wt_TVA",
196 | "type": "text"
197 | }
198 | ],
199 | "body": {
200 | "mode": "raw",
201 | "raw": "{}"
202 | },
203 | "url": {
204 | "raw": "http://localhost:8085/api/orders",
205 | "protocol": "http",
206 | "host": [
207 | "localhost"
208 | ],
209 | "port": "8085",
210 | "path": [
211 | "api",
212 | "orders"
213 | ]
214 | }
215 | },
216 | "response": []
217 | },
218 | {
219 | "name": "Get Customers",
220 | "request": {
221 | "method": "GET",
222 | "header": [],
223 | "url": {
224 | "raw": "http://localhost:8082/api/customers",
225 | "protocol": "http",
226 | "host": [
227 | "localhost"
228 | ],
229 | "port": "8082",
230 | "path": [
231 | "api",
232 | "customers"
233 | ]
234 | }
235 | },
236 | "response": []
237 | },
238 | {
239 | "name": "Get Customer",
240 | "request": {
241 | "method": "GET",
242 | "header": [],
243 | "url": {
244 | "raw": "http://localhost:8082/api/customers/748d48f15-2463-4f6d-bd8d-9147bef107e1",
245 | "protocol": "http",
246 | "host": [
247 | "localhost"
248 | ],
249 | "port": "8082",
250 | "path": [
251 | "api",
252 | "customers",
253 | "748d48f15-2463-4f6d-bd8d-9147bef107e1"
254 | ]
255 | }
256 | },
257 | "response": []
258 | },
259 | {
260 | "name": "Get Products",
261 | "request": {
262 | "method": "GET",
263 | "header": [],
264 | "url": {
265 | "raw": "http://localhost:8083/api/products",
266 | "protocol": "http",
267 | "host": [
268 | "localhost"
269 | ],
270 | "port": "8083",
271 | "path": [
272 | "api",
273 | "products"
274 | ]
275 | }
276 | },
277 | "response": []
278 | }
279 | ]
280 | },
281 | {
282 | "name": "0-KrakenD Register User",
283 | "request": {
284 | "method": "POST",
285 | "header": [],
286 | "body": {
287 | "mode": "raw",
288 | "raw": "{\r\n\t\"FirstName\" :\"kose\",\r\n\t\"LastName\" :\"suat\",\r\n \"Email\": \"suat2@gmail.com\",\r\n \"Password\": \"12345\"\r\n}",
289 | "options": {
290 | "raw": {
291 | "language": "json"
292 | }
293 | }
294 | },
295 | "url": {
296 | "raw": "http://localhost:5000/identity-api/sign-up",
297 | "protocol": "http",
298 | "host": [
299 | "localhost"
300 | ],
301 | "port": "5000",
302 | "path": [
303 | "identity-api",
304 | "sign-up"
305 | ]
306 | }
307 | },
308 | "response": []
309 | },
310 | {
311 | "name": "1-KrakenD Get Token",
312 | "request": {
313 | "method": "POST",
314 | "header": [
315 | {
316 | "key": "Content-Type",
317 | "name": "Content-Type",
318 | "type": "text",
319 | "value": "application/json"
320 | }
321 | ],
322 | "body": {
323 | "mode": "raw",
324 | "raw": "{\n\t\"Email\" :\"suat@gmail.com\",\n\t\"Password\" :\"12345\"\n}"
325 | },
326 | "url": {
327 | "raw": "http://localhost:5000/identity-api/sign-in",
328 | "protocol": "http",
329 | "host": [
330 | "localhost"
331 | ],
332 | "port": "5000",
333 | "path": [
334 | "identity-api",
335 | "sign-in"
336 | ]
337 | }
338 | },
339 | "response": []
340 | },
341 | {
342 | "name": "2-KrakenD Create Product",
343 | "request": {
344 | "auth": {
345 | "type": "bearer",
346 | "bearer": [
347 | {
348 | "key": "token",
349 | "value": "eyJhbGciOiJIUzI1NiIsImtpZCI6InNpbTEifQ.eyJhdWQiOiJodHRwOi8va3Jha2VuZDo1MDAwIiwiZXhwIjoxNjE1NDkzNjcwLCJpc3MiOiJodHRwOi8vaWRlbnRpdHktc2VydmljZTo4MDgxIiwidXNlcl9pZCI6ImZhYTJlMzlkLWU3NTEtNGE3Yi1iNjg3LWRhYmI3NjRjZWM5NyJ9.PBj74QhAehnmaVDgGUtTHIGTLbO-0qhuhMR7gR6rRtI",
350 | "type": "string"
351 | }
352 | ]
353 | },
354 | "method": "POST",
355 | "header": [
356 | {
357 | "key": "Content-Type",
358 | "name": "Content-Type",
359 | "type": "text",
360 | "value": "application/json",
361 | "disabled": true
362 | },
363 | {
364 | "key": "Authorization",
365 | "type": "text",
366 | "value": "bearer eyJhbGciOiJIUasdsdasdd2l6bG8uYXBpLmRlbW8iLCJhdWQiOiJTb21lQ3VzdG9tQXBwIn0.ea5jAqsU8pI4kQwLQUPgo17au4fVrH9VKHyt8wt_TVA",
367 | "disabled": true
368 | }
369 | ],
370 | "body": {
371 | "mode": "raw",
372 | "raw": "{\n \"name\": \"patates\",\n \"quantity\": 100,\n \"price\": 30\n}",
373 | "options": {
374 | "raw": {
375 | "language": "json"
376 | }
377 | }
378 | },
379 | "url": {
380 | "raw": "http://localhost:5000/product-api",
381 | "protocol": "http",
382 | "host": [
383 | "localhost"
384 | ],
385 | "port": "5000",
386 | "path": [
387 | "product-api"
388 | ]
389 | }
390 | },
391 | "response": []
392 | },
393 | {
394 | "name": "3-KrakenD Add To Basket",
395 | "request": {
396 | "auth": {
397 | "type": "noauth"
398 | },
399 | "method": "POST",
400 | "header": [
401 | {
402 | "key": "Content-Type",
403 | "name": "Content-Type",
404 | "type": "text",
405 | "value": "application/json"
406 | },
407 | {
408 | "key": "Authorization",
409 | "type": "text",
410 | "value": "bearer eyJhbGciOiJIUzI1NiIsImtpZCI6InNpbTEifQ.eyJhdWQiOiJodHRwOi8va3Jha2VuZDo1MDAwIiwiZXhwIjoxNjE1NDQ1MTEyLCJpc3MiOiJodHRwOi8vaWRlbnRpdHktc2VydmljZTo4MDgxIiwidXNlcl9pZCI6ImZhYTJlMzlkLWU3NTEtNGE3Yi1iNjg3LWRhYmI3NjRjZWM5NyJ9.yEJUO-78CEkNyRUeGO7qE0Ljz_IGXTthdY2lO1JOQ24"
411 | }
412 | ],
413 | "body": {
414 | "mode": "raw",
415 | "raw": "{\n\t\"ProductId\" :\"4398b878-e28f-4bbb-8539-1527515d345e\",\n\t\"Quantity\":1\n}"
416 | },
417 | "url": {
418 | "raw": "http://localhost:5000/customer-api/basket",
419 | "protocol": "http",
420 | "host": [
421 | "localhost"
422 | ],
423 | "port": "5000",
424 | "path": [
425 | "customer-api",
426 | "basket"
427 | ]
428 | }
429 | },
430 | "response": []
431 | },
432 | {
433 | "name": "4-KrakenD Create Order",
434 | "request": {
435 | "auth": {
436 | "type": "bearer",
437 | "bearer": [
438 | {
439 | "key": "token",
440 | "value": "eyJhbGciOiJIUzI1NiIsImtpZCI6InNpbTEifQ.eyJhdWQiOiJodHRwOi8va3Jha2VuZDo1MDAwIiwiZXhwIjoxNjE1NDQ1NDUzLCJpc3MiOiJodHRwOi8vaWRlbnRpdHktc2VydmljZTo4MDgxIiwidXNlcl9pZCI6ImZhYTJlMzlkLWU3NTEtNGE3Yi1iNjg3LWRhYmI3NjRjZWM5NyJ9.bqqaRJ3YlDoJVAugiTOVFgQiOTxkg8ZaftvocSskPqw",
441 | "type": "string"
442 | }
443 | ]
444 | },
445 | "method": "POST",
446 | "header": [
447 | {
448 | "key": "Content-Type",
449 | "name": "Content-Type",
450 | "value": "application/json",
451 | "type": "text"
452 | },
453 | {
454 | "key": "Authorization",
455 | "value": "bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6InN1YWRldkBnbWFpbC5jb20iLCJDdXN0b21lcklkIjoiMTZmZmQ0NjMtMzE5ZC00MGEyLWJlNjYtYjg0ODdhYjI1ZmFhIiwibmJmIjoxNTUzODgwNjI0LCJleHAiOjE1NTM5NjcwMjQsImlhdCI6MTU1Mzg4MDYyNCwiaXNzIjoid2l6bG8uYXBpLmRlbW8iLCJhdWQiOiJTb21lQ3VzdG9tQXBwIn0.ea5jAqsU8pI4kQwLQUPgo17au4fVrH9VKHyt8wt_TVA",
456 | "type": "text"
457 | }
458 | ],
459 | "body": {
460 | "mode": "raw",
461 | "raw": "{}"
462 | },
463 | "url": {
464 | "raw": "http://localhost:5000/order-api",
465 | "protocol": "http",
466 | "host": [
467 | "localhost"
468 | ],
469 | "port": "5000",
470 | "path": [
471 | "order-api"
472 | ]
473 | }
474 | },
475 | "response": []
476 | },
477 | {
478 | "name": "KrakenD Get Customers",
479 | "request": {
480 | "auth": {
481 | "type": "bearer",
482 | "bearer": [
483 | {
484 | "key": "token",
485 | "value": "eyJhbGciOiJIUzI1NiIsImtpZCI6InNpbTEifQ.eyJhdWQiOiJodHRwOi8va3Jha2VuZDo1MDAwIiwiZXhwIjoxNjE1NDczNDIxLCJpc3MiOiJodHRwOi8vaWRlbnRpdHktc2VydmljZTo4MDgxIiwidXNlcl9pZCI6ImZhYTJlMzlkLWU3NTEtNGE3Yi1iNjg3LWRhYmI3NjRjZWM5NyJ9.7XBTrazq_4H85ap92MP0SorLUra4hJ3BvsJJMk0lW6c",
486 | "type": "string"
487 | }
488 | ]
489 | },
490 | "method": "GET",
491 | "header": [
492 | {
493 | "key": "Authorization",
494 | "value": "eyJhbGciOiJIUzI1NiIsImtpZCI6InNpbTEifQ.eyJhdWQiOiJodHRwOi8va3Jha2VuZDo1MDAwIiwiZXhwIjoxNjE1NDczNDIxLCJpc3MiOiJodHRwOi8vaWRlbnRpdHktc2VydmljZTo4MDgxIiwidXNlcl9pZCI6ImZhYTJlMzlkLWU3NTEtNGE3Yi1iNjg3LWRhYmI3NjRjZWM5NyJ9.7XBTrazq_4H85ap92MP0SorLUra4hJ3BvsJJMk0lW6c",
495 | "type": "text",
496 | "disabled": true
497 | }
498 | ],
499 | "url": {
500 | "raw": "http://localhost:5000/customer-api",
501 | "protocol": "http",
502 | "host": [
503 | "localhost"
504 | ],
505 | "port": "5000",
506 | "path": [
507 | "customer-api"
508 | ]
509 | }
510 | },
511 | "response": []
512 | },
513 | {
514 | "name": "KrakenD Get Customer",
515 | "request": {
516 | "auth": {
517 | "type": "bearer",
518 | "bearer": [
519 | {
520 | "key": "token",
521 | "value": "eyJhbGciOiJIUzI1NiIsImtpZCI6InNpbTEifQ.eyJhdWQiOiJodHRwOi8va3Jha2VuZDo1MDAwIiwiZXhwIjoxNjE1NTgxMDI3LCJpc3MiOiJodHRwOi8vaWRlbnRpdHktc2VydmljZTo4MDgxIiwidXNlcl9pZCI6IjMwODU5NzA0LTJiMGQtNDExZi04MTI0LTVlMDhlYjZhNTk3YSJ9.OyZq13RsxFA6Pnh__lDNg8RovrERXPQDH-CA8PVRX_I",
522 | "type": "string"
523 | }
524 | ]
525 | },
526 | "method": "GET",
527 | "header": [],
528 | "url": {
529 | "raw": "http://localhost:5000/customer-api/0995da54-30db-4298-a395-ed1a444c14e9",
530 | "protocol": "http",
531 | "host": [
532 | "localhost"
533 | ],
534 | "port": "5000",
535 | "path": [
536 | "customer-api",
537 | "0995da54-30db-4298-a395-ed1a444c14e9"
538 | ]
539 | }
540 | },
541 | "response": []
542 | },
543 | {
544 | "name": "KrakenD Get Products",
545 | "request": {
546 | "auth": {
547 | "type": "bearer",
548 | "bearer": [
549 | {
550 | "key": "token",
551 | "value": "eyJhbGciOiJIUzI1NiIsImtpZCI6InNpbTEifQ.eyJhdWQiOiJodHRwOi8va3Jha2VuZDo1MDAwIiwiZXhwIjoxNjE1NTgxMDI3LCJpc3MiOiJodHRwOi8vaWRlbnRpdHktc2VydmljZTo4MDgxIiwidXNlcl9pZCI6IjMwODU5NzA0LTJiMGQtNDExZi04MTI0LTVlMDhlYjZhNTk3YSJ9.OyZq13RsxFA6Pnh__lDNg8RovrERXPQDH-CA8PVRX_I",
552 | "type": "string"
553 | }
554 | ]
555 | },
556 | "method": "GET",
557 | "header": [],
558 | "url": {
559 | "raw": "http://localhost:5000/product-api",
560 | "protocol": "http",
561 | "host": [
562 | "localhost"
563 | ],
564 | "port": "5000",
565 | "path": [
566 | "product-api"
567 | ]
568 | }
569 | },
570 | "response": []
571 | }
572 | ]
573 | }
--------------------------------------------------------------------------------
/_script/stop_all_win.bat:
--------------------------------------------------------------------------------
1 | taskkill /F /IM dlv.exe
--------------------------------------------------------------------------------
/_script/wait-for-it.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # Use this script to test if a given TCP host/port are available
3 |
4 | WAITFORIT_cmdname=${0##*/}
5 |
6 | echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi }
7 |
8 | usage()
9 | {
10 | cat << USAGE >&2
11 | Usage:
12 | $WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args]
13 | -h HOST | --host=HOST Host or IP under test
14 | -p PORT | --port=PORT TCP port under test
15 | Alternatively, you specify the host and port as host:port
16 | -s | --strict Only execute subcommand if the test succeeds
17 | -q | --quiet Don't output any status messages
18 | -t TIMEOUT | --timeout=TIMEOUT
19 | Timeout in seconds, zero for no timeout
20 | -- COMMAND ARGS Execute command with args after the test finishes
21 | USAGE
22 | exit 1
23 | }
24 |
25 | wait_for()
26 | {
27 | if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then
28 | echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT"
29 | else
30 | echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout"
31 | fi
32 | WAITFORIT_start_ts=$(date +%s)
33 | while :
34 | do
35 | if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then
36 | nc -z $WAITFORIT_HOST $WAITFORIT_PORT
37 | WAITFORIT_result=$?
38 | else
39 | (echo > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1
40 | WAITFORIT_result=$?
41 | fi
42 | if [[ $WAITFORIT_result -eq 0 ]]; then
43 | WAITFORIT_end_ts=$(date +%s)
44 | echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds"
45 | break
46 | fi
47 | sleep 1
48 | done
49 | return $WAITFORIT_result
50 | }
51 |
52 | wait_for_wrapper()
53 | {
54 | # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692
55 | if [[ $WAITFORIT_QUIET -eq 1 ]]; then
56 | timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT &
57 | else
58 | timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT &
59 | fi
60 | WAITFORIT_PID=$!
61 | trap "kill -INT -$WAITFORIT_PID" INT
62 | wait $WAITFORIT_PID
63 | WAITFORIT_RESULT=$?
64 | if [[ $WAITFORIT_RESULT -ne 0 ]]; then
65 | echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT"
66 | fi
67 | return $WAITFORIT_RESULT
68 | }
69 |
70 | # process arguments
71 | while [[ $# -gt 0 ]]
72 | do
73 | case "$1" in
74 | *:* )
75 | WAITFORIT_hostport=(${1//:/ })
76 | WAITFORIT_HOST=${WAITFORIT_hostport[0]}
77 | WAITFORIT_PORT=${WAITFORIT_hostport[1]}
78 | shift 1
79 | ;;
80 | --child)
81 | WAITFORIT_CHILD=1
82 | shift 1
83 | ;;
84 | -q | --quiet)
85 | WAITFORIT_QUIET=1
86 | shift 1
87 | ;;
88 | -s | --strict)
89 | WAITFORIT_STRICT=1
90 | shift 1
91 | ;;
92 | -h)
93 | WAITFORIT_HOST="$2"
94 | if [[ $WAITFORIT_HOST == "" ]]; then break; fi
95 | shift 2
96 | ;;
97 | --host=*)
98 | WAITFORIT_HOST="${1#*=}"
99 | shift 1
100 | ;;
101 | -p)
102 | WAITFORIT_PORT="$2"
103 | if [[ $WAITFORIT_PORT == "" ]]; then break; fi
104 | shift 2
105 | ;;
106 | --port=*)
107 | WAITFORIT_PORT="${1#*=}"
108 | shift 1
109 | ;;
110 | -t)
111 | WAITFORIT_TIMEOUT="$2"
112 | if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi
113 | shift 2
114 | ;;
115 | --timeout=*)
116 | WAITFORIT_TIMEOUT="${1#*=}"
117 | shift 1
118 | ;;
119 | --)
120 | shift
121 | WAITFORIT_CLI=("$@")
122 | break
123 | ;;
124 | --help)
125 | usage
126 | ;;
127 | *)
128 | echoerr "Unknown argument: $1"
129 | usage
130 | ;;
131 | esac
132 | done
133 |
134 | if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then
135 | echoerr "Error: you need to provide a host and port to test."
136 | usage
137 | fi
138 |
139 | WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15}
140 | WAITFORIT_STRICT=${WAITFORIT_STRICT:-0}
141 | WAITFORIT_CHILD=${WAITFORIT_CHILD:-0}
142 | WAITFORIT_QUIET=${WAITFORIT_QUIET:-0}
143 |
144 | # check to see if timeout is from busybox?
145 | WAITFORIT_TIMEOUT_PATH=$(type -p timeout)
146 | WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH)
147 | if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then
148 | WAITFORIT_ISBUSY=1
149 | WAITFORIT_BUSYTIMEFLAG="-t"
150 |
151 | else
152 | WAITFORIT_ISBUSY=0
153 | WAITFORIT_BUSYTIMEFLAG=""
154 | fi
155 |
156 | if [[ $WAITFORIT_CHILD -gt 0 ]]; then
157 | wait_for
158 | WAITFORIT_RESULT=$?
159 | exit $WAITFORIT_RESULT
160 | else
161 | if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then
162 | wait_for_wrapper
163 | WAITFORIT_RESULT=$?
164 | else
165 | wait_for
166 | WAITFORIT_RESULT=$?
167 | fi
168 | fi
169 |
170 | if [[ $WAITFORIT_CLI != "" ]]; then
171 | if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then
172 | echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess"
173 | exit $WAITFORIT_RESULT
174 | fi
175 | exec "${WAITFORIT_CLI[@]}"
176 | else
177 | exit $WAITFORIT_RESULT
178 | fi
--------------------------------------------------------------------------------
/api_gateway/krakend.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 2,
3 | "timeout": "3000ms",
4 | "cache_ttl": "300s",
5 | "output_encoding": "json",
6 | "name": "go-microservices",
7 | "port": 5000,
8 | "extra_config": {
9 | "github_com/devopsfaith/krakend-opencensus": {
10 | "exporters": {
11 | "influxdb": {
12 | "address": "http://influxdb:8086",
13 | "db": "krakend",
14 | "username": "admin",
15 | "password": "password"
16 | },
17 | "jaeger": {
18 | "sample_rate": 100,
19 | "reporting_period": 1,
20 | "exporters": {
21 | "jaeger": {
22 | "endpoint": "http://jaeger:14268/api/traces",
23 | "service_name": "krakend"
24 | }
25 | }
26 | }
27 | }
28 | },
29 | "github_com/devopsfaith/krakend-gologging": {
30 | "level": "ERROR",
31 | "prefix": "[KRAKEND]",
32 | "syslog": false,
33 | "stdout": true,
34 | "format": "default"
35 | },
36 | "github_com/devopsfaith/krakend-metrics": {
37 | "collection_time": "30s",
38 | "listen_address": ":8090"
39 | }
40 | },
41 | "endpoints": [
42 | {
43 | "endpoint": "/identity-api/sign-in",
44 | "method": "POST",
45 | "backend": [
46 | {
47 | "url_pattern": "/api/identity/sign-in",
48 | "method": "POST",
49 | "host": [
50 | "http://identity-service:8081"
51 | ],
52 | "extra_config": {
53 | "github.com/devopsfaith/krakend/http": {
54 | "return_error_details": "identity"
55 | }
56 | }
57 | }
58 | ],
59 | "extra_config": {
60 | "github.com/devopsfaith/krakend-jose/signer": {
61 | "alg": "HS256",
62 | "kid": "sim1",
63 | "keys-to-sign": [
64 | "access_token"
65 | ],
66 | "jwk-url": "http://file_server:8080/jwk/symmetric.json",
67 | "disable_jwk_security": true
68 | }
69 | }
70 | },
71 | {
72 | "endpoint": "/identity-api/sign-up",
73 | "method": "POST",
74 | "backend": [
75 | {
76 | "url_pattern": "/api/identity/sign-up",
77 | "method": "POST",
78 | "host": [
79 | "http://identity-service:8081"
80 | ],
81 | "extra_config": {
82 | "github.com/devopsfaith/krakend/http": {
83 | "return_error_details": "identity"
84 | }
85 | }
86 | }
87 | ],
88 | "extra_config": {}
89 | },
90 | {
91 | "endpoint": "/customer-api",
92 | "headers_to_pass": [
93 | "Authorization"
94 | ],
95 | "method": "GET",
96 | "output_encoding": "json",
97 | "extra_config": {
98 | "github.com/devopsfaith/krakend-jose/validator": {
99 | "alg": "HS256",
100 | "audience": [
101 | "http://krakend:5000"
102 | ],
103 | "issuer": "http://identity-service:8081",
104 | "jwk-url": "http://file_server:8080/jwk/symmetric.json",
105 | "disable_jwk_security": true
106 | },
107 | "github.com/devopsfaith/krakend-ratelimit/juju/router": {
108 | "maxRate": 1,
109 | "clientMaxRate": 1,
110 | "strategy": "ip"
111 | }
112 | },
113 | "backend": [
114 | {
115 | "url_pattern": "/api/customers",
116 | "encoding": "json",
117 | "sd": "static",
118 | "host": [
119 | "http://customer-service:8082"
120 | ],
121 | "mapping": {
122 | "collection": "customers"
123 | },
124 | "disable_host_sanitize": true,
125 | "is_collection": true,
126 | "target": "",
127 | "extra_config": {
128 | "github.com/devopsfaith/krakend/http": {
129 | "return_error_details": "customer"
130 | }
131 | }
132 | }
133 | ]
134 | },
135 | {
136 | "endpoint": "/customer-api/{id}",
137 | "headers_to_pass": [
138 | "Authorization"
139 | ],
140 | "method": "GET",
141 | "output_encoding": "json",
142 | "extra_config": {
143 | "github.com/devopsfaith/krakend-jose/validator": {
144 | "alg": "HS256",
145 | "audience": [
146 | "http://krakend:5000"
147 | ],
148 | "issuer": "http://identity-service:8081",
149 | "jwk-url": "http://file_server:8080/jwk/symmetric.json",
150 | "disable_jwk_security": true
151 | }
152 | },
153 | "backend": [
154 | {
155 | "url_pattern": "/api/customers/{id}",
156 | "encoding": "json",
157 | "sd": "static",
158 | "host": [
159 | "http://customer-service:8082"
160 | ],
161 | "disable_host_sanitize": true,
162 | "extra_config": {
163 | "github.com/devopsfaith/krakend/http": {
164 | "return_error_details": "customer"
165 | }
166 | }
167 | }
168 | ]
169 | },
170 | {
171 | "endpoint": "/customer-api/basket",
172 | "headers_to_pass": [
173 | "Authorization",
174 | "user_id"
175 | ],
176 | "method": "POST",
177 | "output_encoding": "json",
178 | "extra_config": {
179 | "github.com/devopsfaith/krakend-jose/validator": {
180 | "alg": "HS256",
181 | "audience": [
182 | "http://krakend:5000"
183 | ],
184 | "issuer": "http://identity-service:8081",
185 | "jwk-url": "http://file_server:8080/jwk/symmetric.json",
186 | "disable_jwk_security": true,
187 | "propagate-claims": [
188 | [
189 | "user_id",
190 | "user_id"
191 | ]
192 | ]
193 | }
194 | },
195 | "backend": [
196 | {
197 | "url_pattern": "/api/customer-basket",
198 | "encoding": "json",
199 | "sd": "static",
200 | "method": "POST",
201 | "host": [
202 | "http://customer-service:8082"
203 | ],
204 | "disable_host_sanitize": true,
205 | "extra_config": {
206 | "github.com/devopsfaith/krakend/http": {
207 | "return_error_details": "customer"
208 | }
209 | }
210 | }
211 | ]
212 | },
213 | {
214 | "endpoint": "/product-api",
215 | "headers_to_pass": [
216 | "Authorization"
217 | ],
218 | "method": "POST",
219 | "output_encoding": "json",
220 | "extra_config": {
221 | "github.com/devopsfaith/krakend-jose/validator": {
222 | "alg": "HS256",
223 | "audience": [
224 | "http://krakend:5000"
225 | ],
226 | "issuer": "http://identity-service:8081",
227 | "jwk-url": "http://file_server:8080/jwk/symmetric.json",
228 | "disable_jwk_security": true
229 | }
230 | },
231 | "backend": [
232 | {
233 | "url_pattern": "/api/products",
234 | "encoding": "json",
235 | "sd": "static",
236 | "method": "POST",
237 | "host": [
238 | "http://product-service:8083"
239 | ],
240 | "disable_host_sanitize": true,
241 | "extra_config": {
242 | "github.com/devopsfaith/krakend/http": {
243 | "return_error_details": "customer"
244 | }
245 | }
246 | }
247 | ]
248 | },
249 | {
250 | "endpoint": "/product-api",
251 | "headers_to_pass": [
252 | "Authorization"
253 | ],
254 | "method": "GET",
255 | "output_encoding": "json",
256 | "extra_config": {
257 | "github.com/devopsfaith/krakend-jose/validator": {
258 | "alg": "HS256",
259 | "audience": [
260 | "http://krakend:5000"
261 | ],
262 | "issuer": "http://identity-service:8081",
263 | "jwk-url": "http://file_server:8080/jwk/symmetric.json",
264 | "disable_jwk_security": true
265 | }
266 | },
267 | "backend": [
268 | {
269 | "url_pattern": "/api/products",
270 | "encoding": "json",
271 | "sd": "static",
272 | "host": [
273 | "http://product-service:8083"
274 | ],
275 | "mapping": {
276 | "collection": "products"
277 | },
278 | "disable_host_sanitize": true,
279 | "is_collection": true,
280 | "target": "",
281 | "extra_config": {
282 | "github.com/devopsfaith/krakend/http": {
283 | "return_error_details": "product"
284 | }
285 | }
286 | }
287 | ]
288 | },
289 | {
290 | "endpoint": "/order-api",
291 | "headers_to_pass": [
292 | "Authorization",
293 | "user_id"
294 | ],
295 | "method": "POST",
296 | "output_encoding": "json",
297 | "extra_config": {
298 | "github.com/devopsfaith/krakend-jose/validator": {
299 | "alg": "HS256",
300 | "audience": [
301 | "http://krakend:5000"
302 | ],
303 | "issuer": "http://identity-service:8081",
304 | "jwk-url": "http://file_server:8080/jwk/symmetric.json",
305 | "disable_jwk_security": true,
306 | "propagate-claims": [
307 | [
308 | "user_id",
309 | "user_id"
310 | ]
311 | ]
312 | }
313 | },
314 | "backend": [
315 | {
316 | "url_pattern": "/api/orders",
317 | "encoding": "json",
318 | "sd": "static",
319 | "method": "POST",
320 | "host": [
321 | "http://order-service:8085"
322 | ],
323 | "disable_host_sanitize": true,
324 | "extra_config": {
325 | "github.com/devopsfaith/krakend/http": {
326 | "return_error_details": "customer"
327 | }
328 | }
329 | }
330 | ]
331 | }
332 | ]
333 | }
--------------------------------------------------------------------------------
/db_all.sql:
--------------------------------------------------------------------------------
1 | CREATE DATABASE "identity";
2 | CREATE DATABASE "customer";
3 | CREATE DATABASE "order";
4 | CREATE DATABASE "product";
--------------------------------------------------------------------------------
/docker-compose-infra-only.yml:
--------------------------------------------------------------------------------
1 | version: '3.7'
2 |
3 | services:
4 | postgres:
5 | image: postgres:10.6-alpine
6 | container_name: go_postgres
7 | ports:
8 | - "5492:5432"
9 | environment:
10 | - POSTGRES_USER=dbadmin
11 | - POSTGRES_PASSWORD=dbadmin
12 |
13 | kraken_designer:
14 | image: devopsfaith/krakendesigner:latest
15 | container_name: krakend_designer
16 | ports:
17 | - "8787:80"
18 |
19 | krakend:
20 | image: devopsfaith/krakend:latest
21 | container_name: krakend
22 | volumes:
23 | - ./api_gateway:/etc/krakend
24 | ports:
25 | - "5000:5000"
26 |
27 | jaeger:
28 | container_name: jaeger
29 | networks:
30 | - broker-kafka
31 | image: jaegertracing/all-in-one:latest
32 | ports:
33 | - "16686:16686"
34 | - "14268:14268"
35 |
36 | file_server:
37 | image: jaxgeller/lwan
38 | container_name: file_server
39 | volumes:
40 | - ./data:/lwan/wwwroot
41 | ports:
42 | - 8002:8080
43 |
44 | zookeeper:
45 | image: confluentinc/cp-zookeeper:latest
46 | container_name: zookeeper
47 | networks:
48 | - broker-kafka
49 | ports:
50 | - 2181:2181
51 | environment:
52 | ZOOKEEPER_CLIENT_PORT: 2181
53 | ZOOKEEPER_TICK_TIME: 2000
54 |
55 | kafka:
56 | image: confluentinc/cp-kafka:latest
57 | container_name: kafka
58 | networks:
59 | - broker-kafka
60 | depends_on:
61 | - zookeeper
62 | ports:
63 | - 9092:9092
64 | environment:
65 | KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
66 | KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092
67 | KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
68 | KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
69 | KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
70 | KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
71 | KAFKA_LOG_CLEANER_DELETE_RETENTION_MS: 5000
72 | KAFKA_BROKER_ID: 1
73 | KAFKA_MIN_INSYNC_REPLICAS: 1
74 | volumes:
75 | - ./_script:/wait
76 | command: ["/wait/wait-for-it.sh", "--timeout=0", "zookeeper:2181", "--", "/etc/confluent/docker/run"]
77 |
78 | kafdrop:
79 | image: obsidiandynamics/kafdrop:latest
80 | container_name: kafdrop
81 | networks:
82 | - broker-kafka
83 | depends_on:
84 | - kafka
85 | ports:
86 | - 9000:9000
87 | environment:
88 | KAFKA_BROKERCONNECT: kafka:29092
89 |
90 | broker:
91 | container_name: broker
92 | image: pactfoundation/pact-broker:latest
93 | ports:
94 | - 9292:9292
95 | networks:
96 | - broker-kafka
97 | environment:
98 | - PACT_BROKER_DATABASE_URL=postgresql://dbadmin:dbadmin@postgres:5432/postgres
99 | - PACT_BROKER_BASIC_AUTH_USERNAME=admin
100 | - PACT_BROKER_BASIC_AUTH_PASSWORD=admin
101 | - PACT_BROKER_LOG_LEVEL=INFO
102 | - PACT_BROKER_PORT=9292
103 | restart: on-failure
104 | depends_on:
105 | - postgres
106 |
107 | networks:
108 | broker-kafka:
109 | driver: bridge
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.7'
2 |
3 | services:
4 | postgres:
5 | image: postgres:10.6-alpine
6 | container_name: go_postgres
7 | ports:
8 | - "5492:5432"
9 | networks:
10 | - broker-kafka
11 | environment:
12 | - POSTGRES_USER=dbadmin
13 | - POSTGRES_PASSWORD=dbadmin
14 |
15 | kraken_designer:
16 | image: devopsfaith/krakendesigner:latest
17 | container_name: krakend_designer
18 | ports:
19 | - "8787:80"
20 |
21 | krakend:
22 | container_name: krakend
23 | image: devopsfaith/krakend:latest
24 | networks:
25 | - broker-kafka
26 | volumes:
27 | - ./api_gateway:/etc/krakend
28 | ports:
29 | - "5000:5000"
30 | - "8090:8090"
31 |
32 | jaeger:
33 | image: jaegertracing/all-in-one:latest
34 | container_name: jaeger
35 | networks:
36 | - broker-kafka
37 | ports:
38 | - "16686:16686"
39 | - "14268:14268"
40 |
41 | grafana:
42 | container_name: grafana
43 | build:
44 | dockerfile: Dockerfile
45 | context: ./_grafana
46 | networks:
47 | - broker-kafka
48 | ports:
49 | - "3003:3000"
50 |
51 | influxdb:
52 | image: influxdb:latest
53 | container_name: influxdb
54 | networks:
55 | - broker-kafka
56 | environment:
57 | DOCKER_INFLUXDB_INIT_MODE: setup
58 | DOCKER_INFLUXDB_INIT_USERNAME: admin
59 | DOCKER_INFLUXDB_INIT_PASSWORD: password
60 | DOCKER_INFLUXDB_INIT_ORG: myorg
61 | DOCKER_INFLUXDB_INIT_BUCKET: krakend
62 | ports:
63 | - "8086:8086"
64 |
65 | file_server:
66 | image: jaxgeller/lwan
67 | container_name: file_server
68 | networks:
69 | - broker-kafka
70 | volumes:
71 | - ./_file_server:/lwan/wwwroot
72 | ports:
73 | - 8002:8080
74 |
75 | zookeeper:
76 | image: confluentinc/cp-zookeeper:latest
77 | container_name: zookeeper
78 | networks:
79 | - broker-kafka
80 | ports:
81 | - 2181:2181
82 | environment:
83 | ZOOKEEPER_CLIENT_PORT: 2181
84 | ZOOKEEPER_TICK_TIME: 2000
85 |
86 | kafka:
87 | image: confluentinc/cp-kafka:latest
88 | container_name: kafka
89 | networks:
90 | - broker-kafka
91 | depends_on:
92 | - zookeeper
93 | ports:
94 | - 9092:9092
95 | - 29092:29092
96 | environment:
97 | KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
98 | KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://kafka:9092
99 | KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
100 | KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
101 | KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
102 | KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
103 | KAFKA_LOG_CLEANER_DELETE_RETENTION_MS: 5000
104 | KAFKA_BROKER_ID: 1
105 | KAFKA_MIN_INSYNC_REPLICAS: 1
106 | # volumes:
107 | # - ./_script:/wait
108 | # command: ["/wait/wait-for-it.sh", "--timeout=0", "zookeeper:2181", "--", "/etc/confluent/docker/run"]
109 |
110 | kafdrop:
111 | image: obsidiandynamics/kafdrop:latest
112 | container_name: kafdrop
113 | networks:
114 | - broker-kafka
115 | depends_on:
116 | - kafka
117 | ports:
118 | - 9000:9000
119 | environment:
120 | KAFKA_BROKERCONNECT: kafka:29092
121 |
122 | identity-service:
123 | build:
124 | context: ./
125 | dockerfile: ./services.identity/Dockerfile
126 | container_name: identity_service
127 | ports:
128 | - 8081:8081
129 | networks:
130 | - broker-kafka
131 | depends_on:
132 | - postgres
133 | environment:
134 | - APP_PORT=8081
135 | - DB_HOST=postgres
136 | - DB_PORT=5432
137 | - KAFKA_BROKER_ADDRESS=kafka:9092
138 |
139 | customer-service:
140 | build:
141 | context: ./
142 | dockerfile: ./services.customer/Dockerfile
143 | container_name: customer_service
144 | ports:
145 | - 8082:8082
146 | networks:
147 | - broker-kafka
148 | depends_on:
149 | - postgres
150 | environment:
151 | - APP_PORT=8082
152 | - DB_HOST=postgres
153 | - DB_PORT=5432
154 | - KAFKA_BROKER_ADDRESS=kafka:9092
155 |
156 | product-service:
157 | build:
158 | context: ./
159 | dockerfile: ./services.product/Dockerfile
160 | container_name: product_service
161 | ports:
162 | - 8083:8083
163 | networks:
164 | - broker-kafka
165 | depends_on:
166 | - postgres
167 | environment:
168 | - APP_PORT=8083
169 | - DB_HOST=postgres
170 | - DB_PORT=5432
171 | - KAFKA_BROKER_ADDRESS=kafka:9092
172 |
173 | notification-service:
174 | build:
175 | context: ./
176 | dockerfile: ./services.notification/Dockerfile
177 | container_name: notification_service
178 | ports:
179 | - 8084:8084
180 | networks:
181 | - broker-kafka
182 | depends_on:
183 | - postgres
184 | environment:
185 | - APP_PORT=8084
186 | - DB_HOST=postgres
187 | - DB_PORT=5432
188 | - KAFKA_BROKER_ADDRESS=kafka:9092
189 |
190 | order-service:
191 | build:
192 | context: ./
193 | dockerfile: ./services.order/Dockerfile
194 | container_name: order_service
195 | ports:
196 | - 8085:8085
197 | networks:
198 | - broker-kafka
199 | depends_on:
200 | - postgres
201 | environment:
202 | - APP_PORT=8085
203 | - DB_HOST=postgres
204 | - DB_PORT=5432
205 | - KAFKA_BROKER_ADDRESS=kafka:9092
206 | - CUSTOMER_SERVICE_ENDPOINT=http://customer-service:8082/api
207 |
208 | # pactbroker:
209 | # container_name: pactbroker
210 | # image: pactfoundation/pact-broker:latest
211 | # ports:
212 | # - 9292:9292
213 | # networks:
214 | # - broker-kafka
215 | # environment:
216 | # - PACT_BROKER_DATABASE_URL=postgresql://dbadmin:dbadmin@postgres:5432/postgres
217 | # - PACT_BROKER_BASIC_AUTH_USERNAME=admin
218 | # - PACT_BROKER_BASIC_AUTH_PASSWORD=admin
219 | # - PACT_BROKER_LOG_LEVEL=INFO
220 | # - PACT_BROKER_PORT=9292
221 | # restart: on-failure
222 | # depends_on:
223 | # - postgres
224 |
225 | networks:
226 | broker-kafka:
227 | driver: bridge
--------------------------------------------------------------------------------
/services.customer/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:alpine
2 | WORKDIR /build
3 | COPY services.customer services.customer
4 | COPY shared shared
5 | WORKDIR /build/services.customer
6 | RUN go build -o main ./src/main.go
7 | WORKDIR /dist
8 | RUN cp /build/services.customer/main .
9 | RUN cp /build/services.customer/src/app.env .
10 | CMD ["/dist/main"]
--------------------------------------------------------------------------------
/services.customer/README.MD:
--------------------------------------------------------------------------------
1 | ## Customer Service
--------------------------------------------------------------------------------
/services.customer/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/suadev/microservices/services.customer
2 |
3 | go 1.16
4 |
5 | replace github.com/suadev/microservices/shared => ../shared
6 |
7 | require (
8 | github.com/gin-gonic/gin v1.6.3
9 | github.com/google/uuid v1.2.0
10 | github.com/pact-foundation/pact-go v1.5.1
11 | github.com/satori/go.uuid v1.2.0
12 | github.com/suadev/microservices/shared v0.0.0-00010101000000-000000000000
13 | gorm.io/driver/postgres v1.0.8
14 | gorm.io/gorm v1.21.3
15 | )
16 |
--------------------------------------------------------------------------------
/services.customer/src/app.env:
--------------------------------------------------------------------------------
1 | #App
2 | APP_PORT=8082
3 |
4 | #Postgres
5 | DB_HOST=127.0.0.1
6 | DB_DRIVER=postgres
7 | DB_USER=dbadmin
8 | DB_PASSWORD=dbadmin
9 | DB_NAME=customer
10 | DB_PORT=5492
11 |
12 | #Kafka
13 | KAFKA_BROKER_ADDRESS="localhost:9092"
14 | KAFKA_USER_TOPIC="user_events"
15 | KAFKA_PRODUCT_TOPIC="product_events"
16 | KAFKA_ORDER_TOPIC="order_events"
--------------------------------------------------------------------------------
/services.customer/src/entity/basket.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/google/uuid"
7 | )
8 |
9 | type Basket struct {
10 | ID uuid.UUID `gorm:"type:uuid;primary_key;"`
11 | CustomerID uuid.UUID `gorm:"type:uuid;not null;"`
12 | CreatedAt time.Time `gorm:"not null default CURRENT_TIMESTAMP"`
13 | UpdatedAt time.Time `gorm:"not null default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP"`
14 | }
15 |
--------------------------------------------------------------------------------
/services.customer/src/entity/basket_item.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/google/uuid"
7 | )
8 |
9 | type BasketItem struct {
10 | ID uuid.UUID `gorm:"type:uuid;primary_key;"`
11 | BasketID uuid.UUID `gorm:"type:uuid;not null;"`
12 | ProductID uuid.UUID `gorm:"type:uuid;not null;"`
13 | ProductName string `gorm:"size:255;not null;"`
14 | UnitPrice float64 `gorm:"not null;"`
15 | Quantity int `gorm:"not null;"`
16 | CreatedAt time.Time `gorm:"not null default CURRENT_TIMESTAMP"`
17 | UpdatedAt time.Time `gorm:"not null default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP"`
18 | }
19 |
--------------------------------------------------------------------------------
/services.customer/src/entity/customer.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/google/uuid"
7 | )
8 |
9 | type Customer struct {
10 | ID uuid.UUID `gorm:"type:uuid;primary_key"`
11 | Email string `gorm:"size:255;not null;"`
12 | FirstName string `gorm:"size:255;not null;"`
13 | LastName string `gorm:"size:255;not null;"`
14 | Address string `gorm:"size:255;"`
15 | PhoneNumber string `gorm:"size:255;"`
16 | CreatedAt time.Time `gorm:"not null default CURRENT_TIMESTAMP"`
17 | UpdatedAt time.Time `gorm:"not null default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP"`
18 | }
19 |
--------------------------------------------------------------------------------
/services.customer/src/entity/product.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/google/uuid"
7 | )
8 |
9 | type Product struct {
10 | ID uuid.UUID `gorm:"type:uuid;primary_key"`
11 | Name string `gorm:"size:255;not null;"`
12 | Price float64 `gorm:"not null;"`
13 | CreatedAt time.Time `gorm:"not null default CURRENT_TIMESTAMP"`
14 | UpdatedAt time.Time `gorm:"not null default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP"`
15 | }
16 |
--------------------------------------------------------------------------------
/services.customer/src/event/order_created.go:
--------------------------------------------------------------------------------
1 | package event
2 |
3 | import (
4 | "github.com/google/uuid"
5 | )
6 |
7 | type OrderBasketItem struct {
8 | ProductID uuid.UUID
9 | Quantity int
10 | }
11 |
12 | type OrderCreated struct {
13 | ID uuid.UUID
14 | CustomerID uuid.UUID
15 | TotalAmount float64
16 | Items []OrderBasketItem
17 | }
18 |
--------------------------------------------------------------------------------
/services.customer/src/event_handler/order_completed_handler.go:
--------------------------------------------------------------------------------
1 | package eventhandler
2 |
3 | import (
4 | "encoding/json"
5 |
6 | "github.com/suadev/microservices/services.customer/src/event"
7 | "github.com/suadev/microservices/services.customer/src/internal"
8 | )
9 |
10 | func ClearBasket(service *customer.Service, message []byte) {
11 | var order event.OrderCreated
12 | json.Unmarshal(message, &order)
13 | basket, _ := service.GetBasket(order.CustomerID)
14 | service.ClearBasketItems(basket.ID)
15 | }
16 |
--------------------------------------------------------------------------------
/services.customer/src/event_handler/product_created_handler.go:
--------------------------------------------------------------------------------
1 | package eventhandler
2 |
3 | import (
4 | "encoding/json"
5 |
6 | "github.com/suadev/microservices/services.customer/src/internal"
7 | "github.com/suadev/microservices/services.customer/src/entity"
8 | )
9 |
10 | func CreateProduct(service *customer.Service, message []byte) {
11 | var product entity.Product
12 | json.Unmarshal(message, &product)
13 | service.CreateProduct(product)
14 | }
15 |
--------------------------------------------------------------------------------
/services.customer/src/event_handler/user_created_handler.go:
--------------------------------------------------------------------------------
1 | package eventhandler
2 |
3 | import (
4 | "encoding/json"
5 |
6 | "github.com/suadev/microservices/services.customer/src/internal"
7 | "github.com/suadev/microservices/services.customer/src/entity"
8 | )
9 |
10 | func CreateCustomer(service *customer.Service, message []byte) {
11 | var customer entity.Customer
12 | json.Unmarshal(message, &customer)
13 | service.CreateCustomer(customer)
14 | }
15 |
--------------------------------------------------------------------------------
/services.customer/src/internal/api.go:
--------------------------------------------------------------------------------
1 | package customer
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/gin-gonic/gin"
7 | "github.com/google/uuid"
8 | "github.com/suadev/microservices/services.customer/src/entity"
9 | )
10 |
11 | func (h *Handler) health(context *gin.Context) {
12 | context.JSON(http.StatusOK, "Customer service is up!")
13 | }
14 |
15 | func (h *Handler) getCustomers(context *gin.Context) {
16 |
17 | customers, err := h.service.GetCustomers()
18 | if err != nil {
19 | context.JSON(http.StatusBadRequest, nil)
20 | return
21 | }
22 | context.JSON(http.StatusOK, customers)
23 | }
24 |
25 | func (h *Handler) getCustomer(context *gin.Context) {
26 |
27 | id, _ := uuid.Parse(context.Param("id"))
28 | customer, err := h.service.GetCustomer(id)
29 | if err != nil {
30 | context.JSON(http.StatusBadRequest, nil)
31 | return
32 | }
33 | context.JSON(http.StatusOK, customer)
34 | }
35 |
36 | func (h *Handler) getBasketItems(context *gin.Context) {
37 |
38 | id, _ := uuid.Parse(context.Param("id"))
39 | basketItems, err := h.service.GetBasketItems(id)
40 | if err != nil {
41 | context.JSON(http.StatusBadRequest, nil)
42 | return
43 | }
44 | context.JSON(http.StatusOK, basketItems)
45 | }
46 |
47 | func (h *Handler) addItemToBasket(context *gin.Context) {
48 |
49 | var userID = context.Request.Header["User_id"][0] // injected by KrakenD
50 | // fmt.Println(userID)
51 | var input *entity.BasketItem
52 | context.BindJSON(&input)
53 | item, err := h.service.AddItemToBasket(input, userID)
54 | if err != nil {
55 | context.JSON(http.StatusBadRequest, err.Error())
56 | return
57 | }
58 | context.JSON(http.StatusOK, item)
59 | }
60 |
--------------------------------------------------------------------------------
/services.customer/src/internal/handler.go:
--------------------------------------------------------------------------------
1 | package customer
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | )
6 |
7 | type Handler struct {
8 | service *Service
9 | }
10 |
11 | func NewHandler(service *Service) *Handler {
12 | return &Handler{service: service}
13 | }
14 |
15 | func (h *Handler) Init() *gin.Engine {
16 | router := gin.Default()
17 | h.initRoutes(router)
18 | return router
19 | }
20 |
21 | func (h *Handler) initRoutes(router *gin.Engine) {
22 | routerGroup := router.Group("/api")
23 | routerGroup.GET("/health", h.health)
24 | routerGroup.GET("/customers", h.getCustomers)
25 | routerGroup.GET("/customers/:id", h.getCustomer)
26 | routerGroup.GET("/customers/:id/basketItems", h.getBasketItems)
27 | routerGroup.POST("/customer-basket", h.addItemToBasket)
28 | }
29 |
--------------------------------------------------------------------------------
/services.customer/src/internal/repository.go:
--------------------------------------------------------------------------------
1 | package customer
2 |
3 | import (
4 | "github.com/google/uuid"
5 | "github.com/suadev/microservices/services.customer/src/entity"
6 | "gorm.io/gorm"
7 | "gorm.io/gorm/logger"
8 | )
9 |
10 | type Repository struct {
11 | db *gorm.DB
12 | }
13 |
14 | func NewRepository(db *gorm.DB) *Repository {
15 | db.Logger.LogMode(logger.Info)
16 | return &Repository{db: db}
17 | }
18 |
19 | func (r *Repository) CreateCustomer(customer entity.Customer) (uuid.UUID, error) {
20 |
21 | err := r.db.Model(&entity.Customer{}).Create(&customer).Error
22 | if err == nil { // create empty basket for the new customer
23 | basket := entity.Basket{ID: uuid.New(), CustomerID: customer.ID}
24 | err = r.db.Model(&entity.Basket{}).Create(&basket).Error
25 | }
26 | return customer.ID, err
27 | }
28 |
29 | func (r *Repository) CreateProduct(product entity.Product) (uuid.UUID, error) {
30 | err := r.db.Model(&product).Create(&product).Error
31 | return product.ID, err
32 | }
33 |
34 | func (r *Repository) GetList() ([]entity.Customer, error) {
35 | var customers []entity.Customer
36 | err := r.db.Find(&customers).Error
37 | return customers, err
38 | }
39 |
40 | func (r *Repository) GetById(id uuid.UUID) (entity.Customer, error) {
41 | var customer entity.Customer
42 | err := r.db.Where(entity.Customer{ID: id}).Take(&customer).Error
43 | return customer, err
44 | }
45 |
46 | func (r *Repository) GetProductById(id uuid.UUID) (entity.Product, error) {
47 | var product entity.Product
48 | err := r.db.Where(entity.Product{ID: id}).Take(&product).Error
49 | return product, err
50 | }
51 |
52 | func (r *Repository) GetBasket(customerID uuid.UUID) (entity.Basket, error) {
53 | var basket entity.Basket
54 | err := r.db.Where(&entity.Basket{CustomerID: customerID}).Take(&basket).Error
55 | return basket, err
56 | }
57 |
58 | func (r *Repository) GetBasketItem(productID uuid.UUID) (entity.BasketItem, error) {
59 | var basketItem entity.BasketItem
60 | err := r.db.Where(&entity.BasketItem{ProductID: productID}).Take(&basketItem).Error
61 | return basketItem, err
62 | }
63 |
64 | func (r *Repository) GetBasketItems(customerID uuid.UUID) ([]entity.BasketItem, error) {
65 | basket, _ := r.GetBasket(customerID)
66 | var basketItems []entity.BasketItem
67 | err := r.db.Where(&entity.BasketItem{BasketID: basket.ID}).Find(&basketItems).Error
68 | return basketItems, err
69 | }
70 |
71 | func (r *Repository) CreateBasketItem(item *entity.BasketItem) (*entity.BasketItem, error) {
72 | err := r.db.Model(&entity.BasketItem{}).Create(&item).Error
73 | return item, err
74 | }
75 |
76 | func (r *Repository) UpdateItemQuantity(item *entity.BasketItem, quantity int) (*entity.BasketItem, error) {
77 | err := r.db.Model(&item).
78 | Where("id = ?", item.ID).Update("quantity", item.Quantity+quantity).Error
79 | return item, err
80 | }
81 |
82 | func (r *Repository) ClearBasketItems(basketID uuid.UUID) error {
83 | return r.db.Where(&entity.BasketItem{BasketID: basketID}).Delete(&entity.BasketItem{}).Error
84 | }
85 |
--------------------------------------------------------------------------------
/services.customer/src/internal/service.go:
--------------------------------------------------------------------------------
1 | package customer
2 |
3 | import (
4 | "github.com/google/uuid"
5 | gouuid "github.com/satori/go.uuid"
6 | "github.com/suadev/microservices/services.customer/src/entity"
7 | )
8 |
9 | type Service struct {
10 | repo *Repository
11 | }
12 |
13 | func NewService(repo *Repository) *Service {
14 | return &Service{
15 | repo: repo,
16 | }
17 | }
18 |
19 | func (s *Service) CreateCustomer(customer entity.Customer) (uuid.UUID, error) {
20 | return s.repo.CreateCustomer(customer)
21 | }
22 |
23 | func (s *Service) CreateProduct(product entity.Product) (uuid.UUID, error) {
24 | return s.repo.CreateProduct(product)
25 | }
26 |
27 | func (s *Service) GetCustomers() ([]entity.Customer, error) {
28 | return s.repo.GetList()
29 | }
30 |
31 | func (s *Service) GetCustomer(id uuid.UUID) (entity.Customer, error) {
32 | return s.repo.GetById(id)
33 | }
34 |
35 | func (s *Service) GetBasket(customerID uuid.UUID) (entity.Basket, error) {
36 | return s.repo.GetBasket(customerID)
37 | }
38 |
39 | func (s *Service) GetBasketItems(customerID uuid.UUID) ([]entity.BasketItem, error) {
40 | return s.repo.GetBasketItems(customerID)
41 | }
42 |
43 | func (s *Service) AddItemToBasket(basketItem *entity.BasketItem, userID string) (*entity.BasketItem, error) {
44 |
45 | customerID, _ := gouuid.FromString(userID)
46 | basket, err := s.repo.GetBasket(uuid.UUID(customerID))
47 | if err != nil {
48 | return basketItem, err
49 | }
50 |
51 | product, err := s.repo.GetProductById(basketItem.ProductID)
52 | if err != nil {
53 | return basketItem, err
54 | }
55 |
56 | existingBasketItem, err := s.repo.GetBasketItem(basketItem.ProductID)
57 | if err == nil {
58 | return s.repo.UpdateItemQuantity(&existingBasketItem, basketItem.Quantity)
59 | }
60 |
61 | basketItem.ID = uuid.New()
62 | basketItem.BasketID = basket.ID
63 | basketItem.ProductName = product.Name
64 | basketItem.UnitPrice = product.Price
65 | return s.repo.CreateBasketItem(basketItem)
66 | }
67 |
68 | func (s *Service) ClearBasketItems(basketID uuid.UUID) error {
69 | return s.repo.ClearBasketItems(basketID)
70 | }
71 |
--------------------------------------------------------------------------------
/services.customer/src/internal/verify_contract_test.go:
--------------------------------------------------------------------------------
1 | package customer
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "os"
7 | "path/filepath"
8 | "strconv"
9 | "testing"
10 |
11 | "github.com/gin-gonic/gin"
12 | "github.com/pact-foundation/pact-go/dsl"
13 | "github.com/pact-foundation/pact-go/types"
14 | "github.com/pact-foundation/pact-go/utils"
15 | "github.com/suadev/microservices/services.customer/src/entity"
16 | )
17 |
18 | var port, _ = utils.GetFreePort()
19 |
20 | func Test_Verify_Pacts(t *testing.T) {
21 |
22 | pact := dsl.Pact{
23 | Provider: "customer-service",
24 | LogDir: "../../../_logs",
25 | PactDir: "../../../_pacts",
26 | DisableToolValidityCheck: true,
27 | LogLevel: "INFO",
28 | }
29 |
30 | go startProvider()
31 |
32 | var dir, _ = os.Getwd()
33 | var pactDir = fmt.Sprintf("%s/../../../_pacts", dir)
34 |
35 | _, err := pact.VerifyProvider(t, types.VerifyRequest{
36 | ProviderBaseURL: "http://localhost:" + strconv.Itoa(port),
37 | PactURLs: []string{filepath.FromSlash(fmt.Sprintf("%s/order-service-customer-service.json", pactDir))},
38 | BrokerURL: "http://localhost:9292",
39 | BrokerUsername: "admin",
40 | BrokerPassword: "admin",
41 | ProviderVersion: "1.0.0",
42 | PublishVerificationResults: true,
43 | })
44 | if err != nil {
45 | t.Fatal(err)
46 | }
47 | }
48 |
49 | func getBasketItems(context *gin.Context) {
50 | basketItems := [1]entity.BasketItem{{}}
51 | context.JSON(http.StatusOK, basketItems)
52 | }
53 |
54 | func startProvider() {
55 | router := gin.Default()
56 | router.GET("/customers/:id/basketItems", getBasketItems)
57 | router.Run(fmt.Sprintf(":%d", port))
58 | }
59 |
--------------------------------------------------------------------------------
/services.customer/src/kafka/consumer.go:
--------------------------------------------------------------------------------
1 | package kafka
2 |
3 | import (
4 | "log"
5 | "os"
6 | "os/signal"
7 |
8 | eventhandler "github.com/suadev/microservices/services.customer/src/event_handler"
9 | customer "github.com/suadev/microservices/services.customer/src/internal"
10 | "github.com/suadev/microservices/shared/kafka"
11 | )
12 |
13 | func RegisterConsumer(topic string, service *customer.Service) {
14 |
15 | partitionConsumer, _ := kafka.CreatePartitionConsumer(topic)
16 |
17 | signals := make(chan os.Signal, 1)
18 | signal.Notify(signals, os.Interrupt)
19 |
20 | doneCh := make(chan struct{})
21 |
22 | go func() {
23 | for {
24 | select {
25 | case err := <-partitionConsumer.Errors():
26 | log.Println(err)
27 | case msg := <-partitionConsumer.Messages():
28 | log.Println("Message Received:", string(msg.Key), string(msg.Value))
29 | eventType := string(msg.Headers[0].Value)
30 |
31 | if eventType == "UserCreated" {
32 | eventhandler.CreateCustomer(service, msg.Value)
33 | } else if eventType == "ProductCreated" {
34 | eventhandler.CreateProduct(service, msg.Value)
35 | } else if eventType == "OrderCompleted" {
36 | eventhandler.ClearBasket(service, msg.Value)
37 | }
38 | case <-signals:
39 | log.Println("Interrupt is detected")
40 | doneCh <- struct{}{}
41 | }
42 | }
43 | }()
44 | <-doneCh
45 | }
46 |
--------------------------------------------------------------------------------
/services.customer/src/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/suadev/microservices/services.customer/src/entity"
5 | customer "github.com/suadev/microservices/services.customer/src/internal"
6 | "github.com/suadev/microservices/services.customer/src/kafka"
7 | "github.com/suadev/microservices/shared/config"
8 | "github.com/suadev/microservices/shared/server"
9 |
10 | "gorm.io/driver/postgres"
11 | "gorm.io/gorm"
12 | )
13 |
14 | func main() {
15 |
16 | config := config.LoadConfig(".")
17 |
18 | db, err := gorm.Open(postgres.New(postgres.Config{
19 | DSN: config.GetDBURL(),
20 | PreferSimpleProtocol: true,
21 | }), &gorm.Config{})
22 | if err != nil {
23 | panic("Couldn't connect to the DB.")
24 | }
25 |
26 | db.AutoMigrate(&entity.Basket{})
27 | db.AutoMigrate(&entity.BasketItem{})
28 | db.AutoMigrate(&entity.Customer{})
29 | db.AutoMigrate(&entity.Product{})
30 |
31 | repo := customer.NewRepository(db)
32 | service := customer.NewService(repo)
33 | handler := customer.NewHandler(service)
34 |
35 | go kafka.RegisterConsumer(config.KafkaUserTopic, service)
36 | go kafka.RegisterConsumer(config.KafkaProductTopic, service)
37 | go kafka.RegisterConsumer(config.KafkaOrderTopic, service)
38 |
39 | err = server.NewServer(handler.Init(), config.AppPort).Run()
40 | if err != nil {
41 | panic("Couldn't start the HTTP server.")
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/services.identity/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:alpine
2 | WORKDIR /build
3 | COPY services.identity services.identity
4 | COPY shared shared
5 | WORKDIR /build/services.identity
6 | RUN go build -o main ./src/main.go
7 | WORKDIR /dist
8 | RUN cp /build/services.identity/main .
9 | RUN cp /build/services.identity/src/app.env .
10 | CMD ["/dist/main"]
--------------------------------------------------------------------------------
/services.identity/README.MD:
--------------------------------------------------------------------------------
1 | ## Identity Service
--------------------------------------------------------------------------------
/services.identity/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/suadev/microservices/services.identity
2 |
3 | go 1.16
4 |
5 | replace github.com/suadev/microservices/shared => ../shared
6 |
7 | require (
8 | github.com/dgrijalva/jwt-go v3.2.0+incompatible
9 | github.com/gin-gonic/gin v1.6.3
10 | github.com/google/uuid v1.2.0
11 | github.com/suadev/microservices/shared v0.0.0-00010101000000-000000000000
12 | gorm.io/driver/postgres v1.0.8
13 | gorm.io/gorm v1.21.3
14 | )
15 |
--------------------------------------------------------------------------------
/services.identity/src/app.env:
--------------------------------------------------------------------------------
1 | #App
2 | APP_PORT=8081
3 |
4 | #Postgres
5 | DB_HOST=127.0.0.1
6 | DB_DRIVER=postgres
7 | DB_USER=dbadmin
8 | DB_PASSWORD=dbadmin
9 | DB_NAME=identity
10 | DB_PORT=5492
11 |
12 | #Kafka
13 | KAFKA_BROKER_ADDRESS="localhost:9092"
14 | KAFKA_USER_TOPIC="user_events"
--------------------------------------------------------------------------------
/services.identity/src/entity/user.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/google/uuid"
7 | )
8 |
9 | type User struct {
10 | ID uuid.UUID `gorm:"type:uuid;primary_key"`
11 | Email string `gorm:"size:255;not null;"`
12 | Password string `gorm:"size:255;not null;"`
13 | FirstName string `gorm:"size:255;not null;"`
14 | LastName string `gorm:"size:255;not null;"`
15 | CreatedAt time.Time `gorm:"not null default CURRENT_TIMESTAMP"`
16 | UpdatedAt time.Time `gorm:"not null default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP"`
17 | }
18 |
--------------------------------------------------------------------------------
/services.identity/src/event/user_created.go:
--------------------------------------------------------------------------------
1 | package event
2 |
3 | import "github.com/google/uuid"
4 |
5 | type UserCreated struct {
6 | ID uuid.UUID
7 | Email string
8 | FirstName string
9 | LastName string
10 | }
11 |
--------------------------------------------------------------------------------
/services.identity/src/internal/api.go:
--------------------------------------------------------------------------------
1 | package identity
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/gin-gonic/gin"
7 | "github.com/suadev/microservices/services.identity/src/entity"
8 | "github.com/suadev/microservices/services.identity/src/jwt"
9 | )
10 |
11 | func (h *Handler) health(context *gin.Context) {
12 | context.JSON(http.StatusOK, "Identity service is up!")
13 | }
14 |
15 | func (h *Handler) signUp(context *gin.Context) {
16 |
17 | var input *entity.User
18 | context.BindJSON(&input)
19 |
20 | user, err := h.service.SignUp(input)
21 | if err != nil {
22 | context.JSON(http.StatusBadRequest, err.Error())
23 | return
24 | }
25 | context.JSON(http.StatusOK, user)
26 | }
27 |
28 | func (h *Handler) signIn(context *gin.Context) {
29 |
30 | var input *entity.User
31 | context.BindJSON(&input)
32 |
33 | user, err := h.service.ValidateUser(input.Email, input.Password)
34 | if err != nil {
35 | context.JSON(http.StatusBadRequest, "Invalid Credentials.")
36 | return
37 | }
38 |
39 | token := jwt.GenerateToken(user.ID)
40 | if err != nil {
41 | context.JSON(http.StatusInternalServerError, err.Error())
42 | return
43 | }
44 | context.JSON(http.StatusOK, jwt.TokenResponse{AccessToken: token})
45 | }
46 |
--------------------------------------------------------------------------------
/services.identity/src/internal/handler.go:
--------------------------------------------------------------------------------
1 | package identity
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | )
6 |
7 | type Handler struct {
8 | service *Service
9 | }
10 |
11 | func NewHandler(service *Service) *Handler {
12 | return &Handler{service: service}
13 | }
14 |
15 | func (h *Handler) Init() *gin.Engine {
16 | router := gin.Default()
17 | h.initRoutes(router)
18 | return router
19 | }
20 |
21 | func (h *Handler) initRoutes(router *gin.Engine) {
22 | routerGroup := router.Group("/api")
23 | routerGroup.GET("/health", h.health)
24 | routerGroup.POST("/identity/sign-up", h.signUp)
25 | routerGroup.POST("/identity/sign-in", h.signIn)
26 | }
27 |
--------------------------------------------------------------------------------
/services.identity/src/internal/repository.go:
--------------------------------------------------------------------------------
1 | package identity
2 |
3 | import (
4 | "github.com/suadev/microservices/services.identity/src/entity"
5 | "gorm.io/gorm"
6 | "gorm.io/gorm/logger"
7 | )
8 |
9 | type Repository struct {
10 | db *gorm.DB
11 | }
12 |
13 | func NewRepository(db *gorm.DB) *Repository {
14 | db.Logger.LogMode(logger.Info)
15 | return &Repository{db: db}
16 | }
17 |
18 | func (r *Repository) IsUserExist(email string) bool {
19 | user := entity.User{}
20 | r.db.Find(&user, "email = ?", email)
21 | return user.Email != ""
22 | }
23 |
24 | func (r *Repository) Create(user *entity.User) (*entity.User, error) {
25 | err := r.db.Model(&entity.User{}).Create(&user).Error
26 | return user, err
27 | }
28 |
29 | func (r *Repository) GetUserByEmail(email string, password string) (entity.User, error) {
30 | var user entity.User
31 | err := r.db.Where(entity.User{Email: email, Password: password}).Take(&user).Error
32 | return user, err
33 | }
34 |
--------------------------------------------------------------------------------
/services.identity/src/internal/service.go:
--------------------------------------------------------------------------------
1 | package identity
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 |
7 | "github.com/google/uuid"
8 | "github.com/suadev/microservices/services.identity/src/entity"
9 | "github.com/suadev/microservices/services.identity/src/event"
10 | "github.com/suadev/microservices/shared/config"
11 | "github.com/suadev/microservices/shared/kafka"
12 | )
13 |
14 | type Service struct {
15 | repo *Repository
16 | }
17 |
18 | func NewService(repo *Repository) *Service {
19 | return &Service{
20 | repo: repo,
21 | }
22 | }
23 |
24 | func (s *Service) SignUp(user *entity.User) (*entity.User, error) {
25 |
26 | isEmailExist := s.repo.IsUserExist(user.Email)
27 | if isEmailExist {
28 | return user, errors.New("Email is already exist!")
29 | }
30 |
31 | user.ID = uuid.New()
32 | usr, err := s.repo.Create(user)
33 | if err != nil {
34 | return user, err
35 | }
36 |
37 | event := event.UserCreated{
38 | ID: usr.ID,
39 | Email: usr.Email,
40 | FirstName: usr.FirstName,
41 | LastName: usr.LastName}
42 |
43 | payload, _ := json.Marshal(event)
44 | kafka.Publish(user.ID, payload, "UserCreated", config.AppConfig.KafkaUserTopic)
45 |
46 | return user, nil
47 | }
48 |
49 | func (s *Service) ValidateUser(email string, password string) (entity.User, error) {
50 | return s.repo.GetUserByEmail(email, password)
51 | }
52 |
--------------------------------------------------------------------------------
/services.identity/src/jwt/jwt.go:
--------------------------------------------------------------------------------
1 | package jwt
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/dgrijalva/jwt-go"
7 | "github.com/google/uuid"
8 | )
9 |
10 | type token struct {
11 | UserID uuid.UUID `json:"user_id"`
12 | jwt.StandardClaims
13 | }
14 |
15 | type TokenResponse struct {
16 | AccessToken token `json:"access_token"`
17 | }
18 |
19 | func GenerateToken(userID uuid.UUID) token {
20 | return token{
21 | UserID: userID,
22 | StandardClaims: jwt.StandardClaims{
23 | ExpiresAt: time.Now().Add(time.Hour * 72).Unix(),
24 | Audience: "http://krakend:5000",
25 | Issuer: "http://identity-service:8081",
26 | },
27 | }
28 | // KrakenD signs the access token. Uncomment to sign in identity service.
29 | // token := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), claims)
30 | // mySigningKey := []byte("my_secret_key")
31 | // tokenString, err := token.SignedString(mySigningKey)
32 | }
33 |
--------------------------------------------------------------------------------
/services.identity/src/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/suadev/microservices/services.identity/src/entity"
5 | identity "github.com/suadev/microservices/services.identity/src/internal"
6 | "github.com/suadev/microservices/shared/config"
7 | "github.com/suadev/microservices/shared/server"
8 |
9 | "gorm.io/driver/postgres"
10 | "gorm.io/gorm"
11 | )
12 |
13 | func main() {
14 |
15 | config := config.LoadConfig(".")
16 |
17 | db, err := gorm.Open(postgres.New(postgres.Config{
18 | DSN: config.GetDBURL(),
19 | PreferSimpleProtocol: true,
20 | }), &gorm.Config{})
21 | if err != nil {
22 | panic("Couldn't connect to the DB.")
23 | }
24 |
25 | db.AutoMigrate(&entity.User{})
26 |
27 | repo := identity.NewRepository(db)
28 | service := identity.NewService(repo)
29 | handler := identity.NewHandler(service)
30 |
31 | err = server.NewServer(handler.Init(), config.AppPort).Run()
32 | if err != nil {
33 | panic("Couldn't start the HTTP server.")
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/services.identity/src/services-identity:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suadev/go-microservices-and-krakend-api-gateway/3d9c321f60b883fd3814fc839f9cbef67c3b3633/services.identity/src/services-identity
--------------------------------------------------------------------------------
/services.notification/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:alpine
2 | WORKDIR /build
3 | COPY services.notification services.notification
4 | COPY shared shared
5 | WORKDIR /build/services.notification
6 | RUN go build -o main ./src/main.go
7 | WORKDIR /dist
8 | RUN cp /build/services.notification/main .
9 | RUN cp /build/services.notification/src/app.env .
10 | CMD ["/dist/main"]
--------------------------------------------------------------------------------
/services.notification/README.MD:
--------------------------------------------------------------------------------
1 | ## Notification Service
--------------------------------------------------------------------------------
/services.notification/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/suadev/microservices/services.notification
2 |
3 | go 1.16
4 |
5 | replace github.com/suadev/microservices/shared => ../shared
6 |
7 | require github.com/suadev/microservices/shared v0.0.0-00010101000000-000000000000
8 |
--------------------------------------------------------------------------------
/services.notification/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/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
9 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
10 | cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
11 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
12 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
13 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
14 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
15 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
16 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
17 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
18 | github.com/Shopify/sarama v1.28.0 h1:lOi3SfE6OcFlW9Trgtked2aHNZ2BIG/d6Do+PEUAqqM=
19 | github.com/Shopify/sarama v1.28.0/go.mod h1:j/2xTrU39dlzBmsxF1eQ2/DdWrxyBCl6pzz7a81o/ZY=
20 | github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc=
21 | github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
22 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
23 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
24 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
25 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
26 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
27 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
28 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
29 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
30 | github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
31 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
32 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
33 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
34 | github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
35 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
36 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
37 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
38 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
39 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
40 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
41 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
42 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
43 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
44 | github.com/eapache/go-resiliency v1.2.0 h1:v7g92e/KSN71Rq7vSThKaWIq68fL4YHvWyiUKorFR1Q=
45 | github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
46 | github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw=
47 | github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
48 | github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
49 | github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
50 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
51 | github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
52 | github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
53 | github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY=
54 | github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
55 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
56 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
57 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
58 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
59 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
60 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
61 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
62 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
63 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
64 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
65 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
66 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
67 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
68 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
69 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
70 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
71 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
72 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
73 | github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw=
74 | github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
75 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
76 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
77 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
78 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
79 | github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
80 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
81 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
82 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
83 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
84 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
85 | github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
86 | github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
87 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
88 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
89 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
90 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
91 | github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
92 | github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
93 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
94 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
95 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
96 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
97 | github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
98 | github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
99 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
100 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
101 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
102 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
103 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
104 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
105 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
106 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
107 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
108 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
109 | github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
110 | github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
111 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
112 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
113 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
114 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
115 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
116 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
117 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
118 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
119 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
120 | github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
121 | github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
122 | github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
123 | github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
124 | github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8=
125 | github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
126 | github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
127 | github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
128 | github.com/jcmturner/gokrb5/v8 v8.4.2 h1:6ZIM6b/JJN0X8UM43ZOM6Z4SJzla+a/u7scXFJzodkA=
129 | github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc=
130 | github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
131 | github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
132 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
133 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
134 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
135 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
136 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
137 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
138 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
139 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
140 | github.com/klauspost/compress v1.11.7 h1:0hzRabrMN4tSTvMfnL3SCv1ZGeAP23ynzodBgaHeMeg=
141 | github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
142 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
143 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
144 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
145 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
146 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
147 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
148 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
149 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
150 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
151 | github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
152 | github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
153 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
154 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
155 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
156 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
157 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
158 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
159 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
160 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
161 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
162 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
163 | github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
164 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
165 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
166 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
167 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
168 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
169 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
170 | github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
171 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
172 | github.com/pierrec/lz4 v2.6.0+incompatible h1:Ix9yFKn1nSPBLFl/yZknTp8TU5G4Ps0JDmguYK6iH1A=
173 | github.com/pierrec/lz4 v2.6.0+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
174 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
175 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
176 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
177 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
178 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
179 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
180 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
181 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
182 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
183 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
184 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
185 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
186 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
187 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
188 | github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
189 | github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
190 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
191 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
192 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
193 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
194 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
195 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
196 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
197 | github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
198 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
199 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
200 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
201 | github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
202 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
203 | github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
204 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
205 | github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
206 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
207 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
208 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
209 | github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
210 | github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
211 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
212 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
213 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
214 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
215 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
216 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
217 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
218 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
219 | github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
220 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
221 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
222 | github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
223 | github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
224 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
225 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
226 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
227 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
228 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
229 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
230 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
231 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
232 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
233 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
234 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
235 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
236 | golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
237 | golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
238 | golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
239 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
240 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
241 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
242 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
243 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
244 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
245 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
246 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
247 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
248 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
249 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
250 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
251 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
252 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
253 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
254 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
255 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
256 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
257 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
258 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
259 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
260 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
261 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
262 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
263 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
264 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
265 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
266 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
267 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
268 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
269 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
270 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
271 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
272 | golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
273 | golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
274 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
275 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
276 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
277 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
278 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
279 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
280 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
281 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
282 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
283 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
284 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
285 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
286 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
287 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
288 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
289 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
290 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
291 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
292 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
293 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
294 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
295 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
296 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
297 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
298 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
299 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
300 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
301 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
302 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
303 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
304 | golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
305 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
306 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
307 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
308 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
309 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
310 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
311 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
312 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
313 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
314 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
315 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
316 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
317 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
318 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
319 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
320 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
321 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
322 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
323 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
324 | golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
325 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
326 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
327 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
328 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
329 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
330 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
331 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
332 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
333 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
334 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
335 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
336 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
337 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
338 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
339 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
340 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
341 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
342 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
343 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
344 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
345 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
346 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
347 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
348 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
349 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
350 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
351 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
352 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
353 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
354 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
355 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
356 | gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
357 | gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
358 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
359 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
360 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
361 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
362 | gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
363 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
364 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
365 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
366 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
367 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
368 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
369 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
370 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
371 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
372 |
--------------------------------------------------------------------------------
/services.notification/src/app.env:
--------------------------------------------------------------------------------
1 | #App
2 | APP_PORT=8084
3 |
4 | #Kafka
5 | KAFKA_BROKER_ADDRESS="localhost:9092"
6 | KAFKA_ORDER_TOPIC="order_events"
--------------------------------------------------------------------------------
/services.notification/src/kafka/consumer.go:
--------------------------------------------------------------------------------
1 | package kafka
2 |
3 | import (
4 | "log"
5 | "os"
6 | "os/signal"
7 |
8 | "github.com/suadev/microservices/shared/kafka"
9 | )
10 |
11 | func RegisterConsumer(topic string) {
12 |
13 | partitionConsumer, _ := kafka.CreatePartitionConsumer(topic)
14 | signals := make(chan os.Signal, 1)
15 | signal.Notify(signals, os.Interrupt)
16 |
17 | doneCh := make(chan struct{})
18 |
19 | go func() {
20 | for {
21 | select {
22 | case err := <-partitionConsumer.Errors():
23 | log.Println(err)
24 | case msg := <-partitionConsumer.Messages():
25 | log.Println("Message Received:", string(msg.Key), string(msg.Value))
26 |
27 | eventType := string(msg.Headers[0].Value)
28 | if eventType == "OrderCompleted" {
29 | log.Println("[Notification]: Order Completed!")
30 | } else if eventType == "OrderFailed" {
31 | log.Println("[Notification]: Order Failed!")
32 | }
33 | case <-signals:
34 | log.Println("Interrupt is detected")
35 | doneCh <- struct{}{}
36 | }
37 | }
38 | }()
39 | <-doneCh
40 | }
41 |
--------------------------------------------------------------------------------
/services.notification/src/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/suadev/microservices/services.notification/src/kafka"
5 | "github.com/suadev/microservices/shared/config"
6 | )
7 |
8 | func main() {
9 | config := config.LoadConfig(".")
10 | kafka.RegisterConsumer(config.KafkaOrderTopic)
11 | }
12 |
--------------------------------------------------------------------------------
/services.order/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:alpine
2 | WORKDIR /build
3 | COPY services.order services.order
4 | COPY shared shared
5 | WORKDIR /build/services.order
6 | RUN go build -o main ./src/main.go
7 | WORKDIR /dist
8 | RUN cp /build/services.order/main .
9 | RUN cp /build/services.order/src/app.env .
10 | CMD ["/dist/main"]
--------------------------------------------------------------------------------
/services.order/README.MD:
--------------------------------------------------------------------------------
1 | ## Order Service
--------------------------------------------------------------------------------
/services.order/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/suadev/microservices/services.order
2 |
3 | go 1.16
4 |
5 | replace github.com/suadev/microservices/shared => ../shared
6 |
7 | require (
8 | github.com/gin-gonic/gin v1.6.3
9 | github.com/google/uuid v1.2.0
10 | github.com/pact-foundation/pact-go v1.5.1
11 | github.com/satori/go.uuid v1.2.0
12 | github.com/suadev/microservices/shared v0.0.0-00010101000000-000000000000
13 | gorm.io/driver/postgres v1.0.8
14 | gorm.io/gorm v1.21.3
15 | )
16 |
--------------------------------------------------------------------------------
/services.order/src/app.env:
--------------------------------------------------------------------------------
1 | #App
2 | APP_PORT=8085
3 | CUSTOMER_SERVICE_ENDPOINT=http://localhost:8082/api
4 |
5 | #Postgres
6 | DB_HOST=127.0.0.1
7 | DB_DRIVER=postgres
8 | DB_USER=dbadmin
9 | DB_PASSWORD=dbadmin
10 | DB_NAME=order
11 | DB_PORT=5492
12 |
13 | #Kafka
14 | KAFKA_BROKER_ADDRESS="localhost:9092"
15 | KAFKA_PRODUCT_TOPIC="product_events"
16 | KAFKA_ORDER_TOPIC="order_events"
--------------------------------------------------------------------------------
/services.order/src/dto/basket_item_dto.go:
--------------------------------------------------------------------------------
1 | package dto
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/google/uuid"
7 | )
8 |
9 | type BasketItemDto struct {
10 | ID uuid.UUID
11 | BasketID uuid.UUID
12 | ProductID uuid.UUID
13 | ProductName string
14 | UnitPrice float64
15 | Quantity int
16 | CreatedAt time.Time
17 | UpdatedAt time.Time
18 | }
19 |
--------------------------------------------------------------------------------
/services.order/src/entity/order.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/google/uuid"
7 | )
8 |
9 | type OrderStatus int
10 |
11 | const (
12 | OrderCreated OrderStatus = 0
13 | OrderCompleted OrderStatus = 1
14 | OrderFailed OrderStatus = 2
15 | )
16 |
17 | type Order struct {
18 | ID uuid.UUID `gorm:"type:uuid;primary_key;"`
19 | CustomerID uuid.UUID `gorm:"type:uuid;not null;"`
20 | TotalAmount float64 `gorm:"not null;"`
21 | Status OrderStatus `gorm:"not null;"`
22 | CreatedAt time.Time `gorm:"not null default CURRENT_TIMESTAMP"`
23 | UpdatedAt time.Time `gorm:"not null default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP"`
24 | }
25 |
--------------------------------------------------------------------------------
/services.order/src/entity/order_item.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/google/uuid"
7 | )
8 |
9 | type OrderItem struct {
10 | ID uuid.UUID `gorm:"type:uuid;primary_key;"`
11 | OrderID uuid.UUID `gorm:"type:uuid;not null;"`
12 | ProductID uuid.UUID `gorm:"type:uuid;not null;"`
13 | ProductName string `gorm:"size:255;not null;"`
14 | UnitPrice float64 `gorm:"not null;"`
15 | Quantity int `gorm:"not null;"`
16 | CreatedAt time.Time `gorm:"not null default CURRENT_TIMESTAMP"`
17 | UpdatedAt time.Time `gorm:"not null default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP"`
18 | }
19 |
--------------------------------------------------------------------------------
/services.order/src/event/order_created.go:
--------------------------------------------------------------------------------
1 | package event
2 |
3 | import (
4 | "github.com/google/uuid"
5 | )
6 |
7 | type OrderBasketItem struct {
8 | ProductID uuid.UUID
9 | Quantity int
10 | }
11 |
12 | type OrderCreated struct {
13 | ID uuid.UUID
14 | CustomerID uuid.UUID
15 | TotalAmount float64
16 | Items []OrderBasketItem
17 | }
18 |
--------------------------------------------------------------------------------
/services.order/src/event_handler/product_reserve_failed_handler.go:
--------------------------------------------------------------------------------
1 | package eventhandler
2 |
3 | import (
4 | "encoding/json"
5 | "log"
6 |
7 | "github.com/google/uuid"
8 | gouuid "github.com/satori/go.uuid"
9 | "github.com/suadev/microservices/services.order/src/entity"
10 | "github.com/suadev/microservices/services.order/src/internal"
11 | "github.com/suadev/microservices/shared/config"
12 | "github.com/suadev/microservices/shared/kafka"
13 | )
14 |
15 | func FailOrder(service *order.Service, messageKey []byte) {
16 |
17 | orderID, _ := gouuid.FromString(string(messageKey))
18 | order, err := service.UpdateOrderStatus(uuid.UUID(orderID), int(entity.OrderFailed))
19 | if err != nil {
20 | log.Printf("FailOrder.UpdateOrderStatus failed: %v", err.Error())
21 | return
22 | }
23 | payload, _ := json.Marshal(order)
24 | kafka.Publish(order.ID, payload, "OrderFailed", config.AppConfig.KafkaOrderTopic)
25 | }
26 |
--------------------------------------------------------------------------------
/services.order/src/event_handler/product_reserved_handler.go:
--------------------------------------------------------------------------------
1 | package eventhandler
2 |
3 | import (
4 | "encoding/json"
5 | "log"
6 |
7 | "github.com/google/uuid"
8 | gouuid "github.com/satori/go.uuid"
9 | "github.com/suadev/microservices/services.order/src/entity"
10 | "github.com/suadev/microservices/services.order/src/internal"
11 | "github.com/suadev/microservices/shared/config"
12 | "github.com/suadev/microservices/shared/kafka"
13 | )
14 |
15 | func CompleteOrder(service *order.Service, messageKey []byte) {
16 |
17 | orderID, _ := gouuid.FromString(string(messageKey))
18 | order, err := service.UpdateOrderStatus(uuid.UUID(orderID), int(entity.OrderCompleted))
19 | if err != nil {
20 | log.Printf("CompleteOrder.UpdateOrderStatus failed: %v", err.Error())
21 | return
22 | }
23 | payload, _ := json.Marshal(order)
24 | kafka.Publish(order.ID, payload, "OrderCompleted", config.AppConfig.KafkaOrderTopic)
25 | }
26 |
--------------------------------------------------------------------------------
/services.order/src/http_client/customer_contract_test.go:
--------------------------------------------------------------------------------
1 | package httpclient
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "os"
7 | "path/filepath"
8 | "testing"
9 | "time"
10 |
11 | "github.com/google/uuid"
12 | "github.com/pact-foundation/pact-go/dsl"
13 | "github.com/pact-foundation/pact-go/types"
14 | "github.com/suadev/microservices/services.order/src/dto"
15 | )
16 |
17 | var pact dsl.Pact
18 |
19 | func TestMain(m *testing.M) {
20 |
21 | setupMockServer()
22 | // Run all the tests
23 | var exitCode = m.Run()
24 | // Shutdown the Mock Service and Write pact files to disk
25 | pact.WritePact()
26 | pact.Teardown()
27 |
28 | err := publishPacts()
29 |
30 | if err != nil {
31 | log.Println("ERROR: ", err)
32 | os.Exit(1)
33 | }
34 | os.Exit(exitCode)
35 | }
36 |
37 | func publishPacts() error {
38 |
39 | publisher := dsl.Publisher{}
40 |
41 | var dir, _ = os.Getwd()
42 | var pactDir = fmt.Sprintf("%s/../../../_pacts", dir)
43 |
44 | return publisher.Publish(types.PublishRequest{
45 | PactURLs: []string{filepath.FromSlash(fmt.Sprintf("%s/order-service-customer-service.json", pactDir))},
46 | ConsumerVersion: "1.0.0",
47 | PactBroker: "http://localhost:9292",
48 | BrokerUsername: "admin",
49 | BrokerPassword: "admin",
50 | })
51 | }
52 |
53 | var term = dsl.Term
54 |
55 | type request = dsl.Request
56 |
57 | func Test_ClientPact_Get_Basket_Items(t *testing.T) {
58 | t.Run("Get Basket Items...", func(t *testing.T) {
59 | sampleUUID := uuid.MustParse("e153ef59-5708-48a4-848b-a65bd2667ac4")
60 | UUIDRegex := "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}"
61 | customerID := sampleUUID.String()
62 |
63 | basketItemsResponse := [1]dto.BasketItemDto{
64 | {
65 | ID: sampleUUID,
66 | BasketID: sampleUUID,
67 | ProductID: sampleUUID,
68 | ProductName: "Sample Product",
69 | UnitPrice: 5,
70 | Quantity: 1,
71 | CreatedAt: time.Time{},
72 | UpdatedAt: time.Time{},
73 | }}
74 |
75 | pact.
76 | AddInteraction().
77 | Given("There is an available basket for the customer").
78 | UponReceiving("A GET request to retrieve customer basket items.").
79 | WithRequest(request{
80 | Method: "GET",
81 | Path: term("/customers/"+customerID+"/basketItems", "/customers/"+UUIDRegex+"/basketItems"),
82 | }).
83 | WillRespondWith(dsl.Response{
84 | Status: 200,
85 | Body: dsl.Like(basketItemsResponse),
86 | Headers: dsl.MapMatcher{
87 | "Content-Type": term("application/json; charset=utf-8", `application\/json`),
88 | },
89 | })
90 |
91 | err := pact.Verify(func() error {
92 | _, err := client.GetBasketItems(customerID)
93 | return err
94 | })
95 |
96 | if err != nil {
97 | t.Fatalf("Error on Verify: %v", err)
98 | }
99 | })
100 | }
101 |
102 | var client *Client
103 |
104 | func setupMockServer() {
105 | pact = createPact()
106 | // Start service to get access to the port
107 | pact.Setup(true)
108 | client = &Client{
109 | hostURL: fmt.Sprintf("http://localhost:%d", pact.Server.Port),
110 | }
111 | }
112 |
113 | func createPact() dsl.Pact {
114 | return dsl.Pact{
115 | Consumer: "order-service",
116 | Provider: "customer-service",
117 | LogDir: "../../../_logs",
118 | PactDir: "../../../_pacts",
119 | LogLevel: "DEBUG",
120 | DisableToolValidityCheck: true,
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/services.order/src/http_client/customer_http_client.go:
--------------------------------------------------------------------------------
1 | package httpclient
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 | "io/ioutil"
8 | "net/http"
9 |
10 | "github.com/suadev/microservices/services.order/src/dto"
11 | "github.com/suadev/microservices/shared/config"
12 | )
13 |
14 | type Client struct {
15 | hostURL string
16 | }
17 |
18 | func NewCustomerClient() *Client {
19 | return &Client{
20 | hostURL: config.AppConfig.CustomerServiceEndpoint,
21 | }
22 | }
23 |
24 | func (c *Client) GetBasketItems(customerID string) ([]dto.BasketItemDto, error) {
25 | resp, err := http.Get(c.hostURL + fmt.Sprintf("/customers/%v/basketItems", customerID))
26 | if err != nil {
27 | return nil, err
28 | }
29 | defer resp.Body.Close()
30 | if resp.StatusCode != 200 {
31 | return nil, errors.New("Failed fetching basket items.")
32 | }
33 | body, err := ioutil.ReadAll(resp.Body)
34 | if err != nil {
35 | return nil, err
36 | }
37 |
38 | var basketItems []dto.BasketItemDto
39 | json.Unmarshal(body, &basketItems)
40 | return basketItems, nil
41 | }
42 |
--------------------------------------------------------------------------------
/services.order/src/internal/api.go:
--------------------------------------------------------------------------------
1 | package order
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 |
7 | "github.com/gin-gonic/gin"
8 | "github.com/google/uuid"
9 | )
10 |
11 | func (h *Handler) health(context *gin.Context) {
12 | context.JSON(http.StatusOK, "Order service is up!")
13 | }
14 |
15 | func (h *Handler) getOrders(context *gin.Context) {
16 |
17 | orders, err := h.service.GetOrders()
18 | if err != nil {
19 | context.JSON(http.StatusBadRequest, err.Error())
20 | return
21 | }
22 | context.JSON(http.StatusOK, orders)
23 | }
24 |
25 | func (h *Handler) getOrder(context *gin.Context) {
26 |
27 | id, _ := uuid.Parse(context.Param("id"))
28 | order, err := h.service.GetOrder(id)
29 | if err != nil {
30 | context.JSON(http.StatusBadRequest, err.Error())
31 | return
32 | }
33 | context.JSON(http.StatusOK, order)
34 | }
35 |
36 | func (h *Handler) createOrder(context *gin.Context) {
37 |
38 | var userID = context.Request.Header["User_id"][0] // injected by KrakenD
39 | order, err := h.service.CreateOrder(userID)
40 | if err != nil {
41 | fmt.Println(err.Error())
42 | context.JSON(http.StatusBadRequest, err.Error())
43 | return
44 | }
45 | context.JSON(http.StatusOK, order)
46 | }
47 |
--------------------------------------------------------------------------------
/services.order/src/internal/handler.go:
--------------------------------------------------------------------------------
1 | package order
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | )
6 |
7 | type Handler struct {
8 | service *Service
9 | }
10 |
11 | func NewHandler(service *Service) *Handler {
12 | return &Handler{service: service}
13 | }
14 |
15 | func (h *Handler) Init() *gin.Engine {
16 | router := gin.Default()
17 | h.initRoutes(router)
18 | return router
19 | }
20 |
21 | func (h *Handler) initRoutes(router *gin.Engine) {
22 | routerGroup := router.Group("/api")
23 | routerGroup.GET("/health", h.health)
24 | routerGroup.GET("/orders", h.getOrders)
25 | routerGroup.GET("/orders/:id", h.getOrder)
26 | routerGroup.POST("/orders", h.createOrder)
27 | }
28 |
--------------------------------------------------------------------------------
/services.order/src/internal/repository.go:
--------------------------------------------------------------------------------
1 | package order
2 |
3 | import (
4 | "github.com/google/uuid"
5 | "github.com/suadev/microservices/services.order/src/entity"
6 | "gorm.io/gorm"
7 | "gorm.io/gorm/logger"
8 | )
9 |
10 | type Repository struct {
11 | db *gorm.DB
12 | }
13 |
14 | func NewRepository(db *gorm.DB) *Repository {
15 | db.Logger.LogMode(logger.Info)
16 | return &Repository{db: db}
17 | }
18 |
19 | func (r *Repository) CreateOrder(order entity.Order, items []entity.OrderItem) (entity.Order, []entity.OrderItem, error) {
20 |
21 | err := r.db.Model(&entity.Order{}).Create(&order).Error
22 | if err != nil {
23 | return order, nil, err
24 | }
25 | r.db.Model(&[]entity.OrderItem{}).Create(&items)
26 | return order, items, err
27 | }
28 |
29 | func (r *Repository) UpdateOrderStatus(id uuid.UUID, status int) (entity.Order, error) {
30 |
31 | order, err := r.GetById(id)
32 | if err != nil {
33 | return order, err
34 | }
35 | err = r.db.Model(&order).Update("status", status).Error
36 | return order, err
37 | }
38 |
39 | func (r *Repository) GetList() ([]entity.Order, error) {
40 | var customers []entity.Order
41 | err := r.db.Find(&customers).Error
42 | return customers, err
43 | }
44 |
45 | func (r *Repository) GetById(id uuid.UUID) (entity.Order, error) {
46 | order := &entity.Order{ID: id}
47 | err := r.db.Find(&order).Error
48 | return *order, err
49 | }
50 |
--------------------------------------------------------------------------------
/services.order/src/internal/service.go:
--------------------------------------------------------------------------------
1 | package order
2 |
3 | import (
4 | "encoding/json"
5 |
6 | "github.com/google/uuid"
7 | gouuid "github.com/satori/go.uuid"
8 | "github.com/suadev/microservices/services.order/src/dto"
9 | "github.com/suadev/microservices/services.order/src/entity"
10 | "github.com/suadev/microservices/services.order/src/event"
11 | httpclient "github.com/suadev/microservices/services.order/src/http_client"
12 | "github.com/suadev/microservices/shared/config"
13 | "github.com/suadev/microservices/shared/kafka"
14 | )
15 |
16 | type Service struct {
17 | repo *Repository
18 | }
19 |
20 | func NewService(repo *Repository) *Service {
21 | return &Service{
22 | repo: repo,
23 | }
24 | }
25 |
26 | //CreateOrder ...
27 | func (s *Service) CreateOrder(customerID string) (entity.Order, error) {
28 | order := entity.Order{}
29 | order.ID = uuid.New()
30 |
31 | client := httpclient.NewCustomerClient()
32 | basketItems, err := client.GetBasketItems(customerID)
33 | if err != nil {
34 | return order, err
35 | }
36 |
37 | customerIDGouuid, _ := gouuid.FromString(customerID)
38 | order.Status = entity.OrderCreated
39 | order.CustomerID = uuid.UUID(customerIDGouuid)
40 |
41 | var orderItems []entity.OrderItem
42 | for _, basketItem := range basketItems {
43 | orderItem := entity.OrderItem{
44 | ID: uuid.New(),
45 | OrderID: order.ID,
46 | ProductID: basketItem.ProductID,
47 | ProductName: basketItem.ProductName,
48 | UnitPrice: basketItem.UnitPrice,
49 | Quantity: basketItem.Quantity,
50 | }
51 | orderItems = append(orderItems, orderItem)
52 |
53 | order.TotalAmount += (basketItem.UnitPrice * float64(basketItem.Quantity))
54 | }
55 |
56 | createdOrder, _, err := s.repo.CreateOrder(order, orderItems)
57 | if err != nil {
58 | return createdOrder, err
59 | }
60 |
61 | publishOrderCreatedEvent(createdOrder, basketItems)
62 |
63 | return createdOrder, err
64 | }
65 |
66 | func publishOrderCreatedEvent(createdOrder entity.Order, basketItems []dto.BasketItemDto) {
67 | orderCreatedEvent := event.OrderCreated{
68 | ID: createdOrder.ID,
69 | CustomerID: createdOrder.CustomerID,
70 | TotalAmount: createdOrder.TotalAmount,
71 | }
72 |
73 | for _, basketItem := range basketItems {
74 | orderCreatedEvent.Items = append(orderCreatedEvent.Items,
75 | event.OrderBasketItem{ProductID: basketItem.ProductID,
76 | Quantity: basketItem.Quantity})
77 | }
78 |
79 | payload, _ := json.Marshal(orderCreatedEvent)
80 | kafka.Publish(createdOrder.ID, payload, "OrderCreated", config.AppConfig.KafkaOrderTopic)
81 | }
82 |
83 | func (s *Service) UpdateOrderStatus(id uuid.UUID, status int) (entity.Order, error) {
84 | return s.repo.UpdateOrderStatus(id, status)
85 | }
86 |
87 | func (s *Service) GetOrders() ([]entity.Order, error) {
88 | return s.repo.GetList()
89 | }
90 |
91 | func (s *Service) GetOrder(id uuid.UUID) (entity.Order, error) {
92 | return s.repo.GetById(id)
93 | }
94 |
--------------------------------------------------------------------------------
/services.order/src/kafka/consumer.go:
--------------------------------------------------------------------------------
1 | package kafka
2 |
3 | import (
4 | "log"
5 | "os"
6 | "os/signal"
7 |
8 | eventhandler "github.com/suadev/microservices/services.order/src/event_handler"
9 | order "github.com/suadev/microservices/services.order/src/internal"
10 | "github.com/suadev/microservices/shared/kafka"
11 | )
12 |
13 | func RegisterConsumer(topic string, service *order.Service) {
14 |
15 | partitionConsumer, _ := kafka.CreatePartitionConsumer(topic)
16 |
17 | signals := make(chan os.Signal, 1)
18 | signal.Notify(signals, os.Interrupt)
19 |
20 | doneCh := make(chan struct{})
21 |
22 | go func() {
23 | for {
24 | select {
25 | case err := <-partitionConsumer.Errors():
26 | log.Println(err)
27 | case msg := <-partitionConsumer.Messages():
28 |
29 | log.Println("Message Received:", string(msg.Key), string(msg.Value))
30 | eventType := string(msg.Headers[0].Value)
31 |
32 | if eventType == "ProductReserved" {
33 | eventhandler.CompleteOrder(service, msg.Key)
34 | } else if eventType == "ProductReserveFailed" {
35 | eventhandler.FailOrder(service, msg.Key)
36 | }
37 | case <-signals:
38 | log.Println("Interrupt is detected")
39 | doneCh <- struct{}{}
40 | }
41 | }
42 | }()
43 | <-doneCh
44 | }
45 |
--------------------------------------------------------------------------------
/services.order/src/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/suadev/microservices/services.order/src/entity"
5 | order "github.com/suadev/microservices/services.order/src/internal"
6 | "github.com/suadev/microservices/services.order/src/kafka"
7 | "github.com/suadev/microservices/shared/config"
8 | "github.com/suadev/microservices/shared/server"
9 |
10 | "gorm.io/driver/postgres"
11 | "gorm.io/gorm"
12 | )
13 |
14 | func main() {
15 |
16 | config := config.LoadConfig(".")
17 |
18 | db, err := gorm.Open(postgres.New(postgres.Config{
19 | DSN: config.GetDBURL(),
20 | PreferSimpleProtocol: true,
21 | }), &gorm.Config{})
22 | if err != nil {
23 | panic("Couldn't connect to the DB.")
24 | }
25 |
26 | db.AutoMigrate(&entity.Order{})
27 | db.AutoMigrate(&entity.OrderItem{})
28 |
29 | repo := order.NewRepository(db)
30 | service := order.NewService(repo)
31 | handler := order.NewHandler(service)
32 |
33 | go kafka.RegisterConsumer(config.KafkaProductTopic, service)
34 |
35 | err = server.NewServer(handler.Init(), config.AppPort).Run()
36 | if err != nil {
37 | panic("Couldn't start the HTTP server.")
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/services.product/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:alpine
2 | WORKDIR /build
3 | COPY services.product services.product
4 | COPY shared shared
5 | WORKDIR /build/services.product
6 | RUN go build -o main ./src/main.go
7 | WORKDIR /dist
8 | RUN cp /build/services.product/main .
9 | RUN cp /build/services.product/src/app.env .
10 | CMD ["/dist/main"]
--------------------------------------------------------------------------------
/services.product/README.MD:
--------------------------------------------------------------------------------
1 | ## Product Service
--------------------------------------------------------------------------------
/services.product/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/suadev/microservices/services.product
2 |
3 | go 1.16
4 |
5 | replace github.com/suadev/microservices/shared => ../shared
6 |
7 | require (
8 | github.com/gin-gonic/gin v1.6.3
9 | github.com/google/uuid v1.2.0
10 | github.com/suadev/microservices/shared v0.0.0-00010101000000-000000000000
11 | gorm.io/driver/postgres v1.0.8
12 | gorm.io/gorm v1.21.3
13 | )
14 |
--------------------------------------------------------------------------------
/services.product/src/app.env:
--------------------------------------------------------------------------------
1 | #App
2 | APP_PORT=8083
3 |
4 | #Postgres
5 | DB_HOST=127.0.0.1
6 | DB_DRIVER=postgres
7 | DB_USER=dbadmin
8 | DB_PASSWORD=dbadmin
9 | DB_NAME=product
10 | DB_PORT=5492
11 |
12 | #Kafka
13 | KAFKA_BROKER_ADDRESS="localhost:9092"
14 | KAFKA_PRODUCT_TOPIC="product_events"
15 | KAFKA_ORDER_TOPIC="order_events"
--------------------------------------------------------------------------------
/services.product/src/entity/product.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/google/uuid"
7 | )
8 |
9 | type Product struct {
10 | ID uuid.UUID `gorm:"type:uuid;primary_key"`
11 | Name string `gorm:"size:255;not null;"`
12 | Price float64 `gorm:"not null;"`
13 | Quantity int `gorm:"not null;"`
14 | CreatedAt time.Time `gorm:"not null default CURRENT_TIMESTAMP"`
15 | UpdatedAt time.Time `gorm:"not null default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP"`
16 | }
17 |
--------------------------------------------------------------------------------
/services.product/src/event/order_created.go:
--------------------------------------------------------------------------------
1 | package event
2 |
3 | import (
4 | "github.com/google/uuid"
5 | )
6 |
7 | type orderBasketItem struct {
8 | ProductID uuid.UUID
9 | Quantity int
10 | }
11 |
12 | type OrderCreated struct {
13 | ID uuid.UUID
14 | CustomerID uuid.UUID
15 | TotalAmount float64
16 | Items []orderBasketItem
17 | }
18 |
--------------------------------------------------------------------------------
/services.product/src/event/product_created.go:
--------------------------------------------------------------------------------
1 | package event
2 |
3 | import "github.com/google/uuid"
4 |
5 | type ProcuctCreated struct {
6 | ID uuid.UUID
7 | Name string
8 | Price float64
9 | Quantity int
10 | }
11 |
--------------------------------------------------------------------------------
/services.product/src/event_handler/order_created_handler.go:
--------------------------------------------------------------------------------
1 | package eventhandler
2 |
3 | import (
4 | "encoding/json"
5 | "log"
6 |
7 | "github.com/suadev/microservices/services.product/src/entity"
8 | "github.com/suadev/microservices/services.product/src/event"
9 | "github.com/suadev/microservices/services.product/src/internal"
10 | "github.com/suadev/microservices/shared/config"
11 | "github.com/suadev/microservices/shared/kafka"
12 | )
13 |
14 | func ReserveProducts(service *product.Service, message []byte) {
15 |
16 | isReserved := false
17 | var products []entity.Product
18 |
19 | var order event.OrderCreated
20 | json.Unmarshal(message, &order)
21 |
22 | for _, item := range order.Items {
23 |
24 | product, err := service.GetProduct(item.ProductID)
25 | if err != nil {
26 | log.Printf("Product not found: %v", item.ProductID.String())
27 | isReserved = false
28 | break
29 | }
30 |
31 | if item.Quantity > product.Quantity {
32 | isReserved = false
33 | log.Printf("Not available %v %v product.", item.Quantity, product.Name)
34 | break
35 | } else {
36 | isReserved = true
37 | product.Quantity -= item.Quantity
38 | products = append(products, product)
39 | }
40 | }
41 |
42 | eventType := "ProductReserveFailed"
43 | if isReserved {
44 | err := service.BulkUpdate(&products)
45 | if err == nil {
46 | eventType = "ProductReserved"
47 | }
48 | }
49 | kafka.Publish(order.ID, nil, eventType, config.AppConfig.KafkaProductTopic)
50 | }
51 |
--------------------------------------------------------------------------------
/services.product/src/internal/api.go:
--------------------------------------------------------------------------------
1 | package product
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/gin-gonic/gin"
7 | "github.com/google/uuid"
8 | "github.com/suadev/microservices/services.product/src/entity"
9 | )
10 |
11 | func (h *Handler) health(context *gin.Context) {
12 | context.JSON(http.StatusOK, "Product service is up!")
13 | }
14 |
15 | func (h *Handler) getProducts(context *gin.Context) {
16 |
17 | products, err := h.service.GetProducts()
18 | if err != nil {
19 | context.JSON(http.StatusBadRequest, nil)
20 | return
21 | }
22 | context.JSON(http.StatusOK, products)
23 | }
24 |
25 | func (h *Handler) getProduct(context *gin.Context) {
26 |
27 | id, _ := uuid.Parse(context.Param("id"))
28 | product, err := h.service.GetProduct(id)
29 | if err != nil {
30 | context.JSON(http.StatusBadRequest, nil)
31 | return
32 | }
33 | context.JSON(http.StatusOK, product)
34 | }
35 |
36 | func (h *Handler) createProduct(context *gin.Context) {
37 |
38 | var input *entity.Product
39 | context.BindJSON(&input)
40 | product, err := h.service.CreateProduct(input)
41 | if err != nil {
42 | context.JSON(http.StatusBadRequest, err.Error())
43 | return
44 | }
45 | context.JSON(http.StatusOK, product)
46 | }
47 |
--------------------------------------------------------------------------------
/services.product/src/internal/handler.go:
--------------------------------------------------------------------------------
1 | package product
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | )
6 |
7 | type Handler struct {
8 | service *Service
9 | }
10 |
11 | func NewHandler(service *Service) *Handler {
12 | return &Handler{service: service}
13 | }
14 |
15 | func (h *Handler) Init() *gin.Engine {
16 | router := gin.Default()
17 | h.initRoutes(router)
18 | return router
19 | }
20 |
21 | func (h *Handler) initRoutes(router *gin.Engine) {
22 | routerGroup := router.Group("/api")
23 | routerGroup.GET("/health", h.health)
24 | routerGroup.GET("/products", h.getProducts)
25 | routerGroup.GET("/products/:id", h.getProduct)
26 | routerGroup.POST("/products", h.createProduct)
27 | }
28 |
--------------------------------------------------------------------------------
/services.product/src/internal/repository.go:
--------------------------------------------------------------------------------
1 | package product
2 |
3 | import (
4 | "github.com/google/uuid"
5 | "github.com/suadev/microservices/services.product/src/entity"
6 | "gorm.io/gorm"
7 | "gorm.io/gorm/logger"
8 | )
9 |
10 | type Repository struct {
11 | db *gorm.DB
12 | }
13 |
14 | func NewRepository(db *gorm.DB) *Repository {
15 | db.Logger.LogMode(logger.Info)
16 | return &Repository{db: db}
17 | }
18 |
19 | func (r *Repository) Create(product *entity.Product) (*entity.Product, error) {
20 | err := r.db.Model(&product).Create(&product).Error
21 | return product, err
22 | }
23 |
24 | func (r *Repository) BulkUpdate(products *[]entity.Product) error {
25 | return r.db.Model(entity.Product{}).Save(products).Error
26 | }
27 |
28 | func (r *Repository) GetList() ([]entity.Product, error) {
29 | var products []entity.Product
30 | err := r.db.Find(&products).Error
31 | return products, err
32 | }
33 |
34 | func (r *Repository) GetById(id uuid.UUID) (entity.Product, error) {
35 | var product entity.Product
36 | err := r.db.Where(entity.Product{ID: id}).Take(&product).Error
37 | return product, err
38 | }
39 |
--------------------------------------------------------------------------------
/services.product/src/internal/service.go:
--------------------------------------------------------------------------------
1 | package product
2 |
3 | import (
4 | "encoding/json"
5 |
6 | "github.com/google/uuid"
7 | "github.com/suadev/microservices/services.product/src/entity"
8 | "github.com/suadev/microservices/services.product/src/event"
9 | "github.com/suadev/microservices/shared/config"
10 | "github.com/suadev/microservices/shared/kafka"
11 | )
12 |
13 | type Service struct {
14 | repo *Repository
15 | }
16 |
17 | func NewService(repo *Repository) *Service {
18 | return &Service{
19 | repo: repo,
20 | }
21 | }
22 |
23 | func (s *Service) CreateProduct(product *entity.Product) (*entity.Product, error) {
24 | product.ID = uuid.New()
25 | createdProduct, err := s.repo.Create(product)
26 |
27 | if err != nil {
28 | return createdProduct, err
29 | }
30 |
31 | event := event.ProcuctCreated{
32 | ID: createdProduct.ID,
33 | Name: createdProduct.Name,
34 | Price: createdProduct.Price,
35 | Quantity: createdProduct.Quantity,
36 | }
37 | payload, _ := json.Marshal(event)
38 | kafka.Publish(product.ID, payload, "ProductCreated", config.AppConfig.KafkaProductTopic)
39 |
40 | return createdProduct, nil
41 | }
42 |
43 | func (s *Service) BulkUpdate(products *[]entity.Product) error {
44 | return s.repo.BulkUpdate(products)
45 | }
46 |
47 | func (s *Service) GetProducts() ([]entity.Product, error) {
48 | return s.repo.GetList()
49 | }
50 |
51 | func (s *Service) GetProduct(id uuid.UUID) (entity.Product, error) {
52 | return s.repo.GetById(id)
53 | }
54 |
--------------------------------------------------------------------------------
/services.product/src/kafka/consumer.go:
--------------------------------------------------------------------------------
1 | package kafka
2 |
3 | import (
4 | "log"
5 | "os"
6 | "os/signal"
7 |
8 | eventhandler "github.com/suadev/microservices/services.product/src/event_handler"
9 | "github.com/suadev/microservices/services.product/src/internal"
10 | "github.com/suadev/microservices/shared/kafka"
11 | )
12 |
13 | func RegisterConsumer(topic string, service *product.Service) {
14 |
15 | partitionConsumer, _ := kafka.CreatePartitionConsumer(topic)
16 |
17 | signals := make(chan os.Signal, 1)
18 | signal.Notify(signals, os.Interrupt)
19 |
20 | doneCh := make(chan struct{})
21 |
22 | go func() {
23 | for {
24 | select {
25 | case err := <-partitionConsumer.Errors():
26 | log.Println(err)
27 | case msg := <-partitionConsumer.Messages():
28 | log.Println("Message Received:", string(msg.Key), string(msg.Value))
29 |
30 | eventType := string(msg.Headers[0].Value)
31 | if eventType == "OrderCreated" {
32 | eventhandler.ReserveProducts(service, msg.Value)
33 | }
34 | case <-signals:
35 | log.Println("Interrupt is detected")
36 | doneCh <- struct{}{}
37 | }
38 | }
39 | }()
40 | <-doneCh
41 | }
42 |
--------------------------------------------------------------------------------
/services.product/src/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/suadev/microservices/services.product/src/entity"
5 | product "github.com/suadev/microservices/services.product/src/internal"
6 | "github.com/suadev/microservices/services.product/src/kafka"
7 | "github.com/suadev/microservices/shared/config"
8 | "github.com/suadev/microservices/shared/server"
9 |
10 | "gorm.io/driver/postgres"
11 | "gorm.io/gorm"
12 | )
13 |
14 | func main() {
15 |
16 | config := config.LoadConfig(".")
17 |
18 | db, err := gorm.Open(postgres.New(postgres.Config{
19 | DSN: config.GetDBURL(),
20 | PreferSimpleProtocol: true,
21 | }), &gorm.Config{})
22 | if err != nil {
23 | panic("Couldn't connect to the DB.")
24 | }
25 |
26 | db.AutoMigrate(&entity.Product{})
27 |
28 | repo := product.NewRepository(db)
29 | service := product.NewService(repo)
30 | handler := product.NewHandler(service)
31 |
32 | go kafka.RegisterConsumer(config.KafkaOrderTopic, service)
33 |
34 | err = server.NewServer(handler.Init(), config.AppPort).Run()
35 | if err != nil {
36 | panic("Couldn't start the HTTP server.")
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/shared/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/spf13/viper"
7 | )
8 |
9 | var AppConfig Config
10 |
11 | type Config struct {
12 | DbDriver string `mapstructure:"DB_DRIVER"`
13 | DbUser string `mapstructure:"DB_USER"`
14 | DbPassword string `mapstructure:"DB_PASSWORD"`
15 | DbPort string `mapstructure:"DB_PORT"`
16 | DbHost string `mapstructure:"DB_HOST"`
17 | DbName string `mapstructure:"DB_NAME"`
18 | AppPort string `mapstructure:"APP_PORT"`
19 | KafkaUserTopic string `mapstructure:"KAFKA_USER_TOPIC"`
20 | KafkaProductTopic string `mapstructure:"KAFKA_PRODUCT_TOPIC"`
21 | KafkaOrderTopic string `mapstructure:"KAFKA_ORDER_TOPIC"`
22 | KafkaBrokerAddress string `mapstructure:"KAFKA_BROKER_ADDRESS"`
23 | CustomerServiceEndpoint string `mapstructure:"CUSTOMER_SERVICE_ENDPOINT"`
24 | }
25 |
26 | func (c Config) GetDBURL() string {
27 | return fmt.Sprintf("host=%s port=%s user=%s dbname=%s sslmode=disable password=%s",
28 | c.DbHost, c.DbPort, c.DbUser, c.DbName, c.DbPassword)
29 | }
30 |
31 | func LoadConfig(path string) (config Config) {
32 | viper.AddConfigPath(path)
33 | viper.SetConfigName("app")
34 | viper.SetConfigType("env")
35 | viper.AutomaticEnv()
36 | viper.ReadInConfig()
37 | viper.Unmarshal(&config)
38 | AppConfig = config
39 | return AppConfig
40 | }
41 |
--------------------------------------------------------------------------------
/shared/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/suadev/microservices/shared
2 |
3 | go 1.16
4 |
5 | require (
6 | github.com/Shopify/sarama v1.28.0
7 | github.com/google/uuid v1.2.0
8 | github.com/spf13/viper v1.7.1
9 | )
10 |
--------------------------------------------------------------------------------
/shared/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/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
9 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
10 | cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
11 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
12 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
13 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
14 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
15 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
16 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
17 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
18 | github.com/Shopify/sarama v1.28.0 h1:lOi3SfE6OcFlW9Trgtked2aHNZ2BIG/d6Do+PEUAqqM=
19 | github.com/Shopify/sarama v1.28.0/go.mod h1:j/2xTrU39dlzBmsxF1eQ2/DdWrxyBCl6pzz7a81o/ZY=
20 | github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc=
21 | github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
22 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
23 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
24 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
25 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
26 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
27 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
28 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
29 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
30 | github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
31 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
32 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
33 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
34 | github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
35 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
36 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
37 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
38 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
39 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
40 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
41 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
42 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
43 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
44 | github.com/eapache/go-resiliency v1.2.0 h1:v7g92e/KSN71Rq7vSThKaWIq68fL4YHvWyiUKorFR1Q=
45 | github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
46 | github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw=
47 | github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
48 | github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
49 | github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
50 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
51 | github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
52 | github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
53 | github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY=
54 | github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
55 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
56 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
57 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
58 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
59 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
60 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
61 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
62 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
63 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
64 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
65 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
66 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
67 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
68 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
69 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
70 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
71 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
72 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
73 | github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw=
74 | github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
75 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
76 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
77 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
78 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
79 | github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
80 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
81 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
82 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
83 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
84 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
85 | github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
86 | github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
87 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
88 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
89 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
90 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
91 | github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
92 | github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
93 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
94 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
95 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
96 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
97 | github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
98 | github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
99 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
100 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
101 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
102 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
103 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
104 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
105 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
106 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
107 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
108 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
109 | github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
110 | github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
111 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
112 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
113 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
114 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
115 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
116 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
117 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
118 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
119 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
120 | github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
121 | github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
122 | github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
123 | github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
124 | github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8=
125 | github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
126 | github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
127 | github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
128 | github.com/jcmturner/gokrb5/v8 v8.4.2 h1:6ZIM6b/JJN0X8UM43ZOM6Z4SJzla+a/u7scXFJzodkA=
129 | github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc=
130 | github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
131 | github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
132 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
133 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
134 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
135 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
136 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
137 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
138 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
139 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
140 | github.com/klauspost/compress v1.11.7 h1:0hzRabrMN4tSTvMfnL3SCv1ZGeAP23ynzodBgaHeMeg=
141 | github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
142 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
143 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
144 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
145 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
146 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
147 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
148 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
149 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
150 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
151 | github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
152 | github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
153 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
154 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
155 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
156 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
157 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
158 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
159 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
160 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
161 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
162 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
163 | github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
164 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
165 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
166 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
167 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
168 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
169 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
170 | github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
171 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
172 | github.com/pierrec/lz4 v2.6.0+incompatible h1:Ix9yFKn1nSPBLFl/yZknTp8TU5G4Ps0JDmguYK6iH1A=
173 | github.com/pierrec/lz4 v2.6.0+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
174 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
175 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
176 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
177 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
178 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
179 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
180 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
181 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
182 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
183 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
184 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
185 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
186 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
187 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
188 | github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
189 | github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
190 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
191 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
192 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
193 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
194 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
195 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
196 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
197 | github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
198 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
199 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
200 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
201 | github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
202 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
203 | github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
204 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
205 | github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
206 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
207 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
208 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
209 | github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
210 | github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
211 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
212 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
213 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
214 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
215 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
216 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
217 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
218 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
219 | github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
220 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
221 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
222 | github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
223 | github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
224 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
225 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
226 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
227 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
228 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
229 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
230 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
231 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
232 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
233 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
234 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
235 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
236 | golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
237 | golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
238 | golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
239 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
240 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
241 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
242 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
243 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
244 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
245 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
246 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
247 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
248 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
249 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
250 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
251 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
252 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
253 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
254 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
255 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
256 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
257 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
258 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
259 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
260 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
261 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
262 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
263 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
264 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
265 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
266 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
267 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
268 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
269 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
270 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
271 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
272 | golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
273 | golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
274 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
275 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
276 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
277 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
278 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
279 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
280 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
281 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
282 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
283 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
284 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
285 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
286 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
287 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
288 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
289 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
290 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
291 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
292 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
293 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
294 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
295 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
296 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
297 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
298 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
299 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
300 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
301 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
302 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
303 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
304 | golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
305 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
306 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
307 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
308 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
309 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
310 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
311 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
312 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
313 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
314 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
315 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
316 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
317 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
318 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
319 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
320 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
321 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
322 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
323 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
324 | golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
325 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
326 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
327 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
328 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
329 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
330 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
331 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
332 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
333 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
334 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
335 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
336 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
337 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
338 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
339 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
340 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
341 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
342 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
343 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
344 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
345 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
346 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
347 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
348 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
349 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
350 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
351 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
352 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
353 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
354 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
355 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
356 | gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
357 | gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
358 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
359 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
360 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
361 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
362 | gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
363 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
364 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
365 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
366 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
367 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
368 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
369 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
370 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
371 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
372 |
--------------------------------------------------------------------------------
/shared/kafka/kafka.go:
--------------------------------------------------------------------------------
1 | package kafka
2 |
3 | import (
4 | "log"
5 |
6 | "github.com/Shopify/sarama"
7 | "github.com/google/uuid"
8 | "github.com/suadev/microservices/shared/config"
9 | )
10 |
11 | func Publish(key uuid.UUID, payload []byte, eventType string, topic string) {
12 |
13 | saramaConfig := sarama.NewConfig()
14 | saramaConfig.Producer.RequiredAcks = sarama.WaitForAll
15 | saramaConfig.Producer.Retry.Max = 3
16 | saramaConfig.Producer.Return.Successes = true
17 |
18 | producer, err := sarama.NewSyncProducer([]string{config.AppConfig.KafkaBrokerAddress}, saramaConfig)
19 |
20 | msg := &sarama.ProducerMessage{
21 | Topic: topic,
22 | Value: sarama.StringEncoder(payload),
23 | Key: sarama.StringEncoder(key.String()),
24 | Headers: []sarama.RecordHeader{
25 | {
26 | Key: []byte("EventType"),
27 | Value: []byte(eventType),
28 | }},
29 | }
30 |
31 | partition, offset, err := producer.SendMessage(msg)
32 | if err != nil {
33 | log.Panic(err)
34 | }
35 | log.Printf("[%s] message is stored in topic(%s)/partition(%d)/offset(%d)\n", eventType, topic, partition, offset)
36 | }
37 |
38 | func CreatePartitionConsumer(topic string) (sarama.PartitionConsumer, error) {
39 | saramaConfig := sarama.NewConfig()
40 | saramaConfig.Consumer.Return.Errors = true
41 |
42 | consumer, err := sarama.NewConsumer([]string{config.AppConfig.KafkaBrokerAddress}, saramaConfig)
43 | if err != nil {
44 | log.Panic(err)
45 | }
46 | return consumer.ConsumePartition(topic, 0, sarama.OffsetNewest)
47 | }
48 |
--------------------------------------------------------------------------------
/shared/server/server.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "context"
5 | "net/http"
6 | )
7 |
8 | type Server struct {
9 | httpServer *http.Server
10 | }
11 |
12 | func NewServer(handler http.Handler, port string) *Server {
13 | return &Server{
14 | httpServer: &http.Server{
15 | Addr: ":" + port,
16 | Handler: handler,
17 | },
18 | }
19 | }
20 |
21 | func (s *Server) Run() error {
22 | return s.httpServer.ListenAndServe()
23 | }
24 |
25 | func (s *Server) Stop(ctx context.Context) error {
26 | return s.httpServer.Shutdown(ctx)
27 | }
28 |
--------------------------------------------------------------------------------