├── .gitignore ├── README.md ├── cmd ├── call │ └── call.go ├── cmd.go ├── completion │ ├── completion.go │ └── templates.go ├── describe │ ├── describe.go │ └── service.go ├── generate │ └── generate.go ├── go-micro │ └── main.go ├── new │ └── new.go ├── run │ └── run.go ├── services │ └── services.go └── stream │ ├── bidi.go │ ├── server.go │ └── stream.go ├── debug └── trace │ ├── jaeger │ ├── jaeger.go │ └── options.go │ └── trace.go ├── generator ├── generator.go ├── options.go └── template │ ├── docker.go │ ├── gitignore.go │ ├── handler.go │ ├── kubernetes.go │ ├── kustomize.go │ ├── main.go │ ├── makefile.go │ ├── module.go │ ├── plugins.go │ ├── proto.go │ ├── skaffold.go │ ├── sqlc.go │ ├── tern.go │ └── tilt.go ├── go.mod └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | _build 10 | .vim/ 11 | 12 | # Architecture specific extensions/prefixes 13 | *.[568vq] 14 | [568vq].out 15 | 16 | *.cgo1.go 17 | *.cgo2.c 18 | _cgo_defun.c 19 | _cgo_gotypes.go 20 | _cgo_export.* 21 | 22 | _testmain.go 23 | 24 | *.exe 25 | *.test 26 | *.prof 27 | 28 | # ignore go build and test outputs 29 | coverage.txt 30 | coverage.out 31 | 32 | # ignore locally built binaries 33 | dist 34 | micro 35 | cmd/go-micro/go-micro 36 | cmd/go-micro/go-micro-dev 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go Micro CLI 2 | 3 | Go Micro CLI is the command line interface for developing [Go Micro][1] projects. 4 | 5 | ## Getting Started 6 | 7 | [Download][2] and install **Go**. Version `1.16` or higher is required. 8 | 9 | Installation is done by using the [`go install`][3] command. 10 | 11 | ```bash 12 | go install github.com/go-micro/cli/cmd/go-micro@latest 13 | ``` 14 | 15 | Let's create a new service using the `new` command. 16 | 17 | ```bash 18 | go-micro new service helloworld 19 | ``` 20 | 21 | Follow the on-screen instructions. Next, we can run the program. 22 | 23 | ```bash 24 | cd helloworld 25 | make proto tidy 26 | go-micro run 27 | ``` 28 | 29 | Finally we can call the service. 30 | 31 | ```bash 32 | go-micro call helloworld Helloworld.Call '{"name": "John"}' 33 | ``` 34 | 35 | That's all you need to know to get started. Refer to the [Go Micro][1] 36 | documentation for more info on developing services. 37 | 38 | ## Dependencies 39 | 40 | You will need protoc for code generation. 41 | 42 | You can either install it using your pacakge manager, or manually install it by downloading the protoc zip packages 43 | (protoc-$VERSION-$PLATFORM.zip) from https://github.com/protocolbuffers/protobuf/releases/latest 44 | and installing its contents. 45 | 46 | ## Creating A Service 47 | 48 | To create a new service, use the `micro new service` command, and provide either a bare 49 | service name, or a full GitHub repo module name. 50 | 51 | ```bash 52 | $ go-micro new service github.com///helloworld 53 | ... 54 | ``` 55 | 56 | or 57 | 58 | ```bash 59 | $ go-micro new service helloworld 60 | creating service helloworld 61 | 62 | install requirements: 63 | 64 | protoc is needed for code generation. You can either install it using your 65 | pacakge manager, or manually install it by downloading the protoc zip packages 66 | (protoc-$VERSION-$PLATFORM.zip) from https://github.com/protocolbuffers/protobuf/releases/latest 67 | and installing its contents. 68 | 69 | compile the proto file helloworld-new.proto and install dependencies: 70 | 71 | cd helloworld-new 72 | make proto init update tidy 73 | ``` 74 | 75 | To create a new function, use the `micro new function` command. Functions differ 76 | from services in that they exit after returning. 77 | 78 | ```bash 79 | $ go-micro new function helloworld 80 | creating function helloworld 81 | 82 | install requirements: 83 | 84 | protoc is needed for code generation. You can either install it using your 85 | pacakge manager, or manually install it by downloading the protoc zip packages 86 | (protoc-$VERSION-$PLATFORM.zip) from https://github.com/protocolbuffers/protobuf/releases/latest 87 | and installing its contents. 88 | 89 | compile the proto file test-func.proto and install dependencies: 90 | 91 | cd test-func 92 | make init proto update tidy 93 | ``` 94 | 95 | ### Jaeger 96 | 97 | To create a new service with [Jaeger][7] integration, pass the `--jaeger` flag 98 | to the `micro new service` or `micro new function` commands. You may configure 99 | the Jaeger client using [environment variables][8]. 100 | 101 | ```bash 102 | go-micro new service --jaeger helloworld 103 | ``` 104 | 105 | You may invoke `trace.NewSpan(context.Context).Finish()` to nest spans. For 106 | example, consider the following handler implementing a greeter. 107 | 108 | `handler/helloworld.go` 109 | 110 | ```go 111 | package helloworld 112 | 113 | import ( 114 | "context" 115 | 116 | "go-micro.dev/v4/logger" 117 | 118 | "helloworld/greeter" 119 | pb "helloworld/proto" 120 | ) 121 | 122 | type Helloworld struct{} 123 | 124 | func (e *Helloworld) Call(ctx context.Context, req pb.CallRequest, rsp *pb.CallResponse) error { 125 | logger.Infof("Received Helloworld.Call request: %v", req) 126 | rsp.Msg = greeter.Greet(ctx, req.Name) 127 | return nil 128 | } 129 | ``` 130 | 131 | `greeter/greeter.go` 132 | 133 | ```go 134 | package greeter 135 | 136 | import ( 137 | "context" 138 | "fmt" 139 | 140 | "go-micro.dev/v4/cmd/micro/debug/trace" 141 | ) 142 | 143 | func Greet(ctx context.Context, name string) string { 144 | defer trace.NewSpan(ctx).Finish() 145 | return fmt.Sprint("Hello " + name) 146 | } 147 | ``` 148 | 149 | ### gRPC Server/Client 150 | 151 | By default, go-micro uses an JSON/HTTP RPC server. Many microservice use 152 | cases require a gRPC server or client, therefore, go-micro offers a gRPC server 153 | built in. 154 | 155 | To create a new service with a gRPC server pass the `--grpc` flag to 156 | the `micro new service` or `micro new function` commands. 157 | 158 | ```bash 159 | go-micro new service --grpc helloworld 160 | ``` 161 | 162 | ### Tern - Postgres Migrations 163 | 164 | Tern can be used to create and manage Postgres migrations. Go-micro can set the 165 | service up to use Tern SQL migrations. 166 | 167 | To create a new service with Tern pass the `--tern` flag to 168 | the `micro new service` or `micro new function` commands. 169 | 170 | To locally run `tern migrate` it is recommended to create a `.env` file with 171 | connection details as shown below, and manually run `source .env`, as these environment variables 172 | will also be picked up by pgx. 173 | 174 | ```env 175 | PGHOST=localhost 176 | PGUSER=helloworld 177 | PGDATABASE=helloworld 178 | PGPASSWORD= 179 | ``` 180 | 181 | Setting the `--tern` flag in combination with any of the Kubernetes flags will 182 | also create a `InitContainer` in the deployment manifest to automatically apply 183 | all migrations upon deployment. You will need to create a `helloworld-postgres-env` 184 | secret with a `PGPASSWORD` to allow tern to connect to your database instance. 185 | 186 | The default database address used is `postgres.database.svc`, and the user and 187 | database are set to the service name. To specify a different database address 188 | use the `--postgresaddress="my.namespace.svc"` flag 189 | 190 | If you are also using Kustomize to manage your Kubernetes resources, be aware 191 | that you will have to manually add every migration to `resouces/base/kustomization.yaml`. 192 | And that you will have to pass the `--load-restrictor LoadRestrictionsNone` flag 193 | to `kustomize build`, to allow Kustomize to access resources outside of the `base` 194 | folder. Tilt and Skaffold will do this for you automatically. 195 | 196 | ```bash 197 | go-micro new service --tern helloworld 198 | ``` 199 | 200 | ### sqlc - SQL Code Generation 201 | 202 | [Sqlc](14) can compile SQL queries into boilerplate Go code that allows you to easily 203 | create and manage your database layer. Go-micro can set your service up for use 204 | with sqlc, and used Postgres as a default backend. Sqlc works well in combination 205 | with [Tern](#tern---postgres-migrations). 206 | 207 | Place your SQL queries in `postgres/queries/*.sql` and run `make sqlc` to compile. 208 | Be sure you have your SQL schema defined in `postgres/migrations/*.sql`, as can 209 | be done with Tern. 210 | 211 | After compilation, you can create your database layer in `postgres/*.go` with the 212 | sqlc connector. An example is provided in `postgres/postgres.go`. 213 | 214 | To create a new service with sqlc pass the `--sqlc` flag to 215 | the `micro new service` or `micro new function` commands. 216 | 217 | ```bash 218 | go-micro new service --sqlc helloworld 219 | ``` 220 | 221 | ### Docker BuildKit 222 | 223 | [Docker BuildKit](11) is a new container build engine that provides new useful 224 | features, such as the ability to cache specific directories across builds. This 225 | can prevent Go from having to re-download modules every build. 226 | 227 | To create a new service with the BuildKit engine pass the `--buildkit` flag to 228 | the `micro new service` or `micro new function` commands. 229 | 230 | ```bash 231 | go-micro new service --buildkit helloworld 232 | ``` 233 | 234 | ### Private Git Repository 235 | 236 | If you plan on hosting the service in a private Git repository, the docker file 237 | needs some tweaks to allow Go to access and clone private repositories. 238 | 239 | For this, SSH Git access needs to be set up, and an SSH agent needs to be running, 240 | with the Git SSH key added. You can manually start an SSH agent and add the SSH key 241 | by running: 242 | 243 | ```bash 244 | $ eval $(ssh-agent) && ssh-add 245 | ``` 246 | 247 | Alternatively, you can use the generated [Tiltfile](#tilt---kubernets-deployment) 248 | to let Tilt set one up for you. 249 | 250 | To create a new service with a private Git repository pass the `--privaterepo` flag to 251 | the `micro new service` or `micro new function` commands. This implies the `--buildkit` 252 | flag. 253 | 254 | ```bash 255 | go-micro new service --privaterepo helloworld 256 | ``` 257 | 258 | ### Kubernetes 259 | 260 | Micro can automatically generate Kubernetes manifests for a service template. 261 | 262 | To create a new service with Kubernetes resources pass the `--kubernetes` flag to 263 | the `micro new service` or `micro new function` commands. 264 | 265 | ```bash 266 | go-micro new service --kubernetes helloworld 267 | ``` 268 | 269 | ### Kustomize - Kubernetes Resource Management 270 | 271 | [Kustomize](15) can be used to manage more complex Kubernetes manifests for various 272 | deployments, such as a development and production environment. 273 | 274 | To create a new service with Kubernetes resources organized in a Kustomize structure 275 | pass the `--kustomize` flag to the `micro new service` or `micro new function` 276 | commands. 277 | 278 | ```bash 279 | go-micro new service --kustomize helloworld 280 | ``` 281 | 282 | ### gRPC Health Protocol - Kubernetes Probes 283 | 284 | Since Kubernetes [1.24](12), probes can make use of the [gRPC Health Protocol](13). 285 | This allows you to directly probe the go-micro service in a Kubernetes container 286 | if it implements the health protocol. 287 | 288 | By passing the `--health` flag the gRPC protocol will be implemented, and if 289 | Kubernetes manifests are generated through any of the flags, it will add probes 290 | to the deployment manifest. 291 | 292 | To use this feature, the `GRPCContainerProbe` feature gate needs to be enabled 293 | inside your cluster. In version 1.24 this is enabled by default, in version 1.23 294 | you need to manually enable the feature gate. 295 | 296 | To create a new service with the gRPC health protocol implemented, pass the 297 | `--health` flag to the `micro new service` or `micro new function` commands. This 298 | implies the `--grpc` flag. 299 | 300 | ```bash 301 | go-micro new service --health helloworld 302 | ``` 303 | 304 | ### Kubernetes Options 305 | 306 | #### Namespace 307 | 308 | Kubernetes manifests and Kustomize files set an explicit namespace by default. 309 | The default Kubernetes namespace is `default`. You can manually specify a different 310 | namespace during service creation. 311 | 312 | ```bash 313 | go-micro new service --kustomize --namespace=custom helloworld 314 | ``` 315 | 316 | #### Postgres Address 317 | 318 | If you create the service with the `--tern` flag, the default Postgres address 319 | used is `postgres.database.svc`. To specify a different address use the 320 | `--postgresaddress` flag 321 | 322 | ```bash 323 | go-micro new service --kustomize --tern --postgresaddress="my.namespace.svc" helloworld 324 | ``` 325 | 326 | ### Tilt - Kubernetes Deployment 327 | 328 | [Tilt][16] can be used to set up a local Kubernetes deployment pipeline. 329 | 330 | To create a new service with a [Tiltfile][17] file, pass the `--tilt` flag to 331 | the `micro new service` or `micro new function` commands. 332 | 333 | This implies the `--kubernetes` flag. 334 | 335 | ```bash 336 | go-micro new service --tilt helloworld 337 | ``` 338 | 339 | ### Skaffold - Kubernetes Deployment 340 | 341 | Skaffold can be used to locally deploy a service. 342 | 343 | To create a new service with [Skaffold][9] files, pass the `--skaffold` flag to 344 | the `micro new service` or `micro new function` commands. 345 | 346 | This implies the `--kubernetes` flag. 347 | 348 | ```bash 349 | go-micro new service --skaffold helloworld 350 | ``` 351 | 352 | ### Advanced 353 | 354 | Some patterns will often occur in more complex services. Such as the need to 355 | gracefully shutdown go routines, and pass down a context to provide the cancelation 356 | signal. 357 | 358 | To prevent you from having to rewrite them for every service, you can pass the 359 | `--advanced` flag. This will generate a waitgroup, context, and define functions 360 | for `BeforeStart`, `BeforeStop` and `AfterStop`. 361 | 362 | ```bash 363 | go-micro new service --advanced helloworld 364 | ``` 365 | 366 | ### Complete 367 | 368 | With so many possible flags to create a service, the `--complete` flag will 369 | set the following flags to true: 370 | 371 | ```bash 372 | go-micro new service --jaeger --health --grpc --sqlc --tern --buildkit --kustomize --tilt --advanced 373 | ``` 374 | 375 | ## Running A Service 376 | 377 | To run a service, use the `micro run` command to build and run your service 378 | continuously. 379 | 380 | ```bash 381 | $ go-micro run 382 | 2021-08-20 14:05:54 file=v3@v3.5.2/service.go:199 level=info Starting [service] helloworld 383 | 2021-08-20 14:05:54 file=server/rpc_server.go:820 level=info Transport [http] Listening on [::]:34531 384 | 2021-08-20 14:05:54 file=server/rpc_server.go:840 level=info Broker [http] Connected to 127.0.0.1:44975 385 | 2021-08-20 14:05:54 file=server/rpc_server.go:654 level=info Registry [mdns] Registering node: helloworld-45f43a6f-5fc0-4b0d-af73-e4a10c36ef54 386 | ``` 387 | 388 | ### With Docker 389 | 390 | To run a service with Docker, build the Docker image and run the Docker 391 | container. 392 | 393 | ```bash 394 | $ make docker 395 | $ docker run helloworld:latest 396 | 2021-08-20 12:07:31 file=v3@v3.5.2/service.go:199 level=info Starting [service] helloworld 397 | 2021-08-20 12:07:31 file=server/rpc_server.go:820 level=info Transport [http] Listening on [::]:36037 398 | 2021-08-20 12:07:31 file=server/rpc_server.go:840 level=info Broker [http] Connected to 127.0.0.1:46157 399 | 2021-08-20 12:07:31 file=server/rpc_server.go:654 level=info Registry [mdns] Registering node: helloworld-31f58714-72f5-4d12-b2eb-98f66aea7a34 400 | ``` 401 | 402 | ### With Skaffold 403 | 404 | When you've created your service using the `--skaffold` flag, you may run the 405 | Skaffold pipeline using the `skaffold` command. 406 | 407 | ```bash 408 | skaffold dev 409 | ``` 410 | 411 | ### With Tilt 412 | 413 | When you've created your service using the `--tilt` flag, you may run the 414 | Tilt pipeline using the `tilt` command. 415 | 416 | ```bash 417 | tilt up --stream 418 | ``` 419 | 420 | If you don't want to stream logs, but do want to exit on errors, you can run 421 | 422 | ```bash 423 | tilt ci 424 | ``` 425 | 426 | 427 | ## Creating A Client 428 | 429 | To create a new client, use the `micro new client` command. The name is the 430 | service you'd like to create a client project for. 431 | 432 | ```bash 433 | $ go-micro new client helloworld 434 | creating client helloworld 435 | cd helloworld-client 436 | make tidy 437 | ``` 438 | 439 | You may optionally pass the fully qualified package name of the service you'd 440 | like to create a client project for. 441 | 442 | ```bash 443 | $ go-micro new client github.com/auditemarlow/helloworld 444 | creating client helloworld 445 | cd helloworld-client 446 | make tidy 447 | ``` 448 | 449 | ## Running A Client 450 | 451 | To run a client, use the `micro run` command to build and run your client 452 | continuously. 453 | 454 | ```bash 455 | $ go-micro run 456 | 2021-09-03 12:52:23 file=helloworld-client/main.go:33 level=info msg:"Hello John" 457 | ``` 458 | 459 | ## Generating Files 460 | 461 | To generate Go Micro project template files after the fact, use the `micro 462 | generate` command. It will place the generated files in the current working 463 | directory. 464 | 465 | ```bash 466 | $ go-micro generate skaffold 467 | skaffold project template files generated 468 | ``` 469 | 470 | ## Listing Services 471 | 472 | To list services, use the `micro services` command. 473 | 474 | ```bash 475 | $ go-micro services 476 | helloworld 477 | ``` 478 | 479 | ## Describing A Service 480 | 481 | To describe a service, use the `micro describe service` command. 482 | 483 | ```bash 484 | $ go-micro describe service helloworld 485 | { 486 | "name": "helloworld", 487 | "version": "latest", 488 | "metadata": null, 489 | "endpoints": [ 490 | { 491 | "name": "Helloworld.Call", 492 | "request": { 493 | "name": "CallRequest", 494 | "type": "CallRequest", 495 | "values": [ 496 | { 497 | "name": "name", 498 | "type": "string", 499 | "values": null 500 | } 501 | ] 502 | }, 503 | "response": { 504 | "name": "CallResponse", 505 | "type": "CallResponse", 506 | "values": [ 507 | { 508 | "name": "msg", 509 | "type": "string", 510 | "values": null 511 | } 512 | ] 513 | } 514 | } 515 | ], 516 | "nodes": [ 517 | { 518 | "id": "helloworld-9660f06a-d608-43d9-9f44-e264ff63c554", 519 | "address": "172.26.165.161:45059", 520 | "metadata": { 521 | "broker": "http", 522 | "protocol": "mucp", 523 | "registry": "mdns", 524 | "server": "mucp", 525 | "transport": "http" 526 | } 527 | } 528 | ] 529 | } 530 | ``` 531 | 532 | You may pass the `--format=yaml` flag to output a YAML formatted object. 533 | 534 | ```bash 535 | $ go-micro describe service --format=yaml helloworld 536 | name: helloworld 537 | version: latest 538 | metadata: {} 539 | endpoints: 540 | - name: Helloworld.Call 541 | request: 542 | name: CallRequest 543 | type: CallRequest 544 | values: 545 | - name: name 546 | type: string 547 | values: [] 548 | response: 549 | name: CallResponse 550 | type: CallResponse 551 | values: 552 | - name: msg 553 | type: string 554 | values: [] 555 | nodes: 556 | - id: helloworld-9660f06a-d608-43d9-9f44-e264ff63c554 557 | address: 172.26.165.161:45059 558 | metadata: 559 | broker: http 560 | protocol: mucp 561 | registry: mdns 562 | server: mucp 563 | transport: http 564 | ``` 565 | 566 | ## Calling A Service 567 | 568 | To call a service, use the `micro call` command. This will send a single request 569 | and expect a single response. 570 | 571 | ```bash 572 | $ go-micro call helloworld Helloworld.Call '{"name": "John"}' 573 | {"msg":"Hello John"} 574 | ``` 575 | 576 | To call a service's server stream, use the `micro stream server` command. This 577 | will send a single request and expect a stream of responses. 578 | 579 | ```bash 580 | $ go-micro stream server helloworld Helloworld.ServerStream '{"count": 10}' 581 | {"count":0} 582 | {"count":1} 583 | {"count":2} 584 | {"count":3} 585 | {"count":4} 586 | {"count":5} 587 | {"count":6} 588 | {"count":7} 589 | {"count":8} 590 | {"count":9} 591 | ``` 592 | 593 | To call a service's bidirectional stream, use the `micro stream bidi` command. 594 | This will send a stream of requests and expect a stream of responses. 595 | 596 | ```bash 597 | $ go-micro stream bidi helloworld Helloworld.BidiStream '{"stroke": 1}' '{"stroke": 2}' '{"stroke": 3}' 598 | {"stroke":1} 599 | {"stroke":2} 600 | {"stroke":3} 601 | ``` 602 | 603 | [1]: https://go-micro.dev 604 | [2]: https://golang.org/dl/ 605 | [3]: https://golang.org/cmd/go/#hdr-Compile_and_install_packages_and_dependencies 606 | [4]: https://grpc.io/docs/protoc-installation/ 607 | [5]: https://micro.mu/github.com/golang/protobuf/protoc-gen-go 608 | [6]: https://go-micro.dev/tree/master/cmd/protoc-gen-micro 609 | [7]: https://www.jaegertracing.io/ 610 | [8]: https://github.com/jaegertracing/jaeger-client-go#environment-variables 611 | [9]: https://skaffold.dev/ 612 | [10]: https://docs.tilt.dev/ 613 | [11]: https://docs.docker.com/develop/develop-images/build_enhancements/ 614 | [12]: https://kubernetes.io/blog/2022/05/13/grpc-probes-now-in-beta/ 615 | [13]: https://github.com/grpc/grpc/blob/master/doc/health-checking.md 616 | [14]: https://github.com/kyleconroy/sqlc 617 | [15]: https://kubectl.docs.kubernetes.io/references/kustomize/ 618 | [16]: https://tilt.dev/ 619 | [17]: https://docs.tilt.dev/tiltfile_authoring.html 620 | -------------------------------------------------------------------------------- /cmd/call/call.go: -------------------------------------------------------------------------------- 1 | package call 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "strings" 8 | 9 | "github.com/urfave/cli/v2" 10 | "go-micro.dev/v4" 11 | "go-micro.dev/v4/client" 12 | mcli "github.com/go-micro/cli/cmd" 13 | ) 14 | 15 | func init() { 16 | mcli.Register(&cli.Command{ 17 | Name: "call", 18 | Usage: "Call a service, e.g. " + mcli.App().Name + " call helloworld Helloworld.Call '{\"name\": \"John\"}'", 19 | Action: RunCall, 20 | }) 21 | } 22 | 23 | // RunCall calls a service endpoint and prints its response. Exits on error. 24 | func RunCall(ctx *cli.Context) error { 25 | args := ctx.Args().Slice() 26 | if len(args) < 2 { 27 | return cli.ShowSubcommandHelp(ctx) 28 | } 29 | 30 | service := args[0] 31 | endpoint := args[1] 32 | req := strings.Join(args[2:], " ") 33 | if len(req) == 0 { 34 | req = `{}` 35 | } 36 | 37 | d := json.NewDecoder(strings.NewReader(req)) 38 | d.UseNumber() 39 | 40 | var creq map[string]interface{} 41 | if err := d.Decode(&creq); err != nil { 42 | return err 43 | } 44 | 45 | srv := micro.NewService() 46 | srv.Init() 47 | c := srv.Client() 48 | 49 | request := c.NewRequest(service, endpoint, creq, client.WithContentType("application/json")) 50 | var response map[string]interface{} 51 | 52 | if err := c.Call(context.Background(), request, &response); err != nil { 53 | return err 54 | } 55 | 56 | b, err := json.Marshal(response) 57 | if err != nil { 58 | return err 59 | } 60 | 61 | fmt.Println(string(b)) 62 | return nil 63 | } 64 | -------------------------------------------------------------------------------- /cmd/cmd.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "runtime/debug" 7 | 8 | "github.com/urfave/cli/v2" 9 | mcmd "go-micro.dev/v4/cmd" 10 | ) 11 | 12 | var ( 13 | // DefaultCLI is the default, unmodified root command. 14 | DefaultCLI CLI = NewCLI() 15 | 16 | name string = os.Args[0] 17 | description string = "The Go Micro CLI tool" 18 | version string = "latest" 19 | ) 20 | 21 | // CLI is the interface that wraps the cli app. 22 | // 23 | // CLI embeds the Cmd interface from the go-micro.dev/v4/cmd 24 | // package and adds a Run method. 25 | // 26 | // Run runs the cli app within this command and exits on error. 27 | type CLI interface { 28 | mcmd.Cmd 29 | Run() error 30 | } 31 | 32 | type cmd struct { 33 | app *cli.App 34 | opts mcmd.Options 35 | } 36 | 37 | // App returns the cli app within this command. 38 | func (c *cmd) App() *cli.App { 39 | return c.app 40 | } 41 | 42 | // Options returns the options set within this command. 43 | func (c *cmd) Options() mcmd.Options { 44 | return c.opts 45 | } 46 | 47 | // Init adds options, parses flags and exits on error. 48 | func (c *cmd) Init(opts ...mcmd.Option) error { 49 | return mcmd.Init(opts...) 50 | } 51 | 52 | // Run runs the cli app within this command and exits on error. 53 | func (c *cmd) Run() error { 54 | return c.app.Run(os.Args) 55 | } 56 | 57 | // DefaultOptions returns the options passed to the default command. 58 | func DefaultOptions() mcmd.Options { 59 | return DefaultCLI.Options() 60 | } 61 | 62 | // App returns the cli app within the default command. 63 | func App() *cli.App { 64 | return DefaultCLI.App() 65 | } 66 | 67 | // Register appends commands to the default app. 68 | func Register(cmds ...*cli.Command) { 69 | app := DefaultCLI.App() 70 | app.Commands = append(app.Commands, cmds...) 71 | } 72 | 73 | // Run runs the cli app within the default command. On error, it prints the 74 | // error message and exits. 75 | func Run() { 76 | if err := DefaultCLI.Run(); err != nil { 77 | fmt.Println(err.Error()) 78 | os.Exit(1) 79 | } 80 | } 81 | 82 | // NewCLI returns a new command. 83 | func NewCLI(opts ...mcmd.Option) CLI { 84 | options := mcmd.DefaultOptions() 85 | 86 | // Clear the name, version and description parameters from the default 87 | // options so the options passed may override them. 88 | options.Name = "" 89 | options.Version = "" 90 | options.Description = "" 91 | 92 | for _, o := range opts { 93 | o(&options) 94 | } 95 | 96 | if len(options.Name) == 0 { 97 | options.Name = name 98 | } 99 | if len(options.Description) == 0 { 100 | options.Description = description 101 | } 102 | if len(options.Version) == 0 { 103 | bi, ok := debug.ReadBuildInfo() 104 | if !ok { 105 | options.Version = version 106 | } 107 | options.Version = bi.Main.Version 108 | } 109 | 110 | c := new(cmd) 111 | c.opts = options 112 | c.app = cli.NewApp() 113 | c.app.Name = c.opts.Name 114 | c.app.Usage = c.opts.Description 115 | c.app.Version = c.opts.Version 116 | c.app.Flags = mcmd.DefaultFlags 117 | c.app.EnableBashCompletion = true 118 | 119 | if len(options.Version) == 0 { 120 | c.app.HideVersion = true 121 | } 122 | 123 | return c 124 | } 125 | -------------------------------------------------------------------------------- /cmd/completion/completion.go: -------------------------------------------------------------------------------- 1 | package completion 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "text/template" 7 | 8 | mcli "github.com/go-micro/cli/cmd" 9 | "github.com/pkg/errors" 10 | "github.com/urfave/cli/v2" 11 | ) 12 | 13 | func init() { 14 | mcli.Register(&cli.Command{ 15 | Name: "completion", 16 | Usage: "Output shell completion code for the specified shell (bash or zsh)", 17 | Subcommands: []*cli.Command{ 18 | { 19 | Name: "bash", 20 | Usage: "Create completion script for bash shell. Usage: [[ /sbin/" + mcli.App().Name + " ]] && source <(" + mcli.App().Name + " completion bash)", 21 | Action: BashCompletion, 22 | }, 23 | { 24 | Name: "zsh", 25 | Usage: "Create completion script for zsh shell. Usage: [[ /sbin/" + mcli.App().Name + " ]] && source <(" + mcli.App().Name + " completion zsh)", 26 | Action: ZshCompletion, 27 | }, 28 | }, 29 | }) 30 | } 31 | 32 | func ZshCompletion(ctx *cli.Context) error { 33 | return renderTemplate(zshTemplate) 34 | } 35 | 36 | func BashCompletion(ctx *cli.Context) error { 37 | return renderTemplate(bashTemplate) 38 | } 39 | 40 | func renderTemplate(t string) error { 41 | tmpl, err := template.New("completionTemplate").Parse(t) 42 | if err != nil { 43 | return errors.Wrap(err, "Failed to parse completion template") 44 | } 45 | 46 | var b bytes.Buffer 47 | if err := tmpl.Execute(&b, map[string]interface{}{ 48 | "Prog": mcli.App().Name, 49 | }); err != nil { 50 | return errors.Wrap(err, "Failed to render completion template") 51 | } 52 | 53 | fmt.Println(b.String()) 54 | 55 | return nil 56 | } 57 | -------------------------------------------------------------------------------- /cmd/completion/templates.go: -------------------------------------------------------------------------------- 1 | package completion 2 | 3 | var bashTemplate = `#! /bin/bash 4 | 5 | _cli_bash_autocomplete() { 6 | if [[ "${COMP_WORDS[0]}" != "source" ]]; then 7 | local cur opts base 8 | COMPREPLY=() 9 | cur="${COMP_WORDS[COMP_CWORD]}" 10 | if [[ "$cur" == "-"* ]]; then 11 | opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion | awk -F':' '{ print $1}') 12 | else 13 | opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion | awk -F':' '{ print $1}') 14 | fi 15 | COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) 16 | return 0 17 | fi 18 | } 19 | 20 | complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete {{.Prog}}` 21 | 22 | var zshTemplate = `#compdef {{.Prog}} 23 | 24 | _cli_zsh_autocomplete() { 25 | 26 | local -a opts 27 | local cur 28 | cur=${words[-1]} 29 | if [[ "$cur" == "-"* ]]; then 30 | opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}") 31 | else 32 | opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-bash-completion)}") 33 | fi 34 | 35 | if [[ "${opts[1]}" != "" ]]; then 36 | _describe 'values' opts 37 | else 38 | _files 39 | fi 40 | 41 | return 42 | } 43 | 44 | compdef _cli_zsh_autocomplete {{.Prog}}` 45 | 46 | var powershellTemplate = `$fn = $($MyInvocation.MyCommand.Name) 47 | $name = $fn -replace "(.*)\.ps1$", '$1' 48 | Register-ArgumentCompleter -Native -CommandName $name -ScriptBlock { 49 | param($commandName, $wordToComplete, $cursorPosition) 50 | $other = "$wordToComplete --generate-bash-completion" 51 | Invoke-Expression $other | ForEach-Object { 52 | [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) 53 | } 54 | }` 55 | -------------------------------------------------------------------------------- /cmd/describe/describe.go: -------------------------------------------------------------------------------- 1 | package describe 2 | 3 | import ( 4 | mcli "github.com/go-micro/cli/cmd" 5 | "github.com/urfave/cli/v2" 6 | ) 7 | 8 | var flags []cli.Flag = []cli.Flag{ 9 | &cli.StringFlag{ 10 | Name: "format", 11 | Value: "json", 12 | Usage: "output a formatted description, e.g. json or yaml", 13 | }, 14 | } 15 | 16 | func init() { 17 | mcli.Register(&cli.Command{ 18 | Name: "describe", 19 | Usage: "Describe a resource", 20 | Subcommands: []*cli.Command{ 21 | { 22 | Name: "service", 23 | Aliases: []string{"s"}, 24 | Usage: "Describe a service resource, e.g. " + mcli.App().Name + " describe service helloworld", 25 | Action: Service, 26 | Flags: flags, 27 | }, 28 | }, 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /cmd/describe/service.go: -------------------------------------------------------------------------------- 1 | package describe 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | mcli "github.com/go-micro/cli/cmd" 8 | "github.com/urfave/cli/v2" 9 | "gopkg.in/yaml.v2" 10 | ) 11 | 12 | // Service fetches information for a service from the registry and prints it in 13 | // either JSON or YAML, depending on the format flag passed. Exits on error. 14 | func Service(ctx *cli.Context) error { 15 | args := ctx.Args().Slice() 16 | if len(args) < 1 { 17 | return cli.ShowSubcommandHelp(ctx) 18 | } 19 | if ctx.String("format") != "json" && ctx.String("format") != "yaml" { 20 | return cli.ShowSubcommandHelp(ctx) 21 | } 22 | 23 | r := *mcli.DefaultOptions().Registry 24 | srvs, err := r.GetService(args[0]) 25 | if err != nil { 26 | return err 27 | } 28 | if len(srvs) == 0 { 29 | return fmt.Errorf("service %s not found", args[0]) 30 | } 31 | 32 | for _, srv := range srvs { 33 | var b []byte 34 | var err error 35 | if ctx.String("format") == "json" { 36 | b, err = json.MarshalIndent(srv, "", " ") 37 | } else if ctx.String("format") == "yaml" { 38 | b, err = yaml.Marshal(srv) 39 | } 40 | if err != nil { 41 | return err 42 | } 43 | fmt.Println(string(b)) 44 | } 45 | 46 | return nil 47 | } 48 | -------------------------------------------------------------------------------- /cmd/generate/generate.go: -------------------------------------------------------------------------------- 1 | package generate 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | "strings" 8 | 9 | mcli "github.com/go-micro/cli/cmd" 10 | "github.com/go-micro/cli/generator" 11 | tmpl "github.com/go-micro/cli/generator/template" 12 | "github.com/urfave/cli/v2" 13 | ) 14 | 15 | func init() { 16 | mcli.Register(&cli.Command{ 17 | Name: "generate", 18 | Usage: "Generate project template files after the fact", 19 | Subcommands: []*cli.Command{ 20 | { 21 | Name: "kubernetes", 22 | Usage: "Generate Kubernetes resource template files", 23 | Action: Kubernetes, 24 | }, 25 | { 26 | Name: "skaffold", 27 | Usage: "Generate Skaffold template files", 28 | Action: Skaffold, 29 | }, 30 | { 31 | Name: "sqlc", 32 | Usage: "Generate sqlc resources", 33 | Action: Sqlc, 34 | }, 35 | }, 36 | }) 37 | } 38 | 39 | // Kubernetes generates Kubernetes resource template files in the current 40 | // working directory. Exits on error. 41 | func Kubernetes(ctx *cli.Context) error { 42 | service, err := getService() 43 | if err != nil { 44 | return err 45 | } 46 | 47 | vendor, err := getServiceVendor(service) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | g := generator.New( 53 | generator.Service(service), 54 | generator.Vendor(vendor), 55 | generator.Directory("."), 56 | generator.Client(strings.HasSuffix(service, "-client")), 57 | ) 58 | 59 | files := []generator.File{ 60 | {Path: "plugins.go", Template: tmpl.Plugins}, 61 | {Path: "resources/clusterrole.yaml", Template: tmpl.KubernetesClusterRole}, 62 | {Path: "resources/configmap.yaml", Template: tmpl.KubernetesEnv}, 63 | {Path: "resources/deployment.yaml", Template: tmpl.KubernetesDeployment}, 64 | {Path: "resources/rolebinding.yaml", Template: tmpl.KubernetesRoleBinding}, 65 | } 66 | 67 | g.Generate(files) 68 | 69 | return nil 70 | } 71 | 72 | // Skaffold generates Skaffold template files in the current working directory. 73 | // Exits on error. 74 | func Skaffold(ctx *cli.Context) error { 75 | service, err := getService() 76 | if err != nil { 77 | return err 78 | } 79 | 80 | vendor, err := getServiceVendor(service) 81 | if err != nil { 82 | return err 83 | } 84 | 85 | g := generator.New( 86 | generator.Service(service), 87 | generator.Vendor(vendor), 88 | generator.Directory("."), 89 | generator.Client(strings.HasSuffix(service, "-client")), 90 | generator.Skaffold(true), 91 | ) 92 | 93 | files := []generator.File{ 94 | {Path: ".dockerignore", Template: tmpl.DockerIgnore}, 95 | {Path: "go.mod", Template: tmpl.Module}, 96 | {Path: "plugins.go", Template: tmpl.Plugins}, 97 | {Path: "resources/clusterrole.yaml", Template: tmpl.KubernetesClusterRole}, 98 | {Path: "resources/configmap.yaml", Template: tmpl.KubernetesEnv}, 99 | {Path: "resources/deployment.yaml", Template: tmpl.KubernetesDeployment}, 100 | {Path: "resources/rolebinding.yaml", Template: tmpl.KubernetesRoleBinding}, 101 | {Path: "skaffold.yaml", Template: tmpl.SkaffoldCFG}, 102 | } 103 | 104 | if err := g.Generate(files); err != nil { 105 | return err 106 | } 107 | 108 | fmt.Println("skaffold project template files generated") 109 | 110 | return nil 111 | } 112 | 113 | // Sqlc generates sqlc files in the current working directory. 114 | // Exits on error. 115 | func Sqlc(ctx *cli.Context) error { 116 | service, err := getService() 117 | if err != nil { 118 | return err 119 | } 120 | 121 | vendor, err := getServiceVendor(service) 122 | if err != nil { 123 | return err 124 | } 125 | 126 | g := generator.New( 127 | generator.Service(service), 128 | generator.Vendor(vendor), 129 | generator.Directory("."), 130 | generator.Client(strings.HasSuffix(service, "-client")), 131 | generator.Sqlc(true), 132 | ) 133 | 134 | files := []generator.File{ 135 | {Path: "postgres/queries/example.sql", Template: tmpl.QueryExample}, 136 | {Path: "postgres/migrations/", Template: ""}, 137 | } 138 | 139 | path := "postgres/postgres.go" 140 | if _, err := os.Stat("./" + path); err != nil { 141 | files = append(files, generator.File{Path: path, Template: tmpl.Postgres}) 142 | } 143 | 144 | path = "postgres/sqlc.yaml" 145 | if _, err := os.Stat("./" + path); err != nil { 146 | files = append(files, generator.File{Path: path, Template: tmpl.Sqlc}) 147 | } 148 | 149 | if err := g.Generate(files); err != nil { 150 | return err 151 | } 152 | 153 | fmt.Println("Sqlc project template files generated") 154 | 155 | return nil 156 | } 157 | 158 | func getService() (string, error) { 159 | dir, err := os.Getwd() 160 | if err != nil { 161 | return "", err 162 | } 163 | return dir[strings.LastIndex(dir, "/")+1:], nil 164 | } 165 | 166 | func getServiceVendor(s string) (string, error) { 167 | f, err := os.Open("go.mod") 168 | if err != nil { 169 | return "", err 170 | } 171 | defer f.Close() 172 | 173 | line := "" 174 | scanner := bufio.NewScanner(f) 175 | for scanner.Scan() { 176 | if strings.HasPrefix(scanner.Text(), "module ") { 177 | line = scanner.Text() 178 | break 179 | 180 | } 181 | } 182 | if line == "" { 183 | return "", nil 184 | } 185 | 186 | module := line[strings.LastIndex(line, " ")+1:] 187 | if module == s { 188 | return "", nil 189 | } 190 | 191 | return module[:strings.LastIndex(module, "/")] + "/", nil 192 | } 193 | -------------------------------------------------------------------------------- /cmd/go-micro/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/go-micro/cli/cmd" 5 | 6 | // register commands 7 | _ "github.com/go-micro/cli/cmd/call" 8 | _ "github.com/go-micro/cli/cmd/completion" 9 | _ "github.com/go-micro/cli/cmd/describe" 10 | _ "github.com/go-micro/cli/cmd/generate" 11 | _ "github.com/go-micro/cli/cmd/new" 12 | _ "github.com/go-micro/cli/cmd/run" 13 | _ "github.com/go-micro/cli/cmd/services" 14 | _ "github.com/go-micro/cli/cmd/stream" 15 | 16 | // plugins 17 | _ "github.com/go-micro/plugins/v4/registry/kubernetes" 18 | ) 19 | 20 | func main() { 21 | cmd.Run() 22 | } 23 | -------------------------------------------------------------------------------- /cmd/new/new.go: -------------------------------------------------------------------------------- 1 | package new 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os" 7 | "path" 8 | "strings" 9 | "text/template" 10 | 11 | "github.com/pkg/errors" 12 | "github.com/urfave/cli/v2" 13 | 14 | mcli "github.com/go-micro/cli/cmd" 15 | "github.com/go-micro/cli/generator" 16 | tmpl "github.com/go-micro/cli/generator/template" 17 | ) 18 | 19 | var flags []cli.Flag = []cli.Flag{ 20 | &cli.BoolFlag{ 21 | Name: "jaeger", 22 | Usage: "Generate Jaeger tracer files", 23 | }, 24 | &cli.BoolFlag{ 25 | Name: "kubernetes", 26 | Usage: "Generate Kubernetes resource files", 27 | }, 28 | &cli.BoolFlag{ 29 | Name: "skaffold", 30 | Usage: "Generate Skaffold files", 31 | }, 32 | &cli.BoolFlag{ 33 | Name: "tilt", 34 | Usage: "Generate Tiltfile", 35 | }, 36 | &cli.BoolFlag{ 37 | Name: "health", 38 | Usage: "Generate gRPC Health service used for Kubernetes liveliness and readiness probes", 39 | }, 40 | &cli.BoolFlag{ 41 | Name: "kustomize", 42 | Usage: "Generate kubernetes resouce files in a kustomize structure", 43 | }, 44 | &cli.BoolFlag{ 45 | Name: "sqlc", 46 | Usage: "Generate sqlc resources", 47 | }, 48 | &cli.BoolFlag{ 49 | Name: "grpc", 50 | Usage: "Use gRPC as default server and client", 51 | }, 52 | &cli.BoolFlag{ 53 | Name: "buildkit", 54 | Usage: "Use BuildKit features in Dockerfile", 55 | }, 56 | &cli.BoolFlag{ 57 | Name: "tern", 58 | Usage: "Generate tern resouces; sql migrations templates", 59 | }, 60 | &cli.BoolFlag{ 61 | Name: "advanced", 62 | Usage: "Generate advanced features in main.go server file", 63 | }, 64 | &cli.BoolFlag{ 65 | Name: "privaterepo", 66 | Usage: "Amend Dockerfile to build from private repositories (add ssh-agent)", 67 | }, 68 | &cli.StringFlag{ 69 | Name: "namespace", 70 | Usage: "Default namespace for kubernetes resources, defaults to 'default'", 71 | Value: "default", 72 | }, 73 | &cli.StringFlag{ 74 | Name: "postgresaddress", 75 | Usage: "Default postgres address for kubernetes resources, defaults to postgres.database.svc", 76 | Value: "postgres.database.svc", 77 | }, 78 | &cli.BoolFlag{ 79 | Name: "complete", 80 | Usage: "Complete will set the following flags to true; jaeger, health, grpc, sqlc, tern, kustomize, tilt, advanced", 81 | }, 82 | } 83 | 84 | // NewCommand returns a new new cli command. 85 | func init() { 86 | mcli.Register(&cli.Command{ 87 | Name: "new", 88 | Usage: "Create a project template", 89 | Subcommands: []*cli.Command{ 90 | { 91 | Name: "client", 92 | Usage: "Create a client template, e.g. " + mcli.App().Name + " new client [github.com/auditemarlow/]helloworld", 93 | Action: Client, 94 | Flags: flags, 95 | }, 96 | { 97 | Name: "function", 98 | Usage: "Create a function template, e.g. " + mcli.App().Name + " new function [github.com/auditemarlow/]helloworld", 99 | Action: Function, 100 | Flags: flags, 101 | }, 102 | { 103 | Name: "service", 104 | Usage: "Create a service template, e.g. " + mcli.App().Name + " new service [github.com/auditemarlow/]helloworld", 105 | Action: Service, 106 | Flags: flags, 107 | }, 108 | }, 109 | }) 110 | } 111 | 112 | func Client(ctx *cli.Context) error { 113 | return createProject(ctx, "client") 114 | } 115 | 116 | // Function creates a new function project template. Exits on error. 117 | func Function(ctx *cli.Context) error { 118 | return createProject(ctx, "function") 119 | } 120 | 121 | // Service creates a new service project template. Exits on error. 122 | func Service(ctx *cli.Context) error { 123 | return createProject(ctx, "service") 124 | } 125 | 126 | func createProject(ctx *cli.Context, pt string) error { 127 | arg := ctx.Args().First() 128 | if len(arg) == 0 { 129 | return cli.ShowSubcommandHelp(ctx) 130 | } 131 | 132 | client := pt == "client" 133 | name, vendor := getNameAndVendor(arg) 134 | 135 | dir := name 136 | if client { 137 | dir += "-client" 138 | } 139 | 140 | if path.IsAbs(dir) { 141 | fmt.Println("must provide a relative path as service name") 142 | return nil 143 | } 144 | 145 | if _, err := os.Stat(dir); !os.IsNotExist(err) { 146 | return fmt.Errorf("%s already exists", dir) 147 | } 148 | 149 | fmt.Printf("creating %s %s\n", pt, name) 150 | 151 | g := generator.New( 152 | generator.Service(name), 153 | generator.Vendor(vendor), 154 | generator.Directory(dir), 155 | generator.Client(client), 156 | generator.Jaeger(ctx.Bool("jaeger") || ctx.Bool("complete")), 157 | generator.Skaffold(ctx.Bool("skaffold")), 158 | generator.Tilt(ctx.Bool("tilt") || ctx.Bool("complete")), 159 | generator.Health(ctx.Bool("health") || ctx.Bool("complete")), 160 | generator.Kustomize(ctx.Bool("kustomize") || ctx.Bool("complete")), 161 | generator.Sqlc(ctx.Bool("sqlc") || ctx.Bool("complete")), 162 | generator.GRPC(ctx.Bool("grpc") || ctx.Bool("health") || ctx.Bool("complete")), 163 | generator.Buildkit(ctx.Bool("buildkit") || ctx.Bool("privaterepo") || ctx.Bool("complete")), 164 | generator.Tern(ctx.Bool("tern") || ctx.Bool("complete")), 165 | generator.Advanced(ctx.Bool("advanced") || ctx.Bool("complete")), 166 | generator.PrivateRepo(ctx.Bool("privaterepo")), 167 | generator.Namespace(ctx.String("namespace")), 168 | generator.PostgresAddress(ctx.String("postgresaddress")), 169 | ) 170 | 171 | files := []generator.File{ 172 | {Path: ".dockerignore", Template: tmpl.DockerIgnore}, 173 | {Path: ".gitignore", Template: tmpl.GitIgnore}, 174 | {Path: "Dockerfile", Template: tmpl.Dockerfile}, 175 | {Path: "Makefile", Template: tmpl.Makefile}, 176 | {Path: "go.mod", Template: tmpl.Module}, 177 | } 178 | 179 | switch pt { 180 | case "client": 181 | files = append(files, []generator.File{ 182 | {Path: "main.go", Template: tmpl.MainCLT}, 183 | }...) 184 | case "function": 185 | files = append(files, []generator.File{ 186 | {Path: "handler/" + name + ".go", Template: tmpl.HandlerFNC}, 187 | {Path: "main.go", Template: tmpl.MainFNC}, 188 | {Path: "proto/" + name + ".proto", Template: tmpl.ProtoFNC}, 189 | }...) 190 | case "service": 191 | files = append(files, []generator.File{ 192 | {Path: "handler/" + name + ".go", Template: tmpl.HandlerSRV}, 193 | {Path: "main.go", Template: tmpl.MainSRV}, 194 | {Path: "proto/" + name + ".proto", Template: tmpl.ProtoSRV}, 195 | }...) 196 | default: 197 | return fmt.Errorf("%s project type not supported", pt) 198 | } 199 | 200 | opts := g.Options() 201 | if opts.Sqlc { 202 | files = append(files, []generator.File{ 203 | {Path: "postgres/sqlc.yaml", Template: tmpl.Sqlc}, 204 | {Path: "postgres/postgres.go", Template: tmpl.Postgres}, 205 | {Path: "postgres/queries/example.sql", Template: tmpl.QueryExample}, 206 | {Path: "postgres/migrations/", Template: ""}, 207 | }...) 208 | } 209 | 210 | if opts.Tern { 211 | files = append(files, []generator.File{ 212 | {Path: "postgres/migrations/001_create_schema.sql", Template: tmpl.TernSql}, 213 | }...) 214 | } 215 | 216 | if opts.Health { 217 | files = append(files, []generator.File{ 218 | {Path: "proto/health.proto", Template: tmpl.ProtoHEALTH}, 219 | {Path: "handler/health.go", Template: tmpl.HealthSRV}, 220 | }...) 221 | } 222 | 223 | if (ctx.Bool("kubernetes") || opts.Skaffold || opts.Tilt) && !opts.Kustomize { 224 | files = append(files, []generator.File{ 225 | {Path: "plugins.go", Template: tmpl.Plugins}, 226 | {Path: "resources/clusterrole.yaml", Template: tmpl.KubernetesClusterRole}, 227 | {Path: "resources/configmap.yaml", Template: tmpl.KubernetesEnv}, 228 | {Path: "resources/deployment.yaml", Template: tmpl.KubernetesDeployment}, 229 | {Path: "resources/rolebinding.yaml", Template: tmpl.KubernetesRoleBinding}, 230 | }...) 231 | } 232 | 233 | if opts.Kustomize { 234 | files = append(files, []generator.File{ 235 | {Path: "plugins.go", Template: tmpl.Plugins}, 236 | {Path: "resources/base/clusterrole.yaml", Template: tmpl.KubernetesClusterRole}, 237 | {Path: "resources/base/app.env", Template: tmpl.AppEnv}, 238 | {Path: "resources/base/deployment.yaml", Template: tmpl.KubernetesDeployment}, 239 | {Path: "resources/base/rolebinding.yaml", Template: tmpl.KubernetesRoleBinding}, 240 | {Path: "resources/base/kustomization.yaml", Template: tmpl.KustomizationBase}, 241 | {Path: "resources/dev/kustomization.yaml", Template: tmpl.KustomizationDev}, 242 | {Path: "resources/prod/kustomization.yaml", Template: tmpl.KustomizationProd}, 243 | }...) 244 | } 245 | 246 | if opts.Skaffold { 247 | files = append(files, []generator.File{ 248 | {Path: "skaffold.yaml", Template: tmpl.SkaffoldCFG}, 249 | }...) 250 | } 251 | 252 | if opts.Tilt { 253 | files = append(files, []generator.File{ 254 | {Path: "Tiltfile", Template: tmpl.Tiltfile}, 255 | }...) 256 | } 257 | 258 | if err := g.Generate(files); err != nil { 259 | return err 260 | } 261 | 262 | var comments []string 263 | if client { 264 | comments = clientComments(name, dir) 265 | } else { 266 | var err error 267 | comments, err = protoComments(name, dir, opts.Sqlc) 268 | if err != nil { 269 | return err 270 | } 271 | } 272 | 273 | for _, comment := range comments { 274 | fmt.Println(comment) 275 | } 276 | 277 | return nil 278 | } 279 | 280 | func clientComments(name, dir string) []string { 281 | return []string{ 282 | "\ninstall dependencies:", 283 | "\ncd " + dir, 284 | "make update tidy", 285 | } 286 | } 287 | 288 | func protoComments(name, dir string, sqlc bool) ([]string, error) { 289 | tmp := ` 290 | install requirements: 291 | 292 | protoc is needed for code generation. You can either install it using your 293 | pacakge manager, or manually install it by downloading the protoc zip packages 294 | (protoc-$VERSION-$PLATFORM.zip) from https://github.com/protocolbuffers/protobuf/releases/latest 295 | and installing its contents. 296 | 297 | compile the proto file {{ .Name }}.proto and install dependencies: 298 | 299 | cd {{ .Dir }} 300 | make init proto {{ if .Sqlc }}sqlc {{ end }}update tidy` 301 | 302 | t, err := template.New("comments").Parse(tmp) 303 | if err != nil { 304 | return nil, errors.Wrap(err, "Failed to parse comments template") 305 | } 306 | 307 | var b bytes.Buffer 308 | if err := t.Execute(&b, map[string]interface{}{ 309 | "Name": name, 310 | "Dir": dir, 311 | "Sqlc": sqlc, 312 | }); err != nil { 313 | return nil, errors.Wrap(err, "Failed to execute proto comments template") 314 | } 315 | return []string{b.String()}, nil 316 | } 317 | 318 | func getNameAndVendor(s string) (string, string) { 319 | var n string 320 | var v string 321 | 322 | if i := strings.LastIndex(s, "/"); i == -1 { 323 | n = s 324 | v = "" 325 | } else { 326 | n = s[i+1:] 327 | v = s[:i+1] 328 | } 329 | 330 | return n, v 331 | } 332 | -------------------------------------------------------------------------------- /cmd/run/run.go: -------------------------------------------------------------------------------- 1 | package run 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/signal" 7 | "path/filepath" 8 | "strings" 9 | 10 | "github.com/fsnotify/fsnotify" 11 | "github.com/urfave/cli/v2" 12 | mcli "github.com/go-micro/cli/cmd" 13 | "go-micro.dev/v4/runtime" 14 | "go-micro.dev/v4/runtime/local/git" 15 | ) 16 | 17 | var ( 18 | DefaultRetries = 3 19 | 20 | flags []cli.Flag = []cli.Flag{ 21 | &cli.StringFlag{ 22 | Name: "command", 23 | Usage: "command to execute", 24 | }, 25 | &cli.StringFlag{ 26 | Name: "args", 27 | Usage: "command arguments", 28 | }, 29 | &cli.StringFlag{ 30 | Name: "type", 31 | Usage: "the type of service to operate on", 32 | }, 33 | } 34 | ) 35 | 36 | func init() { 37 | mcli.Register(&cli.Command{ 38 | Name: "run", 39 | Usage: "Build and run a service continuously, e.g. " + mcli.App().Name + " run [github.com/auditemarlow/helloworld]", 40 | Flags: flags, 41 | Action: Run, 42 | }) 43 | } 44 | 45 | // Run runs a service and watches the project directory for change events. On 46 | // write, the service is restarted. Exits on error. 47 | func Run(ctx *cli.Context) error { 48 | wd, err := os.Getwd() 49 | if err != nil { 50 | return err 51 | } 52 | 53 | source, err := git.ParseSourceLocal(wd, ctx.Args().First()) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | svc := &runtime.Service{ 59 | Name: source.RuntimeName(), 60 | Source: source.RuntimeSource(), 61 | Version: source.Ref, 62 | Metadata: make(map[string]string), 63 | } 64 | 65 | typ := ctx.String("type") 66 | command := strings.TrimSpace(ctx.String("command")) 67 | args := strings.TrimSpace(ctx.String("args")) 68 | 69 | r := *mcli.DefaultCLI.Options().Runtime 70 | 71 | var retries = DefaultRetries 72 | if ctx.IsSet("retries") { 73 | retries = ctx.Int("retries") 74 | } 75 | 76 | opts := []runtime.CreateOption{ 77 | runtime.WithOutput(os.Stdout), 78 | runtime.WithRetries(retries), 79 | runtime.CreateType(typ), 80 | } 81 | 82 | if len(command) > 0 { 83 | opts = append(opts, runtime.WithCommand(command)) 84 | } 85 | 86 | if len(args) > 0 { 87 | opts = append(opts, runtime.WithArgs(args)) 88 | } 89 | 90 | if err := r.Create(svc, opts...); err != nil { 91 | return err 92 | } 93 | 94 | done := make(chan bool) 95 | if r.String() == "local" { 96 | sig := make(chan os.Signal) 97 | signal.Notify(sig, os.Interrupt) 98 | go func() { 99 | <-sig 100 | r.Delete(svc) 101 | done <- true 102 | }() 103 | } 104 | 105 | if source.Local { 106 | watcher, err := fsnotify.NewWatcher() 107 | if err != nil { 108 | fmt.Println(err) 109 | } 110 | defer watcher.Close() 111 | 112 | go func() { 113 | for { 114 | select { 115 | case event, ok := <-watcher.Events: 116 | if !ok { 117 | return 118 | } 119 | if event.Op&fsnotify.Write == fsnotify.Write { 120 | r.Update(svc) 121 | } 122 | if event.Op&fsnotify.Create == fsnotify.Create { 123 | watcher.Add(event.Name) 124 | } 125 | if event.Op&fsnotify.Remove == fsnotify.Remove { 126 | watcher.Remove(event.Name) 127 | } 128 | case err, ok := <-watcher.Errors: 129 | if !ok { 130 | return 131 | } 132 | fmt.Println("ERROR", err) 133 | } 134 | } 135 | }() 136 | 137 | var files []string 138 | filepath.Walk(".", func(path string, info os.FileInfo, err error) error { 139 | if err != nil { 140 | return err 141 | } 142 | files = append(files, path) 143 | return nil 144 | }) 145 | 146 | for _, file := range files { 147 | if err := watcher.Add(file); err != nil { 148 | return err 149 | } 150 | } 151 | } 152 | 153 | if r.String() == "local" { 154 | <-done 155 | } 156 | 157 | return nil 158 | } 159 | -------------------------------------------------------------------------------- /cmd/services/services.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | 7 | "github.com/urfave/cli/v2" 8 | mcli "github.com/go-micro/cli/cmd" 9 | ) 10 | 11 | func init() { 12 | mcli.Register(&cli.Command{ 13 | Name: "services", 14 | Usage: "List services in the registry", 15 | Action: List, 16 | }) 17 | } 18 | 19 | // List fetches running services from the registry and lists them. Exits on 20 | // error. 21 | func List(ctx *cli.Context) error { 22 | r := *mcli.DefaultOptions().Registry 23 | srvs, err := r.ListServices() 24 | if err != nil { 25 | return err 26 | } 27 | 28 | var services []string 29 | for _, srv := range srvs { 30 | services = append(services, srv.Name) 31 | } 32 | 33 | sort.Strings(services) 34 | for _, srv := range services { 35 | fmt.Println(srv) 36 | } 37 | 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /cmd/stream/bidi.go: -------------------------------------------------------------------------------- 1 | package stream 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/urfave/cli/v2" 9 | "go-micro.dev/v4" 10 | "go-micro.dev/v4/client" 11 | ) 12 | 13 | // Bidirectional streams client requests and prints the server stream responses 14 | // it receives. Exits on error. 15 | func Bidirectional(ctx *cli.Context) error { 16 | args := ctx.Args().Slice() 17 | if len(args) < 2 { 18 | return cli.ShowSubcommandHelp(ctx) 19 | } 20 | 21 | service := args[0] 22 | endpoint := args[1] 23 | requests := args[2:] 24 | 25 | srv := micro.NewService() 26 | srv.Init() 27 | c := srv.Client() 28 | 29 | var r interface{} 30 | request := c.NewRequest(service, endpoint, r, client.WithContentType("application/json")) 31 | var rsp map[string]interface{} 32 | stream, err := c.Stream(ctx.Context, request) 33 | if err != nil { 34 | return err 35 | } 36 | 37 | for _, req := range requests { 38 | d := json.NewDecoder(strings.NewReader(req)) 39 | d.UseNumber() 40 | 41 | var creq map[string]interface{} 42 | if err := d.Decode(&creq); err != nil { 43 | return err 44 | } 45 | 46 | if err := stream.Send(creq); err != nil { 47 | return err 48 | } 49 | 50 | err := stream.Recv(&rsp) 51 | if err != nil { 52 | return err 53 | } 54 | 55 | b, err := json.Marshal(rsp) 56 | if err != nil { 57 | return err 58 | } 59 | fmt.Println(string(b)) 60 | } 61 | if stream.Error() != nil { 62 | return stream.Error() 63 | } 64 | if err := stream.Close(); err != nil { 65 | return err 66 | } 67 | 68 | return nil 69 | } 70 | -------------------------------------------------------------------------------- /cmd/stream/server.go: -------------------------------------------------------------------------------- 1 | package stream 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "strings" 9 | 10 | "github.com/urfave/cli/v2" 11 | "go-micro.dev/v4" 12 | "go-micro.dev/v4/client" 13 | ) 14 | 15 | // Server sends a single client request and prints the server stream responses 16 | // it receives. Exits on error. 17 | func Server(ctx *cli.Context) error { 18 | args := ctx.Args().Slice() 19 | if len(args) < 2 { 20 | return cli.ShowSubcommandHelp(ctx) 21 | } 22 | 23 | service := args[0] 24 | endpoint := args[1] 25 | req := strings.Join(args[2:], " ") 26 | if len(req) == 0 { 27 | req = `{}` 28 | } 29 | 30 | d := json.NewDecoder(strings.NewReader(req)) 31 | d.UseNumber() 32 | 33 | var creq map[string]interface{} 34 | if err := d.Decode(&creq); err != nil { 35 | return err 36 | } 37 | 38 | srv := micro.NewService() 39 | srv.Init() 40 | c := srv.Client() 41 | 42 | var r interface{} 43 | request := c.NewRequest(service, endpoint, r, client.WithContentType("application/json")) 44 | 45 | stream, err := c.Stream(context.Background(), request) 46 | if err != nil { 47 | return err 48 | } 49 | if err := stream.Send(creq); err != nil { 50 | return err 51 | } 52 | 53 | for stream.Error() == nil { 54 | rsp := &map[string]interface{}{} 55 | err := stream.Recv(rsp) 56 | if err == io.EOF { 57 | return nil 58 | } 59 | if err != nil { 60 | return err 61 | } 62 | b, err := json.Marshal(rsp) 63 | if err != nil { 64 | return err 65 | } 66 | fmt.Println(string(b)) 67 | } 68 | if stream.Error() != nil { 69 | return stream.Error() 70 | } 71 | if err := stream.Close(); err != nil { 72 | return err 73 | } 74 | 75 | return nil 76 | } 77 | -------------------------------------------------------------------------------- /cmd/stream/stream.go: -------------------------------------------------------------------------------- 1 | package stream 2 | 3 | import ( 4 | "github.com/urfave/cli/v2" 5 | mcli "github.com/go-micro/cli/cmd" 6 | ) 7 | 8 | func init() { 9 | mcli.Register(&cli.Command{ 10 | Name: "stream", 11 | Usage: "Create a service stream", 12 | Subcommands: []*cli.Command{ 13 | { 14 | Name: "bidi", 15 | Aliases: []string{"b"}, 16 | Usage: "Create a bidirectional service stream, e.g. " + mcli.App().Name + " stream bidirectional helloworld Helloworld.PingPong '{\"stroke\": 1}' '{\"stroke\": 2}'", 17 | Action: Bidirectional, 18 | }, 19 | { 20 | Name: "server", 21 | Aliases: []string{"s"}, 22 | Usage: "Create a server service stream, e.g. " + mcli.App().Name + " stream server helloworld Helloworld.ServerStream '{\"count\": 10}'", 23 | Action: Server, 24 | }, 25 | }, 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /debug/trace/jaeger/jaeger.go: -------------------------------------------------------------------------------- 1 | package jaeger 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/opentracing/opentracing-go" 7 | jaegercfg "github.com/uber/jaeger-client-go/config" 8 | ) 9 | 10 | // NewTracer returns a new Jaeger tracer based on the current configuration, 11 | // using the given options, and a closer func that can be used to flush buffers 12 | // before shutdown. 13 | func NewTracer(opts ...Option) (opentracing.Tracer, io.Closer, error) { 14 | options := newOptions(opts...) 15 | 16 | cfg := &jaegercfg.Configuration{} 17 | if options.FromEnv { 18 | c, err := jaegercfg.FromEnv() 19 | if err != nil { 20 | return nil, nil, err 21 | } 22 | cfg = c 23 | } 24 | 25 | if options.Name != "" { 26 | cfg.ServiceName = options.Name 27 | } 28 | 29 | var jOptions []jaegercfg.Option 30 | if options.Logger != nil { 31 | jOptions = append(jOptions, jaegercfg.Logger(options.Logger)) 32 | } 33 | if options.Metrics != nil { 34 | jOptions = append(jOptions, jaegercfg.Metrics(options.Metrics)) 35 | } 36 | 37 | tracer, closer, err := cfg.NewTracer(jOptions...) 38 | if err != nil { 39 | return nil, nil, err 40 | } 41 | 42 | if options.GlobalTracer { 43 | opentracing.SetGlobalTracer(tracer) 44 | } 45 | 46 | return tracer, closer, nil 47 | } 48 | -------------------------------------------------------------------------------- /debug/trace/jaeger/options.go: -------------------------------------------------------------------------------- 1 | package jaeger 2 | 3 | import ( 4 | "github.com/uber/jaeger-client-go" 5 | "github.com/uber/jaeger-lib/metrics" 6 | ) 7 | 8 | var ( 9 | // DefaultLogger is the default Jaeger logger. 10 | DefaultLogger = jaeger.StdLogger 11 | 12 | // DefaultMetrics is the default Jaeger metrics factory. 13 | DefaultMetrics = metrics.NullFactory 14 | ) 15 | 16 | // Options represents the options passed to the Jaeger tracer. 17 | type Options struct { 18 | Name string 19 | FromEnv bool 20 | GlobalTracer bool 21 | Logger jaeger.Logger 22 | Metrics metrics.Factory 23 | } 24 | 25 | // Option manipulates the passed Options struct. 26 | type Option func(o *Options) 27 | 28 | func newOptions(opts ...Option) Options { 29 | options := Options{ 30 | Logger: DefaultLogger, 31 | Metrics: DefaultMetrics, 32 | } 33 | 34 | for _, o := range opts { 35 | o(&options) 36 | } 37 | 38 | return options 39 | } 40 | 41 | // Name sets the service name for the Jaeger tracer. 42 | func Name(s string) Option { 43 | return func(o *Options) { 44 | o.Name = s 45 | } 46 | } 47 | 48 | // FromEnv determines whether the Jaeger tracer configuration should use 49 | // environment variables. 50 | func FromEnv(e bool) Option { 51 | return func(o *Options) { 52 | o.FromEnv = e 53 | } 54 | } 55 | 56 | // GlobalTracer determines whether the Jaeger tracer should be set as the 57 | // global tracer. 58 | func GlobalTracer(e bool) Option { 59 | return func(o *Options) { 60 | o.GlobalTracer = e 61 | } 62 | } 63 | 64 | // Logger sets the logger for the Jaeger tracer. 65 | func Logger(l jaeger.Logger) Option { 66 | return func(o *Options) { 67 | o.Logger = l 68 | } 69 | } 70 | 71 | // Metrics sets the metrics factory for the Jaeger tracer. 72 | func Metrics(m metrics.Factory) Option { 73 | return func(o *Options) { 74 | o.Metrics = m 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /debug/trace/trace.go: -------------------------------------------------------------------------------- 1 | package trace 2 | 3 | import ( 4 | "context" 5 | "runtime" 6 | 7 | "github.com/opentracing/opentracing-go" 8 | ) 9 | 10 | // NewSpan accepts a context and returns an OpenTracing span. Can be used to 11 | // nest spans. 12 | func NewSpan(ctx context.Context) opentracing.Span { 13 | pc := make([]uintptr, 10) // at least 1 entry needed 14 | runtime.Callers(2, pc) 15 | span := opentracing.StartSpan( 16 | runtime.FuncForPC(pc[0]).Name(), 17 | opentracing.ChildOf(opentracing.SpanFromContext(ctx).Context()), 18 | ) 19 | return span 20 | } 21 | -------------------------------------------------------------------------------- /generator/generator.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "strings" 7 | "text/template" 8 | ) 9 | 10 | // Generator is the interface that generates project template files. 11 | // 12 | // Generate accepts a list of files and generates them based on their template. 13 | type Generator interface { 14 | Generate([]File) error 15 | Options() Options 16 | } 17 | 18 | type generator struct { 19 | opts Options 20 | } 21 | 22 | // File represents a file to generate. 23 | type File struct { 24 | // Path specifies where the file will reside. 25 | Path string 26 | // Template is the template used to generate the file. 27 | Template string 28 | } 29 | 30 | // Generate generates project template files. 31 | func (g *generator) Generate(files []File) error { 32 | for _, file := range files { 33 | fp := filepath.Join(g.opts.Directory, file.Path) 34 | dir := filepath.Dir(fp) 35 | 36 | if file.Template == "" { 37 | dir = fp 38 | } 39 | 40 | if _, err := os.Stat(dir); os.IsNotExist(err) { 41 | if err := os.MkdirAll(dir, 0755); err != nil { 42 | return err 43 | } 44 | } 45 | 46 | if file.Template == "" { 47 | continue 48 | } 49 | 50 | f, err := os.Create(fp) 51 | if err != nil { 52 | return err 53 | } 54 | 55 | fn := template.FuncMap{ 56 | "dehyphen": func(s string) string { 57 | return strings.ReplaceAll(s, "-", "") 58 | }, 59 | "lowerhyphen": func(s string) string { 60 | return strings.ReplaceAll(s, "-", "_") 61 | }, 62 | "tohyphen": func(s string) string { 63 | return strings.ReplaceAll(s, "_", "-") 64 | }, 65 | "gitorg": func(s string) string { 66 | list := strings.Split(s, "/") 67 | return strings.Join(list[:2], "/") 68 | }, 69 | "lower": strings.ToLower, 70 | "title": func(s string) string { 71 | t := strings.ReplaceAll(strings.Title(s), "-", "") 72 | return strings.ReplaceAll(t, "_", "") 73 | }, 74 | } 75 | t, err := template.New(fp).Funcs(fn).Parse(file.Template) 76 | if err != nil { 77 | return err 78 | } 79 | 80 | err = t.Execute(f, g.opts) 81 | if err != nil { 82 | return err 83 | } 84 | } 85 | 86 | return nil 87 | } 88 | 89 | func (g *generator) Options() Options { 90 | return g.opts 91 | } 92 | 93 | // New returns a new generator struct. 94 | func New(opts ...Option) Generator { 95 | var options Options 96 | for _, o := range opts { 97 | o(&options) 98 | } 99 | 100 | return &generator{ 101 | opts: options, 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /generator/options.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | // Options represents the options for the generator. 4 | type Options struct { 5 | // Service is the name of the service the generator will generate files 6 | // for. 7 | Service string 8 | // Vendor is the service vendor. 9 | Vendor string 10 | // Directory is the directory where the files will be generated to. 11 | Directory string 12 | 13 | // Client determines whether or not the project is a client project. 14 | Client bool 15 | // Jaeger determines whether or not Jaeger integration is enabled. 16 | Jaeger bool 17 | // Skaffold determines whether or not Skaffold integration is enabled. 18 | Skaffold bool 19 | // Tilt determines whether or not Tilt integration is enabled. 20 | Tilt bool 21 | // Health determines whether or not health proto service is enabled. 22 | Health bool 23 | // Kustomize determines whether or not Kustomize integration is enabled. 24 | Kustomize bool 25 | // Sqlc determines whether or not Sqlc integration is enabled. 26 | Sqlc bool 27 | // GRPC determines whether or not GRPC integration is enabled. 28 | GRPC bool 29 | // Buildkit determines whether or not Buildkit integration is enabled. 30 | Buildkit bool 31 | // Tern directory whether or not Tern integration is enabled. 32 | Tern bool 33 | // Advanced directory whether or not Advanced integration is enabled. 34 | Advanced bool 35 | // PrivateRepo 36 | PrivateRepo bool 37 | // Namespace sets the default namespace 38 | Namespace string 39 | // PostgresAddress sets the default postgres address 40 | PostgresAddress string 41 | } 42 | 43 | // Option manipulates the Options passed. 44 | type Option func(o *Options) 45 | 46 | // Service sets the service name. 47 | func Service(s string) Option { 48 | return func(o *Options) { 49 | o.Service = s 50 | } 51 | } 52 | 53 | // Vendor sets the service vendor. 54 | func Vendor(v string) Option { 55 | return func(o *Options) { 56 | o.Vendor = v 57 | } 58 | } 59 | 60 | // Directory sets the directory in which files are generated. 61 | func Directory(d string) Option { 62 | return func(o *Options) { 63 | o.Directory = d 64 | } 65 | } 66 | 67 | // Client sets whether or not the project is a client project. 68 | func Client(c bool) Option { 69 | return func(o *Options) { 70 | o.Client = c 71 | } 72 | } 73 | 74 | // Jaeger sets whether or not Jaeger integration is enabled. 75 | func Jaeger(j bool) Option { 76 | return func(o *Options) { 77 | o.Jaeger = j 78 | } 79 | } 80 | 81 | // Skaffold sets whether or not Skaffold integration is enabled. 82 | func Skaffold(s bool) Option { 83 | return func(o *Options) { 84 | o.Skaffold = s 85 | } 86 | } 87 | 88 | // Tilt sets whether or not Tilt integration is enabled. 89 | func Tilt(s bool) Option { 90 | return func(o *Options) { 91 | o.Tilt = s 92 | } 93 | } 94 | 95 | // Health determines whether or not health proto service is enabled. 96 | func Health(s bool) Option { 97 | return func(o *Options) { 98 | o.Health = s 99 | } 100 | } 101 | 102 | // Kustomize determines whether or not Kustomize integration is enabled. 103 | func Kustomize(s bool) Option { 104 | return func(o *Options) { 105 | o.Kustomize = s 106 | } 107 | } 108 | 109 | // Sqlc determines whether or not Sqlc integration is enabled. 110 | func Sqlc(s bool) Option { 111 | return func(o *Options) { 112 | o.Sqlc = s 113 | } 114 | } 115 | 116 | // GRPC determines whether or not GRPC integration is enabled. 117 | func GRPC(s bool) Option { 118 | return func(o *Options) { 119 | o.GRPC = s 120 | } 121 | } 122 | 123 | // Buildkit determines whether or not Buildkit integration is enabled. 124 | func Buildkit(s bool) Option { 125 | return func(o *Options) { 126 | o.Buildkit = s 127 | } 128 | } 129 | 130 | // Tern determines whether or not Tern integration is enabled. 131 | func Tern(s bool) Option { 132 | return func(o *Options) { 133 | o.Tern = s 134 | } 135 | } 136 | 137 | // Advanced determines whether or not Advanced integration is enabled. 138 | func Advanced(s bool) Option { 139 | return func(o *Options) { 140 | o.Advanced = s 141 | } 142 | } 143 | 144 | // PrivateRepo 145 | func PrivateRepo(s bool) Option { 146 | return func(o *Options) { 147 | o.PrivateRepo = s 148 | } 149 | } 150 | 151 | // Namespace 152 | func Namespace(s string) Option { 153 | return func(o *Options) { 154 | o.Namespace = s 155 | } 156 | } 157 | 158 | // PostgresAddress 159 | func PostgresAddress(s string) Option { 160 | return func(o *Options) { 161 | o.PostgresAddress = s 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /generator/template/docker.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | // Dockerfile is the Dockerfile template used for new projects. 4 | var Dockerfile = `FROM golang:alpine AS builder 5 | 6 | # Set Go env 7 | ENV CGO_ENABLED=0 GOOS=linux 8 | WORKDIR /go/src/{{.Service}}{{if .Client}}-client{{end}} 9 | 10 | # Install dependencies 11 | RUN apk --update --no-cache add ca-certificates gcc libtool make musl-dev protoc git{{if .PrivateRepo}} openssh-client{{end}} 12 | {{- if .PrivateRepo}} 13 | {{if ne .Vendor ""}} 14 | # Env config for private repo 15 | ENV GOPRIVATE="{{ gitorg .Vendor }}/*" 16 | RUN git config --global url."ssh://git@{{ gitorg .Vendor }}".insteadOf "https://{{ gitorg .Vendor }}" 17 | {{else}} 18 | # Configure these values 19 | # ENV GOPRIVATE="github.com//*" 20 | # RUN git config --global url."ssh://git@github.com/".insteadOf "https://github.com/" 21 | {{end}} 22 | # Authorize SSH Host 23 | RUN mkdir -p /root/.ssh && \ 24 | chmod 0700 /root/.ssh && \ 25 | ssh-keyscan github.com > /root/.ssh/known_hosts &&\ 26 | chmod 644 /root/.ssh/known_hosts && touch /root/.ssh/config 27 | {{end}} 28 | {{if .Health}} 29 | # Download grpc_health_probe 30 | RUN GRPC_HEALTH_PROBE_VERSION=v0.4.11 && \ 31 | wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \ 32 | chmod +x /bin/grpc_health_probe 33 | {{end}} 34 | # Build Go binary 35 | COPY {{if not .Client}}Makefile {{end}}go.mod go.sum ./ 36 | RUN {{if .PrivateRepo}}--mount=type=ssh {{end}}{{if .Buildkit}}--mount=type=cache,mode=0755,target=/go/pkg/mod {{end}}{{if not .Client}}make init && {{end}}go mod download 37 | COPY . . 38 | RUN {{if .Buildkit}}--mount=type=cache,target=/root/.cache/go-build --mount=type=cache,mode=0755,target=/go/pkg/mod {{end}}make {{if not .Client}}proto {{end}}tidy build 39 | 40 | # Deployment container 41 | FROM scratch 42 | 43 | COPY --from=builder /etc/ssl/certs /etc/ssl/certs 44 | {{- if .Health}} 45 | COPY --from=builder /bin/grpc_health_probe /bin/ 46 | {{- end}} 47 | COPY --from=builder /go/src/{{.Service}}{{if .Client}}-client{{end}}/{{.Service}}{{if .Client}}-client{{end}} /{{.Service}}{{if .Client}}-client{{end}} 48 | ENTRYPOINT ["/{{.Service}}{{if .Client}}-client{{end}}"] 49 | CMD [] 50 | ` 51 | 52 | // DockerIgnore is the .dockerignore template used for new projects. 53 | var DockerIgnore = `.gitignore 54 | Dockerfile{{if or .Skaffold .Kustomize}} 55 | resources/ 56 | skaffold.yaml{{end}} 57 | ` 58 | -------------------------------------------------------------------------------- /generator/template/gitignore.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | // GitIgnore is the .gitignore template used for new projects. 4 | var GitIgnore = `# Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with 'go test -c' 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # don't commit the service binary to vcs 21 | {{.Service}}{{if .Client}}-client{{end}} 22 | ` 23 | -------------------------------------------------------------------------------- /generator/template/handler.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | // HandlerFNC is the handler template used for new function projects. 4 | var HandlerFNC = `package handler 5 | 6 | import ( 7 | "context" 8 | 9 | "go-micro.dev/v4/logger" 10 | 11 | pb "{{.Vendor}}{{.Service}}/proto" 12 | ) 13 | 14 | type {{title .Service}} struct{} 15 | 16 | func (e *{{title .Service}}) Call(ctx context.Context, req *pb.CallRequest, rsp *pb.CallResponse) error { 17 | logger.Infof("Received {{title .Service}}.Call request: %v", req) 18 | rsp.Msg = "Hello " + req.Name 19 | return nil 20 | } 21 | ` 22 | 23 | // HandlerSRV is the handler template used for new service projects. 24 | var HandlerSRV = `package handler 25 | 26 | import ( 27 | "context" 28 | "io" 29 | "time" 30 | 31 | "go-micro.dev/v4/logger" 32 | 33 | pb "{{.Vendor}}{{.Service}}/proto" 34 | ) 35 | 36 | type {{title .Service}} struct{} 37 | 38 | func (e *{{title .Service}}) Call(ctx context.Context, req *pb.CallRequest, rsp *pb.CallResponse) error { 39 | logger.Infof("Received {{title .Service}}.Call request: %v", req) 40 | rsp.Msg = "Hello " + req.Name 41 | return nil 42 | } 43 | 44 | func (e *{{title .Service}}) ClientStream(ctx context.Context, stream pb.{{title .Service}}_ClientStreamStream) error { 45 | var count int64 46 | for { 47 | req, err := stream.Recv() 48 | if err == io.EOF { 49 | logger.Infof("Got %v pings total", count) 50 | return stream.SendMsg(&pb.ClientStreamResponse{Count: count}) 51 | } 52 | if err != nil { 53 | return err 54 | } 55 | logger.Infof("Got ping %v", req.Stroke) 56 | count++ 57 | } 58 | } 59 | 60 | func (e *{{title .Service}}) ServerStream(ctx context.Context, req *pb.ServerStreamRequest, stream pb.{{title .Service}}_ServerStreamStream) error { 61 | logger.Infof("Received {{title .Service}}.ServerStream request: %v", req) 62 | for i := 0; i < int(req.Count); i++ { 63 | logger.Infof("Sending %d", i) 64 | if err := stream.Send(&pb.ServerStreamResponse{ 65 | Count: int64(i), 66 | }); err != nil { 67 | return err 68 | } 69 | time.Sleep(time.Millisecond * 250) 70 | } 71 | return nil 72 | } 73 | 74 | func (e *{{title .Service}}) BidiStream(ctx context.Context, stream pb.{{title .Service}}_BidiStreamStream) error { 75 | for { 76 | req, err := stream.Recv() 77 | if err == io.EOF { 78 | return nil 79 | } 80 | if err != nil { 81 | return err 82 | } 83 | logger.Infof("Got ping %v", req.Stroke) 84 | if err := stream.Send(&pb.BidiStreamResponse{Stroke: req.Stroke}); err != nil { 85 | return err 86 | } 87 | } 88 | } 89 | ` 90 | 91 | var HealthSRV = `package handler 92 | 93 | import ( 94 | "context" 95 | 96 | "google.golang.org/grpc/codes" 97 | "google.golang.org/grpc/status" 98 | 99 | pb "{{.Vendor}}{{.Service}}/proto" 100 | ) 101 | 102 | type Health struct{} 103 | 104 | func (h *Health) Check(ctx context.Context, req *pb.HealthCheckRequest, rsp *pb.HealthCheckResponse) error { 105 | rsp.Status = pb.HealthCheckResponse_SERVING 106 | return nil 107 | } 108 | 109 | func (h *Health) Watch(ctx context.Context, req *pb.HealthCheckRequest, stream pb.Health_WatchStream) error { 110 | return status.Errorf(codes.Unimplemented, "health check via Watch not implemented") 111 | } 112 | ` 113 | -------------------------------------------------------------------------------- /generator/template/kubernetes.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | // KubernetesEnv is a Kubernetes configmap manifest template used for 4 | // environment variables in new projects. 5 | var KubernetesEnv = `--- 6 | 7 | apiVersion: v1 8 | kind: ConfigMap 9 | metadata: 10 | name: {{tohyphen .Service}}{{if .Client}}-client{{end}}-env 11 | data: 12 | MICRO_REGISTRY: kubernetes 13 | {{if .Tern}} 14 | PGHOST: {{ .PostgresAddress }} 15 | PGUSER: {{lowerhyphen .Service}}{{if .Client}}_client{{end}} 16 | PGDATABASE: {{lowerhyphen .Service}}{{if .Client}}_client{{end}} 17 | {{end}} 18 | ` 19 | 20 | // KubernetesClusterRole is a Kubernetes cluster role manifest template 21 | // required for the Kubernetes registry plugin to function correctly. 22 | var KubernetesClusterRole = `--- 23 | 24 | apiVersion: rbac.authorization.k8s.io/v1 25 | kind: ClusterRole 26 | metadata: 27 | name: micro-registry 28 | rules: 29 | - apiGroups: 30 | - "" 31 | resources: 32 | - pods 33 | verbs: 34 | - list 35 | - patch 36 | - watch 37 | ` 38 | 39 | // KubernetesRoleBinding is a Kubernetes role binding manifest template 40 | // required for the Kubernetes registry plugin to function correctly. 41 | var KubernetesRoleBinding = `--- 42 | 43 | apiVersion: rbac.authorization.k8s.io/v1 44 | kind: RoleBinding 45 | metadata: 46 | name: micro-registry 47 | roleRef: 48 | apiGroup: rbac.authorization.k8s.io 49 | kind: ClusterRole 50 | name: micro-registry 51 | subjects: 52 | - kind: ServiceAccount 53 | name: default 54 | namespace: {{ .Namespace }} 55 | ` 56 | 57 | // KubernetesDeployment is a Kubernetes deployment manifest template used for 58 | // new projects. 59 | var KubernetesDeployment = `--- 60 | 61 | apiVersion: apps/v1 62 | kind: Deployment 63 | metadata: 64 | name: {{tohyphen .Service}}{{if .Client}}-client{{end}} 65 | labels: 66 | app: {{tohyphen .Service}}{{if .Client}}-client{{end}} 67 | spec: 68 | replicas: 1 69 | selector: 70 | matchLabels: 71 | app: {{tohyphen .Service}}{{if .Client}}-client{{end}} 72 | template: 73 | metadata: 74 | labels: 75 | app: {{tohyphen .Service}}{{if .Client}}-client{{end}} 76 | spec: 77 | {{- if .Tern}} 78 | initContainers: 79 | - name: {{tohyphen .Service}}{{if .Client}}-client{{end}}-migrations 80 | securityContext: 81 | allowPrivilegeEscalation: false 82 | image: golang:alpine 83 | envFrom: 84 | - configMapRef: 85 | name: {{tohyphen .Service}}{{if .Client}}-client{{end}}-env 86 | - secretRef: 87 | name: {{tohyphen .Service}}{{if .Client}}-client{{end}}-postgres-env 88 | volumeMounts: 89 | - mountPath: /migrations 90 | name: migrations 91 | command: 92 | - sh 93 | - "-c" 94 | - | 95 | go install github.com/jackc/tern@latest 96 | tern migrate --migrations /migrations 97 | {{- end}} 98 | containers: 99 | - name: {{tohyphen .Service}}{{if .Client}}-client{{end}} 100 | securityContext: 101 | allowPrivilegeEscalation: false 102 | image: {{tohyphen .Service}}{{if .Client}}-client{{end}}:latest 103 | envFrom: 104 | - configMapRef: 105 | name: {{tohyphen .Service}}{{if .Client}}-client{{end}}-env 106 | {{- if .Tern}} 107 | - secretRef: 108 | name: {{tohyphen .Service}}{{if .Client}}-client{{end}}-postgres-env 109 | {{- end}} 110 | {{- if .Health}} 111 | readinessProbe: 112 | grpc: 113 | port: 41888 114 | initialDelaySeconds: 10 115 | timeoutSeconds: 5 116 | livenessProbe: 117 | grpc: 118 | port: 41888 119 | initialDelaySeconds: 10 120 | timeoutSeconds: 5 121 | {{- end}} 122 | {{- if .Tern}} 123 | volumes: 124 | - name: migrations 125 | configMap: 126 | name: {{tohyphen .Service}}{{if .Client}}-client{{end}}-migrations 127 | {{- end}} 128 | ` 129 | -------------------------------------------------------------------------------- /generator/template/kustomize.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | var KustomizationBase = `--- 4 | 5 | namespace: {{ .Namespace }} 6 | 7 | resources: 8 | - clusterrole.yaml 9 | - deployment.yaml 10 | - rolebinding.yaml 11 | 12 | configMapGenerator: 13 | - name: {{tohyphen .Service}}{{if .Client}}-client{{end}}-env 14 | envs: 15 | - app.env 16 | {{- if .Tern}} 17 | - name: {{tohyphen .Service}}{{if .Client}}-client{{end}}-migrations 18 | files: 19 | - ../../postgres/migrations/001_create_schema.sql 20 | {{end}} 21 | ` 22 | var KustomizationDev = `--- 23 | 24 | namespace: {{ .Namespace }} 25 | 26 | resources: 27 | - ../base/ 28 | ` 29 | 30 | var KustomizationProd = `--- 31 | 32 | namespace: {{ .Namespace }} 33 | 34 | resources: 35 | - ../base/ 36 | ` 37 | 38 | var AppEnv = `MICRO_REGISTRY=kubernetes 39 | {{- if .Tern}} 40 | PGHOST={{ .PostgresAddress }} 41 | PGUSER={{lowerhyphen .Service}}{{if .Client}}_client{{end}} 42 | PGDATABASE={{lowerhyphen .Service}}{{if .Client}}_client{{end}} 43 | {{end}} 44 | ` 45 | -------------------------------------------------------------------------------- /generator/template/main.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | // MainCLT is the main template used for new client projects. 4 | var MainCLT = `package main 5 | 6 | import ( 7 | "context" 8 | "time" 9 | 10 | pb "{{.Vendor}}{{lower .Service}}/proto" 11 | 12 | "go-micro.dev/v4" 13 | "go-micro.dev/v4/logger" 14 | {{if .GRPC}} 15 | "github.com/go-micro/plugins/v4/client/grpc" 16 | {{end}} 17 | ) 18 | 19 | var ( 20 | service = "{{lower .Service}}" 21 | version = "latest" 22 | ) 23 | 24 | func main() { 25 | // Create service 26 | {{if .GRPC}} 27 | srv := micro.NewService( 28 | micro.Client(grpc.NewClient()), 29 | ) 30 | {{else}} 31 | srv := micro.NewService() 32 | {{end}} 33 | srv.Init() 34 | 35 | // Create client 36 | c := pb.NewHelloworldService(service, srv.Client()) 37 | 38 | for { 39 | // Call service 40 | rsp, err := c.Call(context.Background(), &pb.CallRequest{Name: "John"}) 41 | if err != nil { 42 | logger.Fatal(err) 43 | } 44 | 45 | logger.Info(rsp) 46 | 47 | time.Sleep(1 * time.Second) 48 | } 49 | } 50 | ` 51 | 52 | // MainFNC is the main template used for new function projects. 53 | var MainFNC = `package main 54 | 55 | import ( 56 | "{{.Vendor}}{{.Service}}/handler" 57 | 58 | {{if .Jaeger}} ot "github.com/go-micro/plugins/v4/wrapper/trace/opentracing" 59 | {{end}} "go-micro.dev/v4" 60 | "go-micro.dev/v4/logger"{{if .Jaeger}} 61 | 62 | "go-micro.dev/v4/cmd/micro/debug/trace/jaeger"{{end}} 63 | ) 64 | 65 | var ( 66 | service = "{{lower .Service}}" 67 | version = "latest" 68 | ) 69 | 70 | func main() { 71 | {{if .Jaeger}} // Create tracer 72 | tracer, closer, err := jaeger.NewTracer( 73 | jaeger.Name(service), 74 | jaeger.FromEnv(true), 75 | jaeger.GlobalTracer(true), 76 | ) 77 | if err != nil { 78 | logger.Fatal(err) 79 | } 80 | defer closer.Close() 81 | 82 | {{end}} // Create function 83 | fnc := micro.NewFunction( 84 | micro.Name(service), 85 | micro.Version(version), 86 | {{if .Jaeger}} micro.WrapCall(ot.NewCallWrapper(tracer)), 87 | micro.WrapClient(ot.NewClientWrapper(tracer)), 88 | micro.WrapHandler(ot.NewHandlerWrapper(tracer)), 89 | micro.WrapSubscriber(ot.NewSubscriberWrapper(tracer)), 90 | {{end}} ) 91 | fnc.Init() 92 | 93 | // Handle function 94 | fnc.Handle(new(handler.{{title .Service}})) 95 | 96 | // Run function 97 | if err := fnc.Run(); err != nil { 98 | logger.Fatal(err) 99 | } 100 | } 101 | ` 102 | 103 | // MainSRV is the main template used for new service projects. 104 | var MainSRV = `package main 105 | 106 | import ( 107 | {{- if .Advanced}} 108 | "context" 109 | "sync" 110 | {{- end}} 111 | 112 | "{{.Vendor}}{{.Service}}/handler" 113 | pb "{{.Vendor}}{{.Service}}/proto" 114 | 115 | {{if .Jaeger}} ot "github.com/go-micro/plugins/v4/wrapper/trace/opentracing" 116 | {{end}} "go-micro.dev/v4" 117 | "go-micro.dev/v4/logger"{{if .Jaeger}} 118 | {{- if .Advanced}} 119 | "go-micro.dev/v4/server" 120 | {{- end}} 121 | 122 | "github.com/go-micro/cli/debug/trace/jaeger"{{end}} 123 | {{if .GRPC}} 124 | grpcc "github.com/go-micro/plugins/v4/client/grpc" 125 | grpcs "github.com/go-micro/plugins/v4/server/grpc" 126 | {{- end}} 127 | ) 128 | 129 | var ( 130 | service = "{{lower .Service}}" 131 | version = "latest" 132 | ) 133 | 134 | func main() { 135 | {{if .Jaeger}} // Create tracer 136 | tracer, closer, err := jaeger.NewTracer( 137 | jaeger.Name(service), 138 | jaeger.FromEnv(true), 139 | jaeger.GlobalTracer(true), 140 | ) 141 | if err != nil { 142 | logger.Fatal(err) 143 | } 144 | defer closer.Close() 145 | {{ if .Advanced }} 146 | wg := sync.WaitGroup{} 147 | ctx, cancel := context.WithCancel(context.Background()) 148 | {{- end }} 149 | 150 | {{end}} // Create service 151 | srv := micro.NewService( 152 | {{- if .GRPC}} 153 | micro.Server(grpcs.NewServer()), 154 | micro.Client(grpcc.NewClient()), 155 | {{- end}} 156 | {{- if .Advanced}} 157 | micro.BeforeStart(func() error { 158 | logger.Infof("Starting service %s", service) 159 | return nil 160 | }), 161 | micro.BeforeStop(func() error { 162 | logger.Infof("Shutting down service %s", service) 163 | cancel() 164 | return nil 165 | }), 166 | micro.AfterStop(func() error { 167 | wg.Wait() 168 | return nil 169 | }), 170 | {{- end}} 171 | {{if .Jaeger}} micro.WrapCall(ot.NewCallWrapper(tracer)), 172 | micro.WrapClient(ot.NewClientWrapper(tracer)), 173 | micro.WrapHandler(ot.NewHandlerWrapper(tracer)), 174 | micro.WrapSubscriber(ot.NewSubscriberWrapper(tracer)), 175 | {{end}} ) 176 | srv.Init( 177 | micro.Name(service), 178 | micro.Version(version), 179 | ) 180 | {{- if .Advanced}} 181 | srv.Server().Init( 182 | server.Wait(&wg), 183 | ) 184 | 185 | ctx = server.NewContext(ctx, srv.Server()) 186 | {{- end}} 187 | 188 | // Register handler 189 | if err := pb.Register{{title .Service}}Handler(srv.Server(), new(handler.{{title .Service}})); err != nil { 190 | logger.Fatal(err) 191 | } 192 | {{- if .Health}} 193 | if err := pb.RegisterHealthHandler(srv.Server(), new(handler.Health)); err != nil { 194 | logger.Fatal(err) 195 | } 196 | {{end}} 197 | // Run service 198 | if err := srv.Run(); err != nil { 199 | logger.Fatal(err) 200 | } 201 | } 202 | ` 203 | -------------------------------------------------------------------------------- /generator/template/makefile.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | // Makefile is the Makefile template used for new projects. 4 | var Makefile = `GOPATH:=$(shell go env GOPATH) 5 | 6 | .PHONY: init 7 | init: 8 | @go get -u google.golang.org/protobuf/proto 9 | @go install google.golang.org/protobuf/cmd/protoc-gen-go@latest 10 | @go install github.com/go-micro/generator/cmd/protoc-gen-micro@latest 11 | {{- if .Tern}} 12 | @go install github.com/kyleconroy/sqlc/cmd/sqlc@latest 13 | {{- end}} 14 | {{- if .Sqlc}} 15 | @go install github.com/jackc/tern@latest 16 | {{- end}} 17 | 18 | .PHONY: proto 19 | proto: 20 | @protoc --proto_path=. --micro_out=. --go_out=:. proto/{{.Service}}.proto 21 | {{- if .Health}} 22 | @protoc --proto_path=. --micro_out=. --go_out=:. proto/health.proto 23 | {{end}} 24 | 25 | .PHONY: update 26 | update: 27 | @go get -u 28 | 29 | .PHONY: tidy 30 | tidy: 31 | @go mod tidy 32 | 33 | .PHONY: build 34 | build: 35 | @go build -o {{.Service}}{{if .Client}}-client{{end}} *.go 36 | 37 | .PHONY: test 38 | test: 39 | @go test -v ./... -cover 40 | 41 | .PHONY: docker 42 | docker: 43 | @{{if .Buildkit}}DOCKER_BUILDKIT=1 {{end}}docker build -t {{.Service}}{{if .Client}}-client{{end}}:latest {{if .PrivateRepo}}--ssh=default {{end}}. 44 | 45 | {{- if .Sqlc}} 46 | 47 | .PHONY: sqlc 48 | sqlc: 49 | @sqlc generate -f ./postgres/sqlc.yaml 50 | {{- end -}} 51 | ` 52 | -------------------------------------------------------------------------------- /generator/template/module.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | // Module is the go.mod template used for new projects. 4 | var Module = `module {{.Vendor}}{{.Service}}{{if .Client}}-client{{end}} 5 | 6 | go 1.18 7 | 8 | require ( 9 | go-micro.dev/v4 v4.7.0 10 | ) 11 | {{if eq .Vendor ""}} 12 | replace {{lower .Service}} => ./ 13 | {{end}} 14 | ` 15 | -------------------------------------------------------------------------------- /generator/template/plugins.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | // Plugins is the plugins template used for new projects. 4 | var Plugins = `package main 5 | 6 | import ( 7 | _ "github.com/go-micro/plugins/v4/registry/kubernetes" 8 | ) 9 | ` 10 | -------------------------------------------------------------------------------- /generator/template/proto.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | // ProtoFNC is the .proto file template used for new function projects. 4 | var ProtoFNC = `syntax = "proto3"; 5 | 6 | package {{dehyphen .Service}}; 7 | 8 | option go_package = "./proto;{{dehyphen .Service}}"; 9 | 10 | service {{title .Service}} { 11 | rpc Call(CallRequest) returns (CallResponse) {} 12 | } 13 | 14 | message CallRequest { 15 | string name = 1; 16 | } 17 | 18 | message CallResponse { 19 | string msg = 1; 20 | } 21 | ` 22 | 23 | // ProtoSRV is the .proto file template used for new service projects. 24 | var ProtoSRV = `syntax = "proto3"; 25 | 26 | package {{dehyphen .Service}}; 27 | 28 | option go_package = "./proto;{{dehyphen .Service}}"; 29 | 30 | service {{title .Service}} { 31 | rpc Call(CallRequest) returns (CallResponse) {} 32 | rpc ClientStream(stream ClientStreamRequest) returns (ClientStreamResponse) {} 33 | rpc ServerStream(ServerStreamRequest) returns (stream ServerStreamResponse) {} 34 | rpc BidiStream(stream BidiStreamRequest) returns (stream BidiStreamResponse) {} 35 | } 36 | 37 | message CallRequest { 38 | string name = 1; 39 | } 40 | 41 | message CallResponse { 42 | string msg = 1; 43 | } 44 | 45 | message ClientStreamRequest { 46 | int64 stroke = 1; 47 | } 48 | 49 | message ClientStreamResponse { 50 | int64 count = 1; 51 | } 52 | 53 | message ServerStreamRequest { 54 | int64 count = 1; 55 | } 56 | 57 | message ServerStreamResponse { 58 | int64 count = 1; 59 | } 60 | 61 | message BidiStreamRequest { 62 | int64 stroke = 1; 63 | } 64 | 65 | message BidiStreamResponse { 66 | int64 stroke = 1; 67 | } 68 | ` 69 | 70 | // ProtoHEALTH is the .proto file template used for health. 71 | var ProtoHEALTH = `syntax = "proto3"; 72 | 73 | package {{dehyphen .Service}}; 74 | 75 | option go_package = "./proto;{{dehyphen .Service}}"; 76 | 77 | service Health { 78 | rpc Check(HealthCheckRequest) returns (HealthCheckResponse) {} 79 | rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse) {} 80 | } 81 | 82 | message HealthCheckRequest { 83 | string service = 1; 84 | } 85 | 86 | message HealthCheckResponse { 87 | enum ServingStatus { 88 | UNKNOWN = 0; 89 | SERVING = 1; 90 | NOT_SERVING = 2; 91 | SERVICE_UNKNOWN = 3; 92 | } 93 | ServingStatus status = 1; 94 | } 95 | ` 96 | -------------------------------------------------------------------------------- /generator/template/skaffold.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | // SkaffoldCFG is the Skaffold config template used for new projects. 4 | var SkaffoldCFG = `--- 5 | 6 | apiVersion: skaffold/v2beta21 7 | kind: Config 8 | metadata: 9 | name: {{.Service}}{{if .Client}}-client{{end}} 10 | build: 11 | artifacts: 12 | - image: {{.Service}}{{if .Client}}-client{{end}} 13 | deploy: 14 | {{- if .Kustomize}} 15 | kustomize: 16 | paths: 17 | - ./resources/dev/ 18 | {{- if .Tern}} 19 | buildArgs: 20 | - load-restrictor LoadRestrictionsNone 21 | {{end}} 22 | {{- else}} 23 | kubectl: 24 | manifests: 25 | - resources/*.yaml 26 | {{end}} 27 | ` 28 | -------------------------------------------------------------------------------- /generator/template/sqlc.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | var Sqlc = `--- 4 | 5 | version: 1 6 | packages: 7 | - path: "sqlc" 8 | name: "sqlc" 9 | engine: "postgresql" 10 | sql_package: "pgx/v4" 11 | schema: "./migrations/" 12 | queries: "./queries/" 13 | ` 14 | 15 | var Postgres = `package postgres 16 | 17 | import ( 18 | "context" 19 | 20 | "github.com/jackc/pgx/v4/pgxpool" 21 | "github.com/pkg/errors" 22 | 23 | "{{.Vendor}}{{.Service}}/postgres/sqlc" 24 | ) 25 | 26 | type DB struct { 27 | conn *sqlc.Queries 28 | } 29 | 30 | func NewDB(connString string) (*DB, func(), error) { 31 | // Do not use main context since some business logic closing down might still 32 | // need to commit to database. Be sure to defer pool.Close in main. 33 | pool, err := pgxpool.Connect(context.Background(), connString) 34 | if err != nil { 35 | return nil, nil, errors.Wrap(err, "Failed to create pgx connection pool") 36 | } 37 | 38 | db := DB{ 39 | conn: sqlc.New(pool), 40 | } 41 | return &db, pool.Close, nil 42 | } 43 | 44 | // QueryExample is a example of how you can use sqlc to create your database layer 45 | func (db *DB) QueryExample() (int, error) { 46 | i, err := db.conn.SampleQuery(context.Background()) 47 | if err != nil { 48 | return int(i), errors.Wrap(err, "Failed to query SampleQuery") 49 | } 50 | return int(i), nil 51 | } 52 | ` 53 | 54 | var QueryExample = `-- name: SampleQuery :one 55 | SELECT 56 | 1::int; 57 | ` 58 | -------------------------------------------------------------------------------- /generator/template/tern.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | var TernSql = `-- Write your migrate up statements here 4 | 5 | ---- create above / drop below ---- 6 | 7 | -- Write your migrate down statements here. If this migration is irreversible 8 | -- Then delete the separator line above. 9 | ` 10 | -------------------------------------------------------------------------------- /generator/template/tilt.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | var Tiltfile = `{{if .PrivateRepo -}} 4 | # Start SSH Agent 5 | if os.getenv('SSH_AUTH_SOCK', '') == "": 6 | git_key = os.getenv('GIT_SSH_KEY', '') 7 | local('eval $(ssh-agent) && ssh-add {}'.format(git_key)) 8 | 9 | {{end -}} 10 | # Build Docker image 11 | docker_build('{{.Service}}{{if .Client}}-client{{end}}', 12 | context='.', 13 | dockerfile='./Dockerfile', 14 | {{- if .PrivateRepo}} 15 | ssh='default', 16 | {{- end}} 17 | ) 18 | 19 | # Config 20 | {{- if .Kustomize}}KUSTOMIZE_DIR="./resources/dev/" 21 | 22 | {{- if .Tern}}# LoadRestrictor option is passed as migrations need to be accessed outside of base directory 23 | # See: https://github.com/kubernetes-sigs/kustomize/issues/865 24 | manifests = local("kustomize build --load-restrictor LoadRestrictionsNone {dir}".format(dir=KUSTOMIZE_DIR), quiet=True) 25 | {{- else}} 26 | manifests = kustomize(KUSTOMIZE_DIR) 27 | {{- end}} 28 | {{- else}} 29 | KUBERNETS_DIR="./resources" 30 | manifests = listdir(KUBERNETES_DIR) 31 | {{- end}} 32 | 33 | # Apply Kubernetes manifests 34 | # Allow duplcates is marked true for when you import multiple go-micro Tiltfiles 35 | # into a single Tiltfile it will mark the clusterrole as duplicate. 36 | k8s_yaml(manifests, allow_duplicates=True) 37 | ` 38 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/go-micro/cli 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/fsnotify/fsnotify v1.5.4 7 | github.com/go-micro/plugins/v4/registry/kubernetes v1.0.0 8 | github.com/opentracing/opentracing-go v1.2.0 9 | github.com/pkg/errors v0.9.1 10 | github.com/uber/jaeger-client-go v2.29.1+incompatible 11 | github.com/uber/jaeger-lib v2.4.1+incompatible 12 | github.com/urfave/cli/v2 v2.8.1 13 | go-micro.dev/v4 v4.7.0 14 | gopkg.in/yaml.v2 v2.4.0 15 | ) 16 | 17 | require ( 18 | github.com/Microsoft/go-winio v0.5.2 // indirect 19 | github.com/ProtonMail/go-crypto v0.0.0-20220517143526-88bb52951d5b // indirect 20 | github.com/acomagu/bufpipe v1.0.3 // indirect 21 | github.com/bitly/go-simplejson v0.5.0 // indirect 22 | github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect 23 | github.com/emirpasic/gods v1.18.1 // indirect 24 | github.com/go-git/gcfg v1.5.0 // indirect 25 | github.com/go-git/go-billy/v5 v5.3.1 // indirect 26 | github.com/go-git/go-git/v5 v5.4.2 // indirect 27 | github.com/golang/protobuf v1.5.2 // indirect 28 | github.com/google/uuid v1.3.0 // indirect 29 | github.com/imdario/mergo v0.3.13 // indirect 30 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect 31 | github.com/kevinburke/ssh_config v1.2.0 // indirect 32 | github.com/miekg/dns v1.1.49 // indirect 33 | github.com/mitchellh/go-homedir v1.1.0 // indirect 34 | github.com/nxadm/tail v1.4.8 // indirect 35 | github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect 36 | github.com/patrickmn/go-cache v2.1.0+incompatible // indirect 37 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 38 | github.com/sergi/go-diff v1.2.0 // indirect 39 | github.com/xanzy/ssh-agent v0.3.1 // indirect 40 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect 41 | go.uber.org/atomic v1.4.0 // indirect 42 | golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect 43 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect 44 | golang.org/x/net v0.0.0-20220607020251-c690dde0001d // indirect 45 | golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect 46 | golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d // indirect 47 | golang.org/x/text v0.3.7 // indirect 48 | golang.org/x/tools v0.1.11 // indirect 49 | golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect 50 | google.golang.org/protobuf v1.28.0 // indirect 51 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect 52 | gopkg.in/warnings.v0 v0.1.2 // indirect 53 | ) 54 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= 2 | github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= 3 | github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= 4 | github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= 5 | github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= 6 | github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= 7 | github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= 8 | github.com/ProtonMail/go-crypto v0.0.0-20220517143526-88bb52951d5b h1:lcbBNuQhppsc7A5gjdHmdlqUqJfgGMylBdGyDs0j7G8= 9 | github.com/ProtonMail/go-crypto v0.0.0-20220517143526-88bb52951d5b/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= 10 | github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= 11 | github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= 12 | github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= 13 | github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= 14 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= 15 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= 16 | github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= 17 | github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= 18 | github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= 19 | github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= 20 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 21 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 22 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 23 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 24 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 25 | github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= 26 | github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= 27 | github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= 28 | github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= 29 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 30 | github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= 31 | github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= 32 | github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= 33 | github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= 34 | github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= 35 | github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= 36 | github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= 37 | github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34= 38 | github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= 39 | github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8= 40 | github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= 41 | github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4= 42 | github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= 43 | github.com/go-micro/plugins/v4/registry/kubernetes v1.0.0 h1:C/6cWtwXw2eMVupDU0kY6zo60ZGemWdGBZtOc6LOEKQ= 44 | github.com/go-micro/plugins/v4/registry/kubernetes v1.0.0/go.mod h1:zRn2eRRSu/Jql5pPesOjTUnedYSlml9rMVpCzw8Zvng= 45 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 46 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 47 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 48 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 49 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 50 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 51 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 52 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 53 | github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= 54 | github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= 55 | github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= 56 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= 57 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= 58 | github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= 59 | github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= 60 | github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= 61 | github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= 62 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 63 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 64 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= 65 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 66 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 67 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 68 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 69 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 70 | github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= 71 | github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= 72 | github.com/miekg/dns v1.1.49 h1:qe0mQU3Z/XpFeE+AEBo2rqaS1IPBJ3anmqZ4XiZJVG8= 73 | github.com/miekg/dns v1.1.49/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= 74 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 75 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 76 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 77 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= 78 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= 79 | github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= 80 | github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= 81 | github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= 82 | github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= 83 | github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= 84 | github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= 85 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 86 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 87 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 88 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 89 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 90 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 91 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 92 | github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 93 | github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= 94 | github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 95 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 96 | github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 97 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 98 | github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= 99 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 100 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 101 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 102 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 103 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 104 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 105 | github.com/uber/jaeger-client-go v2.29.1+incompatible h1:R9ec3zO3sGpzs0abd43Y+fBZRJ9uiH6lXyR/+u6brW4= 106 | github.com/uber/jaeger-client-go v2.29.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= 107 | github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= 108 | github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= 109 | github.com/urfave/cli/v2 v2.8.1 h1:CGuYNZF9IKZY/rfBe3lJpccSoIY1ytfvmgQT90cNOl4= 110 | github.com/urfave/cli/v2 v2.8.1/go.mod h1:Z41J9TPoffeoqP0Iza0YbAhGvymRdZAd2uPmZ5JxRdY= 111 | github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= 112 | github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo= 113 | github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w= 114 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= 115 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= 116 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 117 | go-micro.dev/v4 v4.7.0 h1:vjvZ94JNBMXb7MrbpSIf2zMmj8oVMmOnJRDJLnGGGaE= 118 | go-micro.dev/v4 v4.7.0/go.mod h1:7UY87mLE6T4zHKsNS5D+VWZcXGTEvU1rbA90PezzlWM= 119 | go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= 120 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 121 | golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 122 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 123 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 124 | golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 125 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 126 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 127 | golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= 128 | golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 129 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 130 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= 131 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 132 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 133 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 134 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 135 | golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= 136 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 137 | golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 138 | golang.org/x/net v0.0.0-20220607020251-c690dde0001d h1:4SFsTMi4UahlKoloni7L4eYzhFRifURQLw+yv0QDCx8= 139 | golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 140 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 141 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 142 | golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8= 143 | golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 144 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 145 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 146 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 147 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 148 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 149 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 150 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 151 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 152 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 153 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 154 | golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 155 | golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 156 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 157 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 158 | golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 159 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 160 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 161 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 162 | golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 163 | golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d h1:Zu/JngovGLVi6t2J3nmAf3AoTDwuzw85YZ3b9o4yU7s= 164 | golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 165 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 166 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= 167 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 168 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 169 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 170 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 171 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 172 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 173 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 174 | golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 175 | golang.org/x/tools v0.1.11 h1:loJ25fNOEhSXfHrpoGj91eCUThwdNX6u24rO1xnNteY= 176 | golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= 177 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 178 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 179 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 180 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 181 | golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f h1:uF6paiQQebLeSXkrTqHqz0MXhXXS1KgF41eUdBNvxK0= 182 | golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= 183 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 184 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 185 | google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= 186 | google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 187 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 188 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 189 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 190 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 191 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 192 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 193 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 194 | gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= 195 | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= 196 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 197 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 198 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 199 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 200 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 201 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 202 | gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= 203 | gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 204 | --------------------------------------------------------------------------------