File: feature.rb

package info (click to toggle)
puppet-agent 7.23.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 19,092 kB
  • sloc: ruby: 245,074; sh: 456; makefile: 38; xml: 33
file content (129 lines) | stat: -rw-r--r-- 4,239 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
require_relative '../../puppet'
require_relative '../../puppet/util/warnings'

class Puppet::Util::Feature
  include Puppet::Util::Warnings

  attr_reader :path

  # Create a new feature test. You have to pass the feature name, and it must be
  # unique. You can pass a block to determine if the feature is present:
  #
  #     Puppet.features.add(:myfeature) do
  #       # return true or false if feature is available
  #       # return nil if feature may become available later
  #     end
  #
  # The block should return true if the feature is available, false if it is
  # not, or nil if the state is unknown. True and false values will be cached. A
  # nil value will not be cached, and should be used if the feature may become
  # true in the future.
  #
  # Features are often used to detect if a ruby library is installed. To support
  # that common case, you can pass one or more ruby libraries, and the feature
  # will be true if all of the libraries load successfully:
  #
  #     Puppet.features.add(:myfeature, libs: 'mylib')
  #     Puppet.features.add(:myfeature, libs: ['mylib', 'myotherlib'])
  #
  # If the ruby library is not installed, then the failure is not cached, as
  # it's assumed puppet may install the gem during catalog application.
  #
  # If a feature is defined using `:libs` and a block, then the block is
  # used and the `:libs` are ignored.
  #
  # Puppet evaluates the feature test when the `Puppet.features.myfeature?`
  # method is called. If the feature test was defined using a block and the
  # block returns nil, then the feature test will be re-evaluated the next time
  # `Puppet.features.myfeature?` is called.
  #
  # @param [Symbol] name The unique feature name
  # @param [Hash<Symbol,Array<String>>] options The libraries to load
  def add(name, options = {}, &block)
    method = name.to_s + "?"
    @results.delete(name)

    meta_def(method) do
      # we return a cached result if:
      #  * if we've tested this feature before
      #  AND
      #    * the result was true/false
      #    OR
      #    * we're configured to never retry
      if @results.has_key?(name) &&
         (!@results[name].nil? || !Puppet[:always_retry_plugins])
        !!@results[name]
      else
        @results[name] = test(name, options, &block)
        !!@results[name]
      end
    end
  end

  # Create a new feature collection.
  def initialize(path)
    @path = path
    @results = {}
    @loader = Puppet::Util::Autoload.new(self, @path)
  end

  def load
    @loader.loadall(Puppet.lookup(:current_environment))
  end

  def method_missing(method, *args)
    return super unless method.to_s =~ /\?$/

    feature = method.to_s.sub(/\?$/, '')
    @loader.load(feature, Puppet.lookup(:current_environment))

    respond_to?(method) && self.send(method)
  end

  # Actually test whether the feature is present.  We only want to test when
  # someone asks for the feature, so we don't unnecessarily load
  # files.
  def test(name, options, &block)
    if block_given?
      begin
        result = yield
      rescue StandardError,ScriptError => detail
        warn _("Failed to load feature test for %{name}: %{detail}") % { name: name, detail: detail }
        result = nil
      end
      @results[name] = result
      result
    else
      libs = options[:libs]
      if libs
        libs = [libs] unless libs.is_a?(Array)
        libs.all? { |lib| load_library(lib, name) } ? true : nil
      else
        true
      end
    end
  end

  private

  def load_library(lib, name)
    raise ArgumentError, _("Libraries must be passed as strings not %{klass}") % { klass: lib.class } unless lib.is_a?(String)

    @rubygems ||= Puppet::Util::RubyGems::Source.new
    @rubygems.clear_paths

    begin
      require lib
      true
    rescue LoadError
      # Expected case. Required library insn't installed.
      debug_once(_("Could not find library '%{lib}' required to enable feature '%{name}'") %
        {lib: lib, name: name})
      false
    rescue StandardError, ScriptError => detail
      debug_once(_("Exception occurred while loading library '%{lib}' required to enable feature '%{name}': %{detail}") %
        {lib: lib, name: name, detail: detail})
      false
    end
  end
end