<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>deathking (DeathKing)</title>
    <link>https://ruby-china.org/deathking</link>
    <description>( _2b || !_2b ).is_a? Question</description>
    <language>en-us</language>
    <item>
      <title> Puppet 扩展研究 - 使用 Log.io 作为即时日志输出</title>
      <description>&lt;h2 id="绪言"&gt;绪言&lt;/h2&gt;
&lt;p&gt;手里有一系列 Puppet Hacking Guide 文集，但这些都只是从“原理剖析”的层面上来解释 Puppet 的内部运行原理，虽然给出了很多有用的扩展建议，但并没有“动真格地”给出一个扩展的实例。这篇文章，就是根据一个真实的需求来扩展 Puppet。但需要说明的是，这个扩展只是为了证成“需求是可以实现的”。之所以这么说，一方面是为糟糕的代码质量开脱，另一方面，是想要强调，&lt;strong&gt;本文所描述的并不是最终的解决方案，希望读者不要生搬硬套&lt;/strong&gt;！&lt;/p&gt;

&lt;p&gt;离职前，我在团队内部做了关于《Puppet Hacking Guide - 设计理念及运行原理》的内部技术分享，在会后 Q&amp;amp;A 环节，我的主管提出了这么一个需求，转述如下：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;考虑 Puppet 的整个运行流程，当 Agent 端在完成整个部署后，才会向报告服务器发送报告。我们希望 Agent 在部署每一个资源后，能够马上将信息（尤其是错误信息）发送给服务器，以便尽快做出应急处理等。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;由于这两个月一直在读 Puppet 源码，所以对它的运行原理还是相当熟悉的，当时想了一下，说感觉不难。然后隔天上午看了悠哉悠哉看了下 Puppet 日志处理的相关源码，吃过午饭后花了半小时把这个需求实现了。比较巧合的是，那天上午逛 GitHub 的时候，偶然看到了 Log.io 这个项目，然后就决定暂时先拿 Log.io 搞。这里将整个思考过程和解决方案记录下来，以供后人将这个需求整合入现有系统时参考。&lt;/p&gt;

&lt;p&gt;一图胜千言，上个图：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2015/49d6c494ffe0377a7682b0901ddf670d.png" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="启：突破口"&gt;启：突破口&lt;/h2&gt;
&lt;p&gt;参加过分享会的同学可能对这张 slide 已经没有什么印象了，但是这张幻灯片里面有个关键点，是实现这个需求的突破口：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2015/804f3ae8ad91ec4a94acb1495aa3e28e.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;关键点就是 &lt;code&gt;Puppet::Util::Log.newdestination(report)&lt;/code&gt; 这句代码，正如我注释里写的那样，这段代码是为日志系统“添加”一个新的目的的，也就是每次日志系统的 IO 输出都会向这个 &lt;code&gt;report&lt;/code&gt; 对象输出一份。我们的思路就是：&lt;strong&gt;编写一个新的日志目的地，当然，就是往远端服务器写日志！&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="承：Puppet 日志系统原理"&gt;承：Puppet 日志系统原理&lt;/h2&gt;
&lt;p&gt;Puppet 日志系统是这样处理新消息的：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newmessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@levels.index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;level&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="vi"&gt;@loglevel&lt;/span&gt;

  &lt;span class="n"&gt;queuemessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@destinations.length&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

  &lt;span class="vi"&gt;@destinations.each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;具体来说：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;如果日志的等级小于当前等级，那么我们直接忽视它（比如调试信息 &lt;code&gt;debug&lt;/code&gt; 级的日志，不应该出现在产品级环境中）；&lt;/li&gt;
&lt;li&gt;如果当前没有日志输出目的地的话，我们就将日志缓存在一个队列里面；&lt;/li&gt;
&lt;li&gt;调用日志目的地的 &lt;code&gt;handle&lt;/code&gt; 方法，来处理日志；&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这段代码给我提供了两个关键信息：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;同样一条日志，可能会写往多个目的地；&lt;/li&gt;
&lt;li&gt;重要的是 &lt;code&gt;handle&lt;/code&gt; 方法，我们编写的目的地需要响应 &lt;code&gt;handle&lt;/code&gt; 这个方法；&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;实际上，打开文件 &lt;code&gt;lib/puppet/util/log/destinations.rb&lt;/code&gt; ，我们可以发现 Puppet 自己定义的几个目的地，下面举几个比较有意思的例子；&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;
&lt;span class="c1"&gt;# Log to a transaction report.&lt;/span&gt;
&lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Util&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newdesttype&lt;/span&gt; &lt;span class="ss"&gt;:report&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:report&lt;/span&gt;

  &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="s2"&gt;"Puppet::Transaction::Report"&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;report&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@report&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


&lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Util&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newdesttype&lt;/span&gt; &lt;span class="ss"&gt;:file&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'fileutils'&lt;/span&gt;

  &lt;span class="c1"&gt;# 有意省略了部分代码&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@file.puts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;source&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; (&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;level&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;): &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="vi"&gt;@file.flush&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@autoflush&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Util&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newdesttype&lt;/span&gt; &lt;span class="ss"&gt;:array&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="s2"&gt;"Puppet::Test::LogCollector"&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@messages&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;file&lt;/code&gt; 是文件读写，&lt;code&gt;report&lt;/code&gt; 是往一个 &lt;code&gt;Report&lt;/code&gt; 对象里面写，&lt;code&gt;array&lt;/code&gt; 就更有意思啦，可以往一个数组里面写，也就是把日志暂存在内存里面。不过我们再次看到，关键的关键是 &lt;code&gt;handle&lt;/code&gt; 方法，我们实现 &lt;code&gt;handle&lt;/code&gt; 方法就好。&lt;/p&gt;

&lt;p&gt;到了这里，我们不得不提一下实现这个需求的另一个工具：&lt;a href="http://logio.org" rel="nofollow" target="_blank" title=""&gt;Log.io&lt;/a&gt;，这是一个基于 Node.js 和 socket.io 的“实时日志监控系统”。&lt;/p&gt;

&lt;p&gt;Log.io 的部署和使用都很简单，如果要发送一条日志，那么先通过 TCP 套接字连接上服务器，然后按照下面的格式发送消息：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;my_stream&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;my_node&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt; &lt;span class="n"&gt;is&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;\&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;\&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以我们的 &lt;code&gt;handle&lt;/code&gt; 方法将日志信息按格式写入这样的一个 TCP 连接即可，我实现的代码大致入戏下：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;
&lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Util&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newdesttype&lt;/span&gt; &lt;span class="ss"&gt;:logio&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;

  &lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'socket'&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msgstr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@connection.write&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;msgstr&lt;/span&gt;&lt;span class="si"&gt;}#{&lt;/span&gt;&lt;span class="no"&gt;ENDLINE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;send_message&lt;/span&gt; &lt;span class="n"&gt;build_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_log_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;filter_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="s2"&gt;"+log|&lt;/span&gt;&lt;span class="si"&gt;#@stream&lt;/span&gt;&lt;span class="s2"&gt;|&lt;/span&gt;&lt;span class="si"&gt;#@node&lt;/span&gt;&lt;span class="s2"&gt;|&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;level&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;|[&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;source&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;level&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;send_log&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_log_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time_format&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# Maybe you wanto filter some message such as password stuff?&lt;/span&gt;
  &lt;span class="c1"&gt;# This method should have type Puppet::Util::Log -&amp;gt; String&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;filter_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;msgstr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;
    &lt;span class="c1"&gt;# all another filter methd should have type String -&amp;gt; String&lt;/span&gt;
    &lt;span class="n"&gt;escape_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msgstr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;escape_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msgstr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;msgstr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ENDLINE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;NEWLINE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_close&lt;/span&gt;
    &lt;span class="n"&gt;send_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;build_close_message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_close_message&lt;/span&gt;
    &lt;span class="s2"&gt;"-node|&lt;/span&gt;&lt;span class="si"&gt;#@node&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;close&lt;/span&gt;
    &lt;span class="n"&gt;send_close&lt;/span&gt;
    &lt;span class="n"&gt;close_connection&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;close_connection&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@connection&lt;/span&gt;
      &lt;span class="vi"&gt;@connection.close&lt;/span&gt;
      &lt;span class="vi"&gt;@connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;函数拆分得有点散，主要是为了实现复用和扩展&lt;/strong&gt;，这里说一下我的考虑：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;由于 Log.io 支持几种不同的消息（比如，发送日志、流的注册、节点退订等），所以在我的实现里，最基本的语义是“发送一条 Log.io 消息”，Log.io 消息被定义为“&lt;strong&gt;一条字符串 + 结束符\r\n&lt;/strong&gt;”，发送消息由 &lt;code&gt;send_message&lt;/code&gt; 方法实现；&lt;/li&gt;
&lt;li&gt;发送日志，则交由 &lt;code&gt;build_log&lt;/code&gt; 方法构建一条格式化的日志，考虑到可能想要过滤一些敏感信息，因此定义了一个 &lt;code&gt;filter_message&lt;/code&gt; 方法，来实现敏感信息的过滤。现在这个方法中只是调用了 &lt;code&gt;escape_message&lt;/code&gt; 方法，将日志中出现的 &lt;code&gt;\r\n&lt;/code&gt; 替换为 &lt;code&gt;\n&lt;/code&gt;，避免系统错误地将一条日志分为多条（我感觉这个设定是没有必要的……），后期可以根据需要添加过滤方法，过滤在日志系统中出现的敏感信息（文章开头的那个图，我就是定义了一个 &lt;code&gt;filter_domain&lt;/code&gt; 方法，将所有的域名信息都替换为 &lt;code&gt;** FILTERED **&lt;/code&gt;）；&lt;/li&gt;
&lt;li&gt;可以通过覆盖 &lt;code&gt;get_log_time&lt;/code&gt; 方法来自定义日志时间的格式化，不过这个的变动似乎不大；&lt;/li&gt;
&lt;li&gt;最后是关闭 TCP 连接，在实际关闭之前，调用 &lt;code&gt;send_close&lt;/code&gt; 方法，向服务器发送一个节点退订的消息（似乎发不发影响都不大？）；&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;下面说下初始化的问题。&lt;/p&gt;

&lt;p&gt;我们定义每个日志输出目的地时，使用的是 &lt;code&gt;Puppet::Util::Log.newdesttype&lt;/code&gt; 方法，这个方法用到了 Ruby 元编程的技巧，用来动态地创建一个类；&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newdesttype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;dest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;genclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;:parent&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Util&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Destination&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;:prefix&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"Dest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;:block&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;:hash&lt;/span&gt;       &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="vi"&gt;@desttypes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;:attributes&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;dest&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

 &lt;span class="c1"&gt;# Create a new log destination.&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newdestination&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# 有意省略了部分代码&lt;/span&gt;
  &lt;span class="k"&gt;begin&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:initialize&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;arity&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
      &lt;span class="vi"&gt;@destinations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="vi"&gt;@destinations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="c1"&gt;# 有意省略了部分代码&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意到 &lt;code&gt;if type.instance_method(:initialize).arity == 1&lt;/code&gt; 这句代码，会检查我们自定义目的地类的 &lt;code&gt;initialize&lt;/code&gt; 方法的参数数目，&lt;code&gt;initialize&lt;/code&gt; 方法要么是单参的，要么是无参的。单参的时候，传递过来的是一个对象，也就是我们通过 &lt;code&gt;newdesttype&lt;/code&gt; 只是一个中介，最终的目的地是输出化时传递给 &lt;code&gt;initialize&lt;/code&gt; 方法的那个对象。要理解这个设定比较复杂，我们就值考虑 &lt;code&gt;initialize&lt;/code&gt; 方法是无参时候的情况，这时候，我们定义的 &lt;code&gt;newdesttype&lt;/code&gt; 就应该是实际的目的地了。&lt;/p&gt;

&lt;p&gt;我的实现中是这样实现的&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Util&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newdesttype&lt;/span&gt; &lt;span class="ss"&gt;:logio&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;

  &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:node&lt;/span&gt;

  &lt;span class="no"&gt;DEFAULT_SERVER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"logio"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;
  &lt;span class="no"&gt;DEFAULT_PORT&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;28777&lt;/span&gt;
  &lt;span class="no"&gt;DEFAULT_STREAM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"agent"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;

  &lt;span class="vi"&gt;@time_format&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"%Y-%m-%d %H:%M:%S %z"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;

  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;
    &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:time_format&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;time_format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="vi"&gt;@time_format&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;
    &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt; &lt;span class="ss"&gt;:agent&lt;/span&gt;
    &lt;span class="n"&gt;setup_log&lt;/span&gt; &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:logio_stream&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:logio_node&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;setup_connection&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setup_connection&lt;/span&gt;
    &lt;span class="n"&gt;server&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:logio_server&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="no"&gt;DEFAULT_SERVER&lt;/span&gt;
    &lt;span class="n"&gt;port&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:logio_port&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="no"&gt;DEFAULT_PORT&lt;/span&gt;
    &lt;span class="vi"&gt;@connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;TCPSocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setup_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="no"&gt;DEFAULT_STREAM&lt;/span&gt;
    &lt;span class="vi"&gt;@node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:certname&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;稍微解释一下我的考虑：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;由于 Log.io 发送日志消息的时候有几个必要的要素：节点名、Stream 名、日志信息，而为了发这么一条日志，我们还需要知道 Log.io 的主机名、端口号等信息，现在我们没办法从参数中取得这样的信息，那么我们只能求助于 &lt;code&gt;puppet.conf&lt;/code&gt; 文件（这样做带来的结果就是给用户有带来了更大的灵活性）&lt;/li&gt;
&lt;li&gt;默认的 Log.io 主机名为 &lt;code&gt;logio&lt;/code&gt; ，这样也可以通过改 Hosts 文件来实现 Log.io 主机的定位&lt;/li&gt;
&lt;li&gt;提供了一个 &lt;code&gt;setup_log&lt;/code&gt; 方法，这样程序员还有机会设置发送日志的节点名和订阅的 Stream 名&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;为了在 &lt;code&gt;puppet.conf&lt;/code&gt; 文件中启用这些配置项，我们还需要在 &lt;code&gt;defaults.rb&lt;/code&gt; 文件中加入下面的代码，这些代码是自注释的（你一看就知道大概是啥意思了）：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Puppet&lt;/span&gt;

  &lt;span class="n"&gt;define_settings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="ss"&gt;:logio&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                      &lt;span class="ss"&gt;:default&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="ss"&gt;:type&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="ss"&gt;:desc&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Whether to use Log.io to record live log for each resource.'&lt;/span&gt;
                  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;define_settings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="ss"&gt;:logio_server&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                      &lt;span class="ss"&gt;:default&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"logio"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="ss"&gt;:type&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="ss"&gt;:desc&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'The domain or hostname or the ip address of Log.io server.'&lt;/span&gt;
                  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;define_settings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="ss"&gt;:logio_port&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                      &lt;span class="ss"&gt;:default&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;28777&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="ss"&gt;:desc&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'The port of Log.io server.'&lt;/span&gt;
                  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;define_settings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="ss"&gt;:logio_node&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                      &lt;span class="ss"&gt;:default&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"$certname"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="ss"&gt;:type&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="ss"&gt;:desc&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"The node name which used to identify the host on Log.io"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;define_settings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="ss"&gt;:logio_stream&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                      &lt;span class="ss"&gt;:default&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="ss"&gt;:type&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:array&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="ss"&gt;:desc&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"On which stream this host push to, may be a single stream or a group of streams
                     split by ',' symbol."&lt;/span&gt;
                  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="转：怎么使用呢？"&gt;转：怎么使用呢？&lt;/h2&gt;
&lt;p&gt;这个需要根据具体需求具体判断。因为我们只关心资源应用阶段的日志，所以我把它放到了 &lt;code&gt;lib/puppet/configurer.rb&lt;/code&gt; 文件里面的 &lt;code&gt;run_interal&lt;/code&gt; 方法中，读者可以看到有一行形如 &lt;code&gt;Puppet::Util::Log.newdestination(report)&lt;/code&gt; 地代码，而我们只加了一行 &lt;code&gt;Puppet::Util::Log.newdestination(:logio)&lt;/code&gt; 。如果你关心的是整个启动过程，那么你可以把这行代码加在尽可能早的地方。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Puppet::Configurer&lt;/span&gt;
  &lt;span class="c1"&gt;# 有意省略了部分代码&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_internal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# We create the report pre-populated with default settings for&lt;/span&gt;
    &lt;span class="c1"&gt;# environment and transaction_uuid very early, this is to ensure&lt;/span&gt;
    &lt;span class="c1"&gt;# they are sent regardless of any catalog compilation failures or&lt;/span&gt;
    &lt;span class="c1"&gt;# exceptions.&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:report&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Transaction&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"apply"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@environment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@transaction_uuid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:report&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;init_storage&lt;/span&gt;

    &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Util&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newdestination&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Util&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newdestination&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:logio&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# &amp;lt;-- 使用我们定义的日志目的地   &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为什么我要把这节放到“转”呢？实际上，我并不满意这个扩展。在之前我通过编写 gem 来扩展 Puppet，我甚至没有修改一行 Puppet 源码！然而这次的扩展，我们不得不修改 &lt;code&gt;run_internal&lt;/code&gt; 的源码，添加一行代码。这让我感觉很不爽。&lt;/p&gt;

&lt;p&gt;Puppet 有一个 &lt;code&gt;Plugin Hook&lt;/code&gt; 机制，它会尝试搜索一个 &lt;code&gt;plugin_init.rb&lt;/code&gt; 的文件，加载并在适当的实际，调用其中的钩子方法（碍于时间关系，这里就不深入讨论了，有兴趣的同学可以读下 &lt;code&gt;lib/puppet/application.rb&lt;/code&gt; 文件中 &lt;code&gt;plugin_hook&lt;/code&gt; 方法的源码）。我们似乎可以可以通过在在这个文件中加点 Trick，以实现在不修改 Puppet 源码的基础上，实现我们的功能。但是 Puppet 搜索这个文件的机制有点操蛋，它只搜索 &lt;code&gt;$LOAD_PATH&lt;/code&gt; ，并不会搜索所有安装的 gems 的路径还有编写的 Puppet 模块的路径，导致我们的这个想法也随之告吹。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;所以，如果哪位对 Puppet 有更深入研究的同学知道如何更绿色地扩展这个需求，一定要让我知道！！！&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;碍于时间关系，这篇文章并没有条理清晰地讲解我们整个思考过程，也没有像之前系列文章那样，仔细地考察 Puppet 的底层原理，但它描绘了一个骨架，一种可行性。有智慧的人总能找到他所需要的东西！&lt;/p&gt;

&lt;p&gt;对于 Puppet 日志系统，似乎有很多可以改进的地方，比如可以将日志记录派发到一个后台队列，异步地执行（尤其是涉及到网络操作的日志系统），这样对于有多个日志目的地的系统，性能提升还是很明显的。&lt;/p&gt;

&lt;p&gt;我们这个简单的解决方案还有很多要改进的地方，比如：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Puppet 本身有个 HTTP 连接缓存池，不过它是构建在应用层的，我们这里用到的是 TCP 连接，处在更加底层的传输层。我们可以模仿 Puppet HTTP 连接缓存池的实现，自己实现一个 TCP 连接的缓存池；&lt;/li&gt;
&lt;li&gt;错误处理。目前我们的实现没有考虑网络通信的错误处理，不是很健壮。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;目前相关项目已通过公司开源项目开源流程和对外披露流程，具体代码可以在以下 Repo 找到：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/DeathKing/puppet-destlogio" rel="nofollow" target="_blank"&gt;https://github.com/DeathKing/puppet-destlogio&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>deathking</author>
      <pubDate>Mon, 21 Sep 2015 13:47:20 +0800</pubDate>
      <link>https://ruby-china.org/topics/27420</link>
      <guid>https://ruby-china.org/topics/27420</guid>
    </item>
    <item>
      <title>Puppet Hacking Guide —— Puppet 的启动：守护进程</title>
      <description>&lt;h2 id="Acknowledgment"&gt;Acknowledgment&lt;/h2&gt;
&lt;p&gt;本系列文集是在我受雇于阿里巴巴期间撰写的一系列技术文档重新整理而成，其版权属于阿里巴巴公司以及我本人。经雇主同意，现特许以技术交流为目的，在开源技术社区分享此文集。&lt;/p&gt;

&lt;p&gt;因此您可以：在保留原作者 DeathKing 以及阿里巴巴 - 技术保障部署名的情况下，以学习交流为目的，以非盈的形式将本文以电子版或印刷版的形式分发给您的朋友，或者转载到任何一个开源社区；&lt;/p&gt;

&lt;p&gt;以下行为是禁止的：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;以盈利为目的，将文章转载到微信公众号等媒体平台；&lt;/li&gt;
&lt;li&gt;去掉原作者 DeathKing 以及阿里巴巴 - 技术保障部的署名，以自己的名义发布本文集；&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;请在转载时，保留以下署名：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;本系列文章作者 DeathKing&lt;/li&gt;
&lt;li&gt;阿里巴巴技术保障部&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2015/a98441415c78969f2670817758102a37.png" title="" alt=""&gt;&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;在默认的工作流中，Puppet Agent 会以守护进程的方式运行，每 30 分钟与 Puppet Master 同步一次。为了方面交互式测试，可以在调用 Puppet 时使用 &lt;code&gt;--no-daemonize&lt;/code&gt; 选项，这样 Puppet 则会阻止程序的后台化，日志信息会全部输出在终端中。&lt;/p&gt;

&lt;p&gt;我们自然而然地会产生如下的疑问：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Puppet 是如何实现进程的后台化的？&lt;/li&gt;
&lt;li&gt;调度又是体现在何处？&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;本文就将解答以上问题。理解后台进程的原理，将有助于我们认识 Puppet Agent 和 Master 的工作原理。&lt;/p&gt;
&lt;h2 id="Daemon: 可后台化进程"&gt;Daemon: 可后台化进程&lt;/h2&gt;
&lt;p&gt;为了避免长时间占据系统前台，周期性的任务或者网络服务器都应该实现为守护进程，放入系统后台执行。Puppet 提供的 &lt;code&gt;Puppet::Daemon&lt;/code&gt; 类实现了&lt;strong&gt;可后台化（daemonized）进程&lt;/strong&gt;。我们把这些在后台执行的、并不直接被用户操控的进程称为&lt;strong&gt;守护进程（Daemon Process）&lt;/strong&gt;。我们将 &lt;code&gt;Daemon&lt;/code&gt; 类实现的进程称为是“可后台化的”，是因为程序员可以根据需要选择是否将这个进程放入后台执行，而并非强制将进程放入后台执行。&lt;/p&gt;

&lt;p&gt;一个守护进程必须有：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;一个&lt;strong&gt;配置重解析器（Reparser）&lt;/strong&gt;，它需要能够重新解析配置文件并应用到系统，以应对配置文件或清单文件的修改；&lt;/li&gt;
&lt;li&gt;（要么有）能够响应 &lt;code&gt;run&lt;/code&gt; 方法的&lt;strong&gt;代理（Agent）&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;（要么有）能够响应 &lt;code&gt;stop&lt;/code&gt;、&lt;code&gt;start&lt;/code&gt; 和 &lt;code&gt;wait_for_shutdown&lt;/code&gt; 方法的&lt;strong&gt;服务器（Server）&lt;/strong&gt;；&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;需要强调的是，必须至少为守护进程配置一个代理或服务器，否则 Puppet 会抛出 &lt;code&gt;Puppet::DevError&lt;/code&gt; 异常。同时，这里的代理指的是可以被守护进程调用并自主执行的对象（可以将此处的 Agent 非正式地理解为实际的业务代码），而并非 Puppet Agent——后者是一个 Puppet 子命令程序，请读者仔细甄别两者。&lt;/p&gt;

&lt;p&gt;代理和配置重解析器都可以按照配置文件中的设定，周期性地运行（&lt;code&gt;Puppet[:filetimeout]&lt;/code&gt;）。考虑到在代码的执行过程中，配置文件会发生改变。因此配置解析器会在每次运行时重新解析配置文件，以更新其自身和代理的运行周期。&lt;/p&gt;

&lt;p&gt;守护进程会调用 &lt;code&gt;server.start&lt;/code&gt; 来启动服务器，但服务器应该自己管理运行循环（run loop），以避免阻塞守护进程的运行。同时，服务器需要有一个 &lt;code&gt;wait_for_shutdown&lt;/code&gt; 方法来等待线程的结束。&lt;/p&gt;
&lt;h3 id="Daemon 的层次观"&gt;Daemon 的层次观&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Daemon&lt;/code&gt; 的实现用了许多基本组件，Puppet 按照一定的层次组织了这些组件： &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Puppet 子命令应用程序可能会拥有一个守护进程，而守护进程的代码实体可能是一个代理或网络服务器（或者两者都有）；&lt;/li&gt;
&lt;li&gt;守护进程用 &lt;code&gt;Pidlock&lt;/code&gt; 来管理 PID 文件，&lt;code&gt;Pidlock&lt;/code&gt; 更底层的实现是 &lt;code&gt;Lockfile&lt;/code&gt; 类；&lt;/li&gt;
&lt;li&gt;守护进程还有一个&lt;strong&gt;调度器（Scheduler）&lt;/strong&gt;，该调度器以&lt;strong&gt;作业（Job）&lt;/strong&gt;为基本单位，调度程序运行；&lt;/li&gt;
&lt;li&gt;调度器有个&lt;strong&gt;计时器（Timer）&lt;/strong&gt;，调度器根据计时器的时间戳来检查作业的调度；&lt;/li&gt;
&lt;li&gt;每个作业负责启动代理或配置重解析器的执行； &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;请注意第 1 点，对于 Puppet Agent 来说，它可能会为守护进程设置一个 &lt;code&gt;agent&lt;/code&gt; ，该代理执行的是实际与 Puppet 通信的业务代码；而对于 Puppet Master 来说，它可能会给一个守护进程设置一个 &lt;code&gt;server&lt;/code&gt; ，以提供 HTTP API 服务，使得 Puppet Agent 能够与之通信。对于这两种分别单独设置 &lt;code&gt;agent&lt;/code&gt; 或 &lt;code&gt;server&lt;/code&gt; 的情况，似乎都很容易理解，那么有没有同时设置 &lt;code&gt;agent&lt;/code&gt; 和 &lt;code&gt;server&lt;/code&gt; 的情况呢？&lt;/p&gt;

&lt;p&gt;答案是肯定的。Puppet 3.X 版本中提供了一个 &lt;code&gt;puppet kick&lt;/code&gt; 命令，允许用户远程触发 Puppet Agent 的同步。其具体实现就是，在为 Puppet Agent 的后台进程设置 &lt;code&gt;agent&lt;/code&gt; 的同时也设置 &lt;code&gt;server&lt;/code&gt; ，从而实现对远程命令的监听。但是这个功能在 Puppet 4.X 以后就被取消了。&lt;/p&gt;

&lt;p&gt;下图是一个非正式地 UML 图，它描绘了 Daemon 各组件之间的层次观：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2015/6a64381b2fefdc863707eeac989fab12.png" title="" alt=""&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;h3 id="注意"&gt;注意&lt;/h3&gt;
&lt;p&gt;希望 Java 背景的读者注意到这样一个事实：Ruby 中并没有&lt;strong&gt;接口（Interface）&lt;/strong&gt;这一说法，取而代之的是所谓的“鸭子类型”，或者说“面向协议”编程。在本例中，&lt;code&gt;Daemon&lt;/code&gt; 类对于代理和服务器有特定的要求，如果按照 Java 程序员的观点，可以理解为我们有 &lt;code&gt;Agent&lt;/code&gt; 和 &lt;code&gt;Server&lt;/code&gt; 两个抽象类，前者包含抽象方法 &lt;code&gt;run()&lt;/code&gt; ，而后者包含抽象方法 &lt;code&gt;start()&lt;/code&gt;、&lt;code&gt;stop()&lt;/code&gt;、&lt;code&gt;wait_for_shutdown()&lt;/code&gt;，Puppet 要求传递给 &lt;code&gt;Daemon&lt;/code&gt; 的代理或服务器需要分别实现这两个接口。&lt;br&gt;
虽然我们的 UML 图是按照 Java 的观点来绘制的，但是 Ruby 并不要求有什么继承或实现关系，只要对象具有特定的方法即可。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="Daemon 的数据结构及初始化"&gt;Daemon 的数据结构及初始化&lt;/h3&gt;&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th style="text-align:center;"&gt;实例变量&lt;/th&gt;
&lt;th style="text-align:center;"&gt;类&lt;/th&gt;
&lt;th style="text-align:center;"&gt;初始值&lt;/th&gt;
&lt;th style="text-align:center;"&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;&lt;a href="/scheduler" class="user-mention" title="@scheduler"&gt;&lt;i&gt;@&lt;/i&gt;scheduler&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align:center;"&gt;Puppet::Scheduler::Scheduler&lt;/td&gt;
&lt;td style="text-align:center;"&gt;Puppet::Scheduler::Scheduler.new&lt;/td&gt;
&lt;td style="text-align:center;"&gt;任务调度器。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;&lt;a href="/pidfile" class="user-mention" title="@pidfile"&gt;&lt;i&gt;@&lt;/i&gt;pidfile&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align:center;"&gt;Puppet::Util::Pidlock&lt;/td&gt;
&lt;td style="text-align:center;"&gt;由参数传递&lt;/td&gt;
&lt;td style="text-align:center;"&gt;PID 文件。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;&lt;a href="/agent" class="user-mention" title="@agent"&gt;&lt;i&gt;@&lt;/i&gt;agent&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align:center;"&gt;{#run}&lt;/td&gt;
&lt;td style="text-align:center;"&gt;nil&lt;/td&gt;
&lt;td style="text-align:center;"&gt;代理。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;&lt;a href="/server" class="user-mention" title="@server"&gt;&lt;i&gt;@&lt;/i&gt;server&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align:center;"&gt;{#stop, #start, #wait_for_shutdown}&lt;/td&gt;
&lt;td style="text-align:center;"&gt;nil&lt;/td&gt;
&lt;td style="text-align:center;"&gt;网络服务器。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;&lt;a href="/argv" class="user-mention" title="@argv"&gt;&lt;i&gt;@&lt;/i&gt;argv&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align:center;"&gt;Array&lt;/td&gt;
&lt;td style="text-align:center;"&gt;nil&lt;/td&gt;
&lt;td style="text-align:center;"&gt;命令行参数。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;需要注意的是，&lt;code&gt;Daemon&lt;/code&gt; 的初始化只完成了对 &lt;code&gt;@pidfile&lt;/code&gt; 和 &lt;code&gt;@scheduler&lt;/code&gt; 的设置，&lt;code&gt;@agent&lt;/code&gt; 、&lt;code&gt;@server&lt;/code&gt; 和 &lt;code&gt;@argv&lt;/code&gt; 的设置由相应的 setter 方法完成。&lt;/p&gt;
&lt;h3 id="daemonize：进程后台化"&gt;daemonize：进程后台化&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;文件：&lt;code&gt;lib/puppet/daemon.rb&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;daemonize&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fork&lt;/span&gt;
    &lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;create_pidfile&lt;/span&gt;

  &lt;span class="c1"&gt;# Get rid of console logging&lt;/span&gt;
  &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Util&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:console&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setsid&lt;/span&gt;
  &lt;span class="no"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;close_streams&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Puppet 通过 &lt;code&gt;fork&lt;/code&gt;、&lt;code&gt;detach&lt;/code&gt; 方法的组合来实现进程的后台化。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;fork&lt;/code&gt; 方法为当前进程创建一个子进程，子进程是父进程的副本，它将获得父进程数据空间、堆、栈等资源的副本。如果不是以传递代码块的方式调用 &lt;code&gt;fork&lt;/code&gt; 方法，那么 &lt;code&gt;fork&lt;/code&gt; 会返回两次，在父进程中，&lt;code&gt;fork&lt;/code&gt; 方法返回子进程的进程 ID 号，而在子进程中，&lt;code&gt;fork&lt;/code&gt; 返回 &lt;code&gt;nil&lt;/code&gt; 。&lt;/p&gt;

&lt;p&gt;当子进程退出以后，某些操作系统仍然会为其维护一个包含了其退出码的数据结构，如果父进程不通过 &lt;code&gt;wait()&lt;/code&gt; 及其变种系统调用来收集这些退出状态的话，操作系统中将产生大量的&lt;strong&gt;僵尸进程（Zombie Process）&lt;/strong&gt;。当我们不想显式地等待子进程结束时，可以使用 &lt;code&gt;Process::detach&lt;/code&gt; 方法创建一个单独的 Ruby 线程，用来收集子进程的退出码。&lt;/p&gt;

&lt;p&gt;父进程调用 &lt;code&gt;exit(0)&lt;/code&gt; 结束自己的生命周期，因此 &lt;code&gt;if..end&lt;/code&gt; 语句之后的代码，都是由后台进程——也就是我们创建的子进程来执行的。&lt;code&gt;create_pidfile&lt;/code&gt; 方法用于为我们的守护进程创建 PID 文件并加锁，主要作用是保证在系统中只存在该守护进程的一个实例，同时也便于系统统一管理这些守护进程。&lt;/p&gt;

&lt;p&gt;后续的代码分别完成：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;不让日志输出到终端；&lt;/li&gt;
&lt;li&gt;将进程设置为新的会话的领头进程，并与其父进程的会话组和进程组脱离；&lt;/li&gt;
&lt;li&gt;将路径切换至根目录；&lt;/li&gt;
&lt;li&gt;关闭 &lt;code&gt;stdin&lt;/code&gt;、&lt;code&gt;stdout&lt;/code&gt; 和 &lt;code&gt;stderr&lt;/code&gt; 以完成进程的后台化；&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="start：守护进程的运转"&gt;start：守护进程的运转&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;start&lt;/code&gt; 方法用于启动整个守护进程的实际运转，该方法首先调用 &lt;code&gt;set_signal_traps&lt;/code&gt; 设定进程的信号处理，然后调用 &lt;code&gt;create_pidfile&lt;/code&gt; 方法创建 PID 文件。需要注意的是，用户可能会使用 &lt;code&gt;--no-daemonize&lt;/code&gt; 方法要求 Puppet 不要以守护进程的方式执行，因此虽然 &lt;code&gt;daemonize&lt;/code&gt; 方法定义了 PID 文件的创建与加锁，但它很可能没被调用，所以我们也要在 &lt;code&gt;start&lt;/code&gt; 方法里面再次调用 &lt;code&gt;create_pidfile&lt;/code&gt; 方法，这样可以确保进程的 PID 文件被创建且正确加锁。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;文件：&lt;code&gt;lib/puppet/daemon.rb&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;
  &lt;span class="n"&gt;set_signal_traps&lt;/span&gt;

  &lt;span class="n"&gt;create_pidfile&lt;/span&gt;

  &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DevError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Daemons must have an agent, server, or both"&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;

  &lt;span class="c1"&gt;# Start the listening server, if required.&lt;/span&gt;
  &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;

  &lt;span class="c1"&gt;# Finally, loop forever running events - or, at least, until we exit.&lt;/span&gt;
  &lt;span class="n"&gt;run_event_loop&lt;/span&gt;

  &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait_for_shutdown&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果没有为守护进程配置代理或服务器，那么 Puppet 抛出一个异常。Puppet 要求服务器的运行循环不能阻塞守护进程的运行，所以调用 &lt;code&gt;server.start&lt;/code&gt; 后，Puppet 进入守护进程的运行循环（&lt;code&gt;run_event_loop&lt;/code&gt;），该循环会一直占据主线程，直到执行完毕主动退出。最后，&lt;code&gt;server.wait_for_shutdown&lt;/code&gt; 会等待服务器的结束。&lt;/p&gt;
&lt;h3 id="run_event_loop：主运行循环"&gt;run_event_loop：主运行循环&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;文件：&lt;code&gt;lib/puppet/daemon.rb&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_event_loop&lt;/span&gt;
  &lt;span class="n"&gt;agent_run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_job&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:runinterval&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:splay&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:splaylimit&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c1"&gt;# Splay for the daemon is handled in the scheduler&lt;/span&gt;
    &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:splay&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;reparse_run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_job&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:filetimeout&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reparse_config_files&lt;/span&gt;
    &lt;span class="n"&gt;agent_run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:runinterval&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:filetimeout&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
      &lt;span class="n"&gt;reparse_run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disable&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;reparse_run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:filetimeout&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;reparse_run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disable&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:filetimeout&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="n"&gt;agent_run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disable&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;

  &lt;span class="vi"&gt;@scheduler.run_loop&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;reparse_run&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agent_run&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;守护进程的主运行循环通常会创建两个&lt;strong&gt;作业（Job）&lt;/strong&gt;，并周期性地调用它们：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;agent_run&lt;/code&gt;：负责调用代理的 &lt;code&gt;run&lt;/code&gt; 方法；&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reparse_run&lt;/code&gt;：负责重新解析配置文件，并更新 &lt;code&gt;agent_run&lt;/code&gt; 和 &lt;code&gt;reparse_run&lt;/code&gt; 的运行周期；&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这两个作业交由调度器 &lt;code&gt;@scheduler&lt;/code&gt; 调度，&lt;code&gt;@scheduler.run_loop&lt;/code&gt; 会不断调度传递过来的作业，直到所有作业都变成无效为止。&lt;/p&gt;

&lt;p&gt;这里需要提及一下 &lt;code&gt;SplayJob&lt;/code&gt; ，如果在配置文件中启动了 &lt;code&gt;splay&lt;/code&gt; 选项，那么 Puppet 在创建调度作业时，会则会 &lt;code&gt;SplayJob&lt;/code&gt; 作业。&lt;code&gt;SplayJob&lt;/code&gt; 作业在到达指定启动时间后，会随机延迟一定时间再启动，这是为了避免成千上万台 Puppet Agent 同时启动所带来的惊群效应——如此高的并发量，将对 Puppet Master 构成相当严峻的挑战！&lt;/p&gt;
&lt;h3 id="stop：守护进程的终止"&gt;stop：守护进程的终止&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;文件：&lt;code&gt;lib/puppet/daemon.rb&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:exit&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop!&lt;/span&gt;

  &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;

  &lt;span class="n"&gt;remove_pidfile&lt;/span&gt;

  &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Util&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close_all&lt;/span&gt;

  &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:exit&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;守护进程的终止比较容易理解，主要是：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;Puppet::Application.stop!&lt;/code&gt;：给当前运行的应用发送停止请求；&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;server.stop if server&lt;/code&gt;：如果启动了服务器，那么就将其停止；&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;remove_pidfile&lt;/code&gt;：解锁并移除进程的 PID 文件；&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Puppet::Util::Log.close_all&lt;/code&gt;：关闭所有的日志；&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;exit if args[:exit]&lt;/code&gt;：如果没有特别指明，那么退出进程；&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;需要说明的是，如果守护进程并不是放在后台执行的，此时守护进程由主线程执行。那么要在调用 &lt;code&gt;stop&lt;/code&gt; 时，需要置 &lt;code&gt;exit&lt;/code&gt; 一项为 &lt;code&gt;true&lt;/code&gt;，避免退出整个 Ruby 解释器；&lt;/p&gt;
&lt;h3 id="set_signal_traps：设置信号处理"&gt;set_signal_traps：设置信号处理&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;文件：&lt;code&gt;lib/puppet/daemon.rb&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_signal_traps&lt;/span&gt;
  &lt;span class="n"&gt;signals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:INT&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:stop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:TERM&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:stop&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;# extended signals not supported under windows&lt;/span&gt;
  &lt;span class="n"&gt;signals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="ss"&gt;:HUP&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:restart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:USR1&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:reload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:USR2&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:reopen_logs&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;features&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;microsoft_windows?&lt;/span&gt;
  &lt;span class="n"&gt;signals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="no"&gt;Signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notice&lt;/span&gt; &lt;span class="s2"&gt;"Caught &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;; calling &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
      &lt;span class="nb"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;set_signal_traps&lt;/code&gt; 方法调用 &lt;code&gt;Signal.trap&lt;/code&gt; 方法为一些主要的信号添加处理程序。Microsoft Windows 系统并不支持 &lt;code&gt;HUP&lt;/code&gt;、&lt;code&gt;USR1&lt;/code&gt; 和 &lt;code&gt;USR2&lt;/code&gt; 信号，所以对于 Windows 系统，不要处理处理信号。&lt;/p&gt;</description>
      <author>deathking</author>
      <pubDate>Mon, 21 Sep 2015 13:30:27 +0800</pubDate>
      <link>https://ruby-china.org/topics/27419</link>
      <guid>https://ruby-china.org/topics/27419</guid>
    </item>
    <item>
      <title>Puppet Hacking Guide —— Puppet 的启动：子命令</title>
      <description>&lt;blockquote&gt;
&lt;p&gt;“知己知彼，百战不殆。——《孙子·谋攻篇》”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;在阿里巴巴实习期间，由于各种机缘巧合，我开始专注于研读配置自动化管理软件 Puppet。这项工作持续了两个月，期间我在内网发布过多篇技术文章，详细地剖析 Puppet 的运行原理。业已实习完毕，所有的技术文档、演示幻灯以及部分实例源码，均已通过阿里巴巴对外数据披露备案，被允许向开源社区分享这些技术文档。社区曾给了我很多帮助，我想，现在是时候我向社区尽一些绵薄之力了。&lt;/p&gt;

&lt;p&gt;虽然没有把 Puppet 多达 10 万行的源码彻底地分析清楚，但大致的脉络已经理清。整个系列文集将会以《Puppet Hacking Guide》为总标题（致敬《Ruby Hacking Guide》），解释 Puppet 3.X 版本的内部运行原理，为用户定制 Puppet 提供指导。整个系列以我在阿里巴巴内部发表的文章为草稿，重新组织整理，以期能以一种清晰的思路引导读者理解 Puppet 源码。&lt;/p&gt;

&lt;p&gt;尽管本文几经修改，但笔者才疏学浅，难免有所纰漏，欢迎各位指正！另外，出于职业道德，我不能向各位透露以下信息，也请各位不要打听，见谅：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Puppet 在阿里巴巴内部的使用场景；&lt;/li&gt;
&lt;li&gt;阿里巴巴所使用的 Puppet 具体版本号；&lt;/li&gt;
&lt;li&gt;阿里巴巴所采用的 Puppet 体系架构；&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;另外，如果读者所在公司有定制 Puppet 的需求，可以联系我，我可以在研究需求后，给出一些可行的方案。&lt;/p&gt;
&lt;h2 id="Acknowledgment"&gt;Acknowledgment&lt;/h2&gt;
&lt;p&gt;本系列文集是在我受雇于阿里巴巴期间撰写的一系列技术文档重新整理而成，其版权属于阿里巴巴公司以及我本人。经雇主同意，现特许以技术交流为目的，在开源技术社区分享此文集。&lt;/p&gt;

&lt;p&gt;因此您可以：在保留原作者 DeathKing 以及阿里巴巴 - 技术保障部署名的情况下，以学习交流为目的，以非盈的形式将本文以电子版或印刷版的形式分发给您的朋友，或者转载到任何一个开源社区；&lt;/p&gt;

&lt;p&gt;以下行为是禁止的：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;以盈利为目的，将文章转载到微信公众号等媒体平台；&lt;/li&gt;
&lt;li&gt;去掉原作者 DeathKing 以及阿里巴巴 - 技术保障部的署名，以自己的名义发布本文集；&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;请在转载时，保留以下署名：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;本系列文章作者 DeathKing&lt;/li&gt;
&lt;li&gt;阿里巴巴技术保障部&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2015/a98441415c78969f2670817758102a37.png" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="Puppet 的启动：子命令"&gt;Puppet 的启动：子命令&lt;/h2&gt;&lt;h3 id="Puppet 的启动方式"&gt;Puppet 的启动方式&lt;/h3&gt;
&lt;p&gt;Puppet Agent 通常通过命令行触发，而 Puppet Master 即可以通过命令行方式触发，以 WEBrick 服务器模式运行，也可以通过设置 config.ru 文件，以 Rack 中间件的形式运行。&lt;/p&gt;

&lt;p&gt;下面的命令可以让 Puppet Master 以 WEBrick 服务器的模式启动，选项 &lt;code&gt;--no-daemonize&lt;/code&gt; 可以阻止 Master 的后台化，在测试的时候，我们通常添加一个 &lt;code&gt;--debug&lt;/code&gt; 选项用以设置日志等级，让 Puppet 显示更多有用的信息方便系统管理员进行调试：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;puppet master &lt;span class="nt"&gt;--no-daemonize&lt;/span&gt; &lt;span class="nt"&gt;--debug&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;下面的命令可以启动 Agent 使之与 Master 进行通信并完成一次完整的工作流。与默认的 Agent 与 Master 每 30 分钟同步一次不同，选项 &lt;code&gt;--onetime&lt;/code&gt; 使得 Agent 与 Master 只进行一次同步，完成后立即退出。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;puppet agent &lt;span class="nt"&gt;--no-daemonize&lt;/span&gt; &lt;span class="nt"&gt;--debug&lt;/span&gt; &lt;span class="nt"&gt;--onetime&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;要了解 Master 和 Agent 的启动过程，就需要先知道 Puppet 中子命令的概念，使用 &lt;code&gt;puppet help&lt;/code&gt; 可以查看 Puppet 的使用帮助：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;puppet &lt;span class="nb"&gt;help

&lt;/span&gt;Usage: puppet &amp;lt;subcommand&amp;gt; &lt;span class="o"&gt;[&lt;/span&gt;options] &amp;lt;action&amp;gt; &lt;span class="o"&gt;[&lt;/span&gt;options]

Available subcommands:

  agent             The puppet agent daemon
  apply             Apply Puppet manifests locally
  ca                Local Puppet Certificate Authority management.
  &lt;span class="c"&gt;# 有意省略了整个列表。&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;从使用帮助中，我们不难看出，跟在 &lt;code&gt;puppet&lt;/code&gt; 命令后的参数被称作&lt;strong&gt;子命令（subcommand）&lt;/strong&gt;。子命令通常对应了一个 Ruby 脚本文件或外部可执行文件。事实上，不单 &lt;code&gt;agent&lt;/code&gt; 和 &lt;code&gt;master&lt;/code&gt; 分别是一个 Puppet 子命令，连 &lt;code&gt;help&lt;/code&gt; 也是 Puppet 的一个子命令，对应的是 &lt;code&gt;lib/puppet/application/help.rb&lt;/code&gt; 文件。Puppet 将不同的功能模块抽象为子命令，并将这些子命令实现为不同的文件，这样实现和管理起来更为方便。&lt;/p&gt;
&lt;h3 id="子命令分类"&gt;子命令分类&lt;/h3&gt;
&lt;p&gt;Puppet 子命令可以分为四类：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;空子命令&lt;/strong&gt;：调用 Puppet 时，如果没有指定任何子命令，Puppet 则认为调用的是空子命令；&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;应用子命令&lt;/strong&gt;：存放在 &lt;code&gt;puppet/application&lt;/code&gt; 文件夹中的脚本所对应的命令。需要注意的是，这是一个相对路径，Puppet 会在多个路径中搜索该相对路径下的文件。这类子命令的典型的代表是 &lt;code&gt;agent&lt;/code&gt;、&lt;code&gt;master&lt;/code&gt; 和 &lt;code&gt;config&lt;/code&gt; 等；&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;外部子命令&lt;/strong&gt;：存放在环境变量 &lt;code&gt;PATH&lt;/code&gt; 所指示的文件夹中、以 &lt;code&gt;puppet-&lt;/code&gt; 开头的文件所对应的命令。例如，命令行调用 &lt;code&gt;puppet foo&lt;/code&gt; 会使 Puppet 在环境变量 &lt;code&gt;PATH&lt;/code&gt; 所指示的文件夹中搜寻名为 &lt;code&gt;puppet-foo&lt;/code&gt; 的可执行文件；&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;未知子命令&lt;/strong&gt;：不是上述命令的其他子命令，Puppet 都将其视作未知子命令；&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="子命令的查找与加载"&gt;子命令的查找与加载&lt;/h3&gt;
&lt;p&gt;以命令行调用 Puppet 时，实际执行的是 &lt;code&gt;bin\puppet&lt;/code&gt; 。作为整个系统的入口，这个文件却只有简单的几句代码：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;文件：&lt;code&gt;bin\puppet&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env ruby&lt;/span&gt;

&lt;span class="c1"&gt;# For security reasons, ensure that '.' is not on the load path&lt;/span&gt;
&lt;span class="c1"&gt;# This is primarily for 1.8.7 since 1.9.2+ doesn't put '.' on the load path&lt;/span&gt;
&lt;span class="vg"&gt;$LOAD_PATH&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt; &lt;span class="s1"&gt;'.'&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'puppet/util/command_line'&lt;/span&gt;
&lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Util&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CommandLine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;bin\puppet&lt;/code&gt; 主要实例化了一个 &lt;code&gt;Puppet::Util::CommandLine&lt;/code&gt; 对象，这个对象主要用于理清 Puppet 的调用信息：调用的是哪个命令、有哪些命令行选项等。在后面的章节中我们会发现，如果以 Rack 中间件的方式启动 Puppet Master，会用到一些 trick，这也是 &lt;code&gt;CommandLine&lt;/code&gt; 存在的原因。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CommandLine&lt;/code&gt; 对象实例化完毕后，&lt;code&gt;execute&lt;/code&gt; 方法被执行。在这个方法中，最重要的是 &lt;code&gt;find_subcommand.run&lt;/code&gt; 语句。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;文件：&lt;code&gt;lib/puppet/util/command_line.rb&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;
  &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit_on_fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"initialize global default settings"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initialize_settings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;setpriority&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:priority&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="n"&gt;find_subcommand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;CommandLine&lt;/code&gt; 类的私有方法 &lt;code&gt;find_command&lt;/code&gt; 是理解整个启动机制的关键点，Puppet 在这个方法中，通过一些规则确定子命令的分类，然后再调用子命令对应的类，加载对应的文件。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;文件：&lt;code&gt;lib/puppet/util/command_line.rb&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;
&lt;span class="kp"&gt;private&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_subcommand&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;subcommand_name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nil?&lt;/span&gt;
    &lt;span class="no"&gt;NilSubcommand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;available_application_names&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subcommand_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;ApplicationSubcommand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subcommand_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;path_to_subcommand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;external_subcommand&lt;/span&gt;
    &lt;span class="no"&gt;ExternalSubcommand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path_to_subcommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="no"&gt;UnknownSubcommand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subcommand_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;需要强调的是，Puppet 并不是根据命令的分类去查找文件，而是根据文件查找的结果，确定命令的分类，理解这一点很重要。在深入每句代码内部之前，我们应该对 &lt;code&gt;find_subcommand&lt;/code&gt; 方法的模式有个大概认知：查找方法，然后实例化一个对应类的对象。比较令人疑惑的是，用于实例化的参数中有一个 &lt;code&gt;self&lt;/code&gt; 。这里的 &lt;code&gt;self&lt;/code&gt; 就是 &lt;code&gt;CommandLine&lt;/code&gt; 对象，我们之前已经说过，&lt;code&gt;CommandLine&lt;/code&gt; 对象已经理清了命令行调用的信息，因此我们可以在每个 Subcommand 对象中，通过 &lt;code&gt;CommandLine&lt;/code&gt; 对象获得调用的命令行参数等信息。&lt;/p&gt;

&lt;p&gt;Puppet 的命令分类我们已经在前面描述了，接下来，我们将详细地讨论查找的规则。由于空子命令和未知子命令的代码比较简单，我们先行介绍，接着我们将介绍外部子命令，我们最后再来分析最为复杂的内部子命令。&lt;/p&gt;
&lt;h2 id="空命令与未知命令"&gt;空命令与未知命令&lt;/h2&gt;
&lt;p&gt;将这两个命令放在一起讨论，是因为在某种程度上，这两者是一致的，以至于 &lt;code&gt;UnknownSubcommand&lt;/code&gt; 是 &lt;code&gt;NilSubcommand&lt;/code&gt; 的子类。前面已经说过，如果调用 Puppet
时，没有指明任何子命令，则实例化 &lt;code&gt;NilSubcommand&lt;/code&gt; ，如果调用的子命令既不是内部子命令，又不是外部子命令，那么则实例化 &lt;code&gt;UnknownSubcommand&lt;/code&gt; 。两者的区别在于——考虑到&lt;code&gt;puppet --version&lt;/code&gt; 这样的输入也是合法的，&lt;code&gt;NilCommand&lt;/code&gt; 通常要处理 &lt;code&gt;-v&lt;/code&gt; 或 &lt;code&gt;--version&lt;/code&gt; 选项，即显示版本号。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;文件：&lt;code&gt;lib/puppet/util/command_line.rb&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NilSubcommand&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Util&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Colors&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command_line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@command_line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;command_line&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@command_line.args&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt; &lt;span class="s2"&gt;"--version"&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt; &lt;span class="s2"&gt;"-V"&lt;/span&gt;
      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;version&lt;/span&gt;
    &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="vi"&gt;@command_line.subcommand_name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nil?&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
      &lt;span class="c1"&gt;# If the subcommand is truly nil and there is an arg, it's an option; print out the invalid option message&lt;/span&gt;
      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;colorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:hred&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Error: Could not parse application options: invalid option: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"See 'puppet help' for help on available puppet subcommands"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# @api private&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UnknownSubcommand&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;NilSubcommand&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subcommand_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;command_line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@subcommand_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subcommand_name&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command_line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;colorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:hred&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Error: Unknown Puppet subcommand '&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@subcommand_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;
    &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="空对象模式"&gt;空对象模式&lt;/h3&gt;
&lt;p&gt;可能部分读者不太理解为什么要把空子命令和未知子命令抽象分别抽象为一个类，实际上，这里用到了一种称为“&lt;strong&gt;空对象模式（Null Object Pattern）&lt;/strong&gt;”的设计模式。在大多数地方，这个模式的定义非常暧昧，根据在此处的使用场景，也许按照下面的方式理解空对象模式会更好一些：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;空对象模式&lt;/strong&gt;&lt;br&gt;
将对缺失对象的错误处理，封装在一个用于表征缺失对象的类的一个方法中。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;你也许无法立马理解这个拗口的定义，让我们回过头来审视 &lt;code&gt;find_command&lt;/code&gt; 方法：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_subcommand&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;subcommand_name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nil?&lt;/span&gt;
     &lt;span class="no"&gt;NilSubcommand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="c1"&gt;# 有意省略了部分代码&lt;/span&gt;
   &lt;span class="k"&gt;else&lt;/span&gt;
     &lt;span class="no"&gt;UnknownSubcommand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subcommand_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;end&lt;/span&gt;
 &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;想一想，我们能不能把空子命令、未知子命令的处理逻辑放在 &lt;code&gt;if&lt;/code&gt; 子句、&lt;code&gt;else&lt;/code&gt; 子句中呢？更进一步地，我们考虑 &lt;code&gt;find_subcommand&lt;/code&gt; 方法的调用者，&lt;code&gt;find_subcommand.run&lt;/code&gt; ，我们是否可以写成这样的形式呢：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;subcommand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;find_subcommand&lt;/span&gt;
  &lt;span class="n"&gt;subcommand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
  &lt;span class="c1"&gt;# logic for object not found&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当然，从语法角度上来说，这些都是可以的！但请思考，这样做真的合适么？这样做真的是“面向对象设计”么？答案是否定的。请看下面的调用关系，并仔细思考：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2015/26e3eeb3efc3b6150d87befbb0979938.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;我们观察到：&lt;code&gt;find_subcommand&lt;/code&gt; 方法的语义是查找一个特定的命令，这个命令要能够响应 &lt;code&gt;run&lt;/code&gt; 方法。这样，当我们没有找到合适的子命令时，与其返回特殊值 &lt;code&gt;nil&lt;/code&gt; 用于表征“对象没找到”，不如返回一个“空对象”表示“嗨，我找到了这么一个对象，但这个对象是‘空的’”。虽然后者看起来是绕了一个圈子，但两者的重要区别在于：“空对象可以响应 &lt;code&gt;run&lt;/code&gt; 方法！因而，我们可以将错误处理的逻辑放在 &lt;code&gt;run&lt;/code&gt; 方法中。”&lt;/p&gt;

&lt;p&gt;下面这幅图演示了处理逻辑位置的变化，我们发现，处理逻辑被移动到了方法的内部。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2015/86cb5063e35f51a4ab46e7e9f0b9a264.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;空对象模式有什么好处呢？除了绕了一个圈子，我们似乎看不到任何一点好处。实际上，如果你只有一段这样的代码，是否使用了空对象模式都并没有太大的关系，但请考虑这段代码在系统的各个地方重复出现时的情景：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2015/ba23e8b6c3a91f70ba7676cd3f352119.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;重复（Duplication）是一件非常危险的事，这种违背 DRY 原则的做法，很容易导致系统的不一致性。如果我们要修改失败时的逻辑，那么我们又需要在系统各个地方复制 - 粘贴大量的代码，我们能保证所有的出现都被有效地更新了吗？而考虑使用空对象模式的情景，缺失处理的代码被移动到了一个方法中，这样使得处理逻辑只有一份副本，对方法所做的修改，可以一致地应用到系统全局。&lt;/p&gt;
&lt;h3 id="空对象模式的优势与劣势"&gt;空对象模式的优势与劣势&lt;/h3&gt;
&lt;p&gt;设计模式只是一种工具，并不是最终目的，我们不能为了用设计模式而用设计模式。严肃的工程师需要仔细分析这些工具的适用场景。对于空对象模式来说，它的优势在于：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;由于将逻辑封装在了一个方法中，由外部调用该方法，实践了 DRY 原则，极大程度上提高了代码的复用性；&lt;/li&gt;
&lt;li&gt;通过合理地拆分，降低了系统耦合度，使得业务逻辑更加清晰；&lt;/li&gt;
&lt;li&gt;由于可以在空对象的类中统一修改方法定义，避免了因为代码冗余而产生的不一致行为；&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;当然，空对象模式依然存在一些劣势：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;不适合用在上下文高度相关的场景，因为此时我们很难对系统进行合理地拆分解耦；&lt;/li&gt;
&lt;li&gt;可能会产生各种类型的空对象类，因此维护任务并没有消失，只是转移到对各个空对象类的维护上去了；&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;程序员在设计软件时，应该仔细取舍，合理地使用这些模式！&lt;/p&gt;
&lt;h3 id="后话"&gt;后话&lt;/h3&gt;
&lt;p&gt;理解空对象模式了么？它只是一种教条主义的繁文缛节么？如果我的解释并没有让读者信服，那么不妨聆听 Sandi Metz 在 RailsConf 2015 上的演讲 &lt;a href="https://www.youtube.com/watch?v=OMPfEXIlTVE" rel="nofollow" target="_blank" title=""&gt;Nothing is Something&lt;/a&gt; ，这一定会再次震撼你的！&lt;/p&gt;
&lt;h2 id="外部子命令"&gt;外部子命令&lt;/h2&gt;&lt;h3 id="查找规则"&gt;查找规则&lt;/h3&gt;
&lt;p&gt;外部子命令的查找由 &lt;code&gt;external_subcommand&lt;/code&gt; 方法实现，如果指定的外部子命令存在（我们说过，外部子命令是一个可执行文件），则返回该可执行文件的路径。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;文件：&lt;code&gt;lib/puppet/util/command_line.rb&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;
&lt;span class="kp"&gt;private&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_subcommand&lt;/span&gt;
  &lt;span class="c1"&gt;# 有意省略了部分代码&lt;/span&gt;
  &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;path_to_subcommand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;external_subcommand&lt;/span&gt;
    &lt;span class="no"&gt;ExternalSubcommand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path_to_subcommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# 有意省略了部分代码&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Puppet 会以 &lt;code&gt;puppet-&amp;lt;subcommand&amp;gt;&lt;/code&gt; 作为文件名，在 &lt;code&gt;PATH&lt;/code&gt; 环境变量中的指定的路径下进行查找。例如，当我们在命令行调用 &lt;code&gt;puppet foo&lt;/code&gt; 启动 Puppet 时，Puppet 会尝试在 &lt;code&gt;PATH&lt;/code&gt; 所对应的路径中，寻找名为 &lt;code&gt;puppet-foo&lt;/code&gt; 的可执行文件。对于 Windows 系统，Puppet 会尝试为 &lt;code&gt;puppet-foo&lt;/code&gt; 文件加上 &lt;code&gt;.COM&lt;/code&gt;、&lt;code&gt;.EXE&lt;/code&gt;、&lt;code&gt;.BAT&lt;/code&gt;、&lt;code&gt;.CMD&lt;/code&gt; 等 Windows 可执行文件的后缀名进行查找。&lt;/p&gt;

&lt;p&gt;外部命令路径的解析由 &lt;code&gt;Puppet::Util.which&lt;/code&gt; 方法提供。它实现了一个类似于 UNIX 系统中 &lt;code&gt;which&lt;/code&gt; 命令，可以传递给该方法一个绝对路径，或者命令名。如果找到该命令，则返回对应的绝对路径，否则抛出异常。&lt;/p&gt;
&lt;h3 id="执行"&gt;执行&lt;/h3&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;
  &lt;span class="no"&gt;Kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@path_to_subcommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="vi"&gt;@command_line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ruby 解释器不对外部命令的执行负责，直接调用 &lt;code&gt;Kernel.exec&lt;/code&gt;，用外部命令的进程替换掉当前 Puppet 的进程。&lt;/p&gt;
&lt;h3 id="小实验：用 Puppet 执行外部命令"&gt;小实验：用 Puppet 执行外部命令&lt;/h3&gt;
&lt;p&gt;首先，让我们为 &lt;code&gt;top&lt;/code&gt; 命令创建一个别名：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;which top
/usr/bin/top

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /usr/bin/top /usr/bin/puppet-top
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在，调用 &lt;code&gt;puppet top&lt;/code&gt; ，我们马上就进入了 &lt;code&gt;top&lt;/code&gt; 命令。这个小实验为扩展 Puppet 提供了一些思路，但考虑到执行外部命令时，Puppet 会让出控制权，因此这样的扩展是及其有限的。&lt;/p&gt;
&lt;h2 id="应用子命令"&gt;应用子命令&lt;/h2&gt;&lt;h3 id="查找规则"&gt;查找规则&lt;/h3&gt;
&lt;p&gt;在讨论完三种相对简单的子命令后，我们将关注的焦点移动到相对复杂的内部子命令中。首先，“应用子命令”可能不是一个很好的名字，也许叫做“内部子命令（InternalSubcommand）”更佳合适。这是因为 &lt;code&gt;ApplicationSubcommand&lt;/code&gt; 所对应的文件会被 Ruby 解释器加载，并在 Puppet 程序的上下文中继续执行——这与替换掉 Puppet 进程执行的外部子命令形成了鲜明对比。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;文件：&lt;code&gt;lib/puppet/util/command_line.rb&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;
&lt;span class="kp"&gt;private&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_subcommand&lt;/span&gt;
  &lt;span class="c1"&gt;# 有意省略了部分代码&lt;/span&gt;
  &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;available_application_names&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subcommand_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;ApplicationSubcommand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subcommand_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# 有意省略了部分代码&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;类方法 &lt;code&gt;Puppet::Application.avaliable_application_names&lt;/code&gt; 可以根据搜索查找到的文件，返回所有可以使用的子命令的名字构成的数组。如果 &lt;code&gt;&amp;lt;subcommand&amp;gt;&lt;/code&gt; 存在于这个数组中，那么它就被定义为一个应用子命令。下面是 &lt;code&gt;avaliable_application_names&lt;/code&gt; 方法的定义：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;文件：&lt;code&gt;lib/puppet/application.rb&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;
&lt;span class="vi"&gt;@loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Util&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Autoload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'puppet/application'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;# 有意省略了部分代码&lt;/span&gt;

  &lt;span class="c1"&gt;# @return [Array&amp;lt;String&amp;gt;] the names of available applications&lt;/span&gt;
  &lt;span class="c1"&gt;# @api public&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;available_application_names&lt;/span&gt;
    &lt;span class="vi"&gt;@loader.files_to_load&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'.rb'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uniq&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;puppet/application&lt;/code&gt; 文件夹下的脚本文件所对应的子命令都是应用命令，例如，子命令 &lt;code&gt;agent&lt;/code&gt; 就与 &lt;code&gt;puppet/application/agent.rb&lt;/code&gt; 文件相对应。需要注意的是，Puppet 利用了一个 &lt;code&gt;Puppet::Util::Autoload&lt;/code&gt; 类的对象 &lt;code&gt;@loader&lt;/code&gt; 来执行目录查找，而 &lt;code&gt;Autoload&lt;/code&gt; 对象的默认查找的路径包括：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;gem_directories&lt;/code&gt;：当前系统安装的所有 gem 的路径；&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;module_directories(env)&lt;/code&gt;：安装的所有 Puppet 模块的路径；&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;libdirs&lt;/code&gt;：Puppet &lt;code&gt;lib&lt;/code&gt; 文件夹的路径，默认值是 &lt;code&gt;$vardir/lib&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;$LOAD_PATH&lt;/code&gt;：Ruby 的文件加载路径；&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;理解应用子命令的查找规则后，我们开始考虑应用子命令的加载与执行。&lt;/p&gt;
&lt;h3 id="加载"&gt;加载&lt;/h3&gt;
&lt;p&gt;由于 &lt;code&gt;Autoload&lt;/code&gt; 类的设计，脚本文件的内容还需要满足一些约束：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;必须定义在 Puppet::Application 命名空间下；&lt;/li&gt;
&lt;li&gt;必须是 Puppet::Application 的子类；&lt;/li&gt;
&lt;li&gt;必须采用驼峰式大小写风格书写类名；&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;也就是说，应用子命令 &lt;code&gt;my_subcommand&lt;/code&gt; 应该对应：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;puppet/application/my_subcommand.rb&lt;/code&gt; 文件；&lt;/li&gt;
&lt;li&gt;文件中应该包含这样的类定义： &lt;code&gt;class Puppet::Application::MySubcommand &amp;lt; Puppet::Application&lt;/code&gt; ；&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;如果 &lt;code&gt;my_subcommand&lt;/code&gt; 对应的是一个有效的应用子命令，那么 Puppet 就使用一个 &lt;code&gt;ApplicationSubcommand&lt;/code&gt; 来加载它。&lt;code&gt;ApplicationSubcommand#run&lt;/code&gt; 方法会被调用，在此方法中，代码 &lt;code&gt;Puppet::Application.find&lt;/code&gt; 方法用于找到 &lt;code&gt;@subcommand_name&lt;/code&gt; 所对应的 Ruby 文件，加载该 Ruby 文件，并返回在该 Ruby 文件中定义的类。这样做之所以可行，是因为就像我们前面所解释的那样，文件名和文件内容之间需要满足一定的映射关系。类似的技巧在很多 Ruby 项目中使用。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;文件：&lt;code&gt;lib/puppet/util/command_line.rb&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ApplicationSubcommand&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;
    &lt;span class="c1"&gt;# 有意省略了部分代码&lt;/span&gt;

    &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@subcommand_name&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@command_line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Plugins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on_application_initialization&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:application_object&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="vi"&gt;@command_line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;随后的 &lt;code&gt;new&lt;/code&gt; 会实例化该类的一个对象，并且，Puppet 会将 &lt;code&gt;@command_line&lt;/code&gt; 作为参数传递给 &lt;code&gt;new&lt;/code&gt; 方法，这样，我们编写的子命令就可以通过解析 &lt;code&gt;@command_line&lt;/code&gt; 来处理命令行参数。&lt;/p&gt;

&lt;p&gt;实例化完成以后，加载过程完成，调用 &lt;code&gt;app.run&lt;/code&gt;，进入到子命令的执行。程序的控制权转移到 &lt;code&gt;Puppet::Application&lt;/code&gt; 类及其特定子类。&lt;/p&gt;
&lt;h3 id="执行"&gt;执行&lt;/h3&gt;
&lt;p&gt;Puppet 所有的应用命令都继承自 &lt;code&gt;Puppet::Application&lt;/code&gt; 类，而子类大多没有覆盖父类的 &lt;code&gt;run&lt;/code&gt; 方法，因此我们有必要考察一下 &lt;code&gt;Application&lt;/code&gt; 类是如何定义命令的执行的。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;文件：&lt;code&gt;lib/puppet/application.rb&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;

  &lt;span class="n"&gt;exit_on_fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"get application-specific default settings"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;plugin_hook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'initialize_app_defaults'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;initialize_app_defaults&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;base_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s2"&gt;"Update for application settings (&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_mode&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;configured_environment_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:environment&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_mode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="ss"&gt;:agent&lt;/span&gt;
    &lt;span class="n"&gt;configured_environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;configured_environment_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;configured_environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:environments&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;configured_environment_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="n"&gt;configured_environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;configured_environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;override_from_commandline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;# Setup a new context using the app's configuration&lt;/span&gt;
  &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push_context&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="ss"&gt;:current_environment&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;configured_environment&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                  &lt;span class="s2"&gt;"Update current environment from application's configuration"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'puppet/util/instrumentation'&lt;/span&gt;
  &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Util&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Instrumentation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;

  &lt;span class="n"&gt;exit_on_fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"initialize"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                                   &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;plugin_hook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'preinit'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;       &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;preinit&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;exit_on_fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"parse application options"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;plugin_hook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'parse_options'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;parse_options&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;exit_on_fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"prepare for execution"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;plugin_hook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'setup'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;         &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;exit_on_fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"configure routes from &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:route_file&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;configure_indirector_routes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;exit_on_fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"log runtime debug info"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                       &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;log_runtime_environment&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;exit_on_fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"run"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                                          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;plugin_hook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'run_command'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;run_command&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里用到了模板方法的模式，run 方法定义了子类方法执行的步骤，子类只需要完成或实现这些方法即可：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2015/cda3643d01dea38868be7b704a57a8f2.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;这里有必要明确地提一下 &lt;code&gt;exit_on_fail&lt;/code&gt; 这个方法，这个方法由模块 &lt;code&gt;Puppet::Util&lt;/code&gt; 提供。由于在顶层环境抛出异常会导致 Ruby 解释器退出，所以 Puppet 特意封装了 &lt;code&gt;exit_on_fail&lt;/code&gt; 这个方法。我们可以将想要执行的代码作为代码块传递给 &lt;code&gt;exit_on_fail&lt;/code&gt; ，后者会帮我们做异常处理。 &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;文件：&lt;code&gt;lib/puppet/util.rb&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Executes a block of code, wrapped with some special exception handling.  Causes the ruby interpreter to&lt;/span&gt;
&lt;span class="c1"&gt;#  exit if the block throws an exception.&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# @api public&lt;/span&gt;
&lt;span class="c1"&gt;# @param [String] message a message to log if the block fails&lt;/span&gt;
&lt;span class="c1"&gt;# @param [Integer] code the exit code that the ruby interpreter should return if the block fails&lt;/span&gt;
&lt;span class="c1"&gt;# @yield&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;exit_on_fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt;
&lt;span class="c1"&gt;# First, we need to check and see if we are catching a SystemExit error.  These will be raised&lt;/span&gt;
&lt;span class="c1"&gt;#  when we daemonize/fork, and they do not necessarily indicate a failure case.&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;SystemExit&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
  &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;

&lt;span class="c1"&gt;# Now we need to catch *any* other kind of exception, because we may be calling third-party&lt;/span&gt;
&lt;span class="c1"&gt;#  code (e.g. webrick), and we have no idea what they might throw.&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
  &lt;span class="c1"&gt;## NOTE: when debugging spec failures, these two lines can be very useful&lt;/span&gt;
  &lt;span class="c1"&gt;#puts err.inspect&lt;/span&gt;
  &lt;span class="c1"&gt;#puts Puppet::Util.pretty_backtrace(err.backtrace)&lt;/span&gt;
  &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log_exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Could not &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Util&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;force_flushqueue&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="kp"&gt;module_function&lt;/span&gt; &lt;span class="ss"&gt;:exit_on_fail&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;回到 &lt;code&gt;run&lt;/code&gt; 方法中来，在它定义的几个步骤中，除 &lt;code&gt;run_command&lt;/code&gt; 方法外，其它几个方法都在 &lt;code&gt;Application&lt;/code&gt; 中定义好了，子类只需要根据具体需求覆盖这些方法即可。而 &lt;code&gt;run_command&lt;/code&gt; 方法通过一些 Ruby 技巧，间接实现了 Java 中的抽象方法的效果——如果子类没有实现该方法，程序就会抛出异常——这意味着子类必须实现该方法。因此，我们需要尤为关注这个方法。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;
  &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;NotImplementedError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"No valid command or main"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_command&lt;/span&gt;
  &lt;span class="n"&gt;main&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;子类可以根据需要选择覆盖 &lt;code&gt;main&lt;/code&gt; 方法或 &lt;code&gt;run_command&lt;/code&gt; 方法实现实际的执行结果。&lt;/p&gt;
&lt;h3 id="小实验：以应用子命令的方式扩展 Puppet"&gt;小实验：以应用子命令的方式扩展 Puppet&lt;/h3&gt;
&lt;p&gt;在“查找规则”小节中，我们介绍 Puppet 会从四个路径搜索应用子命令，这为我们扩展 Puppet 提供给了无穷的想象力。在本节中，我们将简单介绍，如何通过编写 Ruby Gem 来实现一个 Puppet 应用子命令。&lt;/p&gt;

&lt;p&gt;首先，我们需要新建一个 Ruby Gem，我们可以利用 Bundler 来创建一个 Gem 骨架：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundler gem my_app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后新建 &lt;code&gt;lib/puppet/application&lt;/code&gt; 目录，并在该目录下新建 &lt;code&gt;my_app.rb&lt;/code&gt; 文件。作为示例，这里在文件中输入下面的内容，其中，&lt;code&gt;options&lt;/code&gt; 是在 &lt;code&gt;Application&lt;/code&gt; 类中定义的用于参数处理的方法，Puppet 的参数处理机制，我们会在后面的文章中讨论：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Puppet::Application::MyApp&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Application&lt;/span&gt;

  &lt;span class="n"&gt;option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"--from [NAME]"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:from&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;
    &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:from&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;"my customize application subcommmand"&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Greet from &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;."&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;打包并安装 Gem。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gem build my_app.gemspec
&lt;span class="nv"&gt;$ &lt;/span&gt;gem &lt;span class="nb"&gt;install &lt;/span&gt;my_app-0.1.0.gem
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这时候就可以尝试运行我们自定义的应用命令了！&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;puppet my_app &lt;span class="nt"&gt;--from&lt;/span&gt; me
Greet from me.

&lt;span class="nv"&gt;$ &lt;/span&gt;puppet my_app
Greet from my customize application subcommmand.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里只是演示了通过编写 Ruby Gem 来扩展 Puppet 应用子命令，之所以这样做，很大程度上是为了代码分发。实际上，我们还可以利用 Puppet 的插件同步（PluginSync）机制来实现类似的扩展。&lt;/p&gt;

&lt;p&gt;Puppet 对扩展友好，并预留了各种扩展方式。读者完全可以发挥想象力，利用 Ruby 的动态特性，通过继承等手段（合理使用猴子补丁也无妨），根据需要定制 Puppet！&lt;/p&gt;
&lt;h3 id="plugin_hook 模式"&gt;
&lt;code&gt;plugin_hook&lt;/code&gt; 模式&lt;/h3&gt;
&lt;p&gt;读者也许会注意到 &lt;code&gt;plugin_hook&lt;/code&gt; 这个方法，这个方法用到了许多 Ruby 黑魔法实现了一个非常灵活的效果。例如，考虑代码：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;文件：&lt;code&gt;lib/puppet/application.rb&lt;/code&gt;，有意重新编排了格式&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;exit_on_fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"initialize"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;plugin_hook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'preinit'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;preinit&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它完成了这样几个步骤：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;plugin_hook&lt;/code&gt; 会首先向 Puppet 所有已载入的插件广播，即，调用他们的 &lt;code&gt;before_application_preinit&lt;/code&gt; 方法。&lt;/li&gt;
&lt;li&gt;然后执行传递给它的 &lt;code&gt;preinit&lt;/code&gt; 方法，并记录该方法的返回值。&lt;/li&gt;
&lt;li&gt;此后，再调用所有插件的 &lt;code&gt;after_application_preinit&lt;/code&gt; 方法。&lt;/li&gt;
&lt;li&gt;最后，返回刚才调用的 &lt;code&gt;preinit&lt;/code&gt; 方法的返回值。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;对于有元编程经验的程序员来说，理解 &lt;code&gt;plugin_hook&lt;/code&gt; 方法的实现并不构成问题，它的实现代码如下：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;plugin_hook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Plugins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"before_application_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="ss"&gt;:application_object&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;
  &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Plugins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"after_application_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="ss"&gt;:application_object&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:return_value&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;x&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="kp"&gt;private&lt;/span&gt; &lt;span class="ss"&gt;:plugin_hook&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;需要说明的是，这个设计最初是为扩展 Puppet 而预留的，但实际上它并没有多大的用处。首先，Puppet 会在几个目录中搜索 &lt;code&gt;plugin_init.rb&lt;/code&gt; 文件，但初始搜索路径只有 &lt;code&gt;$LOAD_PATH&lt;/code&gt; ，这是非常有限的。其次，这个功能已经在 4.X 系统中被去掉了。&lt;/p&gt;
&lt;h2 id="结语"&gt;结语&lt;/h2&gt;
&lt;p&gt;在这篇文章中，我们讨论了 Puppet 的启动过程，了解了 Puppet 中四类命令的查找与加载。请读者仔细理解其中的运行机制，我们将在后面的章节中，继续介绍 Agent 和 Master 的启动过程。&lt;/p&gt;</description>
      <author>deathking</author>
      <pubDate>Wed, 16 Sep 2015 09:52:55 +0800</pubDate>
      <link>https://ruby-china.org/topics/27350</link>
      <guid>https://ruby-china.org/topics/27350</guid>
    </item>
    <item>
      <title>关于 OpenSSL 的 verify_callback 的疑惑</title>
      <description>&lt;p&gt;&lt;code&gt;verify_callback&lt;/code&gt; 指定的回调函数，是用于验证的，还是验证后处理的？下面的文档抄录自 OpenSSL 文档 &lt;a href="https://www.openssl.org/docs/ssl/SSL_CTX_set_verify.html" rel="nofollow" target="_blank" title=""&gt;set peer certificate verification parameters&lt;/a&gt; ，Ruby Doc 关于这个问题描述地不是很详细。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The actual verification procedure is performed either using the built-in verification procedure or using another application provided verification function set with SSL_CTX_set_cert_verify_callback. ...
The verify_callback function is used to control the behaviour when the SSL_VERIFY_PEER flag is set. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;如果是验证后处理的，但在读 Puppet 源码的时候，发现即使 &lt;code&gt;preverify_ok&lt;/code&gt; 为 &lt;code&gt;true&lt;/code&gt; 也要再检查 &lt;code&gt;peer&lt;/code&gt;。&lt;code&gt;preverify_ok = valid_peer?&lt;/code&gt; 不太明白为什么要这样做。&lt;/li&gt;
&lt;li&gt;如果是这个回调函数负责 &lt;code&gt;peer&lt;/code&gt; 的验证，那么这个 &lt;code&gt;preverify_ok&lt;/code&gt; 的值是怎么确定的？&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;preverify_ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;store_context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# We must make a copy since the scope of the store_context will be lost&lt;/span&gt;
  &lt;span class="c1"&gt;# across invocations of this method.&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;preverify_ok&lt;/span&gt;
    &lt;span class="n"&gt;current_cert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;store_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current_cert&lt;/span&gt;
    &lt;span class="vi"&gt;@peer_certs&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SSL&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Certificate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_instance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_cert&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# If we've copied all of the certs in the chain out of the SSL library&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@peer_certs.length&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;store_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;length&lt;/span&gt;
      &lt;span class="c1"&gt;# (#20027) The peer cert must be issued by a specific authority&lt;/span&gt;
      &lt;span class="n"&gt;preverify_ok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;valid_peer?&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;store_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;error_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;store_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error_string&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;"OpenSSL error &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="no"&gt;OpenSSL&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;X509&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;V_ERR_CRL_NOT_YET_VALID&lt;/span&gt;
      &lt;span class="c1"&gt;# current_crl can be nil&lt;/span&gt;
      &lt;span class="c1"&gt;# https://github.com/ruby/ruby/blob/ruby_1_9_3/ext/openssl/ossl_x509store.c#L501-L510&lt;/span&gt;
      &lt;span class="n"&gt;crl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;store_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current_crl&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;crl&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;crl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last_update&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;crl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last_update&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="no"&gt;FIVE_MINUTES_AS_SECONDS&lt;/span&gt;
          &lt;span class="no"&gt;Puppet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Ignoring CRL not yet valid, current time &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utc&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, CRL last updated &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;crl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last_update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utc&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;preverify_ok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;
          &lt;span class="vi"&gt;@verify_errors&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;error_string&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; for &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;crl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;issuer&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="vi"&gt;@verify_errors&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;error_string&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;current_cert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;store_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current_cert&lt;/span&gt;
      &lt;span class="vi"&gt;@verify_errors&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;error_string&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; for &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;current_cert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subject&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="n"&gt;preverify_ok&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;
  &lt;span class="vi"&gt;@verify_errors&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt;
  &lt;span class="kp"&gt;false&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;</description>
      <author>deathking</author>
      <pubDate>Thu, 30 Jul 2015 11:30:06 +0800</pubDate>
      <link>https://ruby-china.org/topics/26701</link>
      <guid>https://ruby-china.org/topics/26701</guid>
    </item>
    <item>
      <title>关于 Bootstrap nav-pill active 属性问题</title>
      <description>&lt;p&gt;现在我有这样的胶囊式导航栏，我是用 active_link_to 来判断按钮与当前页面的关系，并生成相应的 active 属性。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2014/19880cababb9e1c7a22e84e64dc2fe75.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;我想说的是，每次生成这个 nav，就要调用 7 次 active_link_to 这个 helper，感觉很费时，而且也很不必要。&lt;/p&gt;

&lt;p&gt;有什么跟好的解决办法吗？（先不考虑 DRY 的问题）&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ul&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"nav nav-pills"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= active_link_to fa_icon('home', text: ' 课程主页', class: 'fa-fw fa-lg'), home_course_path(@course), wrap_tag: :li %&amp;gt;
  &amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;active_link_to&lt;/span&gt; &lt;span class="n"&gt;fa_icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'files-o'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="s1"&gt;' 课件资料'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s1"&gt;'fa-fw fa-lg'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;docs_course_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@course&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;wrap_tag: :li&lt;/span&gt;  &lt;span class="o"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= active_link_to fa_icon('comments-o', text: ' 讨论专区', class: 'fa-fw fa-lg'), forum_course_path(@course), wrap_tag: :li  %&amp;gt;
  &amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;active_link_to&lt;/span&gt; &lt;span class="n"&gt;fa_icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'edit'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="s1"&gt;' 课程作业'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s1"&gt;'fa-fw fa-lg'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;assmt_course_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@course&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;wrap_tag: :li&lt;/span&gt;  &lt;span class="o"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= active_link_to fa_icon('book', text: ' 课程维基', class: 'fa-fw fa-lg'), wiki_course_path(@course), wrap_tag: :li  %&amp;gt;
  &amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;active_link_to&lt;/span&gt; &lt;span class="n"&gt;fa_icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'group'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="s1"&gt;' 成员'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s1"&gt;'fa-fw fa-lg'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;members_course_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@course&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;wrap_tag: :li&lt;/span&gt;  &lt;span class="o"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= active_link_to fa_icon('gears', text: ' 课程管理', class: 'fa-fw fa-lg'), admin_course_path(@course), wrap_tag: :li  %&amp;gt;
&amp;lt;/ul&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</description>
      <author>deathking</author>
      <pubDate>Thu, 30 Oct 2014 14:40:54 +0800</pubDate>
      <link>https://ruby-china.org/topics/22350</link>
      <guid>https://ruby-china.org/topics/22350</guid>
    </item>
    <item>
      <title>[已解决] 提交使用 js 动态创建的表单，如何添加 CSRF_TOKEN？</title>
      <description>&lt;p&gt;因为某些原因，我需要用 js 创建一个 form，然后自行提交，但是遇到&lt;code&gt;InvalidAuthenticityToken&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;不是 Ajax 提交&lt;/strong&gt;。关键代码如下，页面里面有个 button，然后点击时执行&lt;code&gt;testPost()&lt;/code&gt;。&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// from https://stackoverflow.com/questions/133925/javascript-post-request-like-a-form-submit&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;post&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Set method to post by default if not specified.&lt;/span&gt;

    &lt;span class="c1"&gt;// The rest of this code assumes you are not using a library.&lt;/span&gt;
    &lt;span class="c1"&gt;// It can be made less wordy if you use one.&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;form&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;method&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;action&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasOwnProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;hiddenField&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;hiddenField&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hidden&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;hiddenField&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;hiddenField&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

            &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hiddenField&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
         &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;testPost&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/books&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bar&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="解决方案："&gt;解决方案：&lt;/h2&gt;
&lt;p&gt;在 post 中加入&lt;code&gt;authenticity_token&lt;/code&gt;项，其值是 head 中的元数据 csrf-token 的值（由 Rails 生成）。&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;meta[name="csrf-token"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;</description>
      <author>deathking</author>
      <pubDate>Thu, 28 Aug 2014 23:36:24 +0800</pubDate>
      <link>https://ruby-china.org/topics/21272</link>
      <guid>https://ruby-china.org/topics/21272</guid>
    </item>
    <item>
      <title>将特定网页结点与对应 CSS 一同抓取</title>
      <description>&lt;p&gt;现在有个比较特别地需求，假设我要抓取网页中的一个特定结点，比如&lt;code&gt;id="content"&lt;/code&gt;的&lt;code&gt;div&lt;/code&gt;吧，我想连同它对应的 CSS 规则也一同抓取下来。现在的想法是，先用&lt;code&gt;nokogiri&lt;/code&gt;抓取 DOM Tree，然后用&lt;code&gt;css_parser&lt;/code&gt;读里面的 CSS 文件，自己再做一个 match。但这样做开销似乎太大了，所以想通过操作 WebKit 来取出 RenderTree。&lt;/p&gt;

&lt;p&gt;有更好地办法么？&lt;/p&gt;</description>
      <author>deathking</author>
      <pubDate>Sat, 08 Feb 2014 10:31:16 +0800</pubDate>
      <link>https://ruby-china.org/topics/17111</link>
      <guid>https://ruby-china.org/topics/17111</guid>
    </item>
    <item>
      <title>Mavericks 下安装 do_postgres 失败</title>
      <description>&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;由于要部署在 Heroku 上，所以必须要用 PostgreSQL。就有了这样的依赖关系：&lt;code&gt;data_mapper -&amp;gt; dm-postgres-adapter -&amp;gt; do_postgres&lt;/code&gt;。但是安装&lt;code&gt;do_postgres&lt;/code&gt;的时候总提示找不到某些头文件。这下问题就来了，我用两种策略在本机上安装 PostgreSQL：&lt;/p&gt;
&lt;h2 id="PostgreSQL.app(PostgreSQL93)"&gt;PostgreSQL.app(PostgreSQL93)&lt;/h2&gt;
&lt;p&gt;直接安装不行，所以要手动指定&lt;code&gt;include&lt;/code&gt;，于是我尝试了：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo gem install dm-postgres-adapter -- --with-pgsql-server-include=/usr/Applications/Postgres93.app/Contents/MacOS/include
$ sudo gem install dm-postgres-adapter -- --with-pgsql-server-include=/usr/Applications/Postgres93.app/Contents/MacOS/include/postgresql
$ sudo gem install dm-postgres-adapter -- --with-pgsql-server-include=/usr/Applications/Postgres93.app/Contents/MacOS/include/postgresql/server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;都得到提示：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;checking for main() in -lpq... yes
checking for libpq-fe.h... no
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="改用brew install postgres依旧无果"&gt;改用&lt;code&gt;brew install postgres&lt;/code&gt;依旧无果&lt;/h2&gt;
&lt;p&gt;用&lt;code&gt;brew&lt;/code&gt;安装的 PostgreSQL 是&lt;code&gt;9.2.4&lt;/code&gt;版的，再次尝试安装 gem，情况貌似有所好转：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;checking for main() in -lpq... yes
checking for libpq-fe.h... yes
checking for libpq/libpq-fs.h... yes
checking for postgres.h... yes
checking for mb/pg_wchar.h... no
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;情况如&lt;a href="http://stackoverflow.com/questions/19998879/gem-install-dm-postgres-adapter-build-error" rel="nofollow" target="_blank" title=""&gt;这位哥们儿&lt;/a&gt;所说，&lt;code&gt;mb/pg_wchar.h&lt;/code&gt;确实存在，但有 error，但出错的地方不一样。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
have_header: checking for mb/pg_wchar.h... -------------------- no

"xcrun clang -I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/include/ruby-2.0.0/universal-darwin13 -I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/include/ruby-2.0.0/ruby/backward -I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/include/ruby-2.0.0 -I. -I/usr/local/Cellar/postgresql/9.2.4/include -I/usr/local/Cellar/postgresql/9.2.4/include/server -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT    -g -Os -pipe -DHAVE_NO_DATETIME_NEWBANG     -c conftest.c"
In file included from conftest.c:3:
/usr/local/Cellar/postgresql/9.2.4/include/server/mb/pg_wchar.h:303:16: warning: type specifier missing, defaults to 'int' [-Wimplicit-int]
typedef bool (*mbcharacter_incrementer) (unsigned char *mbstr, int len);
              ~^
/usr/local/Cellar/postgresql/9.2.4/include/server/mb/pg_wchar.h:303:9: warning: type specifier missing, defaults to 'int' [-Wimplicit-int]
typedef bool (*mbcharacter_incrementer) (unsigned char *mbstr, int len);
~~~~~~~ ^
/usr/local/Cellar/postgresql/9.2.4/include/server/mb/pg_wchar.h:303:14: error: function cannot return function type 'int (unsigned char *, int)'
typedef bool (*mbcharacter_incrementer) (unsigned char *mbstr, int len);
             ^
/usr/local/Cellar/postgresql/9.2.4/include/server/mb/pg_wchar.h:328:2: error: unknown type name 'uint32'
        uint32          utf;                    /* UTF-8 */
        ^
/usr/local/Cellar/postgresql/9.2.4/include/server/mb/pg_wchar.h:329:2: error: unknown type name 'uint32'
        uint32          code;                   /* local code */
        ^
/usr/local/Cellar/postgresql/9.2.4/include/server/mb/pg_wchar.h:337:2: error: unknown type name 'uint32'
        uint32          code;                   /* local code */
        ^
/usr/local/Cellar/postgresql/9.2.4/include/server/mb/pg_wchar.h:338:2: error: unknown type name 'uint32'
        uint32          utf;                    /* UTF-8 */
        ^
/usr/local/Cellar/postgresql/9.2.4/include/server/mb/pg_wchar.h:346:2: error: unknown type name 'uint32'
        uint32          utf1;                   /* UTF-8 code 1 */
        ^
/usr/local/Cellar/postgresql/9.2.4/include/server/mb/pg_wchar.h:347:2: error: unknown type name 'uint32'
        uint32          utf2;                   /* UTF-8 code 2 */
        ^
/usr/local/Cellar/postgresql/9.2.4/include/server/mb/pg_wchar.h:348:2: error: unknown type name 'uint32'
        uint32          code;                   /* local code */
        ^
/usr/local/Cellar/postgresql/9.2.4/include/server/mb/pg_wchar.h:356:2: error: unknown type name 'uint32'
        uint32          code;                   /* local code */
        ^
/usr/local/Cellar/postgresql/9.2.4/include/server/mb/pg_wchar.h:357:2: error: unknown type name 'uint32'
        uint32          utf1;                   /* UTF-8 code 1 */
        ^
/usr/local/Cellar/postgresql/9.2.4/include/server/mb/pg_wchar.h:358:2: error: unknown type name 'uint32'
        uint32          utf2;                   /* UTF-8 code 2 */
        ^
/usr/local/Cellar/postgresql/9.2.4/include/server/mb/pg_wchar.h:416:8: error: unknown type name 'mbcharacter_incrementer'
extern mbcharacter_incrementer pg_database_encoding_character_incrementer(void);
       ^
/usr/local/Cellar/postgresql/9.2.4/include/server/mb/pg_wchar.h:456:27: error: function cannot return function type 'bool' (aka 'int (int *)')
extern bool pg_verifymbstr(const char *mbstr, int len, bool noError);
                          ^
/usr/local/Cellar/postgresql/9.2.4/include/server/mb/pg_wchar.h:457:28: error: function cannot return function type 'bool' (aka 'int (int *)')
extern bool pg_verify_mbstr(int encoding, const char *mbstr, int len,
                           ^
/usr/local/Cellar/postgresql/9.2.4/include/server/mb/pg_wchar.h:485:28: error: function cannot return function type 'bool' (aka 'int (int *)')
extern bool pg_utf8_islegal(const unsigned char *source, int length);
                           ^
2 warnings and 15 errors generated.
checked program was:
/* begin */
1: #include "ruby.h"
2: 
3: #include &amp;lt;mb/pg_wchar.h&amp;gt;
/* end */

--------------------

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不得不说，被彻底恶心到了。&lt;/p&gt;</description>
      <author>deathking</author>
      <pubDate>Wed, 22 Jan 2014 00:36:45 +0800</pubDate>
      <link>https://ruby-china.org/topics/16917</link>
      <guid>https://ruby-china.org/topics/16917</guid>
    </item>
    <item>
      <title>有人清楚 RHG (Ruby  Hacking Guide) 中文化的情况么？</title>
      <description>&lt;p&gt;这是一本很好的书，貌似翻译项目停滞了很久，这么多年都没看到更新了。有谁了解具体的情况么？&lt;/p&gt;

&lt;p&gt;&lt;a href="http://axgle.github.io/rhg/" rel="nofollow" target="_blank"&gt;http://axgle.github.io/rhg/&lt;/a&gt;&lt;/p&gt;</description>
      <author>deathking</author>
      <pubDate>Thu, 18 Apr 2013 22:13:51 +0800</pubDate>
      <link>https://ruby-china.org/topics/10321</link>
      <guid>https://ruby-china.org/topics/10321</guid>
    </item>
    <item>
      <title>[更新] 探寻 Ruby 原力——SICP 公开课中英双语字幕</title>
      <description>&lt;h2 id="[SICP]计算机程序的构造和解释"&gt;[SICP] 计算机程序的构造和解释&lt;/h2&gt;&lt;h2 id="探寻Ruby的祖宗之一——Lisp的魔力"&gt;探寻 Ruby 的祖宗之一——Lisp 的魔力&lt;/h2&gt;
&lt;hr&gt;

&lt;p&gt;&lt;img src="https://a248.e.akamai.net/camo.github.com/b76b76f7427e0307147e0d1c222f870b3e288e00/687474703a2f2f7777772e68616364632e6f72672f77702d636f6e74656e742f75706c6f6164732f323031322f30352f736963702e676966" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;不知道怎么在帖内嵌入 Flash 视频，直接放 Youku 专辑地址吧：
&lt;a href="http://www.youku.com/playlist_show/id_18958522.html" rel="nofollow" target="_blank"&gt;http://www.youku.com/playlist_show/id_18958522.html&lt;/a&gt;&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;拖了一年有余了，终于更新了，真是泪流满面。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lec2a：&lt;a href="http://v.youku.com/v_show/id_XNzAzNjI1NjU2.html" rel="nofollow" target="_blank" title=""&gt;高阶过程&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Lec2b：&lt;a href="http://v.youku.com/v_show/id_XNzAzNjg4Mjk2.html" rel="nofollow" target="_blank" title=""&gt;复合数据&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;高阶过程&lt;/strong&gt;介绍了如何抽象地思考，&lt;strong&gt;复合数据&lt;/strong&gt;介绍了如何对数据建立抽象屏障。&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;想了很久，要不要把这个东西在这里放出来。一来是 SICP 用的是 Scheme，不是 Ruby，所以放在 Ruby-China 似乎不是很好。但转念一想，Ruby 确实从 Lisp 里面“借”来很多东西，很多是 Ruby 对 Lisp 思想的延续。我当初接触 Ruby 时，花了很久的时间才体会到了 Ruby 的思想，然而，当我后面看到 SICP 这本书，接触到 Lisp 这个老前辈时，发现他留下了 Ruby 太多太多的珍宝：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;代码和数据之间没有不可逾越的鸿沟

&lt;ul&gt;
&lt;li&gt;符号（Symbol）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;高阶函数

&lt;ul&gt;
&lt;li&gt;Ruby 里面的块、lambda 方法&lt;/li&gt;
&lt;li&gt;Lisp 里面的 lambda 过程&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;通过提供一致的接口实现的数据抽象

&lt;ul&gt;
&lt;li&gt;Ruby 的鸭子类型&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;可变参数

&lt;ul&gt;
&lt;li&gt;Lisp 里的点&lt;/li&gt;
&lt;li&gt;Ruby 里面的星&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;元语言抽象

&lt;ul&gt;
&lt;li&gt;用 Lisp 构建 Lisp 或其它语言&lt;/li&gt;
&lt;li&gt;Ruby 是一门很好的 DSL 语言&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;例如，作为 OOP 语言的 Ruby 所强调的“模板方法”这种设计模式，很早就体现在 Lisp 所强调的“提取公共模式”的抽象方法上了。所以，我决定还是放出这个系列视频，让大家同我们一起去探寻其中蕴涵的思想。&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;本视频由&lt;br&gt;
&lt;strong&gt;哈尔滨工业大学 IBM 技术中心 FoOTOo 实验室&lt;/strong&gt;负责统筹翻译&lt;br&gt;
&lt;strong&gt;哈尔滨工业大学清影 PT 压制小组&lt;/strong&gt;进行压制和后期制作。&lt;/p&gt;

&lt;p&gt;由于我们都是高校学生，所以只能利用课余时间进行翻译工作。在加上校正、打轴、后期等字幕制作工作较为繁琐，所以进度很慢。整个翻译项目托管在：&lt;a href="https://github.com/FoOTOo/Learning-SICP" rel="nofollow" target="_blank"&gt;https://github.com/FoOTOo/Learning-SICP&lt;/a&gt;。欢迎大家给我们提出意见和简易，并对翻译错误或者可以提高的地方进行指正！&lt;/p&gt;

&lt;p&gt;&lt;code&gt;DeathKing&lt;/code&gt;&lt;/p&gt;</description>
      <author>deathking</author>
      <pubDate>Thu, 04 Apr 2013 10:43:41 +0800</pubDate>
      <link>https://ruby-china.org/topics/9954</link>
      <guid>https://ruby-china.org/topics/9954</guid>
    </item>
    <item>
      <title>将一个 Ruby 脚本置为服务，调用该脚本则调用该服务</title>
      <description>&lt;p&gt;先说一下需求吧。假设我有一个脚本叫&lt;code&gt;poj.rb&lt;/code&gt;吧，他完成下面的功能：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;poj.rb get 1001&lt;/code&gt; ：从我设定的网站获取&lt;code&gt;id&lt;/code&gt;为&lt;code&gt;1001&lt;/code&gt;的网页；&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;poj.rb submit 1001.c&lt;/code&gt; ：将&lt;code&gt;1001.c&lt;/code&gt;文件提交；&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我现在想说的是，这个脚本初始化需要消耗一定的资源，而调用的频率也非常高，因此，我希望初始化一次而响应参数多次。也许有人说你可以这样实现：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 初始化&lt;/span&gt;
&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# 获得输入&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;gets&lt;/span&gt;
  &lt;span class="c1"&gt;# 处理str&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但实际的需求不允许我这样，我想要实现类似于&lt;code&gt;git&lt;/code&gt;的效果，也就是在终端中这样：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git add README.md
&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s1"&gt;'first commit'&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;poj.rb get 1001
&lt;span class="nv"&gt;$ &lt;/span&gt;poj.rb submit 1001.pas
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此，我想让&lt;code&gt;hoj.rb&lt;/code&gt;在第一次运行后就成为&lt;code&gt;daemon&lt;/code&gt;，以后在&lt;code&gt;shell&lt;/code&gt;调用&lt;code&gt;poj.rb&lt;/code&gt;时，不会新建一个实例，而是把消息发送给之前创建好的&lt;code&gt;daemon&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;我目前能想到的方法是用&lt;code&gt;Daemons&lt;/code&gt;这个 gem，但是使用&lt;code&gt;Daemons.daemonize&lt;/code&gt;方法似乎不起作用。所以想问下各位：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;daemons&lt;/code&gt;这个库应该如何实现这个功能；&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;daemons&lt;/code&gt;如果不能实现这个功能，那么&lt;code&gt;ruby&lt;/code&gt;能么？要如何实现呢？&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>deathking</author>
      <pubDate>Sat, 26 Jan 2013 01:21:54 +0800</pubDate>
      <link>https://ruby-china.org/topics/8377</link>
      <guid>https://ruby-china.org/topics/8377</guid>
    </item>
    <item>
      <title>如何 alias 一个类方法或者模块方法呢？</title>
      <description>&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Foo&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bar&lt;/span&gt;
    &lt;span class="s2"&gt;"bar"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;alias&lt;/span&gt; &lt;span class="n"&gt;foobar&lt;/span&gt; &lt;span class="n"&gt;bar&lt;/span&gt;  &lt;span class="c1"&gt;#=&amp;gt; undefined bar method&lt;/span&gt;
  &lt;span class="k"&gt;alias&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;foobar&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bar&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; Syntax Error&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以用 &lt;code&gt;alias&lt;/code&gt; 来操作模块方法么？  &lt;/p&gt;</description>
      <author>deathking</author>
      <pubDate>Tue, 03 Jul 2012 15:51:59 +0800</pubDate>
      <link>https://ruby-china.org/topics/4106</link>
      <guid>https://ruby-china.org/topics/4106</guid>
    </item>
  </channel>
</rss>
