以前docker启动客户端和服务端使用的都是同一个命令(二进制可执行文件),现在docker将客户端程序和服务端程序分开了分别为docker和dockerd。而且docker的命令解析使用了一个第三方的库,名字叫cobra,github地址在:https://github.com/getting-started。客户端和服务端使用的是一样的命令解析方式,所以我们选取客户端分析docker的命令解析过程。
首先我们介绍下cobra这个库的简单用法,从cobra的github页面的例子中删减了下:
package main
import (
"fmt"
"github.com/spf13/cobra"
)
func main() {
//1.定义主命令
var Version bool
var rootCmd = &cobra.Command{
Use: "root [sub]",
Short: "My root command",
//命令执行的函数
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Inside rootCmd Run with args: %v\n", args)
if Version {
fmt.Printf("Version:1.0\n")
}
},
}
//2.定义子命令
var subCmd = &cobra.Command{
Use: "sub [no options!]",
Short: "My subcommand",
//命令执行的函数
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Inside subCmd Run with args: %v\n", args)
},
}
//添加子命令
rootCmd.AddCommand(subCmd)
//3.为命令添加选项
flags := rootCmd.Flags()
flags.BoolVarP(&Version, "version", "v", false, "Print version information and quit")
//执行命令
_ = rootCmd.Execute()
}
基本用法大概就是四步:
1.定义一个主命令(包含命令执行函数等)
2.定义若干子命令(包含命令执行函数等,根据需要可以为子命令定义子命令),并添加到主命令
3.为命令添加选项
4.执行命令
好了,了解了cobra的大概用法,我们从docker(客户端)的main函数开始分析
贴一下main函数:
func main() {
// Set terminal emulation based on platform as required.
stdin, stdout, stderr := term.StdStreams()
logrus.SetOutput(stderr)
dockerCli := command.NewDockerCli(stdin, stdout, stderr)
//定义主命令
cmd := newDockerCommand(dockerCli)
//执行命令
if err := cmd.Execute(); err != nil {
if sterr, ok := err.(cli.StatusError); ok {
if sterr.Status != "" {
fmt.Fprintln(stderr, sterr.Status)
}
// StatusError should only be used for errors, and all errors should
// have a non-zero exit status, so never exit with 0
if sterr.StatusCode == 0 {
os.Exit(1)
}
os.Exit(sterr.StatusCode)
}
fmt.Fprintln(stderr, err)
os.Exit(1)
}
}
main函数中并没有我们前面提到的完整四步,而是使用了一个函数newDockerCommand来做了其他工作。我们接着分析该函数,代码不多,我们还是贴一下该函数代码:
func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command {
//选项结构对象,之后通过传入的选项选项参数进行填充
opts := cliflags.NewClientOptions()
//选项集合
var flags *pflag.FlagSet
//定义主命令
cmd := &cobra.Command{
Use: "docker [OPTIONS] COMMAND [arg...]",
Short: "A self-sufficient runtime for containers.",
SilenceUsage: true,
SilenceErrors: true,
TraverseChildren: true,
Args: noArgs,
RunE: func(cmd *cobra.Command, args []string) error {
if opts.Version {
showVersion()
return nil
}
fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
return nil
},
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
// flags must be the top-level command flags, not cmd.Flags()
opts.Common.SetDefaultOptions(flags)
dockerPreRun(opts)
return dockerCli.Initialize(opts)
},
}
//设置默认的处理方式
cli.SetupRootCommand(cmd)
//为主命令添加选项
flags = cmd.Flags()
flags.BoolVarP(&opts.Version, "version", "v", false, "Print version information and quit")
flags.StringVar(&opts.ConfigDir, "config", cliconfig.ConfigDir(), "Location of client config files")
//为主命令添加公共选项
opts.Common.InstallFlags(flags)
//设置命令的输入
cmd.SetOutput(dockerCli.Out())
//添加daemon选项,其实已经作废了,应为新代码已经将客户端和服务端守护进程命令分开了,不需要这个选项区分启动客户端还是服务端
cmd.AddCommand(newDaemonCommand())
// AddCommands adds all the commands from cli/command to the root command
//添加子命令及子命令的选项
commands.AddCommands(cmd, dockerCli)
return cmd
}
可以看到,表面上在该函数里面也只是定义了主命令,并为主命令添加了选项。那子命令呢?看到
//添加子命令及子命令的选项
commands.AddCommands(cmd, dockerCli)
其实该函数的工作就是添加子命令,子命令的子命令,以及命令的选项。
// AddCommands adds all the commands from cli/command to the root command
func AddCommands(cmd *cobra.Command, dockerCli *command.DockerCli) {
cmd.AddCommand(
node.NewNodeCommand(dockerCli),
service.NewServiceCommand(dockerCli),
stack.NewStackCommand(dockerCli),
stack.NewTopLevelDeployCommand(dockerCli),
swarm.NewSwarmCommand(dockerCli),
container.NewContainerCommand(dockerCli),
image.NewImageCommand(dockerCli),
system.NewSystemCommand(dockerCli),
container.NewRunCommand(dockerCli),
image.NewBuildCommand(dockerCli),
network.NewNetworkCommand(dockerCli),
hide(system.NewEventsCommand(dockerCli)),
registry.NewLoginCommand(dockerCli),
registry.NewLogoutCommand(dockerCli),
registry.NewSearchCommand(dockerCli),
system.NewVersionCommand(dockerCli),
volume.NewVolumeCommand(dockerCli),
hide(system.NewInfoCommand(dockerCli)),
hide(container.NewAttachCommand(dockerCli)),
hide(container.NewCommitCommand(dockerCli)),
hide(container.NewCopyCommand(dockerCli)),
hide(container.NewCreateCommand(dockerCli)),
hide(container.NewDiffCommand(dockerCli)),
hide(container.NewExecCommand(dockerCli)),
hide(container.NewExportCommand(dockerCli)),
hide(container.NewKillCommand(dockerCli)),
hide(container.NewLogsCommand(dockerCli)),
hide(container.NewPauseCommand(dockerCli)),
hide(container.NewPortCommand(dockerCli)),
hide(container.NewPsCommand(dockerCli)),
hide(container.NewRenameCommand(dockerCli)),
hide(container.NewRestartCommand(dockerCli)),
hide(container.NewRmCommand(dockerCli)),
hide(container.NewStartCommand(dockerCli)),
hide(container.NewStatsCommand(dockerCli)),
hide(container.NewStopCommand(dockerCli)),
hide(container.NewTopCommand(dockerCli)),
hide(container.NewUnpauseCommand(dockerCli)),
hide(container.NewUpdateCommand(dockerCli)),
hide(container.NewWaitCommand(dockerCli)),
hide(image.NewHistoryCommand(dockerCli)),
hide(image.NewImagesCommand(dockerCli)),
hide(image.NewImportCommand(dockerCli)),
hide(image.NewLoadCommand(dockerCli)),
hide(image.NewPullCommand(dockerCli)),
hide(image.NewPushCommand(dockerCli)),
hide(image.NewRemoveCommand(dockerCli)),
hide(image.NewSaveCommand(dockerCli)),
hide(image.NewTagCommand(dockerCli)),
hide(system.NewInspectCommand(dockerCli)),
checkpoint.NewCheckpointCommand(dockerCli),
plugin.NewPluginCommand(dockerCli),
)
}
可以看到添加了很多子命令,这里面的都是一级子命令,也就是“docker xxx xxx xxx”中跟着docker的命令.注意到有些是被hide函数包裹的,有些则不是,我们在这里先不关心这个。我们选取一个命令docker images来分析
hide(image.NewImagesCommand(dockerCli)),
进入该函数:
// NewImagesCommand creates a new `docker images` command
func NewImagesCommand(dockerCli *command.DockerCli) *cobra.Command {
opts := imagesOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{
Use: "images [OPTIONS] [REPOSITORY[:TAG]]",
Short: "List images",
Args: cli.RequiresMaxArgs(1),
//命令执行函数,有一个错误类型的变量作为返回值
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
opts.matchName = args[0]
}
return runImages(dockerCli, opts)
},
}
//为命令添加选项
flags := cmd.Flags()
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only show numeric IDs")
flags.BoolVarP(&opts.all, "all", "a", false, "Show all images (default hides intermediate images)")
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Don't truncate output")
flags.BoolVar(&opts.showDigests, "digests", false, "Show digests")
flags.StringVar(&opts.format, "format", "", "Pretty-print images using a Go template")
flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
return cmd
}
当我们执行docker images时,就会执行命令中定义的函数,进而执行runImages(),改函数根据选项执行不同的操作。