最近在看doccker的源码,最新的master分支(估计是1.12.4,因为最新的release是1.12.3)命令行解析全部都使用了第3方的包https://github.com/spf13/cobra。然后看了一下别的分支的代码,感觉结构确实清晰了很多,可读性变高了不少。先看一下如何去使用。
客户端main()在docker/docker/cmd/docker下,可以直接使用go build编译(把vendor下的依赖包移出来就可以了)。
L20-58:
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()) cmd.AddCommand(newDaemonCommand()) commands.AddCommands(cmd, dockerCli) return cmd }
这个方法会完成所有命令行规则的添加,和相应执行的方法。RunE: func(cmd *cobra.Command, args []string) error {}是命令执行的方法,flags是参数的解析。当我们输入docker -v,执行showversion()方法,*cobra.Command结构体是一个树的结构,docker下面有很多的子命令,比如docker image,然后image下面可以添加参数 -a等,最后只要在main()中执行cmd.Execute()就可以完成所有解析,这是也是第3方包的方法。
ctrl+鼠标右键点击进入commands.AddCommands(cmd, dockerCli)
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), 。。。) }
封装了一下cmd.AddCommand()方法,然后node.service.stack.swarm.container这些包都写了个大写new方法来创建命令,继续进入container.NewContainerCommand(dockerCli),这是所有的方法都传入了dockerCli(单例模式直视感),
func NewContainerCommand(dockerCli *command.DockerCli) *cobra.Command { cmd := &cobra.Command{ Use: "container", Short: "Manage containers", Args: cli.NoArgs, Run: func(cmd *cobra.Command, args []string) { fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString()) }, } cmd.AddCommand( NewAttachCommand(dockerCli), NewCommitCommand(dockerCli), NewCopyCommand(dockerCli), NewCreateCommand(dockerCli), NewDiffCommand(dockerCli), NewExecCommand(dockerCli), NewExportCommand(dockerCli), NewKillCommand(dockerCli), NewLogsCommand(dockerCli), NewPauseCommand(dockerCli), NewPortCommand(dockerCli), NewRenameCommand(dockerCli), NewRestartCommand(dockerCli), NewRmCommand(dockerCli), NewRunCommand(dockerCli), NewStartCommand(dockerCli), NewStatsCommand(dockerCli), NewStopCommand(dockerCli), NewTopCommand(dockerCli), NewUnpauseCommand(dockerCli), NewUpdateCommand(dockerCli), NewWaitCommand(dockerCli), newListCommand(dockerCli), newInspectCommand(dockerCli), NewPruneCommand(dockerCli), ) return cmd }
docker contain下又添加了很多的命令,继续进入
func NewCreateCommand(dockerCli *command.DockerCli) *cobra.Command { var opts createOptions var copts *runconfigopts.ContainerOptions cmd := &cobra.Command{ Use: "create [OPTIONS] IMAGE [COMMAND] [ARG...]", Short: "Create a new container", Args: cli.RequiresMinArgs(1), RunE: func(cmd *cobra.Command, args []string) error { copts.Image = args[0] if len(args) > 1 { copts.Args = args[1:] } return runCreate(dockerCli, cmd.Flags(), &opts, copts) }, } flags := cmd.Flags() flags.SetInterspersed(false) flags.StringVar(&opts.name, "name", "", "Assign a name to the container") // Add an explicit help that doesn‘t have a `-h` to prevent the conflict // with hostname flags.Bool("help", false, "Print usage") command.AddTrustedFlags(flags, true) copts = runconfigopts.AddFlags(flags) return cmd }
这里可以看到解析了name和help,docker run create命令就是这么来的,然后看一下RunE中是如何向服务器端发送请求的,继续进入最后可以得到一个这个方法,response, err := dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, name)
func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (types.ContainerCreateResponse, error) { var response types.ContainerCreateResponse query := url.Values{} if containerName != "" { query.Set("name", containerName) } body := configWrapper{ Config: config, HostConfig: hostConfig, NetworkingConfig: networkingConfig, } serverResp, err := cli.post(ctx, "/containers/create", query, body, nil) if err != nil { if serverResp != nil && serverResp.statusCode == 404 && strings.Contains(err.Error(), "No such image") { return response, imageNotFoundError{config.Image} } return response, err } err = json.NewDecoder(serverResp.body).Decode(&response) ensureReaderClosed(serverResp) return response, err }
发送post /containers/create请求,细节的地方需要继续去看,服务器端的命令解析也是一样的,目录在docker/docker/cmd/dockerd下,go编译的包是通过包名获取的,所以deamon的命令就是dockerd,有一个很好的方法是dockerd -D,可以看到调试的信息,service api的添加,网络的初始化等。。
https://github.com/zjj2wry/httptest,这是拿那个命令行解析写的一个post请求,可以携带参数,cookies等,然后生成接口文档,对我已经完全够了(目前业务百分之90都是post),主要写个接口得给前端一个文档,偷个懒。。