gitee.com/sasukebo/go-micro/v4@v4.7.1/cmd/cmd.go (about) 1 // Package cmd is an interface for parsing the command line 2 package cmd 3 4 import ( 5 "fmt" 6 "math/rand" 7 "strings" 8 "time" 9 10 "gitee.com/sasukebo/go-micro/v4/auth" 11 "gitee.com/sasukebo/go-micro/v4/broker" 12 "gitee.com/sasukebo/go-micro/v4/cache" 13 "gitee.com/sasukebo/go-micro/v4/client" 14 "gitee.com/sasukebo/go-micro/v4/config" 15 "gitee.com/sasukebo/go-micro/v4/debug/profile" 16 "gitee.com/sasukebo/go-micro/v4/debug/profile/http" 17 "gitee.com/sasukebo/go-micro/v4/debug/profile/pprof" 18 "gitee.com/sasukebo/go-micro/v4/debug/trace" 19 "gitee.com/sasukebo/go-micro/v4/logger" 20 "gitee.com/sasukebo/go-micro/v4/registry" 21 "gitee.com/sasukebo/go-micro/v4/runtime" 22 "gitee.com/sasukebo/go-micro/v4/selector" 23 "gitee.com/sasukebo/go-micro/v4/server" 24 "gitee.com/sasukebo/go-micro/v4/store" 25 "gitee.com/sasukebo/go-micro/v4/transport" 26 "github.com/urfave/cli/v2" 27 ) 28 29 type Cmd interface { 30 // The cli app within this cmd 31 App() *cli.App 32 // Adds options, parses flags and initialise 33 // exits on error 34 Init(opts ...Option) error 35 // Options set within this command 36 Options() Options 37 } 38 39 type cmd struct { 40 opts Options 41 app *cli.App 42 } 43 44 type Option func(o *Options) 45 46 var ( 47 DefaultCmd = newCmd() 48 49 DefaultFlags = []cli.Flag{ 50 &cli.StringFlag{ 51 Name: "client", 52 EnvVars: []string{"MICRO_CLIENT"}, 53 Usage: "Client for go-micro; rpc", 54 }, 55 &cli.StringFlag{ 56 Name: "client_request_timeout", 57 EnvVars: []string{"MICRO_CLIENT_REQUEST_TIMEOUT"}, 58 Usage: "Sets the client request timeout. e.g 500ms, 5s, 1m. Default: 5s", 59 }, 60 &cli.IntFlag{ 61 Name: "client_retries", 62 EnvVars: []string{"MICRO_CLIENT_RETRIES"}, 63 Value: client.DefaultRetries, 64 Usage: "Sets the client retries. Default: 1", 65 }, 66 &cli.IntFlag{ 67 Name: "client_pool_size", 68 EnvVars: []string{"MICRO_CLIENT_POOL_SIZE"}, 69 Usage: "Sets the client connection pool size. Default: 1", 70 }, 71 &cli.StringFlag{ 72 Name: "client_pool_ttl", 73 EnvVars: []string{"MICRO_CLIENT_POOL_TTL"}, 74 Usage: "Sets the client connection pool ttl. e.g 500ms, 5s, 1m. Default: 1m", 75 }, 76 &cli.IntFlag{ 77 Name: "register_ttl", 78 EnvVars: []string{"MICRO_REGISTER_TTL"}, 79 Value: 60, 80 Usage: "Register TTL in seconds", 81 }, 82 &cli.IntFlag{ 83 Name: "register_interval", 84 EnvVars: []string{"MICRO_REGISTER_INTERVAL"}, 85 Value: 30, 86 Usage: "Register interval in seconds", 87 }, 88 &cli.StringFlag{ 89 Name: "server", 90 EnvVars: []string{"MICRO_SERVER"}, 91 Usage: "Server for go-micro; rpc", 92 }, 93 &cli.StringFlag{ 94 Name: "server_name", 95 EnvVars: []string{"MICRO_SERVER_NAME"}, 96 Usage: "Name of the server. go.micro.srv.example", 97 }, 98 &cli.StringFlag{ 99 Name: "server_version", 100 EnvVars: []string{"MICRO_SERVER_VERSION"}, 101 Usage: "Version of the server. 1.1.0", 102 }, 103 &cli.StringFlag{ 104 Name: "server_id", 105 EnvVars: []string{"MICRO_SERVER_ID"}, 106 Usage: "Id of the server. Auto-generated if not specified", 107 }, 108 &cli.StringFlag{ 109 Name: "server_address", 110 EnvVars: []string{"MICRO_SERVER_ADDRESS"}, 111 Usage: "Bind address for the server. 127.0.0.1:8080", 112 }, 113 &cli.StringFlag{ 114 Name: "server_advertise", 115 EnvVars: []string{"MICRO_SERVER_ADVERTISE"}, 116 Usage: "Used instead of the server_address when registering with discovery. 127.0.0.1:8080", 117 }, 118 &cli.StringSliceFlag{ 119 Name: "server_metadata", 120 EnvVars: []string{"MICRO_SERVER_METADATA"}, 121 Value: &cli.StringSlice{}, 122 Usage: "A list of key-value pairs defining metadata. version=1.0.0", 123 }, 124 &cli.StringFlag{ 125 Name: "broker", 126 EnvVars: []string{"MICRO_BROKER"}, 127 Usage: "Broker for pub/sub. http, nats, rabbitmq", 128 }, 129 &cli.StringFlag{ 130 Name: "broker_address", 131 EnvVars: []string{"MICRO_BROKER_ADDRESS"}, 132 Usage: "Comma-separated list of broker addresses", 133 }, 134 &cli.StringFlag{ 135 Name: "profile", 136 Usage: "Debug profiler for cpu and memory stats", 137 EnvVars: []string{"MICRO_DEBUG_PROFILE"}, 138 }, 139 &cli.StringFlag{ 140 Name: "registry", 141 EnvVars: []string{"MICRO_REGISTRY"}, 142 Usage: "Registry for discovery. etcd, mdns", 143 }, 144 &cli.StringFlag{ 145 Name: "registry_address", 146 EnvVars: []string{"MICRO_REGISTRY_ADDRESS"}, 147 Usage: "Comma-separated list of registry addresses", 148 }, 149 &cli.StringFlag{ 150 Name: "runtime", 151 Usage: "Runtime for building and running services e.g local, kubernetes", 152 EnvVars: []string{"MICRO_RUNTIME"}, 153 }, 154 &cli.StringFlag{ 155 Name: "runtime_source", 156 Usage: "Runtime source for building and running services e.g github.com/micro/service", 157 EnvVars: []string{"MICRO_RUNTIME_SOURCE"}, 158 Value: "github.com/micro/services", 159 }, 160 &cli.StringFlag{ 161 Name: "selector", 162 EnvVars: []string{"MICRO_SELECTOR"}, 163 Usage: "Selector used to pick nodes for querying", 164 }, 165 &cli.StringFlag{ 166 Name: "store", 167 EnvVars: []string{"MICRO_STORE"}, 168 Usage: "Store used for key-value storage", 169 }, 170 &cli.StringFlag{ 171 Name: "store_address", 172 EnvVars: []string{"MICRO_STORE_ADDRESS"}, 173 Usage: "Comma-separated list of store addresses", 174 }, 175 &cli.StringFlag{ 176 Name: "store_database", 177 EnvVars: []string{"MICRO_STORE_DATABASE"}, 178 Usage: "Database option for the underlying store", 179 }, 180 &cli.StringFlag{ 181 Name: "store_table", 182 EnvVars: []string{"MICRO_STORE_TABLE"}, 183 Usage: "Table option for the underlying store", 184 }, 185 &cli.StringFlag{ 186 Name: "transport", 187 EnvVars: []string{"MICRO_TRANSPORT"}, 188 Usage: "Transport mechanism used; http", 189 }, 190 &cli.StringFlag{ 191 Name: "transport_address", 192 EnvVars: []string{"MICRO_TRANSPORT_ADDRESS"}, 193 Usage: "Comma-separated list of transport addresses", 194 }, 195 &cli.StringFlag{ 196 Name: "tracer", 197 EnvVars: []string{"MICRO_TRACER"}, 198 Usage: "Tracer for distributed tracing, e.g. memory, jaeger", 199 }, 200 &cli.StringFlag{ 201 Name: "tracer_address", 202 EnvVars: []string{"MICRO_TRACER_ADDRESS"}, 203 Usage: "Comma-separated list of tracer addresses", 204 }, 205 &cli.StringFlag{ 206 Name: "auth", 207 EnvVars: []string{"MICRO_AUTH"}, 208 Usage: "Auth for role based access control, e.g. service", 209 }, 210 &cli.StringFlag{ 211 Name: "auth_id", 212 EnvVars: []string{"MICRO_AUTH_ID"}, 213 Usage: "Account ID used for client authentication", 214 }, 215 &cli.StringFlag{ 216 Name: "auth_secret", 217 EnvVars: []string{"MICRO_AUTH_SECRET"}, 218 Usage: "Account secret used for client authentication", 219 }, 220 &cli.StringFlag{ 221 Name: "auth_namespace", 222 EnvVars: []string{"MICRO_AUTH_NAMESPACE"}, 223 Usage: "Namespace for the services auth account", 224 Value: "go.micro", 225 }, 226 &cli.StringFlag{ 227 Name: "auth_public_key", 228 EnvVars: []string{"MICRO_AUTH_PUBLIC_KEY"}, 229 Usage: "Public key for JWT auth (base64 encoded PEM)", 230 }, 231 &cli.StringFlag{ 232 Name: "auth_private_key", 233 EnvVars: []string{"MICRO_AUTH_PRIVATE_KEY"}, 234 Usage: "Private key for JWT auth (base64 encoded PEM)", 235 }, 236 &cli.StringFlag{ 237 Name: "config", 238 EnvVars: []string{"MICRO_CONFIG"}, 239 Usage: "The source of the config to be used to get configuration", 240 }, 241 } 242 243 DefaultBrokers = map[string]func(...broker.Option) broker.Broker{} 244 245 DefaultClients = map[string]func(...client.Option) client.Client{} 246 247 DefaultRegistries = map[string]func(...registry.Option) registry.Registry{} 248 249 DefaultSelectors = map[string]func(...selector.Option) selector.Selector{} 250 251 DefaultServers = map[string]func(...server.Option) server.Server{} 252 253 DefaultTransports = map[string]func(...transport.Option) transport.Transport{} 254 255 DefaultRuntimes = map[string]func(...runtime.Option) runtime.Runtime{} 256 257 DefaultStores = map[string]func(...store.Option) store.Store{} 258 259 DefaultTracers = map[string]func(...trace.Option) trace.Tracer{} 260 261 DefaultAuths = map[string]func(...auth.Option) auth.Auth{} 262 263 DefaultProfiles = map[string]func(...profile.Option) profile.Profile{ 264 "http": http.NewProfile, 265 "pprof": pprof.NewProfile, 266 } 267 268 DefaultConfigs = map[string]func(...config.Option) (config.Config, error){} 269 270 DefaultCaches = map[string]func(...cache.Option) cache.Cache{} 271 ) 272 273 func init() { 274 rand.Seed(time.Now().Unix()) 275 } 276 277 func newCmd(opts ...Option) Cmd { 278 options := Options{ 279 Auth: &auth.DefaultAuth, 280 Broker: &broker.DefaultBroker, 281 Client: &client.DefaultClient, 282 Registry: ®istry.DefaultRegistry, 283 Server: &server.DefaultServer, 284 Selector: &selector.DefaultSelector, 285 Transport: &transport.DefaultTransport, 286 Runtime: &runtime.DefaultRuntime, 287 Store: &store.DefaultStore, 288 Tracer: &trace.DefaultTracer, 289 Profile: &profile.DefaultProfile, 290 Config: &config.DefaultConfig, 291 Cache: &cache.DefaultCache, 292 293 Brokers: DefaultBrokers, 294 Clients: DefaultClients, 295 Registries: DefaultRegistries, 296 Selectors: DefaultSelectors, 297 Servers: DefaultServers, 298 Transports: DefaultTransports, 299 Runtimes: DefaultRuntimes, 300 Stores: DefaultStores, 301 Tracers: DefaultTracers, 302 Auths: DefaultAuths, 303 Profiles: DefaultProfiles, 304 Configs: DefaultConfigs, 305 Caches: DefaultCaches, 306 } 307 308 for _, o := range opts { 309 o(&options) 310 } 311 312 if len(options.Description) == 0 { 313 options.Description = "a go-micro service" 314 } 315 316 cmd := new(cmd) 317 cmd.opts = options 318 cmd.app = cli.NewApp() 319 cmd.app.Name = cmd.opts.Name 320 cmd.app.Version = cmd.opts.Version 321 cmd.app.Usage = cmd.opts.Description 322 cmd.app.Before = cmd.Before 323 cmd.app.Flags = DefaultFlags 324 cmd.app.Action = func(c *cli.Context) error { 325 return nil 326 } 327 328 if len(options.Version) == 0 { 329 cmd.app.HideVersion = true 330 } 331 332 return cmd 333 } 334 335 func (c *cmd) App() *cli.App { 336 return c.app 337 } 338 339 func (c *cmd) Options() Options { 340 return c.opts 341 } 342 343 func (c *cmd) Before(ctx *cli.Context) error { 344 // If flags are set then use them otherwise do nothing 345 var serverOpts []server.Option 346 var clientOpts []client.Option 347 348 // Set the client 349 if name := ctx.String("client"); len(name) > 0 { 350 // only change if we have the client and type differs 351 if cl, ok := c.opts.Clients[name]; ok && (*c.opts.Client).String() != name { 352 *c.opts.Client = cl() 353 } 354 } 355 356 // Set the server 357 if name := ctx.String("server"); len(name) > 0 { 358 // only change if we have the server and type differs 359 if s, ok := c.opts.Servers[name]; ok && (*c.opts.Server).String() != name { 360 *c.opts.Server = s() 361 } 362 } 363 364 // Set the store 365 if name := ctx.String("store"); len(name) > 0 { 366 s, ok := c.opts.Stores[name] 367 if !ok { 368 return fmt.Errorf("Unsupported store: %s", name) 369 } 370 371 *c.opts.Store = s(store.WithClient(*c.opts.Client)) 372 } 373 374 // Set the runtime 375 if name := ctx.String("runtime"); len(name) > 0 { 376 r, ok := c.opts.Runtimes[name] 377 if !ok { 378 return fmt.Errorf("Unsupported runtime: %s", name) 379 } 380 381 *c.opts.Runtime = r(runtime.WithClient(*c.opts.Client)) 382 } 383 384 // Set the tracer 385 if name := ctx.String("tracer"); len(name) > 0 { 386 r, ok := c.opts.Tracers[name] 387 if !ok { 388 return fmt.Errorf("Unsupported tracer: %s", name) 389 } 390 391 *c.opts.Tracer = r() 392 } 393 394 // Setup auth 395 authOpts := []auth.Option{} 396 397 if len(ctx.String("auth_id")) > 0 || len(ctx.String("auth_secret")) > 0 { 398 authOpts = append(authOpts, auth.Credentials( 399 ctx.String("auth_id"), ctx.String("auth_secret"), 400 )) 401 } 402 if len(ctx.String("auth_public_key")) > 0 { 403 authOpts = append(authOpts, auth.PublicKey(ctx.String("auth_public_key"))) 404 } 405 if len(ctx.String("auth_private_key")) > 0 { 406 authOpts = append(authOpts, auth.PrivateKey(ctx.String("auth_private_key"))) 407 } 408 if len(ctx.String("auth_namespace")) > 0 { 409 authOpts = append(authOpts, auth.Namespace(ctx.String("auth_namespace"))) 410 } 411 if name := ctx.String("auth"); len(name) > 0 { 412 r, ok := c.opts.Auths[name] 413 if !ok { 414 return fmt.Errorf("Unsupported auth: %s", name) 415 } 416 417 *c.opts.Auth = r(authOpts...) 418 } 419 420 // Set the registry 421 if name := ctx.String("registry"); len(name) > 0 && (*c.opts.Registry).String() != name { 422 r, ok := c.opts.Registries[name] 423 if !ok { 424 return fmt.Errorf("Registry %s not found", name) 425 } 426 427 *c.opts.Registry = r() 428 serverOpts = append(serverOpts, server.Registry(*c.opts.Registry)) 429 clientOpts = append(clientOpts, client.Registry(*c.opts.Registry)) 430 431 if err := (*c.opts.Selector).Init(selector.Registry(*c.opts.Registry)); err != nil { 432 logger.Fatalf("Error configuring registry: %v", err) 433 } 434 435 clientOpts = append(clientOpts, client.Selector(*c.opts.Selector)) 436 437 if err := (*c.opts.Broker).Init(broker.Registry(*c.opts.Registry)); err != nil { 438 logger.Fatalf("Error configuring broker: %v", err) 439 } 440 } 441 442 // Set the profile 443 if name := ctx.String("profile"); len(name) > 0 { 444 p, ok := c.opts.Profiles[name] 445 if !ok { 446 return fmt.Errorf("Unsupported profile: %s", name) 447 } 448 449 *c.opts.Profile = p() 450 } 451 452 // Set the broker 453 if name := ctx.String("broker"); len(name) > 0 && (*c.opts.Broker).String() != name { 454 b, ok := c.opts.Brokers[name] 455 if !ok { 456 return fmt.Errorf("Broker %s not found", name) 457 } 458 459 *c.opts.Broker = b() 460 serverOpts = append(serverOpts, server.Broker(*c.opts.Broker)) 461 clientOpts = append(clientOpts, client.Broker(*c.opts.Broker)) 462 } 463 464 // Set the selector 465 if name := ctx.String("selector"); len(name) > 0 && (*c.opts.Selector).String() != name { 466 s, ok := c.opts.Selectors[name] 467 if !ok { 468 return fmt.Errorf("Selector %s not found", name) 469 } 470 471 *c.opts.Selector = s(selector.Registry(*c.opts.Registry)) 472 473 // No server option here. Should there be? 474 clientOpts = append(clientOpts, client.Selector(*c.opts.Selector)) 475 } 476 477 // Set the transport 478 if name := ctx.String("transport"); len(name) > 0 && (*c.opts.Transport).String() != name { 479 t, ok := c.opts.Transports[name] 480 if !ok { 481 return fmt.Errorf("Transport %s not found", name) 482 } 483 484 *c.opts.Transport = t() 485 serverOpts = append(serverOpts, server.Transport(*c.opts.Transport)) 486 clientOpts = append(clientOpts, client.Transport(*c.opts.Transport)) 487 } 488 489 // Parse the server options 490 metadata := make(map[string]string) 491 for _, d := range ctx.StringSlice("server_metadata") { 492 var key, val string 493 parts := strings.Split(d, "=") 494 key = parts[0] 495 if len(parts) > 1 { 496 val = strings.Join(parts[1:], "=") 497 } 498 metadata[key] = val 499 } 500 501 if len(metadata) > 0 { 502 serverOpts = append(serverOpts, server.Metadata(metadata)) 503 } 504 505 if len(ctx.String("broker_address")) > 0 { 506 if err := (*c.opts.Broker).Init(broker.Addrs(strings.Split(ctx.String("broker_address"), ",")...)); err != nil { 507 logger.Fatalf("Error configuring broker: %v", err) 508 } 509 } 510 511 if len(ctx.String("registry_address")) > 0 { 512 if err := (*c.opts.Registry).Init(registry.Addrs(strings.Split(ctx.String("registry_address"), ",")...)); err != nil { 513 logger.Fatalf("Error configuring registry: %v", err) 514 } 515 } 516 517 if len(ctx.String("transport_address")) > 0 { 518 if err := (*c.opts.Transport).Init(transport.Addrs(strings.Split(ctx.String("transport_address"), ",")...)); err != nil { 519 logger.Fatalf("Error configuring transport: %v", err) 520 } 521 } 522 523 if len(ctx.String("store_address")) > 0 { 524 if err := (*c.opts.Store).Init(store.Nodes(strings.Split(ctx.String("store_address"), ",")...)); err != nil { 525 logger.Fatalf("Error configuring store: %v", err) 526 } 527 } 528 529 if len(ctx.String("store_database")) > 0 { 530 if err := (*c.opts.Store).Init(store.Database(ctx.String("store_database"))); err != nil { 531 logger.Fatalf("Error configuring store database option: %v", err) 532 } 533 } 534 535 if len(ctx.String("store_table")) > 0 { 536 if err := (*c.opts.Store).Init(store.Table(ctx.String("store_table"))); err != nil { 537 logger.Fatalf("Error configuring store table option: %v", err) 538 } 539 } 540 541 if len(ctx.String("server_name")) > 0 { 542 serverOpts = append(serverOpts, server.Name(ctx.String("server_name"))) 543 } 544 545 if len(ctx.String("server_version")) > 0 { 546 serverOpts = append(serverOpts, server.Version(ctx.String("server_version"))) 547 } 548 549 if len(ctx.String("server_id")) > 0 { 550 serverOpts = append(serverOpts, server.Id(ctx.String("server_id"))) 551 } 552 553 if len(ctx.String("server_address")) > 0 { 554 serverOpts = append(serverOpts, server.Address(ctx.String("server_address"))) 555 } 556 557 if len(ctx.String("server_advertise")) > 0 { 558 serverOpts = append(serverOpts, server.Advertise(ctx.String("server_advertise"))) 559 } 560 561 if ttl := time.Duration(ctx.Int("register_ttl")); ttl >= 0 { 562 serverOpts = append(serverOpts, server.RegisterTTL(ttl*time.Second)) 563 } 564 565 if val := time.Duration(ctx.Int("register_interval")); val >= 0 { 566 serverOpts = append(serverOpts, server.RegisterInterval(val*time.Second)) 567 } 568 569 if len(ctx.String("runtime_source")) > 0 { 570 if err := (*c.opts.Runtime).Init(runtime.WithSource(ctx.String("runtime_source"))); err != nil { 571 logger.Fatalf("Error configuring runtime: %v", err) 572 } 573 } 574 575 // client opts 576 if r := ctx.Int("client_retries"); r >= 0 { 577 clientOpts = append(clientOpts, client.Retries(r)) 578 } 579 580 if t := ctx.String("client_request_timeout"); len(t) > 0 { 581 d, err := time.ParseDuration(t) 582 if err != nil { 583 return fmt.Errorf("failed to parse client_request_timeout: %v", t) 584 } 585 clientOpts = append(clientOpts, client.RequestTimeout(d)) 586 } 587 588 if r := ctx.Int("client_pool_size"); r > 0 { 589 clientOpts = append(clientOpts, client.PoolSize(r)) 590 } 591 592 if t := ctx.String("client_pool_ttl"); len(t) > 0 { 593 d, err := time.ParseDuration(t) 594 if err != nil { 595 return fmt.Errorf("failed to parse client_pool_ttl: %v", t) 596 } 597 clientOpts = append(clientOpts, client.PoolTTL(d)) 598 } 599 600 // We have some command line opts for the server. 601 // Lets set it up 602 if len(serverOpts) > 0 { 603 if err := (*c.opts.Server).Init(serverOpts...); err != nil { 604 logger.Fatalf("Error configuring server: %v", err) 605 } 606 } 607 608 // Use an init option? 609 if len(clientOpts) > 0 { 610 if err := (*c.opts.Client).Init(clientOpts...); err != nil { 611 logger.Fatalf("Error configuring client: %v", err) 612 } 613 } 614 615 // config 616 if name := ctx.String("config"); len(name) > 0 { 617 // only change if we have the server and type differs 618 if r, ok := c.opts.Configs[name]; ok { 619 rc, err := r() 620 if err != nil { 621 logger.Fatalf("Error configuring config: %v", err) 622 } 623 *c.opts.Config = rc 624 } 625 } 626 627 return nil 628 } 629 630 func (c *cmd) Init(opts ...Option) error { 631 for _, o := range opts { 632 o(&c.opts) 633 } 634 if len(c.opts.Name) > 0 { 635 c.app.Name = c.opts.Name 636 } 637 if len(c.opts.Version) > 0 { 638 c.app.Version = c.opts.Version 639 } 640 c.app.HideVersion = len(c.opts.Version) == 0 641 c.app.Usage = c.opts.Description 642 c.app.RunAndExitOnError() 643 return nil 644 } 645 646 func DefaultOptions() Options { 647 return DefaultCmd.Options() 648 } 649 650 func App() *cli.App { 651 return DefaultCmd.App() 652 } 653 654 func Init(opts ...Option) error { 655 return DefaultCmd.Init(opts...) 656 } 657 658 func NewCmd(opts ...Option) Cmd { 659 return newCmd(opts...) 660 }