注:本文的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)
}