从micro 命令行说起谈谈micro(一)

1,487 阅读9分钟

注:本文的micro 版本为v3.0

1. 一切的开始

package main

//go:generate ./scripts/generate.sh

import (
	"fmt"
	"os"

	"github.com/micro/micro/v3/cmd"

	// internal packages
	_ "github.com/micro/micro/v3/internal/usage"

	// load packages so they can register commands
	_ "github.com/micro/micro/v3/client/cli"
	_ "github.com/micro/micro/v3/client/cli/init"
	_ "github.com/micro/micro/v3/client/cli/new"
	_ "github.com/micro/micro/v3/client/cli/signup"
	_ "github.com/micro/micro/v3/client/cli/user"
	_ "github.com/micro/micro/v3/server"
	_ "github.com/micro/micro/v3/service/auth/cli"
	_ "github.com/micro/micro/v3/service/cli"
	_ "github.com/micro/micro/v3/service/config/cli"
	_ "github.com/micro/micro/v3/service/network/cli"
	_ "github.com/micro/micro/v3/service/runtime/cli"
	_ "github.com/micro/micro/v3/service/store/cli"
)

func main() {
	if err := cmd.DefaultCmd.Run(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

一眼看去,内容确实是不多的。主要涉及的操作就是执行默认命令行的Run方法,并进行错误检测。

但是从导入的软件包看去,可以发现其实内容还是不少的,只不是大多都是进行初始化操作。我们可以看看主要有哪些,以及它的主要用途是做什么的。

2 plugin

第一个匿名导入的是一个内部包usage,我们来看他主要做了什么

func init() {
	plugin.Register(Plugin())
}

func Plugin() plugin.Plugin {
	var requests uint64

	// create rand
	source := rand.NewSource(time.Now().UnixNano())
	r := rand.New(source)

	return plugin.NewPlugin(
		plugin.WithName("usage"),
		plugin.WithInit(func(c *cli.Context) error {
			// only do if enabled
			if !c.Bool("report_usage") {
				os.Setenv("MICRO_REPORT_USAGE", "false")
				return nil
			}

			var service string

			// set service name
			if c.Args().Len() > 0 && len(c.Args().Get(0)) > 0 {
				service = c.Args().Get(0)
			}

			// service subcommand
			if service == "service" {
				// set as the sub command
				if v := c.Args().Get(1); len(v) > 0 {
					service = v
				}
			}

			// kick off the tracker
			go func() {
				// new report
				u := New(service)

				// initial publish in 30-60 seconds
				d := 30 + r.Intn(30)
				time.Sleep(time.Second * time.Duration(d))

				for {
					// get service list
					s, _ := registry.ListServices()
					// get requests
					reqs := atomic.LoadUint64(&requests)
					srvs := uint64(len(s))

					// reset requests
					atomic.StoreUint64(&requests, 0)

					// set metrics
					u.Metrics.Count["instances"] = uint64(1)
					u.Metrics.Count["requests"] = reqs
					u.Metrics.Count["services"] = srvs

					// attempt to send report 3 times
					for i := 1; i <= 3; i++ {
						if err := Report(u); err != nil {
							time.Sleep(backoff.Do(i * 2))
							continue
						}
						break
					}

					// now sleep 24 hours
					time.Sleep(time.Hour * 24)
				}
			}()

			return nil
		}),
		plugin.WithHandler(func(h http.Handler) http.Handler {
			// only enable if set
			if v := os.Getenv("MICRO_REPORT_USAGE"); v == "false" {
				return h
			}

			// return usage recorder
			return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				// count requests
				atomic.AddUint64(&requests, 1)
				// serve the request
				h.ServeHTTP(w, r)
			})
		}),
	)
}

要想理解上面的代码含义以及功能,需要了解一下micro中plugin的功能和原理

Plugins 是一种扩展Micro功能的方式。它可以使Micro扩展和被拦截以提供其他功能。这包含日志,指标获取,追踪,认证等等。插件模型需要注册成满足plugin接口的结构体。然后在Micro启动时进行注册和设置。

以上就是plugin接口设计的目的。

当然了,不同于go-micro中的plugin, 这里的plugin主要用于micro API, Web, Sidecar, CLI.这也是用于在HTTP里面构建中间件的方法。

type Plugin interface {
	// 全局的flags
	Flags() []cli.Flag
	// 自命令集
	Commands() []*cli.Command
	// Handle 是HTTP 请求的中间件处理器。我们将它传入到已经存在的handler中以使它可以被包裹来创建一个调用链
	Handler() Handler
	// Init方法在命令行参数被解析到时候调用,传入的参数是已经初始化后的cli.Context
	Init(*cli.Context) error
	// plugin的名称。
	String() string
}


// Manager 是plugin管理器。它存储plugins并允许他们可以被检索。
// 它被用于micro的所有组件中。
type Manager interface {
	Plugins(...PluginOption) []Plugin
	Register(Plugin, ...PluginOption) error
}


// Handler 是中间件处理器plugin.可以用来包裹一个存在的http.Handler.
// 需要在链中调用下一个http.Handler
type Handler func(http.Handler) http.Handler

看完了这些接口类型原型后,问题来了,如何使用他们呢。下面的例子是一个比较简单的例子,添加一个flag然后输出它的值。

package main

import (
	"log"
	"github.com/urfave/cli/v2"
	"github.com/micro/micro/plugin"
)

func init() {
	plugin.Register(plugin.NewPlugin(
		plugin.WithName("example"),
		plugin.WithFlag(&cli.StringFlag{
			Name:   "example_flag",
			Usage:  "This is an example plugin flag",
			EnvVars: []string{"EXAMPLE_FLAG"},
			Value: "avalue",
		}),
		plugin.WithInit(func(ctx *cli.Context) error {
			log.Println("Got value for example_flag", ctx.String("example_flag"))
			return nil
		}),
	))
}

编译代码

go build -o micro ./main.go ./plugin.go

看看,是不是和我们刚开始看到的plugin匿名导入,既然整个过程的操作流程都是一样的,那我们不妨先来一探究竟

// Register registers a global plugins
func Register(plugin Plugin, opts ...PluginOption) error {
	return defaultManager.Register(plugin, opts...)
}

在默认的管理器defaultManager上注册plugin

var (
	// global plugin manager
	defaultManager = newManager()
)

func newManager() *manager {
	return &manager{
		plugins:    make(map[string][]Plugin),
		registered: make(map[string]map[string]bool),
	}
}

至于注册的动作其实很简单,锁保护下,进行map的操作

func (m *manager) Register(plugin Plugin, opts ...PluginOption) error {
	options := PluginOptions{Module: defaultModule}
	for _, o := range opts {
		o(&options)
	}

	m.Lock()
	defer m.Unlock()

	name := plugin.String()

	if reg, ok := m.registered[options.Module]; ok && reg[name] {
		return fmt.Errorf("Plugin with name %s already registered", name)
	}

	if _, ok := m.registered[options.Module]; !ok {
		m.registered[options.Module] = map[string]bool{name: true}
	} else {
		m.registered[options.Module][name] = true
	}

	if _, ok := m.plugins[options.Module]; ok {
		m.plugins[options.Module] = append(m.plugins[options.Module], plugin)
	} else {
		m.plugins[options.Module] = []Plugin{plugin}
	}

	return nil
}

还有一点提一句plugin.NewPlugin在创建一个plugin时,参数是Options结构体,那些With开头的方法WithFlag...

type Options struct {
	Name     string
	Flags    []cli.Flag
	Commands []*cli.Command
	Handlers []Handler
	Init     func(*cli.Context) error
}

func WithFlag(flag ...cli.Flag) Option {
	return func(o *Options) {
		o.Flags = append(o.Flags, flag...)
	}
}

都是对相关plugin属性进行初始化的。这个比较清晰就不必多说。

3.client

看完了usage,看匿名导入的第二部分内容

	_ "github.com/micro/micro/v3/client/cli"
	_ "github.com/micro/micro/v3/client/cli/init"
	_ "github.com/micro/micro/v3/client/cli/new"
	_ "github.com/micro/micro/v3/client/cli/signup"
	_ "github.com/micro/micro/v3/client/cli/user"

有关客户端的一些初始化工作。

第一个是cli

func init() {
	cmd.Register(
		&cli.Command{
			Name:   "cli",
			Usage:  "Run the interactive CLI",
			Action: Run,
		},
		&cli.Command{
			Name:   "call",
			Usage:  "Call a service e.g micro call greeter Say.Hello '{\"name\": \"John\"}",
			Action: util.Print(callService),
			Flags: []cli.Flag{
				&cli.StringFlag{
					Name:    "address",
					Usage:   "Set the address of the service instance to call",
					EnvVars: []string{"MICRO_ADDRESS"},
				},
				&cli.StringFlag{
					Name:    "output, o",
					Usage:   "Set the output format; json (default), raw",
					EnvVars: []string{"MICRO_OUTPUT"},
				},
				&cli.StringSliceFlag{
					Name:    "metadata",
					Usage:   "A list of key-value pairs to be forwarded as metadata",
					EnvVars: []string{"MICRO_METADATA"},
				},
				&cli.StringFlag{
					Name:  "request_timeout",
					Usage: "timeout duration",
				},
			},
		},
		&cli.Command{
			Name:   "stream",
			Usage:  "Create a service stream",
			Action: util.Print(streamService),
			Flags: []cli.Flag{
				&cli.StringFlag{
					Name:    "output, o",
					Usage:   "Set the output format; json (default), raw",
					EnvVars: []string{"MICRO_OUTPUT"},
				},
				&cli.StringSliceFlag{
					Name:    "metadata",
					Usage:   "A list of key-value pairs to be forwarded as metadata",
					EnvVars: []string{"MICRO_METADATA"},
				},
			},
		},
		&cli.Command{
			Name:   "stats",
			Usage:  "Query the stats of a service",
			Action: util.Print(queryStats),
		},
		&cli.Command{
			Name:   "env",
			Usage:  "Get/set micro cli environment",
			Action: util.Print(listEnvs),
			Subcommands: []*cli.Command{
				{
					Name:   "get",
					Usage:  "Get the currently selected environment",
					Action: util.Print(getEnv),
				},
				{
					Name:   "set",
					Usage:  "Set the environment to use for subsequent commands",
					Action: util.Print(setEnv),
				},
				{
					Name:   "add",
					Usage:  "Add a new environment `micro env add foo 127.0.0.1:8081`",
					Action: util.Print(addEnv),
				},
				{
					Name:   "del",
					Usage:  "Delete an environment from your list",
					Action: util.Print(delEnv),
				},
			},
		},
		&cli.Command{
			Name:   "services",
			Usage:  "List services in the registry",
			Action: util.Print(listServices),
		},
	)
}

命令行注册,主要包含以下命令行功能:

  • cli:用于交互式的命令行操作
  • call:服务调用例如 micro call greeter Say.Hello '{\"name\": \"John\"}
  • stream: 创建一个服务流
  • stats: 查询一个服务的状态
  • env:获取或者设置环境变量
  • services:罗列出所有已经注册的服务列表

第二个是clien包中的匿名导入init导入

func init() {
	cmd.Register(&cli.Command{
		Name:        "init",
		Usage:       "Generate a profile for micro plugins",
		Description: `'micro init' generates a profile.go file defining plugins and profiles`,
		Action:      Run,
		Flags: []cli.Flag{
			&cli.StringSliceFlag{
				Name:  "profile",
				Usage: "A comma separated list of profiles to load",
				Value: cli.NewStringSlice(),
			},
			&cli.StringFlag{
				Name:  "output",
				Usage: "Where to output the file, by default stdout",
				Value: "stdout",
			},
		},
	})
}

该命令行注册的是micro init。用于为micro的plugin产生profile.go。那么执行该命令到底做了什么呢?

func Run(ctx *cli.Context) error {
	var profiles []string

	for _, val := range ctx.StringSlice("profile") {
		for _, profile := range strings.Split(val, ",") {
			p := strings.TrimSpace(profile)
			if len(p) == 0 {
				continue
			}
			profiles = append(profiles, p)
		}
	}

	if len(profiles) == 0 {
		return nil
	}

	f := os.Stdout

	if v := ctx.String("output"); v != "stdout" {
		var err error
		f, err = os.Create(v)
		if err != nil {
			return err
		}
	}

	fmt.Fprint(f, "package main\n\n")
	fmt.Fprint(f, "import (\n")

	// write the profiles
	for _, profile := range profiles {
		path := filepath.Join(Import, profile, Version)
		line := fmt.Sprintf("\t_ \"%s\"\n", path)
		fmt.Fprint(f, line)
	}

	fmt.Fprint(f, ")\n")
	return nil
}

根据该命令对用的执行动作,我们发现,其实就是根据该命令的参数profile提供的参数,把相应的参数组成可导入路径,进行匿名导入,默认写到标准输出,可以通过output选项进行指定输出路径。

第三个是clien包中的匿名导入new导入,用于注册micro new命令行,该命令行用于创建一个消息模板,通过我们通过micro new helloworld && cd helloworld'来创建一个服务时,就是通过该命令来操作的。

func init() {
	cmd.Register(&cli.Command{
		Name:        "new",
		Usage:       "Create a service template",
		Description: `'micro new' scaffolds a new service skeleton. Example: 'micro new helloworld && cd helloworld'`,
		Action:      Run,
	})
}


在上一篇文章中,我们进行了操作,新建一个服务,那么该命令执行的动作是什么呢?

func Run(ctx *cli.Context) error {
	dir := ctx.Args().First()
	if len(dir) == 0 {
		fmt.Println("specify service name")
		return nil
	}

	// check if the path is absolute, we don't want this
	// we want to a relative path so we can install in GOPATH
	if path.IsAbs(dir) {
		fmt.Println("require relative path as service will be installed in GOPATH")
		return nil
	}

	var goPath string
	var goDir string

	goPath = build.Default.GOPATH

	// don't know GOPATH, runaway....
	if len(goPath) == 0 {
		fmt.Println("unknown GOPATH")
		return nil
	}

	// attempt to split path if not windows
	if runtime.GOOS == "windows" {
		goPath = strings.Split(goPath, ";")[0]
	} else {
		goPath = strings.Split(goPath, ":")[0]
	}
	goDir = filepath.Join(goPath, "src", path.Clean(dir))

	c := config{
		Alias:     dir,
		Comments:  protoComments(goDir, dir),
		Dir:       dir,
		GoDir:     goDir,
		GoPath:    goPath,
		UseGoPath: false,
		Files: []file{
			{"main.go", tmpl.MainSRV},
			{"generate.go", tmpl.GenerateFile},
			{"handler/" + dir + ".go", tmpl.HandlerSRV},
			{"proto/" + dir + ".proto", tmpl.ProtoSRV},
			{"Dockerfile", tmpl.DockerSRV},
			{"Makefile", tmpl.Makefile},
			{"README.md", tmpl.Readme},
			{".gitignore", tmpl.GitIgnore},
		},
	}

	// set gomodule
	if os.Getenv("GO111MODULE") != "off" {
		c.Files = append(c.Files, file{"go.mod", tmpl.Module})
	}

	// create the files
	return create(c)
}

首先他会获取第一个参数服务名,如果没有指定,将直接结束。提供了服务名之后,他会进行路径检测,看是否是绝对路径,这里是不提倡使用绝对路径的。如果是绝对路径也会直接返回。

在一切都就绪后,它将创建服务的整体目录。

第四个是clien包中的匿名导入signup导入,用户注册用于登录的命令行。我们在上一篇文章中使用的micro login就跟这个有关系。

func init() {
	cmd.Register(&cli.Command{
		Name:        "signup",
		Usage:       "Signup to the Micro Platform",
		Description: "Enables signup to the Micro Platform which can then be accessed via `micro env set platform` and `micro login`",
		Action:      Run,
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name:  "email",
				Usage: "Email address to use for signup",
			},
			// In fact this is only here currently to help testing
			// as the signup flow can't be automated yet.
			// The testing breaks because we take the password
			// with the `terminal` package that makes input invisible.
			// That breaks tests though so password flag is used to get around tests.
			// @todo maybe payment method token and email sent verification
			// code should also be invisible. Problem for an other day.
			&cli.StringFlag{
				Name:  "password",
				Usage: "Password to use for login. If not provided, will be asked for during login. Useful for automated scripts",
			},
			&cli.BoolFlag{
				Name:  "recover",
				Usage: "Emails you the namespaces you have access to. micro signup --recover --email=youremail@domain.com",
			},
		},
	})
}

而该命令的执行动作就是注册用户的整个流程。注册成功后,后面就可以使用micro login进行登录。

最后一个是clien包中的匿名导入user导入,用户打印出当前登录的用户

$docker exec -it 703b3b8f52d1 /micro user
admin

该命令包含一些子命令,比如用于修改用户的密码的子命令。

func init() {
	cmd.Register(
		&cli.Command{
			Name:   "user",
			Usage:  "Print the current logged in user",
			Action: user,
			Subcommands: []*cli.Command{
				// config as a sub command,
				{
					Name:        "config",
					Usage:       "{set, get, delete} [key] [value]",
					Description: "Manage user related config like id, token, namespace, etc",
					Action:      current,
					Subcommands: config.Commands,
				},
				{
					Name:   "token",
					Usage:  "Get the current user token",
					Action: getToken,
				},
				{
					Name:   "namespace",
					Usage:  "Get the current namespace",
					Action: getNamespace,
					Subcommands: []*cli.Command{
						{
							Name:   "set",
							Usage:  "Set namespace in the current environment",
							Action: setNamespace,
						},
					},
				},
				{
					Name:  "set",
					Usage: "Set various user based properties, eg. password",
					Subcommands: []*cli.Command{
						{
							Name:   "password",
							Usage:  "Set password",
							Action: changePassword,
							Flags: []cli.Flag{
								&cli.StringFlag{
									Name:  "email",
									Usage: "Email to use for password change",
								},
								&cli.StringFlag{
									Name:  "old-password",
									Usage: "Existing password, the one that is used currently.",
								},
								&cli.StringFlag{
									Name:  "new-password",
									Usage: "New password you want to set.",
								},
							},
						},
					},
				},
			},
		},
	)
}

到这里,整个client包的准备工作都已经结束了,而下一部分就到了server

4.server

在这里,注册micro server命令。并运行起整个平台。

func init() {
	command := &cli.Command{
		Name:  "server",
		Usage: "Run the micro server",
		Description: `Launching the micro server ('micro server') will enable one to connect to it by
		setting the appropriate Micro environment (see 'micro env' && 'micro env --help') commands.`,
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name:    "address",
				Usage:   "Set the micro server address :10001",
				EnvVars: []string{"MICRO_SERVER_ADDRESS"},
			},
		},
		Action: func(ctx *cli.Context) error {
			Run(ctx)
			return nil
		},
		Subcommands: []*cli.Command{{
			Name:  "file",
			Usage: "Move files between your local machine and the server",
			Subcommands: []*cli.Command{
				{
					Name:   "upload",
					Action: util.Print(upload),
				},
			},
		}},
	}

	for _, p := range Plugins() {
		if cmds := p.Commands(); len(cmds) > 0 {
			command.Subcommands = append(command.Subcommands, cmds...)
		}

		if flags := p.Flags(); len(flags) > 0 {
			command.Flags = append(command.Flags, flags...)
		}
	}

	cmd.Register(command)
}

而在我们启动micro server时,执行的行为为:

// Run runs the entire platform
func Run(context *cli.Context) error {
	if context.Args().Len() > 0 {
		cli.ShowSubcommandHelp(context)
		os.Exit(1)
	}

	// TODO: reimplement peering of servers e.g --peer=node1,node2,node3
	// peers are configured as network nodes to cluster between

	log.Info("Starting server")

	for _, service := range services {
		name := service

		// set the proxy addres, default to the network running locally
		proxy := context.String("proxy_address")
		if len(proxy) == 0 {
			proxy = "127.0.0.1:8443"
		}

		log.Infof("Registering %s", name)
		// @todo this is a hack
		env := []string{}
		// all things run by the server are `micro service [name]`
		cmdArgs := []string{"service"}

		switch service {
		case "proxy", "api":
			// pull the values we care about from environment
			for _, val := range os.Environ() {
				// only process MICRO_ values
				if !strings.HasPrefix(val, "MICRO_") {
					continue
				}
				// override any profile value because clients
				// talk to services, these may be started
				// differently in future as a `micro client`
				if strings.HasPrefix(val, "MICRO_PROFILE=") {
					val = "MICRO_PROFILE=client"
				}
				env = append(env, val)
			}
		default:
			// pull the values we care about from environment
			for _, val := range os.Environ() {
				// only process MICRO_ values
				if !strings.HasPrefix(val, "MICRO_") {
					continue
				}
				env = append(env, val)
			}
		}

		// inject the proxy address for all services but the network, as we don't want
		// that calling itself
		if len(proxy) > 0 && service != "network" {
			env = append(env, "MICRO_PROXY="+proxy)
		}

		// we want to pass through the global args so go up one level in the context lineage
		if len(context.Lineage()) > 1 {
			globCtx := context.Lineage()[1]
			for _, f := range globCtx.FlagNames() {
				cmdArgs = append(cmdArgs, "--"+f, context.String(f))
			}
		}
		cmdArgs = append(cmdArgs, service)

		// runtime based on environment we run the service in
		args := []runtime.CreateOption{
			runtime.WithCommand(os.Args[0]),
			runtime.WithArgs(cmdArgs...),
			runtime.WithEnv(env),
			runtime.WithRetries(10),
			runtime.CreateImage("micro/micro"),
		}

		// NOTE: we use Version right now to check for the latest release
		muService := &runtime.Service{Name: name, Version: fmt.Sprintf("%d", time.Now().Unix())}
		if err := runtime.Create(muService, args...); err != nil {
			log.Errorf("Failed to create runtime environment: %v", err)
			return err
		}
	}

	log.Info("Starting server runtime")

	// start the runtime
	if err := runtime.DefaultRuntime.Start(); err != nil {
		log.Fatal(err)
		return err
	}
	defer runtime.DefaultRuntime.Stop()

	// internal server
	srv := service.New(
		service.Name(Name),
		service.Address(Address),
	)

	// @todo make this configurable
	uploadDir := filepath.Join(os.TempDir(), "micro", "uploads")
	os.MkdirAll(uploadDir, 0777)
	file.RegisterHandler(srv.Server(), uploadDir)

	// start the server
	if err := srv.Run(); err != nil {
		log.Fatalf("Error running server: %v", err)
	}

	log.Info("Stopped server")

	return nil
}

如果在运行server时,提供了参数,将打印出子命令帮助信息并直接退出。

之后,遍历下面的切片

var (
  // list of services managed
  services = []string{
    "network",  // :8443
    "runtime",  // :8088
    "registry", // :8000
    "config",   // :8001
    "store",    // :8002
    "broker",   // :8003
    "events",   // :unset
    "auth",     // :8010
    "proxy",    // :8081
    "api",      // :8080
  }
)

进行服务的注册.所有的服务运行,都是通过micro service [name]

log.Infof("Registering %s", name)

在这当中,1.如果未设置proxy_address,使用默认的本地网络proxy = "127.0.0.1:8443" 2.获取环境变量中我们关心的值。

在一系列的准备工作就绪后,调用service的Run方法启动起server

  srv := service.New(
    service.Name(Name),
    service.Address(Address),
  )

  // @todo make this configurable
  uploadDir := filepath.Join(os.TempDir(), "micro", "uploads")
  os.MkdirAll(uploadDir, 0777)
  file.RegisterHandler(srv.Server(), uploadDir)

  // start the server
  if err := srv.Run(); err != nil {
    log.Fatalf("Error running server: %v", err)
  }

  log.Info("Stopped server")

结束了上的工作之后,开始进入最后一部分内容的准备工作

5.service

首先是关于鉴权相关命令行注册, micro auth命令。该命令涵盖一些子命令,包含

  • list:罗列出鉴权资讯
  • create: 创建鉴权资源
  • delete:删除鉴权资源
  • login:登录用户
  • logout:登出用户
func init() {
	cmd.Register(
		&cli.Command{
			Name:   "auth",
			Usage:  "Manage authentication, accounts and rules",
			Action: helper.UnexpectedSubcommand,
			Subcommands: []*cli.Command{
				{
					Name:  "list",
					Usage: "List auth resources",
					Subcommands: []*cli.Command{
						{
							Name:   "rules",
							Usage:  "List auth rules",
							Action: listRules,
						},
						{
							Name:   "accounts",
							Usage:  "List auth accounts",
							Action: listAccounts,
						},
					},
				},
				{
					Name:  "create",
					Usage: "Create an auth resource",
					Subcommands: []*cli.Command{
						{
							Name:   "rule",
							Usage:  "Create an auth rule",
							Flags:  ruleFlags,
							Action: createRule,
						},
						{
							Name:   "account",
							Usage:  "Create an auth account",
							Flags:  accountFlags,
							Action: createAccount,
						},
					},
				},
				{
					Name:  "delete",
					Usage: "Delete a auth resource",
					Subcommands: []*cli.Command{
						{
							Name:   "rule",
							Usage:  "Delete an auth rule",
							Flags:  ruleFlags,
							Action: deleteRule,
						},
						{
							Name:   "account",
							Usage:  "Delete an auth account",
							Flags:  ruleFlags,
							Action: deleteAccount,
						},
					},
				},
			},
		},
		&cli.Command{
			Name:        "login",
			Usage:       `Interactive login flow.`,
			Description: "Run 'micro login' for micro servers or 'micro login --otp' for the Micro Platform.",
			Action:      login,
			Flags: []cli.Flag{
				&cli.BoolFlag{
					Name:  "otp",
					Usage: "Login/signup with a One Time Password.",
				},
				&cli.StringFlag{
					Name:  "password",
					Usage: "Password to use for login. If not provided, will be asked for during login. Useful for automated scripts",
				},
				&cli.StringFlag{
					Name:    "username",
					Usage:   "Username to use for login",
					Aliases: []string{"email"},
				},
			},
		},
		&cli.Command{
			Name:        "logout",
			Usage:       `Logout.`,
			Description: "Use 'micro logout' to delete your token in your current environment.",
			Action:      logout,
		},
	)
}

在进行了用户的鉴权后,第二部分是关于service相关命令行的准备工作

func init() {
	// move newAction outside the loop and pass c as an arg to
	// set the scope of the variable
	newAction := func(c srvCommand) func(ctx *ccli.Context) error {
		return func(ctx *ccli.Context) error {
			// configure the logger
			log.DefaultLogger.Init(golog.WithFields(map[string]interface{}{"service": c.Name}))

			// run the service
			c.Command(ctx)
			return nil
		}
	}

	subcommands := make([]*ccli.Command, len(srvCommands))
	for i, c := range srvCommands {
		// construct the command
		command := &ccli.Command{
			Name:   c.Name,
			Flags:  c.Flags,
			Usage:  fmt.Sprintf("Run micro %v", c.Name),
			Action: newAction(c),
		}

		// setup the plugins
		for _, p := range plugin.Plugins(plugin.Module(c.Name)) {
			if cmds := p.Commands(); len(cmds) > 0 {
				command.Subcommands = append(command.Subcommands, cmds...)
			}

			if flags := p.Flags(); len(flags) > 0 {
				command.Flags = append(command.Flags, flags...)
			}
		}

		// set the command
		subcommands[i] = command
	}

	command := &ccli.Command{
		Name:  "service",
		Usage: "Run a micro service",
		Action: func(ctx *ccli.Context) error {
			Run(ctx)
			return nil
		},
		Flags: []ccli.Flag{
			&ccli.StringFlag{
				Name:    "name",
				Usage:   "Name of the service",
				EnvVars: []string{"MICRO_SERVICE_NAME"},
				Value:   "service",
			},
			&ccli.StringFlag{
				Name:    "address",
				Usage:   "Address of the service",
				EnvVars: []string{"MICRO_SERVICE_ADDRESS"},
			},
			&ccli.StringFlag{
				Name:    "endpoint",
				Usage:   "The local service endpoint (Defaults to localhost:9090); {http, grpc, file, exec}://path-or-address e.g http://localhost:9090",
				EnvVars: []string{"MICRO_SERVICE_ENDPOINT"},
			},
			&ccli.StringSliceFlag{
				Name:    "metadata",
				Usage:   "Add metadata as key-value pairs describing the service e.g owner=john@example.com",
				EnvVars: []string{"MICRO_SERVICE_METADATA"},
			},
		},
		Subcommands: subcommands,
	}

	// register global plugins and flags
	for _, p := range plugin.Plugins() {
		if cmds := p.Commands(); len(cmds) > 0 {
			command.Subcommands = append(command.Subcommands, cmds...)
		}

		if flags := p.Flags(); len(flags) > 0 {
			command.Flags = append(command.Flags, flags...)
		}
	}

	cmd.Register(command)
}

第三部分关于config的命令行注册,该命令行用于管理配置项的值的设置,包括获取,删除,设置。

func init() {
	cmd.Register(
		&cli.Command{
			Name:   "config",
			Usage:  "Manage configuration values",
			Action: helper.UnexpectedSubcommand,
			Subcommands: []*cli.Command{
				{
					Name:   "get",
					Usage:  "Get a value; micro config get key",
					Action: getConfig,
					Flags: []cli.Flag{
						&cli.BoolFlag{
							Name:    "secret",
							Aliases: []string{"s"},
							Usage:   "Set it as a secret value",
						},
					},
				},
				{
					Name:   "set",
					Usage:  "Set a key-val; micro config set key val",
					Action: setConfig,
					Flags: []cli.Flag{
						&cli.BoolFlag{
							Name:    "secret",
							Aliases: []string{"s"},
							Usage:   "Set it as a secret value",
						},
					},
				},
				{
					Name:   "del",
					Usage:  "Delete a value; micro config del key",
					Action: delConfig,
				},
			},
		},
	)
}

第四部分是关于network相关的设置的命令行注册。该命令用于管理micro service的网络,包含一些子命令

  • connect:连接指定网络
  • connections:列出直接连接的网络
  • graph:获取网络图
  • nodes:获取网络节点
  • routes:获取网络路由
  • services:获取网络服务
  • call:调用服务
func init() {
	cmd.Register(&cli.Command{
		Name:  "network",
		Usage: "Manage the micro service network",
		Subcommands: []*cli.Command{
			{
				Name:   "connect",
				Usage:  "connect to the network. specify nodes e.g connect ip:port",
				Action: util.Print(networkConnect),
			},
			{
				Name:   "connections",
				Usage:  "List the immediate connections to the network",
				Action: util.Print(networkConnections),
			},
			{
				Name:   "graph",
				Usage:  "Get the network graph",
				Action: util.Print(networkGraph),
			},
			{
				Name:   "nodes",
				Usage:  "List nodes in the network",
				Action: util.Print(networkNodes),
			},
			{
				Name:   "routes",
				Usage:  "List network routes",
				Action: util.Print(networkRoutes),
				Flags: []cli.Flag{
					&cli.StringFlag{
						Name:  "service",
						Usage: "Filter by service",
					},
					&cli.StringFlag{
						Name:  "address",
						Usage: "Filter by address",
					},
					&cli.StringFlag{
						Name:  "gateway",
						Usage: "Filter by gateway",
					},
					&cli.StringFlag{
						Name:  "router",
						Usage: "Filter by router",
					},
					&cli.StringFlag{
						Name:  "network",
						Usage: "Filter by network",
					},
				},
			},
			{
				Name:   "services",
				Usage:  "Get the network services",
				Action: util.Print(networkServices),
			},
			// TODO: duplicates call. Move so we reuse same stuff.
			{
				Name:   "call",
				Usage:  "Call a service e.g micro call greeter Say.Hello '{\"name\": \"John\"}",
				Action: util.Print(netCall),
				Flags: []cli.Flag{
					&cli.StringFlag{
						Name:    "address",
						Usage:   "Set the address of the service instance to call",
						EnvVars: []string{"MICRO_ADDRESS"},
					},
					&cli.StringFlag{
						Name:    "output, o",
						Usage:   "Set the output format; json (default), raw",
						EnvVars: []string{"MICRO_OUTPUT"},
					},
					&cli.StringSliceFlag{
						Name:    "metadata",
						Usage:   "A list of key-value pairs to be forwarded as metadata",
						EnvVars: []string{"MICRO_METADATA"},
					},
				},
			},
		},
	})
}

第五部分,主要涉及micro run/update/kill/status/logs等相关命令的注册,这些命令都是管理特定的服务的相关操作。

func init() {
	cmd.Register(
		&cli.Command{
			// In future we'll also have `micro run [x]` hence `micro run service` requiring "service"
			Name:  "run",
			Usage: RunUsage,
			Description: `Examples:
			micro run github.com/micro/services/helloworld
			micro run .  # deploy local folder to your local micro server
			micro run ../path/to/folder # deploy local folder to your local micro server
			micro run helloworld # deploy latest version, translates to micro run github.com/micro/services/helloworld
			micro run helloworld@9342934e6180 # deploy certain version
			micro run helloworld@branchname	# deploy certain branch`,
			Flags:  flags,
			Action: runService,
		},
		&cli.Command{
			Name:  "update",
			Usage: UpdateUsage,
			Description: `Examples:
			micro update github.com/micro/services/helloworld
			micro update .  # deploy local folder to your local micro server
			micro update ../path/to/folder # deploy local folder to your local micro server
			micro update helloworld # deploy master branch, translates to micro update github.com/micro/services/helloworld
			micro update helloworld@branchname	# deploy certain branch`,
			Flags:  flags,
			Action: updateService,
		},
		&cli.Command{
			Name:  "kill",
			Usage: KillUsage,
			Flags: flags,
			Description: `Examples:
			micro kill github.com/micro/services/helloworld
			micro kill .  # kill service deployed from local folder
			micro kill ../path/to/folder # kill service deployed from local folder
			micro kill helloworld # kill serviced deployed from master branch, translates to micro kill github.com/micro/services/helloworld
			micro kill helloworld@branchname	# kill service deployed from certain branch`,
			Action: killService,
		},
		&cli.Command{
			Name:   "status",
			Usage:  GetUsage,
			Flags:  flags,
			Action: getService,
		},
		&cli.Command{
			Name:   "logs",
			Usage:  "Get logs for a service",
			Action: getLogs,
			Flags: []cli.Flag{
				&cli.StringFlag{
					Name:  "version",
					Usage: "Set the version of the service to debug",
				},
				&cli.StringFlag{
					Name:    "output",
					Aliases: []string{"o"},
					Usage:   "Set the output format e.g json, text",
				},
				&cli.BoolFlag{
					Name:    "follow",
					Aliases: []string{"f"},
					Usage:   "Set to stream logs continuously (default: true)",
				},
				&cli.StringFlag{
					Name:  "since",
					Usage: "Set to the relative time from which to show the logs for e.g. 1h",
				},
				&cli.IntFlag{
					Name:    "lines",
					Aliases: []string{"n"},
					Usage:   "Set to query the last number of log events",
				},
			},
		},
	)
}

最后一部分,主要涉及关于存储相关的micro store命令注册,包括一些子命令

  • read:从存储中读取记录
  • list:罗列出一个存储中所有的key
  • write:写入一条记录到存储中
  • delete:删除存储中的一条记录
  • databases:罗列出已知存储中的所有数据库
  • tables:罗列出指定数据库中所有的表
  • snapshot:备份一个存储
  • sync:同步一份存储到新的里面
  • restore:从一份快照中恢复存储
func init() {
	cmd.Register(&cli.Command{
		Name:   "store",
		Usage:  "Commands for accessing the store",
		Action: helper.UnexpectedSubcommand,
		Subcommands: []*cli.Command{
			{
				Name:      "read",
				Usage:     "read a record from the store",
				UsageText: `micro store read [options] key`,
				Action:    read,
				Flags: []cli.Flag{
					&cli.StringFlag{
						Name:    "database",
						Aliases: []string{"d"},
						Usage:   "database to write to",
						Value:   "micro",
					},
					&cli.StringFlag{
						Name:    "table",
						Aliases: []string{"t"},
						Usage:   "table to write to",
						Value:   "micro",
					},
					&cli.BoolFlag{
						Name:    "prefix",
						Aliases: []string{"p"},
						Usage:   "read prefix",
						Value:   false,
					},
					&cli.UintFlag{
						Name:    "limit",
						Aliases: []string{"l"},
						Usage:   "list limit",
					},
					&cli.UintFlag{
						Name:    "offset",
						Aliases: []string{"o"},
						Usage:   "list offset",
					},
					&cli.BoolFlag{
						Name:    "verbose",
						Aliases: []string{"v"},
						Usage:   "show keys and headers (only values shown by default)",
						Value:   false,
					},
					&cli.StringFlag{
						Name:  "output",
						Usage: "output format (json, table)",
						Value: "table",
					},
				},
			},
			{
				Name:      "list",
				Usage:     "list all keys from a store",
				UsageText: `micro store list [options]`,
				Action:    list,
				Flags: []cli.Flag{
					&cli.StringFlag{
						Name:    "database",
						Aliases: []string{"d"},
						Usage:   "database to list from",
						Value:   "micro",
					},
					&cli.StringFlag{
						Name:    "table",
						Aliases: []string{"t"},
						Usage:   "table to write to",
						Value:   "micro",
					},
					&cli.StringFlag{
						Name:  "output",
						Usage: "output format (json)",
					},
					&cli.BoolFlag{
						Name:    "prefix",
						Aliases: []string{"p"},
						Usage:   "list prefix",
						Value:   false,
					},
					&cli.UintFlag{
						Name:    "limit",
						Aliases: []string{"l"},
						Usage:   "list limit",
					},
					&cli.UintFlag{
						Name:    "offset",
						Aliases: []string{"o"},
						Usage:   "list offset",
					},
				},
			},
			{
				Name:      "write",
				Usage:     "write a record to the store",
				UsageText: `micro store write [options] key value`,
				Action:    write,
				Flags: []cli.Flag{
					&cli.StringFlag{
						Name:    "expiry",
						Aliases: []string{"e"},
						Usage:   "expiry in time.ParseDuration format",
						Value:   "",
					},
					&cli.StringFlag{
						Name:    "database",
						Aliases: []string{"d"},
						Usage:   "database to write to",
						Value:   "micro",
					},
					&cli.StringFlag{
						Name:    "table",
						Aliases: []string{"t"},
						Usage:   "table to write to",
						Value:   "micro",
					},
				},
			},
			{
				Name:      "delete",
				Usage:     "delete a key from the store",
				UsageText: `micro store delete [options] key`,
				Action:    delete,
				Flags: []cli.Flag{
					&cli.StringFlag{
						Name:  "database",
						Usage: "database to delete from",
						Value: "micro",
					},
					&cli.StringFlag{
						Name:  "table",
						Usage: "table to delete from",
						Value: "micro",
					},
				},
			},
			{
				Name:   "databases",
				Usage:  "List all databases known to the store service",
				Action: databases,
				Flags: []cli.Flag{
					&cli.StringFlag{
						Name:  "store",
						Usage: "store service to call",
						Value: "store",
					},
				},
			},
			{
				Name:   "tables",
				Usage:  "List all tables in the specified database known to the store service",
				Action: tables,
				Flags: []cli.Flag{
					&cli.StringFlag{
						Name:  "store",
						Usage: "store service to call",
						Value: "store",
					},
					&cli.StringFlag{
						Name:    "database",
						Aliases: []string{"d"},
						Usage:   "database to list tables of",
						Value:   "micro",
					},
				},
			},
			{
				Name:   "snapshot",
				Usage:  "Back up a store",
				Action: snapshot,
				Flags: append(CommonFlags,
					&cli.StringFlag{
						Name:    "destination",
						Usage:   "Backup destination",
						Value:   "file:///tmp/store-snapshot",
						EnvVars: []string{"MICRO_SNAPSHOT_DESTINATION"},
					},
				),
			},
			{
				Name:   "sync",
				Usage:  "Copy all records of one store into another store",
				Action: sync,
				Flags:  SyncFlags,
			},
			{
				Name:   "restore",
				Usage:  "restore a store snapshot",
				Action: restore,
				Flags: append(CommonFlags,
					&cli.StringFlag{
						Name:  "source",
						Usage: "Backup source",
						Value: "file:///tmp/store-snapshot",
					},
				),
			},
		},
	})
}

// CommonFlags are flags common to cli commands snapshot and restore
var CommonFlags = []cli.Flag{
	&cli.StringFlag{
		Name:    "nodes",
		Usage:   "Comma separated list of Nodes to pass to the store backend",
		EnvVars: []string{"MICRO_STORE_NODES"},
	},
	&cli.StringFlag{
		Name:    "database",
		Usage:   "Database option to pass to the store backend",
		EnvVars: []string{"MICRO_STORE_DATABASE"},
	},
	&cli.StringFlag{
		Name:    "table",
		Usage:   "Table option to pass to the store backend",
		EnvVars: []string{"MICRO_STORE_TABLE"},
	},
}

至此,整个初始化准备工作都已经完成。。。。

开启整个命令行程序。

func (c *command) Run() error {
	defer func() {
		if r := recover(); r != nil {
			report.Errorf(nil, fmt.Sprintf("panic: %v", string(debug.Stack())))
			panic(r)
		}
	}()
	return c.app.Run(os.Args)
}