From c41bd4359cfd97447c872b32609207b48b5abfc9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torbj=C3=B6rn=20L=C3=B6nnemark?= <ketl@nsc.liu.se>
Date: Thu, 10 Nov 2022 16:42:30 +0100
Subject: [PATCH] Convert llnl_hostlist_expand to Puppet 4.x API

In Puppet 7, extra methods within a function definition block are no
longer permitted when using the 3.x API.

Attempting to use such functions aborts catalog compilation with:

    Error: Could not retrieve catalog from remote server:
    Error 500 on SERVER: Internal Server Error: org.jruby.exceptions.SecurityError:
    (SecurityError) Illegal method definition of method 'example_function' in source .../example-module/lib/puppet/parser/functions/example_function.rb on line XXX in legacy function.
    See https://puppet.com/docs/puppet/latest/functions_refactor_legacy.html for more information

We fix the error by updating the only function that has extra methods to
use the 4.x API.

This change drops support for Puppet 3.

https://puppet.com/docs/puppet/7/functions_refactor_legacy.html
https://puppet.com/docs/puppet/5.5/functions_legacy.html
https://puppet.com/docs/puppet/7/functions_ruby_overview.html
---
 lib/puppet/functions/llnl_hostlist_expand.rb  | 216 +++++++++++++++++
 .../parser/functions/llnl_hostlist_expand.rb  | 218 ------------------
 2 files changed, 216 insertions(+), 218 deletions(-)
 create mode 100644 lib/puppet/functions/llnl_hostlist_expand.rb
 delete mode 100644 lib/puppet/parser/functions/llnl_hostlist_expand.rb

diff --git a/lib/puppet/functions/llnl_hostlist_expand.rb b/lib/puppet/functions/llnl_hostlist_expand.rb
new file mode 100644
index 0000000..fd0726f
--- /dev/null
+++ b/lib/puppet/functions/llnl_hostlist_expand.rb
@@ -0,0 +1,216 @@
+#	-*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2022
+#		Thomas Bellman, National Supercomputer Centre, Sweden
+#		Kent Engström, National Supercomputer Centre, Sweden
+#		Torbjörn Lönnemark, National Supercomputer Centre, Sweden
+#
+# Licensed under the GNU GPL v3+; see the README file for more information.
+#
+# This implementation is heavily based on the python-hostlist Python
+# module by the above people (mostly Kent Engström).  It can be found
+# at https://www.nsc.liu.se/~kent/python-hostlist/.
+
+
+# Expand an LLNL hostlist expression into a list of individual names.
+#
+# (Note: While this talks about 'hosts', there is no connection with
+# e.g. DNS.  These are just strings, and you can use them to name
+# anything or nothing.)
+#
+# A couple of examples probably is the easiest way to explain:
+#
+#     llnl_hostlist_expand('n[8-11]')
+#       ==>  ['n8', 'n9', 'n10', 'n11']
+#
+#     llnl_hostlist_expand('n[008-11]')
+#       ==>  ['n008', 'n009', 'n010', 'n011']
+#
+#     llnl_hostlist_expand('n[6-8,01-3]')
+#       ==>  ['n01', 'n02', 'n03', 'n6', 'n7', 'n8']
+#
+#     llnl_hostlist_expand('n[1-3]b,x[1-3].[07-08],a[30,20,10]')
+#       ==>  ['a10', 'a20', 'a30', 'n1b', 'n2b', 'n3b',
+#             'x1.07', 'x1.08', 'x2.07', 'x2.08', 'x3.07', 'x3.08']
+#
+# The resulting lists are always sorted in a \"natural\" order, and
+# duplicates are removed.
+#
+# This syntax is used by several programs originating at the Lawrence
+# Livermore National Laboratory, e.g. SLURM and pdsh.
+Puppet::Functions.create_function(:llnl_hostlist_expand) do
+    def llnl_hostlist_expand(*args)
+	if args.length != 1
+	    raise(Puppet::ParseError,
+		  "llnl_hostlist_expand(): Wrong number of arguments")
+	end
+	hostlist = args[0]
+	NSC_Utils::llnl_hostlist_expand(hostlist)
+    end
+
+
+    # Helper functions, doing the actua work
+    module NSC_Utils
+
+	# Guard against ridiculously long expanded lists
+	LLNL_HOSTLIST_MAXSIZE = 100000
+
+	# Exception raised for bad hostlists
+	class BadLLNLHostlist < RuntimeError; end
+
+
+	# Expand a hostlist expression string to a Python list.
+	#
+	# Example: expand_hostlist("n[9-11],d[01-02]") ==>
+	#          ['n9', 'n10', 'n11', 'd01', 'd02']
+	#
+	# Duplicates will be removed, and the results will be sorted.
+	#
+	def llnl_hostlist_expand(hostlist)
+
+	    results = []
+	    bracket_level = 0
+	    part = ""
+
+	    (hostlist+",").each_char do |c|
+		if c == "," && bracket_level == 0
+		    # Comma at top level, split!
+		    if part != ""
+			results += NSC_Utils::__hostlist_expand_part(part)
+		    end
+		    part = ""
+		else
+		    part += c
+		end
+
+		if c == "["
+		    bracket_level += 1
+		elsif c == "]"
+		    bracket_level -= 1
+		end
+
+		if bracket_level > 1
+		    raise(NSC_Utils::BadLLNLHostlist, "nested brackets")
+		elsif bracket_level < 0
+		    raise(NSC_Utils::BadLLNLHostlist, "unbalanced brackets")
+		end
+
+	    end
+
+	    if bracket_level > 0
+		raise(NSC_Utils::BadLLNLHostlist, "unbalanced brackets")
+	    end
+
+	    results.uniq!
+
+	    # Sort the results in a "natural" order, making sure that e.g.
+	    # "n9" comes before "n10".
+	    #
+	    # Split names into a list of alternating numerical (decimal) and
+	    # non-numerical parts, convert the numerical parts into Intgers,
+	    # and compare the lists.  This splitting will result in an empty
+	    # string first if the name starts with a number, which means that
+	    # the list comparison will always compare elements of equal types.
+	    #
+	    # Converting all elements once before sorting, and then back after,
+	    # is significantly faster than calling sort with a comparison block
+	    # which does the splitting "on demand" for each comparison.
+	    #
+	    results.collect! { |name|
+		name.split(/([0-9]+)/).collect { |part|
+		    /^[0-9]+$/ =~ part ? part.to_i(10) : part
+		}
+	    }
+	    results.sort!
+	    results.collect! { |partlist| partlist.join("") }
+
+	    return results
+
+	end
+	module_function :llnl_hostlist_expand
+
+
+	# Expand a part (e.g. "x[1-2]y[1-3][1-3]") (no outer level commas).
+	#
+	def __hostlist_expand_part(s)
+
+	    # Base case: the empty part expand to the singleton list of ""
+	    return [""]  if s == ""
+
+	    # Split into:
+	    # 1) prefix string (may be empty)
+	    # 2) rangelist in brackets (may be missing)
+	    # 3) the rest
+
+	    /([^,\[]*)(\[[^\]]*\])?(.*)/ =~ s
+	    prefix, rangelist, rest = $1, $2, $3
+
+	    # Expand the rest first (here is where we recurse!)
+	    rest_expanded = NSC_Utils::__hostlist_expand_part(rest)
+
+	    # Expand our own part
+	    if rangelist.nil?
+		# If there is no rangelist, our own contribution is the prefix only
+		us_expanded = [prefix]
+	    else
+		# Otherwise expand the rangelist (adding the prefix before)
+		us_expanded = NSC_Utils::__hostlist_expand_rangelist(
+		    prefix, rangelist[1..-2])
+	    end
+
+	    # Combine our list with the list from the expansion of the rest
+	    # (but guard against too large results first)
+	    if us_expanded.length * rest_expanded.length > NSC_Utils::LLNL_HOSTLIST_MAXSIZE
+		raise(NSC_Utils::BadLLNLHostlist, "results too large")
+	    end
+
+	    result = us_expanded.product(rest_expanded).collect {
+		|us_part, rest_part| us_part + rest_part
+	    }
+	    return result
+
+	end
+	module_function :__hostlist_expand_part
+
+
+	# Expand a rangelist (e.g. "1-10,14"), putting a prefix before.
+	#
+	def __hostlist_expand_rangelist(prefix, rangelist)
+	    # Split at commas and expand each range separately
+	    results = []
+	    rangelist.split(",").each do |range_|
+		results += NSC_Utils::__hostlist_expand_range(prefix, range_)
+	    end
+	    return results
+	end
+	module_function :__hostlist_expand_rangelist
+
+
+	# Expand a range (e.g. 1-10 or 14), putting a prefix before.
+	#
+	def __hostlist_expand_range(prefix, range_)
+
+	    # Check for a single number first
+	    return [ prefix + range_ ]  if /^[0-9]+$/ =~ range_
+
+	    # Otherwise split low-high
+	    if /^([0-9]+)-([0-9]+)$/ !~ range_
+		raise(NSC_Utils::BadLLNLHostlist, "bad range")
+	    end
+	    width = $1.length
+	    low, high = $1.to_i, $2.to_i
+	    if high <low
+		raise(NSC_Utils::BadLLNLHostlist, "start > stop")
+	    elsif high - low > NSC_Utils::LLNL_HOSTLIST_MAXSIZE
+		raise(NSC_Utils::BadLLNLHostlist, "range too large")
+	    end
+
+	    results = (low .. high).collect { |i|
+		"%s%0*d" % [ prefix, width, i ]
+	    }
+	    return results
+	end
+	module_function :__hostlist_expand_range
+
+    end
+end
diff --git a/lib/puppet/parser/functions/llnl_hostlist_expand.rb b/lib/puppet/parser/functions/llnl_hostlist_expand.rb
deleted file mode 100644
index 9699bc4..0000000
--- a/lib/puppet/parser/functions/llnl_hostlist_expand.rb
+++ /dev/null
@@ -1,218 +0,0 @@
-#	-*- coding: utf-8 -*-
-#
-# Copyright (C) 2008-2022
-#		Thomas Bellman, National Supercomputer Centre, Sweden
-#		Kent Engström, National Supercomputer Centre, Sweden
-#		Torbjörn Lönnemark, National Supercomputer Centre, Sweden
-#
-# Licensed under the GNU GPL v3+; see the README file for more information.
-#
-# This implementation is heavily based on the python-hostlist Python
-# module by the above people (mostly Kent Engström).  It can be found
-# at https://www.nsc.liu.se/~kent/python-hostlist/.
-
-
-module Puppet::Parser::Functions
-    newfunction(:llnl_hostlist_expand, :type => :rvalue, :doc => "\
-	Expand an LLNL hostlist expression into a list of individual names.
-
-	(Note: While this talks about 'hosts', there is no connection with
-	e.g. DNS.  These are just strings, and you can use them to name
-	anything or nothing.)
-
-	A couple of examples probably is the easiest way to explain:
-
-	    llnl_hostlist_expand('n[8-11]')
-	      ==>  ['n8', 'n9', 'n10', 'n11']
-
-	    llnl_hostlist_expand('n[008-11]')
-	      ==>  ['n008', 'n009', 'n010', 'n011']
-
-	    llnl_hostlist_expand('n[6-8,01-3]')
-	      ==>  ['n01', 'n02', 'n03', 'n6', 'n7', 'n8']
-
-	    llnl_hostlist_expand('n[1-3]b,x[1-3].[07-08],a[30,20,10]')
-	      ==>  ['a10', 'a20', 'a30', 'n1b', 'n2b', 'n3b',
-		    'x1.07', 'x1.08', 'x2.07', 'x2.08', 'x3.07', 'x3.08']
-
-	The resulting lists are always sorted in a \"natural\" order, and
-	duplicates are removed.
-
-	This syntax is used by several programs originating at the Lawrence
-	Livermore National Laboratory, e.g. SLURM and pdsh.
-    ") \
-    do |args|
-	if args.length != 1
-	    raise(Puppet::ParseError,
-		  "llnl_hostlist_expand(): Wrong number of arguments")
-	end
-	hostlist = args[0]
-	NSC_Utils::llnl_hostlist_expand(hostlist)
-    end
-end
-
-
-# Helper functions, doing the actua work
-module NSC_Utils
-
-    # Guard against ridiculously long expanded lists
-    LLNL_HOSTLIST_MAXSIZE = 100000
-
-    # Exception raised for bad hostlists
-    class BadLLNLHostlist < RuntimeError; end
-
-
-    # Expand a hostlist expression string to a Python list.
-    #
-    # Example: expand_hostlist("n[9-11],d[01-02]") ==>
-    #          ['n9', 'n10', 'n11', 'd01', 'd02']
-    #
-    # Duplicates will be removed, and the results will be sorted.
-    #
-    def llnl_hostlist_expand(hostlist)
-
-	results = []
-	bracket_level = 0
-	part = ""
-
-	(hostlist+",").each_char do |c|
-	    if c == "," && bracket_level == 0
-		# Comma at top level, split!
-		if part != ""
-		    results += NSC_Utils::__hostlist_expand_part(part)
-		end
-		part = ""
-	    else
-		part += c
-	    end
-
-	    if c == "["
-		bracket_level += 1
-	    elsif c == "]"
-		bracket_level -= 1
-	    end
-
-	    if bracket_level > 1
-		raise(NSC_Utils::BadLLNLHostlist, "nested brackets")
-	    elsif bracket_level < 0
-		raise(NSC_Utils::BadLLNLHostlist, "unbalanced brackets")
-	    end
-
-	end
-
-	if bracket_level > 0
-	    raise(NSC_Utils::BadLLNLHostlist, "unbalanced brackets")
-	end
-
-	results.uniq!
-
-	# Sort the results in a "natural" order, making sure that e.g.
-	# "n9" comes before "n10".
-	#
-	# Split names into a list of alternating numerical (decimal) and
-	# non-numerical parts, convert the numerical parts into Intgers,
-	# and compare the lists.  This splitting will result in an empty
-	# string first if the name starts with a number, which means that
-	# the list comparison will always compare elements of equal types.
-	#
-	# Converting all elements once before sorting, and then back after,
-	# is significantly faster than calling sort with a comparison block
-	# which does the splitting "on demand" for each comparison.
-	#
-	results.collect! { |name|
-	    name.split(/([0-9]+)/).collect { |part|
-		/^[0-9]+$/ =~ part ? part.to_i(10) : part
-	    }
-	}
-	results.sort!
-	results.collect! { |partlist| partlist.join("") }
-
-	return results
-
-    end
-    module_function :llnl_hostlist_expand
-
-
-    # Expand a part (e.g. "x[1-2]y[1-3][1-3]") (no outer level commas).
-    #
-    def __hostlist_expand_part(s)
-
-	# Base case: the empty part expand to the singleton list of ""
-	return [""]  if s == ""
-
-	# Split into:
-	# 1) prefix string (may be empty)
-	# 2) rangelist in brackets (may be missing)
-	# 3) the rest
-
-	/([^,\[]*)(\[[^\]]*\])?(.*)/ =~ s
-	prefix, rangelist, rest = $1, $2, $3
-
-	# Expand the rest first (here is where we recurse!)
-	rest_expanded = NSC_Utils::__hostlist_expand_part(rest)
-
-	# Expand our own part
-	if rangelist.nil?
-	    # If there is no rangelist, our own contribution is the prefix only
-	    us_expanded = [prefix]
-	else
-	    # Otherwise expand the rangelist (adding the prefix before)
-	    us_expanded = NSC_Utils::__hostlist_expand_rangelist(
-		prefix, rangelist[1..-2])
-	end
-
-	# Combine our list with the list from the expansion of the rest
-	# (but guard against too large results first)
-	if us_expanded.length * rest_expanded.length > NSC_Utils::LLNL_HOSTLIST_MAXSIZE
-	    raise(NSC_Utils::BadLLNLHostlist, "results too large")
-	end
-
-	result = us_expanded.product(rest_expanded).collect {
-	    |us_part, rest_part| us_part + rest_part
-	}
-	return result
-
-    end
-    module_function :__hostlist_expand_part
-
-
-    # Expand a rangelist (e.g. "1-10,14"), putting a prefix before.
-    #
-    def __hostlist_expand_rangelist(prefix, rangelist)
-	# Split at commas and expand each range separately
-	results = []
-	rangelist.split(",").each do |range_|
-	    results += NSC_Utils::__hostlist_expand_range(prefix, range_)
-	end
-	return results
-    end
-    module_function :__hostlist_expand_rangelist
-
-
-    # Expand a range (e.g. 1-10 or 14), putting a prefix before.
-    #
-    def __hostlist_expand_range(prefix, range_)
-
-	# Check for a single number first
-	return [ prefix + range_ ]  if /^[0-9]+$/ =~ range_
-
-	# Otherwise split low-high
-	if /^([0-9]+)-([0-9]+)$/ !~ range_
-	    raise(NSC_Utils::BadLLNLHostlist, "bad range")
-	end
-	width = $1.length
-	low, high = $1.to_i, $2.to_i
-	if high <low
-	    raise(NSC_Utils::BadLLNLHostlist, "start > stop")
-	elsif high - low > NSC_Utils::LLNL_HOSTLIST_MAXSIZE
-	    raise(NSC_Utils::BadLLNLHostlist, "range too large")
-	end
-
-	results = (low .. high).collect { |i|
-	    "%s%0*d" % [ prefix, width, i ]
-	}
-	return results
-    end
-    module_function :__hostlist_expand_range
-
-end
-- 
GitLab