github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/cmd/cmd.go (about) 1 package cmd 2 3 import ( 4 "crypto/tls" 5 "crypto/x509" 6 "fmt" 7 "io/ioutil" 8 "math/rand" 9 "os" 10 "os/exec" 11 "runtime/debug" 12 "sort" 13 "strings" 14 "sync" 15 "time" 16 "unicode" 17 18 "github.com/google/uuid" 19 "github.com/tickoalcantara12/micro/v3/client/cli/namespace" 20 clitoken "github.com/tickoalcantara12/micro/v3/client/cli/token" 21 "github.com/tickoalcantara12/micro/v3/client/cli/util" 22 _ "github.com/tickoalcantara12/micro/v3/cmd/usage" 23 "github.com/tickoalcantara12/micro/v3/plugin" 24 "github.com/tickoalcantara12/micro/v3/profile" 25 "github.com/tickoalcantara12/micro/v3/service/auth" 26 "github.com/tickoalcantara12/micro/v3/service/broker" 27 "github.com/tickoalcantara12/micro/v3/service/client" 28 "github.com/tickoalcantara12/micro/v3/service/config" 29 configCli "github.com/tickoalcantara12/micro/v3/service/config/client" 30 storeConf "github.com/tickoalcantara12/micro/v3/service/config/store" 31 "github.com/tickoalcantara12/micro/v3/service/errors" 32 "github.com/tickoalcantara12/micro/v3/service/events" 33 "github.com/tickoalcantara12/micro/v3/service/logger" 34 "github.com/tickoalcantara12/micro/v3/service/metrics" 35 "github.com/tickoalcantara12/micro/v3/service/network" 36 "github.com/tickoalcantara12/micro/v3/service/registry" 37 "github.com/tickoalcantara12/micro/v3/service/router" 38 "github.com/tickoalcantara12/micro/v3/service/runtime" 39 "github.com/tickoalcantara12/micro/v3/service/server" 40 "github.com/tickoalcantara12/micro/v3/service/store" 41 uconf "github.com/tickoalcantara12/micro/v3/util/config" 42 "github.com/tickoalcantara12/micro/v3/util/helper" 43 "github.com/tickoalcantara12/micro/v3/util/report" 44 "github.com/tickoalcantara12/micro/v3/util/user" 45 "github.com/tickoalcantara12/micro/v3/util/wrapper" 46 "github.com/urfave/cli/v2" 47 48 muruntime "github.com/tickoalcantara12/micro/v3/service/runtime" 49 50 authSrv "github.com/tickoalcantara12/micro/v3/service/auth/client" 51 brokerSrv "github.com/tickoalcantara12/micro/v3/service/broker/client" 52 grpcCli "github.com/tickoalcantara12/micro/v3/service/client/grpc" 53 eventsSrv "github.com/tickoalcantara12/micro/v3/service/events/client" 54 noopMet "github.com/tickoalcantara12/micro/v3/service/metrics/noop" 55 mucpNet "github.com/tickoalcantara12/micro/v3/service/network/mucp" 56 registrySrv "github.com/tickoalcantara12/micro/v3/service/registry/client" 57 routerSrv "github.com/tickoalcantara12/micro/v3/service/router/client" 58 runtimeSrv "github.com/tickoalcantara12/micro/v3/service/runtime/client" 59 grpcSvr "github.com/tickoalcantara12/micro/v3/service/server/grpc" 60 storeSrv "github.com/tickoalcantara12/micro/v3/service/store/client" 61 ) 62 63 type Cmd interface { 64 // Init initialises options 65 // Note: Use Run to parse command line 66 Init(opts ...Option) error 67 // Options set within this command 68 Options() Options 69 // The cli app within this cmd 70 App() *cli.App 71 // Run executes the command 72 Run() error 73 // Implementation 74 String() string 75 } 76 77 type command struct { 78 opts Options 79 app *cli.App 80 81 // before is a function which should 82 // be called in Before if not nil 83 before cli.ActionFunc 84 85 // indicates whether this is a service 86 service bool 87 } 88 89 var ( 90 DefaultCmd Cmd = New() 91 92 onceBefore sync.Once 93 94 // name of the binary 95 name = "micro" 96 // description of the binary 97 description = "A framework for cloud native development\n\n Use `micro [command] --help` to see command specific help." 98 // defaultFlags which are used on all commands 99 defaultFlags = []cli.Flag{ 100 &cli.StringFlag{ 101 Name: "c", 102 Usage: "Set the config file: Defaults to ~/.micro/config.json", 103 EnvVars: []string{"MICRO_CONFIG_FILE"}, 104 }, 105 &cli.StringFlag{ 106 Name: "env", 107 Aliases: []string{"e"}, 108 Usage: "Set the environment to operate in", 109 EnvVars: []string{"MICRO_ENV"}, 110 }, 111 &cli.StringFlag{ 112 Name: "profile", 113 Usage: "Set the micro server profile: e.g. local or kubernetes", 114 EnvVars: []string{"MICRO_PROFILE"}, 115 }, 116 &cli.StringFlag{ 117 Name: "namespace", 118 EnvVars: []string{"MICRO_NAMESPACE"}, 119 Usage: "Namespace the service is operating in", 120 Value: "micro", 121 }, 122 &cli.StringFlag{ 123 Name: "auth_address", 124 EnvVars: []string{"MICRO_AUTH_ADDRESS"}, 125 Usage: "Comma-separated list of auth addresses", 126 }, 127 &cli.StringFlag{ 128 Name: "auth_id", 129 EnvVars: []string{"MICRO_AUTH_ID"}, 130 Usage: "Account ID used for client authentication", 131 }, 132 &cli.StringFlag{ 133 Name: "auth_secret", 134 EnvVars: []string{"MICRO_AUTH_SECRET"}, 135 Usage: "Account secret used for client authentication", 136 }, 137 &cli.StringFlag{ 138 Name: "auth_public_key", 139 EnvVars: []string{"MICRO_AUTH_PUBLIC_KEY"}, 140 Usage: "Public key for JWT auth (base64 encoded PEM)", 141 }, 142 &cli.StringFlag{ 143 Name: "auth_private_key", 144 EnvVars: []string{"MICRO_AUTH_PRIVATE_KEY"}, 145 Usage: "Private key for JWT auth (base64 encoded PEM)", 146 }, 147 &cli.StringFlag{ 148 Name: "registry_address", 149 EnvVars: []string{"MICRO_REGISTRY_ADDRESS"}, 150 Usage: "Comma-separated list of registry addresses", 151 }, 152 &cli.StringFlag{ 153 Name: "registry_tls_ca", 154 Usage: "Certificate authority for TLS with registry", 155 EnvVars: []string{"MICRO_REGISTRY_TLS_CA"}, 156 }, 157 &cli.StringFlag{ 158 Name: "registry_tls_cert", 159 Usage: "Client cert for TLS with registry", 160 EnvVars: []string{"MICRO_REGISTRY_TLS_CERT"}, 161 }, 162 &cli.StringFlag{ 163 Name: "registry_tls_key", 164 Usage: "Client key for TLS with registry", 165 EnvVars: []string{"MICRO_REGISTRY_TLS_KEY"}, 166 }, 167 &cli.StringFlag{ 168 Name: "broker_address", 169 EnvVars: []string{"MICRO_BROKER_ADDRESS"}, 170 Usage: "Comma-separated list of broker addresses", 171 }, 172 &cli.StringFlag{ 173 Name: "events_tls_ca", 174 Usage: "Certificate authority for TLS with events", 175 EnvVars: []string{"MICRO_EVENTS_TLS_CA"}, 176 }, 177 &cli.StringFlag{ 178 Name: "events_tls_cert", 179 Usage: "Client cert for TLS with events", 180 EnvVars: []string{"MICRO_EVENTS_TLS_CERT"}, 181 }, 182 &cli.StringFlag{ 183 Name: "events_tls_key", 184 Usage: "Client key for TLS with events", 185 EnvVars: []string{"MICRO_EVENTS_TLS_KEY"}, 186 }, 187 &cli.StringFlag{ 188 Name: "broker_tls_ca", 189 Usage: "Certificate authority for TLS with broker", 190 EnvVars: []string{"MICRO_BROKER_TLS_CA"}, 191 }, 192 &cli.StringFlag{ 193 Name: "broker_tls_cert", 194 Usage: "Client cert for TLS with broker", 195 EnvVars: []string{"MICRO_BROKER_TLS_CERT"}, 196 }, 197 &cli.StringFlag{ 198 Name: "broker_tls_key", 199 Usage: "Client key for TLS with broker", 200 EnvVars: []string{"MICRO_BROKER_TLS_KEY"}, 201 }, 202 &cli.StringFlag{ 203 Name: "store_address", 204 EnvVars: []string{"MICRO_STORE_ADDRESS"}, 205 Usage: "Comma-separated list of store addresses", 206 }, 207 &cli.StringFlag{ 208 Name: "proxy_address", 209 Usage: "Proxy requests via the HTTP address specified", 210 EnvVars: []string{"MICRO_PROXY"}, 211 }, 212 &cli.BoolFlag{ 213 Name: "report_usage", 214 Usage: "Report usage statistics", 215 EnvVars: []string{"MICRO_REPORT_USAGE"}, 216 Value: true, 217 }, 218 &cli.StringFlag{ 219 Name: "service_name", 220 Usage: "Name of the micro service", 221 EnvVars: []string{"MICRO_SERVICE_NAME"}, 222 }, 223 &cli.StringFlag{ 224 Name: "service_version", 225 Usage: "Version of the micro service", 226 EnvVars: []string{"MICRO_SERVICE_VERSION"}, 227 }, 228 &cli.StringFlag{ 229 Name: "service_address", 230 Usage: "Address to run the service on", 231 EnvVars: []string{"MICRO_SERVICE_ADDRESS"}, 232 }, 233 &cli.StringFlag{ 234 Name: "config_secret_key", 235 Usage: "Key to use when encoding/decoding secret config values. Will be generated and saved to file if not provided.", 236 Value: "", 237 EnvVars: []string{"MICRO_CONFIG_SECRET_KEY"}, 238 }, 239 &cli.StringFlag{ 240 Name: "tracing_reporter_address", 241 Usage: "The host:port of the opentracing agent e.g. localhost:6831", 242 EnvVars: []string{"MICRO_TRACING_REPORTER_ADDRESS"}, 243 }, 244 } 245 ) 246 247 func init() { 248 rand.Seed(time.Now().Unix()) 249 250 // configure defaults for all packages 251 setupDefaults() 252 } 253 254 // setupDefaults sets the default auth, broker etc implementations incase they arent configured by 255 // a profile. The default implementations are always the RPC implementations. 256 func setupDefaults() { 257 client.DefaultClient = grpcCli.NewClient() 258 server.DefaultServer = grpcSvr.NewServer() 259 network.DefaultNetwork = mucpNet.NewNetwork() 260 metrics.DefaultMetricsReporter = noopMet.New() 261 262 // setup rpc implementations after the client is configured 263 auth.DefaultAuth = authSrv.NewAuth() 264 broker.DefaultBroker = brokerSrv.NewBroker() 265 events.DefaultStream = eventsSrv.NewStream() 266 events.DefaultStore = eventsSrv.NewStore() 267 registry.DefaultRegistry = registrySrv.NewRegistry() 268 router.DefaultRouter = routerSrv.NewRouter() 269 store.DefaultStore = storeSrv.NewStore() 270 store.DefaultBlobStore = storeSrv.NewBlobStore() 271 runtime.DefaultRuntime = runtimeSrv.NewRuntime() 272 } 273 274 func formatErr(err error) string { 275 switch v := err.(type) { 276 case *errors.Error: 277 return upcaseInitial(v.Detail) 278 default: 279 return upcaseInitial(err.Error()) 280 } 281 } 282 283 func upcaseInitial(str string) string { 284 for i, v := range str { 285 return string(unicode.ToUpper(v)) + str[i+1:] 286 } 287 return "" 288 } 289 290 // setupAuthForCLI handles exchanging refresh tokens to access tokens 291 // The structure of the local micro userconfig file is the following: 292 // micro.auth.[envName].token: temporary access token 293 // micro.auth.[envName].refresh-token: long lived refresh token 294 // micro.auth.[envName].expiry: expiration time of the access token, seconds since Unix epoch. 295 func setupAuthForCLI(ctx *cli.Context) error { 296 env, err := util.GetEnv(ctx) 297 if err != nil { 298 return err 299 } 300 ns, err := namespace.Get(env.Name) 301 if err != nil { 302 return err 303 } 304 305 tok, err := clitoken.Get(ctx) 306 if err != nil { 307 return err 308 } 309 310 // If there is no refresh token, do not try to refresh it 311 if len(tok.RefreshToken) == 0 { 312 return nil 313 } 314 315 // Check if token is valid 316 if time.Now().Before(tok.Expiry.Add(time.Minute * -1)) { 317 auth.DefaultAuth.Init( 318 auth.ClientToken(tok), 319 auth.Issuer(ns), 320 ) 321 return nil 322 } 323 324 // Get new access token from refresh token if it's close to expiry 325 tok, err = auth.Token( 326 auth.WithToken(tok.RefreshToken), 327 auth.WithTokenIssuer(ns), 328 auth.WithExpiry(time.Minute*10), 329 ) 330 if err != nil { 331 return nil 332 } 333 334 // Save the token to user config file 335 auth.DefaultAuth.Init( 336 auth.ClientToken(tok), 337 auth.Issuer(ns), 338 ) 339 return clitoken.Save(ctx, tok) 340 } 341 342 // setupAuthForService generates auth credentials for the service 343 func setupAuthForService() error { 344 opts := auth.DefaultAuth.Options() 345 346 // extract the account creds from options, these can be set by flags 347 accID := opts.ID 348 accSecret := opts.Secret 349 350 // if no credentials were provided, self generate an account 351 if len(accID) == 0 || len(accSecret) == 0 { 352 opts := []auth.GenerateOption{ 353 auth.WithType("service"), 354 auth.WithScopes("service"), 355 } 356 357 acc, err := auth.Generate(uuid.New().String(), opts...) 358 if err != nil { 359 return err 360 } 361 if logger.V(logger.DebugLevel, logger.DefaultLogger) { 362 logger.Debugf("Auth [%v] Generated an auth account", auth.DefaultAuth.String()) 363 } 364 365 accID = acc.ID 366 accSecret = acc.Secret 367 } 368 369 // generate the first token 370 token, err := auth.Token( 371 auth.WithCredentials(accID, accSecret), 372 auth.WithExpiry(time.Minute*10), 373 ) 374 if err != nil { 375 return err 376 } 377 378 // set the credentials and token in auth options 379 auth.DefaultAuth.Init( 380 auth.ClientToken(token), 381 auth.Credentials(accID, accSecret), 382 ) 383 return nil 384 } 385 386 // refreshAuthToken if it is close to expiring 387 func refreshAuthToken() { 388 // can't refresh a token we don't have 389 if auth.DefaultAuth.Options().Token == nil { 390 return 391 } 392 393 t := time.NewTicker(time.Second * 15) 394 defer t.Stop() 395 396 for { 397 select { 398 case <-t.C: 399 // don't refresh the token if it's not close to expiring 400 tok := auth.DefaultAuth.Options().Token 401 if tok.Expiry.Unix() > time.Now().Add(time.Minute).Unix() { 402 continue 403 } 404 405 // generate the first token 406 tok, err := auth.Token( 407 auth.WithToken(tok.RefreshToken), 408 auth.WithExpiry(time.Minute*10), 409 ) 410 if err == auth.ErrInvalidToken { 411 logger.Warnf("[Auth] Refresh token expired, regenerating using account credentials") 412 413 tok, err = auth.Token( 414 auth.WithCredentials( 415 auth.DefaultAuth.Options().ID, 416 auth.DefaultAuth.Options().Secret, 417 ), 418 auth.WithExpiry(time.Minute*10), 419 ) 420 } else if err != nil { 421 logger.Warnf("[Auth] Error refreshing token: %v", err) 422 continue 423 } 424 425 // set the token 426 logger.Debugf("Auth token refreshed, expires at %v", tok.Expiry.Format(time.UnixDate)) 427 auth.DefaultAuth.Init(auth.ClientToken(tok)) 428 } 429 } 430 } 431 432 func action(c *cli.Context) error { 433 if c.Args().Len() == 0 { 434 return helper.MissingCommand(c) 435 } 436 437 // if an executable is available with the name of 438 // the command, execute it with the arguments from 439 // index 1 on. 440 v, err := exec.LookPath("micro-" + c.Args().First()) 441 if err == nil { 442 ce := exec.Command(v, c.Args().Slice()[1:]...) 443 ce.Stdout = os.Stdout 444 ce.Stderr = os.Stderr 445 return ce.Run() 446 } 447 448 // lookup the service, e.g. "micro config set" would 449 // firstly check to see if the service, e.g. config 450 // exists within the current namespace, then it would 451 // execute the Config.Set RPC, setting the flags in the 452 // request. 453 if srv, ns, err := util.LookupService(c); err != nil { 454 return util.CliError(err) 455 } else if srv != nil && util.ShouldRenderHelp(c) { 456 return cli.Exit(util.FormatServiceUsage(srv, c), 0) 457 } else if srv != nil { 458 err := util.CallService(srv, ns, c) 459 return util.CliError(err) 460 } 461 462 // srv == nil 463 return helper.UnexpectedCommand(c) 464 } 465 466 func New(opts ...Option) *command { 467 options := Options{} 468 for _, o := range opts { 469 o(&options) 470 } 471 472 cmd := new(command) 473 cmd.opts = options 474 cmd.app = cli.NewApp() 475 cmd.app.Name = name 476 cmd.app.Version = buildVersion() 477 cmd.app.Usage = description 478 cmd.app.Flags = defaultFlags 479 cmd.app.Action = action 480 cmd.app.Before = beforeFromContext(options.Context, cmd.Before) 481 482 // if this option has been set, we're running a service 483 // and no action needs to be performed. The CMD package 484 // is just being used to parse flags and configure micro. 485 if setupOnlyFromContext(options.Context) { 486 cmd.service = true 487 cmd.app.Action = func(ctx *cli.Context) error { return nil } 488 } 489 490 //flags to add 491 if len(options.Flags) > 0 { 492 cmd.app.Flags = append(cmd.app.Flags, options.Flags...) 493 } 494 //action to replace 495 if options.Action != nil { 496 cmd.app.Action = options.Action 497 } 498 // cmd to add to use registry 499 500 return cmd 501 } 502 503 func (c *command) App() *cli.App { 504 return c.app 505 } 506 507 func (c *command) Options() Options { 508 return c.opts 509 } 510 511 // Before is executed before any subcommand 512 func (c *command) Before(ctx *cli.Context) error { 513 // set the config file if specified 514 if cf := ctx.String("c"); len(cf) > 0 { 515 uconf.SetConfig(cf) 516 } 517 518 // certain commands don't require loading 519 if ctx.Args().First() == "env" { 520 return nil 521 } 522 523 // default the profile for the server 524 prof := ctx.String("profile") 525 526 // if no profile is set then set one 527 if len(prof) == 0 { 528 switch ctx.Args().First() { 529 case "service", "server": 530 prof = "server" 531 default: 532 prof = "client" 533 } 534 } 535 536 // apply the profile 537 if profile, err := profile.Load(prof); err != nil { 538 logger.Fatal(err) 539 } else { 540 // load the profile 541 profile.Setup(ctx) 542 } 543 544 // set the proxy address 545 var proxy string 546 if c.service || ctx.IsSet("proxy_address") { 547 // use the proxy address passed as a flag, this is normally 548 // the micro network 549 proxy = ctx.String("proxy_address") 550 } else { 551 // for CLI, use the external proxy which is loaded from the 552 // local config 553 var err error 554 proxy, err = util.CLIProxyAddress(ctx) 555 if err != nil { 556 return err 557 } 558 } 559 if len(proxy) > 0 { 560 client.DefaultClient.Init(client.Proxy(proxy)) 561 } 562 563 // use the internal network lookup 564 client.DefaultClient.Init( 565 client.Lookup(network.Lookup), 566 ) 567 568 onceBefore.Do(func() { 569 // wrap the client 570 client.DefaultClient = wrapper.AuthClient(client.DefaultClient) 571 client.DefaultClient = wrapper.TraceCall(client.DefaultClient) 572 client.DefaultClient = wrapper.LogClient(client.DefaultClient) 573 client.DefaultClient = wrapper.OpentraceClient(client.DefaultClient) 574 575 // wrap the server 576 server.DefaultServer.Init( 577 server.WrapHandler(wrapper.AuthHandler()), 578 server.WrapHandler(wrapper.TraceHandler()), 579 server.WrapHandler(wrapper.HandlerStats()), 580 server.WrapHandler(wrapper.LogHandler()), 581 server.WrapHandler(wrapper.MetricsHandler()), 582 server.WrapHandler(wrapper.OpenTraceHandler()), 583 ) 584 }) 585 586 // setup auth 587 authOpts := []auth.Option{} 588 if len(ctx.String("namespace")) > 0 { 589 authOpts = append(authOpts, auth.Issuer(ctx.String("namespace"))) 590 } 591 if len(ctx.String("auth_address")) > 0 { 592 authOpts = append(authOpts, auth.Addrs(ctx.String("auth_address"))) 593 } 594 if len(ctx.String("auth_id")) > 0 || len(ctx.String("auth_secret")) > 0 { 595 authOpts = append(authOpts, auth.Credentials( 596 ctx.String("auth_id"), ctx.String("auth_secret"), 597 )) 598 } 599 600 // load the jwt private and public keys, in the case of the server we want to generate them if not 601 // present. The server will inject these creds into the core services, if the services generated 602 // the credentials themselves then they wouldn't match 603 if len(ctx.String("auth_public_key")) > 0 || len(ctx.String("auth_private_key")) > 0 { 604 authOpts = append(authOpts, auth.PublicKey(ctx.String("auth_public_key"))) 605 authOpts = append(authOpts, auth.PrivateKey(ctx.String("auth_private_key"))) 606 } else if ctx.Args().First() == "server" || ctx.Args().First() == "service" { 607 privKey, pubKey, err := user.GetJWTCerts() 608 if err != nil { 609 logger.Fatalf("Error getting keys: %v", err) 610 } 611 authOpts = append(authOpts, auth.PublicKey(string(pubKey)), auth.PrivateKey(string(privKey))) 612 } 613 614 auth.DefaultAuth.Init(authOpts...) 615 616 // setup auth credentials, use local credentials for the CLI and injected creds 617 // for the service. 618 var err error 619 if c.service { 620 err = setupAuthForService() 621 } else { 622 err = setupAuthForCLI(ctx) 623 } 624 if err != nil { 625 logger.Fatalf("Error setting up auth: %v", err) 626 } 627 go refreshAuthToken() 628 629 // initialize the server with the namespace so it knows which domain to register in 630 server.DefaultServer.Init(server.Namespace(ctx.String("namespace"))) 631 632 // setup registry 633 registryOpts := []registry.Option{} 634 635 // Parse registry TLS certs 636 if len(ctx.String("registry_tls_cert")) > 0 || len(ctx.String("registry_tls_key")) > 0 { 637 cert, err := tls.LoadX509KeyPair(ctx.String("registry_tls_cert"), ctx.String("registry_tls_key")) 638 if err != nil { 639 logger.Fatalf("Error loading registry tls cert: %v", err) 640 } 641 642 // load custom certificate authority 643 caCertPool := x509.NewCertPool() 644 if len(ctx.String("registry_tls_ca")) > 0 { 645 crt, err := ioutil.ReadFile(ctx.String("registry_tls_ca")) 646 if err != nil { 647 logger.Fatalf("Error loading registry tls certificate authority: %v", err) 648 } 649 caCertPool.AppendCertsFromPEM(crt) 650 } 651 652 cfg := &tls.Config{Certificates: []tls.Certificate{cert}, RootCAs: caCertPool} 653 registryOpts = append(registryOpts, registry.TLSConfig(cfg)) 654 } 655 if len(ctx.String("registry_address")) > 0 { 656 addresses := strings.Split(ctx.String("registry_address"), ",") 657 registryOpts = append(registryOpts, registry.Addrs(addresses...)) 658 } 659 if err := registry.DefaultRegistry.Init(registryOpts...); err != nil { 660 logger.Fatalf("Error configuring registry: %v", err) 661 } 662 663 // Setup broker options. 664 brokerOpts := []broker.Option{} 665 if len(ctx.String("broker_address")) > 0 { 666 brokerOpts = append(brokerOpts, broker.Addrs(ctx.String("broker_address"))) 667 } 668 669 // Parse broker TLS certs 670 if len(ctx.String("broker_tls_cert")) > 0 || len(ctx.String("broker_tls_key")) > 0 { 671 cert, err := tls.LoadX509KeyPair(ctx.String("broker_tls_cert"), ctx.String("broker_tls_key")) 672 if err != nil { 673 logger.Fatalf("Error loading broker TLS cert: %v", err) 674 } 675 676 // load custom certificate authority 677 caCertPool := x509.NewCertPool() 678 if len(ctx.String("broker_tls_ca")) > 0 { 679 crt, err := ioutil.ReadFile(ctx.String("broker_tls_ca")) 680 if err != nil { 681 logger.Fatalf("Error loading broker TLS certificate authority: %v", err) 682 } 683 caCertPool.AppendCertsFromPEM(crt) 684 } 685 686 cfg := &tls.Config{Certificates: []tls.Certificate{cert}, RootCAs: caCertPool} 687 brokerOpts = append(brokerOpts, broker.TLSConfig(cfg)) 688 } 689 if err := broker.DefaultBroker.Init(brokerOpts...); err != nil { 690 logger.Fatalf("Error configuring broker: %v", err) 691 } 692 if err := broker.DefaultBroker.Connect(); err != nil { 693 logger.Fatalf("Error connecting to broker: %v", err) 694 } 695 696 // Setup runtime. This is a temporary fix to trigger the runtime to recreate 697 // its client now the client has been replaced with a wrapped one. 698 if err := muruntime.DefaultRuntime.Init(); err != nil { 699 logger.Fatalf("Error configuring runtime: %v", err) 700 } 701 702 // Setup store options 703 storeOpts := []store.StoreOption{} 704 if len(ctx.String("store_address")) > 0 { 705 storeOpts = append(storeOpts, store.Nodes(strings.Split(ctx.String("store_address"), ",")...)) 706 } 707 if len(ctx.String("namespace")) > 0 { 708 storeOpts = append(storeOpts, store.Database(ctx.String("namespace"))) 709 } 710 if len(ctx.String("service_name")) > 0 { 711 storeOpts = append(storeOpts, store.Table(ctx.String("service_name"))) 712 } 713 if err := store.DefaultStore.Init(storeOpts...); err != nil { 714 logger.Fatalf("Error configuring store: %v", err) 715 } 716 717 // set the registry and broker in the client and server 718 client.DefaultClient.Init( 719 client.Broker(broker.DefaultBroker), 720 client.Registry(registry.DefaultRegistry), 721 ) 722 server.DefaultServer.Init( 723 server.Broker(broker.DefaultBroker), 724 server.Registry(registry.DefaultRegistry), 725 ) 726 727 // Setup config. Do this after auth is configured since it'll load the config 728 // from the service immediately. We only do this if the action is nil, indicating 729 // a service is being run 730 if c.service && config.DefaultConfig == nil { 731 config.DefaultConfig = configCli.NewConfig(ctx.String("namespace")) 732 } else if config.DefaultConfig == nil { 733 config.DefaultConfig, _ = storeConf.NewConfig(store.DefaultStore, ctx.String("namespace")) 734 } 735 736 // initialize plugins 737 for _, p := range plugin.Plugins() { 738 if err := p.Init(ctx); err != nil { 739 return err 740 } 741 } 742 743 return nil 744 } 745 746 func (c *command) Init(opts ...Option) error { 747 for _, o := range opts { 748 o(&c.opts) 749 } 750 if len(c.opts.Name) > 0 { 751 c.app.Name = c.opts.Name 752 } 753 if len(c.opts.Version) > 0 { 754 c.app.Version = c.opts.Version 755 } 756 c.app.HideVersion = len(c.opts.Version) == 0 757 c.app.Usage = c.opts.Description 758 759 //allow user's flags to add 760 if len(c.opts.Flags) > 0 { 761 c.app.Flags = append(c.app.Flags, c.opts.Flags...) 762 } 763 //action to replace 764 if c.opts.Action != nil { 765 c.app.Action = c.opts.Action 766 } 767 768 return nil 769 } 770 771 func (c *command) Run() error { 772 defer func() { 773 if r := recover(); r != nil { 774 report.Errorf(nil, fmt.Sprintf("panic: %v", string(debug.Stack()))) 775 panic(r) 776 } 777 }() 778 return c.app.Run(os.Args) 779 } 780 781 func (c *command) String() string { 782 return "micro" 783 } 784 785 // Register CLI commands 786 func Register(cmds ...*cli.Command) { 787 app := DefaultCmd.App() 788 app.Commands = append(app.Commands, cmds...) 789 790 // sort the commands so they're listed in order on the cli 791 // todo: move this to micro/cli so it's only run when the 792 // commands are printed during "help" 793 sort.Slice(app.Commands, func(i, j int) bool { 794 return app.Commands[i].Name < app.Commands[j].Name 795 }) 796 } 797 798 // Run the default command 799 func Run() { 800 if err := DefaultCmd.Run(); err != nil { 801 fmt.Println(formatErr(err)) 802 os.Exit(1) 803 } 804 }