├── .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 | --------------------------------------------------------------------------------