File: base_loader.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 (178 lines) | stat: -rw-r--r-- 5,063 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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
module Puppet::Pops
module Loader
# BaseLoader
# ===
# An abstract implementation of Loader
#
# A derived class should implement `find(typed_name)` and set entries, and possible handle "miss caching".
#
# @api private
#
class BaseLoader < Loader

  # The parent loader
  attr_reader :parent

  def initialize(parent_loader, loader_name, environment)
    super(loader_name, environment)
    @parent = parent_loader # the higher priority loader to consult
    @named_values = {}      # hash name => NamedEntry
    @last_result = nil      # the value of the last name (optimization)
  end

  def discover(type, error_collector = nil, name_authority = Pcore::RUNTIME_NAME_AUTHORITY, &block)
    result = []
    @named_values.each_pair do |key, entry|
      result << key unless entry.nil? || entry.value.nil? || key.type != type || (block_given? && !yield(key))
    end
    result.concat(parent.discover(type, error_collector, name_authority, &block))
    result.uniq!
    result
  end

  # @api public
  #
  def load_typed(typed_name)
    # The check for "last queried name" is an optimization when a module searches. First it checks up its parent
    # chain, then itself, and then delegates to modules it depends on.
    # These modules are typically parented by the same
    # loader as the one initiating the search. It is inefficient to again try to search the same loader for
    # the same name.
    synchronize do
      if @last_result.nil? || typed_name != @last_result.typed_name
        @last_result = internal_load(typed_name)
      else
        @last_result
      end
    end
  end

  # @api public
  #
  def loaded_entry(typed_name, check_dependencies = false)
    synchronize do
      if @named_values.has_key?(typed_name)
        @named_values[typed_name]
      elsif parent
        parent.loaded_entry(typed_name, check_dependencies)
      else
        nil
      end
    end
  end

  # This method is final (subclasses should not override it)
  #
  # @api private
  #
  def get_entry(typed_name)
    @named_values[typed_name]
  end

  # @api private
  #
  def set_entry(typed_name, value, origin = nil)
    synchronize do
      # It is never ok to redefine in the very same loader unless redefining a 'not found'
      entry = @named_values[typed_name]
      if entry
        fail_redefine(entry) unless entry.value.nil?
      end

      # Check if new entry shadows existing entry and fail
      # (unless special loader allows shadowing)
      if typed_name.type == :type && !allow_shadowing?
        entry = loaded_entry(typed_name)
        if entry
          fail_redefine(entry) unless entry.value.nil? #|| entry.value == value
        end
      end

      @last_result = Loader::NamedEntry.new(typed_name, value, origin)
      @named_values[typed_name] = @last_result
    end
  end

  # @api private
  #
  def add_entry(type, name, value, origin)
    set_entry(TypedName.new(type, name), value, origin)
  end

  # @api private
  #
  def remove_entry(typed_name)
    synchronize do
      unless @named_values.delete(typed_name).nil?
        @last_result = nil unless @last_result.nil? || typed_name != @last_result.typed_name
      end
    end
  end

  # Promotes an already created entry (typically from another loader) to this loader
  #
  # @api private
  #
  def promote_entry(named_entry)
    synchronize do
      typed_name = named_entry.typed_name
      entry = @named_values[typed_name]
      if entry then fail_redefine(entry); end
      @named_values[typed_name] = named_entry
    end
  end

  protected

  def allow_shadowing?
    false
  end

  private

  def fail_redefine(entry)
    origin_info = entry.origin ? _("Originally set %{original}.") % { original: origin_label(entry.origin) } : _("Set at unknown location")
    raise ArgumentError, _("Attempt to redefine entity '%{name}'. %{origin_info}") % { name: entry.typed_name, origin_info: origin_info }
  end

  # TODO: Should not really be here?? - TODO: A Label provider ? semantics for the URI?
  #
  def origin_label(origin)
    if origin && origin.is_a?(URI)
      format_uri(origin)
    elsif origin.respond_to?(:uri)
      format_uri(origin.uri)
    else
      origin
    end
  end

  def format_uri(uri)
    (uri.scheme == 'puppet' ? 'by ' : 'at ') + uri.to_s.sub(/^puppet:/,'')
  end

  # loads in priority order:
  # 1. already loaded here
  # 2. load from parent
  # 3. find it here
  # 4. give up
  #
  def internal_load(typed_name)
    # avoid calling get_entry by looking it up
    te = @named_values[typed_name]
    return te unless te.nil? || te.value.nil?

    te = parent.load_typed(typed_name)
    return te unless te.nil? || te.value.nil?

    # Under some circumstances, the call to the parent loader will have resulted in files being
    # parsed that in turn contained references to the requested entity and hence, caused a
    # recursive call into this loader. This means that the entry might be present now, so a new
    # check must be made.
    te = @named_values[typed_name]
    te.nil? || te.value.nil? ? find(typed_name) : te
  end
end
end
end