jonvi 发表于 2015-11-26 15:42:00

Puppet源码剖析----Type篇(一)

  最近在做一个移植Puppet到公司的网络操作系统上的项目,需要在puppet上进行二次开发,开发type,provider.
  但是发现网上和书上都是讲Puppet布署和使用的居多,讲二次开发的很少。所以自己一边在项目里开发,一边研究源码,现将研究的成果分享出来。
  因为是讲puppet的源码,所以要对puppet的使用和ruby语言有一定的基础。因为Puppet里运用了大量ruby的元编程特性,所以建议看一下<ruby元编程>这本书。
  

  使用的puppet版本是2.7.3,ruby是1.9.3.

  我们在puppet/lib/type目录下可以看到很多puppet自带的type,如常用的file,exec等。定义它们都是形如下面的代码:
  Puppet::Type.newtype(:file) do{}
  Puppet::Type.newtype(:exec) do{}
  我们就从这里出发,看看在puppet里如何自定义type,以及它是如何实现的。为了简化起见,我将puppet的代码抽取出来,用一个最简化的代码来讲解。这些代码都是从puppet的源码中抽取出来的,在不影响理解实现的基础上,删除了一些代码,以求理解起来方便。废话少说,先上代码:
  

  ├─lib
│└─puppet
│      │testType.rb
│      │type.rb
│      │util.rb
│      │
│      ├─metatype
│      │      manager.rb
│      │
│      └─util
│            classgen.rb
│            methodhelper.rb

  为了方便理解,有几点先说明一下:

  1.目录结构和puppet的源码保持一致,puppet在定义module时,module名都用了目录名作为一个命名空间,这样避免了冲突。如manger.rb定义如下:
  module Puppet::MetaType //目录结构
module Manager
  ......

  end
  end
  2.类也是一个对象,puppet/type.rb里定义的type类是管理所有type的一个类,newtype方法自定义的类都会保存在这里。
  

  具体的代码如下:
  type.rb:
  require_relative '../puppet/metatype/manager'

module Puppet
class Type
    class << self
      include Puppet::MetaType::Manager#Manger模块里的方法都成为Type类的类方法,主要是newtype方法,用于定义新的类

      attr_accessor :types          #所有定义的类都保存在@types={}这个hash表里,定义存取器,便于访问验证。
    end

    def self.initvars               #初始化一些类实例变量,自定义的类会继承这个方法。
      @objects = Hash.new
      @aliases = Hash.new

      @is_init = true
    end

end
end
  

  metatype/manager.rb:#此模块主要体现元编程的能力,所以放在metatype目录下,用于产生新的type.

  require_relative '../util'
require_relative '../type'
require_relative '../util/methodhelper'
require_relative '../util/classgen'

module Puppet::MetaType
module Manager
   includePuppet::Util::ClassGen#包含ClassGen模块,这个模块主要是动态生成类的一些方法。如genclass.

    def newtype(name,options={},&block)
      unless options.is_a?(Hash)            #自定义类时的options必须为hash
          warn &quot;Puppet::Type.newtype#{name} expects a hash as the second argument,not #{options.inspect}&quot;
          options = {:parent => options}
      end

      name = symbolize(name)         #将自定义的类名转化为symbol
      newmethod = &quot;new#{name.to_s}&quot; #定义产生新类对象的方法名,如自定义类:file,则产生这个类对象的方法名newfile

      selfobj = singleton_class#获得当前对象的单例类,注意这里其实是Type类的单例类,取得它的单例类,是为了向Type添加或删除类方法。

      @types ||= {} #如果还没有定义@types,则定义它为hash.这个变量成为Type类的实例变量,用于存储所有自定义的Type类。

      #如果已经定义了同名的类,且定义了newmethod方法,则删除它。
      if @types.include?(name)
          if self.respond_to?(newmethod)
            #caution: remove method from self.singleton_class not self
            selfobj.send(:remove_method,newmethod)
          end
      end

         #将options中的key都转换为符号
      options = symbolize_options(options)

      #获取自定义的类的父类,并将其从options里删除
      if parent = options[:parent]
          options.delete(:parent)
      end

      #产生新的类
      kclass = genclass(
            name,
            :parent => (parent || Puppet::Type),
            :overwrite => true,
            :hash => @types,
            :attribute => options,
            &block
      )

        #如果Type类里还没定义产生新类的对象的方法,则定义它。
      if self.respond_to?(newmethod)
            puts &quot;new#{name.to_s} is already exists skipping&quot;
      else
            selfobj.send(:define_method,newmethod) do |*args| #注意selfobj是Type类的单例类,所以定义的方法便成为Type类的方法。
            kclass.new(*args)
            end
      end

         #返回新产生的类对象(类也是对象)
      kclass

    end
end
end
  

  util/classgen.rb: #产生新类的模块,用于产生新的类,在这一节主要是产生新的Type类,后面还可以看到用它产生新的provider类。

  require_relative '../util'
require_relative '../util/methodhelper'

module Puppet::Util::ClassGen
include Puppet::Util::MethodHelper
include Puppet::Util

   #产生新的类
def genclass(name,options={},&block)
      genthing(name,Class,options,block)
end

  #获取常量的名称
def getconst_string(name,options)
    unless const = options[:constant]
      prefix = options[:prefix] || &quot;&quot;
      const = prefix &#43; name2const(name)
    end

    const
end

  #是否定义了这个常量
def is_const_defined?(const)
    if ::RUBY_VERSION =~ /1.9/
      const_defined?(const,false)
    else
      const_defined?(const)
    end
end

  #给类定义新的常量
def handleclassconst(kclass,name,options)
   const = getconst_string(name,options)

   if is_const_defined?(const)
       if options[:overwrite]
         remove_const(const)
       else
          puts &quot;Class #{const} is already defined in #{self}&quot;
       end
   end

   const_set(const,kclass)
end

  #初始化一个类,通过这个方法,我们可以看到,自定义类可以给它定义常量,也可以通过模块扩展自定义类的功能。
def initclass(kclass,options)
    kclass.initvars if kclass.respond_to?(:initvars) #如果类有initvars方法,则调用它。因为新定义type类的父类是Puppet::Type类,这个类里有initvars方法,所以会调用它。

    if attrs = options[:attributes]#如果定义新类时指定了attributes则为它定义这类属性的存储器
      if attrs.is_a?(Hash)
      attrs.each do |param,value|
          method = param.to_s&#43;&quot;=&quot;
          kclass.send(method,value) if kclass.respond_to?(method)
      end
      end
    end

    [:include,:extend].each do |method| #如果定义新类时指定了include,extend在模块,它在新类里加载这些模块。可以通过模块扩展自定义的类
      if mods = options
      mods = unless mods.is_a?(Array)
      mods.each do |mod|
          kclass.send(method,mod)
      end
      end
    end

    kclass.preinit if kclass.respond_to?(:preinit)#最后设置一个钩子,如果新定义的类有preinit方法,则调用它一下下
end

  #将自定义类存储在@types
def stroeclass(kclass,name,options)
    if hash = options[:hash]
      if hash.include?(name) and !options[:overwrite]
      raise &quot;Already a generated class named #{name}&quot;
      end

      hash = kclass
    end


end

   #这个方法是产生自定义类的方法
def genthing(name,type,options,block)
   options = symbolize_options(options)

   name = symbolize(name)

      options[:parent] ||= self
      eval_method = :class_eval
      kclass = Class.new(options[:parent]) do    #产生一个新的自定义类,并给它定义一个实例变量@name
      @name = name
      end

      handleclassconst(kclass,name,options)#定义自定义类的常量,具体功能见上面对方法的注释

      initclass(kclass,options) #初始化自定义类

      block ||= options[:block]
      kclass.send(eval_method,&block) if block #将定义类时的block传给产生的类去执行,这样这个block里就可以执行所有Type的类方法。这也是为什么我们可以在自定义类的块里调用newproperty这些方法的原因。

      kclass.postinit if kclass.respond_to?(:postinit)#又一个钩子函数,用于初始化完成后进行一些处理工作。

      stroeclass(kclass,name,options)#将新定义的类存储起来

end

# :abc => &quot;Abc&quot;
# &quot;abc&quot; => &quot;Abc&quot;
# &quot;123abc&quot; => &quot;123abc&quot;
def name2const(name)
    name.to_s.capitalize
end



end
  

  util/methodhelper.rb     #util目录主要是一些功能函数,如这个模块定义了符号化options的方法

  module Puppet::Util::MethodHelper

def symbolize_options(options)
    options.inject({}) do |hash,opts|
      if opts.respond_to? :intern
      hash.intern] = opts
      else
      hash] = opts
      end
      hash
    end
end

end
  

  util.rb: #同理,这里定义了符号化一个变量的操作

  module Puppet
module Util
    def symbolize(value)
      if value.respond_to? :intern then
      value.intern
      else
      value
      end
    end

end
end

  

  testType.rb
  require_relative './type'

Puppet::Type.newtype(:atest) do

end

Puppet::Type.types.each do |name,kclass|
p kclass.methods
p kclass.instance_variables
end
  

  最后我们用testType.rb测试我们的代码,我们定义了一个新类atest。然后遍历Type类的@types变量,查看所有新定义的类的方法和实例变量。运行结果如下:
  [:types, :types=, :initvars, :newatest, :newtype, :genclass, :getconst_string, :is_const_defined?, :handleclassconst, :initclass, :stroeclass, :genthing, :name2const, :symbolize, :symbolize_options_,..........]
[:@name, :@objects, :@aliases, :@is_init]
  

  可以看到新定义的类从父类Type里继承了许多类方法,并在initvars后产生了自己的实例变量。
  注释较为详细,如果还有不理解或讲的不对的地方,欢迎讨论。
页: [1]
查看完整版本: Puppet源码剖析----Type篇(一)