class Puppet::Pops::Loader::RubyLegacyFunctionInstantiator
Constants
- UNKNOWN
Public Class Methods
Produces an instance of the Function class with the given typed_name, or fails with an error if the given ruby source does not produce this instance when evaluated.
@param loader [Puppet::Pops::Loader::Loader] The loader the function is associated with @param typed_name [Puppet::Pops::Loader::TypedName] the type / name of the function to load @param source_ref [URI, String] a reference to the source / origin of the ruby code to evaluate @param ruby_code_string [String] ruby code in a string
@return [Puppet::Pops::Functions.Function] - an instantiated function with global scope closure associated with the given loader
# File lib/puppet/pops/loader/ruby_legacy_function_instantiator.rb 19 def self.create(loader, typed_name, source_ref, ruby_code_string) 20 # Assert content of 3x function by parsing 21 assertion_result = [] 22 if assert_code(ruby_code_string, source_ref, assertion_result) 23 unless ruby_code_string.is_a?(String) && assertion_result.include?(:found_newfunction) 24 raise ArgumentError, _("The code loaded from %{source_ref} does not seem to be a Puppet 3x API function - no 'newfunction' call.") % { source_ref: source_ref } 25 end 26 end 27 28 # make the private loader available in a binding to allow it to be passed on 29 loader_for_function = loader.private_loader 30 here = get_binding(loader_for_function) 31 32 # Avoid reloading the function if already loaded via one of the APIs that trigger 3x function loading 33 # Check if function is already loaded the 3x way (and obviously not the 4x way since we would not be here in the 34 # first place. 35 environment = Puppet.lookup(:current_environment) 36 func_info = Puppet::Parser::Functions.environment_module(environment).get_function_info(typed_name.name.to_sym) 37 if func_info.nil? 38 # This will do the 3x loading and define the "function_<name>" and "real_function_<name>" methods 39 # in the anonymous module used to hold function definitions. 40 # 41 func_info = eval(ruby_code_string, here, source_ref, 1) 42 43 # Validate what was loaded 44 unless func_info.is_a?(Hash) 45 # TRANSLATORS - the word 'newfunction' should not be translated as it is a method name. 46 raise ArgumentError, _("Illegal legacy function definition! The code loaded from %{source_ref} did not return the result of calling 'newfunction'. Got '%{klass}'") % { source_ref: source_ref, klass: func_info.class } 47 end 48 unless func_info[:name] == "function_#{typed_name.name()}" 49 raise ArgumentError, _("The code loaded from %{source_ref} produced mis-matched name, expected 'function_%{type_name}', got '%{created_name}'") % { 50 source_ref: source_ref, type_name: typed_name.name, created_name: func_info[:name] } 51 end 52 end 53 54 created = Puppet::Functions::Function3x.create_function(typed_name.name(), func_info, loader_for_function) 55 56 # create the function instance - it needs closure (scope), and loader (i.e. where it should start searching for things 57 # when calling functions etc. 58 # It should be bound to global scope 59 60 # Sets closure scope to nil, to let it be picked up at runtime from Puppet.lookup(:global_scope) 61 # If function definition used the loader from the binding to create a new loader, that loader wins 62 created.new(nil, loader_for_function) 63 end
Private Class Methods
# File lib/puppet/pops/loader/ruby_legacy_function_instantiator.rb 73 def self.assert_code(code_string, source_ref, result) 74 ripped = Ripper.sexp(code_string) 75 return false if ripped.nil? # Let the next real parse crash and tell where and what is wrong 76 ripped.each {|x| walk(x, source_ref, result) } 77 true 78 end
Extracts the method name and line number from the Ripper Rast for an id entry. The expected input (a result from Ripper :@ident entry) is an array with:
Returns an Array; a tuple with method name and line number or “<unknown>” if either is missing, or format is not the expected
# File lib/puppet/pops/loader/ruby_legacy_function_instantiator.rb 117 def self.extract_name_line(x) 118 (x.is_a?(Array) ? [ x[1], x[2].is_a?(Array) ? x[2][1] : nil] : [nil, nil]).map {|v| v.nil? ? UNKNOWN : v } 119 end
# File lib/puppet/pops/loader/ruby_legacy_function_instantiator.rb 104 def self.find_identity(rast) 105 rast.find{|x| x.is_a?(Array) && x[0] == :@ident } 106 end
Produces a binding where the given loader is bound as a local variable (loader_injected_arg). This variable can be used in loaded ruby code - e.g. to call Puppet::Function.create_loaded_function(:name, loader,…)
# File lib/puppet/pops/loader/ruby_legacy_function_instantiator.rb 68 def self.get_binding(loader_injected_arg) 69 binding 70 end
# File lib/puppet/pops/loader/ruby_legacy_function_instantiator.rb 81 def self.walk(x, source_ref, result) 82 return unless x.is_a?(Array) 83 first = x[0] 84 case first 85 when :fcall, :call 86 # Ripper returns a :fcall for a function call in a module (want to know there is a call to newfunction()). 87 # And it returns :call for a qualified named call 88 identity_part = find_identity(x) 89 result << :found_newfunction if identity_part.is_a?(Array) && identity_part[1] == 'newfunction' 90 when :def, :defs 91 # There should not be any calls to def in a 3x function 92 mname, mline = extract_name_line(find_identity(x)) 93 raise SecurityError, _("Illegal method definition of method '%{method_name}' in source %{source_ref} on line %{line} in legacy function. See %{url} for more information") % { 94 method_name: mname, 95 source_ref: source_ref, 96 line: mline, 97 url: "https://puppet.com/docs/puppet/latest/functions_refactor_legacy.html" 98 } 99 end 100 x.each {|v| walk(v, source_ref, result) } 101 end