5 步助你成为一名优秀的 Docker 代码贡献者
【编者的话】开源渐成主流,越来越多的开发者想参与开源社区。而时下最火热的Docker也许就是开发者入手开源项目的最好选择,它不仅是目前最流行的开源项目之一,而且在提交Issue方面的文档和流程都是目前我见过的开源项目里最好的。本文主要介绍了如何入手开源项目,一些小经验和小工具,一起来学习。
成为一个流行开源项目(如Docker)的贡献者有如下好处:
- 你可以参与改进很多人都在使用的项目,以此来获得认同感;
- 你可以与开源社区中的那些聪明绝顶的人通力合作;
- 你可以通过参与理解和改进这个项目来使自己成为一名更加出色的程序员。
但是,从一个新的基准代码(codebase)入手绝对是一件恐怖的事情。目前,Docker已经有相当多的代码了,哪怕是修复一个小问题,都需要阅读大量的代码,并理解这些部分是如何组合在一起的。
不过,它们也并不如你想象的那么困难。你可以根据Docker的贡献者指南来完成环境的配置。然后按照如下5个简单的步骤,配合相关的代码片段来深入代码基。你所历练的这些技能,都将会在你的编程生涯的每个新项目中派上用场。那么还等什么,我们这就开始。
步骤1:从'func main()'开始
正如一句古话所述,从你知道的开始。如果你和大部分Docker用户一样,你可能主要使用Docker CLI。因此,让我们从程序的入口开始:‘main’函数。
此处为本文的提示,我们将会使用一个名为Sourcegraph的站点,Docker团队就使用它完成在线检索和代码浏览,和你使用智能IDE所做的差不多。建议在阅读本文时,打开Sourcegraph放在一边,以更好地跟上文章的进度。
在Sourcegraph站点,让我们搜索Docker仓库中的‘func main()’。
我们正在寻找对应‘docker’命令的‘main’函数,它是‘docker/docker/docker.go’中的一个文件。点击搜索结果,我们会跳到其定义(如下所示)。花一点时间浏览一下这个函数:
<span class="pln">func main</span><span class="pun">()</span><span class="pun">{</span>
<span class="kwd">if</span><span class="pln"> reexec</span><span class="pun">.</span><span class="typ">Init</span><span class="pun">()</span><span class="pun">{</span>
<span class="kwd">return</span>
<span class="pun">}</span>
<span class="com">// Set terminal emulation based on platform as required.</span>
<span class="pln">stdin</span><span class="pun">,</span><span class="pln"> stdout</span><span class="pun">,</span><span class="pln"> stderr </span><span class="pun">:=</span><span class="pln"> term</span><span class="pun">.</span><span class="typ">StdStreams</span><span class="pun">()</span>
<span class="pln">initLogging</span><span class="pun">(</span><span class="pln">stderr</span><span class="pun">)</span>
<span class="pln">flag</span><span class="pun">.</span><span class="typ">Parse</span><span class="pun">()</span>
<span class="com">// FIXME: validate daemon flags here</span>
<span class="kwd">if</span><span class="pun">*</span><span class="pln">flVersion </span><span class="pun">{</span>
<span class="pln">showVersion</span><span class="pun">()</span>
<span class="kwd">return</span>
<span class="pun">}</span>
<span class="kwd">if</span><span class="pun">*</span><span class="pln">flLogLevel </span><span class="pun">!=</span><span class="str">""</span><span class="pun">{</span>
<span class="pln">lvl</span><span class="pun">,</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> logrus</span><span class="pun">.</span><span class="typ">ParseLevel</span><span class="pun">(*</span><span class="pln">flLogLevel</span><span class="pun">)</span>
<span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="kwd">nil</span><span class="pun">{</span>
<span class="pln">logrus</span><span class="pun">.</span><span class="typ">Fatalf</span><span class="pun">(</span><span class="str">"Unable to parse logging level: %s"</span><span class="pun">,</span><span class="pun">*</span><span class="pln">flLogLevel</span><span class="pun">)</span>
<span class="pun">}</span>
<span class="pln">setLogLevel</span><span class="pun">(</span><span class="pln">lvl</span><span class="pun">)</span>
<span class="pun">}</span><span class="kwd">else</span><span class="pun">{</span>
<span class="pln">setLogLevel</span><span class="pun">(</span><span class="pln">logrus</span><span class="pun">.</span><span class="typ">InfoLevel</span><span class="pun">)</span>
<span class="pun">}</span>
<span class="com">// -D, --debug, -l/--log-level=debug processing</span>
<span class="com">// When/if -D is removed this block can be deleted</span>
<span class="kwd">if</span><span class="pun">*</span><span class="pln">flDebug </span><span class="pun">{</span>
<span class="pln">os</span><span class="pun">.</span><span class="typ">Setenv</span><span class="pun">(</span><span class="str">"DEBUG"</span><span class="pun">,</span><span class="str">"1"</span><span class="pun">)</span>
<span class="pln">setLogLevel</span><span class="pun">(</span><span class="pln">logrus</span><span class="pun">.</span><span class="typ">DebugLevel</span><span class="pun">)</span>
<span class="pun">}</span>
<span class="kwd">if</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">flHosts</span><span class="pun">)</span><span class="pun">==</span><span class="lit">0</span><span class="pun">{</span>
<span class="pln">defaultHost </span><span class="pun">:=</span><span class="pln"> os</span><span class="pun">.</span><span class="typ">Getenv</span><span class="pun">(</span><span class="str">"DOCKER_HOST"</span><span class="pun">)</span>
<span class="kwd">if</span><span class="pln"> defaultHost </span><span class="pun">==</span><span class="str">""</span><span class="pun">||</span><span class="pun">*</span><span class="pln">flDaemon </span><span class="pun">{</span>
<span class="com">// If we do not have a host, default to unix socket</span>
<span class="pln">defaultHost </span><span class="pun">=</span><span class="pln"> fmt</span><span class="pun">.</span><span class="typ">Sprintf</span><span class="pun">(</span><span class="str">"unix://%s"</span><span class="pun">,</span><span class="pln"> api</span><span class="pun">.</span><span class="pln">DEFAULTUNIXSOCKET</span><span class="pun">)</span>
<span class="pun">}</span>
<span class="pln">defaultHost</span><span class="pun">,</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> api</span><span class="pun">.</span><span class="typ">ValidateHost</span><span class="pun">(</span><span class="pln">defaultHost</span><span class="pun">)</span>
<span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="kwd">nil</span><span class="pun">{</span>
<span class="pln">logrus</span><span class="pun">.</span><span class="typ">Fatal</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span>
<span class="pun">}</span>
<span class="pln">flHosts </span><span class="pun">=</span><span class="pln"> append</span><span class="pun">(</span><span class="pln">flHosts</span><span class="pun">,</span><span class="pln"> defaultHost</span><span class="pun">)</span>
<span class="pun">}</span>
<span class="pln">setDefaultConfFlag</span><span class="pun">(</span><span class="pln">flTrustKey</span><span class="pun">,</span><span class="pln"> defaultTrustKeyFile</span><span class="pun">)</span>
<span class="kwd">if</span><span class="pun">*</span><span class="pln">flDaemon </span><span class="pun">{</span>
<span class="kwd">if</span><span class="pun">*</span><span class="pln">flHelp </span><span class="pun">{</span>
<span class="pln">flag</span><span class="pun">.</span><span class="typ">Usage</span><span class="pun">()</span>
<span class="kwd">return</span>
<span class="pun">}</span>
<span class="pln">mainDaemon</span><span class="pun">()</span>
<span class="kwd">return</span>
<span class="pun">}</span>
<span class="kwd">if</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">flHosts</span><span class="pun">)</span><span class="pun">></span><span class="lit">1</span><span class="pun">{</span>
<span class="pln">logrus</span><span class="pun">.</span><span class="typ">Fatal</span><span class="pun">(</span><span class="str">"Please specify only one -H"</span><span class="pun">)</span>
<span class="pun">}</span>
<span class="pln">protoAddrParts </span><span class="pun">:=</span><span class="pln"> strings</span><span class="pun">.</span><span class="typ">SplitN</span><span class="pun">(</span><span class="pln">flHosts</span><span class="pun">[</span><span class="lit">0</span><span class="pun">],</span><span class="str">"://"</span><span class="pun">,</span><span class="lit">2</span><span class="pun">)</span>
<span class="kwd">var</span><span class="pun">(</span>
<span class="pln">cli </span><span class="pun">*</span><span class="pln">client</span><span class="pun">.</span><span class="typ">DockerCli</span>
<span class="pln">tlsConfig tls</span><span class="pun">.</span><span class="typ">Config</span>
<span class="pun">)</span>
<span class="pln">tlsConfig</span><span class="pun">.</span><span class="typ">InsecureSkipVerify</span><span class="pun">=</span><span class="kwd">true</span>
<span class="com">// Regardless of whether the user sets it to true or false, if they</span>
<span class="com">// specify --tlsverify at all then we need to turn on tls</span>
<span class="kwd">if</span><span class="pln"> flag</span><span class="pun">.</span><span class="typ">IsSet</span><span class="pun">(</span><span class="str">"-tlsverify"</span><span class="pun">)</span><span class="pun">{</span>
<span class="pun">*</span><span class="pln">flTls </span><span class="pun">=</span><span class="kwd">true</span>
<span class="pun">}</span>
<span class="com">// If we should verify the server, we need to load a trusted ca</span>
<span class="kwd">if</span><span class="pun">*</span><span class="pln">flTlsVerify </span><span class="pun">{</span>
<span class="pln">certPool </span><span class="pun">:=</span><span class="pln"> x509</span><span class="pun">.</span><span class="typ">NewCertPool</span><span class="pun">()</span>
<span class="pln">file</span><span class="pun">,</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> ioutil</span><span class="pun">.</span><span class="typ">ReadFile</span><span class="pun">(*</span><span class="pln">flCa</span><span class="pun">)</span>
<span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="kwd">nil</span><span class="pun">{</span>
<span class="pln">logrus</span><span class="pun">.</span><span class="typ">Fatalf</span><span class="pun">(</span><span class="str">"Couldn't read ca cert %s: %s"</span><span class="pun">,</span><span class="pun">*</span><span class="pln">flCa</span><span class="pun">,</span><span class="pln"> err</span><span class="pun">)</span>
<span class="pun">}</span>
<span class="pln">certPool</span><span class="pun">.</span><span class="typ">AppendCertsFromPEM</span><span class="pun">(</span><span class="pln">file</span><span class="pun">)</span>
<span class="pln">tlsConfig</span><span class="pun">.</span><span class="typ">RootCAs</span><span class="pun">=</span><span class="pln"> certPool</span>
<span class="pln">tlsConfig</span><span class="pun">.</span><span class="typ">InsecureSkipVerify</span><span class="pun">=</span><span class="kwd">false</span>
<span class="pun">}</span>
<span class="com">// If tls is enabled, try to load and send client certificates</span>
<span class="kwd">if</span><span class="pun">*</span><span class="pln">flTls </span><span class="pun">||</span><span class="pun">*</span><span class="pln">flTlsVerify </span><span class="pun">{</span>
<span class="pln">_</span><span class="pun">,</span><span class="pln"> errCert </span><span class="pun">:=</span><span class="pln"> os</span><span class="pun">.</span><span class="typ">Stat</span><span class="pun">(*</span><span class="pln">flCert</span><span class="pun">)</span>
<span class="pln">_</span><span class="pun">,</span><span class="pln"> errKey </span><span class="pun">:=</span><span class="pln"> os</span><span class="pun">.</span><span class="typ">Stat</span><span class="pun">(*</span><span class="pln">flKey</span><span class="pun">)</span>
<span class="kwd">if</span><span class="pln"> errCert </span><span class="pun">==</span><span class="kwd">nil</span><span class="pun">&&</span><span class="pln"> errKey </span><span class="pun">==</span><span class="kwd">nil</span><span class="pun">{</span>
<span class="pun">*</span><span class="pln">flTls </span><span class="pun">=</span><span class="kwd">true</span>
<span class="pln">cert</span><span class="pun">,</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> tls</span><span class="pun">.</span><span class="typ">LoadX509KeyPair</span><span class="pun">(*</span><span class="pln">flCert</span><span class="pun">,</span><span class="pun">*</span><span class="pln">flKey</span><span class="pun">)</span>
<span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="kwd">nil</span><span class="pun">{</span>
<span class="pln">logrus</span><span class="pun">.</span><span class="typ">Fatalf</span><span class="pun">(</span><span class="str">"Couldn't load X509 key pair: %q. Make sure the key is encrypted"</span><span class="pun">,</span><span class="pln"> err</span><span class="pun">)</span>
<span class="pun">}</span>
<span class="pln">tlsConfig</span><span class="pun">.</span><span class="typ">Certificates</span><span class="pun">=</span><span class="pun">[]</span><span class="pln">tls</span><span class="pun">.</span><span class="typ">Certificate</span><span class="pun">{</span><span class="pln">cert</span><span class="pun">}</span>
<span class="pun">}</span>
<span class="com">// Avoid fallback to SSL protocols < TLS1.0</span>
<span class="pln">tlsConfig</span><span class="pun">.</span><span class="typ">MinVersion</span><span class="pun">=</span><span class="pln"> tls</span><span class="pun">.</span><span class="typ">VersionTLS10</span>
<span class="pun">}</span>
<span class="kwd">if</span><span class="pun">*</span><span class="pln">flTls </span><span class="pun">||</span><span class="pun">*</span><span class="pln">flTlsVerify </span><span class="pun">{</span>
<span class="pln">cli </span><span class="pun">=</span><span class="pln"> client</span><span class="pun">.</span><span class="typ">NewDockerCli</span><span class="pun">(</span><span class="pln">stdin</span><span class="pun">,</span><span class="pln"> stdout</span><span class="pun">,</span><span class="pln"> stderr</span><span class="pun">,</span><span class="pun">*</span><span class="pln">flTrustKey</span><span class="pun">,</span><span class="pln"> protoAddrParts</span><span class="pun">[</span><span class="lit">0</span><span class="pun">],</span><span class="pln"> protoAddrParts</span><span class="pun">[</span><span class="lit">1</span><span class="pun">],</span><span class="pun">&</span><span class="pln">tlsConfig</span><span class="pun">)</span>
<span class="pun">}</span><span class="kwd">else</span><span class="pun">{</span>
<span class="pln">cli </span><span class="pun">=</span><span class="pln"> client</span><span class="pun">.</span><span class="typ">NewDockerCli</span><span class="pun">(</span><span class="pln">stdin</span><span class="pun">,</span><span class="pln"> stdout</span><span class="pun">,</span><span class="pln"> stderr</span><span class="pun">,</span><span class="pun">*</span><span class="pln">flTrustKey</span><span class="pun">,</span><span class="pln"> protoAddrParts</span><span class="pun">[</span><span class="lit">0</span><span class="pun">],</span><span class="pln"> protoAddrParts</span><span class="pun">[</span><span class="lit">1</span><span class="pun">],</span><span class="kwd">nil</span><span class="pun">)</span>
<span class="pun">}</span>
<span class="kwd">if</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> cli</span><span class="pun">.</span><span class="typ">Cmd</span><span class="pun">(</span><span class="pln">flag</span><span class="pun">.</span><span class="typ">Args</span><span class="pun">()...);</span><span class="pln"> err </span><span class="pun">!=</span><span class="kwd">nil</span><span class="pun">{</span>
<span class="kwd">if</span><span class="pln"> sterr</span><span class="pun">,</span><span class="pln"> ok </span><span class="pun">:=</span><span class="pln"> err</span><span class="pun">.(*</span><span class="pln">utils</span><span class="pun">.</span><span class="typ">StatusError</span><span class="pun">);</span><span class="pln"> ok </span><span class="pun">{</span>
<span class="kwd">if</span><span class="pln"> sterr</span><span class="pun">.</span><span class="typ">Status</span><span class="pun">!=</span><span class="str">""</span><span class="pun">{</span>
<span class="pln">logrus</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">sterr</span><span class="pun">.</span><span class="typ">Status</span><span class="pun">)</span>
<span class="pun">}</span>
<span class="pln">os</span><span class="pun">.</span><span class="typ">Exit</span><span class="pun">(</span><span class="pln">sterr</span><span class="pun">.</span><span class="typ">StatusCode</span><span class="pun">)</span>
<span class="pun">}</span>
<span class="pln">logrus</span><span class="pun">.</span><span class="typ">Fatal</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span>
<span class="pun">}</span>
<span class="pun">}</span>
在‘main’函数的顶部,我们看了许多与日志配置,命令标志读取以及默认初始化相关的代码。在底部,我们发现了对『client.NewDockerCli』的调用,它似乎是用来负责创建结构体的,而这个结构体的函数则会完成所有的实际工作。让我们来搜索『NewDockerCli』。
步骤2:找到核心部分
在很多的应用和程序库中,都有1到2个关键接口,它表述了核心功能或者本质。让我们尝试到达这个关键部分。
点击‘NewDockerCli’的搜索结果,我们会到达函数的定义。由于我们感兴趣的只是这个函数所返回的结构体——「DockerCli」,因此让我们点击返回类型来跳转到其定义。
<span class="pln">func </span><span class="typ">NewDockerCli</span><span class="pun">(</span><span class="kwd">in</span><span class="pln"> io</span><span class="pun">.</span><span class="typ">ReadCloser</span><span class="pun">,</span><span class="kwd">out</span><span class="pun">,</span><span class="pln"> err io</span><span class="pun">.</span><span class="typ">Writer</span><span class="pun">,</span><span class="pln"> keyFile </span><span class="kwd">string</span><span class="pun">,</span><span class="pln"> proto</span><span class="pun">,</span><span class="pln"> addr </span><span class="kwd">string</span><span class="pun">,</span><span class="pln"> tlsConfig </span><span class="pun">*</span><span class="pln">tls</span><span class="pun">.</span><span class="typ">Config</span><span class="pun">)</span><span class="pun">*</span><span class="typ">DockerCli</span><span class="pun">{</span>
<span class="kwd">var</span><span class="pun">(</span>
<span class="pln">inFd uintptr</span>
<span class="pln">outFd uintptr</span>
<span class="pln">isTerminalIn </span><span class="pun">=</span><span class="kwd">false</span>
<span class="pln">isTerminalOut </span><span class="pun">=</span><span class="kwd">false</span>
<span class="pln">scheme </span><span class="pun">=</span><span class="str">"http"</span>
<span class="pun">)</span>
<span class="kwd">if</span><span class="pln"> tlsConfig </span><span class="pun">!=</span><span class="kwd">nil</span><span class="pun">{</span>
<span class="pln">scheme </span><span class="pun">=</span><span class="str">"https"</span>
<span class="pun">}</span>
<span class="kwd">if</span><span class="kwd">in</span><span class="pun">!=</span><span class="kwd">nil</span><span class="pun">{</span>
<span class="pln">inFd</span><span class="pun">,</span><span class="pln"> isTerminalIn </span><span class="pun">=</span><span class="pln"> term</span><span class="pun">.</span><span class="typ">GetFdInfo</span><span class="pun">(</span><span class="kwd">in</span><span class="pun">)</span>
<span class="pun">}</span>
<span class="kwd">if</span><span class="kwd">out</span><span class="pun">!=</span><span class="kwd">nil</span><span class="pun">{</span>
<span class="pln">outFd</span><span class="pun">,</span><span class="pln"> isTerminalOut </span><span class="pun">=</span><span class="pln"> term</span><span class="pun">.</span><span class="typ">GetFdInfo</span><span class="pun">(</span><span class="kwd">out</span><span class="pun">)</span>
<span class="pun">}</span>
<span class="kwd">if</span><span class="pln"> err </span><span class="pun">==</span><span class="kwd">nil</span><span class="pun">{</span>
<span class="pln">err </span><span class="pun">=</span><span class="kwd">out</span>
<span class="pun">}</span>
<span class="com">// The transport is created here for reuse during the client session</span>
<span class="pln">tr </span><span class="pun">:=</span><span class="pun">&</span><span class="pln">http</span><span class="pun">.</span><span class="typ">Transport</span><span class="pun">{</span>
<span class="typ">TLSClientConfig</span><span class="pun">:</span><span class="pln"> tlsConfig</span><span class="pun">,</span>
<span class="pun">}</span>
<span class="com">// Why 32? See issue 8035</span>
<span class="pln">timeout </span><span class="pun">:=</span><span class="lit">32</span><span class="pun">*</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Second</span>
<span class="kwd">if</span><span class="pln"> proto </span><span class="pun">==</span><span class="str">"unix"</span><span class="pun">{</span>
<span class="com">// no need in compressing for local communications</span>
<span class="pln">tr</span><span class="pun">.</span><span class="typ">DisableCompression</span><span class="pun">=</span><span class="kwd">true</span>
<span class="pln">tr</span><span class="pun">.</span><span class="typ">Dial</span><span class="pun">=</span><span class="pln"> func</span><span class="pun">(</span><span class="pln">_</span><span class="pun">,</span><span class="pln"> _ </span><span class="kwd">string</span><span class="pun">)</span><span class="pun">(</span><span class="pln">net</span><span class="pun">.</span><span class="typ">Conn</span><span class="pun">,</span><span class="pln"> error</span><span class="pun">)</span><span class="pun">{</span>
<span class="kwd">return</span><span class="pln"> net</span><span class="pun">.</span><span class="typ">DialTimeout</span><span class="pun">(</span><span class="pln">proto</span><span class="pun">,</span><span class="pln"> addr</span><span class="pun">,</span><span class="pln"> timeout</span><span class="pun">)</span>
<span class="pun">}</span>
<span class="pun">}</span><span class="kwd">else</span><span class="pun">{</span>
<span class="pln">tr</span><span class="pun">.</span><span class="typ">Proxy</span><span class="pun">=</span><span class="pln"> http</span><span class="pun">.</span><span class="typ">ProxyFromEnvironment</span>
<span class="pln">tr</span><span class="pun">.</span><span class="typ">Dial</span><span class="pun">=</span><span class="pun">(&</span><span class="pln">net</span><span class="pun">.</span><span class="typ">Dialer</span><span class="pun">{</span><span class="typ">Timeout</span><span class="pun">:</span><span class="pln"> timeout</span><span class="pun">}).</span><span class="typ">Dial</span>
<span class="pun">}</span>
<span class="kwd">return</span><span class="pun">&</span><span class="typ">DockerCli</span><span class="pun">{</span>
<span class="pln">proto</span><span class="pun">:</span><span class="pln"> proto</span><span class="pun">,</span>
<span class="pln">addr</span><span class="pun">:</span><span class="pln"> addr</span><span class="pun">,</span>
<span class="kwd">in</span><span class="pun">:</span><span class="kwd">in</span><span class="pun">,</span>
<span class="kwd">out</span><span class="pun">:</span><span class="kwd">out</span><span class="pun">,</span>
<span class="pln">err</span><span class="pun">:</span><span class="pln"> err</span><span class="pun">,</span>
<span class="pln">keyFile</span><span class="pun">:</span><span class="pln"> keyFile</span><span class="pun">,</span>
<span class="pln">inFd</span><span class="pun">:</span><span class="pln"> inFd</span><span class="pun">,</span>
<span class="pln">outFd</span><span class="pun">:</span><span class="pln"> outFd</span><span class="pun">,</span>
<span class="pln">isTerminalIn</span><span class="pun">:</span><span class="pln"> isTerminalIn</span><span class="pun">,</span>
<span class="pln">isTerminalOut</span><span class="pun">:</span><span class="pln"> isTerminalOut</span><span class="pun">,</span>
<span class="pln">tlsConfig</span><span class="pun">:</span><span class="pln"> tlsConfig</span><span class="pun">,</span>
<span class="pln">scheme</span><span class="pun">:</span><span class="pln"> scheme</span><span class="pun">,</span>
<span class="pln">transport</span><span class="pun">:</span><span class="pln"> tr</span><span class="pun">,</span>
<span class="pun">}</span>
<span class="pun">}</span>
点击『DockerCli』将我们带到了它的定义。向下滚动这个文件,我们可以看到它的方法, ‘getMethod’,‘Cmd’,‘Subcmd’和‘LoadConfigFile’。其中,‘Cmd’值得留意。它是唯一一个包含docstring的方法,而docstring则表明它是执行每条Docker命令的核心方法。
步骤3:更进一步
既然我们已经找到了‘DockerCli’,这个Docker客户端的核心‘控制器’,接下来让我们继续深入,了解一条具体的Docker命令是如何工作的。让我们放大‘docker build’部分的代码。
<span class="pln">type </span><span class="typ">DockerCli</span><span class="kwd">struct</span><span class="pun">{</span>
<span class="pln">proto </span><span class="kwd">string</span>
<span class="pln">addr </span><span class="kwd">string</span>
<span class="pln">configFile </span><span class="pun">*</span><span class="pln">registry</span><span class="pun">.</span><span class="typ">ConfigFile</span>
<span class="kwd">in</span><span class="pln"> io</span><span class="pun">.</span><span class="typ">ReadCloser</span>
<span class="kwd">out</span><span class="pln"> io</span><span class="pun">.</span><span class="typ">Writer</span>
<span class="pln">err io</span><span class="pun">.</span><span class="typ">Writer</span>
<span class="pln">keyFile </span><span class="kwd">string</span>
<span class="pln">tlsConfig </span><span class="pun">*</span><span class="pln">tls</span><span class="pun">.</span><span class="typ">Config</span>
<span class="pln">scheme </span><span class="kwd">string</span>
<span class="com">// inFd holds file descriptor of the client's STDIN, if it's a valid file</span>
<span class="pln">inFd uintptr</span>
<span class="com">// outFd holds file descriptor of the client's STDOUT, if it's a valid file</span>
<span class="pln">outFd uintptr</span>
<span class="com">// isTerminalIn describes if client's STDIN is a TTY</span>
<span class="pln">isTerminalIn </span><span class="kwd">bool</span>
<span class="com">// isTerminalOut describes if client's STDOUT is a TTY</span>
<span class="pln">isTerminalOut </span><span class="kwd">bool</span>
<span class="pln">transport </span><span class="pun">*</span><span class="pln">http</span><span class="pun">.</span><span class="typ">Transport</span>
<span class="pun">}</span>
阅读‘DockerCli.Cmd’的实现可以发现,它调用了‘DockerCli.getMethod’方法来执行每条Docker命令所对应的函数。
<span class="pln">func </span><span class="pun">(</span><span class="pln">cli </span><span class="pun">*</span><span class="typ">DockerCli</span><span class="pun">)</span><span class="typ">Cmd</span><span class="pun">(</span><span class="pln">args </span><span class="pun">...</span><span class="kwd">string</span><span class="pun">)</span><span class="pln"> error </span><span class="pun">{</span>
<span class="kwd">if</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">args</span><span class="pun">)</span><span class="pun">></span><span class="lit">1</span><span class="pun">{</span>
<span class="pln">method</span><span class="pun">,</span><span class="pln"> exists </span><span class="pun">:=</span><span class="pln"> cli</span><span class="pun">.</span><span class="pln">getMethod</span><span class="pun">(</span><span class="pln">args</span><span class="pun">[:</span><span class="lit">2</span><span class="pun">]...)</span>
<span class="kwd">if</span><span class="pln"> exists </span><span class="pun">{</span>
<span class="kwd">return</span><span class="pln"> method</span><span class="pun">(</span><span class="pln">args</span><span class="pun">[</span><span class="lit">2</span><span class="pun">:]...)</span>
<span class="pun">}</span>
<span class="pun">}</span>
<span class="kwd">if</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">args</span><span class="pun">)</span><span class="pun">></span><span class="lit">0</span><span class="pun">{</span>
<span class="pln">method</span><span class="pun">,</span><span class="pln"> exists </span><span class="pun">:=</span><span class="pln"> cli</span><span class="pun">.</span><span class="pln">getMethod</span><span class="pun">(</span><span class="pln">args</span><span class="pun">[</span><span class="lit">0</span><span class="pun">])</span>
<span class="kwd">if</span><span class="pun">!</span><span class="pln">exists </span><span class="pun">{</span>
<span class="pln">fmt</span><span class="pun">.</span><span class="typ">Fprintf</span><span class="pun">(</span><span class="pln">cli</span><span class="pun">.</span><span class="pln">err</span><span class="pun">,</span><span class="str">"docker: '%s' is not a docker command. See 'docker --help'.\n"</span><span class="pun">,</span><span class="pln"> args</span><span class="pun">[</span><span class="lit">0</span><span class="pun">])</span>
<span class="pln">os</span><span class="pun">.</span><span class="typ">Exit</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span>
<span class="pun">}</span>
<span class="kwd">return</span><span class="pln"> method</span><span class="pun">(</span><span class="pln">args</span><span class="pun">[</span><span class="lit">1</span><span class="pun">:]...)</span>
<span class="pun">}</span>
<span class="kwd">return</span><span class="pln"> cli</span><span class="pun">.</span><span class="typ">CmdHelp</span><span class="pun">()</span>
<span class="pun">}</span>
在‘DockerCli.getMethod’中,我们可以看到它是通过对一个函数的动态调用实现的,其中这个函数名的形式为在Docker命令前预置“Cmd”字符串。那么在‘docker build’这个情况下,我们寻找的是‘DockerCli.CmdBuild’。但在这个文件中并没有对应的方法,因此让我们需要搜索‘CmdBuild’。
<span class="pln">func </span><span class="pun">(</span><span class="pln">cli </span><span class="pun">*</span><span class="typ">DockerCli</span><span class="pun">)</span><span class="pln"> getMethod</span><span class="pun">(</span><span class="pln">args </span><span class="pun">...</span><span class="kwd">string</span><span class="pun">)</span><span class="pun">(</span><span class="pln">func</span><span class="pun">(...</span><span class="kwd">string</span><span class="pun">)</span><span class="pln"> error</span><span class="pun">,</span><span class="kwd">bool</span><span class="pun">)</span><span class="pun">{</span>
<span class="pln">camelArgs </span><span class="pun">:=</span><span class="pln"> make</span><span class="pun">([]</span><span class="kwd">string</span><span class="pun">,</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">args</span><span class="pun">))</span>
<span class="kwd">for</span><span class="pln"> i</span><span class="pun">,</span><span class="pln"> s </span><span class="pun">:=</span><span class="pln"> range args </span><span class="pun">{</span>
<span class="kwd">if</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">s</span><span class="pun">)</span><span class="pun">==</span><span class="lit">0</span><span class="pun">{</span>
<span class="kwd">return</span><span class="kwd">nil</span><span class="pun">,</span><span class="kwd">false</span>
<span class="pun">}</span>
<span class="pln">camelArgs</span><span class="pun">[</span><span class="pln">i</span><span class="pun">]</span><span class="pun">=</span><span class="pln"> strings</span><span class="pun">.</span><span class="typ">ToUpper</span><span class="pun">(</span><span class="pln">s</span><span class="pun">[:</span><span class="lit">1</span><span class="pun">])</span><span class="pun">+</span><span class="pln"> strings</span><span class="pun">.</span><span class="typ">ToLower</span><span class="pun">(</span><span class="pln">s</span><span class="pun">[</span><span class="lit">1</span><span class="pun">:])&