Module: Hiera::Backend

Defined in:
lib/hiera/backend.rb,
lib/hiera/backend/json_backend.rb,
lib/hiera/backend/yaml_backend.rb

Defined Under Namespace

Classes: Backend1xWrapper, Json_backend, Yaml_backend

Class Method Summary collapse

Class Method Details

.clear!Object



310
311
312
# File 'lib/hiera/backend.rb', line 310

def clear!
  @backends = {}
end

.datadir(backend, scope) ⇒ Object

Data lives in /var/lib/hiera by default. If a backend supplies a datadir in the config it will be used and subject to variable expansion based on scope



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/hiera/backend.rb', line 31

def datadir(backend, scope)
  backend = backend.to_sym

  if Config[backend] && Config[backend][:datadir]
    dir = Config[backend][:datadir]
  else
    dir = Hiera::Util.var_dir
  end

  if !dir.is_a?(String)
    raise(Hiera::InvalidConfigurationError,
          "datadir for #{backend} cannot be an array")
  end

  interpolate_config(dir, scope, nil)
end

.datafile(backend, scope, source, extension) ⇒ Object

Finds the path to a datafile based on the Backend#datadir and extension

If the file is not found nil is returned



52
53
54
# File 'lib/hiera/backend.rb', line 52

def datafile(backend, scope, source, extension)
  datafile_in(datadir(backend, scope), source, extension)
end

.datafile_in(datadir, source, extension) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



57
58
59
60
61
62
63
64
65
66
# File 'lib/hiera/backend.rb', line 57

def datafile_in(datadir, source, extension)
  file = File.join(datadir, "#{source}.#{extension}")

  if File.exist?(file)
    file
  else
    Hiera.debug("Cannot find datafile #{file}, skipping")
    nil
  end
end

.datasourcefiles(backend, scope, extension, override = nil, hierarchy = nil) {|String, String| ... } ⇒ Object

Constructs a list of data files to search

If you give it a specific hierarchy it will just use that else it will use the global configured one, failing that it will just look in the ‘common’ data source.

An override can be supplied that will be pre-pended to the hierarchy.

The source names will be subject to variable expansion based on scope

Only files that exist will be returned. If the file is missing, then the block will not receive the file.

Yields:

  • (String, String)

    the source string and the name of the resulting file



117
118
119
120
121
122
123
124
125
126
127
# File 'lib/hiera/backend.rb', line 117

def datasourcefiles(backend, scope, extension, override=nil, hierarchy=nil)
  datadir = Backend.datadir(backend, scope)
  Backend.datasources(scope, override, hierarchy) do |source|
    Hiera.debug("Looking for data source #{source}")
    file = datafile_in(datadir, source, extension)

    if file
      yield source, file
    end
  end
end

.datasources(scope, override = nil, hierarchy = nil) ⇒ Object

Constructs a list of data sources to search

If you give it a specific hierarchy it will just use that else it will use the global configured one, failing that it will just look in the ‘common’ data source.

An override can be supplied that will be pre-pended to the hierarchy.

The source names will be subject to variable expansion based on scope



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/hiera/backend.rb', line 79

def datasources(scope, override=nil, hierarchy=nil)
  if hierarchy
    hierarchy = [hierarchy]
  elsif Config.include?(:hierarchy)
    hierarchy = [Config[:hierarchy]].flatten
  else
    hierarchy = ["common"]
  end

  hierarchy.insert(0, override) if override

  hierarchy.flatten.map do |source|
    source = interpolate_config(source, scope, override)
    if source == "" or source =~ /(^\/|\/\/|\/$)/
      Hiera.debug("Ignoring bad definition in :hierarchy: \'#{source}\'")
    else
      yield(source)
    end
  end
end

.find_backend(backend_constant) ⇒ Object



339
340
341
342
# File 'lib/hiera/backend.rb', line 339

def find_backend(backend_constant)
  backend = Backend.const_get(backend_constant).new
  return backend.method(:lookup).arity == 4 ? Backend1xWrapper.new(backend) : backend
end

.interpolate_config(entry, scope, override) ⇒ Object



345
346
347
348
349
350
351
352
353
354
355
356
357
# File 'lib/hiera/backend.rb', line 345

def interpolate_config(entry, scope, override)
  if @config_lookup_context.nil?
    @config_lookup_context = { :is_interpolate_config => true, :order_override => override, :recurse_guard => Hiera::RecursiveGuard.new }
    begin
      Hiera::Interpolate.interpolate(entry, scope, {}, @config_lookup_context)
    ensure
      @config_lookup_context = nil
    end
  else
    # Nested call (will happen when interpolate method 'hiera' is used)
    Hiera::Interpolate.interpolate(entry, scope, {}, @config_lookup_context.merge(:order_override => override))
  end
end

.lookup(key, default, scope, order_override, resolution_type, context = {:recurse_guard => nil}) ⇒ Object

Returns The value that corresponds to the given key or nil if no such value cannot be found.

Parameters:

  • key (String)

    The key to lookup. May be quoted with single or double quotes to avoid subkey traversal on dot characters

  • scope (#[])

    The primary source of data for substitutions.

  • order_override (#[], nil)

    An override that will be pre-pended to the hierarchy definition.

  • resolution_type (Symbol, Hash, nil)

    One of :hash, :array,:priority or a Hash with deep merge behavior and options

  • context (#[]) (defaults to: {:recurse_guard => nil})

    Context used for internal processing

Returns:

  • (Object)

    The value that corresponds to the given key or nil if no such value cannot be found



245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
# File 'lib/hiera/backend.rb', line 245

def lookup(key, default, scope, order_override, resolution_type, context = {:recurse_guard => nil})
  @backends ||= {}
  answer = nil

  # order_override is kept as an explicit argument for backwards compatibility, but should be specified
  # in the context for internal handling.
  context ||= {}
  order_override ||= context[:order_override]
  context[:order_override] ||= order_override

  strategy = resolution_type.is_a?(Hash) ? :hash : resolution_type

  segments = Util.split_key(key) { |problem| ArgumentError.new("#{problem} in key: #{key}") }
  subsegments = nil
  if segments.size > 1
    unless strategy.nil? || strategy == :priority
      raise ArgumentError, "Resolution type :#{strategy} is illegal when accessing values using dotted keys. Offending key was '#{key}'"
    end
    subsegments = segments.drop(1)
  end

  found = false
  Config[:backends].each do |backend|
    backend_constant = "#{backend.capitalize}_backend"
    if constants.include?(backend_constant) || constants.include?(backend_constant.to_sym)
      backend = (@backends[backend] ||= find_backend(backend_constant))
      found_in_backend = false
      new_answer = catch(:no_such_key) do
        if subsegments.nil? 
          value = backend.lookup(segments[0], scope, order_override, resolution_type, context)
        elsif backend.respond_to?(:lookup_with_segments)
          value = backend.lookup_with_segments(segments, scope, order_override, resolution_type, context)
        else
          value = backend.lookup(segments[0], scope, order_override, resolution_type, context)
          value = qualified_lookup(subsegments, value, key) unless subsegments.nil?
        end
        found_in_backend = true
        value
      end
      next unless found_in_backend
      found = true

      case strategy
      when :array
        raise Exception, "Hiera type mismatch for key '#{key}': expected Array and got #{new_answer.class}" unless new_answer.kind_of? Array or new_answer.kind_of? String
        answer ||= []
        answer << new_answer
      when :hash
        raise Exception, "Hiera type mismatch for key '#{key}': expected Hash and got #{new_answer.class}" unless new_answer.kind_of? Hash
        answer ||= {}
        answer = merge_answer(new_answer, answer, resolution_type)
      else
        answer = new_answer
        break
      end
    end
  end

  answer = resolve_answer(answer, strategy) unless answer.nil?
  answer = parse_string(default, scope, {}, context) if !found && default.is_a?(String)

  return default if !found && answer.nil?
  return answer
end

.merge_answer(left, right, resolution_type = nil) ⇒ Hash

Merges two hashes answers with the given or configured merge behavior. Behavior can be given by passing resolution_type as a Hash

:merge_behavior: {:native|:deep|:deeper}

Deep merge options use the Hash utility function provided by [deep_merge](github.com/danielsdeleo/deep_merge) It uses the compatibility mode [deep_merge](github.com/danielsdeleo/deep_merge#using-deep_merge-in-rails)

:native => Native Hash.merge
:deep   => Use Hash.deeper_merge
:deeper => Use Hash.deeper_merge!

Parameters:

  • left (Hash)

    left side of the merge

  • right (Hash)

    right side of the merge

  • resolution_type (String, Hash) (defaults to: nil)

    The merge type, or if hash, the merge behavior and options

Returns:

  • (Hash)

    The merged result

See Also:



205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/hiera/backend.rb', line 205

def merge_answer(left,right,resolution_type=nil)
  behavior, options =
    if resolution_type.is_a?(Hash)
      merge = resolution_type.clone
      [merge.delete(:behavior), merge]
    else
      [Config[:merge_behavior], Config[:deep_merge_options] || {}]
    end

  case behavior
  when :deeper,'deeper'
    left.deeper_merge!(right, options)
  when :deep,'deep'
    left.deeper_merge(right, options)
  else # Native and undefined
    left.merge(right)
  end
end

.parse_answer(data, scope, extra_data = {}, context = {:recurse_guard => nil, :order_override => nil}) ⇒ Object

Parses a answer received from data files

Ultimately it just pass the data through parse_string but it makes some effort to handle arrays of strings as well



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/hiera/backend.rb', line 153

def parse_answer(data, scope, extra_data={}, context={:recurse_guard => nil, :order_override => nil})
  if data.is_a?(Numeric) or data.is_a?(TrueClass) or data.is_a?(FalseClass)
    return data
  elsif data.is_a?(String)
    return parse_string(data, scope, extra_data, context)
  elsif data.is_a?(Hash)
    answer = {}
    data.each_pair do |key, val|
      interpolated_key = parse_string(key, scope, extra_data, context)
      answer[interpolated_key] = parse_answer(val, scope, extra_data, context)
    end

    return answer
  elsif data.is_a?(Array)
    answer = []
    data.each do |item|
      answer << parse_answer(item, scope, extra_data, context)
    end

    return answer
  end
end

.parse_string(data, scope, extra_data = {}, context = {:recurse_guard => nil, :order_override => nil}) ⇒ String

Parse a string like '%{foo}' against a supplied scope and additional scope. If either scope or extra_scope includes the variable ‘foo’, then it will be replaced else an empty string will be placed.

If both scope and extra_data has “foo”, then the value in scope will be used.

Parameters:

  • data (String)

    The string to perform substitutions on. This will not be modified, instead a new string will be returned.

  • scope (#[])

    The primary source of data for substitutions.

  • extra_data (#[]) (defaults to: {})

    The secondary source of data for substitutions.

  • context (#[]) (defaults to: {:recurse_guard => nil, :order_override => nil})

    Context can include :recurse_guard and :order_override.

Returns:

  • (String)

    A copy of the data with all instances of %{...} replaced.



145
146
147
# File 'lib/hiera/backend.rb', line 145

def parse_string(data, scope, extra_data={}, context={:recurse_guard => nil, :order_override => nil})
  Hiera::Interpolate.interpolate(data, scope, extra_data, context)
end

.qualified_lookup(segments, hash, full_key = nil) ⇒ Object



314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
# File 'lib/hiera/backend.rb', line 314

def qualified_lookup(segments, hash, full_key = nil)
  value = hash
  segments.each do |segment|
    throw :no_such_key if value.nil?
    if segment =~ /^[0-9]+$/
      segment = segment.to_i
      unless value.instance_of?(Array)
        suffix = full_key.nil? ? '' : " from key '#{full_key}'"
        raise Exception,
          "Hiera type mismatch: Got #{value.class.name} when Array was expected to access value using '#{segment}'#{suffix}"
      end
      throw :no_such_key unless segment < value.size
    else
      unless value.respond_to?(:'[]') && !(value.instance_of?(Array) || value.instance_of?(String))
        suffix = full_key.nil? ? '' : " from key '#{full_key}'"
        raise Exception,
          "Hiera type mismatch: Got #{value.class.name} when a hash-like object was expected to access value using '#{segment}'#{suffix}"
      end
      throw :no_such_key unless value.include?(segment)
    end
    value = value[segment]
  end
  value
end

.resolve_answer(answer, resolution_type) ⇒ Object



176
177
178
179
180
181
182
183
184
185
# File 'lib/hiera/backend.rb', line 176

def resolve_answer(answer, resolution_type)
  case resolution_type
  when :array
    [answer].flatten.uniq.compact
  when :hash
    answer # Hash structure should be preserved
  else
    answer
  end
end