go-micro.dev/v5@v5.12.0/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  	"os"
     8  	"sort"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/urfave/cli/v2"
    13  	"go-micro.dev/v5/auth"
    14  	"go-micro.dev/v5/broker"
    15  	nbroker "go-micro.dev/v5/broker/nats"
    16  	rabbit "go-micro.dev/v5/broker/rabbitmq"
    17  	"go-micro.dev/v5/cache"
    18  	"go-micro.dev/v5/cache/redis"
    19  	"go-micro.dev/v5/client"
    20  	"go-micro.dev/v5/config"
    21  	"go-micro.dev/v5/debug/profile"
    22  	"go-micro.dev/v5/debug/profile/http"
    23  	"go-micro.dev/v5/debug/profile/pprof"
    24  	"go-micro.dev/v5/debug/trace"
    25  	"go-micro.dev/v5/events"
    26  	"go-micro.dev/v5/genai"
    27  	"go-micro.dev/v5/genai/gemini"
    28  	"go-micro.dev/v5/genai/openai"
    29  	"go-micro.dev/v5/logger"
    30  	mprofile "go-micro.dev/v5/profile"
    31  	"go-micro.dev/v5/registry"
    32  	"go-micro.dev/v5/registry/consul"
    33  	"go-micro.dev/v5/registry/etcd"
    34  	"go-micro.dev/v5/registry/nats"
    35  	"go-micro.dev/v5/selector"
    36  	"go-micro.dev/v5/server"
    37  	"go-micro.dev/v5/store"
    38  	"go-micro.dev/v5/store/mysql"
    39  	natsjskv "go-micro.dev/v5/store/nats-js-kv"
    40  	postgres "go-micro.dev/v5/store/postgres"
    41  	"go-micro.dev/v5/transport"
    42  	ntransport "go-micro.dev/v5/transport/nats"
    43  )
    44  
    45  type Cmd interface {
    46  	// The cli app within this cmd
    47  	App() *cli.App
    48  	// Adds options, parses flags and initialize
    49  	// exits on error
    50  	Init(opts ...Option) error
    51  	// Options set within this command
    52  	Options() Options
    53  }
    54  
    55  type cmd struct {
    56  	opts Options
    57  	app  *cli.App
    58  }
    59  
    60  type Option func(o *Options)
    61  
    62  var (
    63  	DefaultCmd = newCmd()
    64  
    65  	DefaultFlags = []cli.Flag{
    66  		&cli.StringFlag{
    67  			Name:    "client",
    68  			EnvVars: []string{"MICRO_CLIENT"},
    69  			Usage:   "Client for go-micro; rpc",
    70  		},
    71  		&cli.StringFlag{
    72  			Name:    "client_request_timeout",
    73  			EnvVars: []string{"MICRO_CLIENT_REQUEST_TIMEOUT"},
    74  			Usage:   "Sets the client request timeout. e.g 500ms, 5s, 1m. Default: 5s",
    75  		},
    76  		&cli.IntFlag{
    77  			Name:    "client_retries",
    78  			EnvVars: []string{"MICRO_CLIENT_RETRIES"},
    79  			Value:   client.DefaultRetries,
    80  			Usage:   "Sets the client retries. Default: 1",
    81  		},
    82  		&cli.IntFlag{
    83  			Name:    "client_pool_size",
    84  			EnvVars: []string{"MICRO_CLIENT_POOL_SIZE"},
    85  			Usage:   "Sets the client connection pool size. Default: 1",
    86  		},
    87  		&cli.StringFlag{
    88  			Name:    "client_pool_ttl",
    89  			EnvVars: []string{"MICRO_CLIENT_POOL_TTL"},
    90  			Usage:   "Sets the client connection pool ttl. e.g 500ms, 5s, 1m. Default: 1m",
    91  		},
    92  		&cli.IntFlag{
    93  			Name:    "register_ttl",
    94  			EnvVars: []string{"MICRO_REGISTER_TTL"},
    95  			Value:   60,
    96  			Usage:   "Register TTL in seconds",
    97  		},
    98  		&cli.IntFlag{
    99  			Name:    "register_interval",
   100  			EnvVars: []string{"MICRO_REGISTER_INTERVAL"},
   101  			Value:   30,
   102  			Usage:   "Register interval in seconds",
   103  		},
   104  		&cli.StringFlag{
   105  			Name:    "server",
   106  			EnvVars: []string{"MICRO_SERVER"},
   107  			Usage:   "Server for go-micro; rpc",
   108  		},
   109  		&cli.StringFlag{
   110  			Name:    "server_name",
   111  			EnvVars: []string{"MICRO_SERVER_NAME"},
   112  			Usage:   "Name of the server. go.micro.srv.example",
   113  		},
   114  		&cli.StringFlag{
   115  			Name:    "server_version",
   116  			EnvVars: []string{"MICRO_SERVER_VERSION"},
   117  			Usage:   "Version of the server. 1.1.0",
   118  		},
   119  		&cli.StringFlag{
   120  			Name:    "server_id",
   121  			EnvVars: []string{"MICRO_SERVER_ID"},
   122  			Usage:   "Id of the server. Auto-generated if not specified",
   123  		},
   124  		&cli.StringFlag{
   125  			Name:    "server_address",
   126  			EnvVars: []string{"MICRO_SERVER_ADDRESS"},
   127  			Usage:   "Bind address for the server. 127.0.0.1:8080",
   128  		},
   129  		&cli.StringFlag{
   130  			Name:    "server_advertise",
   131  			EnvVars: []string{"MICRO_SERVER_ADVERTISE"},
   132  			Usage:   "Used instead of the server_address when registering with discovery. 127.0.0.1:8080",
   133  		},
   134  		&cli.StringSliceFlag{
   135  			Name:    "server_metadata",
   136  			EnvVars: []string{"MICRO_SERVER_METADATA"},
   137  			Value:   &cli.StringSlice{},
   138  			Usage:   "A list of key-value pairs defining metadata. version=1.0.0",
   139  		},
   140  		&cli.StringFlag{
   141  			Name:    "broker",
   142  			EnvVars: []string{"MICRO_BROKER"},
   143  			Usage:   "Broker for pub/sub. http, nats, rabbitmq",
   144  		},
   145  		&cli.StringFlag{
   146  			Name:    "broker_address",
   147  			EnvVars: []string{"MICRO_BROKER_ADDRESS"},
   148  			Usage:   "Comma-separated list of broker addresses",
   149  		},
   150  		&cli.StringFlag{
   151  			Name:    "profile",
   152  			Usage:   "Plugin profile to use. (local, nats, etc)",
   153  			EnvVars: []string{"MICRO_PROFILE"},
   154  		},
   155  		&cli.StringFlag{
   156  			Name:    "debug-profile",
   157  			Usage:   "Debug Plugin profile to use.",
   158  			EnvVars: []string{"MICRO_DEBUG_PROFILE"},
   159  		},
   160  		&cli.StringFlag{
   161  			Name:    "registry",
   162  			EnvVars: []string{"MICRO_REGISTRY"},
   163  			Usage:   "Registry for discovery. etcd, mdns",
   164  		},
   165  		&cli.StringFlag{
   166  			Name:    "registry_address",
   167  			EnvVars: []string{"MICRO_REGISTRY_ADDRESS"},
   168  			Usage:   "Comma-separated list of registry addresses",
   169  		},
   170  		&cli.StringFlag{
   171  			Name:    "selector",
   172  			EnvVars: []string{"MICRO_SELECTOR"},
   173  			Usage:   "Selector used to pick nodes for querying",
   174  		},
   175  		&cli.StringFlag{
   176  			Name:    "store",
   177  			EnvVars: []string{"MICRO_STORE"},
   178  			Usage:   "Store used for key-value storage",
   179  		},
   180  		&cli.StringFlag{
   181  			Name:    "store_address",
   182  			EnvVars: []string{"MICRO_STORE_ADDRESS"},
   183  			Usage:   "Comma-separated list of store addresses",
   184  		},
   185  		&cli.StringFlag{
   186  			Name:    "store_database",
   187  			EnvVars: []string{"MICRO_STORE_DATABASE"},
   188  			Usage:   "Database option for the underlying store",
   189  		},
   190  		&cli.StringFlag{
   191  			Name:    "store_table",
   192  			EnvVars: []string{"MICRO_STORE_TABLE"},
   193  			Usage:   "Table option for the underlying store",
   194  		},
   195  		&cli.StringFlag{
   196  			Name:    "transport",
   197  			EnvVars: []string{"MICRO_TRANSPORT"},
   198  			Usage:   "Transport mechanism used; http",
   199  		},
   200  		&cli.StringFlag{
   201  			Name:    "transport_address",
   202  			EnvVars: []string{"MICRO_TRANSPORT_ADDRESS"},
   203  			Usage:   "Comma-separated list of transport addresses",
   204  		},
   205  		&cli.StringFlag{
   206  			Name:    "tracer",
   207  			EnvVars: []string{"MICRO_TRACER"},
   208  			Usage:   "Tracer for distributed tracing, e.g. memory, jaeger",
   209  		},
   210  		&cli.StringFlag{
   211  			Name:    "tracer_address",
   212  			EnvVars: []string{"MICRO_TRACER_ADDRESS"},
   213  			Usage:   "Comma-separated list of tracer addresses",
   214  		},
   215  		&cli.StringFlag{
   216  			Name:    "auth",
   217  			EnvVars: []string{"MICRO_AUTH"},
   218  			Usage:   "Auth for role based access control, e.g. service",
   219  		},
   220  		&cli.StringFlag{
   221  			Name:    "auth_id",
   222  			EnvVars: []string{"MICRO_AUTH_ID"},
   223  			Usage:   "Account ID used for client authentication",
   224  		},
   225  		&cli.StringFlag{
   226  			Name:    "auth_secret",
   227  			EnvVars: []string{"MICRO_AUTH_SECRET"},
   228  			Usage:   "Account secret used for client authentication",
   229  		},
   230  		&cli.StringFlag{
   231  			Name:    "auth_namespace",
   232  			EnvVars: []string{"MICRO_AUTH_NAMESPACE"},
   233  			Usage:   "Namespace for the services auth account",
   234  			Value:   "go.micro",
   235  		},
   236  		&cli.StringFlag{
   237  			Name:    "auth_public_key",
   238  			EnvVars: []string{"MICRO_AUTH_PUBLIC_KEY"},
   239  			Usage:   "Public key for JWT auth (base64 encoded PEM)",
   240  		},
   241  		&cli.StringFlag{
   242  			Name:    "auth_private_key",
   243  			EnvVars: []string{"MICRO_AUTH_PRIVATE_KEY"},
   244  			Usage:   "Private key for JWT auth (base64 encoded PEM)",
   245  		},
   246  		&cli.StringFlag{
   247  			Name:    "config",
   248  			EnvVars: []string{"MICRO_CONFIG"},
   249  			Usage:   "The source of the config to be used to get configuration",
   250  		},
   251  		&cli.StringFlag{
   252  			Name:    "genai",
   253  			EnvVars: []string{"MICRO_GENAI"},
   254  			Usage:   "GenAI provider to use (e.g. openai, gemini, noop)",
   255  		},
   256  		&cli.StringFlag{
   257  			Name:    "genai_key",
   258  			EnvVars: []string{"MICRO_GENAI_KEY"},
   259  			Usage:   "GenAI API key",
   260  		},
   261  		&cli.StringFlag{
   262  			Name:    "genai_model",
   263  			EnvVars: []string{"MICRO_GENAI_MODEL"},
   264  			Usage:   "GenAI model to use (optional)",
   265  		},
   266  	}
   267  
   268  	DefaultBrokers = map[string]func(...broker.Option) broker.Broker{
   269  		"memory":   broker.NewMemoryBroker,
   270  		"http":     broker.NewHttpBroker,
   271  		"nats":     nbroker.NewNatsBroker,
   272  		"rabbitmq": rabbit.NewBroker,
   273  	}
   274  
   275  	DefaultClients = map[string]func(...client.Option) client.Client{}
   276  
   277  	DefaultRegistries = map[string]func(...registry.Option) registry.Registry{
   278  		"consul": consul.NewConsulRegistry,
   279  		"memory": registry.NewMemoryRegistry,
   280  		"nats":   nats.NewNatsRegistry,
   281  		"mdns":   registry.NewMDNSRegistry,
   282  		"etcd":   etcd.NewEtcdRegistry,
   283  	}
   284  
   285  	DefaultSelectors = map[string]func(...selector.Option) selector.Selector{}
   286  
   287  	DefaultServers = map[string]func(...server.Option) server.Server{}
   288  
   289  	DefaultTransports = map[string]func(...transport.Option) transport.Transport{
   290  		"nats": ntransport.NewTransport,
   291  	}
   292  
   293  	DefaultStores = map[string]func(...store.Option) store.Store{
   294  		"memory":   store.NewMemoryStore,
   295  		"mysql":    mysql.NewMysqlStore,
   296  		"natsjskv": natsjskv.NewStore,
   297  		"postgres": postgres.NewStore,
   298  	}
   299  
   300  	DefaultTracers = map[string]func(...trace.Option) trace.Tracer{}
   301  
   302  	DefaultAuths = map[string]func(...auth.Option) auth.Auth{}
   303  
   304  	DefaultDebugProfiles = map[string]func(...profile.Option) profile.Profile{
   305  		"http":  http.NewProfile,
   306  		"pprof": pprof.NewProfile,
   307  	}
   308  
   309  	DefaultConfigs = map[string]func(...config.Option) (config.Config, error){}
   310  
   311  	DefaultCaches = map[string]func(...cache.Option) cache.Cache{
   312  		"redis": redis.NewRedisCache,
   313  	}
   314  	DefaultStreams = map[string]func(...events.Option) (events.Stream, error){}
   315  
   316  	DefaultGenAI = map[string]func(...genai.Option) genai.GenAI{
   317  		"openai": openai.New,
   318  		"gemini": gemini.New,
   319  	}
   320  )
   321  
   322  func init() {
   323  	rand.Seed(time.Now().Unix())
   324  }
   325  
   326  func newCmd(opts ...Option) Cmd {
   327  	options := Options{
   328  		Auth:         &auth.DefaultAuth,
   329  		Broker:       &broker.DefaultBroker,
   330  		Client:       &client.DefaultClient,
   331  		Registry:     &registry.DefaultRegistry,
   332  		Server:       &server.DefaultServer,
   333  		Selector:     &selector.DefaultSelector,
   334  		Transport:    &transport.DefaultTransport,
   335  		Store:        &store.DefaultStore,
   336  		Tracer:       &trace.DefaultTracer,
   337  		DebugProfile: &profile.DefaultProfile,
   338  		Config:       &config.DefaultConfig,
   339  		Cache:        &cache.DefaultCache,
   340  		Stream:       &events.DefaultStream,
   341  
   342  		Brokers:       DefaultBrokers,
   343  		Clients:       DefaultClients,
   344  		Registries:    DefaultRegistries,
   345  		Selectors:     DefaultSelectors,
   346  		Servers:       DefaultServers,
   347  		Transports:    DefaultTransports,
   348  		Stores:        DefaultStores,
   349  		Tracers:       DefaultTracers,
   350  		Auths:         DefaultAuths,
   351  		DebugProfiles: DefaultDebugProfiles,
   352  		Configs:       DefaultConfigs,
   353  		Caches:        DefaultCaches,
   354  	}
   355  
   356  	for _, o := range opts {
   357  		o(&options)
   358  	}
   359  
   360  	if len(options.Description) == 0 {
   361  		options.Description = "a go-micro service"
   362  	}
   363  
   364  	cmd := new(cmd)
   365  	cmd.opts = options
   366  	cmd.app = cli.NewApp()
   367  	cmd.app.Name = cmd.opts.Name
   368  	cmd.app.Version = cmd.opts.Version
   369  	cmd.app.Usage = cmd.opts.Description
   370  	cmd.app.Before = cmd.Before
   371  	cmd.app.Flags = DefaultFlags
   372  	cmd.app.Action = func(c *cli.Context) error {
   373  		return nil
   374  	}
   375  
   376  	if len(options.Version) == 0 {
   377  		cmd.app.HideVersion = true
   378  	}
   379  
   380  	return cmd
   381  }
   382  
   383  func (c *cmd) App() *cli.App {
   384  	return c.app
   385  }
   386  
   387  func (c *cmd) Options() Options {
   388  	return c.opts
   389  }
   390  
   391  func (c *cmd) Before(ctx *cli.Context) error {
   392  	// Set GenAI provider from flags/env
   393  	setGenAIFromFlags(ctx)
   394  	// If flags are set then use them otherwise do nothing
   395  	var serverOpts []server.Option
   396  	var clientOpts []client.Option
   397  	// --- Profile Grouping Extension ---
   398  
   399  	profileName := ctx.String("profile")
   400  	if profileName == "" {
   401  		profileName = os.Getenv("MICRO_PROFILE")
   402  	}
   403  	if profileName != "" {
   404  		switch profileName {
   405  		case "local":
   406  			imported, ierr := mprofile.LocalProfile()
   407  			if ierr != nil {
   408  				return fmt.Errorf("failed to load local profile: %v", ierr)
   409  			}
   410  			*c.opts.Registry = imported.Registry
   411  			registry.DefaultRegistry = imported.Registry
   412  			*c.opts.Broker = imported.Broker
   413  			broker.DefaultBroker = imported.Broker
   414  			*c.opts.Store = imported.Store
   415  			store.DefaultStore = imported.Store
   416  			*c.opts.Transport = imported.Transport
   417  			transport.DefaultTransport = imported.Transport
   418  		case "nats":
   419  			imported, ierr := mprofile.NatsProfile()
   420  			if ierr != nil {
   421  				return fmt.Errorf("failed to load nats profile: %v", ierr)
   422  			}
   423  			// Set the registry
   424  			sopts, clopts := c.setRegistry(imported.Registry)
   425  			serverOpts = append(serverOpts, sopts...)
   426  			clientOpts = append(clientOpts, clopts...)
   427  
   428  			// set the store
   429  			sopts, clopts = c.setStore(imported.Store)
   430  			serverOpts = append(serverOpts, sopts...)
   431  			clientOpts = append(clientOpts, clopts...)
   432  
   433  			// set the transport
   434  			sopts, clopts = c.setTransport(imported.Transport)
   435  			serverOpts = append(serverOpts, sopts...)
   436  			clientOpts = append(clientOpts, clopts...)
   437  
   438  			// Set the broker
   439  			sopts, clopts = c.setBroker(imported.Broker)
   440  			serverOpts = append(serverOpts, sopts...)
   441  			clientOpts = append(clientOpts, clopts...)
   442  
   443  			// Set the stream
   444  			sopts, clopts = c.setStream(imported.Stream)
   445  			serverOpts = append(serverOpts, sopts...)
   446  			clientOpts = append(clientOpts, clopts...)
   447  
   448  		// Add more profiles as needed
   449  		default:
   450  			return fmt.Errorf("unsupported profile: %s", profileName)
   451  		}
   452  	}
   453  	// Set the client
   454  	if name := ctx.String("client"); len(name) > 0 {
   455  		// only change if we have the client and type differs
   456  		if cl, ok := c.opts.Clients[name]; ok && (*c.opts.Client).String() != name {
   457  			*c.opts.Client = cl()
   458  			client.DefaultClient = *c.opts.Client
   459  		}
   460  	}
   461  
   462  	// Set the server
   463  	if name := ctx.String("server"); len(name) > 0 {
   464  		// only change if we have the server and type differs
   465  		if s, ok := c.opts.Servers[name]; ok && (*c.opts.Server).String() != name {
   466  			*c.opts.Server = s()
   467  			server.DefaultServer = *c.opts.Server
   468  		}
   469  	}
   470  
   471  	// Set the store
   472  	if name := ctx.String("store"); len(name) > 0 {
   473  		s, ok := c.opts.Stores[name]
   474  		if !ok {
   475  			return fmt.Errorf("unsupported store: %s", name)
   476  		}
   477  
   478  		*c.opts.Store = s(store.WithClient(*c.opts.Client))
   479  		store.DefaultStore = *c.opts.Store
   480  	}
   481  
   482  	// Set the tracer
   483  	if name := ctx.String("tracer"); len(name) > 0 {
   484  		r, ok := c.opts.Tracers[name]
   485  		if !ok {
   486  			return fmt.Errorf("unsupported tracer: %s", name)
   487  		}
   488  
   489  		*c.opts.Tracer = r()
   490  		trace.DefaultTracer = *c.opts.Tracer
   491  	}
   492  
   493  	// Setup auth
   494  	authOpts := []auth.Option{}
   495  
   496  	if len(ctx.String("auth_id")) > 0 || len(ctx.String("auth_secret")) > 0 {
   497  		authOpts = append(authOpts, auth.Credentials(
   498  			ctx.String("auth_id"), ctx.String("auth_secret"),
   499  		))
   500  	}
   501  	if len(ctx.String("auth_public_key")) > 0 {
   502  		authOpts = append(authOpts, auth.PublicKey(ctx.String("auth_public_key")))
   503  	}
   504  	if len(ctx.String("auth_private_key")) > 0 {
   505  		authOpts = append(authOpts, auth.PrivateKey(ctx.String("auth_private_key")))
   506  	}
   507  	if len(ctx.String("auth_namespace")) > 0 {
   508  		authOpts = append(authOpts, auth.Namespace(ctx.String("auth_namespace")))
   509  	}
   510  	if name := ctx.String("auth"); len(name) > 0 {
   511  		r, ok := c.opts.Auths[name]
   512  		if !ok {
   513  			return fmt.Errorf("unsupported auth: %s", name)
   514  		}
   515  
   516  		*c.opts.Auth = r(authOpts...)
   517  		auth.DefaultAuth = *c.opts.Auth
   518  	}
   519  
   520  	// Set the registry
   521  	if name := ctx.String("registry"); len(name) > 0 && (*c.opts.Registry).String() != name {
   522  		r, ok := c.opts.Registries[name]
   523  		if !ok {
   524  			return fmt.Errorf("Registry %s not found", name)
   525  		}
   526  
   527  		sopts, clopts := c.setRegistry(r())
   528  		serverOpts = append(serverOpts, sopts...)
   529  		clientOpts = append(clientOpts, clopts...)
   530  	}
   531  
   532  	// Set the debug profile
   533  	if name := ctx.String("debug-profile"); len(name) > 0 {
   534  		p, ok := c.opts.DebugProfiles[name]
   535  		if !ok {
   536  			return fmt.Errorf("unsupported profile: %s", name)
   537  		}
   538  		*c.opts.DebugProfile = p()
   539  		profile.DefaultProfile = *c.opts.DebugProfile
   540  	}
   541  
   542  	// Set the broker
   543  	if name := ctx.String("broker"); len(name) > 0 && (*c.opts.Broker).String() != name {
   544  		b, ok := c.opts.Brokers[name]
   545  		if !ok {
   546  			return fmt.Errorf("Broker %s not found", name)
   547  		}
   548  		sopts, clopts := c.setBroker(b())
   549  		serverOpts = append(serverOpts, sopts...)
   550  		clientOpts = append(clientOpts, clopts...)
   551  	}
   552  
   553  	// Set the selector
   554  	if name := ctx.String("selector"); len(name) > 0 && (*c.opts.Selector).String() != name {
   555  		s, ok := c.opts.Selectors[name]
   556  		if !ok {
   557  			return fmt.Errorf("Selector %s not found", name)
   558  		}
   559  
   560  		*c.opts.Selector = s(selector.Registry(*c.opts.Registry))
   561  
   562  		// No server option here. Should there be?
   563  		clientOpts = append(clientOpts, client.Selector(*c.opts.Selector))
   564  		selector.DefaultSelector = *c.opts.Selector
   565  	}
   566  
   567  	// Set the transport
   568  	if name := ctx.String("transport"); len(name) > 0 && (*c.opts.Transport).String() != name {
   569  		t, ok := c.opts.Transports[name]
   570  		if !ok {
   571  			return fmt.Errorf("Transport %s not found", name)
   572  		}
   573  
   574  		sopts, clopts := c.setTransport(t())
   575  		serverOpts = append(serverOpts, sopts...)
   576  		clientOpts = append(clientOpts, clopts...)
   577  
   578  	}
   579  
   580  	// Parse the server options
   581  	metadata := make(map[string]string)
   582  	for _, d := range ctx.StringSlice("server_metadata") {
   583  		var key, val string
   584  		parts := strings.Split(d, "=")
   585  		key = parts[0]
   586  		if len(parts) > 1 {
   587  			val = strings.Join(parts[1:], "=")
   588  		}
   589  		metadata[key] = val
   590  	}
   591  
   592  	if len(metadata) > 0 {
   593  		serverOpts = append(serverOpts, server.Metadata(metadata))
   594  	}
   595  
   596  	if len(ctx.String("broker_address")) > 0 {
   597  		if err := (*c.opts.Broker).Init(broker.Addrs(strings.Split(ctx.String("broker_address"), ",")...)); err != nil {
   598  			logger.Fatalf("Error configuring broker: %v", err)
   599  		}
   600  	}
   601  
   602  	if len(ctx.String("registry_address")) > 0 {
   603  		if err := (*c.opts.Registry).Init(registry.Addrs(strings.Split(ctx.String("registry_address"), ",")...)); err != nil {
   604  			logger.Fatalf("Error configuring registry: %v", err)
   605  		}
   606  	}
   607  
   608  	if len(ctx.String("transport_address")) > 0 {
   609  		if err := (*c.opts.Transport).Init(transport.Addrs(strings.Split(ctx.String("transport_address"), ",")...)); err != nil {
   610  			logger.Fatalf("Error configuring transport: %v", err)
   611  		}
   612  	}
   613  
   614  	if len(ctx.String("store_address")) > 0 {
   615  		if err := (*c.opts.Store).Init(store.Nodes(strings.Split(ctx.String("store_address"), ",")...)); err != nil {
   616  			logger.Fatalf("Error configuring store: %v", err)
   617  		}
   618  	}
   619  
   620  	if len(ctx.String("store_database")) > 0 {
   621  		if err := (*c.opts.Store).Init(store.Database(ctx.String("store_database"))); err != nil {
   622  			logger.Fatalf("Error configuring store database option: %v", err)
   623  		}
   624  	}
   625  
   626  	if len(ctx.String("store_table")) > 0 {
   627  		if err := (*c.opts.Store).Init(store.Table(ctx.String("store_table"))); err != nil {
   628  			logger.Fatalf("Error configuring store table option: %v", err)
   629  		}
   630  	}
   631  
   632  	if len(ctx.String("server_name")) > 0 {
   633  		serverOpts = append(serverOpts, server.Name(ctx.String("server_name")))
   634  	}
   635  
   636  	if len(ctx.String("server_version")) > 0 {
   637  		serverOpts = append(serverOpts, server.Version(ctx.String("server_version")))
   638  	}
   639  
   640  	if len(ctx.String("server_id")) > 0 {
   641  		serverOpts = append(serverOpts, server.Id(ctx.String("server_id")))
   642  	}
   643  
   644  	if len(ctx.String("server_address")) > 0 {
   645  		serverOpts = append(serverOpts, server.Address(ctx.String("server_address")))
   646  	}
   647  
   648  	if len(ctx.String("server_advertise")) > 0 {
   649  		serverOpts = append(serverOpts, server.Advertise(ctx.String("server_advertise")))
   650  	}
   651  
   652  	if ttl := time.Duration(ctx.Int("register_ttl")); ttl >= 0 {
   653  		serverOpts = append(serverOpts, server.RegisterTTL(ttl*time.Second))
   654  	}
   655  
   656  	if val := time.Duration(ctx.Int("register_interval")); val >= 0 {
   657  		serverOpts = append(serverOpts, server.RegisterInterval(val*time.Second))
   658  	}
   659  
   660  	// client opts
   661  	if r := ctx.Int("client_retries"); r >= 0 {
   662  		clientOpts = append(clientOpts, client.Retries(r))
   663  	}
   664  
   665  	if t := ctx.String("client_request_timeout"); len(t) > 0 {
   666  		d, err := time.ParseDuration(t)
   667  		if err != nil {
   668  			return fmt.Errorf("failed to parse client_request_timeout: %v", t)
   669  		}
   670  		clientOpts = append(clientOpts, client.RequestTimeout(d))
   671  	}
   672  
   673  	if r := ctx.Int("client_pool_size"); r > 0 {
   674  		clientOpts = append(clientOpts, client.PoolSize(r))
   675  	}
   676  
   677  	if t := ctx.String("client_pool_ttl"); len(t) > 0 {
   678  		d, err := time.ParseDuration(t)
   679  		if err != nil {
   680  			return fmt.Errorf("failed to parse client_pool_ttl: %v", t)
   681  		}
   682  		clientOpts = append(clientOpts, client.PoolTTL(d))
   683  	}
   684  
   685  	if t := ctx.String("client_pool_close_timeout"); len(t) > 0 {
   686  		d, err := time.ParseDuration(t)
   687  		if err != nil {
   688  			return fmt.Errorf("failed to parse client_pool_close_timeout: %v", t)
   689  		}
   690  		clientOpts = append(clientOpts, client.PoolCloseTimeout(d))
   691  	}
   692  
   693  	// We have some command line opts for the server.
   694  	// Lets set it up
   695  	if len(serverOpts) > 0 {
   696  		if err := (*c.opts.Server).Init(serverOpts...); err != nil {
   697  			logger.Fatalf("Error configuring server: %v", err)
   698  		}
   699  	}
   700  
   701  	// Use an init option?
   702  	if len(clientOpts) > 0 {
   703  		if err := (*c.opts.Client).Init(clientOpts...); err != nil {
   704  			logger.Fatalf("Error configuring client: %v", err)
   705  		}
   706  	}
   707  
   708  	// config
   709  	if name := ctx.String("config"); len(name) > 0 {
   710  		// only change if we have the server and type differs
   711  		if r, ok := c.opts.Configs[name]; ok {
   712  			rc, err := r()
   713  			if err != nil {
   714  				logger.Fatalf("Error configuring config: %v", err)
   715  			}
   716  			*c.opts.Config = rc
   717  			config.DefaultConfig = *c.opts.Config
   718  		}
   719  	}
   720  	return nil
   721  }
   722  
   723  func (c *cmd) setRegistry(r registry.Registry) ([]server.Option, []client.Option) {
   724  	var serverOpts []server.Option
   725  	var clientOpts []client.Option
   726  	*c.opts.Registry = r
   727  	serverOpts = append(serverOpts, server.Registry(*c.opts.Registry))
   728  	clientOpts = append(clientOpts, client.Registry(*c.opts.Registry))
   729  
   730  	if err := (*c.opts.Selector).Init(selector.Registry(*c.opts.Registry)); err != nil {
   731  		logger.Fatalf("Error configuring registry: %v", err)
   732  	}
   733  
   734  	clientOpts = append(clientOpts, client.Selector(*c.opts.Selector))
   735  
   736  	if err := (*c.opts.Broker).Init(broker.Registry(*c.opts.Registry)); err != nil {
   737  		logger.Fatalf("Error configuring broker: %v", err)
   738  	}
   739  	registry.DefaultRegistry = *c.opts.Registry
   740  	return serverOpts, clientOpts
   741  }
   742  func (c *cmd) setStream(s events.Stream) ([]server.Option, []client.Option) {
   743  	var serverOpts []server.Option
   744  	var clientOpts []client.Option
   745  	*c.opts.Stream = s
   746  	// TODO: do server and client need a Stream?
   747  	// serverOpts = append(serverOpts, server.Registry(*c.opts.Registry))
   748  	// clientOpts = append(clientOpts, client.Registry(*c.opts.Registry))
   749  
   750  	events.DefaultStream = *c.opts.Stream
   751  	return serverOpts, clientOpts
   752  }
   753  
   754  func (c *cmd) setBroker(b broker.Broker) ([]server.Option, []client.Option) {
   755  	var serverOpts []server.Option
   756  	var clientOpts []client.Option
   757  	*c.opts.Broker = b
   758  	serverOpts = append(serverOpts, server.Broker(*c.opts.Broker))
   759  	clientOpts = append(clientOpts, client.Broker(*c.opts.Broker))
   760  	broker.DefaultBroker = *c.opts.Broker
   761  	return serverOpts, clientOpts
   762  }
   763  
   764  func (c *cmd) setStore(s store.Store) ([]server.Option, []client.Option) {
   765  	var serverOpts []server.Option
   766  	var clientOpts []client.Option
   767  	*c.opts.Store = s
   768  	store.DefaultStore = *c.opts.Store
   769  	return serverOpts, clientOpts
   770  }
   771  
   772  func (c *cmd) setTransport(t transport.Transport) ([]server.Option, []client.Option) {
   773  	var serverOpts []server.Option
   774  	var clientOpts []client.Option
   775  	*c.opts.Transport = t
   776  	serverOpts = append(serverOpts, server.Transport(*c.opts.Transport))
   777  	clientOpts = append(clientOpts, client.Transport(*c.opts.Transport))
   778  	transport.DefaultTransport = *c.opts.Transport
   779  	return serverOpts, clientOpts
   780  }
   781  
   782  func (c *cmd) Init(opts ...Option) error {
   783  	for _, o := range opts {
   784  		o(&c.opts)
   785  	}
   786  	if len(c.opts.Name) > 0 {
   787  		c.app.Name = c.opts.Name
   788  	}
   789  	if len(c.opts.Version) > 0 {
   790  		c.app.Version = c.opts.Version
   791  	}
   792  	c.app.HideVersion = len(c.opts.Version) == 0
   793  	c.app.Usage = c.opts.Description
   794  	c.app.RunAndExitOnError()
   795  	return nil
   796  }
   797  
   798  func DefaultOptions() Options {
   799  	return DefaultCmd.Options()
   800  }
   801  
   802  func App() *cli.App {
   803  	return DefaultCmd.App()
   804  }
   805  
   806  func Init(opts ...Option) error {
   807  	return DefaultCmd.Init(opts...)
   808  }
   809  
   810  func NewCmd(opts ...Option) Cmd {
   811  	return newCmd(opts...)
   812  }
   813  
   814  // Register CLI commands
   815  func Register(cmds ...*cli.Command) {
   816  	app := DefaultCmd.App()
   817  	app.Commands = append(app.Commands, cmds...)
   818  
   819  	// sort the commands so they're listed in order on the cli
   820  	// todo: move this to micro/cli so it's only run when the
   821  	// commands are printed during "help"
   822  	sort.Slice(app.Commands, func(i, j int) bool {
   823  		return app.Commands[i].Name < app.Commands[j].Name
   824  	})
   825  }
   826  
   827  func setGenAIFromFlags(ctx *cli.Context) {
   828  	provider := ctx.String("genai")
   829  	key := ctx.String("genai_key")
   830  	model := ctx.String("genai_model")
   831  
   832  	switch provider {
   833  	case "openai":
   834  		if key == "" {
   835  			key = os.Getenv("OPENAI_API_KEY")
   836  		}
   837  		genai.DefaultGenAI = openai.New(genai.WithAPIKey(key), genai.WithModel(model))
   838  	case "gemini":
   839  		if key == "" {
   840  			key = os.Getenv("GEMINI_API_KEY")
   841  		}
   842  		genai.DefaultGenAI = gemini.New(genai.WithAPIKey(key), genai.WithModel(model))
   843  	default:
   844  		genai.DefaultGenAI = genai.Default
   845  	}
   846  }