Class: Puppet::Pops::Binder::Lookup

Inherits:
Object
  • Object
show all
Defined in:
lib/puppet/pops/binder/lookup.rb

Overview

This class is the backing implementation of the Puppet function ‘lookup’. See puppet/parser/functions/lookup.rb for documentation.

Defined Under Namespace

Classes: PrivateNotFoundMarker

Class Method Summary collapse

Class Method Details

.fail(msg) ⇒ Object

Raises:



54
55
56
# File 'lib/puppet/pops/binder/lookup.rb', line 54

def self.fail(msg)
  raise Puppet::ParseError, "Function lookup() " + msg
end

.fail_lookup(names) ⇒ Object



58
59
60
61
62
63
64
65
# File 'lib/puppet/pops/binder/lookup.rb', line 58

def self.fail_lookup(names)
  name_part = if names.size == 1
    "the name '#{names[0]}'"
  else
    "any of the names ['" + names.join(', ') + "']"
  end
  fail("did not find a value for #{name_part}")
end

.is_nil_or_undef?(x) ⇒ Boolean

Returns:

  • (Boolean)


115
116
117
# File 'lib/puppet/pops/binder/lookup.rb', line 115

def self.is_nil_or_undef?(x)
  x.nil? || x == :undef
end

.lookup(scope, args) ⇒ Object

This is the method called from the puppet/parser/functions/lookup.rb

Parameters:

  • args (Array)

    array following the puppet function call conventions



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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/puppet/pops/binder/lookup.rb', line 142

def self.lookup(scope, args)
  type_calculator = Puppet::Pops::Types::TypeCalculator.new
  options = parse_lookup_args(args)
  validate_options(options, type_calculator)
  names = [options[:name]].flatten
  type = options[:type]

  result_with_name = names.reduce([]) do |memo, name|
    break memo if !memo[1].nil?
    [name, search_for(scope, type, name, options)]
  end

  result = if result_with_name[1].nil?
    # not found, use default (which may be nil), the default is already type checked
    options[:default]
  else
    # injector.lookup is type-safe already do no need to type check the result
    result_with_name[1]
  end

  # If a block is given it is called with :undef passed as 'nil' since the lookup function
  # is available from 3x with --binder turned on, and the evaluation is always 4x.
  # TODO PUPPET4: Simply pass the value
  #
  result = if pblock = options[:pblock]
    result2 = case pblock.parameter_count
    when 1
      pblock.call(scope, undef_as_nil(result))
    when 2
      pblock.call(scope, result_with_name[ 0 ], undef_as_nil(result))
    else
      pblock.call(scope, result_with_name[ 0 ], undef_as_nil(result), undef_as_nil(options[ :default ]))
    end

    # if the given result was returned, there is no need to type-check it again
    if !result2.equal?(result)
      t = type_calculator.infer(undef_as_nil(result2))
      if !type_calculator.assignable?(type, t)
        fail "the value produced by the given code block #{type_mismatch(type_calculator, type, t)}"
      end
    end
    result2
  else
    result
  end

  # Finally, the result if nil must be acceptable or an error is raised
  if is_nil_or_undef?(result) && !options[:accept_undef]
    fail_lookup(names)
  else
    # Since the function may be used without future parser being in effect, nil is not handled in a good
    # way, and should instead be turned into :undef.
    # TODO PUPPET4: Simply return the result
    #
    Puppet[:parser] == 'future' ? result : nil_as_undef(result)
  end
end

.nil_as_undef(x) ⇒ Object



107
108
109
# File 'lib/puppet/pops/binder/lookup.rb', line 107

def self.nil_as_undef(x)
  x.nil? ? :undef : x
end

.parse_lookup_args(args) ⇒ Object



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
# File 'lib/puppet/pops/binder/lookup.rb', line 6

def self.parse_lookup_args(args)
  options = {}
  pblock = if args[-1].respond_to?(:puppet_lambda)
    args.pop
  end

  case args.size
  when 1
    # name, or all options
    if args[ 0 ].is_a?(Hash)
      options = to_symbolic_hash(args[ 0 ])
    else
      options[ :name ] = args[ 0 ]
    end

  when 2
    # name and type, or name and options
    if args[ 1 ].is_a?(Hash)
      options = to_symbolic_hash(args[ 1 ])
      options[:name] = args[ 0 ] # silently overwrite option with given name
    else
      options[:name] = args[ 0 ]
      options[:type] = args[ 1 ]
    end

  when 3
    # name, type, default (no options)
    options[ :name ] = args[ 0 ]
    options[ :type ] = args[ 1 ]
    options[ :default ] = args[ 2 ]
  else
    raise Puppet::ParseError, "The lookup function accepts 1-3 arguments, got #{args.size}"
  end
  options[:pblock] = pblock
  options
end

.search_for(scope, type, name, options) ⇒ Object



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/puppet/pops/binder/lookup.rb', line 124

def self.search_for(scope, type, name, options)
  # search in order, override, injector, hiera, then extra
  if !(result = options[:override][name]).nil?
    result
  elsif !(result = scope.compiler.injector.lookup(scope, type, name)).nil?
    result
 else
   result = scope.function_hiera([name, PrivateNotFoundMarker])
   if !result.nil? && result != PrivateNotFoundMarker
     result
   else
     options[:extra][name]
   end
 end
end

.to_symbolic_hash(input) ⇒ Object



43
44
45
46
47
48
# File 'lib/puppet/pops/binder/lookup.rb', line 43

def self.to_symbolic_hash(input)
  names = [:name, :type, :default, :accept_undef, :extra, :override]
  options = {}
  names.each {|n| options[n] = undef_as_nil(input[n.to_s] || input[n]) }
  options
end

.type_mismatch(type_calculator, expected, got) ⇒ Object



50
51
52
# File 'lib/puppet/pops/binder/lookup.rb', line 50

def self.type_mismatch(type_calculator, expected, got)
  "has wrong type, expected #{type_calculator.string(expected)}, got #{type_calculator.string(got)}"
end

.undef_as_nil(x) ⇒ Object



111
112
113
# File 'lib/puppet/pops/binder/lookup.rb', line 111

def self.undef_as_nil(x)
  is_nil_or_undef?(x) ? nil : x
end

.validate_options(options, type_calculator) ⇒ Object



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
# File 'lib/puppet/pops/binder/lookup.rb', line 67

def self.validate_options(options, type_calculator)
  type_parser = Puppet::Pops::Types::TypeParser.new
  name_type = type_parser.parse('Variant[Array[String], String]')

  if is_nil_or_undef?(options[:name]) || options[:name].is_a?(Array) && options[:name].empty?
    fail ("requires a name, or array of names. Got nothing to lookup.")
  end

  t = type_calculator.infer(options[:name])
  if ! type_calculator.assignable?(name_type, t)
    fail("given 'name' argument, #{type_mismatch(type_calculator, options[:name], t)}")
  end

  # unless a type is already given (future case), parse the type (or default 'Data'), fails if invalid type is given
  unless options[:type].is_a?(Puppet::Pops::Types::PAnyType)
    options[:type] = type_parser.parse(options[:type] || 'Data')
  end

  # default value must comply with the given type
  if options[:default]
    t = type_calculator.infer(options[:default])
    if ! type_calculator.assignable?(options[:type], t)
      fail("'default' value #{type_mismatch(type_calculator, options[:type], t)}")
    end
  end

  if options[:extra] && !options[:extra].is_a?(Hash)
    # do not perform inference here, it is enough to know that it is not a hash
    fail("'extra' value must be a Hash, got #{options[:extra].class}")
  end
  options[:extra] = {} unless options[:extra]

  if options[:override] && !options[:override].is_a?(Hash)
    # do not perform inference here, it is enough to know that it is not a hash
    fail("'override' value must be a Hash, got #{options[:extra].class}")
  end
  options[:override] = {} unless options[:override]

end