设为首页 收藏本站
查看: 1747|回复: 0

[经验分享] Puppet源码剖析----Type篇(一)

[复制链接]

尚未签到

发表于 2015-11-26 15:42:00 | 显示全部楼层 |阅读模式
  最近在做一个移植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
     include  Puppet::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[method]
        mods = [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[name] = 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[0].respond_to? :intern
        hash[opts[0].intern] = opts[1]
      else
        hash[opts[0]] = opts[1]
      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、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-144013-1-1.html 上篇帖子: 自动化运维工具Puppet学习 下篇帖子: Puppet 语法
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表