docker命令行参数解析流程 通过 docker -v 查看版本,可以得到 对应 仓库的 revision。然后克隆这个仓库,检出对应的源代码。
1 2 $ docker -v Docker version 24.0.2, build cb74dfc
克隆这个仓库,检出对应的源代码
1 2 3 4 5 git clone https://github.com/golang108/docker-cli.git git checkout cb74dfc # 这个就对应 Docker version 24.0.2, build cb74dfc
1. 入口 main 函数 来自文件 cmd/docker/docker.go
1 2 3 4 5 6 7 8 9 10 11 12 func main() { // 1. 主要就是 实例化 一个 dockerCli 这个东西, // dockerCli 其实是 type DockerCli struct 这样的一个结构体 dockerCli, err := command.NewDockerCli() // 2. 然后就是 调用 runDocker 函数,传入刚刚实例化出来的 dockerCli 对象。 if err := runDocker(dockerCli); err != nil { }
2. command.NewDockerCli() 函数 2.1 DockerCli 结构体 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 type DockerCli struct { configFile *configfile.ConfigFile options *cliflags.ClientOptions in *streams.In out *streams.Out err io.Writer client client.APIClient serverInfo ServerInfo contentTrust bool contextStore store.Store currentContext string init sync.Once initErr error dockerEndpoint docker.Endpoint contextStoreConfig store.Config initTimeout time.Duration }
这个DockerCli结构体 实现了 type Cli interface
接口的方法,type Cli interface
接口 里面包含了 type Streams interface
接口。
这个DockerCli结构体 同时也实现了 type Streams interface
接口的方法
2.2 两个 接口 Streams和Cli 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 // Streams is an interface which exposes the standard input and output streams type Streams interface { In() *streams.In Out() *streams.Out Err() io.Writer } // Cli represents the docker command line client. type Cli interface { Client() client.APIClient Streams SetIn(in *streams.In) Apply(ops ...DockerCliOption) error ConfigFile() *configfile.ConfigFile ServerInfo() ServerInfo NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error) DefaultVersion() string CurrentVersion() string ManifestStore() manifeststore.Store RegistryClient(bool) registryclient.RegistryClient ContentTrustEnabled() bool BuildKitEnabled() (bool, error) ContextStore() store.Store CurrentContext() string DockerEndpoint() docker.Endpoint }
2.3 函数 NewDockerCli(ops …DockerCliOption) (*DockerCli, error) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func NewDockerCli(ops ...DockerCliOption) (*DockerCli, error) { defaultOps := []DockerCliOption{ WithContentTrustFromEnv(), WithDefaultContextStoreConfig(), WithStandardStreams(), } ops = append(defaultOps, ops...) cli := &DockerCli{} if err := cli.Apply(ops...); err != nil { return nil, err } return cli, nil }
实例化 DockerCli 的时候 可以 传入 DockerCliOption 类型的参数,但是我们这里没有传参!!!
2.4 DockerCliOption 参数 这个参数 会 被 Apply 到 DockerCli 实例对象里的:cli.Apply(ops...)
,
一个一个的ops是一个一个的函数: type DockerCliOption func(cli *DockerCli) error
, 然后就会想这样去调用 op(cli)
其实真正要做的 就是 修改,或者初始化 cli.xxxx
这样的值,说白了就是要初始化 cli 实例里面属性的值的。
例如下面这个函数 就可以 作为 DockerCliOption 类型,传入 。
1 2 3 4 func(cli *DockerCli) error { cli.client = c return nil }
golang 中就有很多 这样的设计, 一个 Apply()
函数, 然后 一堆 WithXXXX()
这样的函数, 参考 cli/command/cli.go 和 cli/command/cli_options.go 文件。
3. runDocker(dockerCli) 函数 3.1 newDockerCommand(dockerCli) 函数 1 tcmd := newDockerCommand(dockerCli)
3.1.1 初始化 一个 &cobra.Command{} 结构体指针 1 2 3 4 5 6 cmd := &cobra.Command{ Use: "docker [OPTIONS] COMMAND [ARG...]", Short: "A self-sufficient runtime for containers", 以下代码省略 。。。。。。 }
3.1.2 cli.SetupRootCommand(cmd) 函数, docker.go中的57行 1 2 3 opts, helpCmd = cli.SetupRootCommand(cmd)
根命令: docker [OPTIONS] COMMAND [ARG…]
1 2 3 4 5 6 // SetupRootCommand sets default usage, help, and error handling for the // root command. func SetupRootCommand(rootCmd *cobra.Command) (opts *cliflags.ClientOptions, helpCmd *cobra.Command) { rootCmd.SetVersionTemplate("Docker version {{.Version}}\n") return setupCommonRootCommand(rootCmd) }
3.1.3 commands.AddCommands(cmd, dockerCli), docker.go中的66行 1 2 commands.AddCommands(cmd, dockerCli)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func AddCommands(cmd *cobra.Command, dockerCli command.Cli) { cmd.AddCommand( container.NewRunCommand(dockerCli), 这里加入 container 相关的 几个子命令,例如 docker run、docker exec, docker ps 这3个子命令。 container.NewExecCommand(dockerCli), container.NewPsCommand(dockerCli), ...... ...... ...... ) } 这个里面会调用 cobra.Command 中的 AddCommand() 函数 ,把子命令 都给加入进去。 func (c *Command) AddCommand(cmds ...*Command) {
3.1.4 返回 NewTopLevelCommand() -> TopLevelCommand{} 结构体的一个指针, docker.go中的72行 1 2 3 4 5 6 7 8 9 func NewTopLevelCommand(cmd *cobra.Command, dockerCli *command.DockerCli, opts *cliflags.ClientOptions, flags *pflag.FlagSet) *TopLevelCommand { return &TopLevelCommand{ cmd: cmd, // --》 *cobra.Command 类型 dockerCli: dockerCli, // --》 *command.DockerCli 类型 opts: opts, // --》 *cliflags.ClientOptions 类型 flags: flags, // --》 *pflag.FlagSet 类型 args: os.Args[1:], // 命令 其他参数,本次执行的 是 docker run --name my --user 1000:1000 xxx 这样的命令。这里的args 就是 除了第一个后面的所有的。 } }
3.2 HandleGlobalFlags() 函数 3.3 tcmd.Initialize() 3.4 processAliases(dockerCli, cmd, args, os.Args) 3.5 pluginmanager 相关处理 1 2 3 4 5 6 7 8 9 10 11 12 13 if len(args) > 0 { ccmd, _, err := cmd.Find(args) if err != nil || pluginmanager.IsPluginCommand(ccmd) { err := tryPluginRun(dockerCli, cmd, args[0], envs) if !pluginmanager.IsNotFound(err) { return err } // For plugin not found we fall through to // cmd.Execute() which deals with reporting // "command not found" in a consistent way. } }
一个命令是 docker-xxxx 这样命名的,并且放入到环境变量 PATH 中去了 就是一个plugin相关的命令了。
类似这样做的 有 git-xxx 这样的命令,k8s 中也有。。。
这就是 命令埋得扩展点!!!!可以方便的支持扩展更多功能的子命令!!!
3.5.1 cmd.Find(args) 函数 这个 Find() 函数 也是 corba 里面提供的。
3.5.2 IsPluginCommand(ccmd) 函数 3.5.3 tryPluginRun(dockerCli, cmd, args[0], envs) 函数 3.6 重头戏开始,执行 cmd.Execute() 函数 调用 corba 中的 Execute()
函数了,这个会自动 调用 子命令中的 Execute()
函数的
例如: 执行了 docker run --name xxx --user 1000:1000 image:v1.0
这样的命令 下面就会调用到:run
这个子命令, 这个子命令也是 在这里 cli/command/commands/commands.go
文件 container.NewRunCommand(dockerCli)
, 加入 corba 中的。
这些命令行的执行 流程,命令行参数的解析 都是这个第三方库 corba 做的。
详细了解 corba 可以 参考 github.com/spf13/cobra