puppetlabs/puppet · error · ArgumentError

The code loaded from %{source_ref} does not seem to be a Pup

Error message

The code loaded from %{source_ref} does not seem to be a Puppet 3x API function - no 'newfunction' call.

What it means

Raised by RubyLegacyFunctionInstantiator when the Ruby source parses cleanly (Ripper.sexp succeeded in assert_code) but the parse tree contains no call to newfunction. Legacy 3x functions must be built with Puppet::Parser::Functions.newfunction; this is a sanity gate before eval'ing the code. If Ripper cannot parse the source at all, the check is skipped and eval fails later instead.

Source

Thrown at lib/puppet/pops/loader/ruby_legacy_function_instantiator.rb:25

class Puppet::Pops::Loader::RubyLegacyFunctionInstantiator
  UNKNOWN = '<unknown>'

  # 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
  #
  def self.create(loader, typed_name, source_ref, ruby_code_string)
    # Assert content of 3x function by parsing
    assertion_result = []
    if assert_code(ruby_code_string, source_ref, assertion_result)
      unless ruby_code_string.is_a?(String) && assertion_result.include?(:found_newfunction)
        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 }
      end
    end

    # make the private loader available in a binding to allow it to be passed on
    loader_for_function = loader.private_loader
    here = get_binding(loader_for_function)

    # Avoid reloading the function if already loaded via one of the APIs that trigger 3x function loading
    # Check if function is already loaded the 3x way (and obviously not the 4x way since we would not be here in the
    # first place.
    environment = Puppet.lookup(:current_environment)
    func_info = Puppet::Parser::Functions.environment_module(environment).get_function_info(typed_name.name.to_sym)
    if func_info.nil?
      # This will do the 3x loading and define the "function_<name>" and "real_function_<name>" methods
      # in the anonymous module used to hold function definitions.
      #
      func_info = eval(ruby_code_string, here, source_ref, 1) # rubocop:disable Security/Eval

View on GitHub (pinned to e227c27540)

Solutions

  1. Define the function with Puppet::Parser::Functions.newfunction(:name, :type => :rvalue) { |args| ... }
  2. If the code is 4x API, move it to lib/puppet/functions/<module>/<name>.rb with proper namespacing
  3. Confirm via source_ref which file the legacy loader is actually reading

Example fix

# before - lib/puppet/parser/functions/double.rb
Puppet::Functions.create_function(:double) do
  def double(x); x * 2; end
end

# after
module Puppet::Parser::Functions
  newfunction(:double, :type => :rvalue) do |args|
    args[0] * 2
  end
end
Defensive patterns

Strategy: validation

Validate before calling

sexp = Ripper.sexp(ruby_source)
if sexp
  found = sexp.to_s.include?('newfunction')
  raise ArgumentError, "#{path} is not a 3x function - no newfunction call" unless found
end

Try / catch

begin
  fn = loader.load_typed(typed_name)
rescue ArgumentError => e
  raise unless e.message.include?("no 'newfunction' call")
  warn "not a legacy 3x function: #{e.message}"
end

Prevention

When it happens

Trigger: assert_code returns true but assertion_result lacks :found_newfunction: a file in lib/puppet/parser/functions/ (or resolved by the legacy loader) that calls something else -- e.g. 4x-style Puppet::Functions.create_function code in the 3x directory, or plain Ruby with no newfunction call.

Common situations: Migrating functions between 3x and 4x APIs and leaving the file in the wrong directory; vendored gem code that does not use the 3x API; helper modules accidentally named like functions.

Related errors


AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21). Data as JSON: /api/errors/3f2f6dd1d93d71d4. Report an issue: GitHub.