docker命令attach源码分析

一个命令如attach命令,定义为一个Flag,首先看一下FlagSet的结构体表示:
type FlagSet struct {
	// 比如我们在解释一个Flag如attach命令(docker attach xxx)
	// 如果出错咯,我们就输出错误提示到Usage和ShortUsage那个指向的匿名函数
	
	Usage      func()
	ShortUsage func()

	name             string           // "attach".....
	parsed           bool             // 是够被解析过的标志
	actual           map[string]*Flag // 当前解析的Flag
	formal           map[string]*Flag  // 以前解析的Flag 
	args             []string          // 解析参数,如 docker attach ID ID即为参数 
	errorHandling    ErrorHandling     // 错误的钩子  
	output           io.Writer         // 错误输出IO,stderr/stdout
	nArgRequirements []nArgRequirement // 需要参数的个数,比如docker attach xx 则需要参数为1
}

 接着,将分析一个具体的命令的执行过程,比如docker attach的执行过程源码分析

函数定义:func (cli *DockerCli) CmdAttach(args ...string) error 

docker client API命令统一为:Cmd+"命令name"

设置flagset,思想类似map[key]value,先赋值key和默认的value,设置一些参数,方便后面Prase参数的时候判断key是否存在,以及初始化的默认值,参数个数等等....

cmd := Cli.Subcmd("attach", []string{"CONTAINER"}, "Attach to a running container", true)
//看一看Subcmd的函数:
func Subcmd(name string, synopses []string, description string, exitOnError bool) *flag.FlagSet {
	var errorHandling flag.ErrorHandling
	if exitOnError {
		errorHandling = flag.ExitOnError
	} else {
		errorHandling = flag.ContinueOnError
	}
	flags := flag.NewFlagSet(name, errorHandling)
	//关键就是设置了这几个匿名函数,方便以后命令输入错误的时候提示
	flags.Usage = func() {
		flags.ShortUsage()
		flags.PrintDefaults()
	}

	flags.ShortUsage = func() {
		options := ""
		if flags.FlagCountUndeprecated() > 0 {
			options = " [OPTIONS]"
		}

		if len(synopses) == 0 {
			synopses = []string{""}
		}

		// Allow for multiple command usage synopses.
		for i, synopsis := range synopses {
			lead := "\t"
			if i == 0 {
				// First line needs the word 'Usage'.
				lead = "Usage:\t"
			}

			if synopsis != "" {
				synopsis = " " + synopsis
			}

			fmt.Fprintf(flags.Out(), "\n%sdocker %s%s%s", lead, name, options, synopsis)
		}

		fmt.Fprintf(flags.Out(), "\n\n%s\n", description)
	}
	return flags
}
//一个NewFlagSet,就是生成一个FlagSet
func NewFlagSet(name string, errorHandling ErrorHandling) *FlagSet {
	f := &FlagSet{
		name:          name,
		errorHandling: errorHandling,
	}
	return f
}

再看看cmd.Bool是干了什么鬼?

noStdin := cmd.Bool([]string{"#nostdin", "-no-stdin"}, false, "Do not attach STDIN")
proxy := cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxy all received signals to the process")
//这两个鬼是返回的val所保存的地址

// []string{"#nostdin", "-no-stdin"} ->names ,false -> value,"Do not attach STDIN" ->usage
func (fs *FlagSet) Bool(names []string, value bool, usage string) *bool {
	p := new(bool)
	fs.BoolVar(p, names, value, usage)
	return p
	//但是付汇的是这个p的地址
}

//fs.BoolVar(p, names, value, usage)执行
func (fs *FlagSet) BoolVar(p *bool, names []string, value bool, usage string) {
	fs.Var(newBoolValue(value, p), names, usage)
}

// newBoolValue的功能是某个地址p赋值val,然后返回地址,
//注意go语言中,return 栈上的变量会生成在堆上,不是临时的,这一点和c语言不一样

type boolValue bool

func newBoolValue(val bool, p *bool) *boolValue {
	*p = val
	return (*boolValue)(p)
}

//Var 就是给FlagSet赋值上Flag
func (fs *FlagSet) Var(value Value, names []string, usage string) {
	// Remember the default value as a string; it won't change.
	flag := &Flag{names, usage, value, value.String()}
	for _, name := range names {
	//循环的遍历每个命令的name,过滤掉前面的#字符
		name = strings.TrimPrefix(name, "#")
		_, alreadythere := fs.formal[name]
		if alreadythere {
			var msg string
			if fs.name == "" {
				msg = fmt.Sprintf("flag redefined: %s", name)
			} else {
				msg = fmt.Sprintf("%s flag redefined: %s", fs.name, name)
			}
			fmt.Fprintln(fs.Out(), msg)
			panic(msg) // Happens only if flags are declared with identical names
		}
		//如果为空,则初始化map,map一定要先make初始化后才能使用
		if fs.formal == nil {
			fs.formal = make(map[string]*Flag)
		}
		fs.formal[name] = flag
	}
}

//flag的定义简单:
type Flag struct {
	Names    []string // name as it appears on command line
	Usage    string   // help message
	Value    Value    // value as set
	DefValue string   // default value (as text); for usage message
}

接下来再看看cmd.Require

cmd.Require(flag.Exact, 1)
//flag.Exact = 0 后面定义可以看出来

func (fs *FlagSet) Require(nArgRequirementType nArgRequirementType, nArg int) {
fs.nArgRequirements = append(fs.nArgRequirements, nArgRequirement{nArgRequirementType, nArg})
}
//切片的append

type nArgRequirementType int

const (
	Exact nArgRequirementType = iota
	Max
	Min
)

//说明docker attach需要一个额外的参数

再来看看cmd.ParseFlags

cmd.ParseFlags(args, true)

func (fs *FlagSet) ParseFlags(args []string, withHelp bool) error {
	var help *bool
	if withHelp {
		help = fs.Bool([]string{"#help", "-help"}, false, "Print usage")
	}
	if err := fs.Parse(args); err != nil {
		return err
	}
	if help != nil && *help {
		fs.SetOutput(os.Stdout)
		fs.Usage()
		os.Exit(0)
	}
	if str := fs.CheckArgs(); str != "" {
		fs.SetOutput(os.Stderr)
		fs.ReportError(str, withHelp)
		fs.ShortUsage()
		os.Exit(1)
	}
	return nil
}

//主要是检测参数是否正确,如果不正确,则输出Usage/ShortUsage或者withHelp等等提示语

func (fs *FlagSet) CheckArgs() (message string) {
	for _, req := range fs.nArgRequirements {
		var arguments string
		//检测参数的提示语
		if req.N == 1 {
			arguments = "1 argument"
		} else {
			arguments = fmt.Sprintf("%d arguments", req.N)
		}

		str := func(kind string) string {
			return fmt.Sprintf("%q requires %s%s", fs.name, kind, arguments)
		}

		switch req.Type {
		case Exact://attach命令现在默认是Exact
			if fs.NArg() != req.N {
				return str("")
			}
		case Max:
			if fs.NArg() > req.N {
				return str("a maximum of ")
			}
		case Min:
			if fs.NArg() < req.N {
				return str("a minimum of ")
			}
		}
	}
	return ""
}

这次就讲到这里咯,下一次再继续分析一下参数prase的具体过程和cli.call,docker client和docker daemon交互的过程

你可能感兴趣的:(docker命令attach源码分析)