有没有办法懒惰加载Ruby Gem?

时间:2014-08-07 17:24:58

标签: ruby puppet autoload

我正在使用puppet,但仅使用Ruby的解决方案也很好。

问题:我的木偶自定义提供程序ruby代码对尚未安装的Gem有一个require语句:

require 'rubygems'
require 'zip'

我为该gem添加了一个依赖项,但由于puppet用户定义的类型不会编译,所以它永远无法下载并安装gem。

以下是我打算加载gem的方法:

  package { 'rubyzip':
    ensure   => 'latest',
    provider => 'gem'
  }

我以为我可以使用自动加载来延迟加载rubyzip gem并避开问题;在执行ruby代码时,gem已经安装好,不会发生运行时错误。

是否可以自动加载rubyzip?我试过,但得到语法错误。不知道怎么做。 我试过这个,因为我需要Zip :: File类:

module Zip
  autoload :File, 'C:\PROGRA~2\PUPPET~1\PUPPET~1\sys\ruby\lib\ruby\gems\1.9.1\gems\rubyzip-1.1.6\lib\zip.rb'
end

这是错误:

C:/Progra~2/PUPPET~1/PUPPET~1/sys/ruby/lib/ruby/gems/1.9.1/gems/rubyzip-1.1.6/lib/zip/file.rb:45:in `<module:Zip>': uninitialized constant Zip::File (NameError)
        from C:/Progra~2/PUPPET~1/PUPPET~1/sys/ruby/lib/ruby/gems/1.9.1/gems/rubyzip-1.1.6/lib/zip/file.rb:1:in `<top (required)>'
        from C:/Progra~2/PUPPET~1/PUPPET~1/sys/ruby/lib/ruby/site_ruby/1.9.1/rubygems/custom_require.rb:36:in `require'
        from C:/Progra~2/PUPPET~1/PUPPET~1/sys/ruby/lib/ruby/site_ruby/1.9.1/rubygems/custom_require.rb:36:in `require'
        from C:/PROGRA~2/PUPPET~1/PUPPET~1/sys/ruby/lib/ruby/gems/1.9.1/gems/rubyzip-1.1.6/lib/zip.rb:14:in `<top (required)>'
        from C:/Users/paul.chernoch/Documents/puppet deployer/phx_deployer/puppet/modules/mirrored_directory/lib/puppet/provider/mirrored_directory/zip/zipinstall.rb:92:in `from_zip'
        from C:/Users/paul.chernoch/Documents/puppet deployer/phx_deployer/puppet/modules/mirrored_directory/lib/puppet/provider/mirrored_directory/zip/zipinstall.rb:254:in `differences'
        from C:/Users/paul.chernoch/Documents/puppet deployer/phx_deployer/puppet/modules/mirrored_directory/lib/puppet/provider/mirrored_directory/zip/zipinstall.rb:235:in `has_differences?
'
        from C:/Users/paul.chernoch/Documents/puppet deployer/phx_deployer/puppet/modules/mirrored_directory/lib/puppet/provider/mirrored_directory/zip/zipinstall.rb:485:in `<main>'

注意: 我也试图在下一个阶段之前使用木偶运行阶段来强制创建rubyzip包,但不确定如何使用阶段或者它们是否有效。

2 个答案:

答案 0 :(得分:0)

您的问题的主要来源是您正在从模块的lib/目录部署自定义事实。这一切都发生在名为pluginsync的Puppet的一部分中,这是在解析或操作任何目录代码之前。基本上它是:

pluginsync - &gt; upload facts - &gt; return catalog - &gt; execute

所以,没有多少木偶魔法(阶段,资源排序,你有什么)可以解决这个问题。

我看到两种可能性:

1)修改Ruby代码以解决require&#39; zipfile&#39;并为该事实设置一个虚拟值,例如“N / A”和“#N; A&#39;直到宝石可用。当然,更新任何依赖于这个事实的Puppet代码来预期这种行为,因为第一个Puppet运行总是没有这个事实。

2)修改系统引导/转出过程,以确保以其他方式使用Puppet安装此gem。

答案 1 :(得分:0)

我选择了解决问题的另一种方法。 我的傀儡表现为两阶段。 (你需要运行两次木偶申请。我没有使用木偶运行阶段,因为这不起作用。)

1)执行安装的第一部分,不需要解压缩文件。第一部分包括安装rubyzip gem。

2)执行安装的第二部分,解压缩文件并将其复制到目标目录中,然后执行并按照步骤进行操作。

这是通过以下方式完成的:

a)“限制”我的提供商仅在安装了gem的系统上可用。

b)在我需要拉链模块的地方放置一个救援区。

以下是我添加到自定义类型提供程序逻辑中的限制代码:

  confine :true => begin
    begin
      require 'zip'
      true
    rescue LoadError
      false
    end
  end

以下是我现在如何在我的ruby模块中使用rubygems:

require 'rubygems'
begin
  require 'zip'
rescue LoadError
end

(注意:我试图在我的限制逻辑中使用自定义功能,但由于我对如何正确定义它们的无知而无法使用。)

为了帮助其他失去的木偶爱好者(比如我自己),这里是整个提供者ruby文件,“zip.rb”。我在互联网上阅读的建议的一半问题是,人们在没有上下文的情况下提供了小小的片段,我总是将代码插入错误的位置。

# Resource Type: mirrored_directory
# Provider:      zip
#
# Provides the interface between the Puppet custom resource type 'mirrored_directory'
# and the ruby class 'ZipInstall::Unzipper'.
# This permits missing or changed files to be extracted from a Zip archive on a Windows system,
# while unchanged files are left alone.
require 'rubygems'
require 'puppet/provider/mirrored_directory/zip/zipinstall'

Puppet::Type.type(:mirrored_directory).provide :zip do
  desc "Uses rubyzip to extract new or modified files from a zip archive on a Windows system."

  # Confine this provider to (a) Windows Systems where (b) the rubyzip gem is installed.
  confine :osfamily => :windows

  confine :true => begin
    begin
      require 'zip'
      true
    rescue LoadError
      false
    end
  end  

  defaultfor :osfamily => :windows

  include Puppet::Util::Warnings

  # The mirrored_directory resource exists iff the target directory exists.
  def exists?
    Puppet::FileSystem::File.exist?(@resource[:name])
  end

  # Assuming that the target directory is empty (or only contains excluded files),
  # extract all included files from the source archive and deposit them in the target directory
  # (or corresponding subdirectory).
  # Do not delete any files.
  def create
    Puppet::Util::Log.new(:level => :debug, :message => "create: About to unzip #{@resource[:source]}") 
    change_count = unzipper.extract(false)
    @property_hash[:ensure] = exists? ? :present : :absent
  end

  # Delete the contents of the target directory and subdirectories 
  # (other than those that are excluded).
  def destroy
    change_count = unzipper.delete
    @property_hash.clear
  end

  # Check if the files in the target directory match those in the source zip archive or not.
  def is_current
    Puppet::Util::Log.new(:level => :debug, :message => "is_current: About to examine #{@resource[:source]}") 
    !unzipper.has_differences?(false)
  end

  # Extract each file from the source archive that differs from the corresponding file
  # in the target directory. 
  # Optionally delete files found in the target directory but not in the source archive,
  # according to the parameter :do_deletes.
  def is_current=(value)
    unzipper_to_use = unzipper
    changes = unzipper_to_use.differences(false, false)
    count = unzipper_to_use.apply_changes(changes, @resource[:do_deletes])
    @property_hash[:is_current] = value
    0
  end

  # TODO: Search computer for all installed directories so that the installed 
  # mirrored_directory resource instances can be queried by puppet.
  def self.instances
    []
  end

  # Construct a new Unzipper, using these parameters supplied to the resource type:
  #   :source, :name, :exclude, :include
  # Supplies a logger that delegate to puppet's own logging.
  def unzipper
    # Puppet has numerous loggin levels, including:
    #    :crit, :err, :warning, :notice, :info, :debug
    logger = ->(level,message) {
      Puppet::Util::Log.new(:level => level, :message => message) 
    }
    ZipInstall::Unzipper.new(@resource[:source], @resource[:name], :prefix_depth => @resource[:prefix_depth], :exclude_files => @resource[:exclude], :include_files => @resource[:include], :logger => logger) 
  end

end

以下是使用提供程序的关联自定义类型文件mirrored_directory.rb:

Puppet::Type.newtype(:mirrored_directory) do
  desc 'mirrored_directory keeps a directory in sync with an archive by extracting new or changed files from the archive and/or deleting extraneous files from the directory.'
  ensurable

  newparam(:path, :namevar => true) do  
    desc 'path of the target root directory into which files will be extracted.'
    validate do |value|
      fail("Invalid path #{value}") unless Pathname.new(value).absolute?
    end
  end

  newparam(:source) do
    desc 'source path to the archive from which files will be extracted.'
    validate do |value|
      fail("Invalid path #{value}") unless Pathname.new(value).absolute?
    end
  end

  newparam(:prefix_depth) do
    desc 'Number of directory segments to remove from beginning of paths extracted from zip file.'
    defaultto 0
  end

  newparam(:exclude, :array_matching => :all) do
    desc 'List of wildcards that match target files that should NOT be updated or deleted.'
    defaultto []
  end

  newparam(:include, :array_matching => :all) do
    desc 'List of wildcards that match source files that MAY be extracted from the archive.'
    defaultto ['*']
  end

  newparam(:do_deletes) do
    desc 'Determines if files will be deleted or not when found in the target directory but not in the archive.'
    newvalues(:true, :yes, :false, :no)
    defaultto false
    validate { |arg| }
    munge do |value|
      newval = super(value)
      case newval
        when :true, :yes; true
        when :false, :no; false
        else
          self.fail "Invalid do_deletes value #{value.inspect} (expecting 'true' or 'false')"
      end
    end
  end

  newproperty(:is_current) do
    desc 'Indicates if the files in the target directory are consistent with those in the source archive.'
    newvalues(:true, :yes, :false, :no)
    defaultto true
    validate { |arg| }
    munge do |value|
      newval = super(value)
      case newval
        when :true, :yes; true
        when :false, :no; false
        else
          self.fail "Invalid is_current value #{value.inspect} (expecting 'true' or 'false')"
      end
    end
  end

  # Require that the source archive exist.
  autorequire(:file) { self[:source] if Pathname.new(self[:source]).absolute? }

end